`
fyd222
  • 浏览: 99036 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

AXIOM

阅读更多

AXIs Object Model (AXIOM) is not yet another object model. It was designed with one clear objective -- to boost the performance of Axis 2, the next generation SOAP stack from Apache. Toward this end, AXIOM (also referred to as OM) differs from other object models because it strives to be lightweight in construction and it does not try to build things unless required to do so. Being lightweight, it exerts as little pressure as possible on system resources, mainly CPU and memory. Meanwhile, deferred construction allows you to use one part of the tree while other parts remain incomplete. AXIOM unveils the power of this deferred building capability through the support its gets from the underlying Streaming API for XML (StAX) parser. AXIOM provides all this while it makes the resulting complexity transparent to the user.

Tests carried out using XMLBench Document Model Benchmark tests (see Resources) revealed that the performance of AXIOM is well on par with existing high-performance object models. But AXIOM's memory footprint is better than most of the existing object models that depend on SAX and/or DOM for input and output. This makes AXIOM an ideal choice for XML processors like Web services engines or memory-constrained devices; it can be used for generic XML processing, but AXIOM includes an optional layer that's optimized for SOAP.

Using AXIOM

In a typical SOAP engine, data can appear in one of three representations:

  • A serialized form like XML or binary XML
  • An in-memory tree-based object model like DOM
  • Language-specific objects like Plain Old Java Objects (POJOs)

Consider the invocation of a Web service. The data to be passed on to the service provider might be in language-specific objects -- POJOs in the case of Java technology. The first step in the process is to put the information items in these objects into a SOAP envelope and construct a SOAP message. Since a SOAP message is an XML document, the Web service must also convert the data items to the required XML format. Representing an XML Infoset in memory requires the construction of an object tree which is facilitated by an object model (AXIOM).

Creating AXIOM from scratch

Your first step in creating an in-memory object hierarchy is to create an object factory:

OMFactory factory= OMAbstractFactory.getOMFactory();

AXIOM allows many different object factory implementations, but the linked list is by far the most popular. Once a factory is available, you can begin to construct the tree.

Consider the following XML fragment:


Listing 1. Line item details
<po:line-item po:quantity="2" 
   xmlns:po="http://openuri.org/easypo">
      <po:description>
         Burnham's Celestial Handbook, Vol 2
      </po:description>
         <po:price>19.89</po:price>
</po:line-item>

Notice that all of the elements and attributes belong to the "http://openuri.org/easypo" namespace. Hence, your first step in constructing an AXIOM tree for this snippet of XML is to create this namespace, as follows:

OMNamespace poNs= factory.createOMNamespace("http://openuri.org/easypo", "po");

Now you can construct the wrapper line-item element:

OMElement lineItem= factory.createOMElement("line-item", poNs);

Next you create the relevant child elements and attributes of the line-item element.

It is best to create attributes of an element in the following manner:

lineItem.addAttribute("quantity", "2", poNs);

Create child elements as you would any other element, and then incorporate them into the parent in the following manner:

   OMElement description= factory.
      createOMElement("description", poNs);
         description.setText("Burnham's Celestial Handbook, Vol 2");
         lineItem.addChild(description);

Similarly, you add in the price child element as well:

   OMElement price= factory.createOMElement("price", poNs);
         price.setText("19.89");
         lineItem.addChild(price);

Listing 2 shows the complete code fragment.


Listing 2. Create a line item, programmatically
   OMFactory factory = OMAbstractFactory.getOMFactory();

         OMNamespace poNs = 
         factory.createOMNamespace("http://openuri.org/easypo", "po");

         OMElement lineItem =
         factory.createOMElement("line-item", poNs);
         lineItem.addAttribute("quantity", "2", poNs);

         OMElement description = 
         factory.createOMElement("description", poNs);
         description.setText("Burnham's Celestial Handbook, Vol 2");
         lineItem.addChild(description);

         OMElement price = factory.createOMElement("price", poNs);
         price.setText("19.89");
         lineItem.addChild(price);

Output

You can now serialize the constructed element using the StAX writer:


Listing 3. Serialize the line item
   XMLOutputFactory xof = XMLOutputFactory.newInstance();
   XMLStreamWriter writer = xof.
      createXMLStreamWriter(System.out);
      lineItem.serialize(writer);
      writer.flush();

Building AXIOM from an existing source

Now take a look at the the reverse process -- building an in-memory object model from a data stream.

In the simplest case, you would only be concerned with de-serializing a piece of XML. However, in SOAP processing, you de-serialize SOAP messages or MTOM-optimized MIME envelopes. Because it is especially geared toward SOAP processing, AXIOM provides built-in support for all of these and I'll provide those details shortly. But first, I'll show you how to de-serialize a simple XML fragment -- specifically, the one that you just serialized.

Start by constructing a parser. AXIOM supports both SAX and StAX parsers for parsing XML. However, SAX parsing does not permit deferred construction of the object model, so when deferred building is important you should use a StAX-based parser.

The first step is to obtain an XMLStreamReader for the data stream:

File file= new File("line-item.xml");
         FileInputStream fis= new FileInputStream(file);
         XMLInputFactory xif= XMLInputFactory.newInstance();
         XMLStreamReader reader= xif.createXMLStreamReader(fis);

Next, create a builder and pass the XMLStreamReader to it:

   StAXOMBuilder builder= new StAXOMBuilder(reader);
         lineItem= builder.getDocumentElement();

Now you can access the attributes and child elements or the XML Infoset items using the AXIOM API. Here's how to access attributes:

   OMAttribute quantity= lineItem.getFirstAttribute(
         new QName("http://openuri.org/easypo", "quantity"));

   System.out.println("quantity= " + quantity.getValue());

And you access the child elements in a similar fashion:

   price= lineItem.getFirstChildWithName(
         new QName("http://openuri.org/easypo", "price"));
               System.out.println("price= " + price.getText());

Listing 4 shows the complete code fragment.


Listing 4. Build AXIOM from an XML file
File file = new File("line-item.xml");
   FileInputStream fis = new FileInputStream(file);
   XMLInputFactory xif = XMLInputFactory.newInstance();
   XMLStreamReader reader = xif.createXMLStreamReader(fis);

   StAXOMBuilder builder = new StAXOMBuilder(reader);
   OMElement lineItem = builder.getDocumentElement();
   lineItem.serializeWithCache(writer);
   writer.flush();

   OMAttribute quantity = lineItem.getFirstAttribute(
         new QName("http://openuri.org/easypo", "quantity"));
   System.out.println("quantity= " + quantity.getValue());

   OMElement price = lineItem.getFirstChildWithName(
         new QName("http://openuri.org/easypo", "price"));
   System.out.println("price= " + price.getText());

   OMElement description = lineItem.getFirstChildWithName(
         new QName("http://openuri.org/easypo", "description"));
   System.out.println("description= " + description.getText());

One of the best aspects of AXIOM is that it attempts to provide a user-friendly API on top of cutting edge technologies like deferred construction. However, to use it to its full potential you need to understand the underlying architecture.

A little deeper into AXIOM

Caching is one of the central concepts in AXIOM. However, to understand caching you need to consider it in the context of deferred construction of the tree and the AXIOM API. AXIOM offers several APIs to access the underlying XML Infoset. The one I used above is the tree-based API that's provided by all other contemporary object models, such as DOM and JDOM. However, AXIOM also allows you to access information through the SAX or StAX APIs. See Figure 1.


Figure 1. AXIOM, input and output
AXIOM, input and output 

Why would you want to construct an object model if you intend to use one of the XML parsing APIs? Doing this allows you to use different APIs to access the different parts of the object model. For instance, take the case of a SOAP stack: A SOAP message may be processed by a number of handlers prior to being consumed by the service to which it is addressed. These handlers typically use a tree-based API (specifically SOAP with Attachments API for Java, or SAAJ). Yet the service implementation might use a data binding tool to convert the XML document carried in the payload of the SOAP message into objects, such as POJOs. Since the user is not using a tree-based object model to access this part of the document, constructing the entire tree would lead to a waste of memory due to replication of data. The obvious solution is to expose the underlying raw XML stream to the data binding tool. This is where AXIOM shines.

To achieve optimum performance and memory efficiency, you need to give the data binding tool direct access to the underlying XML stream. AXIOM allows you to do exactly that. Deferred building simply means that no part of the tree is built unless it is accessed. Hence if no one accesses the body of a SOAP message, that part of the SOAP message will not be built. If the user starts to access the body with SAX or StAX and it has not yet been built, AXIOM will connect the user directly to the underlying parser to give the best possible performance. This is illustrated in Figure 2:


Figure 2. Access the underlying parser through AXIOM
 Accessing the underlying parser through AXIOM 

However, this can cause problems if the user wants to cycle back and access the same parts of the tree again. Since the parser has thus far been connected directly to the user, AXIOM has been out of the loop -- meaning all information has been flowing directly from the underlying stream to the user. So when the user comes back and asks for the same information -- regardless of which API he chooses to use the second time around -- AXIOM cannot provide it. Notice that both possibilities are equally likely. For instance, most of the time in SOAP body processing only the end service implementation will touch the payload. The service can do so through the use of data binding or other XML processing APIs like SAX, StAX, or XPath. In such cases, the body is rarely accessed twice and the optimization provided by AXIOM gives the best possible performance.

However, suppose you inserted a logging handler somewhere in the handler chain that logs the entire SOAP message using a StAX writer. Then if the service implementation tries to access the body, the body is simply not there!

To clarify this further, here's a simpler, albeit artificial, example.

StAXOMBuilder builder = new StAXOMBuilder(reader);
   lineItem = builder.getDocumentElement();
   lineItem.serialize(writer);
   writer.flush();

   price = lineItem.getFirstChildWithName(
      new QName("http://openuri.org/easypo", "price"));
   System.out.println("price= " + price.getText());

Because of deferred construction, the lineItem element is not fully built when you obtain it. Hence during subsequent serialization with the StAX writer, AXIOM connects the StAX writer (which serializes the lineItem element) directly to the StAX reader (which was initially passed in to the builder). However, in the process AXIOM disconnects itself from the data stream. Now when you ask for the price child element, no such element can be found since all children of lineItem have now vanished into the serializer.

The only solution in this case is to avoid detaching AXIOM completely from the data flow during serialization. The AXIOM terminology for this is caching: AXIOM allows you to get StAX events or serialize the XML with or without building the object model in memory. Hence AXIOM separates the policy (for example, should the message be cached?) from the mechanism (how caching is done). It allows the user to decide at the time he starts using either one of the raw XML processing APIs (such as SAX or StAX) whether to cache as-yet-unused parts of the tree for future reference. If the user chooses to do so, he can cycle back and access those parts again as if the tree was completely built. However, the user will pay the price in memory usage and performance. On the other hand, if the user knows what he is doing and is sure that this is the only instance in which access to those parts of the tree is required, he can choose not to and turn off caching to harness the full potential of AXIOM.

Thus the correct way to write the last code fragment is:

   StAXOMBuilder builder = new StAXOMBuilder(reader);
      lineItem = builder.getDocumentElement();
      lineItem.serializeWithCache(writer);
      writer.flush();

   price = lineItem.getFirstChildWithName(
      new QName("http://openuri.org/easypo", "price"));
   System.out.println("price= " + price.getText());

The method serializeWithCache, unlike its counterpart serialize, never directly connects the StAX reader to the StAX writer. Instead, all data that passes from the reader to the writer is kept in AXIOM. Exactly how this buffering is done is irrelevant to the user. At present, when caching is on, AXIOM builds the tree as if the user were accessing those parts of the tree through the document API.

AXIOM and StAX

With this background in mind, take a look at the StAX API of AXIOM. The most important methods in this API are the following:

(OMElement).getXMLStreamReader();
(OMElement).getXMLStreamReaderWithoutCaching();

The first method allows you to access the XML Infoset of the element on which the method is called through the StAX API, while caching (if necessary) any unbuilt parts of tree for later use. As the name implies, the second method gives you access to the same information, but optimizes performance by turning off caching. These methods are of the utmost importance in writing stubs and skeletons that require the use of data binding frameworks.

Note, however, that if the tree is built prior to calling either of the above methods, AXIOM will emulate the StAX parser. So events for some of the tree nodes come through emulation, while for other nodes you are directly connected to the underlying parser. The beauty of AXIOM is that it does this in a way that is transparent to you as the user. However, at the point you switch to the raw APIs, you must indicate whether you want to cache data or not.

To illustrate the use of the StAX API, I'll show you how to connect AXIOM with XMLBeans generated code.


Listing 5. XMLBeans generated code for a purchase order
public class PurchaseOrderSkel {

   public void submitPurchaseOrder(
         PurchaseOrderDocument doc) throws Exception {
		  
   }

      public void submitPurchaseOrderWrapper(
               OMElement payload) {
            try {
                  XMLStreamReader reader= payload.
                        getXMLStreamReaderWithoutCaching();
                  PurchaseOrderDocument doc
                     = PurchaseOrderDocument.Factory.parse(reader);
                           submitPurchaseOrder(doc);
            } catch (Exception ex) {
                  ex.printStacktrace();
            }

      }
   }

The code in Listing 5 (typically generated by a code generation tool) shows a skeleton that uses XMLBeans generated classes, meaning a PurchaseOrderDocument for data binding. This skeleton contains two service implementation methods. The first allows the service implementer to work on data bound objects, while the second allows direct access to the AXIOM API. Focus on the following lines:

                  XMLStreamReader reader= payload.
                        getXMLStreamReaderWithoutCaching();
                  PurchaseOrderDocument doc
                     = PurchaseOrderDocument.Factory.parse(reader);

To create the objects, you first obtain a reference to the StAX API for the payload that the SOAP stack (such as Apache Axis) pushes into the service implementation. Since you are now at the end of the processing chain, you can safely connect the parser directly to the XMLBeans unmarshaller to achieve maximum performance.

The stub code for the skeleton in Listing 5 looks similar to that in Listing 6.


Listing 6. Stub code
public class PurchaseOrderStub {
         public void submitPurchaseOrder(
            PurchaseOrderDocument doc) throws Exception {
                  SOAPEnvelope envelope = factory.getDefaultEnvelope();
                  XMLStreamReader reader = doc.newXMLStreamReader();
                  StAXOMBuilder builder = new StAXOMBuilder(reader);
                  OMElement payload= builder.getDocumentElement();
                  envelope.getBody().addChild(payload);
                  // ...
         }
      }


Focus on the following lines:

                  XMLStreamReader reader = doc.newXMLStreamReader();
                  StAXOMBuilder builder = new StAXOMBuilder(reader);
                  Element payload= builder.getDocumentElement();

As you can see in this code, going from objects to AXIOM through the StAX API is no different from going from XML to AXIOM.

Yet what is not obvious at first glance is that deferred construction still applies! Even though you created an OMElement in the process of inserting your payload into the SOAP envelope, you have not duplicated information items in memory. This is due to deferred construction and multiplexing that takes place inside AXIOM and routes data coming in through one API directly out through another. When this message is ultimately written to the stream, the XMLStreamReader that's provided by XMLBeans is connected directly to the transport writer that writes out this message to a socket -- provided no handler along the way decides to have a peek at the body. This means that until such time, all data remains only in the XMLBeans objects -- bravo!

AXIOM and data binding

I discuss the SAX API of AXIOM here because some data binding frameworks won't work with anything else -- JAXB, for example. Though the use of SAX clearly leads to suboptimal performance in the above scenario, the use of SAX for going from AXIOM to objects does not result in any loss of performance as the step is atomic in any case.

If you use JAXB, then your stubs will need to use the SAXOMBuilder to construct AXIOM from data-bound objects. Listing 7 illustrates this.


Listing 7. AXIOM and JAXB
public class PurchaseOrderStub {
      public void submitPurchaseOrder(
            PurchaseOrder doc) throws Exception {
                  SOAPEnvelope envelope = factory.getDefaultEnvelope();
                  SAXOMBuilder builder = new SAXOMBuilder();
                  JAXBContext jaxbContext = JAXBContext.newInstance("po");
                  Marshaller marshaller = jaxbContext.createMarshaller();
                  marshaller.marshal(doc, builder);
                  OMElement payload= builder.getDocumentElement();
                  envelope.getBody().addChild(payload);
                  //...
      }
   }

At present, AXIOM does not allow you to register a content handler with an OMElement to receive SAX events. Still, it's easy to write a piece of glue code that picks up events from the provided StAX interface and drives a SAX ContentHandler. Interested readers can find such an implementation in the JAXB reference implementation in Resources.

Conclusion

I showed you some of the promising features introduced by AXIOM, going beyond the typical XML object model. Note that this article has introduced only some of the features available. AXIOM has many more powerful features, and I encourage you to download the latest source from the Axis 2 source repository (see Resources) and explore more about AXIOM.


Resources

Learn

Get products and technologies

About the author

Eran Chinthaka

Eran Chinthaka is a pioneering architect of the Apache Axis 2 project, and works full-time for the Lanka Software Foundation. He has implemented AXIOM, WS-Addressing , SOAP 1.1 and 1.2, client interaction patterns for Axis 2, as well as a Visual Modelling tool for BPEL4WS. In addition, he has worked as an architect on projects involving Web services, business process automation, mobile development, and telecommunications network management.

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics