Serving XML: Embedding

How to embed ServingXML in a Java application


Building the ServingXML distribution
Java API
Start up
Service Invocation
Shut down
Class Path Configuration
Logging Configuration
Advanced Topics
Importing ServingXML components from Java code

Building the ServingXML distribution

From the command line, in the root directory of the servingxml download, build the distribution:

  • [win32] build
  • [unix] ./build.sh

The build command will create a deployment package in the subdirectory target/servingxml. Here, you will find the following file and directories.

  • servingxml.jar - main ServingXML jar file
  • config - directory of configuration files (optional)
  • lib - all jar files required by the servingxml framework and included extensions
  • resources/META-INF/components - directory of components.xml files
  • classes - a place to put java .class files where the command line app can find them

You can reduce the number of jar files that the build command places in the target lib directory by suppressing unneeded extensions in the build-extensions.xml file. Just edit the file and comment out unneeded extensions before performing the build. For flat file conversion applications, the only extensions likely to be of interest are the flatfile extension itself, and perhaps the msv extension (for XML schema validation.) A minimal configuration should require only servingxml.jar and concurrent.jar.

Java API

The code fragments below show how to embed ServingXML in a Java application. For an example, have a look at the class com.servingxml.app.consoleapp.ConsoleApp in the source distribution.

In a server app, a servlet for example, the IocContainerFactory and the AppContext objects, which manage access to resources, are intended to be held as state, and should be thread safe.

On each request, a service context is created with a user id, a default StreamSource, and a default StreamSink. The service context is intended to have a short life in a single thread.

The embedding app is responsible for supplying a default StreamSource and a default StreamSink. You have a number of options.

  • If your input and output take the form of files or system resources, as they do in the console app, you can use a FileSource or UrlSource object for input, and a FileSource object for output.

  • You can adapt a Java InputStream to a source with an InputStreamSourceAdaptor, and an OutputStream to a sink with an OutputStreamSinkAdaptor. In this case the embedding application is responsible for closing the InputStream and the OutputStream.

  • If an incoming "flat file" is arriving as a parameter on an HTTP request, say, you can use a com.servingxml.io.streamsource.StringStreamSource.

  • In a JBI adaptor, the incoming data will be a javax.xml.transform.Source object. You can use the helper class com.servingxml.io.helpers.StringStreamHelper to create an effcient ServingXML StreamSource object from the JAXP Source object, with the static method fromJaxpSource.

  • Or you can provide your own implementations of StreamSource and StreamSink. Refer to the examples in the com.servingxml.io.streamsource and the com.servingxml.io.streamsink packages.

Start up


  import com.servingxml.app.AppContext;
  import com.servingxml.ioc.resources.IocContainer;
  import com.servingxml.ioc.resources.IocContainerFactory;
  import com.servingxml.util.ServingXmlException;

    IocContainerFactory iocContainerFactory = new IocContainerFactory();
    try {
      
      iocContainerFactory.loadComponentDefinitions();

      //  For a J2EE app, use one of the variants of loadComponentDefinitions that takes
      //  an array of components.xml file locations.
    } catch (ServingXmlException e) {
      //  Handle exception
    }
     
    String appName = "my-app";                      

    ParameterBuilder paramBuilder = new ParameterBuilder();
    //  Add some parameters, if any  
    Record parameters = paramBuilder.toRecord();
                                        
    IocContainer resources = iocContainerFactory.createIocContainer(configUrl, parameters);
    resources = iocContainerFactory.createIocContainer(resourcesUrl, parameters, resources);
    
    AppContext appContext = new DefaultAppContext(appName, resources);

The call to

  iocContainerFactory.loadComponentDefinitions();
 

results in a search of the accessible class loaders to resolve the location of ServingXML jar files, and an attempt to read component definition files located in these jar files. This will not work inside a J2EE container, which isolates class loaders. To get around this, use one of the variant loadComponentDefinitions methods that take an array of components.xml file locations.

The files you need to include are the ones in the target/servingxml/resources/META-INF/components directory. You can supply them either as an array of URL objects, or as an array of pathnames that resolve relative to the classpath. For example, if the classpath includes the entry ./resources/META-INF/components/, and you're interested in the core and msv components, you can supply them like this.


  IocContainerFactory appDriver = new IocContainerFactory();
  String[] componentsFiles = new String[]{"com/servingxml/core/components.xml", "com/servingxml/extensions/msv/components.xml"};
  appDriver.loadComponentDefinitions(componentsFiles);

Service Invocation

To obtain a service object, use getResource().lookupServiceComponent(...) on the app context, passing a resource type of Service.class and a service uri. To execute the service, call the execute method with the service context and the parameters.


  import com.servingxml.app.DefaultServiceContext;
  import com.servingxml.app.Flow;
  import com.servingxml.app.FlowImpl;
  import com.servingxml.app.ServiceContext;
  import com.servingxml.io.streamsink.OutputStreamSinkAdaptor;
  import com.servingxml.io.streamsink.StreamSink;
  import com.servingxml.io.streamsource.file.FileSource;
  import com.servingxml.io.streamsource.StreamSource;
  import com.servingxml.util.Name;
  import com.servingxml.util.record.ParameterBuilder;
  import com.servingxml.util.record.Record;

    try {
      String userName = "my-user";                                                         
      ServiceContext serviceContext = new DefaultServiceContext(appContext, userName);

      //  Create a default StreamSource, a default SaxSource, and a default StreamSink
      StreamSource defaultStreamSource = new FileSource(myFile);
      StreamSink defaultSink = new OutputStreamSinkAdaptor(myOutputStream);
      
      ParameterBuilder paramBuilder = new ParameterBuilder();
      //  Add some parameters, if any  
      Record parameters = paramBuilder.toRecord();

      Flow flow =  new FlowImpl(parameters, defaultStreamSource, defaultStreamSink);
        
      String serviceUri = "myService";                                                                                                          
      Service service = (Service)context.getResources().lookupServiceComponent(Service.class,serviceUri);
      if (service == null) {
        throw new ServingXmlException("Cannot find service " + serviceUri);
      }
      service.execute(context, flow);
    } catch (ServingXmlException e) {
      //  Handle exception
    }  finally {
      //  Close any resources created, including input streams or output streams.
    }

Shut down



Class Path Configuration

The target/servingxml/servingxml.jar jar file and the jar files under the target/servingxml/lib directory need to be in the classpath of the Java app. You can optionally include the configuration file target/servingxml/config/servingxml.xml in the classpath.

ServingXML requires implementations of the J2SE endorsed standards for JAXP parsers and transformers. Java 1.5 includes bundled versions of Xerces and Xalan, but unfortunately they are old ones, and the version of Xalan has bugs that make it unusable with Serving XML.

ServingXML is distributed with Saxon-B 9, which provides a very good implementation of the transformer API's. The console app includes the Saxon jar file in its class path, and the configuration file servingxml.xml sets Saxon to be the default transformer.

In a Java application, you may wish to pick up all the jar files implementing the endorsed standards from the standard endorsed locations, and not have Saxon in the class path. You will then need to comment out the Saxon setting in the servingxml.xml file, and update the endorsed directories with more current implementations of the JAXP transformer API, proceeding as follows.

  • Create the directory %JAVA_HOME%\jre\lib\endorsed. Or, if you are using Tomcat, create the directory %TOMCAT_HOME\common\endorsed instead.
  • Add your preferred XSLT transformer jar file to the endorsed directory. ServingXML 0.9.1 has been tested with Saxon-B 9.1 (included in the distribution.) Various versions have been tested with Saxon 6.5.5 (requires saxon.jar) and Xalan 1.2.6 (requires xalan.jar).
  • You may also want to download new versions of the Xerces jar files, including xercesImpl.jar, xml-apis.jar and xmlParserAPIs.jar, and copy them to the endorsed directory as well.

Logging Configuration

By default, ServingXML supports JDK 1.4 logging. The default logger is named com.servingxml.util.system.DefaultLogger. You can configure it by editing your <JRE Home>/lib/logging.properties file, and changing the property values for the handler you want to deliver the log record.

If you want the application to write log messages using a different logger, perhaps log4j, you will need to

  • Write an adaptor class MyLogger that implements com.servingxml.util.system.Logger.
  • Compile the class and add it somewhere in the classpath.
  • Set the system property
    com.servingxml.util.system.Logger=com.servingxml.util.system.MyLogger
    
    System properties may be set in the optional config/servingxml.properties file.

Advanced Topics

Importing ServingXML components from Java code

Consider the resources script shown below. Note that in the definition of the "books" content, there is a reference to a record reader identified by "myBooksReader", but there is no definition of this reader in the script itself. Instead, the definition is supplied by the application.


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

  <sx:service id="books"> 
    <sx:serialize>
      <sx:transform>
        <sx:content ref="books"/>
      </sx:transform>
    </sx:serialize>
  </sx:service>

  <sx:recordContent id="books">
    <sx:recordReader ref="myBooksReader"/> 
    <sx:recordMapping ref="booksToXmlMapping"/>
  </sx:recordContent>
                                                       
  <sx:recordMapping id="booksToXmlMapping">
    <myns:books xmlns:myns="http://mycompany.com/mynames/" 
                     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                     xsi:schemaLocation="url2">
      <sx:onRecord>
        <myns:book>
          <sx:fieldAttributeMap field="category" attribute="categoryCode"/>
          <sx:fieldElementMap field="title" element="myns:title"/>  
          <sx:fieldElementMap field="author" element="myns:author"/>
          <sx:fieldElementMap field="price" element="myns:price"/>
        </myns:book>  
      </sx:onRecord>
    </myns:books>
  </sx:recordMapping>  

</sx:resources>

The code below defines a record reader component and registers it with the ServingXML framework with the identifier "myBooksReader". The source for this example may be found in the distribution under the samples/embed directory.


import java.io.File;
import java.net.URL;
import java.util.Iterator;

import com.servingxml.app.AppContext;
import com.servingxml.app.Application;
import com.servingxml.app.DefaultAppContext;
import com.servingxml.app.DefaultServiceContext;
import com.servingxml.app.Flow;
import com.servingxml.app.FlowImpl;
import com.servingxml.app.Service;
import com.servingxml.app.ServiceContext;
import com.servingxml.components.recordio.AbstractRecordReader;
import com.servingxml.components.recordio.AbstractRecordReaderFactory;
import com.servingxml.components.recordio.RecordReader;
import com.servingxml.components.recordio.RecordReaderFactory;
import com.servingxml.components.recordio.RecordWriter;
import com.servingxml.components.recordio.RecordWriterFilterAdaptor;
import com.servingxml.io.streamsink.file.FileSink;
import com.servingxml.io.streamsink.OutputStreamSinkAdaptor;
import com.servingxml.io.streamsink.StreamSink;
import com.servingxml.io.streamsource.file.FileSource;
import com.servingxml.io.streamsource.StreamSource;
import com.servingxml.ioc.resources.IocContainer;
import com.servingxml.ioc.resources.SimpleIocContainer;
import com.servingxml.util.CommandLine;
import com.servingxml.util.Name;
import com.servingxml.util.QualifiedName;
import com.servingxml.util.record.ParameterBuilder;
import com.servingxml.util.record.Record;
import com.servingxml.util.record.RecordBuilder;
import com.servingxml.util.ServingXmlException;
import com.servingxml.util.system.SystemConfiguration;

/**
 *
 * 
 * @author Daniel A. Parker (daniel.parker@servingxml.com)
 */

public class SampleRecordReaderApp {

  private static final String[] columnNames = {"category", "author", "title", "price"};

  private static final String[][] data = {
    {"F", "Charles Bukowski", "Factotum", "22.95"},
    {"F", "Sergei Lukyanenko", "The Night Watch", "17.99"},
    {"F", "Andrew Crumey", "Mr Mee", "22.00"},
    {"C", "Steven John Metsker", "Building Parsers with Java", "39.95"},
  };

  public SampleRecordReaderApp() {
  }

