Content:
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.4.0</version> </dependency> <dependency> <groupId>org.apache.ws.commons.axiom</groupId> <artifactId>axiom-impl</artifactId> <version>1.4.0</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.4.0</version> <scope>runtime</scope> </dependency>
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(); }
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)); }
Here the goal is to process a large XML document “by chunks”, i.e.
Parse the file and find a relevant element (e.g. by name)
Load this element into memory as an OMElement.
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 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.
This sample shows how to process MTOM messages with Axiom. The code actually sends a request to the following JAX-WS service:
@WebService(targetNamespace="urn:test") @MTOM public class MTOMService { @WebMethod @WebResult(name="content") public DataHandler retrieveContent(@WebParam(name="fileId") String fileId) { return new DataHandler(new URLDataSource(MTOMService.class.getResource("test.txt"))); } }
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 and send the request URLConnection connection = serviceURL.openConnection(); connection.setDoOutput(true); OMOutputFormat format = new OMOutputFormat(); format.setDoOptimize(true); format.setCharSetEncoding("UTF-8"); connection.addRequestProperty("Content-Type", format.getContentType()); OutputStream out = connection.getOutputStream(); request.serialize(out, format); out.close(); // Get the SOAP response InputStream in = connection.getInputStream(); MultipartBody multipartBody = MultipartBody.builder() .setInputStream(in) .setContentType(connection.getContentType()) .build(); SOAPEnvelope response = OMXMLBuilderFactory.createSOAPModelBuilder(multipartBody).getSOAPEnvelope(); OMElement retrieveContentResponse = response.getBody().getFirstElement(); OMElement content = retrieveContentResponse.getFirstElement(); // Extract the DataHandler representing the optimized binary data DataHandler dh = ((OMText)content.getFirstOMChild()).getDataHandler(); // Stream the content of the MIME part InputStream contentStream = ((PartDataHandler)dh).getPart().getInputStream(false); // 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 MultipartBody 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 MultipartBody can be configured to buffer data on disk. The sample actually shows an alternative method to reduce the memory footprint of the MTOM processing, which is to enable streaming.
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. The solution is to use a custom XMLStreamWriter implementation that uses an Axiom specific extension to accept DataHandler objects and replaces them by some placeholder text:
public class LogWriter extends XMLStreamWriterWrapper implements DataHandlerWriter { public LogWriter(XMLStreamWriter parent) { super(parent); } @Override public Object getProperty(String name) throws IllegalArgumentException { if (name.equals(DataHandlerWriter.PROPERTY)) { return this; } else { return super.getProperty(name); } } @Override public void writeDataHandler(DataHandler dataHandler, String contentID, boolean optimize) throws IOException, XMLStreamException { super.writeCharacters("[base64 encoded data]"); } @Override 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 logMessage(SOAPEnvelope env) throws XMLStreamException { StringWriter sw = new StringWriter(); XMLStreamWriter writer = new LogWriter(XMLOutputFactory.newInstance().createXMLStreamWriter(sw)); env.serialize(writer); writer.flush(); log.info("Message: " + sw.toString()); }