Java Processor
1. Rationale
PresentationServer 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":
- Compile MyProcessor.java.
- 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).
- Declare the new processor in
processors.xml (i.e. mapping an URI to this new
processor).
- Use the newly declared processor in an XPL (PresentationServer Pipeline Definition Language) file using the URI
declared in the processors.xml.
- Using the Java processor:
- Place MyProcessor.java with the other
resources.
- Use the new processor in an XPL file through the Java
processor. (We'll see below how this processor works in
detail.)
2. 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.
3. Usage

<p:processor name="oxf:java" xmlns:p="
http://www.orbeon.com/oxf/pipeline"
> <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.
|
4. 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.
5. Compilation Class Path
Before invoking the Java compiler, PresentationServer builds a classpath using two
properties. The following
list summaries the complete classpath order.
-
The class path defined by the classpath
property.
-
The WEB-INF/classes directory, if found and Presentation
Server runs in an application server.
-
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.
-
The JAR path defined by the jarpath
property.
-
All the JAR and ZIP files under the WEB-INF/lib directory, if
found and PresentationServer runs in an application server.
-
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.
6. 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.
7. 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 Flat File or Web app
resource
managers. This is due to a limitation of Sun's
javac that 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.
8. Properties
Several global properties are relevant to the Java processor. Refer to the Properties section for more information.
9. 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();
}
}