Skip to main content

RESTful Web Services with JAX-WS and JAXB

Posted by mhadley on March 8, 2006 at 11:44 AM PST

In a previous blog entry I described how to use the dynamic client functionality of JAX-WS in combination with JAXP XPath capabilities to query a service providing data using XML/HTTP. In this entry I'd like to focus on how to use JAXB instead of XPath to get strongly typed access to the same data.

To use JAXB we have to obtain an XML schema for the documents that will be exchanged. The documentation for the Yahoo News Search service includes a link to an XML schema for the results produced by the service. The JAXB tool time components transform an XML schema file into a set of Java classes that can be used to interact with an instance of a document that conforms to the schema. The JAXB runtime components take care of converting between the XML serialization of the data and its in-memory Java class equivalent.

To integrate JAXB we have to run the xjc tool against the XML schema and then compile the resulting Java classes into the project. Using a standard NetBeans project, this is easily accomplished by overriding the -pre-compile Ant task by adding the following lines to the build.xml file:

<property name="jaxb.home" value="/path/to/jwsdp-2.0/jaxb" />
<taskdef name="xjc" classname="com.sun.tools.xjc.XJCTask">
  <classpath>
    <fileset dir="${jaxb.home}" includes="lib/*.jar" />
  </classpath>
</taskdef>
 
<target name="-pre-compile">
  <echo message="Compiling the schema..." />
  <xjc schema="NewsSearchResponse.xsd" package="com.yahoo.search" target="gen-src">
    <produces dir="gen-src/com/yahoo/search" includes="**/*.java" />
  </xjc>
</target>

Note that the above will produce a new directory separate to the application source code. You'll need to edit the NetBeans project preferences to include this directory as a source code directory to ensure the generated code is compiled into the project.

As before the first step in the application is to add a part for the Yahoo News Search service:

URI nsURI = new URI("urn:yahoo:yn");
QName serviceName = new QName("yahoo",nsURI.toString());
QName portName = new QName("yahoo_port",nsURI.toString());
Service s = Service.create(serviceName);
URI address = new URI("http", null, "api.search.yahoo.com", 80,
  "/NewsSearchService/V1/newsSearch",
  "appid=jaxws_restful_sample&type=all&results=10&sort=date&query=java",
  null);
s.addPort(portName, HTTPBinding.HTTP_BINDING, address.toString());

As before, note that the URI constructed above contains a set of input parameters to the search service. The appid parameter identifies the application to Yahoo, if you are writing a new application you should register a new ID for it here. In this instance we are creating a URI that will search for news about Java and return at most 10 results sorted by date.

Once the port is created we can use the Service instance to create a Dispatch instance:

JAXBContext jbc = JAXBContext.newInstance( "com.yahoo.search" );
Dispatch<Object> d = s.createDispatch(portName, jbc, Service.Mode.PAYLOAD);
Map<String, Object> requestContext = d.getRequestContext();
requestContext.put(MessageContext.HTTP_REQUEST_METHOD, new String("GET"));

Note the following differences in the code compared the previous version that used the XPath APIs:

  • We create a JAXBContext object using the same Java package that we instructed the xjc tool to use for code generation above.
  • The JAXBContext object is passed in when creating the Dispatch object and will be used by the Dispatch object for marshalling and unmarshalling between XML and Java.

Now that we have a Dispatch object we can execute a query and iterate through the results:

ResultSet rs = (ResultSet)d.invoke(null);
for (ResultType r: rs.getResult()) {
  System.out.printf("%s: (%s)\n",r.getTitle(),r.getClickUrl());
}

The ResultSet and ResultType are JAXB generated types that correspond to types in the XML schema we fed to the xjc tool above.

Unfortunately, due to a bug in the JAX-WS implementation included in JWSDP 2.0, the above code will not work and requires the following work-around. I'm told this is fixed in the latest builds but haven't confirmed that personally.

Unmarshaller u = jbc.createUnmarshaller();
ResultSet rs = (ResultSet)u.unmarshal((StreamSource)d.invoke(null));
for (ResultType r: rs.getResult()) {
  System.out.printf("%s: (%s)\n",r.getTitle(),r.getClickUrl());
}
Related Topics >>