Home > code > XOP Converter

XOP Converter

My most recent task at work involves XOP, or XML-binary Optimized Packaging. Basically, if you have an XML document that needs to have binary data in it, like images, you have two ways of doing it. You can base64 encode the data and put it directly in an XML element, or you can use XOP to keep the data in binary form and attach the file to the message using MIME. XOP is usually used to represent SOAP messages with MTOM (Message Transmission Optimization Mechanism).

My app uses JiBX to convert my Java objects into XML documents, and base64 encodes them. But I got a new requirement to also support XOP encoding. So to minimize impact on the app, I decided to write a converter that would take in a XML document with base64 encoded data and spit out a properly encoded XOP document. Then it would also be useful to convert existing documents and creating new ones. The module that helped me do this was Apache Axiom, a component of the Apache Axis2 web services engine.

The main problem with Axiom is its poor documentation. Almost all of the docs I could find about Axiom and Axis2 were from a server-side perspective, and I am writing a client-side app. So I thought I’d share the bit of code I wrote to do this conversion.

public class XOPConverter {

    @SuppressWarnings("rawtypes")
    public static void convertToXOP(String xml, OutputStream os) 
        throws XMLStreamException, JaxenException, JiBXException, IOException {

        // set up all the AXIOM objects
        XMLInputFactory factory = XMLInputFactory.newInstance();
        XMLStreamReader parser = factory.createXMLStreamReader(new StringReader(xml));
        StAXOMBuilder builder = new StAXOMBuilder(parser);
        OMDocument document = builder.getDocument();
        OMElement documentElement =  builder.getDocumentElement();
        OMFactory fac = OMAbstractFactory.getOMFactory();
        
        // find all the BinaryBase64Object nodes
        AXIOMXPath xpathExpression = new AXIOMXPath("//niem-nc:BinaryBase64Object");
        xpathExpression.addNamespace("niem-nc", "http://niem.gov/niem/niem-core/2.0");
        List nodeList = xpathExpression.selectNodes(documentElement);
        
        for (Iterator i = nodeList.iterator(); i.hasNext(); ) {
            Object obj = i.next();
            if (!(obj instanceof OMElement)) {
                continue;
            }
            OMElement element = (OMElement)obj;
            // get bytes data out of that node
            String base64data = element.getText();
            // decode base 64 encoding
            byte[] data = Base64Serializer.deserializeBase64(base64data);
            // create ByteArrayDataSource
            ByteArrayDataSource ds = new ByteArrayDataSource(data);
            // set the MIME type
            ds.setType("image/" + XOPConverter.getMIMEType(data));
            // create OMText object
            OMText textElement = fac.createOMText(new DataHandler(ds), true);
            // replace BinaryBase64Object node text with OMText object
            element.setText("");
            element.addChild(textElement);
        }
        
        // do the XOP conversion
        OMOutputFormat format = new OMOutputFormat();
        format.setDoOptimize(true);
        format.setMimeBoundary("MIME");
        format.setContentType("Multipart/Related");
        MTOMXMLStreamWriter mtomWriter = new MTOMXMLStreamWriter(os, format);
        mtomWriter.setDoOptimize(true);
        document.serializeAndConsume(mtomWriter);
        document.close(false);
    }
    
    /**
     * @param args
     * @throws IOException 
     * @throws XMLStreamException 
     * @throws JaxenException 
     * @throws JiBXException 
     */
    public static void main(String[] args) 
        throws IOException, XMLStreamException, JaxenException, JiBXException {
        
        if (args.length == 0) {
            System.out.println("Usage: <filename>");
            return;
        }
        String inputFileName = args[0];
        String outputFileName = inputFileName.substring(0, 
                inputFileName.lastIndexOf(".")) + ".xop";
        boolean overwrite = false;
        if (args.length > 1) {
            String overwriteStr = args[1];
            overwrite = Boolean.parseBoolean(overwriteStr);
        }
        // check if output file exists
        if (new java.io.File(outputFileName).exists() && !overwrite) {
            System.out.println("ERROR: Output file " + outputFileName + 
                    " exists, please move it.");
            return;
        }
        
        java.io.BufferedReader reader = 
            new java.io.BufferedReader(new java.io.FileReader(args[0]));
        String line = null;
        StringBuilder stringBuilder = new StringBuilder();
        String ls = System.getProperty("line.separator");
        while ((line = reader.readLine()) != null) {
            stringBuilder.append(line);
            stringBuilder.append(ls);
        }
        String xml = stringBuilder.toString();
        
        FileOutputStream os = new FileOutputStream(outputFileName);
        convertToXOP(xml, os);
        System.out.println("done");
    }
}

Now I’ll step through the code and explain it a bit.

The convertToXOP method takes the XML text as a String, and an OutputStream where the XOP is written. In my original document, all the base64 encoded objects I need to extract and attach are in elements labeled . So I cook up an XPath expression to find all those elements for processing. The code gets the data out of the element and un-base64 encodes it. I didn’t write that code, so you’ll have to do that yourself. Then we put the byte array in a ByteArrayDataSource and tag it with the MIME type we want to use. In a later post I’ll show how to figure out what encoding an image uses. Then use the OMFactory to create a new OMText element containing the byte array and set it to be optimized. Add it to the original element and reset its text, clearing out the base64 encoded data.

The key to doing the conversion is Axiom’s MTOMXMLStreamWriter class. You send the MTOMXMLStreamWriter the OutputStream where you want the XOP document to go, and an OMOutputFormat object. Setting the doOptimize flag on the OMOutputFormat object enables the optimization step that moves the binary objects to MIME attachments. Sending the MTOMXMLStreamWriter to the OMDocument’s serializeAndConsume method writes the XOP output.

I hope this code and explanation has been helpful to you.

Advertisements
Categories: code Tags: , ,
  1. January 8, 2011 at 10:20 pm

    I’ve been following your blog since you started. You have made amazing progress. This site is an inspiration for all pursuing a long transition versus the big chop.

    – Rob

  2. Ray
    May 26, 2011 at 5:15 am

    Excellent ! Thanks for sharing!

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: