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

how-to
Sep 26, 201754 mins

Learn about SAAJ and other advanced Java SE Web service features

Parts 1 through 3 of this four-part series on developing Web services in Java SE first presented an overview of Web services and Java SE’s support for developing them. The series then focused on developing SOAP-based and RESTful Web services via this support. Part 4 wraps up this series by focusing on advanced topics.

This article first introduces Java SE’s SAAJ API for working with SOAP-based Web services at a lower level. It then discusses how to create a JAX-WS handler to log the flow of SOAP messages. Next, the article teaches how to create and install a custom lightweight HTTP server to perform authentication.

Moving on, you’re shown how to create a RESTful Web service that returns attachments (e.g., a JPEG image) to its clients. You then dig deeper into JAX-WS by exploring the interplay between providers and dispatch clients, and learn how to create a dispatch client that accesses the javax.xml.transform.Source instance returned from a Web service provider’s invoke() method via a different Source instance in another version of Part 3’s LibraryClient application.

Working with SAAJ

Soap with Attachments API for Java (SAAJ) is the Java API for creating, sending, and receiving SOAP messages that may or may not have MIME-typed attachments. SAAJ is a lower-level alternative to JAX-WS for sending and receiving SOAP messages.

This section first presents an overview of SOAP message architecture. It then takes you on a tour of SAAJ. When this tour finishes, I present an application that uses this API to access a SOAP-based Web service (written in PHP) for converting between integer values and Roman numerals. This application reinforces your understanding of SAAJ.

SOAP message architecture

A SOAP message is an XML document sent from an initial SOAP sender node to an ultimate SOAP receiver node, mostly likely passing through intermediate SOAP sender/receiver nodes along its path. A SOAP node is processing logic that operates on a SOAP message.

The SOAP document consists of an Envelope root element that encapsulates an optional Header element and a nonoptional Body element — see Figure 1.

Figure 1. A SOAP message’s architecture consists of an optional Header element and a mandatory Body element within an Envelope element

A SOAP message's architecture consists of an optional Header element and a mandatory Body element within an Envelope element.

The Header element specifies application-related information (such as authentication details to verify who sent the message) via immediate child elements known as header blocks. A header block represents a logical grouping of data that can target an intermediate SOAP node or the ultimate receiver node.

Although header blocks are defined by the application, their start tags may contain the following SOAP-defined attributes to indicate how SOAP nodes should process them:

  • encodingStyle identifies the rules used to serialize parts of a SOAP message
  • role identifies the SOAP node (via a URI) to which the header block is targeted — this SOAP 1.2-introduced attribute replaces the SOAP 1.1 actor attribute, which performs the same function
  • mustUnderstand indicates whether processing of the header block is mandatory (value 1 in SOAP 1.1; true in SOAP 1.2) or optional (value 0 in SOAP 1.1; false in SOAP 1.2)
  • relay indicates whether the header block targeted at a SOAP receiver must be relayed to another node if not processed — this attribute was introduced in SOAP 1.2

The Body element contains information that targets the ultimate receiver node. This information is known as the payload, and consists of a SOAP-defined Fault child element describing a fault (an error being reported by the Web service), or child elements that are specific to the Web service.

The Fault element contains error and status information that a Web service returns to a client. SOAP 1.1 specifies the following child elements of Fault:

  • faultcode: This mandatory element provides information about the fault in a form that can be processed by software. SOAP defines a small set of SOAP fault codes that cover basic faults; this set can be extended by applications.
  • faultstring: This mandatory element provides information about the fault in a human-readable format.
  • faultactor: This element contains the URI of the SOAP node that generated the fault. A SOAP node that is not the ultimate SOAP receiver must include faultactor when creating a fault; an ultimate SOAP receiver doesn’t have to include this element, but might choose to do so.
  • detail: This element carries application-specific error information related to the Body element. It must be present when Body‘s contents couldn’t be processed successfully. The detail element must not be used to carry error information belonging to header blocks; detailed error information belonging to header blocks is carried within these blocks.

SOAP 1.2 specifies the following child elements of Fault:

  • Code: This mandatory element provides information about the fault in a form that can be processed by software. It contains a Value element and an optional Subcode element.
  • Reason: This mandatory element provides information about the fault in a human-readable format. Reason contains one or more Text elements, each of which contains information about the fault in a different language.
  • Node: This element contains the URI of the SOAP node that generated the fault. A SOAP node that’s not the ultimate SOAP receiver must include Node when creating a fault; an ultimate SOAP receiver doesn’t have to include this element, but might choose to do so.
  • Role: This element contains a URI that identifies the role the node was operating in when the fault occurred.
  • Detail: This optional element contains application-specific error information related to the SOAP fault codes describing the fault. Its presence has no significance as to which parts of the faulty SOAP message were processed.

Listing 1 presents an example SOAP message.

Listing 1. A SOAP message for calling a SOAP-based library Web service’s getTitle() function to retrieve a book’s title when given its ISBN

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 
                   xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <SOAP-ENV:Header />
   <SOAP-ENV:Body>
      <lns:getTitle xmlns:lns="http://javajeff.ca/library">
         <isbn xsi:type="xsd:string">9781484219157</isbn>
      </lns:getTitle>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

This SOAP message describes a request to a library Web service to execute its getTitle() function. Furthermore, it describes the type and value of the ISBN argument passed to this function’s isbn parameter.

The message begins with the SOAP-ENV-prefixed <Envelope> tag, which describes the SOAP message’s envelope. The commonly used SOAP-ENV prefix corresponds to the SOAP 1.1 namespace that provides the schema for SOAP envelopes. The xsd and xsi prefixes correspond to the XML Schema structures and XML Schema Instance namespaces, and are used to denote the XML Schema type that describes the kind of data being passed to getTitle() (a string) via the isbn element.

The empty Header element signifies that there is no SOAP header. In contrast, the Body element identifies a single getTitle operation request.

The getTitle element is namespace-qualified, as recommended by the SOAP 1.1 and 1.2 specifications. In contrast, the isbn child element of getTitle isn’t namespace-qualified because it inherits getTitle‘s namespace — the SOAP 1.1 and 1.2 specifications don’t mandate that such child elements be namespace-qualified.

SAAJ API overview

SAAJ is a small Java SE API that lets you perform the following tasks:

  • create an endpoint-to-endpoint connection
  • create a SOAP message
  • create an XML fragment
  • add content to the header of a SOAP message
  • add content to the body of a SOAP message
  • create attachment parts and add content to them
  • access/add/modify parts of a SOAP message
  • create/add/modify SOAP fault information
  • extract content from a SOAP message
  • send a SOAP request-response message

SAAJ is associated with the javax.xml.soap package, which contains 14 interfaces and 13 classes. Various interfaces and classes extend their counterparts in the org.w3c.dom package, implying that part of a SOAP message is organized as a tree of nodes.

The following classes and interfaces are used to specify the structure of a SOAP message:

  • SOAPMessage represents the entire SOAP message. It contains a single SOAPPart instance and zero or more AttachmentPart instances.
  • SOAPPart contains a SOAPEnvelope instance, which represents the actual SOAP Envelope element.
  • SOAPEnvelope optionally contains a SOAPHeader instance and also contains a mandatory SOAPBody instance.
  • SOAPHeader represents the SOAP message’s header block(s).
  • SOAPBody contains either a SOAPFault object or a SOAPBodyElement object containing the actual SOAP payload XML content.
  • SOAPFault stores a SOAP fault message.

Working with SAAJ involves creating a SOAP connection, creating SOAP messages, populating each message with content and optional attachments, sending the messages to an endpoint and retrieving replies, and closing the connection.

Creating a SOAP connection

You create a connection by working with the SOAPConnectionFactory and SOAPConnection classes. As its name implies, SOAPConnectionFactory is a factory class for retrieving SOAPConnection instances (actually, instances of subclasses of the abstract SOAPConnection class). A SOAPConnection instance represents an endpoint-to-endpoint connection to the Web service; the client and Web service exchange messages over this connection. The following example shows you how to instantiate the factory and obtain a SOAP connection:

SOAPConnectionFactory soapcf = SOAPConnectionFactory.newInstance();
SOAPConnection soapc = soapcf.createConnection();

Instantiate the factory by calling SOAPConnectionFactory‘s SOAPConnectionFactory newInstance() method. This method throws SOAPException when a SOAPConnectionFactory instance cannot be created. If a nonOracle Java implementation doesn’t support the SAAJ communication infrastructure, this method throws an instance of the java.lang.UnsupportedOperationException class.

After instantiating SOAPConnectionFactory, call this instance’s SOAPConnection createConnection() method to create and return a new SOAPConnection object. This method throws SOAPException when it’s unable to create this object.

Creating a SOAP message

Create a SOAP message by working with the MessageFactory and SOAPMessage classes. MessageFactory provides a pair of methods for returning a MessageFactory instance:

  • MessageFactory newInstance() creates a MessageFactory object based on the default SOAP 1.1 implementation. This method follows an ordered lookup procedure to locate the MessageFactory implementation class. This procedure first examines the javax.xml.soap.MessageFactory system property, and lastly calls an instance of the SAAJMetaFactory class’s MessageFactory newMessageFactory(String protocol) method to return that factory. This method throws SOAPException when it’s unable to create the factory.
  • MessageFactory newInstance(String protocol) creates a MessageFactory object that’s based on the SOAP implementation specified by the protocol argument, which is one of the SOAPConstants interface’s DEFAULT_SOAP_PROTOCOL, DYNAMIC_SOAP_PROTOCOL, SOAP_1_1_PROTOCOL, or SOAP_1_2_PROTOCOL constants. This method throws SOAPException when it’s unable to create the factory.

After instantiating MessageFactory, call one of the following methods to create a SOAPMessage instance:

  • SOAPMessage createMessage() creates and returns a new SOAPMessage object (actually, an instance of a concrete subclass of this abstract class) with default SOAPPart, SOAPEnvelope, SOAPBody (initially empty) and SOAPHeader objects. This method throws SOAPException when a SOAPMessage instance cannot be created, and UnsupportedOperationException when the MessageFactory instance’s protocol is DYNAMIC_SOAP_PROTOCOL.
  • SOAPMessage createMessage(MimeHeaders headers, InputStream in) internalizes the contents of the given java.io.InputStream object into a new SOAPMessage object and returns this object. The MimeHeaders instance specifies transport-specific headers that describe the various attachments to the SOAP message. This method throws SOAPException when a SOAPMessage instance cannot be created, java.io.IOException when there’s a problem reading data from the input stream, and java.lang.IllegalArgumentException when the MessageFactory instance requires one or more MIME headers to be present in the argument passed to headers and these headers are missing.

The following example shows you how to instantiate the factory and create a SOAPMessage object that’s ready to be populated:

MessageFactory mf = MessageFactory.newInstance();
SOAPMessage soapm = mf.createMessage();

Populating a SOAP message with content and optional attachments

SOAPMessage describes a SOAP message optionally followed by MIME-typed attachments. The SOAP message part of this object is defined by an instance of a concrete subclass of the abstract SOAPPart class.

SOAPPart encapsulates an instance of a class that implements the SOAPEnvelope interface, and the SOAPEnvelope instance encapsulates instances of classes that implement the SOAPHeader and SOAPBody interfaces. Call SOAPMessage‘s SOAPPart getSOAPPart() method to return the SOAPPart instance. You can then call SOAPPart‘s SOAPEnvelope getEnvelope() method to return the SOAPEnvelope instance, and call SOAPEnvelope‘s SOAPBody getBody() and SOAPHeader getHeader() methods to return the SOAPEnvelope instance’s SOAPBody and SOAPHeader instances.

The following example shows you how to obtain the SOAPPart, SOAPEnvelope, and SOAPBody instances from the SOAPMessage instance, and also how to detach the SOAPHeader instance:

SOAPPart soapp = soapm.getSOAPPart();
SOAPEnvelope soape = soapp.getEnvelope();
SOAPBody soapb = soape.getBody();
soape.getHeader().detachNode();

SOAPEnvelope and various other interfaces extend SOAPElement, which provides methods that are applicable to different kinds of element implementation instances. For example, the SOAPElement addNamespaceDeclaration(String prefix, String uri) method is useful for adding a namespace declaration with the specified prefix and uri values to a SOAPEnvelope instance. The following example shows how to add declarations for the xsd and xsi namespaces shown in Listing 1 to its Envelope element:

soape.addNamespaceDeclaration("xsd", "http://www.w3.org/2001/XMLSchema");
soape.addNamespaceDeclaration("xsi", "http://www.w3.org/2001/XMLSchema-instance");

The SOAPBody instance contains either content or a fault. Adding content to the body first requires that you create SOAPBodyElement objects (to store this content) and add these objects to the SOAPBody instance. This task is accomplished by calling either of SOAPBody‘s two addBodyElement() methods, which create the SOAPBodyElement object, add it to the SOAPBody object, and return a reference to the created object so that you can create method call chains.

When a new subelement of the SOAP Body element is created, you must specify a fully qualified name in the form of a javax.xml.soap.Name instance or a javax.xml.namespace.QName instance. Because the Java documentation for the Name interface states that it may be deprecated in favor of QName, you should get into the habit of using QName instead of Name. As a result, you should use SOAPBody‘s SOAPBodyElement addBodyElement(QName qname) method instead of using this interface’s SOAPBodyElement addBodyElement(Name name) method, as demonstrated below:

QName name = new QName("http://tutortutor.ca/library", "getTitle", "lns");
SOAPElement soapel = soapb.addBodyElement(name);

SOAPBodyElement instances store subelement instances. You create these subelements and add them to the SOAPBodyElement instance by calling SOAPElement‘s various addChildElement() methods, such as SOAPElement addChildElement(String localName), which creates a subelement object having the specified localName, adds this subelement object to the SOAPBodyElement object on which this method is called, and returns a reference to the created SOAPElement object for chaining together method calls.

You can then attach a text node to a body element or a subelement by calling SOAPElement‘s SOAPElement addTextNode(String text) method. You can also call SOAPElement‘s void setAttribute(String name, String value) method (inherited from SOAPElement‘s org.w3c.dom.Element ancestor interface) to add attributes to the subelement as appropriate. The following example demonstrates:

soapel.addChildElement("isbn").addTextNode("9781484219157").setAttribute("xsi:type",
                                                                         "xsd:string");

Attachments are instances of concrete subclasses of the abstract AttachmentPart class. If you need to include an attachment with the SOAP message, call one of SOAPMessage‘s createAttachmentPart() methods to create and return an AttachmentPart object. After configuring this object, call SOAPMessage‘s void addAttachmentPart(AttachmentPart attachmentPart) method to add the given attachmentPart-referenced object to this SOAPMessage object.

Sending a SOAP message and receiving a reply

To send the SOAP message and receive a reply, invoke SOAPConnection‘s SOAPMessage call(SOAPMessage request, Object to) method. The specified request message is sent to the endpoint identified by to, which may be a java.lang.String or java.net.URL instance. This method throws SOAPException when a SOAP problem occurs, and blocks until it receives a SOAP message, which it returns as a SOAPMessage object. The following example provides a demonstration:

String endpoint = "http://tutortutor.ca/library/GetTitle";
// Send the request message identified by soapm to the Web service at the specified 
// endpoint and return the response message.
SOAPMessage response = soapc.call(soapm, endpoint);

Alternatively, you can call SOAPConnection‘s SOAPMessage get(Object to) method to request a SOAP message. As with call(), get() blocks until there’s a reply, and throws SOAPException when a SOAP problem occurs.

Closing a SOAP connection

After finishing your call() and/or get() invocations, call SOAPConnection‘s void close() method to close the connection to the endpoint. If this method has already been called, a subsequent attempt to close the connection results in a thrown SOAPException instance.

Roman numerals and SAAJ

I’ve created a RomanNumerals application that demonstrates SAAJ in a more practical context. It uses this API to communicate with a SOAP-based Roman Numerals Conversion Web service, which converts between Roman numerals and base-10 integer values. This Web service’s WSDL document is located at https://javajeff.ca/php/rncws.php?wsdl and appears in Listing 2.

Listing 2. WSDL for the Roman numerals/base-10 integer values conversion Web service

<definitions name="IRomanservice" 
             targetNamespace="http://javajeff.ca/"
             xmlns:tns='http://javajeff.ca/'
             xmlns:soap='http://schemas.xmlsoap.org/wsdl/soap/'
             xmlns:xsd='http://www.w3.org/2001/XMLSchema'
             xmlns:soapenc='http://schemas.xmlsoap.org/soap/encoding/'
             xmlns:wsdl='http://schemas.xmlsoap.org/wsdl/'
             xmlns='http://schemas.xmlsoap.org/wsdl/'>
   <message name="IntToRoman0Request">
      <part name="Int" type="xs:int"/>
   </message>
   <message name="IntToRoman0Response">
      <part name="return" type="xs:string"/>
   </message>
   <message name="RomanToInt1Request">
      <part name="Rom" type="xs:string"/>
   </message>
   <message name="RomanToInt1Response">
      <part name="return" type="xs:int"/>
   </message>
   <portType name="IRoman">
      <operation name="IntToRoman">
         <input message="tns:IntToRoman0Request"/>
         <output message="tns:IntToRoman0Response"/>
      </operation>
      <operation name="RomanToInt">
         <input message="tns:RomanToInt1Request"/>
         <output message="tns:RomanToInt1Response"/>
      </operation>
   </portType>
   <binding name="IRomanbinding" type="tns:IRoman">
      <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
      <operation name="IntToRoman">
         <soap:operation soapAction="urn:Roman-IRoman#IntToRoman" style="rpc"/>
         <input message="tns:IntToRoman0Request">
            <soap:body use="encoded"
                       encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
                       namespace="urn:Roman-IRoman"/>
         </input>
         <output message="tns:IntToRoman0Response">
            <soap:body use="encoded"
                       encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
                       namespace="urn:Roman-IRoman"/>
         </output>
      </operation>
      <operation name="RomanToInt">
         <soap:operation soapAction="urn:Roman-IRoman#RomanToInt" style="rpc"/>
         <input message="tns:RomanToInt1Request">
            <soap:body use="encoded"
                       encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
                       namespace="urn:Roman-IRoman"/>
         </input>
         <output message="tns:RomanToInt1Response">
            <soap:body use="encoded"
                       encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
                       namespace="urn:Roman-IRoman"/>
         </output>
      </operation>
   </binding>
   <service name="IRomanservice">
      <port name="IRomanPort" binding="tns:IRomanbinding">
         <soap:address
               location="http://javajeff.ca/php/rncws.php"/>
      </port>
   </service>
</definitions>

Listing 2’s WSDL document provides important information for constructing SOAP request and response messages — note the absence of a types element because the service uses only XML Schema builtin simple types; furthermore, the document style is rpc. This information includes the IntToRoman and RomanToInt operation names (which the application calls to perform the conversions) along with parameter and return type information. This listing also presents the service’s endpoint address.

Listing 3 reveals RomanNumerals.java, which stores the application’s source code.

Listing 3. Using SAAJ to access the Roman Numerals Conversion Web service

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import java.io.IOException;

import java.util.Iterator;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;

import javax.swing.border.Border;

import javax.xml.namespace.QName;

import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPBodyElement;
import javax.xml.soap.SOAPConnection;
import javax.xml.soap.SOAPConnectionFactory;
import javax.xml.soap.SOAPConstants;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPMessage;

public class RomanNumerals extends JFrame
{
   private JTextField txtResult;

   RomanNumerals()
   {
      super("RomanNumerals");
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      // Create a gradient panel in which to present the GUI.
      GPanel pnl = new GPanel();
      pnl.setLayout(new BorderLayout());
      // Build input panel.
      JPanel pnlInput = new JPanel();
      Border inner = BorderFactory.createEtchedBorder();
      Border outer = BorderFactory.createEmptyBorder(10, 10, 10, 10);
      pnlInput.setBorder(BorderFactory.createCompoundBorder(outer, inner));
      pnlInput.setOpaque(false);
      pnlInput.add(new JLabel("Enter Roman numerals or integer:"));
      final JTextField txtInput = new JTextField(15);
      pnlInput.add(txtInput);
      pnl.add(pnlInput, BorderLayout.NORTH);
      // Build buttons panel.
      JPanel pnlButtons = new JPanel();
      inner = BorderFactory.createEtchedBorder();
      outer = BorderFactory.createEmptyBorder(10, 10, 10, 10);
      pnlButtons.setBorder(BorderFactory.createCompoundBorder(outer, inner));
      pnlButtons.setOpaque(false);
      JButton btnToRoman = new JButton("To Roman");
      ActionListener alToRoman;
      alToRoman = (ae) ->
                  {
                     try
                     {
                        String roman = toRoman(txtInput.getText());
                        txtResult.setText(roman);
                     }
                     catch (SOAPException se)
                     {
                        JOptionPane.showMessageDialog(RomanNumerals.this,
                                                      se.getMessage());
                     }
                  };
      btnToRoman.addActionListener(alToRoman);
      pnlButtons.add(btnToRoman);
      JButton btnToInteger = new JButton("To Integer");
      ActionListener alToInteger;
      alToInteger = (ae) ->
                    {
                       try
                       {
                          String integer = toInteger(txtInput.getText());
                          txtResult.setText(integer);
                       }
                       catch (SOAPException se)
                       {
                          JOptionPane.showMessageDialog(RomanNumerals.this,
                                                        se.getMessage());
                       }
                    };
      btnToInteger.addActionListener(alToInteger);
      pnlButtons.add(btnToInteger);
      pnl.add(pnlButtons, BorderLayout.CENTER);
      // Build result panel.
      JPanel pnlResult = new JPanel();
      inner = BorderFactory.createEtchedBorder();
      outer = BorderFactory.createEmptyBorder(10, 10, 10, 10);
      pnlResult.setBorder(BorderFactory.createCompoundBorder(outer, inner));
      pnlResult.setOpaque(false);
      pnlResult.add(new JLabel("Result:"));
      txtResult = new JTextField(35);
      pnlResult.add(txtResult);
      pnl.add(pnlResult, BorderLayout.SOUTH);
      setContentPane(pnl);
      pack();
      setResizable(false);
      setLocationRelativeTo(null); // center on the screen
      setVisible(true);
   }

   String toInteger(String input) throws SOAPException
   {
      // Build a request message. The first step is to create an empty message
      // via a message factory. The default SOAP 1.1 message factory is used.
      MessageFactory mfactory = MessageFactory.newInstance();
      SOAPMessage request = mfactory.createMessage();
      // The request SOAPMessage object contains a SOAPPart object, which
      // contains a SOAPEnvelope object, which contains an empty SOAPHeader
      // object followed by an empty SOAPBody object.
      // Detach the header since a header is not required. This step is
      // optional.
      SOAPHeader header = request.getSOAPHeader();
      header.detachNode();
      // Access the body so that content can be added.
      SOAPBody body = request.getSOAPBody();
      // Add the RomanToInt operation body element to the body.
      QName bodyName = new QName("http://javajeff.ca/", "RomanToInt", "tns");
      SOAPBodyElement bodyElement = body.addBodyElement(bodyName);
      // Add the Rom child element to the RomanToInt body element.
      QName name = new QName("Rom");
      SOAPElement element = bodyElement.addChildElement(name);
      element.addTextNode(input).setAttribute("xsi:type", "xs:string");
      // Add appropriate namespaces and an encoding style to the envelope.
      SOAPEnvelope env = request.getSOAPPart().getEnvelope();
      env.addNamespaceDeclaration("env",
                                  "http://schemas.xmlsoap.org/soap/envelop/");
      env.addNamespaceDeclaration("enc",
                                  "http://schemas.xmlsoap.org/soap/encoding/");
      env.setEncodingStyle(SOAPConstants.URI_NS_SOAP_ENCODING);
      env.addNamespaceDeclaration("xs", "http://www.w3.org/2001/XMLSchema");
      env.addNamespaceDeclaration("xsi",
                                  "http://www.w3.org/2001/XMLSchema-instance");
      // Output the request just built to standard output, to see what the
      // SOAP message looks like (which is useful for debugging).
      System.out.println("nSoap request:n");
      try
      {
         request.writeTo(System.out);
      }
      catch (IOException ioe)
      {
         JOptionPane.showMessageDialog(RomanNumerals.this,
                                       ioe.getMessage());
      }
      System.out.println();
      // Prepare to send message by obtaining a connection factory and creating
      // a connection.
      SOAPConnectionFactory factory = SOAPConnectionFactory.newInstance();
      SOAPConnection con = factory.createConnection();
      // Identify the message's target.
      String endpoint = "http://www.javajeff.ca/php/rncws.php";
      // Call the Web service at the target using the request message. Capture
      // the response message and send it to standard output.
      SOAPMessage response = con.call(request, endpoint);
      System.out.println("nSoap response:n");
      try
      {
         response.writeTo(System.out);
      }
      catch (IOException ioe)
      {
         JOptionPane.showMessageDialog(RomanNumerals.this,
                                       ioe.getMessage());
      }
      // Close the connection to release resources.
      con.close();
      // Return a response consisting of the reason for a SOAP Fault or the
      // value of the RomanToIntResponse body element's return child element.
      if (response.getSOAPBody().hasFault())
         return response.getSOAPBody().getFault().getFaultString();
      else
      {
         body = response.getSOAPBody();
         bodyName = new QName("urn:Roman-IRoman", "RomanToIntResponse", "NS1");
         Iterator iter = body.getChildElements(bodyName);
         bodyElement = (SOAPBodyElement) iter.next();
         iter = bodyElement.getChildElements(new QName("return"));
         return ((SOAPElement) iter.next()).getValue();
      }
   }

   String toRoman(String input) throws SOAPException
   {
      // Build a request message. The first step is to create an empty message
      // via a message factory. The default SOAP 1.1 message factory is used.
      MessageFactory mfactory = MessageFactory.newInstance();
      SOAPMessage request = mfactory.createMessage();
      // The request SOAPMessage object contains a SOAPPart object, which
      // contains a SOAPEnvelope object, which contains an empty SOAPHeader
      // object followed by an empty SOAPBody object.
      // Detach the header since a header is not required. This step is
      // optional.
      SOAPHeader header = request.getSOAPHeader();
      header.detachNode();
      // Access the body so that content can be added.
      SOAPBody body = request.getSOAPBody();
      // Add the IntToRoman operation body element to the body.
      QName bodyName = new QName("http://javajeff.ca/", "IntToRoman", "tns");
      SOAPBodyElement bodyElement = body.addBodyElement(bodyName);
      // Add the Int child element to the IntToRoman body element.
      QName name = new QName("Int");
      SOAPElement element = bodyElement.addChildElement(name);
      element.addTextNode(input).setAttribute("xsi:type", "xs:int");
      // Add appropriate namespaces and an encoding style to the envelope.
      SOAPEnvelope env = request.getSOAPPart().getEnvelope();
      env.addNamespaceDeclaration("env",
                                  "http://schemas.xmlsoap.org/soap/envelop/");
      env.addNamespaceDeclaration("enc",
                                  "http://schemas.xmlsoap.org/soap/encoding/");
      env.setEncodingStyle(SOAPConstants.URI_NS_SOAP_ENCODING);
      env.addNamespaceDeclaration("xs", "http://www.w3.org/2001/XMLSchema");
      env.addNamespaceDeclaration("xsi",
                                  "http://www.w3.org/2001/XMLSchema-instance");
      // Output the request just built to standard output, to see what the
      // SOAP message looks like (which is useful for debugging).
      System.out.println("nSoap request:n");
      try
      {
         request.writeTo(System.out);
      }
      catch (IOException ioe)
      {
         JOptionPane.showMessageDialog(RomanNumerals.this,
                                       ioe.getMessage());
      }
      System.out.println();
      // Prepare to send message by obtaining a connection factory and creating
      // a connection.
      SOAPConnectionFactory factory = SOAPConnectionFactory.newInstance();
      SOAPConnection con = factory.createConnection();
      // Identify the message's target.
      String endpoint = "http://www.javajeff.ca/php/rncws.php";
      // Call the Web service at the target using the request message. Capture
      // the response message and send it to standard output.
      SOAPMessage response = con.call(request, endpoint);
      System.out.println("nSoap response:n");
      try
      {
         response.writeTo(System.out);
      }
      catch (IOException ioe)
      {
         JOptionPane.showMessageDialog(RomanNumerals.this,
                                       ioe.getMessage());
      }
      // Close the connection to release resources.
      con.close();
      // Return a response consisting of the reason for a SOAP Fault or the
      // value of the IntToRomanResponse body element's return child element.
      if (response.getSOAPBody().hasFault())
         return response.getSOAPBody().getFault().getFaultString();
      else
      {
         body = response.getSOAPBody();
         bodyName = new QName("urn:Roman-IRoman", "IntToRomanResponse", "NS1");
         Iterator iter = body.getChildElements(bodyName);
         bodyElement = (SOAPBodyElement) iter.next();
         iter = bodyElement.getChildElements(new QName("return"));
         return ((SOAPElement) iter.next()).getValue();
      }
   }

   public static void main(String[] args)
   {
      EventQueue.invokeLater(() -> new RomanNumerals());
   }
}

class GPanel extends JPanel
{
   private GradientPaint gp;

   @Override
   public void paintComponent(Graphics g)
   {
      if (gp == null)
         gp = new GradientPaint(0, 0, Color.pink, 0, getHeight(), Color.orange);
      // Paint a nice gradient background with pink at the top and orange at
      // the bottom.
      ((Graphics2D) g).setPaint(gp);
      g.fillRect(0, 0, getWidth(), getHeight());
   }
}

Listing 3 combines Swing/Abstract Window Toolkit code for creating a user interface with SAAJ code for communicating with the Roman Numerals Conversion Web service.

The user interface consists of a pair of text fields and a pair of buttons. One of these text fields is used to enter the Roman numerals or base-10 integer digits of the value to be converted. The other text field displays the conversion result. Click one of the buttons to convert from Roman numerals to integer digits; click the other button to achieve the opposite conversion. In response to a button click, either the String toInteger(String input) method or the String toRoman(String input) method is called to perform the conversion.

Consider the GPanel (Gradient Panel) class. I introduced GPanel so that I could generate a colorful background for the application’s window.

GPanel extends javax.swing.JPanel to describe a custom panel whose surface is painted with a gradient whenever its inherited void paintComponent(Graphics g) method is called. This happens when the window is first displayed, and when the window is restored after being minimized (at least on Windows platforms).

GPanel uses the java.awt.GradientPaint class to paint the gradient. (I could have used the java.awt.LinearGradientPaint class instead, but flipped a coin and ended up using GradientPaint.) The first two arguments passed to this class’s constructor identify the upper-left corner of the rectangular area over which the gradient is drawn, the third argument specifies the color at the top of the gradient, the fourth and fifth arguments identify the rectangular area’s lower-right corner, and the final argument identifies the color at the bottom of the gradient.

Ideally, the user interface’s components appear over a gradient background, and not over some intermediate background. However, because the user interface is created from panels of components added to the gradient panel, the gradient panel’s surface will not show through these “upper” panels unless they are made transparent, by calling their void setOpaque(boolean opaque) method with false as the argument. For example, pnlInput.setOpaque(false); makes the input panel (the panel containing a label and input text field) transparent so that the gradient background shows through.

Listing 3 uses SOAPMessage‘s void writeTo(OutputStream out) method to output a request or response message to the standard output stream. You’ll find this feature helpful for understanding the relationship between SAAJ API calls and the SOAP messages that are constructed, especially if you’re having difficulty following the API calls. This feature is also helpful when you’ve created a SOAP-based Web service with a Service Endpoint Interface (SEI) and Service Implementation Bean (SIB) and are trying to create a SAAJ-based client.

Compiling and running the roman numerals application

Compile Listing 3 as follows:

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

You can omit --add-modules java.xml.ws when compiling under Java SE 8.

Run the resulting application as follows:

java --add-modules java.xml.ws RomanNumerals

Again, you can omit --add-modules java.xml.ws when running under Java SE 8.

Figure 2 shows the resulting window with an example conversion from 2017 to MMXVII.

Figure 2. Converting 2017 to its Roman Numerals counterpart

Converting 2017 to its Roman Numerals counterpart.

Additionally RomanNumerals outputs the following request and response SOAP messages (reformatted for readability):

Soap request:

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 
                   xmlns:enc="http://schemas.xmlsoap.org/soap/encoding/" 
                   xmlns:env="http://schemas.xmlsoap.org/soap/envelop/" 
                   xmlns:xs="http://www.w3.org/2001/XMLSchema" 
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
                   SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
   <SOAP-ENV:Body>
      <tns:IntToRoman xmlns:tns="http://javajeff.ca/">
         <Int xsi:type="xs:int">2017</Int>
      </tns:IntToRoman>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>


Soap response:


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

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 
                   xmlns:ns1="urn:Roman-IRoman" 
                   xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
                   xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" 
                   SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
   <SOAP-ENV:Body>
      <ns1:IntToRomanResponse>
         <return xsi:type="xsd:string">MMXVII</return>
      </ns1:IntToRomanResponse>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Extracting response values

Note the response MMXVII value. Each of Listing 3’s toInteger() and toRoman() methods extracts the response value by first checking the response message’s body to learn if it describes a fault. This task is accomplished by invoking SOAPBody‘s boolean hasFault() method.

If hasFault() returns true, SOAPBody‘s SOAPFault getFault() method is called. This method returns an object that describes the fault in terms of the SOAPFault interface’s methods, and SOAPFault‘s String getFaultString() method is called to return the string-based fault message.

If hasFault() returns false, the message’s body provides the response value that must be extracted. The following excerpt from the toRoman() method handles this extraction task:

body = response.getSOAPBody();
bodyName = new QName("urn:Roman-IRoman", "IntToRomanResponse", "NS1");
Iterator iter = body.getChildElements(bodyName);
bodyElement = (SOAPBodyElement) iter.next();
iter = bodyElement.getChildElements(new QName("return"));
return ((SOAPElement) iter.next()).getValue();

After calling SOAPMessage‘s SOAPBody getSOAPBody() convenience method to return the SOAPBody object describing the SOAP message’s body, the excerpt creates a QName object that identifies the qualified name for the IntToRomanResponse element. This object is then passed to SOAPBody‘s inherited Iterator getChildElements(QName qname) method to return a java.util.Iterator instance that will be used to iterate over all IntToRomanResponse child elements of the Body element.

Because there’s only one such child element, only a single call to next() is made to return this element, as a SOAPBodyElement instance. This object is used to invoke getChildElements(), but this time with the qualified name of the return element. The returned iterator’s next() method is called to extract the return element as a SOAPElement instance, and getValue() is invoked on this instance to return the value of the return element, which happens to be MMXVII.

Logging SOAP messages with a JAX-WS handler

The RomanNumerals application used SOAPMessage‘s void writeTo(OutputStream out) method to dump SOAP messages to the standard output stream. If you want to accomplish this task in the context of Part 2’s UCClient application, you need to install a JAX-WS handler.

JAX-WS lets you install a chain of handlers on a Web service class, a client class, or both to perform custom processing of request and response messages. For example, you might use a handler to add security information to the message or to log message details.

A handler is an instance of a class that ultimately implements the javax.xml.ws.handler.Handler<C extends MessageContext> interface in terms of the following methods:

  • void close(MessageContext context) is called at the conclusion of a Message-Exchange Pattern (MEP) just before the JAX-WS runtime dispatches a message, fault or exception. This method lets a handler clean up any resources used for processing request-only or request-response message exchanges.
  • boolean handleFault(C context) is invoked for fault message processing. This method returns true when the handler wants to continue handling fault messages; otherwise, it returns false. It may throw javax.xml.ws.ProtocolException (a subclass of javax.xml.ws.WebServiceException) or java.lang.RuntimeException to cause the JAX-WS runtime to cease the handler’s fault processing and dispatch the fault.
  • boolean handleMessage(C context) is invoked for normal processing of inbound and outbound messages. This method returns true when the handler wants to continue handling such messages; otherwise, it returns false. It may throw ProtocolException or RuntimeException to cause the JAX-WS runtime to cease the handler’s normal message processing and generate a fault.

Each method is called with a javax.xml.ws.handler.MessageContext or subinterface argument that stores a map of properties for handlers to use to communicate with each other and for other purposes. For example, MessageContext.MESSAGE_OUTBOUND_PROPERTY stores a java.lang.Boolean object that identifies a message’s direction. During a request (from client to Web service), this property’s value is Boolean.TRUE from a client handler’s perspective and Boolean.FALSE from a Web service handler’s perspective.

JAX-WS supports logical and protocol handlers. A logical handler is independent of the message protocol (it only has access to the message payload) and is associated with the javax.xml.ws.handler.LogicalMessageContext and javax.xml.ws.handler.LogicalHandler<C extends LogicalMessageContext> interfaces. In contrast, a protocol handler is tied to a specific protocol; JAX-WS supports SOAP protocol handlers with the javax.xml.ws.handler.soap.SOAPMessageContext and javax.xml.ws.handler.soap.SOAPHandler interfaces.

Logging SOAP messages to standard output

You need to work with SOAPMessageContext and SOAPHandler to log the flow of messages. Listing 4 presents a SOAPLoggingHandler class that implements SOAPHandler<SOAPMessageContext> to log the flow of SOAP messages by outputting them to the standard output stream.

Listing 4. Logging SOAP messages to standard output

import java.io.IOException;
import java.io.PrintStream;

import java.util.Map;
import java.util.Set;

import javax.xml.namespace.QName;

import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;

import javax.xml.ws.handler.MessageContext;

import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;

public final class SOAPLoggingHandler implements SOAPHandler<SOAPMessageContext>
{
   private static PrintStream out = System.out;

   @Override
   public Set<QName> getHeaders()
   {
      return null;
   }

   @Override
   public void close(MessageContext messageContext)
   {
   }

   @Override
   public boolean handleFault(SOAPMessageContext soapmc)
   {
      log(soapmc); 
      return true;
   }

   @Override
   public boolean handleMessage(SOAPMessageContext soapmc)
   {
      log(soapmc);
      return true;
   }

   private void log(SOAPMessageContext soapmc)
   {
      Boolean outboundProperty = (Boolean)
         soapmc.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
      if (outboundProperty.booleanValue())
         out.println("Outbound message:");
      else
         out.println("Inbound message:");
      SOAPMessage soapm = soapmc.getMessage();
      try
      {
         soapm.writeTo(out);
         out.println("n");
      }
      catch (IOException|SOAPException e)
      {
         out.println("Handler exception: " + e);
      }
   }
}

SOAPLoggingHandler first declares a java.io.PrintStream field named out that identifies the destination. Although System.out is assigned to out, you can assign a different output stream to this field for logging SOAP messages to another destination.

SOAPHandler introduces a Set<QName> getHeaders() method for informing the JAX-WS runtime about the SOAP headers that the handler is responsible for processing. This method returns a set of qualified names for those SOAP message header blocks that the handler can process. Although we must implement this method, it returns null because there are no headers to process.

The overriding close() method does nothing because there are no resources that need to be cleaned up. In contrast, handleFault() and handleMessage() invoke the private log() method to log a SOAP message.

The log() method uses its SOAPMessageContext argument to obtain the value of the property identified as MessageContext.MESSAGE_OUTBOUND_PROPERTY. The return value determines whether an Inbound message string or an Outbound message string is logged. log() next uses this argument to invoke the SOAPMessage getMessage() method, which returns a SOAPMessage object whose write(Object o) method is called to write the SOAP message to the stream identified by out.

Integrating the SOAP message handler into UCClient

After declaring the handler class, you need to instantiate it and add the instance to the client’s or Web service’s handler chain, either via the @HandlerChain annotation or programmatically. Listing 5 demonstrates the programmatic approach by adding a SOAPLoggingHandler instance to UCClient‘s (see Part 2) handler chain.

Listing 5. Adding a SOAPHandler instance to UCClient‘s handler chain

import java.net.URL;

import java.util.List;

import javax.xml.namespace.QName;

import javax.xml.ws.Binding;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.Service;

import javax.xml.ws.handler.Handler;

import ca.javajeff.uc.UC;

public class UCClient
{
   public static void main(String[] args) throws Exception
   {
      URL url = new URL("http://localhost:9901/UC?wsdl");
      QName qname = new QName("http://uc.javajeff.ca/",
                              "UCImplService");
      Service service = Service.create(url, qname);
      qname = new QName("http://uc.javajeff.ca/", "UCImplPort");
      UC uc = service.getPort(qname, UC.class);
//      UC uc = service.getPort(UC.class);
      BindingProvider bp = (BindingProvider) uc;
      Binding binding = bp.getBinding();
      List<Handler> hc = binding.getHandlerChain();
      hc.add(new SOAPLoggingHandler());
      binding.setHandlerChain(hc);
      System.out.printf("DC to DF: 37 DC = %f DF%n", uc.c2f(37.0));
      System.out.printf("CM to IN: 10 CM = %f IN%n", uc.cm2in(10));
      System.out.printf("DF to DC: 212 DF = %f DC%n", uc.f2c(212.0));
      System.out.printf("IN to CM: 10 IN = %f CM%n", uc.in2cm(10));
   }
}

Listing 5’s main() method accesses UCClient‘s handler chain and inserts an instance of SOAPLoggingHandler into this chain by completing the following steps:

  1. Cast the proxy instance returned from getPort() to javax.xml.ws.BindingProvider because the proxy instance’s class implements this interface. BindingProvider provides access to the protocol binding and associated context objects for request and response message processing.
  2. Call BindingProvider‘s Binding getBinding() method to return the protocol binding instance, which is an instance of a class that ultimately implements the javax.xml.ws.Binding interface – the class actually implements Binding‘s javax.xml.ws.soap.SOAPBinding subinterface.
  3. Invoke Binding‘s List<Handler> getHandlerChain() method on this instance to return a copy of the handler chain.
  4. Instantiate SOAPLoggingHandler and add this instance to the java.util.List instance of Handler instances.
  5. Pass this list of handlers to Binding‘s void setHandlerChain(List<Handler> chain) method.

Compile the contents of Listing 5 (see Part 2). Assuming that UCPublisher is running (see Part 2), run UCClient (see Part 2). You should observe the following output (reformatted for readbility):

Outbound message:
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/" 
            xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Header/>
  <S:Body xmlns:ns2="http://uc.javajeff.ca/">
    <ns2:c2f>
      <arg0>37.0</arg0>
    </ns2:c2f>
  </S:Body>
</S:Envelope>


Inbound message:
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/" 
            xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Header/>
  <S:Body xmlns:ns2="http://uc.javajeff.ca/">
    <ns2:c2fResponse>
      <return>98.6</return>
    </ns2:c2fResponse>
  </S:Body>
</S:Envelope>


DC to DF: 37 DC = 98.600000 DF
Outbound message:
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/" 
            xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Header/>
  <S:Body xmlns:ns2="http://uc.javajeff.ca/">
    <ns2:cm2in>
      <arg0>10.0</arg0>
    </ns2:cm2in>
  </S:Body>
</S:Envelope>


Inbound message:
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/" 
            xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Header/>
  <S:Body xmlns:ns2="http://uc.javajeff.ca/">
    <ns2:cm2inResponse>
      <return>3.937007874015748</return>
    </ns2:cm2inResponse>
  </S:Body>
</S:Envelope>


CM to IN: 10 CM = 3.937008 IN
Outbound message:
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/" 
            xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Header/>
  <S:Body xmlns:ns2="http://uc.javajeff.ca/">
    <ns2:f2c>
      <arg0>212.0</arg0>
    </ns2:f2c>
  </S:Body>
</S:Envelope>


Inbound message:
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/" 
            xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Header/>
  <S:Body xmlns:ns2="http://uc.javajeff.ca/">
    <ns2:f2cResponse>
      <return>100.0</return>
    </ns2:f2cResponse>
  </S:Body>
</S:Envelope>


DF to DC: 212 DF = 100.000000 DC
Outbound message:
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/" 
            xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Header/>
  <S:Body xmlns:ns2="http://uc.javajeff.ca/">
    <ns2:in2cm>
      <arg0>10.0</arg0>
    </ns2:in2cm>
  </S:Body>
</S:Envelope>


Inbound message:
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/" 
            xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Header/>
  <S:Body xmlns:ns2="http://uc.javajeff.ca/">
    <ns2:in2cmResponse>
      <return>25.4</return>
    </ns2:in2cmResponse>
  </S:Body>
</S:Envelope>


IN to CM: 10 IN = 25.400000 CM

The S: and ns2: namespace prefixes are generated by JAX-WS.

Creating a customized lightweight HTTP server for authentication

You can create a customized lightweight HTTP server that offers additional features for testing a Web service, and replace the default lightweight HTTP server that’s started in response to an Endpoint.publish() invocation with your server. What makes this possible is that Endpoint‘s void publish(Object serverContext) method can accept as its argument an instance of a class that subclasses the abstract com.sun.net.httpserver.HttpContext class.

This section shows you how to create a customized Lightweight HTTP Server for the purpose of demonstrating basic authentication with a Web service.

Authentication 101

Authentication is the process or action of verifying the identity of a user or a process. RFC 1945: Hypertext Transfer Protocol – HTTP/1.0 introduced support for authentication via a simple challenge-response mechanism that a server can use to challenge a client’s request to access some resource. A client can use this mechanism to provide credentials (typically username and password) that authenticate (prove) the client’s identity. When the supplied credentials satisfy the server, the user is authorized (allowed) to access the resource.

HTTP 1.0 introduced the basic authentication scheme by which a client identifies itself via a username and a password. The basic authentication scheme works as follows:

  1. The WWW-Authenticate header specifies Basic as the token and a single realm="quoted string" pair that identifies the realm (a protected space to which a resource belongs, such as a specific group of Web pages) referred to by the browser address.
  2. In response to this header, the browser displays a dialog box in which a username and password are entered.
  3. Once entered, the username and password are concatenated into a string (a colon is inserted between the username and password), the string is base64-encoded, and the result is placed in an Authorization header that’s sent back to the server.
  4. The server base64-decodes these credentials and compares them to values stored in its username/password database. When there’s a match, the application is granted access to the resource (and any other resource belonging to the realm).

