Skip to main content

Automatically generating WADL in Spring MVC REST application

Last time we have learnt the basics of WADL. The language itself is not as interesting to write a separate article about it, but the title of this article reveals why we needed that knowledge.

Many implementations of JSR 311: JAX-RS: The Java API for RESTful Web Services provide runtime WADL generation out-of-the-box: Apache CXF, Jersey and Restlet. RESTeasy still waiting. Basically these frameworks examine Java code with JSR-311 annotations and generate WADL document available under some URL. Unfortunately Spring MVC not only does not implement the JSR-311 standard (see: Does Spring MVC support JSR 311 annotations?), but it also does not generate WADL for us (see: SPR-8705), even though it is perfectly suited for exposing REST services.

For various reasons I started developing server-side REST services with Spring MVC and after a while (say, thirdy resources later) I started to get a bit lost. I really needed a way to catalogue and document all available resources and operations. WADL seemed like a great choice.

Fortunately Spring framework is open for extension and it is easy to add new features based on existing infrastructure if you are willing to dig through the code for a while. In order to generate WADL I needed a list of URIs that an application handles, what HTTP methods are implemented and – ideally – which Java method handles each one of them. Obviously Spring does that job already somewhere during boot-strapping MVC DispatcherServlet - scanning for @Controller, @RequestMapping, @PathVariable, etc. - so it seems smart to reuse that information rather then performing the job again.

Guess what, it looks like all the information we need is kept in an oddly named RequestMappingHandlerMapping class. Here is a debugger screenshot just to give you an overview how rich information is available:


But it gets even better: RequestMappingHandlerMapping is actually a Spring bean which you can easily inject and use:

@Controller
class WadlController @Autowired()(mapping: RequestMappingHandlerMapping) {

    @RequestMapping(method = Array(GET))
    @ResponseBody def generate(request: HttpServletRequest) = new WadlApplication()

}
That's right, we will use yet another Spring MVC controller to generate WADL document. Last time we managed to generate JAXB classes representing WADL document (after all WADL is an XML file) so by returning empty instance of WadlApplication we are actually returning empty, but valid WADL:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<application xmlns="http://wadl.dev.java.net/2009/02"/>
I won't explain the details of the implementation (full source code is available including sample application). It was basically a matter of rewriting Spring models to WADL classes. If you are interested, have a look at WadlGenerator.scala that is a central point of the solution and test cases. Here is one of them:
test("should add parameter info for template parameter in URL") {
    given("")
    val mapping = Map(
        mappingInfo("/books", GET) -> handlerMethod("listBooks"),
        mappingInfo("/books/{bookId}", GET) -> handlerMethod("readBook")
    )

    when("")
    val wadl = generate(mapping)

    then("")
    assertXMLEqual(wadlHeader + """
        <resource path="books">
            <method name="GET">
                <doc title="com.blogspot.nurkiewicz.springwadl.TestController.listBooks"/>
            </method>
            <resource path="{bookId}">
                <param name="bookId" required="true" />
                <method name="GET">
                    <doc title="com.blogspot.nurkiewicz.springwadl.TestController.readBook"/>
                </method>
            </resource>
        </resource>
    """ + wadlFooter, wadl)
}
Unfortunately I was too lazy to correctly name given/when/then blocks. But tests should be pretty readable.

The only technical difficulty I would like to mention was translating flat URI patterns provided by Spring infrastructure to hierarchical WADL objects (basically a tree). Here is a simplified version of this problem: having a list of URI patterns as follows:
/books
/books/{bookId}
/books/{bookId}/reviews
/books/best-sellers
/readers
/readers/{readerId}
/readers/{readerId}/account/new-password
/readers/active
/readers/passive
Generate the following tree data structure:


Of course the data structure is as simple as a Node object holding a label and a children list of Nodes. Not really that challenging, but probably an interesting CodeKata.

So what is it all about with this WADL? Is the XML really more readable and helps in managing REST-heavy applications? I wouldn't even bother playing with it if not the great soapUI support for WADL. The WADL generated for an example application I pushed as well can be easily imported to soapUI:


Two features are worth mentioning. First of all soapUI displays a tree of REST resources (as opposed to flat list of operations when WSDL is imported). Next to every HTTP method there is a corresponding Java method that handles it (this can be disabled) for troubleshooting and debugging purposes. Secondly, we can pick any HTTP method/resource and invoke it. Based on WADL description soapUI will create user-friendly wizard where one can input parameters. Default values are automatically populated. When we are done, the application will generate HTTP request with correct URL and content, displaying the response when it arrives. Really helpful!

By the way have you noticed the max and page query parameters? Our small library uses reflection to find @RequestParam annotations so e.g. the following controller:
@Controller
@RequestMapping(value = Array("/book/{bookId}/review"))
class ReviewController @Autowired()(reviewService: ReviewService) {

    @RequestMapping(method = Array(GET))
    @ResponseBody def listReviews(
            @RequestParam(value = "page", required = false, defaultValue = "1") page: Int,
            @RequestParam(value = "max", required = false, defaultValue = "20") max: Int) =
        new ResultPage(reviewService.listReviews(new PageRequest(page - 1, max)))

