# Serving XML: Embedding

### How to embed ServingXML in a Java application

## 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();
          }
        }
      }
    }