10.3.  Use Provider API to create a web service.

10.3.1.  Process the entire SOAP message, using the SAAJ APIs.

SAAJ overview

Although the SAAJ API can be used from JAX-WS applications, it can also be used alone. In fact, the SAAJ API contains all the classes that you need to send and receive SOAP messages. All the SAAJ classes belong to the javax.xml.soap package namespace.

As shown in the figure below, the core SAAJ API exposes classes that closely mimic the actual SOAP messages structure:

Figure 10.6. Core SAAJ 1.3 API

Core SAAJ 1.3 API


The core SAAJ API includes the following classes and interfaces:

  • SOAPMessage

    The SOAPMessage object represents the entire SOAP message. It has a single SOAPPart and possibly one or more AttachmentParts.

  • SOAPPart

    The SOAPPart object contains a SOAPEnvelope message. The SOAPEnvelope represents the actual SOAP Envelope.

  • SOAPEnvelope

    The SOAPEnvelope has an optional SOAPHeader and a mandatory SOAPBody.

  • SOAPHeader

    The SOAPHeader represents the SOAP header block in a SOAP message and is allowed to be empty as is the case with the header section in a SOAP 1.1 or 1.2 message.

  • SOAPBody

    The SOAPBody element can contain either a SOAPFault object or the actual SOAP payload XML content (SOAPBodyElement).

  • SOAPFault

    The SOAPFault object represents a SOAP fault message.

The SAAJ types and the types from the org.w3c.dom Java XML package are closely related. In fact, many of the classes in SAAJ extend or implement behavior from classes in the org.w3c.dom package namespace. An example of this is the SOAPPart object that implements the org.w3c.dom.Document interface.

Accessing Elements of a Message

There are two ways to do this.

First way:

SOAPPart soapPart = message.getSOAPPart();

SOAPEnvelope envelope = soapPart.getEnvelope();

SOAPHeader header = envelope.getHeader();

SOAPBody body = envelope.getBody();
						

Second way:

SOAPHeader header = message.getSOAPHeader();

SOAPBody body = message.getSOAPBody();
						

Adding Content to the Body

The SOAPBody object contains either content or a fault. To add content to the body, you normally create one or more SOAPBodyElement objects to hold the content. You can also add subelements to the SOAPBodyElement objects by using the addChildElement method. For each element or child element, you add content by using the addTextNode method.

When you create any new element, you also need to create an associated javax.xml.namespace.QName object so that it is uniquely identified.

QName objects associated with SOAPBodyElement or SOAPHeaderElement objects must be fully qualified; that is, they must be created with a namespace URI, a local part, and a namespace prefix. Specifying a namespace for an element makes clear which one is meant if more than one element has the same local name.

The following code fragment retrieves the SOAPBody object body from message, constructs a QName object for the element to be added, and adds a new SOAPBodyElement object to body:

SOAPBody body = message.getSOAPBody();
QName bodyName = new QName("http://wombat.ztrade.com", "GetLastTradePrice", "m");
SOAPBodyElement bodyElement = body.addBodyElement(bodyName);
						

At this point, body contains a SOAPBodyElement object identified by the QName object bodyName, but there is still no content in bodyElement. Assuming that you want to get a quote for the stock of Sun Microsystems, Inc., you need to create a child element for the symbol using the addChildElement method. Then you need to give it the stock symbol using the addTextNode method. The QName object for the new SOAPElement object symbol is initialized with only a local name because child elements inherit the prefix and URI from the parent element:

QName name = new QName("symbol");
SOAPElement symbol = bodyElement.addChildElement(name);
symbol.addTextNode("SUNW");
						

You might recall that the headers and content in a SOAPPart object must be in XML format. The SAAJ API takes care of this for you, building the appropriate XML constructs automatically when you call methods such as addBodyElement, addChildElement, and addTextNode. Note that you can call the method addTextNode only on an element such as bodyElement or any child elements that are added to it. You cannot call addTextNode on a SOAPHeader or SOAPBody object because they contain elements and not text.

The content that you have just added to your SOAPBody object will look like the following:


<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Body>
    <m:GetLastTradePrice xmlns:m="http://wombat.ztrade.com">
      <symbol>SUNW</symbol>
    </m:GetLastTradePrice>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

						

Getting the Content of a Message

The initial steps for retrieving a message's content are the same as those for giving content to a message: Either you use the SOAPMessage object to get the SOAPBody object, or you access the SOAPBody object through the SOAPPart and SOAPEnvelope objects.

Then you access the SOAPBody object's SOAPBodyElement object, because that is the element to which content was added in the example.

To get the content, which was added with the method SOAPElement.addTextNode, you call the method Node.getValue. Note that getValue returns the value of the immediate child of the element that calls the method. Therefore, in the following code fragment, the getValue method is called on bodyElement, the element on which the addTextNode method was called.

To access bodyElement, you call the getChildElements method on soapBody. Passing bodyName to getChildElements returns a java.util.Iterator object that contains all the child elements identified by the Name object bodyName. You already know that there is only one, so calling the next method on it will return the SOAPBodyElement you want. Note that the Iterator.next() method returns a Java Object, so you need to cast the Object it returns to a SOAPBodyElement object before assigning it to the variable bodyElement:

SOAPBody soapBody = response.getSOAPBody();
QName bodyName = new QName("http://wombat.ztrade.com", "GetLastTradePrice", "m");
java.util.Iterator iterator = soapBody.getChildElements(bodyName);
SOAPBodyElement bodyElement = (SOAPBodyElement)iterator.next();
String lastPrice = bodyElement.getValue();
						

Adding Content to the Header

To add content to the header, you create a SOAPHeaderElement object. As with all new elements, it must have an associated QName object.

For example, suppose you want to add a conformance claim header to the message to state that your message conforms to the WS-I Basic Profile. The following code fragment retrieves the SOAPHeader object from message and adds a new SOAPHeaderElement object to it. This SOAPHeaderElement object contains the correct qualified name and attribute for a WS-I conformance claim header:

SOAPHeader header = message.getSOAPHeader();
QName headerName = new QName("http://ws-i.org/schemas/conformanceClaim/", "Claim", "wsi");
SOAPHeaderElement headerElement = header.addHeaderElement(headerName);
headerElement.addAttribute(new QName("conformsTo"), "http://ws-i.org/profiles/basic/1.1/");
						

At this point, header contains the SOAPHeaderElement object headerElement identified by the QName object headerName. Note that the addHeaderElement method both creates headerElement and adds it to header.

A conformance claim header has no content. This code produces the following XML header:


<SOAP-ENV:Header>
  <wsi:Claim
      xmlns:wsi="http://ws-i.org/schemas/conformanceClaim/"
      conformsTo="http://ws-i.org/profiles/basic/1.1/"/>
</SOAP-ENV:Header>

						

For a different kind of header, you might want to add content to headerElement. The following line of code uses the method addTextNode to do this:

headerElement.addTextNode("order");
						

Creating an AttachmentPart Object and Adding Content

The SOAPMessage object creates an AttachmentPart object, and the message also must add the attachment to itself after content has been added. The SOAPMessage class has three methods for creating an AttachmentPart object.

The first method creates an attachment with no content. In this case, an AttachmentPart method is used later to add content to the attachment:

AttachmentPart attachment = message.createAttachmentPart();
						

You add content to attachment by using the AttachmentPart method setContent. This method takes two parameters: a Java Object for the content, and a String object for the MIME content type that is used to encode the object. Content in the SOAPBody part of a message automatically has a Content-Type header with the value "text/xml" because the content must be in XML. In contrast, the type of content in an AttachmentPart object must be specified because it can be any type.