    //...

}
will be translated into WADL-compatible description:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<application xmlns="http://wadl.dev.java.net/2009/02">
  <doc title="Spring MVC REST appllication"/>
  <resources base="http://localhost:8080/api">
    <resource path="book">
      <!-- -->
      <resource path="{bookId}">
        <param required="true" style="template" name="bookId"/>
        <!-- -->
        <resource path="review">
          <method name="GET">
            <doc title="com.blogspot.nurkiewicz.web.ReviewController.listReviews"/>
            <request>
              <param required="false" default="1" style="query" name="page"/>
              <param required="false" default="20" style="query" name="max"/>
            </request>
        </resource>
      </resource>
    </resource>
  </resource
</application>
Hope you had fun with this small library I have written. Feel free to include it in your project and don't hesitate to report bugs. Full source code under Apache license is available on GitHub: https://github.com/nurkiewicz/spring-rest-wadl.

Comments

  1. Thank you for a great post.

    How can I use the WadlGenerator from a Java controller?

    ReplyDelete
    Replies
    1. I knew this question will pop-up soon :-). I suspect that when compiled it can be used just like any other Java class, however you will have to add not-so-small scala-lang.jar to your CLASSPTH. I will have a look next week, maybe the API can be slightly improved to be easier to use in Java.

      Delete
    2. This comment has been removed by the author.

      Delete
  2. I like this. I will give it a try and let you know. I already implemented your chain of responsibility pattern with some minor changes to suit my requirement. Thanks for posting this.

    ReplyDelete
  3. Hi,
    First of all, thank you for an awesome article! I started digging into your code and was wondering how to embed grammar content in the generated wadl?

    I tried creating a WadlGrammar instance and added the xml schema(its a string generated using JAXB on the fly from the pojos used in request/response). Next, I tried serializing this grammar inline with the generated wadl application using:

    wadlApp.withGrammar(new WadlGrammars.withAny(xmlSchemaString))

    However, JAXB complains when I serialize this using the controller. The only way around this is to use grammars.withInclude(new WadlInclude.withHref("href")).

    Is there any other way to inline the xml schema ?

    Thank you!

    ReplyDelete
    Replies
    1. I did a quick research on your issues. For sure including grammars in WADL would be great. Looks like you are able to generate XML schema grammar from POJOs (how?), which is awesome. Unfortunately, you cannot simply say:

      new WadlGrammars.withAny("<schema>...</schema>")

      this is not how "any" works in JAXB. Instead you must provide a reference to any JAXB-annotated object. This means that you would have to parse the string containing serialized schema into JAXB objects and then pass root object to 'withAny()'. A little bit cumbersome and also I feel like referencing an external file is actually a better approach as you can maintain them separately. Of course if you manage to overcome these problems, I encourage you to contribute your changes so I can incorporate them on GitHub.

      Delete
  4. I have create a full Java Controller based on your articles and codes who do the same as your scala Controller except for the tree structure. Thank you for your article.
    http://tuxgalaxy.blogspot.com/2012/03/spring3-et-wadl-generation.html

    ReplyDelete
    Replies
    1. Thank you for your contribution. What do you think about pushing it to GitHub so that others can improve your implementation (you are mentioning there is no tree support yet)? I can give you access to my repository. Also please try running your implementation against my unit tests.

      Delete
  5. Hi,

    I tried the example given in this blog http://tuxgalaxy.blogspot.com/2012/03/spring3-et-wadl-generation.html to create WADL using RequestMappingHandlerMapping, derived by you.
    At the time of tomcat start up i saw lot of debug for Mapping for the service and path. There is no problem in tomcat start up. But some how my spring MVC application is not comming up.All my servivcs/ api throwing 404. Do you know why?

    Snippet of log

    13:04:03.495 [main] INFO o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/my-path/id/{abc}/email/{abc}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public com.xxx.iii.model.MyData com.xxx.yyy.controller.MyReportController.getReport(java.lang.String,java.lang.String) throws com.xxx.yyy.error.exception.MyException
    13:04:03.496 [main] INFO o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/application.wadl],methods=[GET],params=[],headers=[],consumes=[],produces=[application/xml],custom=[]}" onto public net.java.dev.wadl.WadlApplication com.xxx.yyy.controller.WadlController.generateWadl(javax.servlet.http.HttpServletRequest)

    ReplyDelete
    Replies
    1. I could able to solve the problem by moving to a method in a different controller. By this post http://forum.springsource.org/showthread.php?121084-Unable-to-Autowire-RequestMappingHandlerMapping-in-Controller it means we should not do context and component scan together.

      Delete
  6. Can we use this directly in our java project or we have to create java counterpart project of this ?

    ReplyDelete
    Replies
    1. Scala is highly compatible with Java so it should just work (although I haven't tested it). Also you would have to include scala.jar in your CLASSPATH, which is quite big. Seems like someone already translated this controller to Java, but I haven't tried it.

      Delete
  7. Very interesting how WADL has resurfaced in importance because of the resurgence of public api management solutions. WADL provides a nice way to tell the tools about the interface so that they can let developers play with the RESTful interfaces via a generic tool.

    Robert
    http://soa-probe.com

    ReplyDelete
  8. Does this tool support complex user defined types and @RequestBody?

    ReplyDelete
  9. How can we generate XSD for the objects?

    ReplyDelete

Post a Comment