Attachments are a very important element for extending the functionality of Web services. Typical applications of Web services with attachments are:
- Implementation of a command pattern with Web services, where a SOAP message defines the command and attachments contained in supporting XML documents. This approach is becoming increasingly popular for implementation of Web services in cases where all data is defined as one very large schema (see, for example, the ACORD proposal for Web services.)
- Sending pictures and documents in support of Web services requests.
The simplest solution for dealing with attachments is base-64 data encoding, which allows for placing attachments directly inside the SOAP message. The data that needs to be attached is encoded, converted to a string representation, and placed inside the SOAP body of an XML payload. This works across all possible transports, is known to interoperate well across different implementations, and is pretty much guaranteed to work with small amounts of data. Yet base-64 encoding has its limits. It is inefficient; by using only 6 bits of each byte, it adds about a third to the length of a message. It also causes problems for processing of received messages due to the fact that XML parsers are not usually engineered to deal with very large strings.
A better approach, especially as the size of attachments grows, is to use messages with attachments. In this case the message itself contains multiple parts, one of which is the SOAP message, and other parts of the message contain additional (attachments) information. Unfortunately, when it comes to messages with attachments, there is not much agreement in the industry today. In fact, there are multiple standards currently on the market (see Resources for more information on these standards):
- Multipurpose Internet Mail Extensions (MIME) is a standard used for e-mail and widely adopted by Java implementations of Web services.
- Direct Internet Message Encapsulation (DIME) is a new standard proposed by Microsoft and IBM and the only implementation of attachments, available from Microsoft as part of Web Services Extensions (WSE) framework.
- Web Service with attachments (WS-Attachment) based on DIME, currently submitted as an internet draft.
- SOAP Message Transmission Optimization Mechanism (MTOM) describes an abstract feature and a concrete implementation of it for optimizing the transmission and wire format of SOAP messages, which is currently a proposed recommendation of the W3C.
To make the situation even worse, the Web Services Definition Language (WSDL), although it supports attachments definitions, specifies them differently, depending on whether MIME or DIME attachments are used. The WSDL definition is also rather limited, in the sense that it does not allow for specifying arrays of attachments of arbitrary types. Every possible attachment has to be explicitly specified as part of the WSDL service definition.
On another hand, SOAP with Attachments API for Java (SAAJ -- see Resources) allows for direct manipulation of attachments which are part of the SOAP message. Combined with the JAX-RPC handlers -- a standard mechanism for JAX-RPC extensibility, usage of SAAJ allows for generic support for attachments processing.
In this paper, I show you a generic JAX-RPC handler implementation, capable of sending and receiving arrays of an arbitrary attachment type. I first demonstrate the creation and usage of an attachment handler that supports a Web service implementation and client based on IBM® WebSphere® Application Server (Application Server) and then extend the implementation to support a .Net client, based on the Web Services Enhancements (WSE) add-on to Microsoft® Visual Studio .NET and the Microsoft .NET Framework from Microsoft.
Architecture of the proposed implementation
Message handlers are one the most powerful features of the JAX-RPC specification. They provide additional message-handling facilities to Web service endpoints (both client and server) as extensions to the basic service implementation logic. Handlers can manage encryption and decryption, logging and auditing, and so on. I use message handlers as a foundation for this proposed implementation, which will look as Figure 1 shows:
Figure 1. Overall implementation architecture

