Automatically generating WADL in Spring MVC REST application
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:@ControllerThat'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
class WadlController @Autowired()(mapping: RequestMappingHandlerMapping) {
@RequestMapping(method = Array(GET))
@ResponseBody def generate(request: HttpServletRequest) = new WadlApplication()
}
WadlApplication
we are actually returning empty, but valid WADL:<?xml version="1.0" encoding="UTF-8" standalone="yes"?>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
<application xmlns="http://wadl.dev.java.net/2009/02"/>
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") {Unfortunately I was too lazy to correctly name
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)
}
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:
/booksGenerate the following tree data structure:
/books/{bookId}
/books/{bookId}/reviews
/books/best-sellers
/readers
/readers/{readerId}
/readers/{readerId}/account/new-password
/readers/active
/readers/passive
Of course the data structure is as simple as a
Node
object holding a label and a children
list of Node
s. 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: @Controllerwill be translated into WADL-compatible description:
@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)))
//...
}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>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. Tags: rest, soapui, spring, spring mvc, wadl
<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>