Serving XML: Inversion of Control Container

Daniel Parker


Configuration and Service Components
Creating a Custom Service Component
Creating an Extension Package
Creating a Custom FTP Sink Component

This is the fourth of four articles describing the ServingXML pipeline language.

This article looks at ServingXML as an "inversion of control" (IoC) container that allows us to assemble components from a variety of projects - the Apache FOP project, the Sun MSV project and others - and make them work together to process records and XML. The term "inversion" conveys the idea that the ServingXML container does not instantiate components directly, but rather supports an extensible component assembler module that allows user defined components to be injected into the container.

Configuration and Service Components

The ServingXML container supports two types of components:

  • configuration components
  • service components

The difference between the two is in how they are identified.

  • Configuration components are identified by object type and scope within an XML document.
  • Service components are identified by object type and instance name.

In Java code, we can see the difference in how we look up component instances in the IoC container. This is how we look up a configuration component, passing only an object type to the lookup method:


  XsltConfiguration config = (XsltConfiguration)resources.lookupConfigurationComponent(JaxpConfiguration.class);

This is how we look up a service component, passing both an object type and an instance name to the lookup method:


  Service service = (Service)resources.lookupServiceComponent(Service.class, "books");

The example below shows a resources script that declares some configuration and service components, in particular, the sx:xsltConfiguration configuration component and the sx:service service component named "books".


<sx:resources xmlns:sx="http://www.servingxml.com/core">

  <!-- A configuration component -->                  
  <sx:xsltConfiguration>           
    <sx:systemProperty name="javax.xml.transform.TransformerFactory" 
                       value="com.icl.saxon.TransformerFactoryImpl"/>
    <sx:outputProperty name="indent" value="no"/>
    <sx:outputProperty name="{http://icl.com/saxon}omit-meta-tag" value="yes"/>
  </sx:xsltConfiguration>

  <!-- A service component -->                 
  <sx:service id="books"> 
    <sx:serialize>
      <sx:transform>
        <sx:content ref="style1"/>
      </sx:transform>
    </sx:serialize>
  </sx:service>

  <sx:xslt id="style1"><sx:urlSource url="styles/catalog.xsl"/></sx:xslt>
  <sx:xslt id="style2"><sx:urlSource url="styles/brochure.xsl"/></sx:xslt>
                        
   ...

The components declared in a resources script are assembled by the IoC container, using assemblers that are defined in a components file. The assemblers for the components shown above are defined as follows.


<ioc:components xmlns:ioc="http://www.servingxml.com/ioc"
               xmlns:sx="http://www.servingxml.com/core">

  <!--  Configuration Components -->
  <ioc:configurationComponent name="sx:xsltConfiguration"
    assemblerClass="com.servingxml.components.xsltconfig.XsltConfigurationAssembler"/>

  <!--  Abstract Components -->
  <ioc:abstractComponent name="sx:content" 
    interface="com.servingxml.app.xmlpipeline.Content"/>
  <ioc:abstractComponent name="sx:stringable" 
    interface="com.servingxml.components.string.StringFactory"/>
  <ioc:abstractComponent name="sx:task" 
    interface="com.servingxml.components.task.Task"/>

  <!--  Service Components -->
  <ioc:serviceComponent name="sx:outputProperty" 
    assemblerClass="com.servingxml.components.property.OutputPropertyFactoryAssembler"/>
  <ioc:serviceComponent name="sx:serialize" base="sx:task sx:stringable"
    assemblerClass="com.servingxml.components.transform.SerializedContentAssembler"/>
  <ioc:serviceComponent name="sx:xslt" base="sx:content"
    assemblerClass="com.servingxml.components.xslt.XsltFilterFactoryAssembler"/>
  <ioc:serviceComponent name="sx:systemProperty" 
    assemblerClass="com.servingxml.components.property.SystemPropertyAssembler"/>
  <ioc:serviceComponent name="sx:transform" 
    interface="com.servingxml.app.xmlpipeline.XmlPipelineFactory"
    assemblerClass="com.servingxml.components.transform.TransformedContentAssembler"/>

  ...  
                        

