Quick Start Samples
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>2.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.ws.commons.axiom</groupId>
<artifactId>axiom-impl</artifactId>
<version>2.0.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>2.0.0</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.
-
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.
Processing MTOM messages
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 Blob representing the optimized binary data
Blob blob = ((OMText) content.getFirstOMChild()).getBlob();
// Stream the content of the MIME part
InputStream contentStream = ((PartBlob) blob).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.
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. 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 BlobWriter {
public LogWriter(XMLStreamWriter parent) {
super(parent);
}
@Override
public Object getProperty(String name) throws IllegalArgumentException {
if (name.equals(BlobWriter.PROPERTY)) {
return this;
} else {
return super.getProperty(name);
}
}
@Override
public void writeBlob(Blob blob, String contentID, boolean optimize)
throws IOException, XMLStreamException {
super.writeCharacters("[base64 encoded data]");
}
@Override
public void writeBlob(BlobProvider blobProvider, 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());
}