Processors - Java

Rationale

Orbeon Forms comes with a number of pre-built processors. However, in some cases, it makes sense for the developer to write a new processor in Java. For the purpose of this discussion, let's assume that the processor is implemented in a file called MyProcessor.java. Custom processors can essentially be deployed in two ways:

  • "Manually":
    1. Compile MyProcessor.java.

    2. Place the compiled class in WEB-INF/classes (or in any other location where it can be found by the classloader used to load the classes in orbeon.jar).

    3. Declare the new processor in /config/processors-local.xml (i.e. mapping an URI to this new processor). The syntax of /config/processors-local.xml is as described here.

    4. Use the newly declared processor in an XPL (Orbeon Forms Pipeline Definition Language) file using the URI declared in the processors.xml.


  • Using the Java processor:
    1. Place MyProcessor.java with the other resources.

    2. Use the new processor in an XPL file through the Java processor. (We'll see below how this processor works in detail.)

Benefits and drawbacks

The main advantages of using the Java processor versus manually compiling and deploying the compiled processor are:

  • Easy deployment: the Java file is placed with the other resources and one does not need to worry about compilation, packaging and deployment.
  • Immediate visibility upon modifications: one can change the Java file, save it and instantly see the result in the browser (no need to compile, redeploy the application).

However, one should also note the drawbacks that come with the Java processor. In particular, the XML code needed in the XPL file to call a custom processor using the Java processor is a bit more complex than the XML code used to call a processor declared in processors.xml.

Usage

<p:processor xmlns:p="http://www.orbeon.com/oxf/pipeline" name="oxf:java">
<p:input name="config">
<config sourcepath="oxf:/java" class="MyProcessor"/>
</p:input>
<p:input name="data" href="..."/>
<p:output name="data" id="..."/>
</p:processor>
config input The config element has two attributes:
  • The optional sourcepath attribute points to the directory containing the Java source in the resources. When omitted, the default sourcepath is the directory of the pipeline calling the Java processor (i.e. sourcepath="."). It is possible to use the file: protocol and the oxf: protocol. It is also possible to enter URLs relative to the location of the calling pipeline, such as sourcepath="." (the default), sourcepath="../examples/java", etc.
  • class is the name of the Java class. The class has to implement the org.orbeon.oxf.processor.Processor interface, as described in the Processors API.

Let's assume you place your Java source files in your resources directory under the java subdirectory, and that the class you want to use is MyProcessor, in the com.example package. Consequently, you will have a file java/com/example/MyProcessor.java in your resources. To use this class, the Java processor config is: <config sourcepath="oxf:/java" class="com.example.MyProcessor"/>.

Other inputs and outputs The processor implemented in Java can take an arbitrary number of inputs and outputs. The only restriction on the inputs/outputs is that no input can be named config as this input is already used to configure the Java processor.

Compilation

Before it can run a custom processor, the Java processor must compile the source code to generate the class files from the java files, and load those class files in the Java VM.

By default, the Java processor uses Sun's compiler (com.sun.tools.javac.Main) to produce class files. See the compiler-class and compiler-jar properties for more information about specifying the compiler to use.

The class files are stored in the temporary directory, as defined by the Java system property java.io.tmpdir. Since compilation is a time consuming process, it is performed only when necessary. The Java processor compiles a custom processor when one of these conditions is met:

  • The source of the custom processor has never been compiled before.
  • The last modified date of the source file for the custom processor is prior to the last modified date of the corresponding class file.

Note that Sun's javac automatically compiles all the files that the custom processor depends on, but that the Java processor only runs javac by comparing the dates of the .java and class of the custom processor itself. So if only one of the classes used by the custom processor has changed since the last compilation, the Java processor will not run the compiler. You should be aware of this limitation and "touch" the source of the custom processor when such a case occurs to force a compilation.

Compilation class path

Before invoking the Java compiler, Orbeon Forms builds a classpath using two properties. The following list summaries the complete classpath order.

  1. The class path defined by the classpath property.
  2. The WEB-INF/classes directory, if found and Presentation Server runs in an application server.
  3. The JAR and ZIP files used by the classloader hierarchy that loaded the Java processor. This automatically puts on the compilation class path the classes that the Java processor can use.
  4. The JAR path defined by the jarpath property.
  5. All the JAR and ZIP files under the WEB-INF/lib directory, if found and Orbeon Forms runs in an application server.
  6. If the WEB-INF/lib directory is not found, all the JAR and ZIP files in the same directory as the JAR file containing the Java processor. Typically, when running from a command line, this is the orbeon.jar JAR file. If orbeon.jar is stored in a directory with all the JAR files it depends on, those will automatically be added to the compilation class path.

Runtime class loading

The compiled files are loaded by a class loader created by the Java processor. A different class loader is created for each source path, and all the classes in the same source path are loaded in the same class loader. For a given source path, a new class loader is created if one of these conditions is met:

  • No class loader has been previously created for this source path.
  • One of the classes in this source path has been compiled since the class loader has been created.

When a new class loader is created, the previous one, if it exists, is discarded with all the loaded classes, and all the classes are re-loaded in the new class loader.

Limitations

  • A custom processor used with the Java processor cannot use its config input, as this input is used to configure the Java processor.
  • Java source files must be stored on the file system, i.e. the resources can only be loaded with the Filesystem or Web App resource managers. This is due to a limitation of Sun's javac which can only compile source files stored on disk.
  • The Java processor will recompile a custom processor only if the Java source of the processor itself has changed. If only one of the classes (that the custom processor depends on) has changed since the last compilation, the source of the customer processor must be "touched" to force a re-compilation.

Properties

Several global properties are relevant to the Java processor. Refer to the Properties section for more information.

Example

The processor below declares a single output data and no inputs. It will always send the same XML content to its data output, namely an answer element containing the text "42". For more details on how to implement processors in Java, please refer to the Processors API.

import org.orbeon.oxf.pipeline.api.PipelineContext;
import org.orbeon.oxf.processor.ProcessorInputOutputInfo;
import org.orbeon.oxf.processor.SimpleProcessor;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;

public class DeepThoughtProcessor extends SimpleProcessor {

    public DeepThoughtProcessor() {
        addOutputInfo(new ProcessorInputOutputInfo(OUTPUT_DATA));
    }

    public void generateData(PipelineContext context,
                             ContentHandler contentHandler)
            throws SAXException {
        String answer = "42";
        contentHandler.startDocument();
        contentHandler.startElement("", "answer", "answer", new AttributesImpl());
        contentHandler.characters(answer.toCharArray(), 0, answer.length());
        contentHandler.endElement("", "answer", "answer");
        contentHandler.endDocument();
    }
}

Configuration properties

Class path

Name classpath
Purpose Defines a directory where Java class files are located. The Java processor dynamically compiles Java code, and may need some libraries. This property defines the classpath used by the compiler.
Processor name oxf:java
Type xs:string
Default Value None

JAR path

Name jarpath
Purpose Defines a list of directories where JAR files are located. The Java processor dynamically compiles Java code, and may need some libraries. This property defines a "JAR path", a list of directories containing JAR files that will be added to the classpath when compiling and running the processor executed by the Java processor.
Processor name oxf:java
Type xs:string
Default Value None

Compiler JAR

Name compiler-jar
Purpose Define a URL pointing to a JAR file containing the Java compiler to use. If this property is set, the Java processor adds the specified JAR file to the class path used to search for the main compiler class.
Processor name oxf:java
Type xs:anyURI
Default Value If the property is not specified, the Java processor tries to load the main compiler class first using the current class loader. If this fails, it retrieves the java.home system property which specifies a directory on disk. If that directory is called jre, and there exists a JAR file relative to that directory under ../lib/tools.jar, that JAR file is added to the class path used to search for the main compiler class. This covers most cases where the standard Sun JDK is used, so that the compiler-jar property does not have to be specified.

Compiler main class

Name compiler-class
Purpose Define a class name containing the Java compiler to use. The Java processor loads the corresponding class and calls a static method on this class with the following signature: public static int compile(String[] commandLine, PrintWriter printWriter).
Processor name oxf:java
Type xs:string
Default Value com.sun.tools.javac.Main
Comments