O'Reilly logo

Java Web Services: Up and Running by Martin Kalin

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

The Provider and Dispatch Twins

In the RestfulTeams service, the clients send request information to the service through the HTTP start line (for instance, in a GET request) and optionally through an inserted HTTP header (for instance, in a POST request). Recall that GET and DELETE requests result in HTTP messages that have no body, whereas POST and PUT requests result in HTTP messages with bodies. Clients of the RestfulTeams service do not use the HTTP body at all. Even in a POST or PUT request, information about the new team to create or the existing team to update is contained in the HTTP header rather than in the body.

The approach in the RestfulTeams service illustrates the flexibility of REST-style services. The revision in this section shows how the HTTP body can be used in a POST request by introducing the Dispatch interface, which is the client-side twin of the server-side Provider interface. The RestfulTeams service already illustrates that a Provider on the service side can be used without a Dispatch on the client side; and a later example shows how a Dispatch can be used on the client side regardless of how the RESTful service is implemented. Nonetheless, the Provider and Dispatch interfaces are a natural pair.

A RESTful Provider implements the method:

public Source invoke(Source request)

and a Dispatch object, sometimes described as a dynamic service proxy, provides an implementation of this method on the client side. Recall that a Source is a source of an XML document suitable as input to a Transform, which then generates a Result that is typically an XML document as well. The Dispatch to Provider relationship supports a natural exchange of XML documents between client and service:

  • The client invokes the Dispatch method invoke, with an XML document as the Source argument. If the request does not require an XML document, then the Source argument can be null.

  • The service-side runtime dispatches the client request to the Provider method invoke whose Source argument corresponds to the client-side Source.

  • The service transforms the Source into some appropriate Result (for instance, a DOM tree), processes this Result in an application-appropriate way, and returns an XML source to the client. If no response is needed, null can be returned.

  • The Dispatch method invoke returns a Source, sent from the service, that the client then transforms into an appropriate Result and processes as needed.

The fact that the Provider method invoke and the Dispatch method invoke have the same signature underscores the natural fit between them.

A Provider/Dispatch Example

The RabbitCounterProvider is a RESTful service that revises the SOAP-based version of Chapter 3. The RESTful revision honors POST, GET, and DELETE requests from clients. A POST request, as a CRUD create operation, creates a list of Fibonacci numbers that the service caches for subsequent read or delete operations. The doPost method responds to a POST request and the method expects a Source argument, which is the source of an XML document such as:

<fib:request xmlns:fib = 'urn:fib'>[1, 2, 3, 4]</fib:request>

The XML document is thus a list of integers whose Fibonacci values are to be computed. The doGet and doDelete methods handle GET and PUT requests, respectively, neither of which has an HTTP body; hence, the doGet and doDelete methods do not have a Source parameter. All three methods return a Source value, which is the source of an XML confirmation. For example, doPost returns a confirmation XML document such as:

<fib:response xmlns:fib = 'urn:fib'>POSTed[1, 1, 2, 3]</fib:response>

The other two methods return operation-specific confirmations.

Here is the source code for the RabbitCounterProvider:

package ch04.dispatch;

import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.Collection;
import javax.xml.ws.Provider;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.annotation.Resource;
import javax.xml.ws.BindingType;
import javax.xml.ws.WebServiceContext;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.http.HTTPException;
import javax.xml.ws.WebServiceProvider;
import javax.xml.ws.http.HTTPBinding;
import java.io.ByteArrayInputStream;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.xpath.XPathFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;

// The RabbitCounter service implemented as REST style rather than SOAP based.
@WebServiceProvider
@BindingType(value = HTTPBinding.HTTP_BINDING)

public class RabbitCounterProvider implements Provider<Source> {
    @Resource
    protected WebServiceContext ws_ctx;

    // stores previously computed values
    private Map<Integer, Integer> cache = 
       Collections.synchronizedMap(new HashMap<Integer, Integer>());

