To read and manipulate SOAP header blocks in JAX-RPC you have to use message handlers, which are covered in Chapter 14. Message handlers, however, use SOAP with Attachments API for Java (SAAJ), version 1.2, to represent an XML SOAP message. Therefore, you need to understand how to use SAAJ before you can learn how to use message handlers. It's the purpose of this chapter to teach you how to create, read, and manipulate SOAP messages using SAAJ. While SAAJ is central to the JAX-RPC Message Handler API, it's also useful by itself. In fact, it's easier to understand SAAJ by discussing it outside the context of JAX-RPC. This chapter explains the SAAJ programming model and explains how to use it as a standalone API. Chapter 14: Message Handlers explains how to use SAAJ with JAX-RPC.
SAAJ is an API-based SOAP toolkit, which means that it models the structure of SOAP messages. SAAJ models SOAP Messages with Attachments (SwA) in Java. SwA is the MIME message format for SOAP.[1] For all practical purposes SwA is a standard and is used throughout the Web services industry. While SAAJ models plain SOAP messages as well as SwA, the WS-I Basic Profile does not endorse SwA, so it's not covered in this part of the book. You're very likely to encounter SwA messaging at some time, though, so this book provides detailed coverage of SAAJ's support for SwA in Appendix F.
SAAJ (rhymes with “page”) is an API you can use to create, read, or modify SOAP messages using Java. It includes classes and interfaces that model the SOAP Envelope
, Body
, Header
, and Fault
elements, along with XML namespaces, elements, and attributes, and text nodes and MIME attachments. SAAJ is similar to JDBC, in that it's a hollow API. You can't use it by itself; you need a vendor implementation. Each J2EE vendor will provide its own SAAJ implementation. They should all function the same way, although some may be more efficient than others.
You can use SAAJ to manipulate simple SOAP messages (just the XML without any attachments) or more complex SOAP messages with MIME attachments. SAAJ is used in combination with JAX-RPC (Java API for XML-based RPC), which is the J2EE standard API for sending and receiving SOAP messages. SAAJ can also be used independently of JAX-RPC and has its own, optional facilities for basic Request/ Response-style messaging over HTTP or other protocols.
SAAJ's network communication is based on the
java.net.URL
class, which can be extended to support any network protocol, not just HTTP. HTTP is the only protocol actually sanctioned by the Basic Profile, however.
Java developers can use SAAJ to work with SOAP messages within any SOAP application, including initial senders, intermediaries, and ultimate receivers. For example, you might develop a SOAP intermediary that processes a specific header block before sending the message on to the next receiver. Using SAAJ you can easily examine a SOAP message, extract the appropriate header block, then send the message along to the next node in the message path. Similarly, an ultimate receiver can use SAAJ to process the application-specific content of the SOAP body.
SAAJ is based on the Abstract Factory Pattern,[2] which means SAAJ is a family of types in which each type of object is manufactured by another type in the SAAJ family. The root of the Abstract Factory Pattern in SAAJ is the MessageFactory
class. It's responsible for manufacturing an instance of itself, which can in turn be used to manufacture a SOAPMessage
. A SOAPMessage
contains a SOAPPart
, which represents the SOAP document, and zero or more AttachmentPart
objects, which represent MIME attachments (such as GIFs and PDFs).
The SOAPPart
contains a family of objects that model the SOAP document, including the Envelope
, Header
, and Body
elements. You can obtain a clearer understanding of SAAJ by examining the basic structure of the SAAJ API alongside a diagram of an SwA message. As you can see from Figure 13-1, the SAAJ API models the exact structure of an SwA message.
SAAJ 1.2 is also based, in part, on the W3C Document Object Model (DOM), version 2. This relationship is explained in more detail in Section 13.6: SAAJ 1.2 and DOM 2.
The best way to learn SAAJ is to jump right in and build a simple SOAP message using the API. Listing 13-1 shows the BookQuote request message you've seen before.
Example 13-1. A Simple SOAP Message
<?xml version="1.0" encoding="UTF-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:mh="http://www.Monson-Haefel.com/jwsbook/BookQuote"> <soap:Body> <mh:getBookPrice> <isbn>0321146182</isbn> </mh:getBookPrice> </soap:Body> </soap:Envelope>
SaajExample_1
in Listing 13-2 uses the SAAJ API to create the SOAP message, which is semantically equivalent to the SOAP message shown in Listing 13-1.
Example 13-2. Using SAAJ to Create a Simple SOAP Message
package com.jwsbook.saaj; import javax.xml.soap.*; public class SaajExample_1 { public static void main(String [] args) throws SOAPException{ MessageFactory msgFactory = MessageFactory.newInstance(); SOAPMessage message = msgFactory.createMessage(); message.getSOAPHeader().detachNode(); SOAPBody body = message.getSOAPBody(); SOAPElement getBookPrice = body.addChildElement( "getBookPrice", "mh", "http://www.Monson-Haefel.com/jwsbook/BookQuote"); getBookPrice.setEncodingStyle(SOAPConstants.URI_NS_SOAP_ENCODING); SOAPElement isbn = getBookPrice.addChildElement("isbn"); isbn.addTextNode("0321146182"); SaajOutputter.writeToScreen(message); } }
To run SaajExample_1
and the rest of the examples in this book, you'll need to have a J2EE platform installed, the proper classpaths set up, and supporting Web services deployed. You'll know if SaajExample_1
is working properly by the lack of error messages—if there is a problem a SOAPException
will be thrown and displayed on the output screen.
The rest of this chapter will use SaajExample_1
and similar examples to discuss the SAAJ API and how to use its various classes and interfaces. Although you will frequently use the SAAJ API inside J2EE message handlers for JAX-RPC service endpoints and EJB endpoints, the examples in this chapter are standalone applications. This approach spares you the arduous process of deploying a J2EE component every time you want to test an example.
To create a simple SOAP document, you obtain a new SOAPMessage
object from a MessageFactory
object, as shown in this snippet from SaajExample_1
(Listing 13-2):
MessageFactory msgFactory = MessageFactory.newInstance(); SOAPMessage message = msgFactory.createMessage();
Although you will usually work with basic SOAP messages, without attachments, the SAAJ API models SwA, not just SOAP. Appendix F: SAAJ Attachments shows how to create an SwA message in which the SOAP document is treated as a MIME part, and accessed via the SOAPMessage.getSOAPPart()
method.
The MessageFactory
is the root factory of SAAJ. It's the class you will start with each time you create a SOAP message. MessageFactory
is an abstract class that contains three methods, as shown in Listing 13-3 (the implementations are omitted).
Example 13-3. The javax.xml.soap.MessageFactory
Class
package javax.xml.soap; public abstract class MessageFactory { private static final String DEFAULT_MESSAGE_FACTORY = "com.sun.xml.messaging.saaj.soap.MessageFactoryImpl"; private static final String MESSAGE_FACTORY_PROPERTY = "javax.xml.soap.MessageFactory"; public static MessageFactory newInstance() throws SOAPException; public SOAPMessage createMessage() throws SOAPException; public SOAPMessage createMessage(MimeHeaders headers,java.io.InputStream in) throws SOAPException; }
The MessageFactory
class itself is abstract and cannot be instantiated. Its new Instance()
method creates an object that is actually a subtype of MessageFactory
. By default, the instance is of a proprietary type provided by Sun Microsystems, when SAAJ is employed as a standalone API.
In addition to the newInstance()
method, the MessageFactory
has two create Message()
methods. The first takes no arguments, and is the one used in SaajExample_1
. It simply creates a new SOAPMessage
. The following code line from Listing 13-2 shows how the createMessage()
method is used in an application.
SOAPMessage message = msgFactory.createMessage();
In this case the SOAPMessage
object is generated from scratch, and contains only the framework of a SOAP message. If you were to dump the contents of the SOAPMessage
instance to the screen, it would look something like the following.
<?xml version="1.0" encoding="UTF-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Header/> <soap:Body/> </soap:Envelope>
Notice that the main message elements, Envelope
, Header
, and Body
, are present, the last two empty. These are supplied as a convenience for the developer. Once the SOAPMessage
is created, all you need to do is fill in the blanks using the SAAJ API.
The MessageFactory
's second create method can construct a SAAJ representation of an existing SOAP message, instead of building a new one from scratch. The following snippet from Listing 13-3 shows the createMessage()
method declaration.
public abstract class MessageFactory {
...
public SOAPMessage createMessage(MimeHeaders headers,java.io.InputStream in)
throws SOAPException;
}
The MimeHeaders
parameter holds one or more MIME headers. A MIME header is a name-value pair that describes the contents of a MIME block. For example, a SOAP document might have a MIME header with a name-value pair of "Content-Type = text/xml"
.
The InputStream
parameter can be any kind of stream. For example it could be a network stream from a socket connection, an IO stream from a JDBC connection, or a simple file stream. The data obtained from the InputStream
parameter must be a valid SOAP or SwA message. For example, suppose a file called soap.xml
contains a SOAP document like the one shown in Listing 13-4.
Example 13-4. The soap.xml
File
<?xml version="1.0" encoding="UTF-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:mh="http://www.Monson-Haefel.com/jwsbook/BookQuote"> <soap:Body> <mh:getBookPrice> <isbn>0321146182</isbn> </mh:getBookPrice> </soap:Body> </soap:Envelope>
Using a FileInputStream
, the MessageFactory
class can read the soap.xml
file and generate a SAAJ object graph of the SOAP message it contains. In Listing 13-5 SaajExample_2
uses the MessageFactory
class to read the file and generate a SOAPMessage
.
Example 13-5. Building a SAAJ Object Graph from a File
package com.jwsbook.saaj;
import java.io.FileInputStream;
import javax.xml.soap.*;
public class SaajExample_2 {
public static void main(String [] args)
throws SOAPException, java.io.IOException{
MessageFactory msgFactory = MessageFactory.newInstance();
MimeHeaders mimeHeaders = new MimeHeaders();
mimeHeaders.addHeader("Content-Type","text/xml; charset=UTF-8");
FileInputStream file = new FileInputStream("soap.xml");
SOAPMessage message = msgFactory.createMessage(mimeHeaders, file);
file.close();
SaajOutputter.writeToScreen(message);
}
}
In SaajExample_2
the MIME header "Content-Type=text/xml;charset =UTF-8"
is added to an instance of MimeHeaders
, which is the first parameter of the createMessage()
method. The second parameter is an instance of FileInput Stream
that points to the soap.xml
file.
To summarize: You can use MessageFactory
to create new SOAP messages from scratch or from an existing SOAP message obtained from some type of input stream. In many cases, however, you will use SAAJ in combination with JAX-RPC, as explained in Chapter 14, and receive a complete SAAJ message automatically. In such cases you will not need to use either of the createMessage()
methods.
To become skilled with SOAP, you must be able to read the SOAP messages generated by SAAJ, so this book uses a custom-built class called SaajOutputter
to write SOAPMessage
objects out in a nice, readable format. The following snippet shows how SaajOutputter
is used.
MessageFactory msgFactory = MessageFactory.newInstance();
SOAPMessage message = msgFactory.createMessage();
SaajOutputter.writeToScreen(message);
SaajOutputter
is not a part of the SAAJ API. It was developed specifically for this book and belongs to the com.jwsbook.saaj
package, the example code for this chapter. As an alternative you can simply use the SOAPMessage.writeTo()
method (using System.out
as the parameter), but the output will have no line breaks and won't be as easy to read.
SaajOutputter
is a simple hack that works only when theSOAPMessage
does not contain attachments. If theSOAPMessage
contains attachments, useSOAPMessage.writeTo()
instead ofSaajOutputter
.
In many cases, the SOAP message you're working with will not have attachments and will not need the MIME message format. The SAAJ implementations are smart enough to recognize when no attachments are added, and omit the MIME packaging when writing the message. If you look closely at SaajExample_1
(Listing 13-2), you'll notice that no MIME attachments are added, and that the output of the program is strictly XML and doesn't include any MIME headers or boundaries. When SaajExample_1
executed, the SAAJ toolkit realized that no attachments had been added, so it didn't enforce the use of the MIME message format.
Most of the methods defined by the SOAPMessage
class are related to SwA MIME parts; these are covered in Appendix F: SAAJ Attachments. The only methods that are relevant to this chapter are writeTo()
, getSOAPBody()
, getSOAPHeader()
, getProperty()
, and setProperty()
.
The SOAPMessage.writeTo()
method simply writes the message represented by the SOAPMessage
object to an output stream. If the SOAPMessage
has no attachments, writeTo()
will write only the XML SOAP part of the message to the stream. For example, in the following code snippet a SOAPMessage
writes its contents to System.out
(the screen) using writeTo()
.
MessageFactory msgFactory = MessageFactory.newInstance(); SOAPMessage soapMessage = msgFactory.createMessage(); soapMessage.writeTo(System.out);
The output from the SOAPMessage.writeTo()
method is the default content of the SOAP message without line breaks. The lack of line breaks and white space (except between the XML declaration and the SOAP message) creates a tight stream of text. Because SOAP message are, in practice, processed by software and not read by people, readability is not important; eliminating line breaks and unnecessary white space is considered more efficient.
The root MIME part of any SwA message is always the XML SOAP document; this is covered in more detail in Appendix F. The getSOAPPart()
method allows you to access the SOAP MIME part directly, as shown in this snippet.
MessageFactory msgFactory = MessageFactory.newInstance();
SOAPMessage message = msgFactory.createMessage();
SOAPPart soap = message.getSOAPPart();
Accessing the SOAPBody
and SOAPHeader
via the SOAPPart
is not necessary, however, if the SOAP message does not use attachments, as will be the case with BP-conformant Web services. When there are no attachments, you can simply use the getSOAPBody()
and getSOAPHeader()
methods to access those elements in the SOAP message directly. The following snippet from SaajExample_1
shows how these methods are used.
MessageFactory msgFactory = MessageFactory.newInstance(); SOAPMessage message = msgFactory.createMessage(); message.getSOAPHeader().detachNode(); SOAPBody body = message.getSOAPBody();
The examples in this chapter use the getSOAPBody()
and getSOAPHeader()
methods. In Appendix F: SAAJ Attachments, the getSOAPPart()
and getSOAPEnvelope()
methods are used. The detachNode()
method simply removes the Header
element from the SOAP message—SAAJ always includes the Header
by default. Header blocks are not used in this example, so the Header
element is not needed.
The SOAPMessage
type also defines the setProperty()
and getProperty()
methods, which are used to set and obtain standard and vendor-specific properties of a SOAPMessage
object. There are two standard properties:
javax.xml.soap.CHARACTER_SET_ENCODING
can be either "UTF-8"
or "UTF-16"
in applications that conform with the BP. The default is "UTF-8"
.BP
javax.xml.soap.WRITE_XML_DECLARATION
can have a value of "true"
or "false"
. If it's "true"
, then the XML declaration is included in the SOAP message; if "false"
, it is not. The default is "false"
. Web service endpoints must accept SOAP 1.1 messages with or without an XML declaration.BP
As an example, the following snippet modifies SaajExample_2
(Listing 13-5) so that it sets the values of CHARACTER_SET_ENCODING
and WRITE_XML_DECLARATION
to "UTF-16"
and "true"
respectively.
FileInputStream file = new FileInputStream("soap.xml"); SOAPMessage message = msgFactory.createMessage(mimeHeaders, file); message.setProperty("javax.xml.soap.CHARACTER_SET_ENCODING", "UTF-16"); message.setProperty("javax.xml.soap.WRITE_XML_DECLARATION", "true");
With these properties set, the output for SaajExample_2
would be an XML document in UTF-16 with an XML document declaration, as follows:
<?xml version="1.0" encoding="UTF-16"?>
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:mh="http://www.Monson-Haefel.com/jwsbook/BookQuote">
<soap:Body>
<mh:getBookPrice>
<isbn>0321146182</isbn>
</mh:getBookPrice>
</soap:Body>
</soap:Envelope>
This SOAP message is exactly the same as the one in Listing 13-4, except that it includes the XML declaration shown in bold. XML declarations are explained in more detail in Section 2.1.2.1 and Section 4.1. The fact that the SOAP message is encoded using UTF-16 rather than UTF-8 is not visible to the eye.
SAAJ provides a number of interfaces you can use to construct a simple SOAP document. This section covers most of these types; the SOAP fault types are covered in Section 13.4. Figure 13-2 is an inheritance class diagram that shows all the SOAP document elements. The gray types are fault types, which are covered later.
A SOAP message or document is an XML instance composed of elements and attributes. As a convenience each of the major parts of a SOAP document has a corresponding type in SAAJ. The Envelope
is represented by SOAPEnvelope
, the Header
is represented by SOAPHeader
, the Body
by SOAPBody
, and so on. The SOAPElement
type is used for application-specific elements that don't belong to the SOAP 1.1 namespace. Figure 13-3 shows the correlation between SOAP elements and SAAJ types.
When working with SwA messages you will frequently make use of the SOAPPart
and SOAPEnvelope
types. The SOAPPart
represents the root MIME part of an SwA message, which is always the SOAP XML document. You access the SOAPPart
of an SwA message by invoking the SOAPMessage.getSOAPPart()
method. Because the BP doesn't support SwA, you may never need to access the SOAPPart
, because you may never use SwA. That said, Appendix F: SAAJ Attachments provides a detailed discussion of the SOAPPart
, in case you ever need to transmit or process SwA messages.
You can obtain a reference to the SOAPEnvelope
by invoking the getEnvelope()
method of a SOAPPart
. The SOAPEnvelope
represents the root of the XML SOAP document. It includes methods for accessing or creating the SOAPHeader
and SOAPBody
. Unless you're working with attachments, you won't usually need to deal with the SOAPEnvelope
type, because SAAJ constructs the SOAPEnvelope
automatically when you create a new SOAPMessage
object. In addition, you can access the SOAPBody
and SOAPHeader
types directly from the SOAPMessage
object.
The SOAPFactory
provides two factory methods for creating Name
type objects. As you already know from Section 2.2: XML Namespaces, the name of an element or attribute dictates which XML namespace it belongs to. The Name
type is simply an abstraction of an XML qualified name. For example, in Listing 13-6 SaajExample_3
shows how the SOAPFactory
is used to create the getBookPrice
and isbn
elements in the BookPrice
SOAP message.
Example 13-6. Creating and Using Name
Objects
package com.jwsbook.saaj; import javax.xml.soap.*; public class SaajExample_3 { public static void main(String [] args) throws SOAPException, java.io.IOException{ MessageFactory msgFactory = MessageFactory.newInstance(); SOAPMessage message = msgFactory.createMessage(); SOAPFactory soapFactory = SOAPFactory.newInstance(); Name getBookPrice_Name = soapFactory.createName("getBookPrice","mh", "http://www.Monson-Haefel.com/jwsbook/BookQuote"); Name isbnName = soapFactory.createName("isbn"); SOAPBody body = message.getSOAPBody(); SOAPBodyElement getBookPrice_Element = body.addBodyElement(getBookPrice_Name); getBookPrice_Element.addChildElement( isbnName ); SaajOutputter.writeToScreen(message); } }
The following code shows the output from SaajExample_3
: a SOAP document that contains the XML names corresponding to ones created using the SOAPFactory
object.
<?xml version="1.0" encoding="UTF-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:mh="http://www.Monson-Haefel.com/jwsbook/BookQuote"> <soap:Body> <mh:getBookPrice> <isbn/> </mh:getBookPrice> </soap:Body> </soap:Envelope>
The preceding SOAP message is not complete (it's missing a value for the isbn
element), but it does demonstrate how you can add new elements via Name
objects created by the SOAPFactory
. There is another way to add new elements without having to create Name
objects first. This technique was demonstrated in SaajExample_1
, and I'll expand on it when we talk about the SOAPElement
methods.
Remember that a Name
type represents an XML qualified name. The SOAP Envelope
or the SOAPFactory
can create Name
objects—the SOAPFactory
is a general-purpose factory class. When a Name
object is created, it's assigned either a local name or a fully qualified name that includes the local name, a prefix, and a URI identifying the XML namespace. Listing 13-7 shows the definition of the Name
interface, which declares four methods for obtaining various parts of the XML name as String
values.
Example 13-7. The javax.xml.soap.Name
Interface
package javax.xml.soap; public interface Name { public String getLocalName(); public String getPrefix(); public String getQualifedName(); public String getURI(); }
Each Name
object is associated with an element or attribute. For example, in Listing 13-8 the getBookPrice
element declares a local name, prefix, for an XML namespace.
Example 13-8. XML Namespaces in a SOAP Document
<?xml version="1.0" encoding="UTF-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:mh="http://www.Monson-Haefel.com/jwsbook/BookQuote"> <soap:Body> <mh:getBookPrice> <isbn/> </mh:getBookPrice> </soap:Body> </soap:Envelope>
Table 13-1. Name
Properties for the getBookPrice
Element in Listing 13-8
Method | Return Value |
---|---|
|
|
|
|
|
|
|
|
The String
values returned by the Name
object associated with the getBookPrice
element in Listing 13-8 are shown in Table 13-1.
While the Name
object obtained from the getBookPrice
element has values for each accessor, the Name
object for the isbn
element would contain only a local name and qualified name of "isbn"
—it would not contain a prefix or a URI. In other words, the Name
object is a very literal representation of the XML name used in the corresponding SOAP document; it is not derived and will not contain inherited namespaces or prefixes. In the absence of a prefix, the qualified name is equal to the local name. Table 13-2 shows the String
values that will be returned when invoking the methods of the Name
object that represents the isbn
element.
Table 13-2. Name
Properties for the isbn
Element in Listing 13-8
Method | Return Value |
---|---|
|
|
|
|
|
|
|
|
The application-specific elements, those that are not part of the SOAP 1.1 XML namespace, are represented directly by objects of the SOAPElement
type. This type can represent any XML element. It contains methods for accessing the child elements, attributes, namespace information, and so on. Just as an XML element may contain other XML elements, a SOAPElement
may contain other SOAPElement
objects. The SOAPElement
type models a hierarchical structure that corresponds to the hierarchical structure of XML. As you saw in Figure 13-2, the SOAPElement
type is the supertype of the other SOAP types, including SOAPEnvelope
, SOAPBody
, SOAP BodyElement
, SOAPHeader
, SOAPHeaderElement
, and the fault elements, which are covered later. The Node
type is the supertype of SOAPElement
.
As the supertype of all other SOAP element types, SOAPElement
contains a number of useful methods you can use to create and access child elements, attributes, namespace declarations, the element name, and the encoding style. Listing 13-9 shows the definition of the SOAPElement
type.
Example 13-9. The javax.xml.soap.SOAPElement
Interface
package javax.xml.soap; import java.util.Iterator; public interface SOAPElement extends Node, org.w3c.dom.Element { public SOAPElement addAttribute(Name name, String value) throws SOAPException; public SOAPElement addChildElement(Name name) throws SOAPException; public SOAPElement addChildElement(SOAPElement element) throws SOAPException; public SOAPElement addChildElement(String localName) throws SOAPException; public SOAPElement addChildElement(String localName, String prefix) throws SOAPException; public SOAPElement addChildElement(String localName, String prefix, String uri) throws SOAPException; public SOAPElement addNamespaceDeclaration(String prefix, String uri) throws SOAPException; public SOAPElement addTextNode(String text); public Iterator getAllAttributes(); public String getAttributeValue(Name name); public Iterator getChildElements(); public Iterator getChildElements(Name name); public Name getElementName(); public String getEncodingStyle(); public Iterator getNamespacePrefixes(); public String getNamespaceURI(String prefix); public Iterator getVisableNamespacePrefixes(); public boolean removeAttribute(Name name); public boolean removeNamespaceDeclaration(String prefix); public boolean removeContents(); public void setEncodingStyle(String encodingStyle); }
Most of the methods defined in SOAPElement
are self-explanatory and won't be covered in any detail here. The SAAJ API provides concise definitions of each method, in case the behavior is not obvious to you. In Listing 13-10, SaajExample_4
shows a SOAP message being created by an initial sender. SaajExample_4
makes extensive use of the SOAPElement
interface and methods.
Example 13-10. Using the SOAPElement
Type
package com.jwsbook.saaj; import javax.xml.soap.*; public class SaajExample_4 { public static void main(String [] args) throws SOAPException { // Create SOAPMessage MessageFactory msgFactory = MessageFactory.newInstance(); SOAPMessage message = msgFactory.createMessage(); SOAPElement header = message.getSOAPHeader(); // Create message-id header block SOAPElement msgIdHeader = (SOAPElement) header.addChildElement("message-id","mi", "http://www.Monson-Haefel.com/jwsbook/message-id"); String uuid = new java.rmi.dgc.VMID().toString(); msgIdHeader.addTextNode(uuid); // Create processed-by header block SOAPElement prcssdByHeader = (SOAPElement) header.addChildElement("processed-by","proc", "http://www.Monson-Haefel.com/jwsbook/processed-by"); SOAPElement node = prcssdByHeader.addChildElement("node"); SOAPElement time = node.addChildElement("time-in-millis"); long millis = System.currentTimeMillis(); time.addTextNode(String.valueOf(millis)); SOAPElement identity = node.addChildElement("identity"); identity.addTextNode("SaajExample_4"); // Create getBookPrice RPC call SOAPElement body = message.getSOAPBody(); SOAPElement getBookPrice = body.addChildElement("getBookPrice","mh", "http://www.Monson-Haefel.com/jwsbook/BookQuote"); SOAPElement isbn = getBookPrice.addChildElement("isbn"); isbn.addTextNode("0321146182"); SaajOutputter.writeToScreen(message); } }
Although SAAJ provides special types for SOAP Header
, Body
, and header-block elements, it's frequently more convenient to use the SOAPElement
supertype. SaajExample_4
provides an example: It creates the header blocks using SOAP Element.addChildElement()
instead of SOAPHeader.addHeaderElement()
because it's easier (you don't have to create a Name
object).
// Create message-id header block SOAPElement msgIdHeader = (SOAPElement) header.addChildElement("message-id","mi", "http://www.Monson-Haefel.com/jwsbook/message-id"); ... // Create processed-by header block SOAPElement prcssdByHeader = (SOAPElement) header.addChildElement("processed-by","proc", "http://www.Monson-Haefel.com/jwsbook/processed-by");
Naturally, the more familiar you are with the methods of SOAPElement
and its derived types, the easier and more efficient your code becomes.
It's important to note that each of the addChildElement()
methods returns a SOAPElement
. If you use the overloading of addChildElement()
that expects another SOAPElement
, the instance returned may not be the same one you passed into the method. Be careful not to use the original reference after you pass it to addChildElement()
; modifying it may not have the desired effect. For example:
SOAPElement child = ... // get SOAPElement from somewhere element.addChildElement( child ); child.addAttribute( attribName, attribValue );
This code may not actually add the attribute to the child as you might expect, because addChildElement()
may have copied the child when adding it to the parent element, and returned the copy rather than the original. You should always use the SOAPElement
returned by the addChildElement()
method if you need to modify a SOAPElement
object after you add it, thus:
SOAPElement child = // get SOAPElement from somewhere child = element.addChildElement( child ); child.addAttribute( attribName, attribValue );
The supertype of the SOAPElement
type, and therefore all of its subtypes, is Node
(see Figure 13-2). The Node
interface provides a few useful methods for navigating through a hierarchical tree of elements, removing nodes from the tree, and marking nodes for “recycling.” Listing 13-11 shows the interface definition for the Node
type.
Example 13-11. The javax.xml.soap.Node
Interface
package javax.xml.soap; public interface Node extends org.w3c.dom.Node { public void detachNode(); public SOAPElement getParentElement() throws java.lang.UnsupportedOperationException; public String getValue(); public void setValue(String value) throws java.lang.IllegalStateException; public void recycleNode(); public void setParentElement(SOAPElement parent) throws SOAPException; }
The detachNode()
method is frequently used to remove the SOAPHeader
object from a newly created SOAP message. When a SOAPMessage
is first created, it automatically contains Envelope
, Header
, and Body
elements. If the SOAP message you are constructing will not be using any header blocks, then removing the Header
element is a good idea. You may have noticed that SaajExample_1
(Listing 13-2) used detachNode()
for this purpose:
message.getSOAPHeader().detachNode();
The recycleNode()
method is a bit odd. It's supposed to help the underlying SAAJ implementation conserve resources—no harm in that, but with recent improvements in JVM garbage collection it hardly seems necessary. It's difficult to determine the overhead of using this method compared to simply dereferencing a node to release it for garage collection, but you may want to err on the side of resource conservation, and call recycleNode()
if you detach a node you won't be using further. The following snippet illustrates.
SOAPHeader header = message.getSOAPHeader();
header.detachNode();
header.recycleNode();
The behavior of the setValue()
method depends on the type of Node
. If it's a Text
type, setValue()
assigns the String
argument to be the value of the text. If setValue()
is invoked on an Element
, then it will assign the String
argment to the Text
node contained by the Element
. If the Element
object has no children, the setValue()
method will create a child Text
node. If, however, the Element
object has another Element
object as its child, or multiple children of any type, then setValue()
will throw a java.lang.IllegalStateException
.
In Chapter 4 you learned that the SOAP Header
element may have zero or more header blocks. In SAAJ, the SOAPHeader
type represents the Header
element, and the SOAPHeaderElement
type represents an individual header block. SOAPHeader
provides methods for adding, examining, and removing SOAPHeaderElement
objects—effectively adding, examining, or removing header blocks from the SOAP document. For example, we can insert the message-id
header block into a SOAP message as shown in bold in Listing 13-12.
Example 13-12. Adding a Header Block to a SOAP Message
package com.jwsbook.saaj; import javax.xml.soap.*; public class SaajExample_5 { public static void main(String [] args) throws SOAPException { // Create SOAPMessage MessageFactory msgFactory = MessageFactory.newInstance(); SOAPMessage message = msgFactory.createMessage(); SOAPHeader header = message.getSOAPHeader(); // Create message-id header block SOAPHeaderElement msgId = (SOAPHeaderElement) header.addChildElement("message-id","mi", "http://www.Monson-Haefel.com/jwsbook/message-id"); String uuid = new java.rmi.dgc.VMID().toString(); msgId.addTextNode(uuid); msgId.setActor("http://www.Monson-Haefel.com/logger"); msgId.setMustUnderstand(false); // Create getBookPrice RPC call SOAPBody body = message.getSOAPBody(); SOAPElement getBookPrice = body.addChildElement( "getBookPrice", "mh", "http://www.Monson-Haefel.com/jwsbook/BookQuote"); SOAPElement isbn = getBookPrice.addChildElement("isbn"); isbn.addTextNode("0321146182"); SaajOutputter.writeToScreen(message); } }
SOAPHeader.addChildElement()
adds the root element of the header block, in this case the message-id
element. The header block should be assigned a namespace and possibly an actor. Here we have assigned it both a namespace, "http://www.Monson-Haefel.com/jwsbook/message-id"
, and an actor, "http://www.Monson-Haefel.com/logger"
. As you learned in Chapter 4, the actor
attribute specifies a role that should process the header block. The call to java.rmi.dgc.VMID()
is a simple hack to obtain a unique ID.
There are multiple
addChildElement()
methods. If you pass aSOAPHeaderElement
parameter, you can add the whole header block, not just its root. TheaddChildElement()
method is defined by theSOAPElement
class, which is the supertype ofSOAPHeader
. TheSOAPElement
is discussed in more detail in Section 13.3.3.
The output of SaajExample_5
would look something like this:
<?xml version="1.0" encoding="UTF-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:mh="http://www.Monson-Haefel.com/jwsbook/BookQuote"> <soap:Header> <mi:message-id xmlns:mi="http://www.Monson-Haefel.com/jwsbook/message-id" soap:actor="http://www.Monson-Haefel.com/logger" soap:mustUnderstand="0"> 11d1def534ea1be0:b1c5fa:f3bfb4dcd7:-8000 </mi:message-id> </soap:Header> <soap:Body> <mh:getBookPrice> <isbn>0321146182</isbn> </mh:getBookPrice> </soap:Body> </soap:Envelope>
In addition to adding header blocks, the SOAPHeader
type allows us to examine and remove specific header blocks. SOAP receivers use this functionality to access all header blocks, header blocks associated with a particular actor, or only those header blocks with mustUnderstand
equal to true
for a particular actor. The SOAPHeader
class provides five methods for examining or extracting (accessing or removing) header blocks. These methods are shown in bold in the Listing 13-13, which is the definition of the SOAPHeader
class.
Example 13-13. The javax.xml.SOAPHeader
Interface
package javax.xml.soap; import java.util.Iterator; public interface SOAPHeader extends SOAPElement { public SOAPHeaderElement addHeaderElement( Name name ) throws SOAPException; public Iterator extractHeaderElements(String actor); public Iterator examineHeaderElements(String actor); public Iterator examineMustUnderstandHeaderElements(String actor); public Iterator examinAllHeaderElements(); public Iterator extractAllHeaderElements(); }
All of these methods return a java.util.Iterator
whose elements are SOAPHeaderElement
objects. For example, the following SOAP message contains two header blocks (message-id
and processed-by
), each with a different actor
attribute.
<?xml version="1.0" encoding="UTF-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Header> <mi:message-id soap:actor="http://www.Monson-Haefel.com/logger" xmlns:mi="http://www.Monson-Haefel.com/jwsbook/message-id"> 11d1def534ea1be0:b1c5fa:f3bfb4dcd7:-8000 </mi:message-id> <proc:processed-by soap:actor="http://schemas.xmlsoap.org/soap/actor/next" xmlns:proc="http://www.Monson-Haefel.com/jwsbook/processed-by"> <node> <time-in-millis>1013694684723</time-in-millis> <identity>http://local/SOAPClient2</identity> </node> </proc:processed-by> </soap:Header> <soap:Body> <!-- application-specific data goes here --> </soap:Body> </soap:Envelope>
Upon receiving this SOAP message, a SOAP node will request all header blocks that are associated with the standard next
actor so that those headers can be processed. The following snippet shows code for extracting and processing SOAPHeaderElement
objects associated with the next
actor role.
SOAPHeader header = message.getSOAPHeader();
String actor = "http://schemas.xmlsoap.org/soap/actor/next";
Iterator headerBlocks = header.extractHeaderElements( actor );
while(headerBlocks.hasNext()){
SOAPHeaderElement block = (SOAPHeaderElement)headerBlocks.next();
if(block.getElementName().getLocalName().equals("processed-by")){
SOAPElement node = block.addChildElement("node");
// do something useful with header blocks and then discard them
}
}
Obviously, the ability to examine, modify, and remove header blocks is a very important feature of SAAJ, one receivers (such as JAX-RPC handlers) will use extensively as they process incoming messages. As you learned in Chapter 4, intermediaries are required to remove any header block targeted to a role they play. The extractHeaderElements()
method enables a receiver to fulfill that obligation in one operation. Chapter 4 also mentioned, though, that some receivers will feign removal and insertion of the same header block, by simply modifying it. In such cases the receiver invokes examineHeaderElements()
instead of extract HeaderElements()
. This method allows the node to search for and access header blocks easily, without removing them.
SOAPHeaderElement
is an abstraction of a header block that lets us create and examine the attributes and child elements of a particular header block. Each header block may have an actor
attribute, a mustUnderstand
attribute, or both, in addition to child elements and other attributes. The definition of the SOAPHeaderElement
is shown in Listing 13-14 (its methods for accessing child elements and other attributes are defined in its supertype, SOAPElement
).
Example 13-14. The javax.xml.soap.SOAPHeaderElement
Interface
package javax.xml.soap; public interface SOAPHeaderElement extends SOAPElement { public String getActor(); public boolean getMustUnderstand(); public void setActor(String actorURI); public void setMustUnderstand(boolean flag); }
The SOAPHeaderElement
methods are employed when modifying and examining header blocks. For example, when creating the message-id
header block, we use these methods to set the value of the actor
and mustUnderstand
attributes, as shown in the following snippet from SaajExample_5
(Listing 13-12).
// Create message-id header block SOAPHeaderElement msgId = (SOAPHeaderElement) header.addChildElement("message-id","mi", "http://www.Monson-Haefel.com/jwsbook/message-id"); String uuid = new java.rmi.dgc.VMID().toString(); msgId.addTextNode(uuid); msgId.setActor("http://www.Monson-Haefel.com/logger"); msgId.setMustUnderstand(false);
In addition to the actor
and mustUnderstand
attributes, a SOAPHeader Element
may also contain one or more SOAPElement
objects, which represent the child elements of the header block.
As its names suggests, the SOAPBody
type represents the SOAP Body
element. Of its four methods, three deal with SOAP faults and one with the Body
of a non-fault SOAP message. Listing 13-15 shows the definition of the SOAPBody
interface.
Example 13-15. The javax.xml.soap.SOAPBody
Interface
package javax.xml.soap; public interface SOAPBody extends SOAPElement { public SOAPBodyElement addBodyElement(Name name) throws SOAPException; public SOAPBodyElement addDocument(org.w3c.dom.Document doc) throws SOAPException; public SOAPFault addFault() throws SOAPException; public SOAPFault addFault(Name faultcode, String faultString, java.util.Locale local) throws SOAPException; public SOAPFault addFault(Name faultcode, String faultString) throws SOAPException; public SOAPFault getFault(); public boolean hasFault(); }
The SOAPBody
type is used in several of the earlier examples. The following snippet from SaajExample_3
(Listing 13-6) shows how SAAJ can be used to create a SOAP body with contents.
Name getBookPrice_Name = soapFactory.createName("getBookPrice","mh",
"http://www.Monson-Haefel.com/jwsbook/BookQuote");
Name isbnName = soapFactory.createName("isbn");
SOAPBody body = message.getSOAPBody();
SOAPBodyElement getBookPrice_Element =
body.addBodyElement(getBookPrice_Name);
getBookPrice_Element.addChildElement( isbnName );
The addFault()
, getFault()
, and hasFault()
methods are discussed in Section 13.4: Working with SOAP Faults. The addDocument()
method is discussed in Section 13.6: SAAJ 1.2 and DOM 2.
SOAPBodyElement
extends SOAPElement
and doesn't add any methods of its own, as you see in Listing 13-16.
Example 13-16. The javax.xml.soap.SOAPBodyElement
Interface
package javax.xml.soap; public interface SOAPBodyElement extends SOAPElement {}
Although it may seem a bit silly to define a SOAPBodyElement
type that adds no methods—SOAPElement
would seem to suffice—this empty type may be beneficial in the future. As the SOAP protocol evolves, the SOAPBodyElement
will already be present as a construct for adding new functionality (methods), without breaking backward-compatibility of existing code. In addition, the SOAPBodyElement
type, which is also the base type of the SOAPFault
, suggests type safety by restricting the type of SOAPElement
object a SOAPBody
can contain.
A SOAPBodyElement
can be added to a SOAPBody
object using a Name
object, as shown in the following snippet from SaajExample_3
.
Name getBookPrice_Name = soapFactory.createName("getBookPrice","mh", "http://www.Monson-Haefel.com/jwsbook/BookQuote"); ... SOAPBodyElement getBookPrice_Element = body.addBodyElement(getBookPrice_Name);
The Text
type is an extension of Node
that represents literal text contained by an element or a comment. The interface definition of Text
requires familiarity with Node
to be useful, because the subtype adds only a single method, isComment()
, as shown in Listing 13-17.
Example 13-17. The javax.xml.soap.Text
Interface
package javax.xml.soap; public interface Text extends Node, org.w3c.dom.Text { public boolean isComment(); }
You have to use the methods getValue()
and setValue()
defined by the Node
supertype to retrieve and set the contents of a Text
object. To access a Text
object from an element use Node.getValue()
, which will return a String
value if the element contains a Text
node, or null
if it doesn't. For example, we could retrieve the ISBN number from the isbn
element as follows:
SOAPElement isbn = getBookPrice.addChildElement("isbn");
...
isbn.addTextNode("0321146182");
...
Text textValue = isbn.getValue();
For some reason the addTextNode()
method returns a SOAPElement
rather than the Text
node itself. I suspect this was done to aid in changing calls, but its effectiveness is debatable.
There is no obvious way to add a comment to a SOAP message without reverting to the DOM object model. Of course, SOAP messages are not meant for human consumption, so this lack is not a big problem.
Certain XML namespaces will not change and will be used over and over again. The values of these namespaces are assigned to constants as a convenience for the developer. Specifically, the SOAPConstants
class defines constants for the SOAP 1.1 namespace, the namespace of the standard next
actor attribute value, and the namespace of standard SOAP encoding. Table 13-3 shows the SOAPConstants
fields and their values.
Table 13-3. Namespace Constants
| XML Namespace String Value |
---|---|
|
|
|
|
|
|
You can see how SOAPConstants
is used in some of the examples in this chapter; for example, SaajExample_4
uses the constant for the next
SOAP actor, as shown in this code snippet:
prcssdBy.setActor(SOAPConstants.URI_SOAP_ACTOR_NEXT);
prcssdBy.setMustUnderstand(true);
The URI_NS_SOAP_ENCODING
constant is used to set the encoding style to RPC/Encoding. As I noted in Part II, the BP doesn't support this messaging mode, so you should avoid it.
SOAPException
is used to report errors encountered by the SOAP toolkit while attempting to perform some operation. Many of the methods that manufacture objects throw this exception because they affect the structure of the SOAP message. Remember: A SOAPException
does not represent a SOAP fault generated by the receiver. A SOAP fault will always be received as a SOAPMessage
(see Section 13.4).
A SOAPException
may contain an embedded exception if the error was caused by a subsystem like I/O or an XML parser. It may also contain a reason message. A skeletal definition of SOAPException
is shown in Listing 13-18 (the method bodies have been omitted for brevity).
Example 13-18. The javax.xml.soap.SOAPException
Class
package javax.xml.soap; public class SOAPException extends Exception { public SOAPException(){...} public SOAPException(String reason){...} public SOAPException(String reason, Throwable cause){...} public SOAPException(Throwable cause){...} public Throwable getCause(){...} public getMessage(){...} public Throwable initCause(Throwable cause){...} }
The SOAPFactory
class is provided for the developer's convenience. It's a nice thing to have around because it allows you to create a SOAPElement
independent of context. In other words, you can use it to create detached instances of the SOAPElement
type. Listing 13-19 shows the definition of SOAPFactory
(the method bodies have been omitted for brevity).
Example 13-19. The javax.xml.soap.SOAPFactory
Class
package javax.xml.soap; public abstract class SOAPFactory { … public abstract SOAPElement createElement(Name name) throws SOAPException{…} public abstract SOAPElement createElement(String localName) throws SOAPException{…} public abstract SOAPElement createElement(String localName, String prefix, String uri) throws SOAPException{…} … }
SOAPFactory
can be used to construct portions of a SOAP message independent of a SOAPMessage
object. For example, you might use it to construct a specialized header block in one module, which can be added to a SOAP document in some other module. As a demonstration, in Listing 13-20 SaajExample_6
creates a SOAP message but uses a separate class, the MessageIDHeader
class, to construct the message-id
header block.
Example 13-20. Delegating Header-Block Creation to a MessageIDHeader
package com.jwsbook.saaj;
import javax.xml.soap.*;
public class SaajExample_6 {
public static void main(String [] args) throws SOAPException {
// Create SOAPMessage
MessageFactory msgFactory = MessageFactory.newInstance();
SOAPMessage message = msgFactory.createMessage();
SOAPHeader header = message.getSOAPHeader();
// Create message-id header block
SOAPElement msgId = MessageIDHeader.createHeaderBlock();
SOAPHeaderElement msgId_header =
(SOAPHeaderElement)header.addChildElement(msgId);
msgId_header.setActor("http://www.Monson-Haefel.com/logger");
SaajOutputter.writeToScreen(message);
}
}
The MessageIDHeader
class in Listing 13-21 uses the SOAPFactory
to create an isolated header block that can be generated for any SOAP application that needs it.
Example 13-21. Using SOAPFactory
to Create a Header Block
package com.jwsbook.saaj; import javax.xml.soap.*; public class MessageIDHeader { public static SOAPElement createHeaderBlock() throws SOAPException{ SOAPFactory factory = SOAPFactory.newInstance(); SOAPElement msgId = factory.createElement("message-id","mi", "http://www.Monson-Haefel.com/jwsbook/message-id"); String messageid = new java.rmi.dgc.VMID().toString(); msgId.addTextNode( messageid ); return msgId; } }
The
java.rmi.dgc.VMID
does a fairly good job of generating unique identifiers. It's convenient because it's found in the core libraries, but it's not perfect and is not recommended for use in production systems.
The output from SaajExample_6
would look something like the following (the text value of message-id
would differ):
<?xml version="1.0" encoding="UTF-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Header> <mi:message-id xmlns:mi="http://www.Monson-Haefel.com/jwsbook/message-id" soap:actor="http://www.Monson-Haefel.com/logger"> 11d1def534ea1be0:194a4e:f3c05ce67a:-8000 </mi:message-id> </soap:Header> <soap:Body/> </soap:Envelope>
SOAPFactory
makes it fairly easy to modularize the construction of SOAP messages, especially SOAP headers, which are specialized and often used across a variety of SOAP applications. For example, at Monson-Haefel Books almost every Web service requires that a message-id
header block be included for logging purposes. A class like MessageIDHeader
can be reused throughout the system to generate the message-id
header block.
As you know from Chapter 4, faults are a special kind of SOAP message that contains error information. SOAP faults are always delivered from the receiver back to the sender. In SAAJ, SOAP fault messages are constructed in basically the same way as a plain SOAP message, except the SOAPBody
object contains a SOAPFault
instead of a SOAPBodyElement
. SOAPFault
is actually a subtype of SOAP BodyElement
that specializes the behavior to support the structure of a SOAP Fault
element. Several interfaces play a role in creating SOAP fault messages with SAAJ. Figure 13-4 shows these interfaces in the context of all the other SAAJ types.
Every instance of the SOAPFault
type is contained by a SOAPBody
element. It describes an error generated by the receiver while processing a SOAP message.
The SOAPFault
interface (Listing 13-22) defines several methods for setting and getting the faultactor
, faultcode
, and faultstring
(description) elements of the Fault
element, as well as creating and accessing detail
elements.
Example 13-22. The javax.xml.soap.SOAPFault
Interface
package javax.xml.soap; import java.util.Locale; public interface SOAPFault extends SOAPBodyElement { public Detail addDetail() throws SOAPException; public Detail getDetail(); public String getFaultActor(); public String getFaultCode(); public Name getFaultCodeAsName(); public String getFaultString(); public Locale getFaultStringLocale(); public void setFaultActor(String faultActor) throws SOAPException; public void setFaultCode(String faultCode) throws SOAPException; public void setFaultCode(Name faultCode) throws SOAPException; public void setFaultString(String faultString) throws SOAPException; public void setFaultString(String faultString, Locale local) throws SOAPException; }
As an example, imagine that the ultimate receiver of the BookQuote SOAP message determines that the ISBN number declared in the Body
of an incoming message is invalid. The receiver will generate a SOAP Fault message and deliver it to the sender immediately before it in the message path. The Fault message might look like the one in Listing 13-23.
Example 13-23. A SOAP Fault Message
<?xml version="1.0" encoding="UTF-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <soap:Fault> <faultcode>soap:Client</faultcode> <faultstring>The ISBN contains an invalid character(s)</faultstring> <faultactor> http://www.Monson-Haefel.omc/BookQuote_WebService </faultactor> <detail> <mh:InvalidIsbnFaultDetail xmlns:mh="http://www.Monson-Haefel.com/jwsbook/BookQuote"> <offending-value>19318224-D</offending-value> <conformance-rules> The first nine characters must be digits. The last character may be a digit or the letter 'X'. Case is not important. </conformance-rules> </mh:InvalidIsbnFaultDetail> </detail> </soap:Fault> </soap:Body> </soap:Envelope>
This fault message can be constructed fairly easily using SAAJ, as shown in Listing 13-24, SaajExample_7
.
Example 13-24. Building a Fault Message with SAAJ
package com.jwsbook.saaj; import javax.xml.soap.*; public class SaajExample_7 { public static void main(String [] args) throws SOAPException { // Create SOAPMessage MessageFactory msgFactory = MessageFactory.newInstance(); SOAPMessage message = msgFactory.createMessage(); message.getSOAPHeader().detachNode(); // Create Fault message SOAPBody body = message.getSOAPBody(); SOAPFault fault = body.addFault(); fault.setFaultCode("soap:Client"); fault.setFaultString("The ISBN contains an invalid character(s)"); fault.setFaultActor("http://www.Monson-Haefel.org/BookQuote_WebService"); Detail detail = fault.addDetail(); SOAPFactory soapFactory = SOAPFactory.newInstance(); Name errorName = soapFactory.createName( "InvalidIsbnFaultDetail","mh", "http://www.Monson-Haefel.com/jwsbook/BookQuote"); DetailEntry detailEntry = detail.addDetailEntry(errorName); SOAPElement offendingValue = detailEntry.addChildElement("offending-value"); offendingValue.addTextNode("19318224-D"); SOAPElement conformanceRules = detailEntry.addChildElement("conformance-rules"); conformanceRules.addTextNode( "The first nine characters must be digits. The last character "+ "may be a digit or the letter 'X'. Case is not important."); SaajOutputter.writeToScreen(message); } }
Version 1.2 of SAAJ modified the SOAPFault
interface and added a couple of methods that are QName-oriented. For example, when you call the setFault Code()
method, you can pass it a Name
parameter that represents a proper QName, instead of a String
. The following snippet illustrates.
SOAPFault fault = body.addFault(); SOAPFactory soapFactory = SOAPFactory.newInstance(); Name faultCode = soapFactory.createName("Client","soap", SOAPConstants.URI_NS_SOAP_ENVELOPE); fault.setFaultCode(faultCode); fault.setFaultString("The ISBN contains an invalid character(s)");
You can also access fault codes as either String
values or Name
objects. The getFaultCodeAsName()
method returns the fault code as a SAAJ Name
object, rather than a String
value. This book tends to use the String
methods because they are easier to read in example code, but use the Name
object methods if you wish.
To support international applications, the getFaultString()
and set FaultString()
methods are complemented by java.util.Locale
style methods, namely getFaultStringLocale()
and an overloading of setFaultString()
that expects both a String
and a Locale
. These methods access or assign an xml:lang
attribute to the faultstring
value. If you do not use a Locale
method, the default locale is used. A java.util.Locale
represents a geographic, cultural, or political region (for example, the French-speaking area of Canada, or Simplified Chinese).
The legal values for the
xml:lang
attribute are specified in the IETF standard RFC 1766 and its successors (currently RFC 3066), which are based in part on ISO 639:1988. Language values begin with a primary two-character language identifier, optionally followed by a series of hyphen-delimited sub-ids for country or dialect identification; the ids are not case-sensitive. Examples include"en-us"
for United States English and"fr-ca"
for Canadian French. You don't have to use the sub-ids if the nature of the application doesn't require you to;"en"
or"fr"
by itself is fine.
The simplest way to add a SOAPFault
to a message is to use the overloaded addFault()
methods, which allow you to initialize the fault code, fault string, and language (Locale
) of the fault string when it's created. For example, the following snippet shows how to create a complete SOAPFault
object in one operation.
SOAPFactory soapFactory = SOAPFactory.newInstance(); Name faultCode = soapFactory.createName("Client","soap", SOAPConstants.URI_NS_SOAP_ENVELOPE); SOAPFault fault = body.addFault(faultCode, "The ISBN contains an invalid character(s)", Locale.US);
If you're using the default Locale
, you can just invoke the addFault(Name,String)
overloading of the method instead of passing the default Locale
object explicitly.
A SOAPFault
object contains an object of type Detail
, which in turn contains one or more DetailEntry
objects. As shown in Listing 13-25, the Detail
type defines two methods: one for adding new DetailEntry
objects and one for accessing existing ones.
Example 13-25. The javax.xml.soap.Detail
Interface
package javax.xml.soap; import java.util.Iterator; public interface Detail extends SOAPFaultElement { public DetailEntry addDetailEntry(Name name) throws SOAPException; public Iterator getDetailEntries() }
The Detail
object was used in SaajExample_7
, as shown in the following snippet from Listing 13-24.
Detail detail = fault.addDetail(); Name errorName = envelope.createName("InvalidIsbnFaultDetail","mh", "http://www.Monson-Haefel.com/jwsbook/BookQuote"); DetailEntry detailEntry = detail.addDetailEntry(errorName);
SOAPFaultElement
is the supertype of SOAPDetail
type (see Figure 13-4). It defines no methods or fields; it's an empty interface. It provides the same kind of typing benefit that SOAPBodyElement
does: some assurance of backward-compatibility if the SOAP protocol changes, and some type safety. Its definition appears in Listing 13-26.
Example 13-26. The javax.xml.soap.SOAPFaultElement
Interface
package javax.xml.soap; public interface SOAPFaultElement extends SOAPElement {}
SOAPFaultElement
is not usually used directly in your code, but it has utility because it extends SOAPElement
, and thus inherits all the methods of that interface.
DetailEntry
, defined in Listing 13-27, is another empty interface. It has no methods or fields, and is useful only for type safety (the Detail
object may contain DetailEntry
objects only). Its supertype, SOAPElement
, defines all the methods you need to work with detail entries. It also offers some flexibility for the future, if new versions of SOAP add new restrictions on detail elements.
Building SOAP messages with SAAJ wouldn't be very useful if you didn't send them anywhere. Usually SAAJ is used in combination with JAX-RPC, but it's not dependent on JAX-RPC.
SAAJ comes with its own, fairly simple and limited, message-delivery system, which is a part of the basic API. Using SAAJ you can exchange Request/Response-style SOAP messages with a Web service over HTTP. You can also use SAAJ's native message delivery system with any other kind of URL-based protocol, depending on what your vendor supports—but remember that HTTP is the only protocol sanctioned by the Basic Profile.
SAAJ's native message-delivery system is so simple that it takes only a few lines of code to send and receive SOAP messages. In fact it's so simple that there is not much to explain. You simply create a SOAPConnection
and send the message. SaajExample_8
in Listing 13-28 demonstrates this procedure.
Example 13-28. Using SAAJ to Send and Receive SOAP Messages
package com.jwsbook.saaj; import javax.xml.soap.*; import java.net.URL; import java.io.FileInputStream; public class SaajExample_8 { public static void main(String [] args) throws SOAPException, java.io.IOException{ // Build a SOAPMessage from a file MessageFactory msgFactory = MessageFactory.newInstance(); MimeHeaders mimeHeaders = new MimeHeaders(); mimeHeaders.addHeader("Content-Type","text/xml; charset=UTF-8"); FileInputStream file = new FileInputStream("soap.xml"); SOAPMessage requestMsg = msgFactory.createMessage(mimeHeaders, file); file.close(); // Send the SOAP message to the BookQuote Web service SOAPConnectionFactory conFactry = SOAPConnectionFactory.newInstance(); SOAPConnection connection = conFactry.createConnection(); URL url = new URL(args[0]); SOAPMessage replyMsg =connection.call(requestMsg, url); // Print out the reply message SaajOutputter.writeToScreen(replyMsg); } }
SaajExample_8
builds a SOAP message from a file (soap.xml
), then sends the message to the URL you provide as an argument. The SOAPConnection
object creates an HTTP connection to the specified URL and sends the SOAP message to the Web service as an HTTP POST message. The HTTP reply that's sent from the Web service back to the SOAPConnection
object will contain a SOAP message, a SOAP fault, or an HTTP error code; in the last case a SOAPException
is thrown.
The SOAP reply message is returned by the SOAPConnection.call()
method as a SOAPMessage
object, which can be accessed using the SAAJ API. SaajExample_8
writes the reply SOAP message to the screen. It will look something like this:
<?xml version="1.0" encoding="UTF-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns0="http://www.Monson-Haefel.com/jwsbook/BookQuote" > <soap:Body> <ns0:getBookPriceResponse> <result>24.99</result> </ns0:getBookPriceResponse> </soap:Body> </soap:Envelope>
SAAJ's native messaging service is optional, so your vendor may not support it; if so, the SOAPConnection.call()
method may not work. Check your vendor's documentation to determine whether it's supported.
SAAJ 1.1, the first official version of SAAJ, was not as flexible as it could have been. Vendors and developers complained that it should have been based on DOM 2. They claimed—rightly—that a DOM-based SAAJ would be a more flexible API, would make it easier to work with arbitrary XML document fragments, and would allow SAAJ to work well with DOM. In recognition of version 1.1's shortcomings, SAAJ 1.2 redefines the API so that it's an extension of the DOM 2 Java object model. SAAJ is now a lot more powerful, because you can use it to create and manipulate SOAP messages, but you can also take advantage of low-level DOM 2 functionality as the need arises. In addition, you can import Node
s from a DOM 2 document into a SOAP message, which is useful when working with JAX-RPC message handlers and Document/Literal payloads.
This section assumes you are already familiar with DOM 2. If you're not, then you should take time out to read Chapter 21: DOM 2 now, or this section is not going to make much sense to you. Chapter 21 will teach some of the basics about the DOM 2 programming API and how it's used. To find out where you can learn still more about DOM, see the introduction to Part VI: JAXP.
Aligning SAAJ 1.2 with DOM 2 didn't really complicate the SAAJ API too much. In most cases interfaces were simply redefined to extend DOM 2 interface types like Document
, Element
, and Text
. Figure 13-5 shows how SAAJ 1.2 types inherit from DOM 2 types.
SaajExample_9
in Listing 13-29 is a simple example of how you can use SAAJ 1.2 and DOM 2 in concert to construct a Document/Literal SOAP message.
Example 13-29. Importing a DOM Element into a SAAJ 1.2 SOAPMessage
package com.jwsbook.saaj; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilder; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import javax.xml.soap.*; public class SaajExample_9 { public static void main(String [] args) throws Exception{ // Read an XML document from a file into a DOM tree using JAXP. String filePath = args[0]; DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder parser = factory.newDocumentBuilder(); Document xmlDoc = parser.parse(filePath); // Create a SAAJ SOAPMessage object. Get the SOAPBody and SOAPPart. MessageFactory msgFactory = MessageFactory.newInstance(); SOAPMessage message = msgFactory.createMessage(); SOAPPart soapPart = message.getSOAPPart(); SOAPBody soapBody = soapPart.getEnvelope().getBody(); // Import the root element and append it to the body of the SOAP message. Node elementCopy = soapPart.importNode(xmlDoc.getDocumentElement(), true); soapBody.appendChild(elementCopy); // Output SOAP message. SaajOutputter.writeToScreen(message); } }
SaajExample_9
reads an XML document from a file and has a DOM provider parse it into a Document
object. It also creates a new SOAPMessage
object. The application imports the root of the XML document into the SOAPPart
(using the Document.importNode()
method) and subsequently appends the imported copy to the SOAPBody
(using the Node.appendNode()
method).
As an alternative to using the importNode()
method, you can use the SOAP Body.addDocument()
method, which requires a lot less code, but has a significant side effect. When you call SOAPBody.addDocument()
, the root element of the Document
is actually moved (not copied) to become a child of the SOAPBody
. In other words, the root element is physically reassigned to the SOAPMessage
object. As you learned earlier in chapter 21, a Node
cannot refer to more than one Document
object, so moving the root element to the SOAPMessage
(specifically to the SOAPPart
) invalidates the source Document
—there is no longer a root element that refers to it. In Listing 13-30, SaajExample_10
shows how you can use SOAPBody.addDocument()
.
Example 13-30. Moving a DOM Root Element to a SAAJ 1.2 SOAPMessage
package com.jwsbook.saaj; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilder; import org.w3c.dom.Document; import org.w3c.dom.Element; import javax.xml.soap.*; public class SaajExample_10 { public static void main(String [] args) throws Exception{ // Read an XML document from a file into a DOM tree using JAXP. String filePath = args[0]; DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder parser = factory.newDocumentBuilder(); Document xmlDoc = parser.parse(filePath); // Create a SAAJ SOAPMessage object. Get the SOAPBody. MessageFactory msgFactory = MessageFactory.newInstance(); SOAPMessage message = msgFactory.createMessage(); SOAPBody soapBody = message.getSOAPPart().getEnvelope().getBody(); // Append the root element of the XML doc to the body of the SOAP message. soapBody.addDocument(xmlDoc); // Output SOAP message. jwsed1.SaajOutputter.writeToScreen(message); } }
Using SAAJ makes it fairly easy to construct SOAP messages from scratch, and to peruse SOAP messages received from other nodes. While SAAJ is normally used in combination with JAX-RPC or some other API, it can also be used independently, which is why it's a separate API from JAX-RPC.
JAX-RPC's Message Handler API depends heavily on SAAJ to represent incoming and outgoing SOAP messages so that you can manipulate SOAP header blocks. The next chapter explains in detail how to use SAAJ with JAX-RPC message handlers.
This chapter covered the construction of simple SOAP documents using SAAJ, but SAAJ is also designed to model SOAP Messages with Attachments (SwA), the MIME format used to deliver SOAP messages that refer to XML and non-XML data. Because the Basic Profile doesn't support SwA, this subject is addressed in Appendix F: SAAJ Attachments.
[1] The SwA specification is actually a Note maintained by the World Wide Web Consortium (W3C). A W3C “Note” is not a finished specification (which W3C calls a “Recommendation”), but simply a suggestion or a work in progress.
[2] Erich Gamma, et al. Design Patterns: Elements of Reusable Object-Oriented Software. Reading, MA: Addison-Wesley, 1995, p. 87.
18.222.199.131