Skip to main content

Publishing a RESTful Web Service with JAX-WS

Posted by kohlert on January 17, 2006 at 12:11 PM PST

The EA3 version of JAX-WS or the JAX-WS released in JWSDP 2.0, supports the publishing and use RESTful Web Services. Here is an example that shows how to publish a RESTful Web Service using JAX-WS.

Publishing a RESTful Web Service with JAX-WS starts by creating an implementation of the javax.xml.ws.Provider<T> interface. The Provider interface is the dynamic alternative to a standard endpoint implementation class. It is analogous to the javax.xml.ws.Dispatch interface used on the client. You will notice that Provider<T> is a generic class. It can support Provider<javax.xml.transform.Source> and
Provider<javax.xml.soap.SOAPMessage> with the SOAP/HTTP binding, or Provider<javax.activation.DataSource> and
Provider<javax.xml.transform.Source> with XML/HTTP binding. When creating an
implementation of Provider you choose which form the requests and response messages will be processed by your implementation.



The example Web Service we are going to create is very simple, it will add two numbers and return the result ad we will use Provider<Source> for our Provider implementation and we will use the XML/HTTP binding.

The first step in writing a the AddNumbers Provider implementation is to declare the AddNumbers class.

public class AddNumbersImpl implements Provider {
}

The next step is to declare a @Resource annotation that will be used by the JAX-WS runtime to inject a WebServiceContext into our AddNumbersImpl instance.

public class AddNumbersImpl implements Provider {
    @Resource
    protected WebServiceContext wsContext;
}

The next step is to implement the T Provider.invoke(T request) method. To do this we will first declared the following method declaration which has a simple try-catch block to handle exceptions. Notice that this method takes a Source object as the request and returns a Source object as the response. This corresponds to the type of Provider that we created.

public class AddNumbersImpl implements Provider {
    @Resource
    protected WebServiceContext wsContext;

    public Source invoke(Source request) {
        try {

        } catch(Exception e) {
            e.printStackTrace();
            throw new HTTPException(500);
        }
    }
}

For this example, the AddNumbers web service will accept requests by extracting the numbers to be added from either the the URL path or from an HTTP query. The query string and path string can be retrieved from the MessageContext which is retrieved from the WebServiceContext wsContext that will be injected into our AddNumbers object. The following code can be used to retrieve the PATH_INFO from the URL and check to see if it is in the proper format.

            String path = (String)mc.get(MessageContext.PATH_INFO);
            if (path != null && path.contains("/num1") &&
                       path.contains("/num2")) {
                return createResultSource(path);
            }
   

The createResultSource(String str) method simply creates a Source object from a properly formatted MessageContext.PATH_INFO string, by extracting the two numbers to be added from the path and adding them together and calling the createResultSource(int sum)method. The source for these two methods are:

    private Source createResultSource(String str) {
        StringTokenizer st = new StringTokenizer(str, "=&/");
        String token = st.nextToken();
        int number1 = Integer.parseInt(st.nextToken());
        st.nextToken();
        int number2 = Integer.parseInt(st.nextToken());
        int sum = number1+number2;
        return createResultSource(sum);
    }

    private Source createResultSource(int sum) {
        String body =
            "<ns:addNumbersResponse xmlns:ns="http://java.duke.org"><ns:return>"
            +sum
            +"</ns:return></ns:addNumbersResponse>";
        Source source = new StreamSource(
            new ByteArrayInputStream(body.getBytes()));
        return source;
    }

So our example invoke method looks like the following.

   public Source invoke(Source source) {
        try {
            MessageContext mc = wsContext.getMessageContext();
            // check for a PATH_INFO request
            String path = (String)mc.get(MessageContext.PATH_INFO);
            if (path != null && path.contains("/num1") &&
                       path.contains("/num2")) {
                return createResultSource(path);
            }
            throw new HTTPException(404);
        } catch(Exception e) {
            e.printStackTrace();
            throw new HTTPException(500);
        }
    }

You will notice that if the proper MessageContext.PATH_INFO is not found, the example throws a HTTPException(404). Instead of throwing this exception, our example endpoint can instead check the query string for the method arguments. Like the MessageContext.PATH_INFO, the query string can be retrieved from the MessageContext with the following code.

            String query = (String)mc.get(MessageContext.QUERY_STRING);

Given this we can either pass the query string to the
createResultSource(String str) method which will parse the parameters from the query string or we
can use the standard ServletRequest object to extract the query arguments if we are deploying to a servlet container as we are for this example. If we were to deploy a Java SE based endpoint we would have to use the MessageContext.QUERY_STRING.

The following code extracts the num1 and num2 query values, adds them together and invokes the createResultSource(int sum) method to create the Source object that will be returned from the invoke method.

            ServletRequest req = (ServletRequest)mc.get(MessageContext.SERVLET_REQUEST);
            int num1 = Integer.parseInt(req.getParameter("num1"));
            int num2 = Integer.parseInt(req.getParameter("num2"));
            return createResultSource(num1+num2);

So far, our AddNumbers class looks like the following.

public class AddNumbersImpl implements Provider {

    @Resource
    protected WebServiceContext wsContext;

    public Source invoke(Source source) {
        try {
            MessageContext mc = wsContext.getMessageContext();
            // check for a PATH_INFO request
            String path = (String)mc.get(MessageContext.PATH_INFO);
            if (path != null && path.contains("/num1") &&
                       path.contains("/num2")) {
                return createResultSource(path);
            }
            String query = (String)mc.get(MessageContext.QUERY_STRING);
            System.out.println("Query String = "+query);
            ServletRequest req = (ServletRequest)mc.get(MessageContext.SERVLET_REQUEST);
            int num1 = Integer.parseInt(req.getParameter("num1"));
            int num2 = Integer.parseInt(req.getParameter("num2"));
            return createResultSource(num1+num2);
        } catch(Exception e) {
            e.printStackTrace();
            throw new HTTPException(500);
        }
    }
   