Java supports basic authentication via the abstract java.net.Authenticator and java.net.PasswordAuthentication classes. Authenticator represents an object that knows how to obtain authentication for a network connection. This task is performed by overriding its PasswordAuthentication getPasswordAuthentication() method, which returns the password and user name supplied by the user or null when no such information is provided.

Demonstrating basic authentication via a customized lightweight HTTP server

Suppose you want to test basic authentication with the UC Web service introduced in Part 2 of this series. On the client side, you install a default authenticator that supplies a username and password to the Web service. Listing 6 reveals this authenticator in the context of UCClient.

Listing 6. Supporting basic authentication with the UCClient application

import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.net.URL;

import java.util.List;

import javax.xml.namespace.QName;

import javax.xml.ws.Binding;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.Service;

import javax.xml.ws.handler.Handler;

import ca.javajeff.uc.UC;

public class UCClient
{
   public static void main(String[] args) throws Exception
   {
      Authenticator auth;
      auth = new Authenticator()
      {
         @Override
         protected PasswordAuthentication getPasswordAuthentication()
         {
            return new PasswordAuthentication("x", new char[] { 'y' });
         }
      };
      Authenticator.setDefault(auth);

      URL url = new URL("http://localhost:9901/UC?wsdl");
      QName qname = new QName("http://uc.javajeff.ca/",
                              "UCImplService");
      Service service = Service.create(url, qname);
      qname = new QName("http://uc.javajeff.ca/", "UCImplPort");
      UC uc = service.getPort(qname, UC.class);
//      UC uc = service.getPort(UC.class);
      BindingProvider bp = (BindingProvider) uc;
      Binding binding = bp.getBinding();
      List<Handler> hc = binding.getHandlerChain();
      hc.add(new SOAPLoggingHandler());
      binding.setHandlerChain(hc);
      System.out.printf("DC to DF: 37 DC = %f DF%n", uc.c2f(37.0));
      System.out.printf("CM to IN: 10 CM = %f IN%n", uc.cm2in(10));
      System.out.printf("DF to DC: 212 DF = %f DC%n", uc.f2c(212.0));
      System.out.printf("IN to CM: 10 IN = %f CM%n", uc.in2cm(10));
   }
}

For simplicity, Listing 6 embeds x as the username and y as the password in the source code. A more useful and secure application would prompt for this information. At runtime the JVM invokes getPasswordAuthentication() to obtain these credentials and make them available to the HTTP server when requested to do so.

This method will not be called if the HTTP server doesn’t make a request, and Part 2’s version of UCPublisher will never cause the HTTP server to make this request. However, you can install a customized server that will result in this request, and Listing 7 presents an enhanced UCPublisher application that accomplishes this task.

Listing 7. Supporting basic authentication with the UCPublisher application

import java.io.IOException;

import java.net.InetSocketAddress;

import javax.xml.ws.Endpoint;

import com.sun.net.httpserver.BasicAuthenticator;
import com.sun.net.httpserver.HttpContext;
import com.sun.net.httpserver.HttpServer;

import ca.javajeff.uc.UCImpl;

public class UCPublisher
{
   public static void main(String[] args) throws IOException
   {
      HttpServer server = HttpServer.create(new InetSocketAddress(9901), 0);
      HttpContext context = server.createContext("/UC");
      BasicAuthenticator auth;
      auth = new BasicAuthenticator("myAuth")
      {
         @Override
         public boolean checkCredentials(String username, String password)
         {
            return username.equals("x") && password.equals("y");
         }
      };
      context.setAuthenticator(auth);
      Endpoint endpoint = Endpoint.create(new UCImpl());
      endpoint.publish(context);
      server.start();
   }
}

The main() method first creates an HttpServer instance that describes an HTTP server connected to port 9901 of the local host. This method next creates the /UC context, and returns the resulting HttpContext subclass object.

Continuing, the abstract com.sun.net.httpserver.BasicAuthenticator class is anonymously subclassed to describe a server side implementation of HTTP basic authentication; its boolean checkCredentials(String username, String password) method is called to verify the given name and password in the context of the basic authenticator’s realm. This method returns true for valid credentials, and false when they are invalid.

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.

At this point, doGet() executes Source result = dispatch.invoke(null);, passing null instead of a Source object as an argument because the provider’s doGet() method expects to receive its data as a query string. If an exception occurs during the invocation, a catch block outputs the exception information and exits doGet(). Otherwise, the result object’s XML content is transformed into a javax.xml.transform.dom.DOMResult object, which is processed via XPath expressions to obtain result data, which is then output.

If you were to run LibraryClient with Listing 9’s doGet() method (you’ll need to insert --add-module java.xml.ws on the javac and java command lines when using Java SE 9), and if you were to use the same book-related data presented in Part 3, you would observe the following output:

<?xml version="1.0" ?><response>book inserted</response>

<?xml version="1.0" ?><response>book inserted</response>

9781430210450
0201548550

Title: Advanced C+
Author: James O. Coplien
ISBN: 0201548550
Publication Year: 1992
Publisher: Addison Wesley

Title: Beginning Groovy and Grails
Author: Christopher M. Judd
Author: Joseph Faisal Nusairat
Author: James Shingler
ISBN: 9781430210450
Publication Year: 2008
Publisher: Apress

<?xml version="1.0" ?><response>book updated</response>

Title: Advanced C++
Author: James O. Coplien
ISBN: 0201548550
Publication Year: 1992
Publisher: Addison Wesley

<?xml version="1.0" ?><response>book deleted</response>

9781430210450

Conclusion

Now that you’ve completed this series on Web services in Java SE, I have some homework that will reinforce your understanding of what you’ve read:

  1. Create a SOAP-based Library Web service that recognizes two operations, expressed via methods void addBook(String isbn, String title) and String getTitle(String isbn). Create a LibraryClient application that invokes addBook() followed by getTitle() to test this Web service.
  2. Create a LibraryClientSAAJ application that uses SAAJ to perform the equivalent of LibraryClient‘s tasks. Use SOAPMessage‘s writeTo() method to output each of the request and response messages for the addBook and getTitle operations.
  3. The RESTful Web service described by Part 3’s Library class is flawed in that the doDelete() method doesn’t notify the client when requested to delete a nonexistent book. How might you modify this method to report this attempt?

You’ll find the answers to these exercises in the answersLibrary directory of the code archive that accompanies this article. To gain the most benefit from this series, give the exercises an honest try before peeking at the answers.

download
Get the source code for this post’s applications. Created by Jeff Friesen for JavaWorld

The following software was used to develop the post’s code:

  • 64-bit JDK 9ea+181

The post’s code was tested on the following platform(s):

  • JVM on 64-bit Windows 10