2007/06/05
5 Jun, 2007

Downloading a Binary File from a Web Service using Axis2 and SOAP with Attachments

  • Thilina Gunarathne
  • Software Engineer - WSO2

Introduction

The sample application we are going to develop through this tutorial includes writing an Apache Axis2/Java [1] Web service, which will respond to the requests by a SOAP with Attachments (SwA) [2] type message containing a binary file. The sample application also includes writing an Apache Axis2 Web service client, which can receive the above SOAP with Attachments type response. The sample use case which we are going to implement is a Download Statistics Service.

This service provides data relating to monthly download statistics of a set of software products. These statistics includes a graph image of the distribution of downloads over a period of one month together with some other data. Other similar use cases of this kind of a service includes providing graph images of stock market data through a Web service, providing hospital patients' profiles together with images from a central national server, etc.

Background

SOAP with Attachments or SwA is a Note submitted to the W3C by Microsoft and HP Labs. The objective of the SOAP with Attachments note is to describe a standard way to associate one or more binary attachments with a SOAP message in their original formats. SOAP with Attachments utilizes the Multipart/Related MIME [3] packaging to package the SOAP message together with the binary attachments, and utilizes URI based mechanisms for referencing the MIME parts. More information about Apache Axis2/Java SOAP with Attachments support can be found in the Using SOAP with Attachments in Apache Axis2[4] article.

Writing and Deploying the "StatisticsService" Web Service

In this example, we are going to write an XML in/out Web service implementation class with the use of org.apache.axis2.receivers.RawXMLINOutMessageReceiver. The service implementation class returns the download statistics data specific to a software product. The name of the product needs to be sent in the form of a SOAP request by the client. The product name is extracted at the service implementation class.

In this scenario, we need to add a binary image file as an attachment to the response SOAP message. We can achieve this by creating a javax.activation.DataHandler object corresponding to the image file and adding it to the attachment map of the outgoing org.apache.axis2.context.MessageContext.

Open StatisticsService.java to have a look at the complete source listing of the Web service implementation class.

1. Creating the javax.activation.DataHandler Object

In order to create a DataHandler object corresponding to the image file, first we need to create an appropriate javax.activation.DataSource object from the image file. There are several javax.activation.DataSource implementations available in the javabeans activation framework[5] as well as in the Apache Axis2/Java distribution. You can choose an appropriate implementation based on the type of data you are going to handle. You can also write your custom DataSource implementation class based on your need. In this example, we are using the javax.activation.FileDataSource to create the DataSource object corresponding to the image file.

   // Create a data source for the image FileDataSource
graphImageDataSource = new FileDataSource(sampleResourcePath +"1.png");

Now we can wrap the above created DataSource object using a javax.activation.DataHandler object.

   // Create the dataHandler out of the above created datasource 
DataHandler graphImageDataHandler = new DataHandler(graphImageDataSource);

2. Accessing the Axis2 MessageContext Object of the Outgoing(response) Message

Now we need to access the MessageContext object of the outgoing message in order to add the above created DataHandler object to the attachment map. We can use the Axis2 MessageContext.getCurrentMessageContext() method within a service implementation class to access the MessageContext object of the incoming(request) message to that service. Accessing the outgoing MessageContext is not so straight forward. For that we first need to access the incoming MessageContext.

   MessageContext inMessageContext = MessageContext.getCurrentMessageContext(); 

We can obtain the OperationContext for this operation through the above requested MessageContext.

   operationContext = inMessageContext.getOperationContext(); 

Now we can use the OperationContext object to access the response (outgoing) MessageContext object.

   MessageContext outMessageContext = operationContext
.getMessageContext(WSDLConstants.MESSAGE_LABEL_OUT_VALUE);

3. Adding the DataHandler Object to the AttachmentMap of the Outgoing MessageContext

Since we have a reference to the outgoing MessageContext, now we can add the above created DataHandler object to the attachments map of that MessageContext. In this example, we expect Axis2/Java to take care of generating a unique Content-ID for our attachment.

   String graphImageID = outMessageContext.addAttachment(graphImageDataHandler);

4. Adding a Reference to the Attachment from the SOAP Payload

Let's add a reference to the attachment from the XML payload of the message to complete the implementation of the service class. We need to make sure that this reference conforms to the cid: URL scheme defined in the RFC 2392[6]. In order to do that, we create an OMElement, then add a "href" attribute with the value set to the Content-ID of the attachment. Care needs to be taken to prefix the Content-ID value with a "cid:" when setting the value of the "href" attribute.

 OMElement graphElement = factory.createOMElement("graph", omNs, wrapperElement); 
String graphCID = "cid:"+graphImageID; graphElement.addAttribute("href",graphCID, omNs);

NOTE: Make sure to copy the "StatisticsServiceResources" directory provided with this sample in to your local machine. Then set the value of the "sampleResourcePath" variable of the service implementation class point to the full path of the "StatisticsServiceResources" directory in your local machine file system.

5. Writing the Service Descriptor