  public static void main(String[] args) {

    IocContainerFactory iocContainerFactory = new IocContainerFactory();
    File file = new File("output/books.xml");
    StreamSink defaultStreamSink = new FileSink(file);
    String serviceUri = "books";
    String configHref = "";
    String resourcesHref = "resources-books.xml";

    try {

      //  Locate configuration script servingxml.xml 
      //  in the classpath
      ClassLoader parentLoader = Thread.currentThread().getContextClassLoader();
      URL configUrl = parentLoader.getResource("servingxml.xml");
      configHref = configUrl.toString();

      try {
        iocContainerFactory.loadComponentDefinitions();
      } catch (ServingXmlException e) {
        SystemConfiguration.getSystemContext().error(e.getMessage());
        return;
      } catch (Exception e) {
        SystemConfiguration.getSystemContext().error(e.getMessage());
        e.printStackTrace(System.err);
        return;
      }
      Name myRecordReaderId = new QualifiedName("myBooksReader");

      IocContainer configuration = iocContainerFactory.createIocContainer(configHref);
      SimpleIocContainer myResources = new SimpleIocContainer(configuration);
      myResources.registerServiceComponent(
        RecordReaderFactory.class,
        myRecordReaderId.toUri(), new MyRecordReaderFactory(columnNames, data));
      IocContainer resources = iocContainerFactory.createIocContainer(resourcesHref, myResources);

      AppContext appContext = new DefaultAppContext("servingxml", resources);
      final ServiceContext serviceContext = new DefaultServiceContext(appContext, "servingxml");

      // Create parameters, if any
      final ParameterBuilder paramBuilder = new ParameterBuilder();
      Record parameters = paramBuilder.toRecord();

      Flow flow = new FlowImpl(parameters, StreamSource.NULL, defaultStreamSink);

      try {
        // Invoke the service
        Service service = (Service)appContext.getResources().lookupServiceComponent(Service.class,serviceUri);
        if (service == null) {
          throw new ServingXmlException("Cannot find service " + serviceUri);
        }
        service.execute(serviceContext, flow);
      } catch (ServingXmlException e) {
        serviceContext.error(e.getMessage());
        return;
      } catch (Exception e) {
        serviceContext.error(e.getMessage());
        e.printStackTrace(System.err);
        return;
      }

    } catch (Exception e) {
      SystemConfiguration.getSystemContext().error(e.getMessage());
      e.printStackTrace(System.err);
      return;
    } finally {
    }
  }

  static class MyRecordReaderFactory extends AbstractRecordReaderFactory {
    private final String[] columnNames;
    private final String[][] data;

    public MyRecordReaderFactory(String[] columnNames, String[][] data) {
      this.columnNames = columnNames;
      this.data = data;
    }

    protected RecordReader createRecordReader(ServiceContext context, Flow flow) 
     {
      return new MyRecordReader(columnNames, data);
    }
  }

  static class MyRecordReader extends AbstractRecordReader {
    private static final Name BOOK_RECORD_TYPE = new QualifiedName("book");

    private final String[] columnNames;
    private final String[][] data;

    public MyRecordReader(String[] columnNames, String[][] data) {
      this.columnNames = columnNames;
      this.data = data;
    }

    public void readRecords(ServiceContext context, Flow flow) 
     {
      RecordWriterFilterAdaptor writer = (RecordWriterFilterAdaptor)getRecordWriter();

      Name[] fieldNames = new Name[columnNames.length];
      for (int i = 0; i < fieldNames.length; ++i) {
        fieldNames[i] = new QualifiedName(columnNames[i]);
      }

      RecordBuilder recordBuilder = new RecordBuilder(BOOK_RECORD_TYPE);

      try {
        startRecordStream(context, flow);
        for (int i = 0; i < data.length; ++i) {
          for (int j = 0; j < fieldNames.length; ++j) {
            recordBuilder.setString(fieldNames[j],data[i][j]);
          }
          Record record = recordBuilder.toRecord();
          Flow newFlow = flow.replaceRecord(context, record);
          writeRecord(context, newFlow);
          recordBuilder.clear();
        }
        endRecordStream(context, flow);
      } finally {
        close();
      }
    }
  }
}