The code fragment below shows the injectComponent methods on the serialize assembler that receive configuration and service component instances. It is the IoC container's responsibility to call these methods.


public class SerializedContentAssembler ...

  public void injectComponent(XsltConfiguration jaxpConfig) {
    this.jaxpConfig = jaxpConfig;
  }

  public void injectComponent(XmlFilterAppender pipelineFactory) {
    this.pipelineFactory = pipelineFactory;
  }

Each assembler has a method assemble that the IoC container, after it calls the injectXXX methods, calls to instantiate the component.


public class SerializedContentAssembler ...

  public Task assemble(ConfigurationContext context) 
   { ...

    //  Instantiate and return task using the configuration and service
    //  component instances injected into the component.


Creating a Custom Service Component

Creating an Extension Package

The ServingXML distribution includes a template extension package servingxmlx under the extensions directory. In the servingxmlx directory you will find the following files and directories.

  • build.properties - This is where the extension jar file is named.
  • build.xml - Ant build file (called by the main build.)
  • build - Empty directory.
  • lib - Put your extension specific jar files here.
  • samples - Samples go here.
  • src/java/servingxmlx - Component source code goes here.
  • src/resources/components.xml - Register your extension components here.

You can place any extension to the system in the servingxmlx package. Alternatively, you can create a custom package name, using the servingxmlx package as a template, and place your extension in your own package. If you create a custom package name, you will also need to go to the root directory of the servingxml download and add an entry to the build-extensions.xml file.

The components supplied by an extension are registered in the extension's components.xml file. This file is included in the component's jar file. An entry in the jar's manifest named "ServingXML-Components" tells the ServingXML implementation where to find the components.

To build your package and make it part of the ServingXML distribution

  • Edit the root-level build-extensions.xml file to include your extension in the main build.
  • Build the distribution from the command line in the root directory of the servingxml download.

Creating a Custom FTP Sink Component

In the example below, we show a resources script that transforms a document with a style sheet and writes the result to the local file output.xml.


<sx:resources xmlns:sx="http://www.servingxml.com/core">

  <sx:service id="myService">
    <sx:transform>
      <sx:document><sx:urlSource url="document.xml"/></sx:document>
      <sx:xslt><sx:urlSource url="style.xsl"/></sx:xslt>
      <sx:xsltSerializer>
        <sx:fileSink file="output.xml"/>
      </sx:xsltSerializer>
    </sx:transform>
  </sx:service>
  
</sx:resources>

Now, suppose that instead of writing the output to a local file, we want to send it to a remote host using our favourite third party FTP tool. In particular, we want to define a new element, say ftpSink, that we can use anywhere in a resources script where an element of the sx:streamSink family is currently allowed. We want to write something like this.


<sx:resources xmlns:sx="http://www.servingxml.com/core"
              xmlns:sxx="servingxmlx"> ...
              
  <sx:service id="service">
    <sx:serialize>
      <sx:xsltSerializer>
        <sxx:ftpSink remoteFile="output.xml">
          <sxx:ftpClient host="xxx" user="xxx" password="xxx"/>
        </sxx:ftpSink>
      <sx:xsltSerializer>  

Note that we've put our proposed new elements ftpSink and ftpClient in a separate namespace, to distinguish them from the built-in ServingXML elements.

We'll start by examining the framework components.xml file, which is where the sx:fileSink element is registered. We see that it has an assembler class that creates StreamSinkFactory instances.


<ioc:components xmlns:ioc="http://www.servingxml.com/ioc"
               xmlns:sx="http://www.servingxml.com/core"> ...

  <ioc:abstractComponent name="sx:streamSink" 
    interface="com.servingxml.components.streamsink.StreamSinkFactory"/>
  
  <ioc:serviceComponent name="sx:fileSink" base="sx:streamSink"
    assemblerClass="com.servingxml.components.streamsink.file.FileSinkFactoryAssembler"/>
      

The StreamSinkFactory interface is defined as follows.


public interface StreamSinkFactory {
  StreamSink createStreamSink(ServiceContext context,  Flow flow) 
  ;
}


public interface StreamSink {
  
  OutputStream openStream() throws IOException;
  
  void closeStream(OutputStream os) throws IOException; 
}

We'll need StreamSinkFactory and StreamSink implementations. Ultimatelty, the framework will call openStream on a StreamSink object, and we'll want the content that is written to the output stream to be sent to a file on a remote host. There are different ways of implementing this, for example, we could stream the content to the remote file as it is written, or alternatively we could stream it to a local file, and then send the local file to the remote host when the framework calls closeStream.

We'll need to register our new elements with the ServingXML container, which we'll do by adding some entries to the local components.xml file for the servingxmlx extension.

<ioc:components xmlns:ioc="http://www.servingxml.com/ioc"
               xmlns:sx="http://www.servingxml.com/core"
               xmlns:sxx="servingxmlx">...
               
  <ioc:serviceComponent name="sxx:ftpSink" base="sx:streamSink"
    assemblerClass="servingxmlx.components.streamsink.edtftpj.FtpSinkFactoryAssembler"/>
    
  <ioc:serviceComponent name="sxx:ftpClient" 
    interface="servingxmlx.components.streamsink.FtpClient"
    assemblerClass="servingxmlx.components.connect.edtftpj.FtpClientAssembler"/>


We need an assembler class for building a StreamSinkFactory.


public class FtpSinkFactoryAssembler ...

  public StreamSinkFactory assemble(ConfigurationContext context)
   {
    
    return new FtpSinkFactory(ftpClient,remoteFile);
  }
}