Each AttachmentPart object has one or more MIME headers associated with it. When you specify a type to the setContent method, that type is used for the header Content-Type. Note that Content-Type is the only header that is required. You may set other optional headers, such as Content-Id and Content-Location. For convenience, SAAJ provides get and set methods for the headers Content-Type, Content-Id, and Content-Location. These headers can be helpful in accessing a particular attachment when a message has multiple attachments. For example, to access the attachments that have particular headers, you can call the SOAPMessage method getAttachments and pass it a MIMEHeaders object containing the MIME headers you are interested in.

The following code fragment shows one of the ways to use the method setContent. The Java Object in the first parameter can be a String, a stream, a javax.xml.transform.Source object, or a javax.activation.DataHandler object. The Java Object being added in the following code fragment is a String, which is plain text, so the second argument must be "text/plain". The code also sets a content identifier, which can be used to identify this AttachmentPart object. After you have added content to attachment, you must add it to the SOAPMessage object:

String stringContent = "10 Upbeat Street, Pleasant Grove, CA 95439";
attachment.setContent(stringContent, "text/plain");
attachment.setContentId("update_address");

message.addAttachmentPart(attachment);
						

The other two SOAPMessage.createAttachment methods create an AttachmentPart object complete with content. One is very similar to the AttachmentPart.setContent method in that it takes the same parameters and does essentially the same thing. It takes a Java Object containing the content and a String giving the content type. As with AttachmentPart.setContent, the Object can be a String, a stream, a javax.xml.transform.Source object, or a javax.activation.DataHandler object.

The other method for creating an AttachmentPart object with content takes a DataHandler object, which is part of the JavaBeans Activation Framework (JAF). Using a DataHandler object is fairly straightforward. First, you create a java.net.URL object for the file you want to add as content. Then you create a DataHandler object initialized with the URL object:

URL url = new URL("http://java.boot.by/img.jpg");
DataHandler dataHandler = new DataHandler(url);
AttachmentPart attachment = message.createAttachmentPart(dataHandler);
attachment.setContentId("attached_image");

message.addAttachmentPart(attachment);
						

You might note two things about this code fragment. First, it sets a header for Content-ID using the method setContentId. This method takes a String that can be whatever you like to identify the attachment. Second, unlike the other methods for setting content, this one does not take a String for Content-Type. This method takes care of setting the Content-Type header for you, something that is possible because one of the things a DataHandler object does is to determine the data type of the file it contains.

Accessing an AttachmentPart Object

If you receive a message with attachments or want to change an attachment to a message you are building, you need to access the attachment. The SOAPMessage class provides two versions of the getAttachments method for retrieving its AttachmentPart objects. When it is given no argument, the method SOAPMessage.getAttachments() returns a java.util.Iterator object over all the AttachmentPart objects in a message. When getAttachments is given a MimeHeaders object, which is a list of MIME headers, getAttachments returns an iterator over the AttachmentPart objects that have a header that matches one of the headers in the list. The following code uses the getAttachments method that takes no arguments and thus retrieves all the AttachmentPart objects in the SOAPMessage object message. Then it prints the content ID, the content type, and the content of each AttachmentPart object:

java.util.Iterator iterator = message.getAttachments();
while (iterator.hasNext()) {
    AttachmentPart attachment = (AttachmentPart)iterator.next();
    String id = attachment.getContentId();
    String type = attachment.getContentType();
    System.out.print("Attachment " + id + " has content type " + type);
    if (type.equals("text/plain")) {
        Object content = attachment.getContent();
        System.out.println("Attachment contains:\n" + content);
    }
}
						

10.3.2.  Process only the SOAP body, using JAXB.

Developing a dispatch client that uses JAXB

Example below demonstrates how JAX-WS allows you to work with JAXB objects from a dispatch client:

import by.boot.java.ObjectFactory;
import by.boot.java.SayHello;
import by.boot.java.SayHelloResponse;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
import javax.xml.ws.Dispatch;
import javax.xml.ws.Service;
import javax.xml.ws.soap.SOAPBinding;

public class HelloJaxbClient {

    private static final String TNS = "http://java.boot.by/";

    public static void main(String... args) throws Exception {

        // Define the service name, port name, and endpoint address
        QName serviceName = new QName(TNS, "HelloMessengerService");
        QName portName = new QName(TNS, "HelloMessenger");
        String endpoint = "http://localhost:80/Hello";

        // Create a service that can bind to the HelloMessenger port
        Service service = Service.create(serviceName);
        service.addPort(portName, SOAPBinding.SOAP11HTTP_BINDING, endpoint);

        // Create a JAXB enabled Dynamic Dispatch client
        JAXBContext context = JAXBContext.newInstance("by.boot.java");
        Dispatch<Object> dispatch = service.createDispatch(portName, context, Service.Mode.PAYLOAD);

        // Create JAXB request object
        ObjectFactory objectFactory = new ObjectFactory();
        SayHello request = objectFactory.createSayHello();
        request.setArg0("Mikalai");

        JAXBElement<SayHello> requestMessage = objectFactory.createSayHello(request);

        // Invoke the HelloMessenger Web service
        JAXBElement<SayHelloResponse> responseMessage = (JAXBElement<SayHelloResponse>)dispatch.invoke(requestMessage);

        // Get the JAXB response
        SayHelloResponse response = responseMessage.getValue();
        String value = response.getReturn();

        // Print the response
        System.out.println(value);
    }
}
						

The lines in bold illustrate where JAXB functionality is being used.

The key point to note with regards to the dispatch client creation is that the Service.createDispatch method is invoked with a JAXBContext and a PAYLOAD service mode. The PAYLOAD argument specifies to the JAX-WS run time that it should take care of handling the SOAP envelope details. The JAXBContext argument specifies that we will let JAXB handle the marshalling and unmarshalling of the actual SOAP payload.

After creation of the dispatch client, the application demonstrates how the generated ObjectFactory class is used to produce the request objects that will be sent over the wire. Since the generated classes (SayHello and SayHelloResponse) in this example have not been annotated with a @XmlRootElement by the JAXB binding compiler, JAXB requires that they be wrapped in a JAXBElement wrapper.

Developing a JAX-WS logical handler that uses JAXB

JAXB works well with logical handlers that are unaware of the actual transport protocol details. Example below demonstrates a logical JAX-WS handler that transforms outgoing payload text to uppercase:

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.ws.LogicalMessage;
import javax.xml.ws.handler.LogicalHandler;
import javax.xml.ws.handler.LogicalMessageContext;
import javax.xml.ws.handler.MessageContext;

public class UppercaseMessageHandler implements LogicalHandler<LogicalMessageContext> {

    public boolean handleMessage(LogicalMessageContext ctx) {
        String outboundProp = MessageContext.MESSAGE_OUTBOUND_PROPERTY;
        boolean outbound = (Boolean) ctx.get(outboundProp);
        if (outbound) {
            try {
                LogicalMessage message = ctx.getMessage();

                JAXBContext context = JAXBContext.newInstance(SayHelloResponse.class);
                SayHelloResponse response = (SayHelloResponse) message.getPayload(context);
                response.setReturn(response.getReturn().toUpperCase());
                message.setPayload(response, context);

            } catch (JAXBException e) {
                e.printStackTrace();
            }
        }
        return true /* continue chain */;
    }

    public void close(MessageContext ctx) {}

    public boolean handleFault(LogicalMessageContext ctx) {
        return false;
    }
}
						

The lines that involve JAXB are highlighted in bold. The handler checks whether the current message being handled is outbound or inbound. If the message is outbound, it gets the JAXB payload object from the LogicalMessage, updates the return value to uppercase, and overrides the entire payload. The SayHelloResponse object in this example was generated with an @XmlRootElement annotation. Therefore, we did not need to be concerned with the JAXBElement wrapper.

Professional hosting         'Oracle Certified Expert Web Services Developer 6' Quiz     Free SCDJWS 5.0 Guide