Content:

Adding Axiom as a Maven dependency

To use Axiom in a project built using Maven, add the following dependencies:

<dependency>
    <groupId>org.apache.ws.commons.axiom</groupId>
    <artifactId>axiom-api</artifactId>
    <version>1.2.13</version>
</dependency>
<dependency>
    <groupId>org.apache.ws.commons.axiom</groupId>
    <artifactId>axiom-impl</artifactId>
    <version>1.2.13</version>
    <scope>runtime</scope>
</dependency>

Note that the axiom-impl dependency is added in scope runtime because application code should not refer to implementation classes directly. All Axiom features are accessible through the public API which is provided by axiom-api.

If the application code requires a DOM compliant Axiom implementation, then the following dependency needs to be added too:

<dependency>
    <groupId>org.apache.ws.commons.axiom</groupId>
    <artifactId>axiom-dom</artifactId>
    <version>1.2.13</version>
    <scope>runtime</scope>
</dependency>

Parsing and processing an XML document

The following sample shows how to parse and process an XML document using Axiom. It is pretty much self-explaining:

public void processFile(File file) throws IOException, OMException {
    // Create a builder for the file and get the root element
    InputStream in = new FileInputStream(file);
    OMElement root = OMXMLBuilderFactory.createOMBuilder(in).getDocumentElement();
    
    // Process the content of the file
    OMElement urlElement = root.getFirstChildWithName(
            new QName("http://maven.apache.org/POM/4.0.0", "url"));
    if (urlElement == null) {
        System.out.println("No <url> element found");
    } else {
        System.out.println("url = " + urlElement.getText());
    }
    
    // Because Axiom uses deferred parsing, the stream must be closed AFTER
    // processing the document (unless OMElement#build() is called)
    in.close();
}

Schema validation using javax.xml.validation

This sample demonstrates how to validate a part of an Axiom tree (actually the body of a SOAP message) using the javax.xml.validation API:

public void validate(InputStream in, URL schemaUrl) throws Exception {
    SOAPModelBuilder builder = OMXMLBuilderFactory.createSOAPModelBuilder(in, "UTF-8");
    SOAPEnvelope envelope = builder.getSOAPEnvelope();
    OMElement bodyContent = envelope.getBody().getFirstElement();
    SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
    Schema schema = schemaFactory.newSchema(schemaUrl);
    Validator validator = schema.newValidator();
    validator.validate(bodyContent.getSAXSource(true));
}

It leverages the fact that Axiom is capable of constructing a SAXSource from an OMDocument or OMElement.

Alternatively, one can use a DOM compliant Axiom implementation and use a DOMSource to pass the XML fragment to the validator:

public void validateUsingDOM(InputStream in, URL schemaUrl) throws Exception {
    OMMetaFactory mf = OMAbstractFactory.getMetaFactory(OMAbstractFactory.FEATURE_DOM);
    SOAPModelBuilder builder = OMXMLBuilderFactory.createSOAPModelBuilder(mf, in, "UTF-8");
    SOAPEnvelope envelope = builder.getSOAPEnvelope();
    OMElement bodyContent = envelope.getBody().getFirstElement();
    SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
    Schema schema = schemaFactory.newSchema(schemaUrl);
    Validator validator = schema.newValidator();
    validator.validate(new DOMSource((Element)bodyContent));
}

Loading local chunks from a large XML document

Here the goal is to process a large XML document "by chunks", i.e.

  1. Parse the file and find a relevant element (e.g. by name)
  2. Load this element into memory as an OMElement.
  3. Process the OMElement (the "chunk").

The process is repeated until the end of the document is reached.

This can be achieved without loading the entire document into memory (and without loading all the chunks in memory) by scanning the document using the StAX API and switching to Axiom when a matching element is found:

public void processFragments(InputStream in) throws XMLStreamException {
    // Create an XMLStreamReader without building the object model
    XMLStreamReader reader =
        OMXMLBuilderFactory.createOMBuilder(in).getDocument().getXMLStreamReader(false);
    while (reader.hasNext()) {
        if (reader.getEventType() == XMLStreamReader.START_ELEMENT &&
                reader.getName().equals(new QName("tag"))) {
            // A matching START_ELEMENT event was found. Build a corresponding OMElement.
            OMElement element = 
                OMXMLBuilderFactory.createStAXOMBuilder(reader).getDocumentElement();
            // Make sure that all events belonging to the element are consumed so that
            // that the XMLStreamReader points to a well defined location (namely the
            // event immediately following the END_ELEMENT event).
            element.build();
            // Now process the element.
            processFragment(element);
        } else {
            reader.next();
        }
    }
}

The code leverages the fact that createStAXOMBuilder can be used to build a fragment (corresponding to a given element) from a StAX stream reader, simply by passing an XMLStreamReader that is positioned on a START_ELEMENT event.

Processing MTOM messages

This sample shows how to process MTOM messages with Axiom. The code actually sends a request to a Web service with the following JAX-WS service endpoint interface:

@WebService(targetNamespace="urn:test")
@MTOM
public interface MTOMService {
    @WebMethod
    @WebResult(name="content")
    DataHandler retrieveContent(@WebParam(name="fileId") String fileId);
}

It then extracts the binary content from the response and writes it to a given OutputStream:

public void retrieveContent(URL serviceURL, String id, OutputStream result) throws Exception {
    // Build the SOAP request
    SOAPFactory soapFactory = OMAbstractFactory.getSOAP11Factory();
    SOAPEnvelope request = soapFactory.getDefaultEnvelope();
    OMElement retrieveContent = soapFactory.createOMElement(
            new QName("urn:test", "retrieveContent"), request.getBody());
    OMElement fileId = soapFactory.createOMElement(new QName("fileId"), retrieveContent);
    fileId.setText(id);
    
    // Use the java.net.URL API to connect to the service
    URLConnection connection = serviceURL.openConnection();
    connection.setDoOutput(true);
    connection.addRequestProperty("Content-Type", "text/xml; charset=UTF-8");
    OutputStream out = connection.getOutputStream();
    // Send the request
    OMOutputFormat format = new OMOutputFormat();
    format.setCharSetEncoding("UTF-8");
    request.serialize(out, format);
    out.close();
    
    // Get the SOAP response
    InputStream in = connection.getInputStream();
    Attachments attachments = new Attachments(in, connection.getContentType());
    SOAPEnvelope response = OMXMLBuilderFactory.createSOAPModelBuilder(attachments).getSOAPEnvelope();
    OMElement retrieveContentResponse = response.getBody().getFirstElement();
    OMElement content = retrieveContentResponse.getFirstElement();
    // Extract the DataHandler representing the optimized binary data
    DataHandler dh = (DataHandler)((OMText)content.getFirstOMChild()).getDataHandler();
    InputStream contentStream;
    // If possible, stream the content of the MIME part (feature available in Axiom 1.2.13)
    if (dh instanceof DataHandlerExt) {
        contentStream = ((DataHandlerExt)dh).readOnce();
    } else {
        contentStream = dh.getInputStream();
    }
    // Write the content to the result stream
    IOUtils.copy(contentStream, result);
    contentStream.close();
    
    in.close();
}

The sample code shows that in order to parse an MTOM message one first needs to construct an Attachments object that is then passed to the relevant method in OMXMLBuilderFactory. In the object model, an XOP/MTOM attachment is represented as an OMText node for which isBinary() returns true. Such a node is created for each xop:Include element in the original message. The binary data is stored in a DataHandler object that can be obtained by a call to the getDataHandler() method of the OMText node.

By default attachments are loaded into memory, but the constructors of the Attachments class allow to configure caching on disk. The sample actually shows an alternative method to reduce the memory footprint of the MTOM processing, which is to enable streaming. This is supported starting with Axiom 1.2.13.

Logging MTOM messages without inlining optimized binary data

A common way to log a SOAP message is to invoke the toString method on the corresponding SOAPEnvelope object and send the result to the logger. However, this is problematic for MTOM messages because the toString method will always serialize the message as plain XML and therefore inline optimized binary data using base64 encoding.

Except for this particular use case, serializing a message using MTOM without actually producing the MIME parts for the binary data is not meaningful and is therefore not directly supported by the Axiom API. However the following simple trick can be used to implement this:

private void logMessage1(SOAPEnvelope env) throws XMLStreamException {
    StringWriter sw = new StringWriter();
    XMLStreamWriter writer = StAXUtils.createXMLStreamWriter(sw);
    XMLStreamWriter encoder = new XOPEncodingStreamWriter(writer,
            ContentIDGenerator.DEFAULT, OptimizationPolicy.DEFAULT);
    env.serialize(encoder);
    log.info("Message: " + sw.toString());
}

It relies on the XOPEncodingStreamWriter class which is used by Axiom internally to perform the XOP/MTOM encoding. This class is implemented as an XMLStreamWriter wrapper and is responsible for generating the xop:Include elements. It also builds a map of the MIME parts that need to be generated. Since XOPEncodingStreamWriter is an XMLStreamWriter proxy, it can easily be used in conjunction with the serialize(XMLStreamWriter) method and an XMLStreamWriter that writes the result to a StringWriter. The trick here is to only store the SOAP part and to disregard the MIME parts for the attachments. Since XOPEncodingStreamWriter only stores references to the DataHandler objects for these attachements, there is no overhead associated with that.

One may wonder how XOPEncodingStreamWriter can receive DataHandler objects considering that it is an XMLStreamWriter implementation and that the StAX API doesn't know anything about XOP/MTOM or DataHandler objects. The answer is that XOPEncodingStreamWriter actually implements an Axiom specific extension defined by the DataHandlerWriter interface. An alternative solution for the logging problem is therefore to use a custom XMLStreamWriter implementation that supports that extension and that bypasses the serialization of the DataHandler objects, e.g. by replacing them by some placeholder text:

public class LogWriter extends XMLStreamWriterWrapper implements DataHandlerWriter {
    public LogWriter(XMLStreamWriter parent) {
        super(parent);
    }

    public Object getProperty(String name) throws IllegalArgumentException {
        if (name.equals(DataHandlerWriter.PROPERTY)) {
            return this;
        } else {
            return super.getProperty(name);
        }
    }

    public void writeDataHandler(DataHandler dataHandler, String contentID, boolean optimize)
            throws IOException, XMLStreamException {
        super.writeCharacters("[base64 encoded data]");
    }

    public void writeDataHandler(DataHandlerProvider dataHandlerProvider, String contentID,
            boolean optimize) throws IOException, XMLStreamException {
        super.writeCharacters("[base64 encoded data]");
    }
}

The following code shows how this class would be used to log the MTOM message:

private void logMessage2(SOAPEnvelope env) throws XMLStreamException {
    StringWriter sw = new StringWriter();
    env.serialize(new LogWriter(StAXUtils.createXMLStreamWriter(sw)));
    log.info("Message: " + sw.toString());
}