Skip to main content

Mapping WADL to Java

Posted by mhadley on May 25, 2006 at 12:25 PM PDT

In recent entries I've described the REST (or Web Style) oriented features of JAX-WS and touched on a new language, WADL, that aims to provide a description of Web style services. In this entry I'll bring together these two threads to show how a language like WADL can be used to generate strongly-typed client-side stubs layered on JAX-WS and JAXB.

One of the key differences between Web-style and the style commonly associated with WS-* is the inversion of the balance between Web resources (something identified by a URI) and methods that can be applied to those resources. In the former style you often find a single URI (or a port in WSDL terminology) that exposes many custom methods described by a WSDL port type. In the Web-style you find many URIs, each of which supports a subset of a few standard HTTP methods.

The WS-* style maps quite naturally to a single Java class/interface per WSDL port type that exposes Java methods corresponding to each WSDL method. This is the approach taken by JAX-WS which describes in detail how to map between a WSDL port type and a Java class/interface (see chapters 2 and 3). When considering Web-style however, the mapping to Java is less obvious but it seems reasonable to try to preserve the hierarchical nature of HTTP URIs while maintaining the mapping between a Web resource and a Java class/interface. There are a couple of candidate hierarchical structures in the Java language: packages and nested inner classes. Given that the relationship between resources isn't always obvious it seems prudent to avoid member classes which require an instance of the parent class for instantiation. That leaves packages or nested static member classes, I chose the latter after a fair bit of back-and-forth but I think either approach would work.

Lets look at an example. The following WADL file describes the Yahoo! News Search service:

<?xml version="1.0"?>
<application xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:yn="urn:yahoo:yn"
  xmlns:ya="urn:yahoo:api"
  xmlns="http://research.sun.com/wadl">

  <grammars>
    <include href="NewsSearchResponse.xsd"/>
    <include href="NewsSearchError.xsd"/>
  </grammars>
 
  <resources base="http://api.search.yahoo.com/NewsSearchService/V1/">
    <resource uri="newsSearch">
      <method href="#search"/>
    </resource>
  </resources>
   
  <method name="GET" id="search">
    <request>
      <query_variable name="appid" type="xsd:string" required="true"/>
      <query_variable name="query" type="xsd:string" required="true"/>
      <query_variable name="type" type="xsd:string"/>
      <query_variable name="results" type="xsd:int"/>
      <query_variable name="start" type="xsd:int"/>
      <query_variable name="sort" type="xsd:string"/>
      <query_variable name="language" type="xsd:string"/>
    </request>
    <response>
      <representation mediaType="application/xml" element="yn:ResultSet"/>
      <fault id="SearchError" status="400" mediaType="application/xml"
        element="ya:Error"/>
    </response>
  </method>

</application>

The service actually supports a few more optional query parameters but I've omitted those to keep the length of the example down. The WADL file describes a single resource (http://api.search.yahoo.com/NewsSearchService/V1/newsSearch) that supports the HTTP GET method with a set of URI query parameters and returns an XML document whose root element is either yn:ResultSet or ya:Error. The WADL file includes two XML Schema files provided by Yahoo! that define the content models for these two elements. Lets see how this looks when mapped to a client-side Java stub:

package com.yahoo.search;

public class Endpoint {

  ...

  public static class NewsSearch {

    public NewsSearch()
      throws JAXBException
    {
      ...
    }

    public ResultSet getAsResultSet(String appid, String query)
      throws SearchError
    {
      ...
    }

    public ResultSet getAsResultSet(String appid, String query, String type,
      Integer results, Integer start, String sort, String language)
      throws SearchError
    {
      ...
    }
  }
}

I've omitted the implementation code as I want to concentrate on the structure of the generated code rather than implementation details. The first thing to note is the top level class Endpoint has no methods, it is there purely as a container for nested static inner classes mapped from resources in the WADL file. In this example there is a single nested class (NewsSearch) corresponding to the single resource in the WADL file, if this resource had sub-resources then these would be represented using nested static inner classes of NewsSearch. The constructor of the NewsSearch class takes no arguments in this example; if the resource had any variable path components (see <path_variable> in WADL), the constructor would include parameters that provide the value of each variable component so that the complete URI of the corresponding resource can be generated when the class is instantiated.

Once an instance of NewsSearch has been obtained, its methods may be used to invoke the news search service. In this example there are two variants of the GET method described in the WADL file: for convenience the first includes only the query parameters marked as required in the WADL file, the second includes both required and optional query parameters. Both methods return an instance of the same ResultSet class, this is a JAXB-generated class corresponding to the yn:ResultSet XML element. At first sight the Java method naming might seem a little odd since the name of the returned class is duplicated within the method name, this is done to handle the case where a resource supports multiple different representations of the same resource (not shown here) since two Java methods with the same name in the same class cannot differ only by return type.

Notice also that both methods throw SearchError, this is a generated exception class that follows the pattern used by JAX-WS for WSDL declared faults:

package com.yahoo.search;

public class SearchError
    extends Exception
{
    protected Error m_faultInfo;

    public SearchError(String message, Error faultInfo) {
        super(message);
        m_faultInfo = faultInfo;
    }

    public Error getFaultInfo() {
        return m_faultInfo;
    }
}

where Error is a JAXB-generated class corresponding to the ya:Error XML element.

Using this approach we can write client code for the Yahoo! News Search service as follows:

Endpoint.NewsSearch s = new Endpoint.NewsSearch();
ResultSet results = s.getAsResultSet("jaxws_restful_sample", "java");
for (ResultType result: results.getResult()) {
    System.out.println(result.getTitle()+" ("+result.getClickUrl()+")");
}

Hopefully you'll agree that this is a distinct improvement when compared to using JAX-WS and JAXB without code generation though, as is often the case, you sacrifice ultimate flexibility and increase the coupling between client and server as the price for such convenience.

I'm currently working on an implementation of a WADL to Java command line tool and Ant plug-in using the techniques described above. Its currently still a prototype but it produces working code for a variety of interesting WADL feature combinations and I hope to release something people can play with soon. In the meantime, if you are interested in this kind of tooling and/or have a WADL file you'd like me to add to my test set then please email me at firstname.lastname@sun.com (substituting the relevant values).

Related Topics >>