2007/07/30
30 Jul, 2007

Creating an AXIOM Object Model Using Different Input Sources

  • Eran Chinthaka
  • Software Engineer - WSO2

There are various situations in which you might need to create an Axiom object model. If you are using Apache Axis2, you must represent all your XMLs using AXIOM before handing them over to the Axis2 engine. You can create an AXIOM object model either programmatically or you can build it from an existing source. Have you ever wondered about the various methods you can use to create AXIOM from existing sources? Let's see what we have within AXIOM for your convenience.

(It is recommended to read the AXIOM - Fast and Lightweight Object Model for XML article series within Oxygen Tank if you need more information on AXIOM.)

Method 1 - File Location

First let's see how you can create an AXIOM object model from a file.

Listing 1 demonstrates how you can pass a path to your XML file and create the object model.

  String filePath = "my-xml-files/MyXMLFile.xml";
OMElement documentElement = new StAXOMBuilder(filePath).getDocumentElement();

Listing 1. Creating an Axiom model by giving a file location

Method 2 - java.io.File Reference

Listing 2 demonstrates how you can create the model using a java.io.File instance. Here we use the ability to create an AXIOM model using an input stream.

  File file = new File("my-xml-files/MyXMLFile.xml");
OMElement documentElement = new StAXOMBuilder(new FileInputStream(file)).getDocumentElement();

Listing 2. Creating an Axiom model giving a reference to a file

Method 3 - String Containing the XML

How about having your XML inside a java.lang.String object? AXIOMUtil has the method stringToOM(String) which can do what you want. Listing 3 demonstrates the way to create the model using a String.

  OMElement documentElement = AXIOMUtil.stringToOM(xml);

Listing 3. Creating an Axiom model using java.lang.String

Method 4 - LLOM to/from DOOM

AXIOM has two different implementations, namely: linked list based implementation and DOM implementation over AXIOM. Now let's consider how we can convert between these two models.

All the AXIOM elements expose a StAX reader API. The trick we are going to use here is related to that. We get an XmlStreamReader input from the source model and feed it in to the proper builder to create the target model. Listing 4 demonstrates how this can be done.

// first convert from DOOM element to LLOM element
OMElement myDOOMElement; 
OMElement llomElement = new StAXOMBuilder(myDOOMElement.getXMLStreamReader()).getDocumentElement();
// now let's convert the LLOM element back to a DOOM element
OMElement myDOOMElement = new StAXOMBuilder(DOOMAbstractFactory.getOMFactory(), 
llomElement.getXMLStreamReader()).getDocumentElement(); 

Listing 4. Converting to/from DOOM to/from LLOM element

 

The above approach is useful if you want to completely get rid of one object model. However, there are some instances where you need to add, say a DOOM element into an existing LLOM object structure. In this case, you do not have to explicitly do anything to convert the DOOM element to an LLOM element. When you call the OMElement.addChild(OMElement) method, passing the DOOM element, the LLOM implementation automatically imports the DOOM element into the existing structure.

Method 5 - Data Source

Finally, let's see what you can do if you have your own object model which wraps some XML chunk within it. Before explaining how this can be done, let's quickly understand what AXIOM does inside it to maintain an XML object model. AXIOM uses StAX API to read and write XML. All the builders inside AXIOM can read StAX events using the XMLStreamReader API found within StAX API. If you look at the internals of StAXOMBuilder, no matter how you parse the XML into it, it converts it to an instance of XMLStreamReader. AXIOM gains its deferred building capability, mainly because of the usage of the XMLStreamReader internally. (For an introduction to StAX please read the article "Introduction to StAX" in Oxygen Tank) Also when we are writing contents of an OMElement, it is given an instance of XMLStreamWriter to write the content it encapsulates to the output.

If there is an object which can provide an interface

  • to throw StAX events from the XML info-set it encapsulates, and
  • to write the XML info-set it encapsulates into an instance of XMLStreamWriter,

then that object should easily be integrated into an AXIOM object model.

AXIOM has the following class, which does exactly what we need.

org.apache.axiom.om.impl.llom.OMSourcedElementImpl

The OMSourcedElementImpl class can encapsulate your XML object and provide it as a normal OMElement to the other nodes within the model. Since there should be a contract between your XML object and OMSourcedElementImpl, you need to implement org.apache.axiom.om.OMDataSource in your XML object.

Let's see what you need to do in more detail.

