Feeds:
Posts
Comments

Archive for July, 2011

Recently I came across to the need to write a custom Message Formatter to handle a custom message type in Axis2.  As it turned out writing a Message Formatter is a darn simple thing. Mainly you only have to implement three methods. :).

My scenario was to extract out a set of attachments within an incoming SOAP (not SwA or MTOM. A custom XML format to hold attachments) and send out a HTTP MIME multi-part message without SOAP. It has a custom content type “application/x-mime”. Incoming XML format is roughly as follows.


<soap:Body>

<attachments>

<!-- 1 or more occurrances -->

<attachment>[base64 encoded binary]</attachment>

</attachments>

<text>[xml content here]</text>

</soap:Body>

Attachments are of “application/pdf” content-type while text content is normal “application/xml” content-type.  Out bound message should be  a HTTP MIME multi-part (with multipart/related content-type) message as follows. (this listing is without root http headers)


--MIME-Boundary1223233

Content-Type: application/xml

[xml content here]

--MIME-Boundary1223233

Content-Type: application/pdf

Content-Transfer-Encoding: base64

Content-ID: 1.urn:uuid:2F3457E2A5DC1F05631300801064296@apache.org>

[base64 attachment-1]

.

.

.

--MIME-Boundary1223233

Content-Type: application/pdf

Content-Transfer-Encoding: base64

Content-ID: 1.urn:uuid:3243457E2A5DC1F05631364801064523@apache.org>

[base64 attachment-n]

--MIME-Boundary1223233--

Now with this requirement in mind let’s go through how to achieve this with an Axis2 Message Formatter. The interface to implement is org.apache.axis2.transport.MessageFormatter.  It’s methods are..

public byte[] getBytes(MessageContext messageContext, OMOutputFormat format)
throws AxisFault;

public void writeTo(MessageContext messageContext, OMOutputFormat format,
OutputStream outputStream, boolean preserve) throws AxisFault;

public String getContentType(MessageContext messageContext, OMOutputFormat format,
String soapAction);

public URL getTargetAddress(MessageContext messageContext, OMOutputFormat format,
URL targetURL) throws AxisFault;

public String formatSOAPAction(MessageContext messageContext, OMOutputFormat format,
String soapAction);

Main message formatting logic goes inside writeTo method and content type of output message should be returned from the getContentType method implementation. The getBytes method should return output message as a byte array formatted according to the given message format.. Other methods are auxiliary and in my case I just left empty implementations in them.
Now let’s go through the main implementation..

public void writeTo(MessageContext messageContext, OMOutputFormat omOutputFormat,
OutputStream outputStream, boolean b) throws AxisFault {

   MultipartWriter mpWriter = JavaMailMultipartWriterFactory.INSTANCE.createMultipartWriter(
   outputStream, omOutputFormat.getMimeBoundary());

   // Write text content
   OMElement textElement = body.getFirstChildWithName(
   new QName("text"));

   if (textElement != null) {
      writeText(mpWriter, textElement, getContentID());
   }

   // Write attachments..
   SOAPBody body = messageContext.getEnvelope().getBody();
   OMElement attachmentsElement = body.getFirstChildWithName(new QName("attachments"));

   if (attachmentsElement != null) {

      Iterator<OMElement> attachments = attachmentsElement.getChildElements();
      while (attachments.hasNext()) {
        writeAttachment(mpWriter, attachments.next(), getContentID());
      }
   }

   // Complete writing the message and flush the stream
   try {
      mpWriter.complete();
   } catch (IOException e) {
      throw new AxisFault("Failed to complete writing the MIME message..", e);
   }
}

Here I have used MultipartWriter API from Axiom to write MIME multi-parts to the output stream. After initialization of the MultipartWriter text content and attachments are written using private helper methods shown in the next listing. Finally the call to mpWriter.complete() flushes the stream and finish writing the MIME message.

private String getContentID() {
   return "1.urn:uuid:" + UUID.randomUUID() + "@altisource.com>";
}

private void writeAttachment(MultipartWriter mpWriter, OMElement attachment, String contentID)
throws AxisFault {
   try {
      mpWriter.writePart(new DataHandler(new ByteArrayDataSource(attachment.getText()
      .getBytes(), "application/pdf")),
      "base64", contentID);
   } catch (IOException e) {
      throw new AxisFault("Failed to write attachment..", e);
   }
}

