The Source for Java Technology Collaboration
User: Password:



Marc Hadley's Blog

March 2006 Archives


RESTful Web Service Endpoints in JAX-WS

Posted by mhadley on March 21, 2006 at 11:05 AM | Permalink | Comments (2)

In previous entries I've covered use of the client side JAX-WS Dispatch<T> interface to consume RESTful Web services. In this entry I'd like to demonstrate use of the Provider<T> interface to develop a RESTful endpoint and then publish that endpoint using the Endpoint API. To try out the code in this entry you'l need to grab a recent nightly build of JAX-WS since the functionality was not implemented in JWSDP 2.0.

A JAX-WS Provider-based endpoint has to implement the javax.xml.ws.Provider interface:

public interface Provider<T> {
  public T invoke(T request);
}

You also have to pick the service mode in which the endpoint will operate and annotate the endpoint class to allow the JAX-WS runtime to identify the correct class to publish. Here's the start of our endpoint class (I'm going to interleave code and narrative but if you extract and stitch together the code segments below you'll end up with a complete class):

@WebServiceProvider 
@ServiceMode(value=Service.Mode.PAYLOAD)
public class SystemPropertiesProvider  implements Provider<Source> {

    @Resource
    protected WebServiceContext context;

Arun has an informative blog entry that discusses the difference between PAYLOAD and MESSAGE mode and describes which types of Provider work in which mode. The context member variable is initialized by the JAX-WS runtime using resource injection and provides access to the message context which contains various properties that an endpoint may find useful - more on this below.

The endpoint we'll develop here will allow a client to query the Java system properties of the endpoint implementation class, here is the implementation of the Provider.invoke method.

  public Source invoke(Source source) {
    Map<String, List<String>> query = getQueryParameters();
    String replyData = getSystemInfoAsXML(query.get("property"));
    StreamSource reply = new StreamSource(new StringReader(replyData));
    return reply;
  }

The getQueryParameters method obtains the HTTP query string using the appropriate message context property (recall the context member varaible introduced above) and then parses that string into a Map.

  protected Map<String, List<String>> getQueryParameters() {
    MessageContext msgCtx = context.getMessageContext();
    String queryString = (String)msgCtx.get(MessageContext.QUERY_STRING);
    HashMap<String, List<String>> params = 
      new HashMap<String, List<String>>();
    for (String s: queryString.split("&")) {
      String[] keyVal = s.split("=");        
      try {
        String key = URLDecoder.decode(keyVal[0], "UTF-8");
        String val = URLDecoder.decode(keyVal[1], "UTF-8");
        if (params.get(key) == null) {
          ArrayList<String> list = new ArrayList<String>();
          list.add(val);
          params.put(key,list);
        }
        else {
          ArrayList<String> list =
            (ArrayList<String>)params.get(key);
          list.add(val);
        }
      } catch (UnsupportedEncodingException ex) {
        ex.printStackTrace();
      }
    }
    return params;
  }

Its entirely possible that the above functionality already exists in some other class but, if it does, I didn't find it in the brief search I made. The getSystemInfoAsXML method takes a list of Java system property names and creates an XML document containing the name and value of each parameter.

  protected String getSystemInfoAsXML(List properties) {
    StringBuffer buf = new StringBuffer();
    buf.append("<?xml version='1.0' encoding='UTF-8'?>\n");
    buf.append("<?xml-stylesheet href='http://127.0.0.1/info.xsl' type='text/xsl'?>\n");
    buf.append("<properties>");

    for(String property: properties) {
      buf.append("<property name='");
      buf.append(property);
      buf.append("'>");
      buf.append(System.getProperty(property));
      buf.append("</property>");
    }

    buf.append("</properties>");
    return buf.toString();
  }

The xml-stylesheet processing instruction in the generated XML document will allow a modern browser to display the generated document in a human readable form; the stylesheet is included at the end of this entry for completeness. You'll have to put the stylesheet on a Web server somewhere and modify this line to point to the location you chose.

If we wanted to deploy the endpoint in a Java EE container then the above is all the code needed. However, for this example, we'll use the new JAX-WS Endpoint API to publish the endpoint without a container (this is really intended for lightweight endpoints and testing, don't throw out your application server). To do this we add a main method to the class.

  public static void main(String[] args) {
    Endpoint e = Endpoint.create(HTTPBinding.HTTP_BINDING,
      new SystemPropertiesProvider());
    e.publish("http://127.0.0.1:8084/system/info");
    System.out.println("Endpoint running");
    try {
      Thread.sleep(30000);
    } catch (InterruptedException ex) {
    }
    e.stop();
    System.out.println("Endpoint stopped");
  }
}

In the above we publish the endpoint for 30 seconds and then stop it, you could also wait for a keypress in the main thread or use some other scheme to determine when to stop the endpoint.

Now, stitch together the code segments from above, compile and run the class. Fire up a browser, point it to http://127.0.0.1:8084/system/info?property=os.version&property=os.name and see what OS the Java runtime thinks the endpoint is hosted on.

Here's the XSL stylesheet that converts the XML document into a HTML table:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:template match="/">
    <html><body><table border="1">
      <tr>
        <th>Property</th>
        <th>Value</th>
      </tr>
      <xsl:for-each select="/properties/property">
        <tr>
          <td>
            <xsl:value-of select="@name"/>
          </td>
          <td>
            <xsl:value-of select="."/>
          </td>
        </tr>
      </xsl:for-each>
    </table></body></html>
  </xsl:template>
</xsl:stylesheet>


RESTful Web Services with JAX-WS and JAXB

Posted by mhadley on March 08, 2006 at 11:44 AM | Permalink | Comments (1)

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());
}




Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds