Parts 1 through 3 of this four-part series on developing Web services in Java SE first presented an overview of Web services and Java SE's support for developing them. The series then focused on developing SOAP-based and RESTful Web services via this support. Part 4 wraps up this series by focusing on advanced topics.
This article first introduces Java SE's SAAJ API for working with SOAP-based Web services at a lower level. It then discusses how to create a JAX-WS handler to log the flow of SOAP messages. Next, the article teaches how to create and install a custom lightweight HTTP server to perform authentication.
Moving on, you're shown how to create a RESTful Web service that returns attachments (e.g., a JPEG image) to its clients. You then dig deeper into JAX-WS by exploring the interplay between providers and dispatch clients, and learn how to create a dispatch client that accesses the javax.xml.transform.Source
instance returned from a Web service provider's invoke()
method via a different Source
instance in another version of Part 3's LibraryClient
application.
Working with SAAJ
Soap with Attachments API for Java (SAAJ) is the Java API for creating, sending, and receiving SOAP messages that may or may not have MIME-typed attachments. SAAJ is a lower-level alternative to JAX-WS for sending and receiving SOAP messages.
This section first presents an overview of SOAP message architecture. It then takes you on a tour of SAAJ. When this tour finishes, I present an application that uses this API to access a SOAP-based Web service (written in PHP) for converting between integer values and Roman numerals. This application reinforces your understanding of SAAJ.
SOAP message architecture
A SOAP message is an XML document sent from an initial SOAP sender node to an ultimate SOAP receiver node, mostly likely passing through intermediate SOAP sender/receiver nodes along its path. A SOAP node is processing logic that operates on a SOAP message.
The SOAP document consists of an Envelope
root element that encapsulates an optional Header
element and a nonoptional Body
element -- see Figure 1.
Figure 1. A SOAP message's architecture consists of an optional Header
element and a mandatory Body
element within an Envelope
element
The Header
element specifies application-related information (such as authentication details to verify who sent the message) via immediate child elements known as header blocks. A header block represents a logical grouping of data that can target an intermediate SOAP node or the ultimate receiver node.
Although header blocks are defined by the application, their start tags may contain the following SOAP-defined attributes to indicate how SOAP nodes should process them:
encodingStyle
identifies the rules used to serialize parts of a SOAP messagerole
identifies the SOAP node (via a URI) to which the header block is targeted -- this SOAP 1.2-introduced attribute replaces the SOAP 1.1 actor attribute, which performs the same functionmustUnderstand
indicates whether processing of the header block is mandatory (value1
in SOAP 1.1;true
in SOAP 1.2) or optional (value0
in SOAP 1.1;false
in SOAP 1.2)relay
indicates whether the header block targeted at a SOAP receiver must be relayed to another node if not processed -- this attribute was introduced in SOAP 1.2
The Body
element contains information that targets the ultimate receiver node. This information is known as the payload, and consists of a SOAP-defined Fault
child element describing a fault (an error being reported by the Web service), or child elements that are specific to the Web service.
The Fault
element contains error and status information that a Web service returns to a client. SOAP 1.1 specifies the following child elements of Fault
:
faultcode
: This mandatory element provides information about the fault in a form that can be processed by software. SOAP defines a small set of SOAP fault codes that cover basic faults; this set can be extended by applications.faultstring
: This mandatory element provides information about the fault in a human-readable format.faultactor
: This element contains the URI of the SOAP node that generated the fault. A SOAP node that is not the ultimate SOAP receiver must includefaultactor
when creating a fault; an ultimate SOAP receiver doesn't have to include this element, but might choose to do so.detail
: This element carries application-specific error information related to theBody
element. It must be present whenBody
's contents couldn't be processed successfully. Thedetail
element must not be used to carry error information belonging to header blocks; detailed error information belonging to header blocks is carried within these blocks.
SOAP 1.2 specifies the following child elements of Fault
:
Code
: This mandatory element provides information about the fault in a form that can be processed by software. It contains aValue
element and an optionalSubcode
element.Reason
: This mandatory element provides information about the fault in a human-readable format.Reason
contains one or moreText
elements, each of which contains information about the fault in a different language.Node
: This element contains the URI of the SOAP node that generated the fault. A SOAP node that's not the ultimate SOAP receiver must includeNode
when creating a fault; an ultimate SOAP receiver doesn't have to include this element, but might choose to do so.Role
: This element contains a URI that identifies the role the node was operating in when the fault occurred.Detail
: This optional element contains application-specific error information related to the SOAP fault codes describing the fault. Its presence has no significance as to which parts of the faulty SOAP message were processed.
Listing 1 presents an example SOAP message.
Listing 1. A SOAP message for calling a SOAP-based library Web service's getTitle()
function to retrieve a book's title when given its ISBN
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SOAP-ENV:Header />
<SOAP-ENV:Body>
<lns:getTitle xmlns:lns="http://javajeff.ca/library">
<isbn xsi:type="xsd:string">9781484219157</isbn>
</lns:getTitle>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
This SOAP message describes a request to a library Web service to execute its getTitle()
function. Furthermore, it describes the type and value of the ISBN argument passed to this function's isbn
parameter.
The message begins with the SOAP-ENV
-prefixed <Envelope>
tag, which describes the SOAP message's envelope. The commonly used SOAP-ENV
prefix corresponds to the SOAP 1.1 namespace that provides the schema for SOAP envelopes. The xsd
and xsi
prefixes correspond to the XML Schema structures and XML Schema Instance namespaces, and are used to denote the XML Schema type that describes the kind of data being passed to getTitle()
(a string) via the isbn
element.
The empty Header
element signifies that there is no SOAP header. In contrast, the Body
element identifies a single getTitle
operation request.
The getTitle
element is namespace-qualified, as recommended by the SOAP 1.1 and 1.2 specifications. In contrast, the isbn
child element of getTitle
isn't namespace-qualified because it inherits getTitle
's namespace -- the SOAP 1.1 and 1.2 specifications don't mandate that such child elements be namespace-qualified.
SAAJ API overview
SAAJ is a small Java SE API that lets you perform the following tasks:
- create an endpoint-to-endpoint connection
- create a SOAP message
- create an XML fragment
- add content to the header of a SOAP message
- add content to the body of a SOAP message
- create attachment parts and add content to them
- access/add/modify parts of a SOAP message
- create/add/modify SOAP fault information
- extract content from a SOAP message
- send a SOAP request-response message
SAAJ is associated with the javax.xml.soap
package, which contains 14 interfaces and 13 classes. Various interfaces and classes extend their counterparts in the org.w3c.dom
package, implying that part of a SOAP message is organized as a tree of nodes.
The following classes and interfaces are used to specify the structure of a SOAP message:
SOAPMessage
represents the entire SOAP message. It contains a singleSOAPPart
instance and zero or moreAttachmentPart
instances.SOAPPart
contains aSOAPEnvelope
instance, which represents the actual SOAPEnvelope
element.SOAPEnvelope
optionally contains aSOAPHeader
instance and also contains a mandatorySOAPBody
instance.SOAPHeader
represents the SOAP message's header block(s).SOAPBody
contains either aSOAPFault
object or aSOAPBodyElement
object containing the actual SOAP payload XML content.SOAPFault
stores a SOAP fault message.
Working with SAAJ involves creating a SOAP connection, creating SOAP messages, populating each message with content and optional attachments, sending the messages to an endpoint and retrieving replies, and closing the connection.
Creating a SOAP connection
You create a connection by working with the SOAPConnectionFactory
and SOAPConnection
classes. As its name implies, SOAPConnectionFactory
is a factory class for retrieving SOAPConnection
instances (actually, instances of subclasses of the abstract SOAPConnection
class). A SOAPConnection
instance represents an endpoint-to-endpoint connection to the Web service; the client and Web service exchange messages over this connection. The following example shows you how to instantiate the factory and obtain a SOAP connection:
SOAPConnectionFactory soapcf = SOAPConnectionFactory.newInstance();
SOAPConnection soapc = soapcf.createConnection();
Instantiate the factory by calling SOAPConnectionFactory
's SOAPConnectionFactory newInstance()
method. This method throws SOAPException
when a SOAPConnectionFactory
instance cannot be created. If a nonOracle Java implementation doesn't support the SAAJ communication infrastructure, this method throws an instance of the java.lang.UnsupportedOperationException
class.
After instantiating SOAPConnectionFactory
, call this instance's SOAPConnection createConnection()
method to create and return a new SOAPConnection
object. This method throws SOAPException
when it's unable to create this object.
Creating a SOAP message
Create a SOAP message by working with the MessageFactory
and SOAPMessage
classes. MessageFactory
provides a pair of methods for returning a MessageFactory
instance:
MessageFactory newInstance()
creates aMessageFactory
object based on the default SOAP 1.1 implementation. This method follows an ordered lookup procedure to locate theMessageFactory
implementation class. This procedure first examines thejavax.xml.soap.MessageFactory
system property, and lastly calls an instance of theSAAJMetaFactory
class'sMessageFactory newMessageFactory(String protocol)
method to return that factory. This method throwsSOAPException
when it's unable to create the factory.MessageFactory newInstance(String protocol)
creates aMessageFactory
object that's based on the SOAP implementation specified by theprotocol
argument, which is one of theSOAPConstants
interface'sDEFAULT_SOAP_PROTOCOL
,DYNAMIC_SOAP_PROTOCOL
,SOAP_1_1_PROTOCOL
, orSOAP_1_2_PROTOCOL
constants. This method throwsSOAPException
when it's unable to create the factory.
After instantiating MessageFactory
, call one of the following methods to create a SOAPMessage
instance:
SOAPMessage createMessage()
creates and returns a newSOAPMessage
object (actually, an instance of a concrete subclass of this abstract class) with defaultSOAPPart
,SOAPEnvelope
,SOAPBody
(initially empty) andSOAPHeader
objects. This method throwsSOAPException
when aSOAPMessage
instance cannot be created, andUnsupportedOperationException
when theMessageFactory
instance's protocol isDYNAMIC_SOAP_PROTOCOL
.SOAPMessage createMessage(MimeHeaders headers, InputStream in)
internalizes the contents of the givenjava.io.InputStream
object into a newSOAPMessage
object and returns this object. TheMimeHeaders
instance specifies transport-specific headers that describe the various attachments to the SOAP message. This method throwsSOAPException
when aSOAPMessage
instance cannot be created,java.io.IOException
when there's a problem reading data from the input stream, andjava.lang.IllegalArgumentException
when theMessageFactory
instance requires one or more MIME headers to be present in the argument passed to headers and these headers are missing.
The following example shows you how to instantiate the factory and create a SOAPMessage
object that's ready to be populated:
MessageFactory mf = MessageFactory.newInstance();
SOAPMessage soapm = mf.createMessage();
Populating a SOAP message with content and optional attachments
SOAPMessage
describes a SOAP message optionally followed by MIME-typed attachments. The SOAP message part of this object is defined by an instance of a concrete subclass of the abstract SOAPPart
class.