private void writeText(MultipartWriter mpWriter, OMElement text, String contentID)
throws AxisFault {
   try {
      mpWriter.writePart(new DataHandler(new ByteArrayDataSource(text.toString().getBytes(),
      "application/xml")), "",
      contentID);
   } catch (IOException e) {
      throw new AxisFault("Failed to write text content..", e);
   }
}

The getContentID() method returns a unique ID for each part using java.util.UUID. The getBytes method uses writeTo implementation to a ByteArrayStream and return a byte array.

public byte[] getBytes(MessageContext messageContext, OMOutputFormat omOutputFormat)
throws AxisFault {

   ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
   writeTo(messageContext, omOutputFormat, bytesOut, false);

   return bytesOut.toByteArray();

}

The complete listing is shown below.

import org.apache.axiom.attachments.ByteArrayDataSource;
import org.apache.axiom.mime.MultipartWriter;
import org.apache.axiom.mime.impl.javamail.JavaMailMultipartWriterFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMOutputFormat;
import org.apache.axiom.soap.SOAPBody;
import org.apache.axis2.AxisFault;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.transport.MessageFormatter;

import javax.activation.DataHandler;
import javax.xml.namespace.QName;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.util.Iterator;
import java.util.UUID;

public class MIMEFormatter implements MessageFormatter {

public byte[] getBytes(MessageContext messageContext, OMOutputFormat omOutputFormat)
throws AxisFault {

   ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
   writeTo(messageContext, omOutputFormat, bytesOut, false);

   return bytesOut.toByteArray();

}

public void writeTo(MessageContext messageContext, OMOutputFormat omOutputFormat,
OutputStream outputStream, boolean b) throws AxisFault {

   MultipartWriter mpWriter = JavaMailMultipartWriterFactory.INSTANCE.createMultipartWriter(
   outputStream, omOutputFormat.getMimeBoundary());

   // Write attachments..
   SOAPBody body = messageContext.getEnvelope().getBody();
   OMElement attachmentsElement = body.getFirstChildWithName(new QName("attachments"));

   if (attachmentsElement != null) {

      Iterator<OMElement> attachments = attachmentsElement.getChildElements();
      while (attachments.hasNext()) {
      writeAttachment(mpWriter, attachments.next(), getContentID());
   }

   // Write text content
   OMElement textElement = body.getFirstChildWithName(new QName("text"));

   if (textElement != null) {
      writeText(mpWriter, textElement, getContentID());
   }

   // Complete writing the message and flush the stream
   try {
      mpWriter.complete();
   } catch (IOException e) {
      throw new AxisFault("Failed to complete writing the MIME message..", e);
   }
}

public String getContentType(MessageContext messageContext, OMOutputFormat omOutputFormat,
String s) {
   return "multipart/related; " + " boundary=\"" + omOutputFormat.getMimeBoundary() + "\"; " +
   "type=\"application/xml\"";
}

public URL getTargetAddress(MessageContext messageContext, OMOutputFormat omOutputFormat,
URL url) throws AxisFault {
   return null;
}

public String formatSOAPAction(MessageContext messageContext, OMOutputFormat omOutputFormat,
String s) {
   return null;
}

/*******  Private Helper Methods *****/

private String getContentID() {
   return "1.urn:uuid:" + UUID.randomUUID() + "@altisource.com>";
}

private void writeAttachment(MultipartWriter mpWriter, OMElement attachment, String contentID)
throws AxisFault {
   try {
      mpWriter.writePart(new DataHandler(new ByteArrayDataSource(attachment.getText()
      .getBytes(), "application/pdf")),
      "base64", contentID);
   } catch (IOException e) {
      throw new AxisFault("Failed to write attachment..", e);
   }
}

private void writeText(MultipartWriter mpWriter, OMElement text, String contentID)
throws AxisFault {
   try {
      mpWriter.writePart(new DataHandler(new ByteArrayDataSource(text.toString().getBytes(),
      "application/xml")), "",
      contentID);
   } catch (IOException e) {
      throw new AxisFault("Failed to write text content..", e);
   }
}

}

Now you have to make a jar out of this class and put it in the classpath of Axis2 installation. Put the following entry under <messageFormatters> section in axis2.xml to engage this formatter for the content type “application/x-mime”.


<messageFormatter contentType="application/x-mime" class="MIMEFormatter"/>

That’s it. Pretty simple. Isn’t it. :).

Advertisements

Read Full Post »