First implement the OMDataSource interface to your XML object. This interface carries the method getReader(), to get StAX events from your model, and also three serialize methods which have different output mechanisms for you to write your XML content. Listing 5 shows OMDataSource interface.

  public interface OMDataSource {
/**
* Serializes element data directly to stream.
*/
void serialize(OutputStream output, OMOutputFormat format)
throws XMLStreamException;
/**
* Serializes element data directly to writer.
*/
void serialize(Writer writer, OMOutputFormat format)
throws XMLStreamException;
/**
* Serializes element data directly to StAX writer.
*/
void serialize(XMLStreamWriter xmlWriter)
throws XMLStreamException;
/**
* Get parser for element data. In the general case this may require the data source to
* serialize data as XML text and then parse that text.
*/
XMLStreamReader getReader() throws XMLStreamException;
}

Listing 5. OMDataSource interface

 

Then it is just a matter of instantiating the OMSourcedElementImpl, giving your OMDataSource implementation. You can also decide which implementation to create out of the data source. The OMSourcedElementImpl constructor takes an OMFactory as an argument. If you want to create the LLOM object model, then pass OMAbstractFactory.getOMFactory() or if you want to create a DOOM model, pass DOOMAbstractFactory.getOMFactory(). Listing 6 shows how you can create your OMElement.

    OMElement myDataElement = new OMSourcedElementImpl(qNameOfYourElement, properOMFactory, yourOMDataSourceObject);

Listing 6. Creating an OMElement from a custom data source

Method 6 - Cloning

There can be some instances where you already have an instance of OMElement, and you need to clone it. When you are using Axis2, you might need to send the body XML a couple of times. If you use the same OMElement reference again and again, and if the Axis2 engine modifies it, then you will be in trouble.

Let's take a typical scenario. You have to send your employee details to two locations. You will use Method 1 or Method 2 above, to create an OMElement from your data so that you can include it within the SOAP body. AXIOM will not build an object model when creating an OMElement using the above methods. It will only hang on to the input stream and will build the object model only when someone asks for information inside the XML. Now you are trying to use the same OMElement for both requests. If the Axis2 engine does not touch the body of the message, before it actually sends the message, AXIOM will not build the object model in the memory. It will directly stream the input stream from the file to the output stream. Once you consume the input stream, within the first request, there is nothing left inside that OMElement. So when you try to send the second request with the same OMElement, you might be in trouble.

Let's take another example. When you send the first message, a handler in the out path might add a time stamp to the first body element of the message. When you use the same element again for the second request, you will see the previous time stamp also in the second message. So the best thing to do in such situations is to clone the original OMElement. Listing 7 shows how you can do it easily.

 
OMElement myElementToBeCloned = getMyElement();
OMElement clonedElement = myElementToBeCloned.cloneOMElement();

Listing 7. Cloning an OMElement

Since AXIOM internally handles partially built trees, let's quickly see what actually happens inside when you clone an element.

As mentioned earlier, when you create an OMElement passing an input stream, AXIOM will not create the object model at the same time. It will only create the object model, if someone tries to access the content of that XML or if it is asked to do so (calling the build() method). Since all the OMElements provide a StAX reader interface, the easiest way to clone an OMElement is to get a StAX reader from the source OMElement and feed it into the new OMElement through a StAXOMBuilder.

OMElement myElementToBeCloned = getMyElement();
OMElement clonedElement =
new StAXOMBuilder(this.myElementToBeCloned(true)).getDocumentElement();

Listing 8. Internals of cloning.

Still there is a problem. The source OMElement and cloned OMElement might have not been fully built by now. So we need to explicitly call the build() method and make sure it had consumed the whole stream.

clonedElement.build();

When you call the cloneOMElement() method in an OMElement, that is what happens inside that OMElement. The disadvantage in cloning is that you will lose AXIOM's deferred building capability also with a possible waste of some memory. However you will gain a deep copy of the content of the OMElement.

References/Resources

  1. AXIOM - Fast and Lightweight Object Model for XML - Part 1
  2. AXIOM - Fast and Lightweight Object Model for XML - Part 2
  3. Introduction to StAX (Streaming API for XML)

Author

Eran Chinthaka, WS PMC Member, Member, Apache Software Foundation, chinthaka(!) at apache(!) dot org(!)

 

About Author

  • Eran Chinthaka
  • Software Engineer
  • WSO2 Inc.