Source Code Overview

Overview

In a SAX based environment the framework's entry point is the XForgeProcessor class which handles the incoming SAX events, executes the components generating the resulting XML and returns SAX events. In a typical SAX setup, thus, using X:Forge is as easy as setting the XForgeProcessor as the ContentHandler of a parser and provide a ContentHandler to the XForgeProcessor to send SAX events to. This example assumes that the "test.xml" file in the current directory is to be parsed, processed by X:Forge and finally printed:


// Instantiate the x:forge processor
XForgeProcessor processor = new XForgeProcessor();
[...]

// Instantiate a SAX parser
parser = XMLReaderFactory.createXMLReader();	

// Instantiate a Serializer
OutputFormat of = new OutputFormat();
of.setIndenting(true);
of.setEncoding("ISO-8859-1");

XMLSerializer serializer = new XMLSerializer((OutputStream)System.err,of);

// Set the x:forge processor as the ContentHandler
parser.setContentHandler(processor);

// Set the XML Serializer as the ContentHandler for the processor
processor.setContentHandler(serializer.asContentHandler());

// Start the pipeline
parser.parse(new InputSource("test.xml"));

Of course it is also possible to use X:Forge in a DOM based setup by using a utility method in the XForgeProcessor that takes a Document as the input and returns the expanded DOM tree.

Internally when the X:Forge processor receives the startElement event it checks if the element is actually an X:Forge element; if this is the case, the XForgeElementFactory dynamically instantiates the requested element (not components) and the processor sets it as the current ContentHandler. Once an X:Forge element is started, each event is directly passed through to it, except the startElement/endElement events in the X:Forge namespace.

The result is actually a stack of ContentHandlers, each one recursively sending events to his (parent) ContentHandler.

The SAX framework had to be extended in order to generate attribute-like events: this allows to set attributes to element after the element declaration in a way that is similar to the XSLT construct:

<xsl:element name="test">
  <xsl:attribute name="attr">I'm an attribute</xsl:attribute>
</xsl:element>

To achieve this result an AttributeListener interface has been provided with X:Forge. Finally, the same concept has been used with parameters that need to be passed to an X:Forge "process" element: any extension to the X:Forge framework that has to deal with parameters must then implement the ParameterListener interface.

Using Components

When a "process" element is found a component has to be instantiated. In order to instantiate the component a mapping mechanism which associates a symbolic name to a Java class is being used in the configuration file. The mapping is actually done between the symbolic name, the class name and its configuration, thus allowing to use the same Java class, with different configurations just by mapping it to different aliases. As an example:


<component role="process"
  class="org.apache.avalon.excalibur.component.DefaultComponentSelector"
  logger="mylogger">

<component-instance name="postgres" 
  class="org.bibop.xml.xforge.components.examples.XForgeSQLComponent"
  logger="myotherlogger">
<dbparameters>
	<url>jdbc:postgresql://localhost/sample</url>
	<user>auser</user>
	<password>apassword</password>
	<driver>org.postgresql.Driver</driver>
	<connection-pool>
		<initial-connections>5</initial-connections>
		<connections-increment>1</connections-increment>
	</connection-pool>
</dbparameters>
</component-instance>

<component-instance name="oracle" 
  class="org.bibop.xml.xforge.components.examples.XForgeSQLComponent"
  logger="mylogger">
<dbparameters>
	<url>jdbc:oracle:thin:@localhost:1521:SAMPLE</url>
	<user>auser</user>
	<password>apassword</password>
	<driver>oracle.jdbc.driver.OracleDriver</driver>
	<connection-pool>
		<initial-connections>10</initial-connections>
		<connections-increment>3</connections-increment>
	</connection-pool>
</dbparameters>
</component-instance>

</component>

In this example the same Component is used with different configurations (one for PostgreSQL and one for Oracle) and there are also different configurations for the connection pool. The first configuration will be used when an <xf:process using="postgres"> is found, while the second one will be instantiated (or, for that matter, taken from a Pool) when an <xf:process using="oracle"> is encountered. When the Processor intercepts the xf:process endElement event, the toSax() method of the ProcessElement is called. This method in turn sets the Component parameters and calls its toSax() method. The Component's SAX events are passed to his parent and the flow starts over until the recursion ends.

A typical SAX events flow for a simple stack is the following:

Diagramma

Which corresponds to this basic X:Forge example:

<page>
  <xf:process using="example">
    <xf:parameter name="firstname">firstvalue</xforge:parameter>
    <xf:parameter name="secondname">secondvalue</xforge:parameter>
  </xf:process>
</page>

A component example

This is a simple component that returns the current time with a default or a user provided format:

import java.util.Date;
import java.text.SimpleDateFormat;

import org.apache.avalon.Poolable;

import org.xml.sax.SAXException;

import org.bibop.xml.xforge.components.AbstractXForgeComponent;



public class XForgeTimeComponent extends AbstractXForgeComponent
  implements Poolable {

  public void toSax() throws SAXException {
    String paramValue = "yyyy-MM-dd";

    if( parameters.containsKey("format") )
      paramValue = (String) parameters.get( "format" );

    SimpleDateFormat sdf1 = new SimpleDateFormat(paramValue);
    String formattedDate = null;
    try {
      formattedDate = new String( sdf1.format(new Date()) );
    } catch( Exception ex) {
      throw new SAXException(
        "An error has occutred while formatting date : "
        + ex.getMessage()
        );
    }
    this.contentHandler.characters(
      formattedDate.toCharArray(),0,formattedDate.length()
      );
  }
}

The component is configured as follows:

<?xml version="1.0" encoding="UTF-8"?>
<xforge>
  <components>

    <component role="process" class="org.apache.avalon.excalibur.
						component.DefaultComponentSelector">

      <component-instance name="CurrentTime" class="org.bibop.xml.xforge.components.
						    examples.XForgeTimeComponent"
                          logger="mylogger"/>

    </componenet>

  </components>

<role-list>
 <role name="org.apache.avalon.framework.component.ComponentSelector"
       shorthand="process"
       default-class="org.apache.avalon.excalibur.component.DefaultComponentSelector"/>
</role-list>

</xforge>

Finally, this is the XML that has to be processed:

<root>
 <formatted-date>
  <xf:process using="CurrentTime">
   <xf:parameter name="format">
    yyyyy.MMMMM.dd GGG hh:mm aaa
   </xf:parameter>
  </xf:process>
 </formatted-date>
</root>

And this is the Output:

<root>
 <formatted-date>1996.July.10 AD 12:08 PM</formatted-date>
</root>

Mapped Components and Mappers

Why another level?

Sometimes it's hard to deal with complex logic which has already been written, not to mention that SAX programming can be challenging in some environments. Under many circumstances this problems can be solved by recycling existing logic via a generic Mapper that allows to reuse code that does not need to be rewritten.

As an example let's suppose a setup made of existing JavaBeans that pull data out of a database: we can use the X:Forge Reflection Mapper to peek at the code and run methods on top of it. As an example consider the following Java Bean:

package com.acme.beans;

public class MyBean
{
  private String name;
  private String address;

  public void setName(String name) {
    this.name = name;
  }

  public String getName() {
    return this.name;
  }

  public void setAddress(String address) {
    this.address = address;
  }

  public String getAddress() {
    return this.address;
  }

}
        

It is possible to use this bean directly in X:Forge by mapping it trough the X:Forge Reflection Mapper. To achieve this result it's enough to modify the X:Forge configuration file as follows:

<component-instance name="MyBean"
  class="org.bibop.xml.xforge.components.XForgeMappedComponent"
  logger="mylogger">
 <mapper>
  <class>
   org.bibop.xml.xforge.components.mappers.XForgeReflectionMapper
  </class>
  <configuration/>
 </mapper>
 <component>
  <class>com.acme.beans.MyBean</class>
 </component>
</component-instance>
        

From now on this bean can be used in X:Forge just by writing the following XML file:

<page xmlns:xf="http://xforge.org/xforge/3.0/">
 <xf:process using="MyBean">
  <xf:parameter name="setName">George W. Bush</xf:parameter>
  <xf:parameter name="setAddress">White House - USA</xf:parameter>
  <xf:parameter name="getName"/>
  <xf:parameter name="getAddress"/>
 </xf:process>
</page>

Which will turn out after processing in:

<page xmlns:xf="http://xforge.org/xforge/3.0/">
George Bush
White House - USA
</page>
      

Automated Components

Tag Library-like Syntax

Automated components are similar to mapped components in that they support arbitrary methods (not only toSax() like regular components) and that Reflection is used to map from XML to method calls. They are also similar to regular components in that they do not need to use a special mapper like mapped components do (the X:Forge Reflection Mapper). Finally, automated components have their own namespace prefix, which allows for a taglib-like syntax. In the X:Forge configuration file it is possible to map a namespace prefix to the component's class. Example:

<component-instance name="myprefix" class="org.myself.MyComponent" logger="mylogger">
	<myparameter>Some Value</myparameter>
	<anotherparameter>Some other Value</anotherparameter>
</component-instance>

The class MyComponent would look like this:

public class MyComponent extends AbstractAutomatedComponent {

  public void configure(Configuration config) throws ConfigurationException {
    super.configure(config);
    String myparameter = config.getChild("myparameter").getValue();
    String anotherparameter = config.getChild("anotherparameter").getValue("Default Value");
  }

public void myMethod(String waycool) throws SAXException {
  // do my stuff
  }

public void anotherMethod(String lesscool) throws SAXException {
  // do my other stuff
  }

}

Usage would be like this:

<page xmlns:xf="http://xforge.org/xforge/3.0/">
  <start xmlns:myprefix="http://my.namespace.uri">

    <myprefix:mymethod>
      <xf:parameter name="waycool">Something ...</xf:parameter>
    </myprefix:mymethod>

    <myprefix:anothermethod>
      <xf:parameter name="lesscool">Something else...</xf:parameter>
    </myprefix:anothermethod>

  </start>
</page>