    private final String xml_start = "<fib:response xmlns:fib = 'urn:fib'>";
    private final String xml_stop = "</fib:response>";
    private final String uri = "urn:fib";

    public Source invoke(Source request) {
        // Filter on the HTTP request verb
        if (ws_ctx == null) throw new RuntimeException("DI failed on ws_ctx.");

        // Grab the message context and extract the request verb.
        MessageContext msg_ctx = ws_ctx.getMessageContext();
        String http_verb = (String) msg_ctx.get(MessageContext.HTTP_REQUEST_METHOD);
        http_verb = http_verb.trim().toUpperCase();

        // Act on the verb.
        if      (http_verb.equals("GET"))    return doGet();
        else if (http_verb.equals("DELETE")) return doDelete();
        else if (http_verb.equals("POST"))   return doPost(request);
        else throw new HTTPException(405);   // bad verb exception
    }

    private Source doPost(Source request) {
        if (request == null) throw new HTTPException(400); // bad request

        String nums = extract_request(request);
        // Extract the integers from a string such as: "[1, 2, 3]"
        nums = nums.replace('[', '\0');
        nums = nums.replace(']', '\0');
        String[ ] parts = nums.split(",");
        List<Integer> list = new ArrayList<Integer>();
        for (String next : parts) {
            int n = Integer.parseInt(next.trim());
            cache.put(n, countRabbits(n));
            list.add(cache.get(n));
        }
        String xml = xml_start + "POSTed: " + list.toString() + xml_stop;
        return make_stream_source(xml);
    }

    private Source doGet() {
        Collection<Integer> list = cache.values();
        String xml = xml_start + "GET: " + list.toString() + xml_stop;
        return make_stream_source(xml);
    }

    private Source doDelete() {
        cache.clear();
        String xml = xml_start + "DELETE: Map cleared." + xml_stop;
        return make_stream_source(xml);
    }

    private String extract_request(Source request) {
        String request_string = null;
        try {
            DOMResult dom_result = new DOMResult();
            Transformer trans = TransformerFactory.newInstance().newTransformer();
            trans.transform(request, dom_result);

            XPathFactory xpf = XPathFactory.newInstance();
            XPath xp = xpf.newXPath();
            xp.setNamespaceContext(new NSResolver("fib", uri));
            request_string = xp.evaluate("/fib:request", dom_result.getNode());
        }
        catch(TransformerConfigurationException e) { System.err.println(e); }
        catch(TransformerException e) { System.err.println(e); }
        catch(XPathExpressionException e) { System.err.println(e); }

        return request_string;
    }

    private StreamSource make_stream_source(String msg) {
        System.out.println(msg);
        ByteArrayInputStream stream = new ByteArrayInputStream(msg.getBytes());
        return new StreamSource(stream);
    }

    private int countRabbits(int n) {
        if (n < 0) throw new HTTPException(403); // forbidden

        // Easy cases.
        if (n < 2) return n;

        // Return cached values if present.
        if (cache.containsKey(n)) return cache.get(n);
        if (cache.containsKey(n - 1) && cache.containsKey(n - 2)) {
          cache.put(n, cache.get(n - 1) + cache.get(n - 2));
          return cache.get(n);
        }

        // Otherwise, compute from scratch, cache, and return.
        int fib = 1, prev = 0;
        for (int i = 2; i <= n; i++) {
            int temp = fib;
            fib += prev;
            prev = temp;
        }
        cache.put(n, fib);
        return fib;
    }
}

The code segment:

XPathFactory xpf = XPathFactory.newInstance();
XPath xp = xpf.newXPath();
xp.setNamespaceContext(new NSResolver("fib", uri));
request_string = xp.evaluate("/fib:request", dom_result.getNode());      

deserves a closer look because the NSResolver also is used in the RestfulTeams service. The call to xp.evaluate, shown in bold above, takes two arguments: an XPath pattern, in this case /fib:request, and the DOMResult node that contains the desired string data between the start tag <fib:request> and the corresponding end tag </fib:request>. The fib in fib:request is a proxy or alias for a namespace URI, in this case urn:fib. The entire start tag in the request XML document is:

<fib:request xmlns:fib = 'urn:fib'>      

The NSResolver class (NS is short for namespace) provides mappings from fib to urn:fib and vice-versa. Here is the code:

package ch04.dispatch;

import java.util.Collections;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import javax.xml.namespace.NamespaceContext;

public class NSResolver implements NamespaceContext {
    private Map<String, String> prefix2uri;
    private Map<String, String> uri2prefix;
    public NSResolver() {
        prefix2uri =
          Collections.synchronizedMap(new HashMap<String, String>());
        uri2prefix =
          Collections.synchronizedMap(new HashMap<String, String>());
    }


    public NSResolver(String prefix, String uri) {
        this();
        prefix2uri.put(prefix, uri);
        uri2prefix.put(uri, prefix);
    }

    public String getNamespaceURI(String prefix) { return prefix2uri.get(prefix); }
    public String getPrefix(String uri) { return uri2prefix.get(uri); }
    public Iterator getPrefixes(String uri) { return uri2prefix.keySet().iterator(); }
}

The NSResolver provides the namespace context for the XPath searches; that is, the resolver binds together a namespace URI and its proxies or aliases. For the application to work correctly, a client and the service must use the same namespace URI; in this case the structurally simple URI urn:fib.

More on the Dispatch Interface

The Dispatch-based client of the RESTful RabbitCounterProvider service has features reminiscent of a client for a SOAP-based service. The client creates identifying QName instances for a service and a port, creates a service object and adds a port, and then creates a Dispatch proxy associated with the port. Here is the code segment:

QName service_name = new QName("rcService", ns_URI.toString()); // uri is urn:fib
QName port = new QName("rcPort", ns_URI.toString());
String endpoint = "http://localhost:9876/fib";
// Now create a service proxy or dispatcher.
Service service = Service.create(service_name);
service.addPort(port, HTTPBinding.HTTP_BINDING, endpoint);
Dispatch<Source> dispatch = 
   service.createDispatch(port, Source.class, Service.Mode.PAYLOAD);   

This client-side dispatch object can dispatch XML documents as requests to the service as XML Source instances. A document is sent to the service through an invocation of the invoke method. Here are two code segments. In the first, an XML document is prepared as the body of a POST request:

String xml_start = "<fib:request xmlns:fib = 'urn:fib'>";
String xml_end = "</fib:request>";
List<Integer> nums = new ArrayList<Integer>();
for (int i = 0; i < 12; i++) nums.add(i + 1);
String xml = xml_start + nums.toString() + xml_end;

In the second, the request XML document is wrapped in Source and then sent to the service through an invocation of invoke:

StreamSource source = null;
if (data != null) source = make_stream_source(data.toString()); // data = XML doc
Source result = dispatch.invoke(source);
display_result(result, uri); // do an XPath search of the resturned XML

The GET and DELETE operations do not require XML documents; hence, the Source argument to invoke is null in both cases. Here is a client-side trace of the requests sent to the service and the responses received in return:

Request: <fib:request xmlns:fib = 'urn:fib'>
           [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
         </fib:request>
POSTed: [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144]

Request: null
GET: [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144]

Request: null
DELETE: Map cleared.

Request: null
GET: [ ]

Request: <fib:request xmlns:fib = 'urn:fib'>
            [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,...,20, 21, 22, 23, 24]
         </fib:request>
POSTed: [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89,..., 10946, 17711, 28657, 46368]

Request: null
GET: [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89,..., 10946, 6765, 28657, 17711, 46368]

Finally, here is the source code for the entire DispatchClient:

import java.net.URI;
import java.net.URISyntaxException;
import java.io.ByteArrayInputStream;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
import javax.xml.namespace.QName;
import javax.xml.ws.Service;
import javax.xml.ws.Dispatch;
import javax.xml.ws.http.HTTPBinding;
import javax.xml.transform.stream.StreamSource;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.xpath.XPathFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.ws.handler.MessageContext;
import org.w3c.dom.NodeList;
import ch04.dispatch.NSResolver;

class DispatchClient {
    public static void main(String[ ] args) throws Exception {
        new DispatchClient().setup_and_test();
    }

    private void setup_and_test() {
        // Create identifying names for service and port.
        URI ns_URI = null;
        try {
            ns_URI = new URI("urn:fib");
        }
        catch(URISyntaxException e) { System.err.println(e); }

        QName service_name = new QName("rcService", ns_URI.toString());
        QName port = new QName("rcPort", ns_URI.toString());
        String endpoint = "http://localhost:9876/fib";

        // Now create a service proxy or dispatcher.
        Service service = Service.create(service_name);
        service.addPort(port, HTTPBinding.HTTP_BINDING, endpoint);
        Dispatch<Source> dispatch =
            service.createDispatch(port, Source.class, Service.Mode.PAYLOAD);

        // Send some requests.
        String xml_start = "<fib:request xmlns:fib = 'urn:fib'>";
        String xml_end = "</fib:request>";

        // To begin, a POST to create some Fibonacci numbers.
        List<Integer> nums = new ArrayList<Integer>();
        for (int i = 0; i < 12; i++) nums.add(i + 1);
        String xml = xml_start + nums.toString() + xml_end;
        invoke(dispatch, "POST", ns_URI.toString(), xml);

        // GET request to test whether the POST worked.
        invoke(dispatch, "GET", ns_URI.toString(), null);

        // DELETE request to remove the list
        invoke(dispatch, "DELETE", ns_URI.toString(), null);

        // GET to test whether the DELETE worked.
        invoke(dispatch, "GET", ns_URI.toString(), null);

        // POST to repopulate and a final GET to confirm
        nums = new ArrayList<Integer>();
        for (int i = 0; i < 24; i++) nums.add(i + 1);
        xml = xml_start + nums.toString() + xml_end;
        invoke(dispatch, "POST", ns_URI.toString(), xml);
        invoke(dispatch, "GET", ns_URI.toString(), null);
    }

    private void invoke(Dispatch<Source> dispatch,
                        String verb,
                        String uri,
                        Object data) {
        Map<String, Object> request_context = dispatch.getRequestContext();
        request_context.put(MessageContext.HTTP_REQUEST_METHOD, verb);

        System.out.println("Request: " + data);

        // Invoke
        StreamSource source = null;
        if (data != null) source = make_stream_source(data.toString());
        Source result = dispatch.invoke(source);
        display_result(result, uri);
    }

   private void display_result(Source result, String uri) {
        DOMResult dom_result = new DOMResult();
        try {
            Transformer trans = TransformerFactory.newInstance().newTransformer();
            trans.transform(result, dom_result);

            XPathFactory xpf = XPathFactory.newInstance();
            XPath xp = xpf.newXPath();
            xp.setNamespaceContext(new NSResolver("fib", uri));
            String result_string = 
               xp.evaluate("/fib:response", dom_result.getNode());
            System.out.println(result_string);
        }
        catch(TransformerConfigurationException e) { System.err.println(e); }
        catch(TransformerException e) { System.err.println(e); }
        catch(XPathExpressionException e) { System.err.println(e); }
    }

    private StreamSource make_stream_source(String msg) {
        ByteArrayInputStream stream = new ByteArrayInputStream(msg.getBytes());
        return new StreamSource(stream);
    }
}

A Dispatch Client Against a SOAP-based Service

The Dispatch client is flexible in that it may be used to issue requests against any service, REST-style or SOAP-based. This section illustrates how a SOAP-based service can be treated as if it were REST style. This use of Dispatch underscores that SOAP-based web services delivered over HTTP, as most are, represent a special case of REST-style services. What the SOAP libraries spare the programmer is the need to process XML directly on either the service or the client side, with handlers as the exception to this rule.

The DispatchClientTS application uses a Dispatch proxy to submit a request against the SOAP-based TimeServer service of Chapter 1. The TimeServer supports two operations: one supplies the current time as a human-readable string, whereas the other supplies the time as the elapsed milliseconds from the Unix epoch. Here is the source code for DispatchClientTS:

import java.util.Map;
import java.net.URI;
import java.net.URISyntaxException;
import java.io.ByteArrayInputStream;
import javax.xml.namespace.QName;
import javax.xml.ws.Service;
import javax.xml.ws.Dispatch;
import javax.xml.ws.http.HTTPBinding;
import javax.xml.transform.stream.StreamSource;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.xpath.XPathFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.ws.handler.MessageContext;
import ch04.dispatch.NSResolver;

// Dispatch client against the SOAP-based TimeServer service
class DispatchClientTS {
    public static void main(String[ ] args) throws Exception {
        new DispatchClientTS().send_and_receive_SOAP();
    }

    private void send_and_receive_SOAP() {
        // Create identifying names for service and port.
        URI ns_URI = null;
        try {
            ns_URI = new URI("http://ts.ch01/");      // from WSDL
        }
        catch(URISyntaxException e) { System.err.println(e); }

        QName service_name = new QName("tns", ns_URI.toString());
        QName port = new QName("tsPort", ns_URI.toString());
        String endpoint = "http://localhost:9876/ts"; // from WSDL
        // Now create a service proxy or dispatcher.
        Service service = Service.create(service_name);
        service.addPort(port, HTTPBinding.HTTP_BINDING, endpoint);
        Dispatch<Source> dispatch =
            service.createDispatch(port, Source.class, Service.Mode.PAYLOAD);
        // Send a request.
        String soap_request =
            "<?xml version='1.0' encoding='UTF-8'?> " +
            "<soap:Envelope " +
               "soap:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/' " +
               "xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/' " +
               "xmlns:soapenc='http://schemas.xmlsoap.org/soap/encoding/' " +
               "xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' " +
               "xmlns:tns='http://ts.ch01/' " +
               "xmlns:xsd='http://www.w3.org/2001/XMLSchema'> " +
            "<soap:Body>" +
            "<tns:getTimeAsElapsed xsi:nil='true'/>" +
            "</soap:Body>" +
            "</soap:Envelope>";

        Map<String, Object> request_context = dispatch.getRequestContext();
        request_context.put(MessageContext.HTTP_REQUEST_METHOD, "POST");
        StreamSource source = make_stream_source(soap_request);
        Source result = dispatch.invoke(source);
        display_result(result, ns_URI.toString());
    }

    private void display_result(Source result, String uri) {
        DOMResult dom_result = new DOMResult();
        try {
            Transformer trans = TransformerFactory.newInstance().newTransformer();
            trans.transform(result, dom_result);

            XPathFactory xpf = XPathFactory.newInstance();
            XPath xp = xpf.newXPath();
            xp.setNamespaceContext(new NSResolver("tns", uri));
            // In original version, "//time_result" instead
            String result_string = xp.evaluate("//return", dom_result.getNode());
            System.out.println(result_string);
        }
        catch(TransformerConfigurationException e) { System.err.println(e); }
        catch(TransformerException e) { System.err.println(e); }
        catch(XPathExpressionException e) { System.err.println(e); }
    }

    private StreamSource make_stream_source(String msg) {
        ByteArrayInputStream stream = new ByteArrayInputStream(msg.getBytes());
        return new StreamSource(stream);
    }
}

The SOAP request document is hardcoded as a string. The rest of the setup is straightforward. After a service object is created and a port added with the TimeServer’s endpoint, a Dispatch proxy is created with a Service.Mode.PAYLOAD so that the SOAP request document becomes an XML Source transported to the service in the body of the HTTP request. The SOAP-based service responds with a SOAP envelope, which an XPath object then searches for the integer value that gives the elapsed milliseconds. On a sample run, the output was 1,214,514,573,623 (with commas added for readability) on a RESTful call to getTimeAsElapsed.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required