Web services in Java SE, Part 4: SOAP with Attachments API for Java

Learn about SAAJ and other advanced Java SE Web service features

1 2 3 4 5 6 Page 5
Page 5 of 6

After passing the BasicAuthenticator instance to HttpContext's Authenticator setAuthenticator(Authenticator auth) method, Endpoint's Endpoint create(Object implementor) method is called to create an Endpoint instance with the specified UCImpl instance as implementor's argument. This method's void publish(Object serverContext) method is then called with the previous context, and the HttpServer instance is started.

If you were to run UCPublisher and UCClient (see Part 2 for instructions), you would observe the same output shown in Part 2. However, if you modified UCClient's credentials, you would observe a thrown exception in regard to not being able to access the WSDL when Service service = Service.create(url, qname); attempts to execute; the WSDL isn't accessible because authentication has failed.

RESTful web services and attachments

RESTful Web services that implement Provider<Source> cannot return arbitrary MIME-typed data (e.g., a JPEG image). They can only return XML messages with no attachments. If you want to return an attachment (such as an image file), your Web service class must implement the Provider<DataSource> interface; the javax.activation.DataSource interface provides the JavaBeans Activation Framework with an abstraction of an arbitrary collection of data.

Listing 8 presents an Image Publisher RESTful Web service that demonstrations how you could use DataSource with two other javax.activation package types to return a JPEG image to a client.

Listing 8. Returning a JPEG image in response to a GET request

import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.activation.MimetypesFileTypeMap;

import javax.annotation.Resource;

import javax.xml.ws.BindingType;
import javax.xml.ws.Endpoint;
import javax.xml.ws.Provider;
import javax.xml.ws.ServiceMode;
import javax.xml.ws.WebServiceContext;
import javax.xml.ws.WebServiceProvider;

import javax.xml.ws.handler.MessageContext;

import javax.xml.ws.http.HTTPBinding;
import javax.xml.ws.http.HTTPException;

@WebServiceProvider
@ServiceMode(value = javax.xml.ws.Service.Mode.MESSAGE)
@BindingType(value = HTTPBinding.HTTP_BINDING)
public final class ImagePublisher implements Provider<DataSource>
{
   @Resource
   private WebServiceContext wsContext;
   @Override
   public DataSource invoke(DataSource request)
   {
      if (wsContext == null)
         throw new RuntimeException("dependency injection failed on wsContext");
      MessageContext msgContext = wsContext.getMessageContext();
      switch ((String) msgContext.get(MessageContext.HTTP_REQUEST_METHOD))
      {
         case "GET" : return doGet();
         default    : throw new HTTPException(405);
      }
   }
   private DataSource doGet()
   {
      FileDataSource fds = new FileDataSource("balstone.jpg");
      MimetypesFileTypeMap mtftm = new MimetypesFileTypeMap();
      mtftm.addMimeTypes("image/jpeg jpg");
      fds.setFileTypeMap(mtftm);
      System.out.println(fds.getContentType());
      return fds;
   }
   public static void main(String[] args)
   {
      Endpoint.publish("http://localhost:9902/Image", new ImagePublisher());
   }
}

Listing 8's ImagePublisher class describes a simple RESTful Web service whose invoke() method honors only the HTTP GET verb. Its doGet() method responds to a GET request by returning the contents of the balstone.jpg image file to the client.

doGet() first instantiates the javax.activation.FileDataSource class, which implements DataSource, and which encapsulates a file to be returned as an attachment. doGet() passes the name of this file to the FileDataSource(String name) constructor.

doGet() next instantiates the javax.activation.MimetypesFileTypeMap class so that it can associate a MIME type with the JPEG file based on its jpg file extension. This mapping is performed by invoking MimetypesFileTypeMap's void addMimeTypes(String mime_types) method, passing "image/jpeg jpg" as the argument (image/jpeg is the MIME type and jpg is the file extension).

Continuing, doGet() invokes FileDataSource's void setFileTypeMap(FileTypeMap map) method to associate the MimetypesFileTypeMap instance with the FileDataSource instance.

After invoking FileDataSource's String getContentType() method to return the MIME type of the file and outputting its return value, doGet() returns the FileDataSource object to invoke(), which returns this object to the JAX-WS runtime.

Building and running ImagePublisher

Execute the following command to compile Listing 8:

javac --add-modules java.xml.ws ImagePublisher.java

Remove --add-modules java.xml.ws when not compiling under Java SE 9.

Execute the following command to run ImagePublisher.class:

java --add-modules java.xml.ws ImagePublisher

Start a Web browser and point it to http://localhost:9902/Image. You should observe an image as shown in Figure 3.

Figure 3. Balanced stone at Arches National Park in eastern Utah

Balanced stone at Arches National Park in eastern Utah.

Providers and dispatch clients

This series presents high-level and low-level approaches to working with JAX-WS. The high-level approach requires you to work with SEIs and SIBs; it simplifies and hides the details of converting between Java method invocations and their corresponding SOAP-based XML messages. The low-level approach lets you work directly with XML messages, and must be followed to implement a RESTful Web service.

While discussing how to implement a RESTful Web service with JAX-WS (see Part 3), I introduced you to this API's Provider<T> interface, whose invoke() method is called by a client to receive and process a request, and to return a response. I then demonstrated how a client communicates with a provider by using the java.net.HttpURLConnection class. Behind the scenes, the JAX-WS runtime takes the information received from the URL connection and creates the proper object to pass to invoke(). It also takes the object returned from invoke() and makes its contents available to the client via the URL connection's output stream.