    private Source createResultSource(String str) {
        StringTokenizer st = new StringTokenizer(str, "=&/");
        String token = st.nextToken();
        int number1 = Integer.parseInt(st.nextToken());
        st.nextToken();
        int number2 = Integer.parseInt(st.nextToken());
        int sum = number1+number2;
        return createResultSource(sum);
    }
   
    private Source createResultSource(int sum) {
        String body =
            "<ns:addNumbersResponse xmlns:ns="http://java.duke.org"><ns:return>"
            +sum
            +"</ns:return></ns:addNumbersResponse>";
        Source source = new StreamSource(
            new ByteArrayInputStream(body.getBytes()));
        return source;
    }  
}

To finish off the AddNumbers, we need to declare some annotions on the class that instruct the JAX-WS runtime how to use this class. The @WebServiceProvider annotation specifies that this class is a Provider based endpoint rather than a service endpoint implementation class that would be annotated with @WebService. The final annotation we add is @BindingType(value=HTTPBinding.HTTP_BINDING). This annotation specifies that AddNumbers endpoint should be published using the HTTPBinding.HTTP_BINDING as opposed to a
SOAPBinding.SOAP11HTTP_BINDING or a SOAPBinding.SOAP12HTTP_BINDING binding. The resulting class looks like the following.

@WebServiceProvider
@BindingType(value=HTTPBinding.HTTP_BINDING)
public class AddNumbersImpl implements Provider {

    @Resource
    protected WebServiceContext wsContext;

    public Source invoke(Source source) {
        try {
            MessageContext mc = wsContext.getMessageContext();
            // check for a PATH_INFO request
            String path = (String)mc.get(MessageContext.PATH_INFO);
            if (path != null && path.contains("/num1") &&
                       path.contains("/num2")) {
                return createResultSource(path);
            }
            String query = (String)mc.get(MessageContext.QUERY_STRING);
            System.out.println("Query String = "+query);
            ServletRequest req = (ServletRequest)mc.get(MessageContext.SERVLET_REQUEST);
            int num1 = Integer.parseInt(req.getParameter("num1"));
            int num2 = Integer.parseInt(req.getParameter("num2"));
            return createResultSource(num1+num2);
        } catch(Exception e) {
            e.printStackTrace();
            throw new HTTPException(500);
        }
    }
   
    private Source createResultSource(String str) {
        StringTokenizer st = new StringTokenizer(str, "=&/");
        String token = st.nextToken();
        int number1 = Integer.parseInt(st.nextToken());
        st.nextToken();
        int number2 = Integer.parseInt(st.nextToken());
        int sum = number1+number2;
        return createResultSource(sum);
    }
   
    private Source createResultSource(int sum) {
        String body =
            "<ns:addNumbersResponse xmlns:ns="http://java.duke.org"><ns:return>"
            +sum
            +"</ns:return></ns:addNumbersResponse>";
        Source source = new StreamSource(
            new ByteArrayInputStream(body.getBytes()));
        return source;
    }  
}

To deploy our endpoint on a servlet container running with the JAX-WS RI we need to create a WAR file. For this example, we will name the WAR file jaxws-restful.war. The WAR file needs a very simple web.xml file to configure the JAX-WS RI servlet. The following will work.

<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee">
  <listener>
    <listener-class>com.sun.xml.ws.transport.http.servlet.WSServletContextListener
  </listener>
  <servlet>
    <servlet-name>restful-addnumbers</servlet-name>
    <servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>restful-addnumbers</servlet-name>
    <url-pattern>/addnumbers/*</url-pattern>
  </servlet-mapping>
  <session-config>
    <session-timeout>60</session-timeout>
  </session-config>
</web-app>

Next we add a sun-jaxws.xml deployment descriptor to the WAR file. The following is an example.

<endpoints
    xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime"
    version="2.0">

    <endpoint
        name="restful-addnumbers"
        implementation="restful.server.AddNumbersImpl"
        wsdl="WEB-INF/wsdl/AddNumbers.wsdl"
        url-pattern="/addnumbers/*" />
</endpoints>

You will notice in the web.xml and sun-jaxws.xml the presence of a

<url-pattern>/addnumbers/*</url-pattern>
element
and a url-pattern="/addnumbers/*" attrbute,
these are specified so we can match any extra arguments that might be in the path (e.g. .../num1/10/num2/20).




You will also notice from the sun-jaxws.xml deployment descriptor that a wsdl file is specified. This is currently necessary to tell the JAX-WS RI not to try to generate a WSDL for this endpoint. The contents of AddNumbers.wsdl is simply the following.

<?xml version="1.0" encoding="UTF-8"?>
<definitions>
</definitions>

Once the AddNumbers endpoint has been deployed, you can test it by entering the following URLs into a browser (slight modifications maybe necessary depending on your own configuration).



http://localhost:8080/jaxws-restful/addnumbers/num1/10/num2/20


Should return the following:

<ns:addNumbersResponse xmlns:ns="http://java.duke.org">
   <ns:return>30</ns:return>
</ns:addNumbersResponse>



http://localhost:8080/jaxws-restful/addnumbers?num1=10&num2=50


Should return the following:

<ns:addNumbersResponse xmlns:ns="http://java.duke.org">
   <ns:return>60</ns:return>
</ns:addNumbersResponse>



Stay tuned for future blogs on how to write various RESTful clients for this example endpoint.



JAX-WS is part of the Glassfish Community.

Related Topics >>