In order to deploy our service in Axis2/Java, we need to write an Axis2 service descriptor (services.xml) for our service. We are going to use the RawXMLINOutMessageReceiver as the MessageReceiver for the getStats() method of our service implementation.

 
<operation name="getStats">
<actionMapping>urn:getStats</actionMapping>
<messageReceiver
class="org.apache.axis2.receivers.RawXMLINOutMessageReceiver"/>
</operation>

We need to add the "enableSwA" parameter to the services.xml with a value of TRUE for the service to be able to send SOAP with Attachment type messages. Alternatively, you can also set this parameter in the axis2 server's axis2.xml. Enabling this parameter in the axis2.xml will enable sending of SOAP with Attachments messages from all the services deployed in your Axis2 server.

   <parameter name="enableSwA">true</parameter>

Open services.xml to have a look at the complete source listing of the service descriptor.

6. Deploying the Service

We need to create a service archive (.aar) file to deploy this service into an Axis2 server. You can use the provided Ant[7] build script to create the service archive by using the "generate.service" goal. Make sure to set the AXIS2_HOME environmental variable pointing to the path of your extracted Axis2 binary distribution (e.g., /opt/axis2-1.2) before running the Ant build. You can also create the service archive file manually. The Axis2 Deployment Model[8] article contains a good description about the format of an Axis2 service archive file.

Now copy the created service archive file to the services directory of the repository of your Apache Axis2 server. (In a standard Apache Tomcat based Axis2 server installation the services directory is <CATALINA_HOME>/webapps/axis2/WEB-INF/services). Start the Axis2 server. (In the case of a servlet based installation, start the servlet container.) Visit the services listing (https://localhost:{port_the_server_is_running}/axis2/services/listServices) to check the status of the deployed service.

List Services

Writing the Web Service Client

In this example, we are going to write our client application as a the Raw XML (Axiom) client[9] using the OperationClient API. The OperationClient API provides direct access to the incoming response MessageContext object, which is needed to retrieve the attachments from the incoming message.

1. Using the OperationClient API to Invoke the Service

In the client application, we create an OutInOperationClient with the help of a service client API.

   ServiceClient sender = new ServiceClient();
//org.apache.axis2.client.Options object can be used to configure the service client
sender.setOptions(options);
OperationClient mepClient = sender.createClient(ServiceClient.ANON_OUT_IN_OP);

We create the request MessageContext object. Then we create the SOAP Envelope for the request message and set it to the request MessageContext. After that, we can add the request MessageContext to the OperationClient object. Now we are ready to execute the OperationClient in order to send the request message.

   MessageContext mc = new MessageContext();
SOAPEnvelope env = createEnvelope(projectName);
mc.setEnvelope(env);

mepClient.addMessageContext(mc);
mepClient.execute(true);

2. Retrieving the Attachment Content from the Web Service Response Message

After the execution, we can access the incoming MessageContext from the OperationClient API.

 MessageContext response = mepClient.getMessageContext(WSDLConstants.MESSAGE_LABEL_IN_VALUE);

We can access the attachments of the incoming message through the attachment API of the MessageContext by giving the corresponding content-ID values. Once again we need to remember to remove the "cid:" prefix from the value taken out of the "href" attribute.

   //retrieving the ID of the attachment 
String graphImageID = graphElement.getAttributeValue(new QName("href"));
//remove the "cid:" prefix
graphImageID = graphImageID.substring(4);
//Accessing the attachment from the response message context using the ID DataHandler
dataHandler = response.getAttachment(graphImageID);

3. Manipulating the Received Attachment Data

We can manipulate the real attachment data contained in the javax.activation.DataHandler object using the mechanisms provided by the DataHandler API. We can use the DataHandler.writeTo(java.io.OutputStream) method to write the content of the DataHandler to an outputStream as follows. You can also use the DataHandler.getInputStream() to retrieve a java.io.InputStream of the contained data and DataHandler.getDataSource() and to access the underlying DataSource respectively.

   File graphFile = new File("responseGraph.png");
FileOutputStream outputStream = new FileOutputStream(graphFile);
dataHandler.writeTo(outputStream);

You can run the client by using the "run.client" target of the provided Ant build by giving a project name as the parameter as follows. Supported values for the project parameter are "axis1" and "axis2". In order to use the Ant build, make sure to set the AXIS2_HOME environment variable to the pathname of the directory in which you installed the JDK version. Also update the targetEPR variable according to your axis2 server deployment.

   ant run.client -Dproject axis2

You may also use your favorite IDE to run the client by giving the project name as a parameter.

Open StatisticsServiceClient.java to have a look at the complete source listing of the Web service client implementation.

Resources


References

  1. Apache Axis2/Java
  2. SOAP Messages with Attachments
  3. MIME (see the references section)
  4. Using SOAP with Attachments in Apache Axis2
  5. JavaBeans Activation Framework
  6. Content-ID and Message-ID Uniform Resource Locators
  7. Apache Ant
  8. Axis2 Deployment Model
  9. Writing Web Service Clients Using Axis2's Primary APIs
  10. SOAP Message Transmission Optimization Mechanism

Author

Thilina Gunarathne, Senior Software Engineer, WSO2 Inc. thilina at wso2 dot com

 

About Author

  • Thilina Gunarathne
  • Software Engineer
  • WSO2 Inc.