In addition to the resources it gives you when building simple SOAP messages, SAAJ can help you build SOAP Messages with Attachments (SwA). Appendix E pointed out that SwA is not supported by the WS-I Basic Profile 1.0. Supported or not, SwA is a major piece of functionality in SAAJ, so it's covered by this book. Still, I recommend you use SwA only when your project requires you to.
SwA was covered in detail in Appendix E, but a review of the basic concepts here will be helpful. SwA uses the MIME message format to allow SOAP documents to refer to non-XML data, such as images, documents, digital signatures, and serialized objects. A MIME message (also called a package) is divided into parts, each of which is a block of raw data and MIME headers, separated by a line of unique boundary characters. An SwA message has a MIME type of multipart/related
. The root part of an SwA MIME message is the actual SOAP message. Listing F-1 shows an example of an SwA message (the bulk of the raw data in each MIME part is omitted for brevity).
Example F-1. A Sample SwA Message
------=_Part_0_8994558.1029754184304 Content-Type: text/xml Content-Transfer-Encoding: 8bit Content-Id: cid:[email protected] <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <mh:submitBook xmlns:mh="http://www.Monson-Haefel.com/jwsbook/mh"> <isbn>0596002262</isbn> <coverImage href="cid:[email protected]"/> <manuscript href="cid:[email protected]"/> </mh:submitBook> </soap:Body> </soap:Envelope> ------=_Part_0_8994558.1029754184304 Content-Type: image/jpeg Content-Transfer-Encoding: binary Content-Id: [email protected] ÿøÿà ÔJ+ñ á Ëïør‡ü C_'Ñû# á–_ÿ +ëüŒ¿à¥^_—ÃÏø(qÛø§ìj( ò£â-ìmû@|X´ø_¨xËâwÃh_#~Ô- ... °_ÁΫÙâ÷‰|;£kúfo_sR_î»ñãVøÓñö_Æ qâgÔ‡ÄHÿ h© __fã rvNR˧&>N_|_é~_É·tϸ_′¥'Oë ------=_Part_0_8994558.1029754184304 Content-Type: application/pdf Content-Transfer-Encoding: binary Content-Id: [email protected] %PDF-1.3 ... %%EOF ------=_Part_0_8994558.1029754184304--
You learned in Chapter 13 that SAAJ is a SOAP API that models the structure of an SwA message. SAAJ treats all SOAP messages as SOAP messages with attachments. You can build a simple SOAP message without attachments, but you still need to use the SAAJ SwA programming model to do so. In this section we'll develop SwA messages with real attachments, but before we can start building SwA messages, you need to learn a little about the Java Activation Framework, which is central to SAAJ's support for SwA.
The Java Activation Framework (JAF) was finalized in 1998, back when Sun was primarily focused on Java's role as a GUI programming language. At that time JavaBeans was still in vogue, Swing was hot, servlets were new, and the J2EE platform didn't even exist. Back then Sun needed a standard discovery API for both file viewers and JavaMail, which provided the impetus for creating JAF. By “discovery” I mean a mechanism that can dynamically find the right components to handle data that's arbitrary, but typed. In particular, JAF was developed as a discovery API to enable GUI developers to discover viewers and editors for documents and image data dynamically.
If you have used a GUI file browser, a modern e-mail application, or a Web browser, then you have used a discovery mechanism. All of these applications allow you to view and edit a file or embedded data (such as a document or image) using programs associated with that type of data. For example, when you receive an e-mail with a PDF attachment, double-clicking on the attachment will, if you're like most people, launch Adobe Acrobat viewer. In most modern operating systems, double-clicking on an HTML file in a file browser will launch your default Web browser. That's discovery.
Discovery depends on a registry of some kind that can associate each file type with an appropriate application. Microsoft Windows, for example, allows you to associate file extensions (.html
, .doc
, .gif
, and so on) with specific applications, which is how Windows Explorer knows what application to launch when you double-click on a file. Another example is an e-mail application like Microsoft's Outlook Express or Mozilla (open source Netscape), which maps MIME types to software applications. When you choose to view an e-mail attachment, the e-mail program examines the MIME content-type to find out which application to launch.
Simply put, JAF provides a framework for dynamically discovering visual widgets to handle (view, edit, print, and so on) any kind of data described by MIME headers. While JAF is focused on the GUI side of things, as a framework for dynamically discovering objects that can manipulate specific MIME types, it's useful for non-visual systems like SAAJ as well. In particular JAF can map Java types, like java.awt.Image
, to special handlers that seamlessly convert them to streams of data. This mechanism is important in SOAP messaging because it allows SAAJ to convert Java objects (such as AWT images, DOM Document
objects, and files) into raw data contained by SwA MIME parts, automatically. For example, using SAAJ, you can add an Image
object to a SOAP message without having to convert the image to a stream of bytes first—JAF will take care of that for you, behind the scenes. The following code snippet illustrates.
MessageFactory mf = MessageFactory.newInstance(); SOAPMessage message = mf.createMessage(); java.awt.Image image = ...; // get an image form somewhere AttachmentPart jpegAttach = message.createAttachmentPart(image ,"image/jpeg");
While JAF makes it simple to add attachments to SAAJ messages, it's also full of pitfalls that can cause a lot of unexpected problems. The rest of this section will explain in detail how JAF accomplishes its magic so that you are aware of its strengths and weaknesses and can avoid some of the traps that less knowledgeable developers are sure to encounter when using attachments.
Central to the JAF framework is the javax.activation.DataHandler
class, which is also the central figure in SAAJ facilities for creating and adding attachments to SOAP messages. Whenever an attachment is added to a SAAJ message, the attached object is invariably embedded in a DataHandler
object. This may be hidden, occurring behind the scenes, as was the case in the first example, or it can be done explicitly, as in the following snippet.
AttachmentPart pdfAttach = message.createAttachmentPart();
FileDataSource file = new FileDataSource("manuscript.pdf");
DataHandler pdfDH = new DataHandler(file);
pdfAttach.setDataHandler(pdfDH);
The DataHandler
class, in Listing F-2, can be instantiated to represent just about any kind of data: an image, a PDF document, a DOM Document
object—whatever you like. The DataHandler
provides methods for reading and writing data streams, accessing the MIME type of the data, and creating a Java object that represents the data in a stream.
Example F-2. The javax.activation.DataHandler
Class (Abbreviated)
package javax.activation; import java.io.*; ... public class DataHandler implements java.awt.datatransfer.Transferable { ... public java.io.InputStream getInputStream() ... public java.io.OutputStream getOutputStream() ... public void writeTo(java.io.OutputStream os) ... public String getContentType() ... public Object getContent() ... public DataSource getDataSource() ... ... }
The getInputStream()
method provides access to the data contained by the DataHandler
as an InputStream
, which you can use to read the data.
The getOutputStream()
provides access to the data contained by the Data Handler
via an OutputStream
, which you can use to overwrite the data.
The writeTo()
method writes the data contained by the DataHandler
to any output stream that you provide.
The getContentType()
method returns the MIME content-type, such as image/jpeg
, application/pdf
, or text/xml
, of the data contained by the DataHandler
.
The getContent()
method returns an object that represents the data contained by the DataHandler
. If the data is an image, for example, it might return a java.awt.Image
object.
The getDataSource()
method returns a javax.activation.DataSource
object, which is discussed in detail later.
DataHandler
also defines many other methods related to GUI operations that use the JAF CommandInfo
object, which identifies viewers, editors, and printers, and transfers data to AWT components. These are not important to SAAJ, however, because it's not a GUI tool, and won't be covered in this overview of JAF.
DataHandler
follows the Delegation pattern, providing a consistent interface to data available from many different sources and in many different formats. The term “Delegation” is taken from the lexicon of basic design patterns, and refers to an object that delegates some or all of its work to other objects.[1] When you invoke a method of a DataHandler
, a completely different and hidden object may do the work. DataHandler
defines three constructors, shown in Listing F-3. The type of object to which it delegates tasks depends largely on the constructor used.
Example F-3. Constructors for the javax.activation.DataHandler
Class (Abbreviated)
package javax.activation; public class DataHandler ... public DataHandler(DataSource ds) ... public DataHandler(Object obj, String mimeType) ... public DataHandler(java.net.URL url) ... ... }
When DataHandler
is instantiated using the constructor that expects an Object
and a String
identifying a MIME type, it will dynamically discover and use a specialized content handler, which is an implementation of the class javax.activation.DataContentHandler
. If it's instantiated with either of the other two constructors, the DataHandler
will delegate to a subtype of the javax.activation.DataSource
interface. The difference between these delegates is significant, so understanding their strengths and weaknesses is important. The next two sections describe the DataContentHandler class and the DataSource interface in detail.
An implementation of the DataContentHandler
interface is used to convert a Java object into a stream of data, or vice versa. Each different combination of Java object type and data stream type has its own DataContentHandler
type (called a DCH for short). For example, if you need to convert java.awt.Image
objects into JPEG data streams, you configure your system to use a DCH designed specifically for that purpose. Similarly, DCHs can be used to convert a DOM Document
object into a character stream, a serializable Java object into a Java standard serialized stream, and so on. The possibilities are limitless.
When DataHandler
is instantiated using the DataHandler(Object obj, String mimeType)
constructor, it will delegate exclusively to one specialized type of DCH. Which type depends on the MIME type associated with the DataHandler
when it was created. In the following code snippet, the data contained by a Data Handler
that handles the image/jpeg
MIME type is written to a stream. In order for this to work, the DataHandler
must discover and delegate to a DCH that can handle Image
type objects and JPEG data.
java.awt.Image image = ...; // get image from somewhere DataHandler dataHandler = new DataHandler( image, "image/jpeg" ); FileOutputStream outStream = FileOutputStream( "image.jpg" ); dataHandler.writeTo( outStream );
The DataHandler
discovers the type of DCH it will use from a registry called a mailcap
file. (Vendors can implement registries in other ways, but mailcap
is the default.) A mailcap
file maps MIME types to DCHs and other components. The mailcap
file format is defined by RFC 1524, which specifies a generic format for associating MIME content-types with specific applications.[2] RFC 1524 has been around since 1993—before the Java platform was released—but it's well understood and flexible, so it was adopted for use in JAF. Because JAF is a Java API, the only kinds of entries it can understand in a mailcap
file are those that pertain to Java types, so JAF defines a standard naming system to identify JAF DCHs and other types of components in a mailcap
file. For example, Listing F-4 shows two entries in a mailcap
file that map DCHs provided by the SAAJ reference implementation to their respective MIME types.
Example F-4. A mailcap
File
image/jpeg; ; x-java-content-handler= com.sun.xml.messaging.saaj.soap.JpegDataContentHandler text/xml; ; x-java-content-handler= com.sun.xml.messaging.saaj.soap.XmlDataContentHandler
Each entry in a mailcap
file appears as a single line (this book is not wide enough to display a whole line), and each new line designates a new mapping. An entry starts with the MIME type, followed by name-value pairs associating an application with a command. JAF defines several standard commands, such as x-java-view
, x-java-print
, x-java-edit
, and x-java-content-handler
. We are concerned with the x-java-content-handler
command here, and not the GUI-related commands for viewing, editing, and printing.
The JAF framework will use a mailcap
file located anywhere in the classpath, and will usually be able to use the ones in the java.home/lib
directory or in the “users home” directory. The best place to put the mailcap
file is in the META-INF
subdirectory of the JAR file that contains the DCH class files. If the DCH's JAR file is included in the classpath, the mailcap
will be found and its entries registered with JAF. The mailcap
file should always be named mailcap
with no extension. You can have as many mailcap
files as you wish.[3]
The default behavior is for DataHandler
to use the MailcapCommandMap
class to load mailcap
files from the classpath. When trying to find a DCH, it builds a list of mailcap
files, and checks them in the order found. For JAF 1.0.2 (where the behavior was extended slightly to find all mailcap
files), the mailcap
list order is:
user.home/mailcap
java.home/lib/mailcap
META-INF/mailcap
/META-INF/mailcap.default
When looking for a content-type, the DataHandler
traverses this list in order, and uses the first mailcap
file that has a mapping for the desired MIME type to create the DCH. When you're adding your own DCH to override the behavior of a DCH included with some other API, such as JavaMail, it's vital to understand where it will appear in the above order—especially with the classpath ordering of the META-INF/mailcap
case. A problem occurs when JavaMail is in the classpath, for example, because it defines the following mappings:
text/plain;; x-java-content-handler=com.sun.mail.handlers.text_plain text/html;; x-java-content-handler=com.sun.mail.handlers.text_html text/xml;; x-java-content-handler=com.sun.mail.handlers.text_xml image/gif;; x-java-content-handler=com.sun.mail.handlers.image_gif image/jpeg;; x-java-content-handler=com.sun.mail.handlers.image_jpeg multipart/*;; x-java-content-handler=com.sun.mail.handlers.multipart_mixed
Each of these MIME types also has a mapping in SAAJ/JAX-RPC. If you want to use the mailcap
files that come with a specific SAAJ/JAX-RPC implementation, you have to make sure that they are found before the JavaMail mailcap
files. This is tricky business that can cause you some real headaches if mailcap
files are not set up properly.
Another approach to mapping MIME types to DCHs, which avoids the use of
mailcap
files, is to implement a customDataContentHandler Factory
. There is a static reference to this type in theDataHandler
class that is consulted before themailcap
files are checked, provided you set it programmatically. A big drawback of this approach is that the factory may be set only once in a VM process.
Once the mapping is discovered and the DCH type appropriate to the MIME type is instantiated, the DataHandler
delegates to the DCH object. For example, the code snippet below creates a DataHandler
instantiated with a MIME type of image /jpeg
, and the writeTo()
call should trigger the instantiation of a Data Content Handler
for that MIME type.
java.awt.Image image = ... get image from somewhere DataHandler dataHandler = new DataHandler( image, "image/jpeg"); FileOutputStream outStream = FileOutputStream( "image.jpg" ); dataHandler.writeTo( outStream );
For the writeTo()
method to achieve that result, a DCH must exist that is mapped to the image/jpeg
MIME type and is able to convert an Image
object into a data stream. Figure F-1 illustrates the steps that a DataHandler
will go through to locate and delegate to a DCH.
The first time a DataHandler
needs to delegate to a DCH, it will request an instance of the proper DCH from the JAF framework. The JAF framework checks the mailcap
registry to see if a DCH type is registered for the DataHandler
object's MIME type. If it finds such a DCH type, the JAF creates an instance of that type and returns it to the DataHandler
, which will use the DCH as its delegate. If no DCH is associated with the MIME type, the JAF throws an exception. The DataHandler
object will not attempt to find a DCH until it's actually necessary (lazily), which is why it doesn't throw an exception as soon as it's created with a MIME type that isn't mapped to a DCH.
An important thing to remember is that DCHs are limited to the types of Java objects they are programmed to handle. For example, the reference implementation of SAAJ, which is written by Sun Microsystems, has two DCHs for processing image data: GifDataContentHandler
for the image/gif
MIME type, and JpegData ContentHandler
for the image/jpeg
MIME type. Although JpegDataContent Handler
works fine, the GifDataContentHandler
is practically useless. It cannot convert any type of object (not even java.awt.Image
) into a data stream. Of course, this DCH is part of the reference implementation, so it's not so bad that it's limited, but it's an excellent illustration of an important point: The types of Java objects you can attach to a SOAP message are limited by the types of DCHs you have available. In other words, you cannot arbitrarily attach Java objects to SAAJ messages. You have to be sure that there is a DCH mapped to that MIME type and that the DCH will be able to handle that type of Java object. This information can be obtained only by examining the mailcap
files to discover which DCHs are registered, and then examining the documentation of the DCHs themselves to see what type of Java objects they support. This research takes time, but it can save you a lot of headaches.
If the MIME type or Java object you want to attach to SOAP messages is not supported, you can develop a new DCH and register it in a mailcap
file. That may be a trivial effort or it may be a lot of work, depending on what you want to attach. Fortunately, DCHs are not your only option.
In addition to the DataContentHandler
type, JAF defines the interface type javax.activation.DataSource
, which can be very useful when attaching data derived from some resource like a file or a Web server. When a DataHandler
is created using a DataSource
object, the DataHandler
will not depend on the presence of a DCH to function properly—the DataSource
will possess all the functionality necessary to perform the delegated operations. A DataHandler
will use a Data Source
instead of a DCH when it is constructed with a DataSource
or URL
parameter. The following recapituation of Listing F-3 highlights these two constructors.
package javax.activation; public class DataHandler ... public DataHandler(DataSource ds) … public DataHandler(java.net.URL url) … public DataHandler(java.lang.Object obj, java.lang.String mimeType) ... ... }
Careful—don't use a
DataSource
as theobj
parameter in the constructorDataHandler(java.lang.Object obj
,java.lang.String mime Type)
. TheDataHandler
will not recognize theDataSource
and will attempt to delegate operations to a DCH—which will cause the Data Handler to throw an exception.
The DataSource
interface follows the Adapter design pattern.[4] It provides a single abstraction for any kind of data source. The DataSource
interface defines methods for accessing an InputStream
, an OutputStream
, the MIME type, and the name of the underlying source of data. It's defined as shown in Listing F-5.
Example F-5. The javax.activation.DataSource
Interface
package javax.activation; import java.io.InputStream; import java.io.OutputStream import java.io.IOException; public interface DataSource { public String getName(); public String getContentType(); public InputStream getInputStream() throws IOException; public OutputStream getOutputStream() throws IOException; }
You may have noticed that the methods defined in DataSource
have the same signatures as some of the methods defined in DataHandler
. When a DataHandler
is created with a DataSource
object, it will delegate to the DataSource
the methods they have in common.
JAF defines two standard DataSource
objects: javax.activation.FileDataSource
and javax.activation.URLDataSource
. As its name suggests, FileDataSource
provides access to a file using its getInputStream()
and getOutputStream()
methods, and allows you to read from and write to that file. Under the covers, the FileDataSource
uses java.io.FileInputStream
and java.io.FileOutputStream
to read and write. The following snippet shows you how to create a DataHandler
that will use a FileDataSource
.
// Create a FileDataSource that represents a JPEG file FileDataSource jpegSource = new FileDataSource("someimage.jpeg"); // Create a DataHandler that delegates to the FileDataSource DataHandler dataHandler = new DataHandler( jpegSource );
The URLDataSource
represents a file at some URL. Under the covers the URL DataSource
uses a java.net.HttpURLConnection
if the file is at some distant Internet address, or a java.net.JarURLConnection
if the URL points to a local JAR file, or any other implementation of URLConnection
. The HttpURLConnection
employs the HTTP protocol to read and write files on an HTTP Web server. The HttpURLConnection
.getOutputStream()
method will throw an exception, so this method won't work if you are accessing a URL at a distant Web server—but it does work with JAR files. The following shows how to create a DataHandler
that will use a URLDataSource
object.
// Create a URL that points to a remote PDF document URL url = new URL("http://www.Monson-Haefel.com/jwsbook/document.pdf") // Create a DataHandler that delegates to the URLDataSource URLDataSource dataSource = new URLDataSource( url ); DataHandler pdfHandler = new DataHandler( dataSource );
The URL constructor uses a URLDataSource
under the covers, so it's the same as calling the DataHandler
class's DataSource
constructor with the URLData Source
parameter. The following code snippet also results in a DataHandler
that will use a URLDataSource
.
// Create a URL that points to a remote PDF document URL url = new URL("http://www.Monson-Haefel.com/jwsbook/document.pdf") // Create a DataHandler that delegates to the URLDataSource DataHandler pdfHandler = new DataHandler( url );
Some DataSource
implementations are designed to discover their MIME types dynamically. The FileDataSource
, for example, discovers its MIME type using the MIME-type registry. The default MIME-type registry in JAF is the mimetypes. default
file, which is included in the JAF binary JAR file. You can augment this registry with your own mime.types
file, which should be stored in the same kinds of locations you'd place mailcap
files (see Section F.1.2). The mime.types
and mimetypes.default
files map file extensions to MIME types. Listing F-6 shows the mimetypes.default
file that is included with JAF.
Example F-6. The mimetypes.default
File
text/html html htm HTML HTM text/plain txt text TXT TEXT image/gif gif GIF image/ief ief image/jpeg jpeg jpg jpe JPG image/tiff tiff tif image/x-xwindowdump xwd application/postscript ai eps ps application/rtf rtf application/x-tex tex application/x-texinfo texinfo texi application/x-troff t tr roff audio/basic au audio/midi midi mid audio/x-aifc aifc audio/x-aiff aif aiff audio/x-mpeg mpeg mpg audio/x-wav wav video/mpeg mpeg mpg mpe video/quicktime qt mov video/x-msvideo avi
Because the mimetypes.default
file is included in the standard JAF binary JAR file, all these MIME-type/file-extension mappings are available by default. Each line in the MIME-type registry represents a different MIME-type/file-extension mapping. If a given file extension doesn't have a matching MIME type, then the default MIME type application/octet-stream
is used.
In the next section, you will learn how to apply what you have learned about JAF to the task of creating attachments in SAAJ. As you will quickly discover, having a good understanding of DataHandler
, DataContentHandler
, and DataSource
types will be indispensable to creating SOAP Messages with Attachments using SAAJ.
Adding attachments to a SAAJ SOAPMessage
object is done using an Attachment Part
object, which represents a MIME part—that is, an attachment in an SwA message. The AttachmentPart
provides methods for manipulating the headers, as well as the raw data content of a MIME part. AttachmentPart
objects are contained by the SOAPMessage
and are siblings of the SOAPPart
object, which models the root MIME part (the SOAP document) of the SwA message.
In Listing F-7, SaajExample_F1
creates an SwA that has two attachments: a JPEG image and a PDF document. The SwA generated by SaajExample_F1
is incomplete at this point. As the section progresses, I will fill in the missing pieces.
Example F-7. Creating a Simple SwA Message with SAAJ
package com.jwsbook.saaj; import javax.xml.soap.*; import java.awt.Image; import java.awt.Toolkit; import java.io.FileOutputStream; import javax.activation.FileDataSource; import javax.activation.DataHandler; public class SaajExample_F1 { public static void main(String [] args) throws Exception { // Create SOAPMessage MessageFactory mf = MessageFactory.newInstance(); SOAPMessage message = mf.createMessage(); // Attach java.awt.Image object to SOAP message. Image image = Toolkit.getDefaultToolkit().createImage("cover.jpg"); AttachmentPart jpegAttach = message.createAttachmentPart(); jpegAttach.setContent( image ,"image/jpeg"); message.addAttachmentPart(jpegAttach); // Attach PDF FileDataSource to SOAP message FileDataSource file = new FileDataSource("manuscript.pdf"); DataHandler pdfDH = new DataHandler(file); AttachmentPart pdfAttach = message.createAttachmentPart(); pdfAttach.setDataHandler(pdfDH); message.addAttachmentPart(pdfAttach); // Write SOAPMessage to file FileOutputStream fos = new FileOutputStream("SaajExample_F1.out"); message.writeTo(fos); fos.close(); } }
Executing SaajExample_F1
produces a file that contains the MIME message, including the default SOAP document as well as the JPEG and PDF MIME parts. An abbreviated version of the output file, SaajExample_F1.out
, is shown in Listing F-8.
Example F-8. The Output of SaajExample_F1
(Abbreviated)
------=_Part_0_14746332.1029863145115
Content-Type: text/xml
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header/>
<soap:Body/>
</soap:Envelope>
------=_Part_0_14746332.1029863145115
Content-Type: image/jpeg
ÿøÿà ÔJ+ñ á Ëïør‡ü C_'Ñû# á–_ ÿ +ëüŒ¿à¥^_—ÃÏø(qÛø§ìj(
ò£â-ìmû@|X´ø_¨xËâwÃh_#~Ô-
...
°_ÁÿÙâ÷‰|;£kúfo_sR_î»ñãVøÓñö _Æ qâgÔ‡ÄHÿ h© __fã
rvNR˧&>N_|_é~_É·tϸ_′¥'Oë
------=_Part_0_14746332.1029863145115
Content-Type: application/octet-stream
%PDF-1.3
...
%%EOF
------=_Part_0_14746332.1029863145115--
If you compare this SwA message to the one at the beginning of this Appendix, you'll notice some things are missing, like the Content-Transfer-Encoding
and Content-Id
MIME headers. In addition, the SOAP document itself is devoid of any meaningful content; it's just a skeleton. Later we'll fix these problems, but for now we want to focus on how attachments are added to the SOAPMessage
object.
To run SaajExample_F1
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.
SaajExample_F1
(Listing F-7) starts out by creating a SOAPMessage
and then proceeds to add two attachments: a JPEG image and a PDF document. To create an attachment, you'll first need an object that represents the data you want to attach—we can refer to this object generically as the data object. The JPEG attachment uses an Image
object, while the PDF attachment uses a FileDataSource
as shown in this snippet from SaajExample_F1
.
// Attach with java.awt.Image object to the SOAP message Image image = Toolkit.getDefaultToolkit().createImage("cover.jpg"); ... // Attach with PDF FileDataSource to SOAP message FileDataSource file = new FileDataSource("manuscript.pdf"); ...
Image
and FileDataSource
are very different kinds of data objects, which il lustrates the variety of types you can use to create attachments. As you will soon see, however, this variety depends largely on the types of DataContentHandler
and DataSource
available.
You create an AttachmentPart
using the SOAPMessage.createAttachment Part()
method. This method creates an empty, disconnected AttachmentPart
with no data or MIME type. Once an empty AttachmentPart
is created, its content must be set explicitly using either the setContent()
or setDataHandler()
method, as shown in this snippet from SaajExample_F1
(Listing F-7).
// Attach java.awt.Image object to SOAP message Image image = Toolkit.getDefaultToolkit().createImage("cover.jpg"); AttachmentPart jpegAttach = message.createAttachmentPart(); jpegAttach.setContent( image ,"image/jpeg"); message.addAttachmentPart(jpegAttach); // Attach PDF FileDataSource to SOAP message FileDataSource file = new FileDataSource("manuscript.pdf"); DataHandler pdfDH = new DataHandler(file); AttachmentPart pdfAttach = message.createAttachmentPart(); pdfAttach.setDataHandler(pdfDH); message.addAttachmentPart(pdfAttach);
The SOAPMessage
class also provides two more createAttachmentPart()
methods, each of which consolidates the operations of creating the Attachment Part
and setting the data object in one operation. The following code snippet shows how these alternative createAttachmentPart()
methods might have been used in SaajExample_F1
.
// Attach java.awt.Image object to SOAP message Image image = Toolkit.getDefaultToolkit().createImage("cover.jpg"); AttachmentPart jpegAttach = message.createAttachmentPart(image ,"image/jpeg"); message.addAttachmentPart(jpegAttach); // Attach with PDF FileDataSource to SOAP message FileDataSource file = new FileDataSource("manuscript.pdf"); DataHandler pdfDH = new DataHandler(file); AttachmentPart pdfAttach = message.createAttachmentPart(pdfDH); message.addAttachmentPart(pdfAttach);
There are two methods for adding data objects and MIME types to an empty AttachmentPart
. You can use the AttachmentPart.setContent()
or the AttachmentPart.setDataHandler()
method. SaajExample_F1
uses both methods, as illustrated in the following snippet from Listing F-7.
AttachmentPart jpegAttach = message.createAttachmentPart(); jpegAttach.setContent(image ,"image/jpeg"); ... AttachmentPart pdfAttach = message.createAttachmentPart(); DataHandler pdfDH = new DataHandler(file); pdfAttach.setDataHandler(pdfDH);
The setDataHandler()
and setContent()
methods accomplish exactly the same result. Under the covers, the setContent()
method actually creates a new DataHandler
object and then calls its own setDataHandler()
method. For example, Listing F-9 shows an implementation of AttachmentPart
by a fictitious vendor, XYZ Corporation, that represents the most likely implementation of these methods by a vendor.
Example F-9. A Hypothetical AttachmentPart
Implementation
package com.xyz.saaj; import javax.xml.soap.*; import javax.activation.DataHandler; /* This is a hypothetical example of a vendor's implementation of AttachmentPart. It illustrates that the most likely implementation of the setContent and getContent methods is to reuse the setDataHandler and getDataHandler methods. */ public class XyzAttachmentPart implements javax.xml.soap.AttachmentPart{ DataHandler dataHandler; public DataHandler getDataHandler() throws SOAPException { return dataHandler; } public void setDataHandler(DataHandler dh) throws java.lang.IllegalArgumentException{ dataHandler = dh; } public Object getContent() throws SOAPException { try{ return this.getDataHandler().getContent(); }catch (IOException ex){ throw new SOAPException(ex); } } public void setContent(Object object, String mimeType) throws java.lang.IllegalArgumentException{ DataHandler dh = new DataHandler(object, mimeType); this.setDataHandler(dh); } ... }
As this example illustrates, both setDataHandler()
and setContent()
have corresponding get methods. setContent()
and getContent()
are more convenient to use in some cases because they don't require that you work directly with a DataHandler
object. In most cases they save you a couple of lines of code. Of course, if your data is contained in a DataSource
object, you'll have to use the setData Handler()
method because the setContent()
method works only with simple data objects like java.awt.Image
. Never pass a DataSource
or DataHandler
object into the setContent()
method.
As you can see in Listing F-9, AttachmentPart.getContent()
delegates its work to the underlying DataHandler
object, which will attempt to delegate the call to a DataContentHandler
object. If the DataHandler
was constructed with a data object, then it will delegate to a DCH—provided it can find a match in the mailcap
registry; if it can't locate a DCH, it will throw an exception. If the DataHandler
was constructed with a DataSource
object, it will first attempt to delegate the getContent()
method to a DCH; if it can't find a DCH, it will return an InputStream
obtained from the DataSource
. Interestingly, the DataHandler
always attempts to delegate to a DCH first, even if it was created using a DataSource
.
This seemingly minor point is quite important. It illustrates the purpose of the getContent()
method, which is to return a Java object that represents the attachment. The InputStream
of the DataSource
is returned only as a last resort. For example, if when using the SAAJ reference implementation you create an Attachment Part
using a FileDataSource
based on a JPEG file, and then call AttachmentPart.getContent()
, the method returns a java.awt.image.BufferedImage
object (a subtype of java.awt.Image
). Although the attachment is created with a DataSource
, a DCH found in the mailcap
file is used to generate a Java object for the JPEG data. The following snippet shows how the JPEG is added to an AttachmentPart
as a FileDataSource
, and subsequently accessed as an Image
object.
FileDataSource file = new FileDataSource("cover.jpg"); DataHandler jpegDH = new DataHandler(file); AttachmentPart jpegAttach = message.createAttachmentPart(jpegDH); ... Image image = (Image)jpegAttach.getContent();
The type of object returned from a DCH will depend on how that DCH is coded, but SAAJ specifies a set of return types for five specific MIME types that must be supported by a JAX-RPC-compliant implantation of SAAJ. Because you are likely to use SAAJ in combination with JAX-RPC, this requirement is important. Your vendor may support additional MIME types; check your vendor's documentation for details. Table F-1 shows five MIME types and their corresponding Java types.
Appendix G provides a more detailed explanation of each of these mappings.
Notice that the MimeMultipart
class is part of the JavaMail API, so JavaMail will be in the classpath, with its mailcap
file—which could cause problems when you add your own mailcap
file—as I described in Section F.1.2.
Table F-1. Minimum AttachmentPart.getContent()
Return Types
MIME Type |
|
---|---|
|
|
|
|
|
|
|
|
|
|
In essence the type returned by AttachmentPart.getContent()
depends on the DCHs that are installed. One consequence is that developing portable SAAJ applications may force you to register the same DCHs on every platform you expect your SAAJ application to run on. You can, however, expect compliant J2EE Web Services platforms to support, at the very least, the MIME-to-Java mappings shown in Table F-1.
AttachmentPart
offers a number of methods for adding, finding, removing, and replacing the MIME headers associated with an attachment. Listing F-10 shows these methods as they're declared in the AttachmentPart
class. Most of these methods are self-describing and are also well documented by the SAAJ API documentation, so I'll discuss only a few of them here.
Example F-10. The javax.xml.soap.AttachmentPart
Class (Abbreviated)
package javax.xml.soap;
import java.util.Iterator;
...
public abstract class AttachmentPart {
// Commonly used MIME header methods
public String getContentId() ...
public void setContentId(String contentId)
throws IllegalArgumentException ...
public String getContentLocation()...
public void setContentLocation(String contentLocation)
throws IllegalArgumentException ...
public String getContentType() ...
public void setContentType(String contentType)
throws IllegalArgumentException ...
// Generic MIME header methods
public abstract void addMimeHeader(String name,String value)
throws IllegalArgumentException ...
public abstract void setMimeHeader(String name,String value)
throws IllegalArgumentException ...
public abstract String[] getMimeHeader(String name) ...
public abstract Iterator getAllMimeHeaders() ...
public abstract Iterator getMatchingMimeHeaders(String[] names)...
public abstract Iterator getNonMatchingMimeHeaders(String[] names) ...
public abstract void removeAllMimeHeaders() ...
public abstract void removeMimeHeader(String header) ...
...
}
Appendix E: SOAP Messages with Attachments explains that SwA employs the Multipart /Related
MIME message style when packaging a SOAP document with its attachments. This means that it is the root MIME part, the SOAP document, that refers to the attachments in an SwA, using either Content-Id
or Content-Location
headers. As an example, let's take a closer look at the complete SwA message, shown in Listing F-11. The attachments are referred to by their Content-Id
MIME headers, using href
attributes in the SOAP document.
Example F-11. A Sample SwA Message
------=_Part_0_8994558.1029754184304 Content-Type: text/xml Content-Transfer-Encoding: 8bit Content-Id: cid:[email protected] <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <mh:submitBook xmlns:mh="http://www.Monson-Haefel.com/jwsbook/mh"> <isbn>0596002262</isbn> <coverImage href="cid:[email protected]"/> <manuscript href="cid:[email protected]"/> </mh:submitBook> </soap:Body> </soap:Envelope> ------=_Part_0_8994558.1029754184304 Content-Type: image/jpeg Content-Transfer-Encoding: binary Content-Id: [email protected] ÿøÿà ÔJ+ñ á Ëïør‡ü C_'Ñû# á–_ ÿ +ëüŒ¿à¥^_—ÃÏø(qÛø§ìj( ò£âìmû@|X´ø_¨xËâwÃh_#~Ô- ... °_ÁÿÙâ÷‰|;£küfo_sR_î»ñãVøÓñö _Æ qâgÔ‡ÄHÿ h© __ã rvNR˧&>N_|_é~_É·tϸ_′¥'Oë ------=_Part_0_8994558.1029754184304 Content-Type: application/pdf Content-Transfer-Encoding: binary Content-Id:[email protected] %PDF-1.3 ... %%EOF ------=_Part_0_8994558.1029754184304--
The SAAJ reference implementation doesn't create Content-Id
headers automatically; you must add them manually. In addition, the developer can add other MIME headers, such as the Content-Transfer-Encoding
, which tells us the format of the data (such as binary or base64). You can also change headers. For example, when SaajExample_F1
(Listing F-7) generates its output, it sets the PDF attachment with a Content-Type
of "application/octet-stream
", because no MIME type is associated with the .pdf
extension in the mimetypes.default
file. You can create a mime.types
file with an entry for the .pdf
extension, or you can change it manually, as SaajExample_F2
does in Listing F-12, using one of the MIME header methods.
SaajExample_F2
modifies SaajExample_F1
by employing methods for setting the Content-Id
and the Content-Transfer-Encoding
, and modifying the Content-Type
header of the PDF attachment. This example also adds a SOAPPart
with a complete SOAP message that refers to the attachments by way of their Content-Id
headers.
Example F-12. Creating a Complete SwA Message
package com.jwsbook.saaj; import javax.xml.soap.*; import java.awt.Image; import java.awt.Toolkit; import java.io.FileInputStream; import java.io.FileOutputStream; import javax.activation.FileDataSource; import javax.activation.DataHandler; import javax.xml.transform.stream.StreamSource; public class SaajExample_F2 { public static void main(String [] args) throws Exception { // Create SOAPMessage MessageFactory mf = MessageFactory.newInstance(); SOAPMessage message = mf.createMessage(); // Create the SOAPPart createSOAPPart(message); // Attach with java.awt.Image object to the SOAP message Image image = Toolkit.getDefaultToolkit().createImage("cover.jpg"); AttachmentPart jpegAttach = message.createAttachmentPart(image ,"image/jpeg"); jpegAttach.addMimeHeader("Content-Transfer-Encoding","binary"); jpegAttach.setContentId("[email protected]"); message.addAttachmentPart(jpegAttach); // Attach with PDF FileDataSource to SOAP message FileDataSource file = new FileDataSource("manuscript.pdf"); DataHandler pdfDH = new DataHandler(file); AttachmentPart pdfAttach = message.createAttachmentPart(pdfDH); pdfAttach.addMimeHeader("Content-Transfer-Encoding","binary"); pdfAttach.setContentId("[email protected]"); pdfAttach.setContentType("application/pdf"); message.addAttachmentPart(pdfAttach); // Write SOAPMessage to file FileOutputStream fos = new FileOutputStream("SaajExample_F2.out"); message.writeTo(fos); fos.close(); } public static void createSOAPPart(SOAPMessage message) throws SOAPException, java.io.IOException { // implementation goes here } }
The output of SaajExample_F2
will be similar to the output shown previously in Listing F-11.
Except for the implementation of the createSOAPPart()
method, the example above is complete. The creation of the SOAPPart
is very similar to that of the AttachmentPart
. The next section will address the SOAPPart
and show three different implementations of the createSOAPPart()
method of SaajExample_F2
.
A SOAPPart
object represents the root MIME part of an SwA message, which is always a SOAP document. The SOAPPart
class includes many of the same methods defined in AttachmentPart
, but it doesn't implement the AttachmentPart
interface, because the SOAPPart
is more restrictive than the AttachmentPart
. The SOAPPart
is designed exclusively to handle SOAP documents (MIME type text /xml
), and to provide a means for accessing the SOAPEnvelope
and its children.
For example, in the AttachmentPart
the setContent()
and getContent()
methods return the java.lang.Object
type, so they can work with any kind of data object. The SOAPPart
also defines setContent()
and getContent()
methods, but they set and get the javax.xml.transform.Source
type instead. The Source
type is the return type that JAX-RPC requires for the text/xml
MIME type (see Table F-1), so it's the appropriate type for the SOAPPart
object to set and get. Listing F-13 shows these method signatures in a partial listing of the SOAPPart
class.
Example F-13. The javax.xml.soap.SOAPPart
Class (Abbreviated)
package javax.xml.soap; import javax.xml.transform.Source; ... public abstract class SOAPPart { public abstract void setContent(Source source) throws SOAPException ... public abstract Source getContent() throws SOAPException ... ... }
SOAPPart
is also more restrictive than the AttachmentPart
with its Content-Type
MIME header. Unlike the AttachmentPart
, the SOAPPart
doesn't define a setContentType()
method, because this MIME header must be text/xml
for a SOAP 1.1 document. Like AttachmentPart
, SOAPPart
does provide general-purpose MIME methods (that is, addMimeHeader()
, setMimeHeader()
, and removeMimeHeader()
), but any attempt to change the Content-Type
using one of these methods will result in an IllegalArgumentException
.
For all its restrictions a SOAPPart
does represent a MIME part, and as such it contains a content section (data) and MIME headers. You can populate the content section of a SOAPPart
manually by creating a SOAPEnvelope
, SOAPBody
, and SOAP BodyElement
, and so on. The SOAPMessage.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();
As an alternative, you can populate the content section with an existing SOAP document using a javax.xml.transform.Source
type object, which is how Saaj Example_F2
builds its SOAP message. It first reads a file containing a SOAP document using a StreamSource
object (an implementation of Source
) and then passes the StreamSource
to the SOAPPart
using the setContent()
method. The following snippet is the implementation of the createSOAPPart()
method, which was omitted from the previous listing of SaajExample_F2
(Listing F-12).
import java.io.FileInputStream; import javax.xml.transform.stream.StreamSource; ... public class SaajExample_F2 { ... public static void createSOAPPart(SOAPMessage message) throws SOAPException, java.io.IOException { SOAPPart soapPart = message.getSOAPPart(); FileInputStream soapFile = new FileInputStream("soapwa.xml"); StreamSource source = new StreamSource(soapFile); soapPart.setContent(source); soapPart.addMimeHeader("Content-Transfer-Encoding","8bit"); soapPart.setContentId("[email protected]"); soapFile.close(); } }
StreamSource
is only one of three standard Source
types (StreamSource
, DOMSource
, and SAXSource
), that you can use to set the content of a SOAPPart
. The Source
type and its subtypes are members of the TrAX API, which is the standard Java API for XSLT.
XSLT (Xtensible Stylesheet Language Transformation) is an XML technology that defines a grammar, called an XSLT stylesheet, and processing rules for mapping documents in one format to documents in some other format. For example, you might use XSLT to translate XML-RPC messages into SOAP, or DocBook into XHTML.[5] XSLT stylesheets rely heavily on another XML technology called XPath, which is a declarative language for describing elements in an XML document, something like SQL for XML (that's a loose analogy). The key point is that, using XSLT, you can convert one XML document into another XML document. While XSLT is very powerful and interesting in its own right, an in-depth discussion of it is outside the scope of this appendix. Fortunately, you can remain blissfully ignorant of XSLT without it hampering your use of SAAJ one bit.
What we are interested in here are the Source
types defined by TrAX (Transformation API for XML), which is the J2EE standard API for XSLT. TrAX is defined in the javax.xml.transformation
package and its subpackages. One of the primary types defined by TrAX is javax.xml.transform.Source
, which is not much more than an empty interface. The Source
interface is used as an abstraction for an object that contains or has access to an XML document. In TrAX there are three standard implementations of the Source
type:
javax.xml.transform.stream.StreamSource
javax.xml.transform.dom.DOMSource
javax.xml.transform.sax.SAXSource
The TrAX Source
subtypes provide a convenient mechanism for embedding an XML SOAP document in a SOAPPart
. It should be noted that the TrAX Source
type doesn't provide any common methods for extracting the XML data from the Source
object. Each implementation is completely different, which means the SOAPPart
won't be able to handle arbitrary implementations of the Source
type; it supports only the standard TrAX implementations: StreamSource
, DOMSource
, and SAXSource
.
You use a StreamSource
when an XML document is accessible via some sort of data stream, specifically with java.io.InputStream
and java.io.Reader
types. SaajExample_F2
uses a FileInputStream
, a subtype of InputStream
. It could just as easily have used the FileReader
type, which is better at dealing with international character sets.[6] The following snippet shows how SaajExample_F2
could be implemented to use a FileReader
instead of a FileInputStream
.
import java.io.FileReader; import javax.xml.transform.stream.StreamSource; ... public static void createSOAPPart(SOAPMessage message) throws SOAPException, java.io.IOException { SOAPPart soapPart = message.getSOAPPart(); FileReader soapFile = new FileReader("soapwa.xml"); StreamSource source = new StreamSource(soapFile); soapPart.setContent(source); soapPart.addMimeHeader("Content-Transfer-Encoding","8bit"); soapPart.setContentId("[email protected]"); soapFile.close(); }
Of course a file represents only one type of data stream; you can use StreamSource
to read SOAP messages from network streams or JDBC or JMS or some other resource.
You use a DOMSource
when the XML document is contained in a DOM Node
object, specifically the org.w3c.dom.Node
type, which is a part of the JAXP (Java API for XML Processing) family of technologies. If you work with DOM, a DOMSource
is an excellent means to pass the SOAP document to the SOAPPart
. In the following code snippet the createSOAPPart()
method is modified to build a DOM Document
(a subtype of Node
) object from a file, then set the Document
object as the content of the SOAPPart
.
import javax.xml.transform.dom.DOMSource; import org.w3c.dom.Document; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; ... public static void createSOAPPart(SOAPMessage message) throws SOAPException, java.io.IOException, org.xml.sax.SAXException, javax.xml.parsers.ParserConfigurationException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.parse("soapwa.xml"); document.getChildNodes(); DOMSource domSource = new DOMSource(document); SOAPPart soapPart = message.getSOAPPart(); soapPart.setContent(domSource); soapPart.addMimeHeader("Content-Transfer-Encoding","8bit"); soapPart.setContentId("[email protected]"); }
You can also fill the contents of a SOAPPart
object from a SAX parser using a SAX Source
as shown in the following snippet.
import javax.xml.transform.sax.SAXSource; import org.xml.sax.InputSource; import java.io.FileReader; ... public static void createSOAPPart(SOAPMessage message) throws SOAPException, java.io.IOException, org.xml.sax.SAXException { FileReader soapFile = new FileReader("soapwa.xml"); InputSource stream = new InputSource(soapFile); SAXSource saxSource = new SAXSource(stream); SOAPPart soapPart = message.getSOAPPart(); soapPart.setContent(saxSource); soapPart.addMimeHeader("Content-Transfer-Encoding","8bit"); soapPart.setContentId("[email protected]"); }
The SOAPEnvelope
interface represents the root of the XML SOAP document. It includes methods for accessing or creating the SOAPHeader
and SOAPBody
. It also includes two methods for creating Name
objects. Listing F-14 shows the complete definition of the SOAPEnvelope
interface.
Example F-14. The javax.xml.soap.SOAPEnvelope
Interface
package javax.xml.soap; public interface SOAPEnvelope extends SOAPElement { public SOAPBody getBody() throws SOAPException; public SOAPHeader getHeader() throws SOAPException; public SOAPBody addBody() throws SOAPException public SOAPHeader addHeader() throws SOAPException; public Name createName(String localName) throws SOAPException; public Name createName(String localName, String prefix, String uri) throws SOAPException; }
SOAPEnvelope
is a subtype of the SOAPElement
interface, which is a subtype of the Node
interface. The same is true of the other SAAJ types (SOAPBody
, SOAP Header
, and so on) that represent elements of the SOAP document. The SOAPElement
and Node
interfaces are covered in Chapter 13.
I noted in Chapter 13 that a new SOAPMessage
already contains the framework of an XML SOAP document including the Envelope
, Body
, and Header
elements. The getBody()
and getHeader()
methods of the SOAPEnvelope
interface return SAAJ objects of type SOAPBody
and SOAPHeader
, which represent the empty Header
and Body
elements.
SOAPEnvelope envelope = soap.getEnvelope(); envelope.getHeader().detachNode(); ... SOAPBody body = envelope.getBody();
The call to getHeader()
is chained to a detachNode()
call, which effectively removes the Header
element from the SOAP document—useful when header blocks are not used in the SOAP message. Calling the detachNode()
method removes the empty Header
element and creates a tighter SOAP document.
You can also add a new Body
or Header
element using the corresponding add Body()
or addHeader()
method, but you use them only if you have already removed the Body
or Header
element from the SOAP document; if you haven't, these methods will throw a SOAPException
.
The SOAPEnvelope
also provides two factory methods for creating Name
type objects. These behave the same as the createName()
method defined in the SOAPFactory
class discussed in Chapter 13. As you already know from Section 2.2: XML Name spaces, the name of an element or attribute dictates which XML namespace it belongs to. A Name
object is simply an abstraction of an XML qualified name. For example, in Listing F-15 SaajExample_F3
shows how the SOAPEnvelope
is used to create the getBookPrice
and isbn
elements in the GetBookQuote SOAP message.
Example F-15. Creating and Using Name
Objects
package com.jwsbook.saaj; import javax.xml.soap.*; public class SaajExample_F3 { public static void main(String [] args) throws SOAPException, java.io.IOException{ MessageFactory msgFactory = MessageFactory.newInstance(); SOAPMessage message = msgFactory.createMessage(); SOAPPart part = message.getSOAPPart(); SOAPEnvelope envelope = part.getEnvelope(); Name getBookPrice_Name = envelope.createName("getBookPrice","mh", "http://www.Monson-Haefel.com/jwsbook/BookQuote"); Name isbnName = envelope.createName("isbn"); SOAPBody body = message.getBody(); SOAPBodyElement getBookPrice_Element = body.addBodyElement(getBookPrice_Name); getBookPrice_Element.addChildElement( isbnName ); SaajOutputter.writeToScreen(message); } }
Creating SwA messages is fairly easy with SAAJ, provided you have the right kinds of DCHs and DataSource
objects. If you don't, then you'll need to change the types of attachments you use or find or develop DCHs or DataSource
objects that fulfill your needs. Developing and registering a new DCH is not very complicated; all you do is implement the javax.activation.DataContentHandler
interface, then register the implementation in a mailcap
file. In many cases you may not need a DCH. For example, if you obtain data objects from files, you can use the javax.activation.FileDataSource
, as SaajExample_F1
and SaajExample_F2
did with the PDF file, in Listings F-7 and F-12.
Although SAAJ can be used independently of JAX-RPC, in many cases they're used together. Use of SAAJ with JAX-RPC is covered in Chapter 14: Message Handlers.
[1] Erich Gamma, et al. Design Patterns: Elements of Reusable Object-Oriented Software. Reading, MA: Addison-Wesley, 1995, p. 20.
[2] Borenstein and Bellcore, “RFC 1524: A User Agent Configuration Mechanism for Multimedia Mail Format Information” (1993). Available at http://www.ietf.org/rfc/rfc1524.txt.
[3] Each mailcap
file must be in a different directory, because you can't store two files with the same name in a single directory.
[4] Erich Gamma, et al. Design Patterns: Elements of Reusable Object-Oriented Software. Reading, MA: Addison-Wesley, 1995, p. 139.
[5] Elliotte Rusty Harold, Processing XML with Java. Boston: Addison-Wesley, 2002.
[6] Elliotte Rusty Harold, Java I/O. Beijing: O'Reilly & Associates, 1999.
18.117.103.219