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:
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 AttachmentPart
s.
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); } }
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.
![]() |
![]() ![]() |