1919import stroom .util .ConsoleColour ;
2020import stroom .util .logging .LogUtil ;
2121import stroom .util .shared .IsAdminServlet ;
22+ import stroom .util .shared .NullSafe ;
2223
2324import io .dropwizard .core .setup .Environment ;
24- import io .vavr .Tuple ;
25- import io .vavr .Tuple3 ;
2625import jakarta .inject .Inject ;
2726import jakarta .servlet .Servlet ;
27+ import jakarta .servlet .ServletException ;
28+ import jakarta .servlet .http .HttpServlet ;
29+ import jakarta .servlet .http .HttpServletRequest ;
30+ import jakarta .servlet .http .HttpServletResponse ;
2831import org .apache .commons .lang3 .StringUtils ;
2932import org .eclipse .jetty .servlet .ServletContextHandler ;
3033import org .eclipse .jetty .servlet .ServletHolder ;
3134import org .slf4j .Logger ;
3235import org .slf4j .LoggerFactory ;
3336
37+ import java .io .IOException ;
3438import java .util .Comparator ;
3539import java .util .HashSet ;
3640import java .util .List ;
@@ -45,6 +49,7 @@ public class AdminServlets {
4549 private static final Logger LOGGER = LoggerFactory .getLogger (AdminServlets .class );
4650
4751 private static final String SERVLET_PATH_KEY = "servletPath" ;
52+ private static final String MENU_SERVLET_PATH = "/menu" ;
4853
4954 private final Environment environment ;
5055 private final Set <IsAdminServlet > adminServlets ;
@@ -85,39 +90,32 @@ public void register() {
8590 .orElse (0 );
8691
8792 // Register all the path specs for each servlet class in pathspec order
88- adminServlets .stream ()
93+ final List < AdminServletInfo > servletInfoList = adminServlets .stream ()
8994 .flatMap (servlet ->
9095 servlet .getPathSpecs ().stream ()
91- .map (partialPathSpec -> {
96+ .map (pathSpec -> {
9297 final String name = servlet .getClass ().getName ();
93- final String servletPath = Objects .requireNonNull (partialPathSpec );
94- return Tuple . of (servlet , name , servletPath );
98+ final String servletPath = Objects .requireNonNull (pathSpec );
99+ return new AdminServletInfo (servlet , name , servletPath );
95100 }))
96- .sorted (Comparator .comparing (Tuple3 ::_3 ))
97- .forEach (tuple3 -> {
98- final IsAdminServlet isAdminServlet = tuple3 ._1 ();
99- final String name = tuple3 ._2 ();
100- final String fullPathSpec = tuple3 ._3 ();
101-
102- addServlet (
103- servletContextHandler ,
104- allPaths ,
105- maxNameLength ,
106- isAdminServlet ,
107- name ,
108- fullPathSpec );
109- });
101+ .sorted (Comparator .comparing (AdminServletInfo ::partialPathSpec ))
102+ .toList ();
103+
104+ addMenuServlet (servletContextHandler , allPaths , maxNameLength , servletInfoList );
105+
106+ servletInfoList .forEach (servletInfo ->
107+ addServlet (servletContextHandler , allPaths , maxNameLength , servletInfo ));
110108 }
111109
112110 private void addServlet (final ServletContextHandler servletContextHandler ,
113111 final Set <String > allPaths ,
114112 final int maxNameLength ,
115- final IsAdminServlet isAdminServlet ,
116- final String name ,
117- final String partialPathSpec ) {
113+ final AdminServletInfo adminServletInfo ) {
118114
119115 final String contextPath = servletContextHandler .getContextPath ();
120- final String fullPathSpec = contextPath + partialPathSpec ;
116+ final String fullPathSpec = contextPath + adminServletInfo .partialPathSpec ;
117+ final String name = adminServletInfo .servletName ;
118+ final IsAdminServlet isAdminServlet = adminServletInfo .adminServlet ;
121119 if (allPaths .contains (fullPathSpec )) {
122120 LOGGER .error ("\t {} => {} {}" ,
123121 StringUtils .rightPad (name , maxNameLength , " " ),
@@ -137,7 +135,127 @@ private void addServlet(final ServletContextHandler servletContextHandler,
137135 throw new RuntimeException (LogUtil .message ("Injected class {} is not a Servlet" ,
138136 isAdminServlet .getClass ().getName ()));
139137 }
140- servletContextHandler .addServlet (servletHolder , partialPathSpec );
138+ servletContextHandler .addServlet (servletHolder , adminServletInfo . partialPathSpec );
141139 allPaths .add (fullPathSpec );
142140 }
141+
142+ private void addMenuServlet (final ServletContextHandler servletContextHandler ,
143+ final Set <String > allPaths ,
144+ final int maxNameLength ,
145+ final List <AdminServletInfo > adminServletInfoList ) {
146+
147+
148+ final String contextPath = servletContextHandler .getContextPath ();
149+ final String fullPathSpec = contextPath + MENU_SERVLET_PATH ;
150+ final String name = MenuServlet .class .getName ();
151+
152+ final MenuServlet menuServlet = new MenuServlet (contextPath , adminServletInfoList );
153+
154+ if (allPaths .contains (fullPathSpec )) {
155+ //noinspection LoggingSimilarMessage
156+ LOGGER .error ("\t {} => {} {}" ,
157+ StringUtils .rightPad (name , maxNameLength , " " ),
158+ fullPathSpec ,
159+ ConsoleColour .red ("**Duplicate path**" ));
160+ throw new RuntimeException (LogUtil .message ("Duplicate servlet path {}" , fullPathSpec ));
161+ }
162+ //noinspection LoggingSimilarMessage
163+ LOGGER .info ("\t {} => {}" ,
164+ StringUtils .rightPad (name , maxNameLength , " " ),
165+ fullPathSpec );
166+
167+ final ServletHolder servletHolder = new ServletHolder (name , menuServlet );
168+ servletContextHandler .addServlet (servletHolder , MENU_SERVLET_PATH );
169+ allPaths .add (fullPathSpec );
170+ }
171+
172+
173+ // --------------------------------------------------------------------------------
174+
175+
176+ private record AdminServletInfo (IsAdminServlet adminServlet ,
177+ String servletName ,
178+ String partialPathSpec ) {
179+
180+ }
181+
182+
183+ // --------------------------------------------------------------------------------
184+
185+
186+ private static class MenuServlet extends HttpServlet {
187+
188+ private final String html ;
189+
190+ private MenuServlet (final String contextPath ,
191+ final List <AdminServletInfo > servletInfoList ) {
192+ final List <AdminServletInfo > sortedInfo = NullSafe .stream (servletInfoList )
193+ .sorted (Comparator .comparing (adminServletInfo ->
194+ adminServletInfo .adminServlet .getDisplayName ()))
195+ .toList ();
196+
197+ final StringBuilder stringBuilder = new StringBuilder ();
198+ writeHtmlHeader (stringBuilder );
199+ stringBuilder .append ("<h1>Admin Servlets Menu</h1>" );
200+ stringBuilder .append ("<ul>" );
201+ appendMenuItem (stringBuilder , "Dropwizard Admin Servlet" , contextPath );
202+
203+ for (final AdminServletInfo servletInfo : sortedInfo ) {
204+ appendMenuItem (
205+ stringBuilder ,
206+ servletInfo .adminServlet .getDisplayName (),
207+ contextPath + servletInfo .partialPathSpec );
208+ }
209+ stringBuilder .append ("</ul>" );
210+ writeHtmlFooter (stringBuilder );
211+ html = stringBuilder .toString ();
212+ }
213+
214+ @ Override
215+ protected void doGet (final HttpServletRequest req , final HttpServletResponse resp )
216+ throws ServletException , IOException {
217+ resp .setContentType ("text/html" );
218+ resp .getWriter ().print (html );
219+ }
220+
221+ private static void appendMenuItem (final StringBuilder stringBuilder ,
222+ final String name ,
223+ final String url ) {
224+ stringBuilder .append ("<li>" )
225+ .append ("<a href=\" " )
226+ .append (url )
227+ .append ("\" >" )
228+ .append (name )
229+ .append ("</a>" )
230+ .append ("</li>" );
231+ }
232+
233+ private static void writeHtmlHeader (final StringBuilder stringBuilder ) {
234+ stringBuilder .append ("""
235+ <!DOCTYPE html>
236+ <html>
237+ <head>
238+ <title>Admin Servlets Menu</title>
239+ <style>
240+ body {
241+ font-family: arial, tahoma, verdana;
242+ }
243+ li {
244+ line-height: 1.75em;
245+ }
246+ p {
247+ margin-top: 0.5em;
248+ margin-bottom: 0.5em;
249+ }
250+ </style>
251+ </head>
252+ <body>""" );
253+ }
254+
255+ private static void writeHtmlFooter (final StringBuilder stringBuilder ) {
256+ stringBuilder .append ("""
257+ </body>
258+ </html>""" );
259+ }
260+ }
143261}
0 commit comments