[ Team LiB ] Previous Section Next Section

WebLogic Server XML Streaming API

You can also parse XML documents using WebLogic Server's implementation known as the Streaming API. This API is based on SAX, but gives you more control than SAX while parsing your document. This API enables you to be proactive rather than reactive. In other words, you request events from the parser and process them, rather than waiting for events from the parser as you do with SAX. For this reason, this kind of parsing is known as pull parsing. You can parse not only XML documents using the Streaming API, but also DOM trees and SAX events.

Parsing an XML Document

You can parse any XML source by using the Streaming API. Parsing an XML document typically involves the following steps.

Creating an XML Input Stream

The WebLogic Server XML Streaming API classes and interfaces can be found in the package weblogic.xml.stream. All classes and interfaces referenced in this section belong to this package unless otherwise stated. This package contains a factory, which provides you with the starting point for the XML streaming process. This factory, the XMLInputStreamFactory, will create an XML input stream from the input stream passed in. To do this, you invoke the newInputStream method, which returns an object of type XMLInputStream to you. The following code snippet demonstrates the process of creating the input stream:


XMLInputStreamFactory factory = XMLInputStreamFactory.newInstance();
XMLInputStream inputStream = factory.newInputStream(
    new FileInputStream("c:\temp\doc.xml"));

The preceding example is the easiest way of creating an XML input stream. The factory enables you to complicate it further by filtering the XML events that the stream returns. There are several ways of filtering the events. You can filter it by type, by name of the element, by namespace, or by a combination of namespace and type. You can also create custom filters if you require enhanced filtering capabilities.

To filter the events that are generated from the stream, create a filter and pass it on to the newInputStream method. You can create different filters as discussed in Table 29.4. We haven't discussed XMLEvents yet, but for this discussion, it's enough to know that an XMLEvent is simply a wrapper for an event that is generated by the parser. Events may be seen as being similar to SAX events such as start element, end element, and so forth.

Table 29.4. XML Events Filters

Filter Type

Description

TypeFilter

Filters based on the type of events; for instance, start element, end element, and so forth. We'll discuss element types in detail in just a second. For example, you can create a type filter by using the following construct: new TypeFilter(XMLEvent.START_ELEMENT | XMLEvent.END_ELEMENT )

NameFilter

Filters events based on element names. For example, new NameFilter("subject").

NameSpaceFilter

Filters based on namespaces. This enables you to receive elements that are part of a particular namespace only. For example, new NameSpaceFilter("http://www.emailnamespace.com").

NameSpaceTypeFilter

Enables you to filter based on namespace and type. For example, new NameSpaceTypeFilter("http://www.emailnamespace.com", XMLEvent.START_ELEMENT | XMLEvent.END_ELEMENT) will return start and end element events that belong to the given namespace.

As mentioned earlier, you can also create a custom filter if your application requires enhanced filtering. A custom filter is your own class that implements the ElementFilter interface. This interface defines an accept method that takes an XMLEvent as its parameter and returns a boolean indicating whether it is an acceptable event. Based on the return from this method, the event is either passed to you or dropped. Just as in regular filters, you create an instance of the custom filter and pass it to the newInputStream method to enable custom filtering of events.

With an input stream, you can process events only once. In other words, once an event has been served, you cannot get it back. But if your application requires that flexibility, you should use a buffered input stream rather than a regular input stream. Using the newBufferedInputStream method of the factory can create a buffered input stream. This method returns a BufferedXMLInputStream object. You can also specify filters while creating a buffered input stream. After you've created the buffered input stream, you can mark a position by invoking the mark method. You can subsequently go back to that position by invoking the reset method.

You can read more, including an example on filtering and BufferedInputStream, by visiting the URL http://edocs.bea.com/wls/docs81/xml/xml_stream.html#1092257.

Navigating Through the Stream

You can process the stream by using some methods present in the XMLInputStream object. You can consider this stream object as an enumeration of XML events. This object has a hasNext method that returns a boolean indicating whether the stream has more events. If it has more events, you can access the next event by invoking the next method on the stream. The next method returns an XMLEvent object, which can then be processed by your application. Remember that the hasNext and the next methods have already applied any filtering rules that you've requested on the events, so you'll receive only filtered events.


while (inputStream.hasNext()) {
  XMLEvent anEvent = inputStream.next();
  processEvent(anEvent);
}

You can also invoke methods in the XMLInputStream object to skip elements. When you invoke the skip method, the input stream is positioned to the next event in the stream. You can pass an integer into the skip method to skip more than one event. You can also skip until you reach a particular element by passing in the element name into the skip method. You can invoke the skipElement method to skip an element along with its sub-elements. You can invoke the peek method to take a peek at the forthcoming element without actually reading it out of the stream.

You can also get a sub-stream out of the input stream by invoking the getSubStream method. This method returns an XMLInputStream object, which essentially contains a copy of the next element, along with its sub-elements, as an input stream. The position on the parent input stream is not changed. What you get is a copy. If you want to skip this element in the parent input stream, you should explicitly invoke the skipElement method.

When you're done parsing the stream, close it by invoking the close method.

Processing an Event

To process an event, you must identify the type of the event that's being processed. Consider this as equivalent to the different node types that we handled when we discussed DOM. You can get an event type by invoking the getType method on an XMLEvent object. The event may be of different types, as indicated in Table 29.5. For each type of event, your application will receive a distinct event object from the stream that subclasses the XMLEvent class. You can see the actual subclass that will be returned in Table 29.5.

Table 29.5. XML Event Types

Event Type

Description

Represented by Subclass of XMLEvent

CHANGE_PREFIX_MAPPING

Indicates that the prefix mapping has been changed from one namespace to another

ChangePrefixMapping

CHARACTER_DATA

Indicates that the event has the body of the element

CharacterData

COMMENT

Indicates a comment

Comment

END_DOCUMENT

Indicates the end of a document

EndDocument

END_ELEMENT

Indicates the end of an element

EndElement

END_PREFIX_MAPPING

Signals the end of a prefix mapping scope

EndPrefixMapping

ENTITY_REFERENCE

Represents an entity reference

EntityReference

PROCESSING_INSTRUCTION

Indicates a processing instruction

ProcessingInstruction

SPACE

Indicates that the event contains whitespace

Space

START_DOCUMENT

Indicates the start of an XML document

StartDocument

START_ELEMENT

Indicates the start of the element

StartElement

START_PREFIX_MAPPING

Indicates the beginning of the prefix mapping scope

StartPrefixMapping

Thus, you would typically have a switch case statement in your application's processEvent method to enable you to process different event types. In the following code snippet, we demonstrate the use of such a switch case statement.


switch(theEvent.getType()) {
  case XMLEvent.START_ELEMENT:
    // process a start element
    break;
  case XMLEvent.END_ELEMENT:
    // process an end element event
    break;
  ...
}

To access the data within the events, you can convert the events into the appropriate interfaces as indicated in Table 29.5. After you cast the object, you can then access methods present within the interface to get to the data. For instance, the StartDocument interface provides you with access to the system and public IDs; the StartElement interface can be used to access the attributes of the element, the namespace information, and so forth.

NOTE

A more efficient way of performing this task is to create overloaded methods that perform various tasks based on the parameter passed in. For instance, we could create several processEvent methods and have multiple flavors of it based on the type of parameter passed in; for example, processEvent(StartDocument doc), processEvent(StartElement element), and so on. We could then allow the object-oriented method overloading to take charge of invoking the right method.

We don't discuss all the methods that you can access through each interface. You can look that up in the BEA API documentation for this package by accessing the URL http://edocs.bea.com/wls/docs81/xml/xml_stream.html#1092257.


The XMLEvent interface includes a getName method that returns an object of type XMLName.

This interface enables you access to the local name, the namespace URI, and the qualified name of the element name.

A simple XML stream-based processing is listed in the following code. In this example, we print out the name of the root node and its system ID. This is followed by a listing of all elements in the XML, along with their attributes, if any. The events in the stream are read using a while loop in the process method. Each event is then passed to the processEvent method as discussed earlier.


public class ProcessXML {
  /** The XML file to be processed */
  private String fileName = null;

  public ProcessXML(String file) {
    fileName = file;
  }

  private void process()
         throws FileNotFoundException, XMLStreamException {
    XMLInputStreamFactory factory =
         XMLInputStreamFactory.newInstance();
    TypeFilter aFilter =
          new TypeFilter(XMLEvent.START_DOCUMENT
             | XMLEvent.START_ELEMENT);

    XMLInputStream inputStream =
          factory.newInputStream(
              new FileInputStream(fileName),
                       aFilter);

    while (inputStream.hasNext()) {
      XMLEvent anEvent = inputStream.next();
      processEvent(anEvent);
    }
  }