We define setter methods to allow the ServingXML container to inject the attribute values of the edt:ftpSink element into the builder object.


public class FtpSinkFactoryAssembler ...
  
  public void setRemoteFile(String remoteFile) {
    this.remoteFile = remoteFile;
  }
  

We define an injectComponent method to allow the ServingXML container to inject an FtpClient object into the builder object.


public class FtpSinkFactoryAssembler ...

  public void injectComponent(FtpClient ftpClient) {
    this.ftpClient = ftpClient;
  }

We need another builder for the ftpClient. This builder class only requires setter methods.


public class FtpClientAssembler ...
  
  private int port = 21;
                    
  public void setHost(String host) {
    this.host = host;
  }

  public void setPort(int port) {
    this.port = port;
  }
  
  public void setUser(String user) {
    this.user = user;
  }
  
  public void setPassword(String password) {
    this.password = password;
  }

  public FtpClient assemble(ConfigurationContext context)
   {
    
    return new FtpClient(host,port,user,password);
  }
}

We can now rewite the resources script as proposed above, or alternatively, we can rewirte it using references like this.


<sx:resources xmlns:sx="http://www.servingxml.com/core"
              xmlns:sxx="servingxmlx">

  <sxx:ftpClient name="myFtpClient" host="xxx" user="xxx" password="xxx"/>
              
  <sx:service id="service">
    <sx:serialize>
      <sx:xsltSerializer>
        <sx:streamSink ref="myFtpSink"/>
      </sx:xsltSerializer>  
      <sx:transform>
        <sx:document><sx:urlSource url="document.xml"/></sx:document>
        <sx:xslt><sx:urlSource url="style.xsl"/></sx:xslt>
      </sx:transform>
    </sx:serialize>
  </sx:service>
  
  <sxx:ftpSink name="myFtpSink" remoteFile="output.xml">
    <sxx:ftpClient ref="myFtpClient"/>
  </sxx:ftpSink>

  
</sx:resources>

Note that the ServingXML container is responsible for resolving dependencies, so we don't need to worry about the precise order of the sxx:ftpSink and sxx:ftpClient elements, or references to them, in the script.

If we wanted to access the objects we've just defined in Java code, we could do so as follows.



Name myFtpClient = new QualifiedName("myFtpClient");
Name myFtpSink = new QualifiedName("myFtpSink");

IocContainer resources = appContext.getResources();
                                          
FtpClient ftpClient = (FtpClient)resources.lookupResource(FtpClient.class,myFtpClient);

StreamSinkFactory streamSinkFactory = (StreamSinkFactory)resources.lookupResource(
    StreamSinkFactory.class,myFtpSink);