3232import java .util .LinkedList ;
3333import java .util .List ;
3434import java .util .Map ;
35+ import java .util .TreeMap ;
3536
3637import javax .servlet .ServletException ;
3738import javax .servlet .http .HttpServlet ;
3839import javax .servlet .http .HttpServletRequest ;
3940import javax .servlet .http .HttpServletResponse ;
4041import javax .servlet .http .Part ;
42+ import javax .xml .stream .XMLOutputFactory ;
43+ import javax .xml .stream .XMLStreamException ;
44+ import javax .xml .stream .XMLStreamWriter ;
4145
4246import org .httprpc .beans .BeanAdapter ;
4347
@@ -48,15 +52,18 @@ public abstract class WebService extends HttpServlet {
4852 private static final long serialVersionUID = 0 ;
4953
5054 private static class Resource {
51- public final HashMap <String , LinkedList <Method >> handlerMap = new HashMap <>();
52- public final HashMap <String , Resource > resources = new HashMap <>();
55+ private static List <String > order = Arrays .asList ("get" , "post" , "put" , "patch" , "delete" );
5356
54- public String name = null ;
57+ public final TreeMap <String , LinkedList <Method >> handlerMap = new TreeMap <>((verb1 , verb2 ) -> {
58+ int i1 = order .indexOf (verb1 );
59+ int i2 = order .indexOf (verb2 );
5560
56- @ Override
57- public String toString () {
58- return handlerMap .keySet ().toString () + "; " + resources .toString ();
59- }
61+ return Integer .compare ((i1 == -1 ) ? order .size () : i1 , (i2 == -1 ) ? order .size () : i2 );
62+ });
63+
64+ public final TreeMap <String , Resource > resources = new TreeMap <>();
65+
66+ public String key = null ;
6067 }
6168
6269 private Resource root = null ;
@@ -110,7 +117,7 @@ public void init() throws ServletException {
110117 throw new ServletException ("Invalid path component." );
111118 }
112119
113- child .name = component .substring (k );
120+ child .key = component .substring (k );
114121
115122 component = PATH_VARIABLE ;
116123 }
@@ -141,13 +148,23 @@ public void init() throws ServletException {
141148 @ Override
142149 @ SuppressWarnings ("unchecked" )
143150 protected void service (HttpServletRequest request , HttpServletResponse response ) throws ServletException , IOException {
151+ String verb = request .getMethod ().toLowerCase ();
152+ String pathInfo = request .getPathInfo ();
153+
154+ if (verb .equals ("get" ) && pathInfo == null ) {
155+ String queryString = request .getQueryString ();
156+
157+ if (queryString != null && queryString .equals ("description" )) {
158+ describeService (request , response );
159+ return ;
160+ }
161+ }
162+
144163 Resource resource = root ;
145164
146165 ArrayList <String > keyList = new ArrayList <>();
147166 HashMap <String , String > keyMap = new HashMap <>();
148167
149- String pathInfo = request .getPathInfo ();
150-
151168 if (pathInfo != null ) {
152169 String [] components = pathInfo .split ("/" );
153170
@@ -170,16 +187,16 @@ protected void service(HttpServletRequest request, HttpServletResponse response)
170187
171188 keyList .add (component );
172189
173- if (child .name != null ) {
174- keyMap .put (child .name , component );
190+ if (child .key != null ) {
191+ keyMap .put (child .key , component );
175192 }
176193 }
177194
178195 resource = child ;
179196 }
180197 }
181198
182- List <Method > handlerList = resource .handlerMap .get (request . getMethod (). toLowerCase () );
199+ List <Method > handlerList = resource .handlerMap .get (verb );
183200
184201 if (handlerList == null ) {
185202 super .service (request , response );
@@ -454,5 +471,124 @@ protected String getKey(int index) {
454471 protected String getKey (String name ) {
455472 return keyMap .get ().get (name );
456473 }
474+
475+ private void describeService (HttpServletRequest request , HttpServletResponse response ) throws IOException {
476+ response .setContentType (String .format ("text/html;charset=%s" , UTF_8 ));
477+
478+ XMLOutputFactory xmlOutputFactory = XMLOutputFactory .newInstance ();
479+
480+ try {
481+ XMLStreamWriter xmlStreamWriter = xmlOutputFactory .createXMLStreamWriter (response .getWriter ());
482+
483+ xmlStreamWriter .writeStartElement ("html" );
484+ xmlStreamWriter .writeStartElement ("body" );
485+
486+ TreeMap <Class <?>, String > structures = new TreeMap <>((type1 , type2 ) -> {
487+ return type1 .getSimpleName ().compareTo (type2 .getSimpleName ());
488+ });
489+
490+ describeResource (request .getServletPath (), root , structures , xmlStreamWriter );
491+
492+ for (Map .Entry <Class <?>, String > entry : structures .entrySet ()) {
493+ Class <?> type = entry .getKey ();
494+
495+ if (type == URL .class ) {
496+ continue ;
497+ }
498+
499+ String name = type .getSimpleName ();
500+
501+ xmlStreamWriter .writeStartElement ("h3" );
502+ xmlStreamWriter .writeAttribute ("id" , name );
503+ xmlStreamWriter .writeCharacters (name );
504+ xmlStreamWriter .writeEndElement ();
505+
506+ xmlStreamWriter .writeStartElement ("pre" );
507+ xmlStreamWriter .writeCharacters (entry .getValue ());
508+ xmlStreamWriter .writeEndElement ();
509+ }
510+
511+ xmlStreamWriter .writeEndElement ();
512+ xmlStreamWriter .writeEndElement ();
513+
514+ xmlStreamWriter .close ();
515+ } catch (XMLStreamException exception ) {
516+ throw new IOException (exception );
517+ }
518+ }
519+
520+ private void describeResource (String path , Resource resource , TreeMap <Class <?>, String > structures , XMLStreamWriter xmlStreamWriter ) throws XMLStreamException {
521+ if (!resource .handlerMap .isEmpty ()) {
522+ xmlStreamWriter .writeStartElement ("h2" );
523+ xmlStreamWriter .writeCharacters (path );
524+ xmlStreamWriter .writeEndElement ();
525+
526+ for (Map .Entry <String , LinkedList <Method >> entry : resource .handlerMap .entrySet ()) {
527+ for (Method method : entry .getValue ()) {
528+ xmlStreamWriter .writeStartElement ("pre" );
529+
530+ xmlStreamWriter .writeCharacters (entry .getKey ().toUpperCase () + " (" );
531+
532+ Parameter [] parameters = method .getParameters ();
533+
534+ for (int i = 0 ; i < parameters .length ; i ++) {
535+ Parameter parameter = parameters [i ];
536+
537+ if (i > 0 ) {
538+ xmlStreamWriter .writeCharacters (", " );
539+ }
540+
541+ xmlStreamWriter .writeCharacters (getName (parameter ) + ": " );
542+
543+ Type type = parameter .getParameterizedType ();
544+
545+ if (type == URL .class ) {
546+ xmlStreamWriter .writeCharacters ("file" );
547+ } else if (type instanceof ParameterizedType
548+ && ((ParameterizedType )type ).getRawType () == List .class
549+ && ((ParameterizedType )type ).getActualTypeArguments ()[0 ] == URL .class ) {
550+ xmlStreamWriter .writeCharacters ("[file]" );
551+ } else {
552+ xmlStreamWriter .writeCharacters (BeanAdapter .describe (type , structures ));
553+ }
554+ }
555+
556+ xmlStreamWriter .writeCharacters (") -> " );
557+
558+ Type type = method .getGenericReturnType ();
559+ Response response = method .getAnnotation (Response .class );
560+
561+ if ((type == Void .class || type == Void .TYPE ) && response != null ) {
562+ xmlStreamWriter .writeCharacters (response .value ());
563+ } else {
564+ String description = BeanAdapter .describe (type , structures );
565+
566+ if (structures .containsKey (type )) {
567+ xmlStreamWriter .writeStartElement ("a" );
568+ xmlStreamWriter .writeAttribute ("href" , "#" + description );
569+ xmlStreamWriter .writeCharacters (description );
570+ xmlStreamWriter .writeEndElement ();
571+ } else {
572+ xmlStreamWriter .writeCharacters (description );
573+ }
574+ }
575+
576+ xmlStreamWriter .writeEndElement ();
577+ }
578+ }
579+ }
580+
581+ for (Map .Entry <String , Resource > entry : resource .resources .entrySet ()) {
582+ String component = entry .getKey ();
583+
584+ Resource child = entry .getValue ();
585+
586+ if (child .key != null ) {
587+ component += ":" + child .key ;
588+ }
589+
590+ describeResource (path + "/" + component , child , structures , xmlStreamWriter );
591+ }
592+ }
457593}
458594
0 commit comments