  /**
   * Method processEvent.
   *
   * @param anEvent
   */
  private void processEvent(XMLEvent anEvent) {
    switch (anEvent.getType()) {
    case XMLEvent.START_DOCUMENT:

      StartDocument startDoc = (StartDocument) anEvent;
      System.out.println(" In Start Doc: ");
      System.out.println("  -- Name = " +
             startDoc.getName());
      System.out.println("  -- System Id = " +
             startDoc.getSystemId());

      break;

    case XMLEvent.START_ELEMENT:

      StartElement startElem = (StartElement) anEvent;
      System.out.println(" In Start Element: ");
      System.out.println(" -- name = " + startElem.getName());

      AttributeIterator attrs = startElem.getAttributes();

      while (attrs.hasNext()) {
        Attribute anAttr = attrs.next();
        System.out.println(" -- Attr name = " +
                      anAttr.getName());
        System.out.println(" -- -- Attr value = " +
                      anAttr.getValue());
      }

      break;

    default:
      System.out.println(
          "If you see this message, the "
              + " filter has been changed");
      break;
    }
  }
}

Look up the example com.wlsunleashed.xml.stream.ProcessXML on the CD, in the folder for this chapter. You can see a typical usage of the XML Streaming API to process an XML document. This example simply parses the XML document and prints its contents on the screen. Invoke it with any XML document as its parameter.

Generating New XML Documents

The Streaming API can be used not only to parse existing XML documents, but also to create new documents. While creating new documents, you can also append existing XML documents in the new ones.

Opening an Output Stream

To create a new XML document, you first create an output stream instead of the input stream that we created while reading a document. Creating an output stream is achieved by using a factory. The class for this factory is XMLOutputStreamFactory. It has a newOutputStream method that returns a new output stream based on the writer that you pass as an input. The writer can be constructed out of a flat file or any other output stream. The following code snippet creates a new output stream to be used for creating XML documents:


XMLOutputStreamFactory factory = XMLOutputStreamFactory.newInstance();
XMLOutputStream outputStream = factory.newOutputStream(
        new PrintWriter(System.out, true) );

The output stream can also be constructed out of a Document object, in which case it constructs the DOM tree based on the data passed in. All these methods throw an exception of type XMLStreamException, which indicates that there was an error in trying to create the output stream.

The output stream object (XMLOutputStream) consists of add methods that you can use to add attributes, text value, XMLEvents, and the contents of an XMLInputStream object. When you're done with the output stream, you close it by invoking the close method.

Writing the XML Elements

You first create elements of different types before you add them onto the output stream. Elements can be created by using the ElementFactory class. This class has a list of create methods, which will let you create elements of different types. For example, you will find a method createStartElement, which creates a start element based on the attributes. Similarly, you'll see a createAttribute method, which creates an attribute of an element. Use the appropriate create method to create an element and add it to the output stream to write the XML.

For example, consider the following XML document:


<email>
  <to name="John Doe" id="john@doe.com" />
  <subject>This is a Test</subject>
</email>

To write this simple XML document, you would use the following code snippet (assume that the output stream has already been created):


outputStream.add( ElementFactory.createStartElement("email") );
outputStream.add(ElementFactory.createStartElement("to") );
outputStream.add(ElementFactory.createAttribute("name", "John Doe"));
outputStream.add(ElementFactory.createAttribute("id", "john@doe.com"));

outputStream.add(ElementFactory.createEndElement("to") );
outputStream.add(ElementFactory.createStartElement("subject") );
outputStream.add(ElementFactory.createCharacterData("This is a test"));
outputStream.add(ElementFactory.createEndElement("subject") );
outputStream.add(ElementFactory.createEndElement("email") );
Adding Another XML Document

You can also embed an existing XML document inside the XML that you're creating by creating an input stream and adding it to the output stream. For example, the following code snippet does this. Assume that we've already created an input stream object and an output stream object prior to this code snippet.


outputStream.add( inputStream ) ;

This code automatically adds the entire XML event stream present in the input stream into the output stream, thus including it in the output XML document.

Included with this book is a sample code that writes a simple XML document onto the screen. You can execute it by invoking the class com.wlsunleashed.xml.stream.WriteXML. You can pass another XML document as a parameter into this XML; in that case, the passed-in document will be included in the output.

    [ Team LiB ] Previous Section Next Section