JAX-WS also offers the javax.xml.ws.Dispatch<T> interface as a client-side companion to Provider. A client uses Dispatch to construct messages or message payloads as XML, and is known as a dispatch client. As with Provider, Dispatch offers a T invoke(T) method. Dispatch clients call this method to send messages synchronously to providers, and to obtain provider responses from this method's return value.

A dispatch client obtains an object whose class implements Dispatch<T> by invoking one of Service's createDispatch() methods. For example, Dispatch<T> createDispatch(QName portName, Class<T> type, Service.Mode mode) returns a Dispatch instance for communicating with the Web service through the port identified by portName, using the specified Source, SOAPMessage, or DataSource counterpart to the actual type argument passed to Provider<T>, and via the service mode (message or payload) passed to mode.

After the Dispatch instance has been obtained, a dispatch client will create an object conforming to the actual type argument passed to T, and pass this instance to the Web service provider in a call to Dispatch's invoke() method. To understand the interplay between a dispatch client and a provider, consider a client that invokes Dispatch<Source>'s invoke() method with an XML document made available via the Source argument. The following sequence occurs:

  1. The provider's JAX-WS runtime dispatches the client request to Provider<Source>'s invoke() method.
  2. The provider transforms the Source instance into an appropriate javax.xml.transform.Result instance (such as a DOM tree), processes this Result instance in some manner, and returns a Source instance containing XML content to JAX-WS, which transmits the content to Dispatch's invoke() method.
  3. Dispatch's invoke() method returns another Source instance containing the XML content, which the dispatch client transforms into an appropriate Result instance for processing.

Listing 9 demonstrates this interplay by providing an alternate version of the doGet() method that appears in Part 3's LibraryClient application. Instead of working with HttpURLConnection, the alternate doGet() method works with Service and Dispatch.

Listing 9. Revised LibraryClient application's doGet() method as a dispatch client

static void doGet(String isbn) throws Exception
{
   Service service = Service.create(new QName(""));
   String endpoint = "http://localhost:9902/library";
   service.addPort(new QName(""), HTTPBinding.HTTP_BINDING, endpoint);
   Dispatch<Source> dispatch;
   dispatch = service.createDispatch(new QName(""), Source.class,
                                     Service.Mode.MESSAGE);
   Map<String, Object> reqContext = dispatch.getRequestContext();
   reqContext.put(MessageContext.HTTP_REQUEST_METHOD, "GET");
   if (isbn != null)
      reqContext.put(MessageContext.QUERY_STRING, "isbn=" + isbn);
   Source result;
   try
   {
      result = dispatch.invoke(null);
   }
   catch (Exception e)
   {
      System.err.println(e);
      return;
   }
   try
   {
      DOMResult dom = new DOMResult();
      Transformer t = TransformerFactory.newInstance().newTransformer();
      t.transform(result, dom);
      XPathFactory xpf = XPathFactory.newInstance();
      XPath xp = xpf.newXPath();
      if (isbn == null)
      {
         NodeList isbns = (NodeList) xp.evaluate("/isbns/isbn/text()",
                                                 dom.getNode(),
                                                 XPathConstants.NODESET);
         for (int i = 0; i < isbns.getLength(); i++)
            System.out.println(isbns.item(i).getNodeValue());
      }
      else
      {
         NodeList books = (NodeList) xp.evaluate("/book", dom.getNode(),
                                                 XPathConstants.NODESET);
         isbn = xp.evaluate("@isbn", books.item(0));
         String pubYear = xp.evaluate("@pubyear", books.item(0));
         String title = xp.evaluate("title", books.item(0)).trim();
         String publisher = xp.evaluate("publisher", books.item(0)).trim();
         NodeList authors = (NodeList) xp.evaluate("author", books.item(0),
                                                   XPathConstants.NODESET);
         System.out.println("Title: " + title);
         for (int i = 0; i < authors.getLength(); i++)
            System.out.println("Author: " + authors.item(i).getFirstChild()
                                                   .getNodeValue().trim());
         System.out.println("ISBN: " + isbn);
         System.out.println("Publication Year: " + pubYear);
         System.out.println("Publisher: " + publisher);
      }
   }
   catch (TransformerException e)
   {
      System.err.println(e);
   }
   catch (XPathExpressionException xpee)
   {
      System.err.println(xpee);
   }
   System.out.println();
}

This method first invokes Service's Service create(QName serviceName) method to create a Service instance that provides a client view of a Web service. In contrast to a Service instance created from a WSDL file, where the qualified name of the service implementation class and other information is known to the Service instance, a Service instance created by a dispatch client doesn't need to have knowledge of the service when created; the information will be provided to this instance shortly. As a result, a QName instance with an empty qualified name can be passed to create().

A Dispatch<T> instance must be bound to a specific port and endpoint before use. As a result, doGet() next invokes Service's void addPort(QName portName, String bindingId, String endpointAddress) method to create a new port for the service. (Ports created with this method contain no WSDL port type information and can be used only for creating Dispatch instances.) The QName argument passed to portName can contain an empty qualified name. However, an appropriate binding must be specified via a String-based binding identifier. This example specifies HTTPBinding.HTTP_BINDING because we are communicating with a RESTful Web service via HTTP. Also, the target service's endpoint address must be specified as a URI, which happens to be http://localhost:9902/library in this example.

After adding a port to the Service object, doGet() invokes createDispatch() as explained earlier. Once again, a QName object with an empty qualified name is passed because there is no WSDL to indicate a port name.

The returned Dispatch<Source> object's Map<String,Object> getRequestContext() method (which Dispatch inherits from its BindingProvider superinterface) is called to obtain the context that's used to initialize the message context for request messages. doGet() inserts the request method verb (GET) and query string (isbn=isbn) into this map, which will be made available to the provider.

1 2 3 4 5 6 Page 5
Page 5 of 6