From the command line, in the root directory of the servingxml download, build the distribution:
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
.
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.
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);
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. }
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.
%JAVA_HOME%\jre\lib\endorsed
.
Or, if you are using Tomcat, create the directory
%TOMCAT_HOME\common\endorsed
instead.
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).
xercesImpl.jar
, xml-apis.jar
and xmlParserAPIs.jar
,
and copy them to the endorsed directory as well.
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
MyLogger
that implements
com.servingxml.util.system.Logger
.
classpath
.
com.servingxml.util.system.Logger=com.servingxml.util.system.MyLoggerSystem properties may be set in the optional
config/servingxml.properties
file.
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(); } } } }