SAAJ Peter
Komisar © Conestoga
College v.1.2 / 2005
references: Java JWSDP 1.5, SAAJ
Documentation,
/jwsdp-1.5/saaj/docs/index.html 'Java Web Services Tutorial',
M.Fisher et.al, the Sun Web Site 'Using Java API for XML Messaging'
http://developers.sun.com/sw/building/tech_articles/api_xmlmessaging.html
'Writing
Web Application Deployment Descriptors', BEA,
http://e-docs.bea.com/wls/docs61/webapp/webappdeployment.html
Overview
JAX-RPC provides a 'hands-off' approach to programming Web services
where a Java developer needs only program the RMI interface and provide
an implementation class for this interface. This simplicity is traded
for a
complicated compilation, packaging and deployment phase which ends up
putting 'distance' between the developer and the code that is
ultimately
deployed as a web service.
What is SAAJ?
SAAJ, the SOAP with Attachments API for Java, on the other brings
the developer a step closer to controlling the actual processes
involved
in creating Web services. SAAJ allows us to build and exchange SOAP
messages over the web. This is API is in fact used behind the scenes
in JAX-RPC.
SAAJ Versions
The SAAJ API that is shipped with the Java Web Services Development
Package version 1.5, ( jwsdp-1.5 ) is version 1.2.1. SAAJ depends on
packages found in the JAXP API and also uses packages in the JavaMail
and JAF ( JavaBeans Activation Framework) APIs.
The Key SAAJ
Package
The key package for SAAJ is the Java extension package, javax.xml.soap.
Key SAAJ Package
A Brief Review
of Key SOAP Constructs
Because SAAJ is intimately associated with the constructs of the SOAP
API we should refresh our memories as to what the basic parts of a SOAP
message are. If the structure of the SOAP message is not clear in our
minds
before programming in SAAJ, the programming model will be less clear
than
it should be. We review the parts of a SOAP message in the context of
how
SOAP messages are viewed from inside the SAAJ API. First let us recall
a SOAP example from our earlier SOAP note.
A quick look shows the an outer root Envelope element holding a Body
element. The Body element holds a nested PurchaseOrder element.
This element's name is fully
qualified using the namespace prefix, 'myNS'
that has been assigned a URI. Notice the sub-elements within this
element,
<item>, <quantity>
and <description> are not themselves qualified explicitly,
but will by default have the
namespace of the parent element.
SOAP Example
from the Apache
SOAP User Guide
<soap:Envelope xmlns="http://xml.apache.org/axis/wsdd/"
xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
<soap:Body>
<myNS:PurchaseOrder xmlns:myNS="http://commerce.com/PO">
<item>SK001</item>
<quantity>1</quantity>
<description>Sushi Knife</description>
</myNS:PurchaseOrder>
</soap:Body>
</soap:Envelope>
The optional header element is not present in this example. The
following
sample shows a Header element in a SOAP message. Notice the
<Header>
element has a namespace qualified nested header element that is named
by the application.
SOAP Example with Header Element
<?xml
version="1.0"?>
<soap:Envelope xmlns:soap="
xmlns:env="http://www.w3.org/2003/05/soap-envelope"
soap:encodingStyle="http://www.w3.org/2003/05/soap-encoding">
<soap:Header>
<cs:creditStatus xmlns:cs="http://www.example.com/status/"
soap:mustUnderstand="true">
1
</cs:creditStatus>
</soap:Header>
...
...
</soap:Envelope>
Following is a SOAP 1.1 Fault Message.
Sample of a SOAP 1.1 Fault
Message
<env:Envelope
xmlns:SOAP-ENV =
"http://schemas.xmlsoap.org/soap/envelope/">
env:encodingStyle =
"http://schemas.xmlsoap.org/soap/encoding/">
<env:Body>
<env:Fault>
<faultcode> env:VersionMismatch</faultcode>
<faultstring> Cannot process other than SOAP 1.1. messages
</faultstring>
<faultactor>http://OleSoap.com/getOldPublications</faultactor>
</env:Fault>
</env:Body>
<env:Envelope>
Notice in the following SOAP 1.2 Fault Message there is a nested
<Detail>
element that provides additional information regarding the fault.
Sample of a SOAP 1.2 Fault
Message
<?xml version='1.0' ?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope">
<env:Body>
<env:Fault>
<env:Code>
<env:Value>env:Sender</env:Value>
<env:Subcode>
<env:Value>rpc:BadArguments</env:Value>
</env:Subcode>
</env:Code>
<env:Reason>
<env:Text xml:lang="en-US">Processing error</env:Text>
<env:Text xml:lang="cs">Chyba zpracování</env:Text>
</env:Reason>
<env:Detail>
<e:myFaultDetails
xmlns:e="http://travelcompany.example.org/faults" >
<e:message>Name does not match card number</e:message>
<e:errorcode>999</e:errorcode>
</e:myFaultDetails>
</env:Detail>
</env:Fault>
</env:Body>
</env:Envelope>
The Tree View
of SOAP Messages
If we consider that a SOAP message may be composed of a message part
that is SOAP and a non-XML part that is a MIME attachment we can think
of
the overall SOAP message as consisting of a SOAP part and optionally, an
Attachment part.
Nodal View of a SOAP Message
SOAP Message
|_____ SOAP Part
|_____ Attachment Part
We can expand this tree by further decomposing the SOAP Part into a
SOAP
envelope which further divides int an optional Header element and a
SOAP
body. The attachment part, if present is required to have a content
header and
associated content.
Expanded View of a SOAP Message
SOAP Message
|___ SOAP Part
| |___SOAP
Envelope
|
|_________SOAP Header // ?
|
|_________SOAP Body
|
|
|___ Attachment Part // *
|____ContentHeader(s)
|____Content
Corresponding SAAJ Classes & Interfaces
Each of the elements in the above tree diagram has a corresponding SAAJ
class that represents that element or part. Leaving aside the
corresponents
that will populate these element classes, we can repeat the tree a
final time
including the corresponding SAAJ classes.
Expanded View of a SOAP Message with
Corresponding SAAJ classes / interfaces
SOAP Message // SOAPMessage
|__SOAP Part // SOAPPart
| |__SOAP Envelope //
SOAPMessage
| |_________SOAP
Header//
SOAPMessage // optional
|
|_________SOAP Body //
SOAPMessage
|
|
|__Attachment Part //
AttachmentPart
|____ContentHeaders //
MimeHeaders
|___ContentHeader //
MimeHeader
SAAJ API Overview
SAAJ Structural
Classes
If we reconsider our first Tree describing a SOAP message, we collect
structural
elements that are represented by Java classes.
Nodal View of a SOAP Message
SOAP Message
|_____ SOAP Part
|_____ Attachment Part
Major SAAJ Structural Classes
SAAJ
Class
|
Description
|
SOAPMessageFactory
|
- a factory for creating
new SOAPMessage objects
|
SOAPMessage
|
- root for all SOAP
messages
|
SOAPPart
|
- container for
SOAP portion of SOAPMessage object
|
AttachmentPart
|
- container for the
Attachment Part of a SOAPMessage
|
The major SAAJ classes supply methods that return objects that
implement
the following interfaces. These SAAJ interfaces are used to populate
empty
SOAP messages.
SAAJ Interfaces
SAAJ
Interface
|
Description
|
Node
|
SOAP's own
extension of the generic Node interface. Supplies
methods such as detachNode( ) as well as providing it's parent,
generic Node's methods, for getting, setting and interrogating
nodes of Tree structures. |
Envelope
Interfaces |
|
SOAPEnvelope |
- container for the
SOAPHeader and SOAPBody objects
|
SOAPHeader |
- represents a SOAP
Header Element`
|
SOAPHeaderElement |
- represents the contents
in the SOAP header part
|
SOAPBody |
- container for
SOAPBodyElement objects.
|
SOAPBodyElement
|
- represents the element
content of a SOAPBody object |
Fault Interfaces |
|
SOAPFault |
- represents Fault
element containing error information
|
SOAPFaultElement |
-represents an element in
a Fault Message
|
Detail |
-a SOAPFaultElement for
providing detailed error information |
DetailEntry
|
- the content for a
Detail object |
General |
|
Name |
- represents an XML name,
methods get the namespace
prefix, and the local & namespace-qualified names. |
Text
|
- "represents a text
node, in element content or a comment. |
Others |
|
SOAPFactory
|
- factory that creates a
variety of SOAP XML tree objects
- replaces deprecated SOAPElementFactory
|
SOAPConstants
|
- interface supplies
constants for three SOAP namespace
URIs including SOAP encoding, SOAP envelope and
the 'next' URL used in signals to SOAP intermediaries.
|
SOAPElement
|
- this interface is
the parent of all the standard SOAP
element interfaces and defines many get /set methods
used for loading and retrieving information to and from
SOAP messages.
|
SOAPElement
The SOAPElement is the parent
interface to the different interfaces that are used to
represent standard SOAP elements. These include the following, Detail,
DetailEntry,
SOAPBody, SOAPBodyElement, SOAPEnvelope, SOAPFault, SOAPFaultElement,
SOAPHeader and SOAPHeaderElement. The interface defines a large set of
accessor
and mutator methods that are made available through inheritence to it's
sub-interfaces.
Following is a summary of it's method, the details of which are
available in the SAAJ
API documentation. Their names are for the most part self-descriptive.
SOAPElement Methods
- addAttribute(Name name, String value)
- addChildElement(Name name)
- //
plus 4 more overloaded versions create elements with local or fully
qualified names
- addNamespaceDeclaration(String
prefix, String uri)
- addTextNode(String
text)
- getAllAttributes(
)
- getAttributeValue(Name
name)
- getChildElements
- getChildElements(Name)
- getElementName( )
- getEncodingStyle(
)
- getNamespacePrefixes(
)
- getNamespaceURI(String
prefix)
- getVisibleNamespacePrefixes(
)
- removeAttribute(Name
name)
- removeContents( )
- removeNamespaceDeclaration(String
prefix)
- setEncodingStyle(String
encodingStyle)
SOAP Connectivity Classes
SOAP uses a SOAP connection
factory to generate a SOAP Connection
with a remote URL. SAAJ is able to make simple request / response style
connections. Following are the two classes SAAJ supplies to create
connections.
SOAP Connectivity Classes
Class
|
Description
|
SOAPConnectionFactory |
- a factory that
creates SOAPConnection objects |
SOAPConnection |
- used to create a
point-to-point connection for
sending messages to a remote URL |
Mime Attachment Classes
The second part of the SOAP Message is the attachment parts that allow
MIME
attachments to be sent with a SOAP message. There is a SAAJ class for
the
to represent Mime Attachments, and header and headers classes to hole
Mime
names value pairs.
- object that stores a MIME header name and it's value
MimeHeaders - container for MimeHeader objects
Class
|
Description
|
AttachmentPart |
- a single attachment to
a
SOAPMessage object |
MimeHeader |
- holds a Mime name and
value
pair
|
MimeHeaders |
- container to collect
MimeHeader objects |
.
How SAAJ Classes Are Used
The
SOAPMessageFactory
The SOAPMessageFactory has a
newInstance( ) method to create
SOAPFactory object. The
SOAPFactory object two createMessage( )
methods, one which creates an empty SOAPMessage based on default
values and one which takes a MimeHeaders object and a InputStream
to create a SOAPMessage with associated values.
SOAPMessageFactory Methods // all throw
SOAPException
newInstance( ) //
returns a new MessageFactory object
createMessage( ) // returns a SOAPMessage object using
default values
createMessage( ) // returns a SOAPMessage with
predetermined values
Using the SAAJ API generally begins with the creation of a SOAP
message in the form of a SOAPMessage object. The following formulation
is used to obtain a SOAPMessage in default form. This is basically a
'blank' SOAP message waiting to be filled in.
Formulation for obtaining a
SOAPMessage object in Default Form
MessageFactory factory = MessageFactory.newInstance( );
SOAPMessage message = factory.createMessage( );
The SOAPMessage Class // from the SAAJ API Documentation
A SOAP Message object will by default contain the following:
- A SOAPPart object
- A SOAPEnvelope object
- A SOAPHeader object
- A SOAPBody object
The SOAP part of a message can be retrieved by calling the method
getSOAPPart( ) on the SOAPMessage object that was returned by
the factory.
Example
SOAPPart soapPart = message.getSOAPPart( );
Subsequently, the SOAPPart object is used to get the Envelope. From
the envelope the Body and Header objects are obtained.
Example
SOAPEnvelope envelope = soapPart.getEnvelope( );
SOAPBody
body = envelope.getBody( );
SOAPHeader header = envelope.getHeader( );
The SOAPFactory
Class
The SOAPFactory class deprecates the SOAPElementFactory. The latter
hints at what this factory does but is an incomplete description as the
the factory supplies more than just elements. This factory class also
supplies different common features
that are needed by the various
elements that may be found inside a SOAP message.
The newInstance( ) method returns a new instance of the SOAPFactory.
The other methods of SOAPFactory are used to create names, either local
or fully-qualified, to create elements with or without namespace
qualification
and to create Detail objects for use in conjunction with Fault
messages. All
the methods are declared as throwing SOAPException. The methods are
listed below.
SOAPFactory Methods
//
all methods throw SOAPException
createElement(Name name)
//
returns a SOAPElement with given Name object
createElement(String localName)
//
returns a SOAPElement with given local name
createElement(String localName,String prefix,String uri)
//
returns a SOAP Element with given local name, qualified with given
prefix and namespace URI
createName(String localName)
//
returns a Name object, with given local name
createName(String localName, String prefix, String uri )
//
returns a Name object with given local name, prifix and namespace URI
createDetail( )
// creates a
Detail object which acts as a container for DetailEntry objects
newInstance( )
//
returns a SOAP Factory instance
Following is the formula we
need to use to obtain a SOAPFactory object.
Formula for creating a
SOAPFactory object
SOAPFactory soapFactory = SOAPFactory.newInstance( );
//
use it to create elements, names or details
Adding Content
to the SOAPHeader Object // populating the Header
Since the header is an optional element that is supplied by default.
it may be removed if it is not being used for anything. The detachNode( )
method may be used to detach the header object from the underlying
tree data structure.
Example header.detachNode(
);
To populate the Header object the
Header object needs to be obtained
from the message object and then a SOAPFactory instance used to
create a fully-qualified name and an element to serve as a Header
element.
In the following example from the JWSDP SAAJ tutorial an attribute is
added
as well.
Sun Java Web Services Tutorial
SAAJ Header Example
SOAPHeader header = message.getSOAPHeader( );
// get header
object
Name headerName = soapFactory.createName("Claim",
"wsi","http://ws-i.org/schemas/conformanceClaim/");
// using factory to create a
fully-qualified name
SOAPHeaderElement headerElement =
header.addHeaderElement(headerName);
//
header element with given name added to header
headerElement.addAttribute(soapFactory.createName("conformsTo"),
"http:/ws-i.org/profile/basic1.0");
//
attribute (name & value) added to header element
The Sun Tutorial shows the
SOAP XML code that the above Java
code creates inside the SOAP message.
Sun Java Web Services Tutorial
SOAP XML Header Example
<SOAP-ENV:Header>
<wsi:Claim conformsTo=
"http:/ws-i.org/profile/basic1.0/"
xmlns:wsi="http://ws-i.org/schema/conformanceClaim/"/>
</SOAP-ENV:Header>
Adding Content to the SOAPBody Object
Adding content to the body of
the SOAP message follows the same
sort of process that was used to add content to the header. The
following code from the Sun site's Java Web Services Tutorial
shows the steps involved.
One needs to distinguish between the 'SOAPBody' object and the
SOAPBodyElement object. The SOAPBody object must first be
obtained and then elements are added to it. Top level elements
inside the SOAP message are represented by 'SOAPBodyElement'
objects. Sub-elements can be nested inside 'SOAPBodyElement'
objects. Text is added to each element using the addTextNode( )
method.
The sample begins by getting a 'handle' to the
SOAPBody object,
then creating a name that will be used as the XML indentifier for
for a SOAPBodyElement object. The SOAPBodyElement is added
to the SOAPBody object with the addBodyElement( ) method.
The top-level SOAPBodyElement must have a fully qualified name.
Following is the sample that get's the SOAPBody,
provides a fully-
qualified name for the SOAPBodyElement and adds this element
to the body using the addBodyElement( ) method.
Sun Java Web
Services Tutorial Sample 1
SOAPBody body = soapFactory.getSOAPBody( );
Name bodyName = soapFactory.createName("PurchaseLineItems",
"PO", "http://sonata.fruitsgalore.com");
SOAPBodyElement purchaseLineItems =
body.addBodyElement(bodyName);
In the next example, the Java code causes two Order elements to
be added to the Body element, <PO:PurchaseLineItems>.
Each of
these Order elements have further elements nested, <Product>
and <Price> elements. These will hold text values.
Sun Java Web Services
Tutorial Sample 2
Name childName = soapFactory.createName("Order");
SOAPElement order =
purchaseLineItems.addChildElement(childName);
// In the XML,
an 'Order' element is added to the body element
childName = soapFactory.createName("Product");
SOAPElement product = order.addChildElement(childName);
product.addTextNode("Apple");
// a product
element with text "Apple" is added to the Order element
childName = soapFactory.createName("Price");
SOAPElement price = order.addChildElement(childName);
price.addTextNode("1.56");
// a 'Price'
element is added to the 'Order' element with a text set to "1.56"
childName = soapFactory.createName("Order");
SOAPElement order2 =
purchaseLineItems.addChildElement(childName);
// the process
repeats, a secord Order element
// note though it has it's own reference identifier in the Java code
childName = soapFactory.createName("Product");
SOAPElement product2 = order2.addChildElement(childName);
product2.addTextNode("Peach");
// "ditto" for
Product
childName = soapFactory.createName("Price");
SOAPElement price2 = order2.addChildElement(childName);
price2.addTextNode("1.48");
// "ditto' for
second Price element
Following is the XML that this Java code creates. This will
supply the contents of A SOAP Body tag.
Sun Java Web Services Tutorial Sample 3
<PO:PurchaseLineItems
xmlns:PO="http://www.sonata.fruitsgalore/order">
<Order>
<Product>Apple</Product>
<Price>1.56</Price>
</Order>
<Order>
<Product>Peach</Product>
<Price>1.48</Price>
</Order>
</PO:PurchaseLineItems>
When
this XML code is sent as a SOAP message the
element will take it's place inside the message as is
shown in the following example.
Example
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<PO:PurchaseLineItems
xmlns:PO="http://www.sonata.fruitsgalore/order">
<Order>
<Product>Apple</Product>
<Price>1.56</Price>
</Order>
<Order>
<Product>Peach</Product>
<Price>1.48</Price>
</Order>
</PO:PurchaseLineItems>
</SOAP-ENV:Envelope>
Sending SOAP
Messages With SAAJ
The
first objective of the SAAJ developers was to supply a Java API
for creating SOAP messages, leaving messaging to be handled by
Java APIs such as JAXM (Java API for XML Messaging). SAAJ
does however supply a basic connection mechanism for request-
response messaging.
The
process for sending a message using SAAJ is is simple and
elegant. First a connection is obtained, and a call is made on an
endpoint, sending the message in the process.
Following
is the Factory pattern used to obtain a SOAPConnectionFactory
object which then is used to get a connection.
Formula for Getting a SOAPConnection Object
SOAPConnectionFactory soapConnectionFactory =
SOAPConnectionFactory.newInstance( );
SOAPConnection connection =
soapConnectionFactory.createConnection( );
Once the endpoint URL has been encapsulated in the form of a Java URL
it and the message that was created earlier can serve as arguments to
the
call( ) method, invoked on the SOAPConnection object. The SOAP response
is returned as a SOAPMessage object by the call( ) method.
Sending the SOAP Message //
need import java.net.*; for URL class
URL
endpoint =new URL("https://www-3.ibm.com/services/uddi/inquiryapi");
SOAPMessage response = connection.call(message, endpoint);
The connection should be closed when finished to release system
resources.
Example
connection.close( );
We
can let Sun put it all together for us after which we can build our
own
example
using other techniques that SAAJ supplies.
Receiving a SOAP
Message
The
response that comes back from the call( ) method needs to be
unravelled in a manner similar to how the message has been put together.
The getSOAPBody( ) is called on the response message to obtain the
SOAPBody object. Child elements are returned to Iterator using
getChildElements( ). An Iterator is a java.util class of
undefined length
that can collect a variable length, set of objects. The Iterator class
supplies the hasNext( ) and next( ) methods that are used to return the
objects that are stored in the Iterator. Following is the typical form
the
Iterator takes.
General Form of Taken to Access an Iterator
while(iterator.hasNext(
)){
Sometype typeObject = (SomeType) iterator.next( );
// do something with typeObject
}
Following
is a sample from the Sun Java Web Service Tutorial that
shows the Iterator being used to capture SOAPBodyElements. The
Sun Java Web Services sample is built with the knowledge that
there is only one SOAPBodyElement, so they are able to omit
the while loop.
Sun Java Web
Services Tutorial Sample
Iterator iterator = soapBody.getChildElements(bodyName);
SOAPBodyElement bodyElement =
(SOAPBodyElement)iterator.next( );
String
lastPrice = bodyElement.getValue( );
System.out.print("The last price for SUNW is ");
System.out.println(lastPrice);
Sun's Java Web
Service Tutorial's
Request.java Example
Over what was discussed earlier, the code adds import statements, a
main
method, and a try/catch block for exception handling. In order to run
the
following code, the javax.xml.soap package has to be made available.
You
can hard code the paths into your system or find the package in
your JWSDP directory using a -cp or -classpath switch at the command
line. Alternatively, just place the saaj jars into the
<jdk_home>/jre/lib/ext
directory.
Sun Java Web Service Tutorial Example
import
javax.xml.soap.*;
import java.util.*;
import java.net.URL;
public class Request {
public static void main(String[] args) {
try {
SOAPConnectionFactory soapConnectionFactory =
SOAPConnectionFactory.newInstance( );
SOAPConnection connection =
soapConnectionFactory.createConnection( );
SOAPFactory soapFactory =
SOAPFactory.newInstance( );
MessageFactory factory =
MessageFactory.newInstance( );
SOAPMessage message = factory.createMessage( );
SOAPHeader header = message.getSOAPHeader( );
SOAPBody body = message.getSOAPBody( );
header.detachNode( );
Name
bodyName = soapFactory.createName(
"GetLastTradePrice", "m",
"http://wombats.ztrade.com");
SOAPBodyElement bodyElement =
body.addBodyElement(bodyName);
Name
name = soapFactory.createName("symbol");
SOAPElement symbol =
bodyElement.addChildElement(name);
symbol.addTextNode("SUNW");
URL
endpoint = new URL
("http://wombat.ztrade.com/quotes");
SOAPMessage response =
connection.call(message, endpoint);
connection.close( );
SOAPBody soapBody = response.getSOAPBody( );
Iterator iterator =
soapBody.getChildElements(bodyName);
SOAPBodyElement bodyElement2 =
(SOAPBodyElement)iterator.next( );
String lastPrice = bodyElement2.getValue( );
// 2nd SOAPBodyElement name changed to remedy error report
System.out.print("The last price for SUNW is ");
System.out.println(lastPrice);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
Other SAAJ Techniques For Loading SOAP
Message
While building SOAP Messages step by step from inside a Java
program might be described as 'cool', it remains quite a tedious
process. As well, for large and complex messages, the process
would be prone to error if under a human's control. Additionally,
developers who are conversant in XML but not Java would be
more at ease building SOAP messages in XML.
Fortunately, SAAJ supplies techniques for loading preformed
SOAP messages into SOAP Envelopes.
Adding a File
to the SOAPPart Object
An XML file can be associated with a SOAPPart object. This
involves utilizing the JAXP DOM API. A DocumentBuilderFactory
is used to obtain a DocumentBuilder object which is used to parse
an XML file and store the result as a Document object. The Document
object is encapsulated as a DOMSource object which is set as the
content of the SOAPPart object.
These steps are illustrated in the following Sun Tutorial example.
Sun Java Web Services Tutorial
Example
DocumentBuilderFactory dbFactory =
DocumentBuilderFactory.newInstance( );
dbFactory.setNamespaceAware(true);
DocumentBuilder builder = dbFactory.newDocumentBuilder( );
Document document = builder.parse( "file:///music/order/soap.xml");
DOMSource domSource = new DOMSource(document);
SOAPPart soapPart = message.getSOAPPart( );
soapPart.setContent(domSource);
The Sun's tutorial cautions that the file represents the whole SOAP
message and must supply everything that is needed to form the
SOAPPart object. As such, the XML file needs to include the
<SOAP-ENV: Envelope> and <SOAP-ENV:Body> elements.
//
Using this technique the Document needs to include the SOAP Envelope
& Body Elements
Adding a
Document Object to the SOAPBody Object
A second variation on this technique allows a Document object to
be added to a SOAPBody object. This allows us to skip the feeding
the Document object into the DOMSource instance. Instead of obtaining
a SOAPPart object, the SOAPBody object is obtained from the message
instance.
Example // picking up from
the fourth line of code in the above example
SOAPBody body = message.getSOAPBody( );
SOAPBodyElement docElement = body.addDocument(document);
//
because the element by the document is a SOAPBodyElement it is
// assumed that the document in this
case will not include the <Envelope>
// and Body elements.
Adding
Attributes
The SOAPElement interface is
the parent interface for all the structural,
SOAP elements. Accordingly, it's addAttribute( ) method can be
called
on all the standard SOAP elements. Following is the form adding an
attribute takes.
Sun Java Web Service Tutorial
Example
Name attributeName =
envelope.createName("id");
//
creating attribute name
person.addAttribute(attributeName,"Person7" );
//
adding attribute to person object, initialized to "Person7"
// person is a SOAPElement object reference
Associated XML
<person id="Person7"> ... </person>
Shown earlier, the SOAPElement interface defines a get methods
for retrieving attribute values from an element. It supplies the
following methods.
- getAllAttributes( )
- getAttributeValue(Name
name)
The getAllAttributes( ) method returns an Iterator object that
can be traversed to obtain all the attribute names of an element.
Once a name is obtained a value for it can be obtained using
the getAttributeValue( ) method.
Sun Java Web Services Tutorial
Example
// altered
Iterator iterator = person.getAllAtributes( );
while(iterator.hasNext( )){
Name attributeName = (Name) iterator.next( );
String attributeValue = person.getAttributeValue(attributeName);
// . . .
}
SOAP Defined
Header Attributes
The SOAPHeaderElement
Interface defines methods for accessing
SOAP defined attributes. These get and set methods are shown
below.
- setActor(String actorURI)
- getActor( ) // returns actor URI
- setMustUnderstand(boolean mustUnderstand)
- getMustUnderstand // returns boolean
Setting and Retrieving Fault Information
The Fault Element is represented in SAAJ by it's own SOAPFault
object. The way it is created and populated is fairly straight forward
as is shown in the following example from the Java Web Services
Tutorial.
Sun's Java Web Services
Tutorial Example
SOAPBody body = message.getSOAPBody( );
SOAPFault fault = body.addFault( );
//assumes
soapFactory is already created
Name faultName = soapFactory.createName("Server",
"", SOAPConstants.URI_NS_SOAP_ENVELOPE);
//
example of SOAP constant, here representing the SOAP Envelope namespace
URI
fault.setFaultCode(faultName);
fault.setFaultActor("http://gizmos.com/orders");
fault.setFaultString("Server Not Responding");
Retrieving the Fault information mirrors the code used to set
the Fault object. A test to see if the body has a Fault is done
before calling the getFault( ) method.
// avoids a
null pointer exception
Sun's Java Web Services
Tutorial Example
SOAPBody body = message.getSOAPBody( );
if (body.hasFault( )){
//assumes
soapFactory is already created
SOAPFault newFault = body.getFault( );
//
example of SOAP constant, here representing the SOAP Envelope namespace
URI
Name code = newFault.getFaultCodeAsName( );
String string = newFault.getFaultString( );
String actor = newFault.getFaultActor( );
// . . .
}
We have left out dealing with Details, the SAAJ interface that stores
detail information. To see an example of a Detail object being added
to a Fault object and later accessed, see the Java Web Services
Tutorial.
Adding Attachments With SAAJ
Attachments are needed to represent any content that is not
XML. Attachments, of which there may be zero or more, may
have one or more headers associated with them. The only
required header is for Content-Type. The Content-Id and
Content-Location headers are optional.
Adding attachments with SAAJ is is also straight forward as is
shown in the following example where a String object is created
to represent plain text data transported as an attachment.
Sun's Java Web Services Tutorial Example
AttachmentPart attachment = message.createAttachmentPart( );
//
an AttachmentPart object is created
String stringContent = " I hope Sun doesn't sue me " +
"for using so many of their examples.";
//
some extra-XML content
attachment.setContent(stringContent, "text/plain" );
//
associates MIME type with content
attachment.setContentID("instructors_plea");
message.addAttachmentPart(attachment);
The above example results in an attachment being created
with the string represented by 'stringContent' as it's data
part and with a header that identifies Content-Type as "text/plain",
and a header for Content-Id set to "instructors_plea".
Sun's Java Web
Services Tutorial Example
java.util.Iterator iterator = message.getAttachments( );
while(iterator.hasNext( )){
AttachmentPart attachment = (AttachmentPart)
iterator.next( );
String id = attachment.getContentID;
Stirng type= attachment.getContentType( );
System.out.println("Attachment " + id +
"
has content type " + type);
if(type == "text/plain"){
Object
content = getContent( );
System.out.println("Attachment " +
"contains:\n" + content);
}
}
//
The Sun tutorial also shows using the DataHandler class from the
// JAF (JavaBeans Activation
Framework) to add data to an attachment.
SAAJ Client With a JAXM Server Example
// Code
Samples from 'Using Java API for XML Messaging' by Joe Attardi, Sun
MicroSystems
Many thanks to Sun's Joe Attardi for supplying for fine example that
shows
us how the client and server interact to provide a meaningful response
using
SAAJ in conjunction with a JAXM server application. See the original
article at
- Using Java API for XML Messaging' by
Joe Attardi, Sun MicroSystems
- http://developers.sun.com/sw/building/tech_articles/api_xmlmessaging.html
WeatherClient
// from 'Using Java API for XML Messaging'
by Joe Attardi, Sun MicroSystems
import
javax.xml.messaging.*;
import javax.xml.soap.*;
import java.util.Iterator;
public class WeatherClient
{
public static void main(String[] args)
{
try
{
// Create a new, empty message.
MessageFactory msgFactory =
MessageFactory.newInstance( );
SOAPMessage message = msgFactory.createMessage( );
// Obtain
references to the various parts of the
message.
SOAPPart msgSOAP = message.getSOAPPart();
SOAPEnvelope msgEnv = msgSOAP.getEnvelope();
SOAPBody msgBody = msgEnv.getBody();
// Create the <w:GetForecast> body
elements.
Name nameForecast = msgEnv.createName("GetForecast",
"w",
"http://weather.news.com/weatherservice");
SOAPBodyElement beForecastBOS =
msgBody.addBodyElement(nameForecast);
SOAPBodyElement beForecastNYC =
msgBody.addBodyElement(nameForecast);
// Create the
<City> child elements under the
// <w:GetForecast>
elements.
Name nameCity = msgEnv.createName("City");
SOAPElement elCityBOS =
beForecastBOS.addChildElement(nameCity);
SOAPElement elCityNYC =
beForecastNYC.addChildElement(nameCity);
// Add the city
names to each <City> child
element.
elCityBOS.addTextNode("Boston");
elCityNYC.addTextNode("New York");
// Remove the SOAP
header.
msgEnv.getHeader( ).detachNode( );
// Get a SOAP connection from the connection factory.
SOAPConnectionFactory connFactory =
SOAPConnectionFactory.newInstance( );
SOAPConnection conn = connFactory.createConnection(
);
// Define the
message destination.
URLEndpoint destination = new
URLEndpoint("http://weather.news.com/weatherservice");
// Send the message and wait for the response.
SOAPMessage response = conn.call( message,
destination );
// Close the
connection.
conn.close( );
SOAPPart respSOAP = response.getSOAPPart();
SOAPEnvelope respEnv = respSOAP.getEnvelope();
SOAPBody respBody = respEnv.getBody( );
// fault handling -ed
if( respBody.hasFault( ) )
{
SOAPFault err =
respBody.getFault( );
System.out.print("Error: " +
err.getFaultCode( ) +
"
(");
System.out.println(err.getFaultString( ) + ")");
}
Name nameForecastResponse =
respEnv.createName("Forecast",
"w",
"http://weather.news.com/weatherservice");
Iterator itResponses =
respBody.getChildElements(nameForecastResponse);
while (itResponses.hasNext( ))
{
System.out.println("==============[ Weather Forecast ]==========");
SOAPBodyElement elForecast =
(SOAPBodyElement)itResponses.next();
Iterator itChildren =
elForecast.getChildElements( );
while (itChildren.hasNext( ))
{
SOAPElement elData =
(SOAPElement)itChildren.next( );
String strItem =
elData.getElementName().getLocalName( );
String strValue = elData.getValue( );
System.out.println(strItem + ": " + strValue);
}
System.out.println("===========================================\n");
}
}
catch (Exception e)
{
System.out.println("Error: " + e.getMessage());
}
}
}
WeatherService
// from 'Using Java API for XML Messaging'
by Joe Attardi, Sun MicroSystems
import javax.servlet.*;
import javax.servlet.http.*;
import javax.xml.messaging.*;
import javax.xml.soap.*;
import java.util.Iterator;
public class WeatherService extends JAXMServlet
implements ReqRespListener
{
public SOAPMessage onMessage( SOAPMessage message )
{
try
{
// Get a message factory and create the response
message.
MessageFactory msgFactory =
MessageFactory.newInstance( );
SOAPMessage response = msgFactory.createMessage( );
// Obtain references to the various parts of the
response
// message.
SOAPPart respSOAP = response.getSOAPPart();
SOAPEnvelope respEnv = respSOAP.getEnvelope();
SOAPBody respBody = respEnv.getBody();
// Obtain references to the various parts of the
request
// message.
SOAPPart msgSOAP = message.getSOAPPart();
SOAPEnvelope msgEnv = msgSOAP.getEnvelope();
SOAPBody msgBody = msgEnv.getBody();
// Create all the
different Names that are needed.
Name nameForecast = msgEnv.createName("Forecast",
"w",
"http://weather.news.com/weatherservice");
Name nameCity = msgEnv.createName("City");
Name nameConditions =
msgEnv.createName("Conditions");
Name nameLowTemp = msgEnv.createName("LowTemp");
Name nameHighTemp = msgEnv.createName("HighTemp");
Name nameRequest = msgEnv.createName("GetForecast",
"w",
"http://weather.news.com/weatherservice");
// Access the
<w:GetForecast> elements in the
request.
Iterator itRequests =
msgBody.getChildElements(nameRequest);
// Iterate over each <w:GetForecast> element,
// getting the data and adding a new
<w:Forecast>
// element to the response message for each one.
while(itRequests.hasNext())
{
SOAPBodyElement elRequest =
(SOAPBodyElement)itRequests.next();
Iterator itCities =
elRequest.getChildElements(nameCity);
SOAPElement elCity =
(SOAPElement)itCities.next();
String strCityName =
elCity.getValue();
if
(!cityExists(strCityName)) // if city doesn't
exist set up fault -ed
{
if
(!respBody.hasFault())
{
SOAPFault respErr = respBody.addFault();
respErr.setFaultCode("Client.NoSuchCity");
respErr.setFaultString
("You specified one or more
invalid cities in your request.");
}
}
else
// else set up
reponse -ed
{
SOAPBodyElement elResponse =
respBody.addBodyElement(nameForecast);
SOAPElement elRespCity =
elResponse.addChildElement(nameCity);
SOAPElement elRespConds =
elResponse.addChildElement(nameConditions);
SOAPElement elRespLowTemp =
elResponse.addChildElement(nameLowTemp);
SOAPElement elRespHighTemp =
elResponse.addChildElement(nameHighTemp);
elRespCity.addTextNode(strCityName);
elRespConds.addTextNode(
getConditions(strCityName));
elRespLowTemp.addTextNode(
getLowTemp(strCityName));
elRespHighTemp.addTextNode(
getHighTemp(strCityName));
}
}
// Remove the SOAP header.
respEnv.getHeader( ).detachNode( );
// Return and send the response message.
return response;
}
catch (SOAPException se)
{
System.err.println("SOAPException: " +
se.getMessage());
return null;
}
}
// Hard coded replies have been supplied for Mr. Attona's methods - ed
// Access a data
store or Web service to determine
whether
// or not a weather forecast exists for a particular
city.
private boolean cityExists(String strCityName) {
return true; }
// Access a data
store or Web service to get the
conditions.
private String getConditions(String strCityName) {
return "Sunny"; }
// Access a data store or Web service to get the low
// temperature.
private String getLowTemp(String strCityName) {
return "12 degrees Celsius"; }
// Access a data
store or Web service to get the high
// temperature.
private String getHighTemp(String strCityName) {
return "22 degrees Celsius"; }
}
Deployment
The JAXM
package
We go on to look at JAXM next
week, but in the meantime we need to
run this JAXM server we borrowed. The package for JAXM is not part
of the current JWSDP distribution although it soon will be part of
subsequent distributions. The JAXM API is available as a separate
download at the following site.
JAXM Download
http://java.sun.com/xml/downloads/jaxm.html
Deployment of a JAXM Server will be similar but a lot simpler than
JAX-RPC
deployment. This is because JAXM servers are just servlets and have the
same deployment requirements. We still need War directory structure
but,
in Tomcat we do not need to pack the directory structure in a jar.
(This is
not surprising as the first thing Tomcat does is unpack the war file
that an
application is archived in.) We do need a web.xml file to
tell Tomcat about
our servlet, where it is and what path will be used to find it.
We could use the 'Ant' build tool to do this job, however what needs to
be
done is not complicated so it really is not neccessary to use the tool.
First
we need to supply a web.xml file to configure Tomcat.
The web.xml
file
The web.xml file is a standard
specified in J2EE. It is the standard 'deployment
descriptor' used to provide web applications configuration parameters
to J2EE
compliant web servers. No surprise, deployment
descriptors are standard XML
text files. Specifically, the deployment descriptor, web.xml is
described in Sun's
Servlet 2.3 specification.
Interestingly, rather than schema,
the web.xml file uses the classic Document
Type declaration to define it's types. You can view it by using the URL
described
in the DOCTYPE element. Do not be dismayed when you see nothing! The
contents are being filtered. Click on the browser's View --> Source
menu button
and the Doctype document becomes visible.
Doctype Declaration for the
web.xml File
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
Some
of the most basic elements found in the deployment descriptor
that are most relevant to quick deployment of our example are described
below.
The
<webapps> Element
The root element is <webapps>.
Example <web-app>
</web-app>
The
<servlet> Element
A servlet needs to
be given a name that will be used to reference the
the servlet by a user. It's class also needs to be specified. Other
properties may also be set.
The following example gives the servlet identified in the
<servlet-class>
element the name 'PassWordAgent' which will be used as part of the
URL that accesses the servlet's functionality. The
<servlet-class>
element gives the qualified name for the servlet. (Under the classes
directory of the Web Application Archive, there will be an 'access'
folder in which is located the PassWordAgent.class file.)
A Servlet Element
Example
<servlet>
<servlet-name>PassWordAgent</servlet-name>
<servlet-class>access.PassWordAgent</servlet-class>
</servlet>
The
<servlet-mapping> Element
The servlet also requires a
mapping to a URL. The <servlet-mapping>
is used to map an element to one or more URL patterns. The following
URL Pattern will map the servlet under the name of the webapp itself
which is generally the name of the folder that is under the 'webapps'
directory. ( Assume you web archive file is expanded to 'X' under
webapps, your path to this servlet would be as follows on a J2EE
compliant web server running locally.
Example "http://localhost:8080/X/PassWordAgent"
A servlet-mapping Example
<servlet-mapping>
<servlet-name>PassWordAgent</servlet-name>
<url-pattern>/PassWordAgent</url-pattern>
</servlet-mapping>
There are many more elements and details to be investigated in a
web.xml file. For a more detailed inspection of the web.xml file view
"Writing Web Application Deployment Descriptors" at the BEA site.
Apache's Tomcat documentation is also excellent for web.xml files.
http://e-docs.bea.com/wls/docs61/webapp/webappdeployment.html#1006167
The Web App Directory Structure
Recall Web apps have a
standardized directory structure that by and large
provide the portability, allowing a web application to be moved around
and
deployed in different web server environments. Following is a reminder
of
the directory structure that needs to be supplied. Our web.xml file is
the
only configuration file that needs to be supplied and is put in the
WEB-INF
directory.
Our servlets are put under the classes directory. The web.xml file
tells
Tomcat where to find our servlet classes.
Standard J2EE Web
Application
Directory Structure
AWebAppService // store HTML and JSPs under the main
directory
|___WEB-INF //
under WEB-INF include Deployment Descriptor web.xml
|____classes
|_____lib
Exercise
In preparation for your test, I would like you to take as your
exercise this week reading the XML, XML Schema and JAXP
notes as your main duties this week.
Optional!
1) If you have these under control, you can do the following. Using
the article "Using Java API for XML Messaging" by Joe Attardi
from Sun Microsystems, as a guide deploy and test the Weather
Client and Server in Tomcat.
http://developers.sun.com/sw/building/tech_articles/api_xmlmessaging.html
Following is the web.xml file that is used in this example.
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD
Web Application 2.3//EN"
"http://java.sun.com/j2ee/dtds/web-app_2_3.dtd">
<web-app>
<servlet>
<servlet-name>WeatherService</servlet-name>
<servlet-class>WeatherService</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>WeatherService</servlet-name>
<url-pattern>/weatherservice</url-pattern>
</servlet-mapping>
</web-app>
2) a) Create a simple message using SAAJ similar to the Request
example in the note.
b) Reduce Joe Attardi's Servlet and supply a simple servlet that
provides an acknowledgement for any message that is received.
There is an example of such a servlet in the text, 'Developing
Java Web Services', R Nagappan et. al. (page 424 Listing 9.2.
also see a similar one on page 435, Listing 9.6)