Welcome to JaxMe!

SourceForge.net Logo
JaxMe

- A Framework for Java/XML binding based on SAX2 -

Writing your own generators

In the few months of JaxMe's life (writing these words in May 2002) the program has proved to be very useful. Quite some suggestions and ideas for enhancements came up.

To my surprise, the most discussions haven't been with the lack of support for the various XML Schema possibilities (for example, no one has requested support for the schema enumerations so far). Instead the wish to generate other sources from the schema has been there from the start. Thanks to ideas from Stefan Becher, there is now a framework for other generators: The decorators and the parselets.

Decorators: Adding information to the schema tree

The first versions of JaxMe had a strict separation between Schema readers and Source writers. Schema readers had to build a schema tree, source writers had to use it. The idea was that it should be possible to use different readers for the same writer.

While this idea is still valid, it became difficult in practice. It turned out to be very important that writers had to be configured from the schema. In other words: The readers have to supply the information that a specific writer wants. A new concept was needed, which are the decorators. A reader can create a decorator which is a writer specific object attached to some element or attribute. The decorator is configured through its bean properties and later used by the writer.

More formally, the following steps are happening:

  1. The SchemaReader creates a Decorator factory. As the name says, this factory can create the actual decorators. The factory is typically configured through bean properties.
  2. From now on, the schema reader will call the factory for any element or attribute that it has read. The factory may create a decorator, which is attached to the element or attribute, thus a part of the tree that the reader builds. The decorators are also configured through bean properties.
  3. The decorator factory can also create a source writer. This source writer receives the readers tree, including the attached decorators. It can now emit sources, using the decorators for configuring itself.
You can imagine the process similar to this:
Schema Reader
  |
  V
------------------
|
V
Element
(created by the SchemaReader)
Decorator
(created by the Factory)
|
V
Element
(created by the SchemaReader)
Decorator
(created by the Factory)
Attribute
(created by the SchemaReader)
Decorator
(created by the Factory)
  ^
  |
DecoratorFactory
(created by the SchemaReader)

Configuring a decorator factory

As an example for decorators we'll take the Parselets, which will be described later in more detail. The full example is available in message.xsd.

First of all, we have to create the factory. This is done in the appinfo section of the schema node:

        <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
          xml:lang="EN"
          xmlns:jm="http://ispsoft.de/namespaces/jaxme/schema"
          xmlns:jp="http://ispsoft.de/namespaces/jaxme/schema/parselet"
          targetNamespace="http://jaxme.ispsoft.de/namespaces/examples/parselets"
          elementFormDefault="qualified"
          attributeFormDefault="unqualified">
          <xs:annotation>
            <xs:appinfo>
              <jm:elementDecoratorFactory
                class="de.ispsoft.jaxme.generator.parselets.ParseletDecoratorFactory"/>
	    </xs:appinfo>
	  </xs:annotation>
          ...
        </xs:schema>
      
Things you should note:
  • The decorator factory has its own namespace. This is not required, but definitely recommended.
  • The factory is declared with a node xs:elementDecoratorFactory. This forces the schema reader to instantiate an instance of the given class and add it to its list of decorator factories. Multiple factories are allowed.
  • What you don't see here: The factory declaration may have attributes and/or atomic child elements. These attributes and child elements represent beans. That is, if the factory declaration has an attribute someProperty="someValue" and the factory has a method setSomeProperty(String pValue), then this method will be called with the string someValue as an argument. Likewise, if there is a method setSomeProperty(int pValue), then the schema reader will attempt to convert someValue into an integer and call the method. Of course, the conversion can fail, which is a fatal error.

Besides the initial configuration, a decorator factory can configure itself arbitrarily through the schema nodes xs:appinfo section. The schema reader will call the decorator factories parseAppInfoDefaultNode for any child element of the xs:appinfo element that appears after the factory declaration.

Configuring a decorator

Once the factory is configured, it can create decorators. From now on, whenever the schema reader creates an element or attribute, it will allow the factory to add a decorator to the element. For that purpose, the factory method

        public ElementDecorator
          newElementDecorator(SchemaAnnotationOwner parent,
                              SchemaElement schemaElement,
                              ParserData parserData,
                              Element node)
      throws SchemaException;

      
is called. The method can use the created schemaElement (for example, it might want to know its multiplicity or whether it is an attribute), it can look at the parent in order to inspect the tree built so far, it can add data to the parserData node, which is used for configuration settings that hold true for childs and it can look at the elements DOM tree given by node. If the factory returns a decorator, then it will be attached to the element. The node can later be retrieved by calling schemaElement.getElementDecorator(factory). It's a part of the schema tree.

Once again, the decorator can configure itself from the elements xs:appinfo section: The schema reader will call the decorators method parseAppInfoNode for any child element of this section. If the decorator detects, that the child is relevant, then it may use the method de.ispsoft.jaxme.generator.util.DOM.assignBeanProperties to configure itself from the node.

We demonstrate this by looking at the parselet example from above.

      <xs:element name="Type" type="xs:integer">
        <xs:annotation>
          <xs:appinfo>
            <jp:parselet name="IntegerParselet"/>
          </xs:appinfo>
        </xs:annotation>
      </xs:element>
    
The parselet decorator has a method setName(String pName). If the decorators parseAppInfoNode method is called for the jp:parselet element, then it calls DOM.assignBeanProperties, which in turn will call setName("IntegerParselet");.

A decorators source writer

The decorator factory has another method which we haven't seen so far: It's called getSourceWriter() and may return a source writer. This method is called before any decorators are created. In other words, the factory can tell the source writer about any decorator, if it likes. However, it doesn't need to.

Once the schema reader has built its tree, it calls the source writer for doing its work. The source writer receives the element tree with the attached decorators and all the collected information.

Parselets: Implementing conversions from or to other objects

The parselet framework was originally designed for a very simple, but probably typical task: An XML element had to be converted into a binary stream. Vice versa, a binary stream had to be read and converted into XML.

The basic idea is as follows: A Parselet is an interface with two methods:

        public void serialize(Object pSerializable,
	                      Object pData) throws SAXException;
        public Object parse(Object pData) throws SAXException;
      
The method serialize converts the XML document pSerializable into another object, which is created by pData. On the other hand, the method parse is called for converting the object pData into an XML document, which it returns. This is typically simple for atomic elements or attributes. For complex elements, a Parselet can probably be built, by calling the child Parselets in the right order. For example, suggest the following XML document:
        <A id="someId">
	  <B>324</B>
	  <C>12.0>
	  <D>
	    <E>x</E>
	    <E>y</E>
	  </D>
	</A>
      
If we have Parselets for the attribute id and the elements B, C and E which may serialize themselfes into a stream, then we also have Parselets for D (by calling the Parselet for E twice) and F (by using the Parselets for id, B, C and D, in that order.

Configuring the Parselet factory

Parselets are based on Decorators. So, before using them, we have to configure the ParseletFactory:

        <xs:appinfo>
          <jm:elementDecoratorFactory
	    class="de.ispsoft.jaxme.generator.parselets.ParseletDecoratorFactory"/>
          <jp:decorator name="LongParselet"
	    parseletClassName="de.ispsoft.jaxme.examples.parselet.LongParselet"/>
	  <jp:decorator name="StringParselet"
            parseletClassName="de.ispsoft.jaxme.examples.parselet.StringParselet"/>
	  <jp:decorator name="DoubleParselet" parseletClassName="de.ispsoft.jaxme.examples.parselet.DoubleParselet"/>
	</xs:appinfo>
      

Things you should note:

  1. First of all, we specify the use of an ElementDecoratorFactory. This was already explained above. In the case of Parselets, the factory is an instance of ParseletDecoratorFactory.
  2. The Parselet factory needs a list of Parselets, which you want to use. These are the objects that convert your atomic elements and attributes.
  3. Any parselet class in the list is created with an element jp:decorator. The declaration requires the attributes name (a name which we shall later use to reference the declaration) and a parselet class.
  4. The above Parselets cannot be configured, which is probably fine in most cases. If you need to configure your Parselets, you must replace the attribute parseletClassName with class and specify your own implementation of ParseletDecorator. This is typically done by subclassing the implementations SimpleParseletDecoratorFactory (this is the default, if you omit the class or ParseletDecoratorImpl. Such a ParseletDecorator can be configured with bean properties, much like the decorators from above.

Choosing and configuring the Parselets

If you don't have a very clever ParseletDecorator, there remains some work to do: Choosing the Parselets for your elements and attributes. This looks like the following:

        <xs:element name="Type" type="xs:integer">
          <xs:annotation>
            <xs:appinfo>
              <jp:parselet name="IntegerParselet"/>
	    </xs:appinfo>
          </xs:annotation>
	</xs:element>
      
The IntegerParselet is one of the Parselet classes in the factories list, which you have configured
above.

Parselets at runtime

The most important thing to know about parselets at run time is how null values are used.

A Parselet example

A detailed example is available, which converts an XML document into a hex stream and, vice versa, reads a hex stream from a StringBuffer. The Parselets can be found in the parselet directory and the use is demonstrated in a JUnit test.


  FAQ   |   Reference   |   Contact   |   Comments   |   Forward   |   Back   |   Top of Page