The proposed solution in Figure 1 works as follows:
- A service consumer creates an attachment container which contains all of the attachments that are destined to the service implementation, and appends it to the SOAP message context.
- As part of the client request pipeline execution, the client request handler is invoked. This method checks the SOAP message context for an attachment container. If the container is found, then its content is copied to the request SOAP message.
- A SOAP message is delivered to the service implementation pipeline.
- As part of the service request pipeline execution, the service request handler is invoked. This method checks if an incoming message contains any attachments, and if it does, it repackages the attachments into the attachments container, which is appended to the SOAP message context.
- Web services implementations check the SOAP message context for the attachment container, and if one is found, it is used for processing the request.
- As part of the execution, the service implementation creates a response attachment container and adds it to the SOAP message context.
- As part of the service reply pipeline execution, a service response handler is invoked. This method checks the SOAP message context for the attachment container. If the container is found, then its content is copied to the reply SOAP message.
- The SOAP message is then delivered to the service client pipeline.
- As part of the client response pipeline execution, the client response handler is invoked. This method checks if the incoming message contains any attachments, and if it does, it repackages the attachments into the attachments container, which is appended to the SOAP message context.
- Web services client implementations check the SOAP message context for the attachment container, and if one is found, it is used for processing the reply.
The rest of this paper discusses and provides the details of the proposed implementation.
In order to keep implementation simple, I start by creating a very simple Echo Service. I use generation capabilities of WebSphere Studio Application Developer Integration Edition V5.1 (Application Developer). The simplest way to generate both the implementation and client programming support for Web services is to start from a Java bean, implementing the service (Listing 1) and then generating all of the artifacts that are required for deploying this bean as a service and to access this service from the client side.
Listing 1. Echo Service bean
package com.cna.services;
public class EchoService{
public String echo(String request) {
System.out.println("Inside echo service");
// Return
return request;
}
}
|
Based on the generated Java code, a simple test client for this service can be implemented as follows (Listing 2):
Listing 2. Simple service testing client
package com.cna.service.tester;
import com.cna.services.EchoService;
import com.cna.services.EchoServiceProxy;
public class serviceTester {
public void testServices(){
EchoServiceProxy portProxy = new EchoServiceProxy();
EchoService port = portProxy.getEchoService();
String reply = null;
try{
reply = port.echo("my echo");
}
catch(Exception e){
System.out.println("Error invoking service");
e.printStackTrace();
}
System.out.println("Done invoking service " + reply);
}
}
|
A simple JSP (Listing 3) can also be created to invoke the tester (Listing 2) from the Web page:
Listing 3. Simple JSP for service tester invocation
<![CDATA[ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <HTML> <HEAD> <%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding= "ISO-8859-1"%> <jsp:useBean id="serviceTester" scope="session" class= "com.cna.service.tester.serviceTester" /> <META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <META name="GENERATOR" content="IBM WebSphere Studio"> <META http-equiv="Content-Style-Type" content="text/css"> <LINK href="../../theme/Master.css" rel="stylesheet" type="text/css"> <TITLE>serviceTester.jsp</TITLE> </HEAD> <BODY> <P>Testing Service ... </P> <% serviceTester.testServices(); %> <P>Done </P> </BODY> </HTML> ]]> |
Communicating between handlers and implementations
One of the underpinnings of the proposed solution is the ability of handlers to communicate with both service implementation and service invocation clients. Because there is no direct linkage between handler implementations and either a service implementation or a service client implementation, the only way to establish this communication is through the properties on the SOAPMessageContext. Unfortunately the access to this context (in Application Server V5.1) is supported differently on the client and service implementation sides.
Access SOAPMessageContext from the service implementation
In the current version of the JAX-RPC implementation, there is no direct way to access SOAPMessageContext. Fortunately this functionality is available through the javax.xml.rpc.server.ServiceLifecycle interface, which is a part of a JAX-RPC specification. Because service implementations can be derived from the ServiceLifecycle interface, SOAPMessageContext can be obtained in the init method of the ServiceLifecycle interface and then used by the service implementation. Listing 4 shows a simple implementation, which makes SOAPMessageContext available to the service implementations that extends this class.
Listing 4. ServiceLifecycle interface implementation
package com.cna.service.context;
import javax.xml.rpc.handler.MessageContext;
import javax.xml.rpc.server.ServiceLifecycle;
import javax.xml.rpc.server.ServletEndpointContext;
public class CfContextHandler implements ServiceLifecycle{
private MessageContext context;
public void init(Object ctx){
System.out.println("Inside Service init method");
// Initialize message context for the service use
ServletEndpointContext sc = (ServletEndpointContext)ctx;
if(ctx != null)
context = sc.getMessageContext();
else
context = null;
}
public void destroy(){
System.out.println("Inside Service destroy method");
}
public MessageContext getContext() {
return context;
}
}
|
Access SOAPMessageContext from the service consumer
On the service consumer side, there is also no direct way to access SOAPMessageContext. The only way of doing this is by setting or getting properties on the javax.xml.rpc.Stub interface, defined as part of the JAX RPC standard. Every port implementation class (Listing 2) generated by Application Developer can be type-casted to the Stub interface, thus allowing for setting and getting Stub's properties.
One limitation of the Application Server and JAX-RPC implementation is that the property set on the Stub prior to service invocation is copied to the SOAPMessageContext, but changes to the SOAPMessageContext during service invocation and is not copied back to the Stub properties. This means that no changes to the SOAPMessageContext that are done during service invocation are visible to the service consumer. The simple way to get around this limitation is by using containers as properties (for the parameters that have to be set as part of a service invocation) before service invocation. In this case, instead of setting new properties on SOAPMessageContext, values are set in the container, which is directly available to the service consumer.
Implement attachments handlers
In order to implement an attachments handler you need to define a container class for multiple attachments. All of the attachments are based on two main things:
- The attachment identifier: uniquely identifies this particular attachment inside a container. This identifier can also be used for referencing attachments in the SOAP message body.
- The attachment data source: implementing javax.activation.DataSource interface and representing attachment content.
Java 1.4 provides several standard implementations for data source interface -- the FileDataSource class implements a simple data source object that encapsulates a file, and the URLDataSource class provides an object that wraps a URL object as a data source interface. Additional types of data sources can be created as needed. Based on the above, the attachments container (Listing 5) can be implemented as a map that contains attachment data sources and are keyed by attachments identifiers.
Listing 5. Attachment container class
package com.cna.service.attachments;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import javax.activation.DataSource;
import com.ibm.ws.webservices.engine.attachments.Attachments;
public class CfAttachmentsContainer implements Serializable{
private Map map = null;
public CfAttachmentsContainer(){
map = new HashMap();
}
public void addAttachment(DataSource source, String ID){
map.put(ID,source);
}
public Map getAttachments(){
return map;
}
public DataSource getAttachment(String ID){
return (DataSource)map.get(ID);
}
}
|
An additional class, a two-way attachments container (Listing 6), is also required to support a service consumer.
Listing 6. Two-way attachments container
package com.cna.service.attachments;
public class CfTwoWayAttchmentsContainer {
private CfAttachmentsContainer requestContainer = null;
private CfAttachmentsContainer responseContainer = null;
public CfAttachmentsContainer getRequestContainer() {
return requestContainer;
}
public CfAttachmentsContainer getResponseContainer() {
return responseContainer;
}
public void setRequestContainer(CfAttachmentsContainer container) {
requestContainer = container;
}
public void setResponseContainer(CfAttachmentsContainer container) {
responseContainer = container;
}
}
|
Based on the above designed attachments container, the basic functionality of the attachments handler is for two-way conversions between a SOAP message with attachments and an attachments container. Because the attachments handler operates on the javax.xml.soap.SOAPMessage class, you can use SAAJ to manipulate the content of the SOAP message directly. The base handler, which implements conversion operations, is presented in Listing 7.
Listing 7. Base attachments handler
package com.cna.service.attachments.handler;
import java.util.Iterator;
import java.util.Map;
import javax.activation.DataHandler;
import javax.xml.namespace.QName;
import javax.xml.rpc.handler.GenericHandler;
import javax.xml.rpc.handler.MessageContext;
import javax.xml.rpc.handler.soap.SOAPMessageContext;
import javax.xml.soap.AttachmentPart;
import javax.xml.soap.SOAPMessage;
import com.cna.service.attachments.CfAttachmentsContainer;
public class CfAttachmentHandler extends GenericHandler {
public static String HANDLER_REQUEST_ATTACHMENT_PROPERTY = "ReqAttachments";
public static String HANDLER_RESPONSE_ATTACHMENT_PROPERTY = "RespAttachments";
public static String HANDLER_REQUEST_RESPONSE_ATTACHMENT_PROPERTY = "Attachments";
// Metod Required by Generic Handler
public QName[] getHeaders() {
return null;
}
protected void addContainerToMessage(MessageContext mc,CfAttachmentsContainer aContainer){
try{
SOAPMessageContext smc = (SOAPMessageContext) mc;
SOAPMessage sMessage = smc.getMessage();
// Go through all attachments
Map attachments = aContainer.getAttachments();
Iterator aIterator = attachments.keySet().iterator();
while(aIterator.hasNext()){
String ID = (String)aIterator.next();
AttachmentPart attachment = sMessage.createAttachmentPart();
DataHandler dh = new DataHandler(aContainer.getAttachment(ID));
attachment.setDataHandler(dh);
attachment.setContentId(ID);
sMessage.addAttachmentPart(attachment);
}
}
catch(Exception e){
System.out.println("Error appending attachments on the consumer");
e.printStackTrace();
}
}
protected CfAttachmentsContainer extractContainerFromMessage(MessageContext mc){
try{
SOAPMessageContext smc = (SOAPMessageContext) mc;
SOAPMessage sMessage = smc.getMessage();
Iterator attachments = sMessage.getAttachments();
if(!attachments.hasNext())
return null;
System.out.println("Storing Attachments to container");
CfAttachmentsContainer aContainer = new CfAttachmentsContainer();
// Process all attachments
while(attachments.hasNext()){
AttachmentPart attachment = (AttachmentPart)attachments.next();
DataHandler dh = attachment.getDataHandler();
aContainer.addAttachment(dh.getDataSource(),attachment.getContentId());
}
return aContainer;
}
catch(Exception e){
System.out.println("Error retrieving attachments on the consumer");
e.printStackTrace();
return null;
}
}
}
|
Specific attachment handlers for the client (Listing 8) and server-side (Listing 9) extend the base attachment header class (Listing 7) by implementing handleRequest and handleResponse methods, which are invoked by the JAX-RPC pipeline.
Listing 8. Client attachments handler
package com.cna.service.attachments.handler;
import javax.xml.rpc.handler.MessageContext;
import com.cna.service.attachments.CfAttachmentsContainer;
import com.cna.service.attachments.CfTwoWayAttchmentsContainer;
public class CfClientAttachmentHandler extends CfAttachmentHandler{
// Request handler. Takes container and adds it to the message
public boolean handleRequest(MessageContext mc){
System.out.println("Inside Client Hand?e Request");
CfTwoWayAttchmentsContainer tContainer =
(CfTwoWayAttchmentsContainer)mc.getProperty(
HANDLER_REQUEST_RESPONSE_ATTACHMENT_PROPERTY);
if(tContainer != null){
CfAttachmentsContainer aContainer = tContainer.getRequestContainer();
if(aContainer != null)
addContainerToMessage(mc, aContainer);
}
return true;
}
public boolean handleResponse(MessageContext mc){
System.out.println("Inside Client Handle Response");
CfTwoWayAttchmentsContainer tContainer =
(CfTwoWayAttchmentsContainer) mc.getProperty(
HANDLER_REQUEST_RESPONSE_ATTACHMENT_PROPERTY);
if(tContainer != null){
CfAttachmentsContainer aContainer = extractContainerFromMessage(mc);
if(aContainer != null)
tContainer.setResponseContainer(aContainer);
}
return true;
}
}
|
Listing 9. Server attachments handler
package com.cna.service.attachments.handler;
import javax.xml.rpc.handler.MessageContext;
import com.cna.service.attachments.CfAttachmentsContainer;
public class CFServerAttachmentHandler extends CfAttachmentHandler {
public boolean handleRequest(MessageContext mc){
System.out.println("Inside server Handle Request");
CfAttachmentsContainer aContainer = extractContainerFromMessage(mc);
if(aContainer != null)
mc.setProperty(HANDLER_REQUEST_ATTACHMENT_PROPERTY, aContainer);
return true;
}
public boolean handleResponse(MessageContext mc){
System.out.println("Inside Server Handle Response");
CfAttachmentsContainer aContainer =
(CfAttachmentsContainer)mc.getProperty(HANDLER_RESPONSE_ATTACHMENT_PROPERTY);
if(aContainer != null)
addContainerToMessage(mc, aContainer);
return true;
}
}
|
Notice that the server handler uses CfAttachmentsContainer and two distinct properties: one for inbound attachments and another one for the outbound. The client handler also uses a container -- CfTwoWayAttchmentsContainer and a single property. This container is used for both incoming and outgoing attachments and is set prior to service invocation.
To put it all together, you need to modify both the service and client implementations to send and receive attachments. You also need to make sure that the service implementation is extending the CfContextHandler class (Listing 4), so that it has access to the SOAPMessageContext.
Listing 10. Complete implementation of the service
package com.cna.services;
import java.util.Iterator;
import java.util.Map;
import javax.activation.DataSource;
import javax.xml.rpc.handler.MessageContext;
import com.cna.service.attachments.CfAttachmentsContainer;
import com.cna.service.attachments.handler.CfAttachmentHandler;
import com.cna.service.context.CfContextHandler;
import com.cna.services.datasources.CfDataSourceReader;
import com.cna.services.datasources.CfOctetDataSource;
import com.cna.services.datasources.CfPlainTextDataSource;
public class EchoService extends CfContextHandler{
private static String attachment1 = "WAS Server weird attachment";
private static String attachment2 = "WAS Server other weird attachment";
private DataSource ds1 = null;
private DataSource ds2 = null;
public EchoService(){
ds1 = new CfPlainTextDataSource("attachment1",attachment1);
ds2 = new CfOctetDataSource ("attachment2",attachment2.getBytes());
}
public String echo(String request) {
System.out.println("Inside echo service");
// Process incoming attachments
MessageContext context = getContext();
CfAttachmentsContainer reqContainer = null;
if(context != null)
reqContainer =
(CfAttachmentsContainer)context.getProperty(
CfAttachmentHandler. HANDLER_REQUEST_ATTACHMENT_PROPERTY);
System.out.println("Got attachments container " + reqContainer);
if(reqContainer != null){
Map attachments = reqContainer.getAttachments();
Iterator aIterator = attachments.keySet().iterator();
while(aIterator.hasNext()){
String ID = (String)aIterator.next();
System.out.println("Got attachments " + ID);
DataSource ds = reqContainer.getAttachment(ID);
System.out.println(
"Attachment value is " + CfDataSourceReader.convertToString(ds));
}
}
// Create outgoing attachments
if(context != null){
CfAttachmentsContainer repContainer = new CfAttachmentsContainer();
repContainer.addAttachment(ds1,"firstAttchment");
repContainer.addAttachment(ds2,"secondAttchment");
context.setProperty(
CfAttachmentHandler.HANDLER_RESPONSE_ATTACHMENT_PROPERTY,repContainer);
}
// Return
return request;
}
}
|
Listing 11. Complete implementation of client
package com.cna.service.tester;
import java.util.Iterator;
import java.util.Map;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.xml.rpc.Stub;
import javax.xml.soap.AttachmentPart;
import com.cna.service.attachments.CfAttachmentsContainer;
import com.cna.service.attachments.CfTwoWayAttchmentsContainer;
import com.cna.service.attachments.handler.CfAttachmentHandler;
import com.cna.services.EchoService;
import com.cna.services.EchoServiceProxy;
import com.cna.services.datasources.CfDataSourceReader;
import com.cna.services.datasources.CfOctetDataSource;
import com.cna.services.datasources.CfPlainTextDataSource;
public class serviceTester {
private static String attachment1 = "Client weird attachment";
private static String attachment2 = "Client other weird attachment";
private DataSource ds1 = null;
private DataSource ds2 = null;
public serviceTester(){
ds1 = new CfPlainTextDataSource("attachment1",attachment1);
ds2 = new CfOctetDataSource("attachment2",attachment2.getBytes());
}
public void testServices(){
CfTwoWayAttchmentsContainer tContainer = new CfTwoWayAttchmentsContainer();
CfAttachmentsContainer reqContainer = new CfAttachmentsContainer();
reqContainer.addAttachment(ds1,"firstAttchment");
reqContainer.addAttachment(ds2,"secondAttchment");
tContainer.setRequestContainer(reqContainer);
EchoServiceProxy portProxy = new EchoServiceProxy();
EchoService port = portProxy.getEchoService();
Stub stub = (Stub)port;
stub._setProperty(
CfAttachmentHandler.HANDLER_REQUEST_RESPONSE_ATTACHMENT_PROPERTY,tContainer);
String reply = null;
try{
reply = port.echo("my echo");
}
catch(Exception e){
System.out.println("Error invoking service");
e.printStackTrace();
}
System.out.println("Done invoking service " + reply);
CfAttachmentsContainer repContainer = tContainer.getResponseContainer();
System.out.println("Got attachments container " + repContainer);
if(repContainer != null){
Map attachments = repContainer.getAttachments();
Iterator aIterator = attachments.keySet().iterator();
while(aIterator.hasNext()){
String ID = (String)aIterator.next();
System.out.println("Got attachments " + ID);
DataSource ds = repContainer.getAttachment(ID);
System.out.println(
"Attachment value is " + CfDataSourceReader.convertToString(ds));
}
}
}
}
|
Finally, both the client and server invocation pipelines have to be configured to include handlers, which are defined above (Listing 8 and Listing 9).
The following output is produced when a tester JSP (Listing 3) is invoked:
Listing 12. Results of a test service invocation
SystemOut O Inside Client Handle Request SystemOut O Inside server Handle Request SystemOut O Storing Attachments to cont?iner SystemOut O Inside Service init method SystemOut O Inside echo service SystemOut O Got attachments container com.cna.service.attachments.CfAttachmentsContainer@71764583 SystemOut O Got attachments secondAttchment SystemOut O Attachment value is Client other weird attachment SystemOut O Got attachments firstAttchment SystemOut O Attachment value is Client weird attachment SystemOut O Inside Service destroy method SystemOut O Inside Server Handle Response SystemOut O Inside Client Handle Response SystemOut O Storing Attachments to container SystemOut O Done invoking service my echo SystemOut O Got attachments container com.cna.service.attachments.CfAttachmentsContainer@22e60582 SystemOut O Got attachments secondAttchment SystemOut O Attachment value is WAS Server other weird attachment SystemOut O Got attachments firstAttchment SystemOut O Attachment value is WAS Server weird attachment |
Extend handlers to support both MIME and DIME
Although it is not advertised or documented in the Application Server implementation, a SOAP message is based on the com.ibm.ws.webservices.engine.Message class which supports both MIME and DIME attachments. For the incoming messages, it examines HTTP content headers and builds a SOAP message correctly regardless of the attachment's type. Analogous to an AXIS implementation, this class allows querying of the attachment type of the incoming message (Listing 13).
Listing 13. Querying attachment type
import com.ibm.ws.webservices.engine.Message; import com.ibm.ws.webservices.engine.attachments.Attachments; .................................................................... Message messageImpl = (Message)sMessage; if(messageImpl.getAttachmentsImpl().getSendType() == Attachments.SEND_TYPE_DIME) aContainer.setSendType(CfAttachmentsContainer.DIME_ATTACHMENTS); else aContainer.setSendType(CfAttachmentsContainer.MIME_ATTACHMENTS); |
When sending the attachment, Application Server, by default, assumes a MIME attachment, but allows it to overwrite the format in which the attachment will be sent, which is shown in Listing 14:
Listing 14. Overwriting attachment type
import com.ibm.ws.webservices.engine.Message;
import com.ibm.ws.webservices.engine.attachments.Attachments;
....................................................................
if(aContainer.getSendType() == CfAttachmentsContainer.DIME_ATTACHMENTS){
Message messageImpl = (Message)sMessage;
messageImpl.getAttachmentsImpl().setSendType(Attachments.SEND_TYPE_DIME);
}
|
To incorporate the above capabilities, I modified the attachments container class (Listing 5) to add a variable, which contains the send or receive attachment's type and setter and getter methods for this variable. I also incorporated code to query the type of incoming message (Listing 13) and explicitly set the attachment type (Listing 14) into the implementation of a basic handler (Listing 7).
Implement a .Net service consumer
Implementation of attachments support in the .NET framework is provided by WSE, an add-on to .NET 1.1, which is freely available from the Microsoft Web site. I used the most current version of this package, WSE 2.0. WSE 2.0 provides a very simple model for processing attachments:
- Individual attachment -- the DimeAttachment class can be created based on the attachment type and a stream, which represents the attachment content.
- In order to create attachments for the request message, RequestSoapContext can be obtained from the service proxy. This context contains an attachment container, to which outgoing attachments can be added. A standard handler, provided by WSE, takes care of packaging these attachments with the outgoing message.
- On the reply, the WSE-provided handler parses incoming messages and stores incoming attachments into the attachments container of the ResponseSoapContext. This context is then available as an attribute of the service proxy.
The overall sequence of steps, required for creation of a .Net client is as follows:
- Download and install WSE 2.0.
- Create a new C# console project in Visual Studio 2003.
- Add the resource WSE 2.0.
- Add Web resource, import WSDL for echo Service. When this is done Visual Studio generates two proxies: EchoServiceService (normal Web service proxy) and EchoServiceServiceWse (Web service proxy with WSE support). I used EchoServiceServiceWse class in this implementation.
The actual implementation is pretty straightforward, and is presented in Listing 15:
Listing 15. Implementation of a .Net client
using System;
using WasEchoServiceTester.WASEchoService;
using Microsoft.Web.Services2;
using Microsoft.Web.Services2.Dime;
using System.Web;
using System.Web.Services;
using System.IO;
using System.Text;
using System.Xml;
namespace WasEchoServiceTester
{
class WASEchoClient
{
private static string request = "echo request";
private static string aString = "Microsoft Client weird attachment";
private static string aString1 = "Microsoft Client other weird attachment";
[STAThread]
static void Main(string[] args)
{
// create service proxy
EchoServiceServiceWse eService = new EchoServiceServiceWse();
// Create attachments
SoapContext outSOAPContext = eService.RequestSoapContext;
UTF8Encoding encoder = new UTF8Encoding();
byte[] bytes = encoder.GetBytes(aString);
MemoryStream aStream = new MemoryStream(bytes);
DimeAttachment outAttachment =
new DimeAttachment("text/plain",TypeFormat.None,aStream);
outAttachment.Id = "attachment1";
outSOAPContext.Attachments.Add(outAttachment);
bytes = encoder.GetBytes(aString1);
aStream = new MemoryStream(bytes);
outAttachment = new DimeAttachment("text/plain",TypeFormat.None,aStream);
outAttachment.Id = "attachment2";
outSOAPContext.Attachments.Add(outAttachment);
// Invoke service
string reply = eService.echo(request);
System.Console.Out.WriteLine("Returned from service
with result " + reply);
// Process attachments
SoapContext inSOAPContext = eService.ResponseSoapContext;
int nAttachments = inSOAPContext.Attachments.Count;
if (nAttachments > 0){
for(int i = 0; i < nAttachments;i ++){
String attID = inSOAPContext.Attachments[i].Id;
System.Console.Out.WriteLine("Processing attachment " + attID);
Stream inStream = inSOAPContext.Attachments[i].Stream;
int sLengt = (int)inStream.Length;
byte[] sBytes = new byte[sLengt];
int sByte = 0;
while(sLengt > 0){
int n = inStream.Read(sBytes, sByte, sLengt);
if (n==0)
break;
sByte += n;
sLengt -= n;
}
inStream.Close();
Decoder decoder = encoder.GetDecoder();
int charCount = decoder.GetCharCount(sBytes, 0, sBytes.Length);
Char[] chars = new Char[charCount];
decoder.GetChars(sBytes, 0, sBytes.Length, chars, 0);
string attachmentValue = new string(chars);
System.Console.Write(attachmentValue + "\n");
}
}
}
}
}
|
Testing of the .Net client (Listing 15) against the Application Server service (listing 10) produces the following results:
Listing 16. Echo Result of execution on Application Server
SystemOut O Inside server Handle Request SystemOut O Storing Attachments to container SystemOut O Inside Service init method SystemOut O Inside echo service SystemOut O Got attachments container com.cna.service.attachments.CfAttachmentsContainer@4a5b5070 SystemOut O Got attachments attachment2 SystemOut O Attachment value is Microsoft Client other weird attachment SystemOut O Got attachments attachment1 SystemOut O Attachment value is Microsoft Client weird attachment SystemOut O Inside Service destroy method SystemOut O Inside Server Handle Response |
Listing 17. Result of execution on .Net client
Returned from service with result echo request Processing attachment secondAttchment WAS Server other weird attachment Processing attachment firstAttchment WAS Server weird attachment |
This paper proposed a very flexible solution for attachment processing, for both incoming and outgoing messages. The solution allows for the processing of arbitrary arrays of attachments of an arbitrary type.
The author wishes to thank his CNA colleagues, especially James Mckune and Frank Marascom for discussion which helped to improve this paper and supporting code.
| Description | Name | Size | Download method |
|---|---|---|---|
| Source code for the article | ws-jaxhandlecode.zip | 77 KB | HTTP |
Information about download methods
- Get a description and definition of Multipurpose Internet Mail Extensions (MIME).
- Get a description and definition of Direct Internet Message Encapsulation (DIME).
- Get the latest information about Web Service with attachments.
- Get the latest information on SOAP Message Transmission Optimization Mechanism (MTOM).
- Read the WS-I recommendation for defining MIME attachments in WSDL.
- See an example of using WSDL to support attachments in JAX RPC implementations (developerWorks, February 2004).
- Get Proposal for defining DIME attachments in WSDL.
- Learn more about JAX-RPC handlers in the series A developer's introduction to JAX-RPC (developerWorks, January 2003).
- Learn how to use WebSphere Studio Application Developer to create Web services in "Top-down Web services development with WebSphere Studio" (developerWorks, January 2004).
- Find additional implementations of a data source the AXIS package, org.apache.axis.attachments.
- Find out how to use AXIS to process DIME attachments in Steve Loughran's article, "Fear of Attachments" (February 2003).
-
Get your hands on application development tools and middleware products from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®. You can download evaluation versions of the products at no charge, or select the Linux® or Windows® version of developerWorks' Software Evaluation Kit.
- Browse for books on these and other technical topics.
- Get involved in the developerWorks community by participating in
developerWorks blogs.
- Want more? The developerWorks SOA and Web services zone hosts hundreds of informative articles and introductory, intermediate, and advanced tutorials on how to develop Web services applications.
Boris Lublinsky is an enterprise architect at CNA Insurance where he is involved in design and implementation of CNA's integration strategy, building application frameworks, and implementing Service-Oriented Architecture (SOA). Boris has more than 20 years' experience in software engineering and technical architecture. You can reach Boris at boris.lublinsky@cna.com.




