The CommandMachine pattern and JenaConfiguredCommandMachine
The Peruser supports complex behavior description and execution using a variation on a well known software pattern called the Command Pattern.
Our CommandMachine class expects to process Command objects which are submitted to it.
A CommandMachine has identity, and it may have state which changes over time.
When a Command is processed by a CommandMachine:
- An input Doc (an XML document) is consumed.
- An output Doc (an XML document) is produced.
- The state of the CommandMachine itself may be changed by the processing of the Command.
- The CommandMachine may need to access some external computer resources (files, databases, web services).
Each module of the Peruser may define Commands which may, in theory, be executed by any CommandMachine.
Peruser Command Ontology
Commands are configured using RDF ontology instances.
The command ontology is defined by the file PERUSER_SRC/app/_library/rdf/command_ontology.owl
Normally, you will define a set of command configurations by creating an RDF model that imports the command_ontology. Importing makes it easier to populate your command frames using a tool like Protege or TopBraid.
However, you may also create a compliant set of commands without importing the command_ontology, as long as you use the correct class and property URIs to define your statements.
Using JenaConfiguredCommandMachine to implement richly configurable processing
Currently, the most practical way to execute Commands is to use a class called JenaConfiguredCommandMachine , which is part of our Jena binding. This machine may be invoked from the PeruserTransformer, but may also be used outside of Cocoon, e.g. in a standalone application or a simple unit test.
This machine is much more flexible, and also more complex than the other machines we have discussed so far.
The machine itself is configured (before any commands are processed) using a Jena RDF model, accessed via the Jena Assembler model access framework. This framework allows models to be fetched from databases, to have inference applied, etc. So, there is a lot of flexibility in the machine configuration at this stage.
A typical configuration of a PT-type for use with the JCCM looks like this:
<map:transformer name="picky" ogger="net.peruser" src="net.peruser.binding.cocoon.PeruserTransformer"> <pm:machine> <pm:cuteName>picky_machine</pm:cuteName> <pm:class>net.peruser.binding.jena.JenaConfiguredCommandMachine</pm:class> <!-- The assembly URI is matched against the peruser boot model --> <pp:assemblyURI>http://www.peruser.net/2007/punit_test#picky_command_model</pp:assemblyURI> </pm:machine> </map:transformer>
Note that this is very simlar to the configuration of the "simple" machine
above, but the classname is different, and a different machine-specific
parameter has been passed in. Our parameter is assemblyURI
(defined in the "pp" namespace, which refers to the URI "peruser:prop/". See
Peruser Namespace URIs and Abbreviations).
This assemblyURI is used as the address of a thing (i.e. the URI of a RDF
resource) known in the peruser boot model, which is usually described by a file
called cocoon_jena_boot.ttl. This thing/resource is expected to contain a
Jena Assembler
definition, which in turn specifies how the configuration for this particular
command machine should be loaded.
Once the configuration is loaded from the specified location, the machine is ready to process commands. When used within the PeruserTransformer, the pattern of behavior is as follows:
- The transformation is invoked within a pipeline with a configuration that looks like this:
<map:transform type="toolchest" src="http://www.peruser.net/2007/toolchest/conf#get_filtered_toolset"> </map:transform>
- When the PeruserTransformer bound to the JCCM is invoked, it is given an XML input document and expected to produce an XML output document (this is the Cocoon pipeline contract defined by AbstractDOMTransformer).
- The src attribute assigned in the transformation step is used to identify the instructionAddress of the command. In this case, the concept of instructionAddress is identical with the the URI of the root of the command configuration within the JCCM's machine configuration model. In our example above, the instructionAddress is the URI with fragment identifier #get_filtered_toolset, which presumably is descriptive of what the intended command is supposed to do!
- The JCCM machine will look up this commandInstance (in it's RDF configuration) and then fetch its handlerClass property, which contains the name of a java class. This class is the java type of the command. Command classes include:
- net.peruser.module.selector.SelectorCommand - used to execute complex SPARQL queries
- net.peruser.module.projector.ProjectorCommand - used to dump part of an RDF model (e.g. a taxonomy) as a hierarchical XML tree
- You can add your own classes implementing the Command interface.
- A new instance of the command class is instantiated by calling its no-args constructor.
- The comand is then invited to configure itself (via the configure() method) by looking at the properties assigned to its RDF commandInstance. What it does with these properties is up to the Command class.
- The command is then asked to execute (via the workDoc(Doc input) method), and return a Doc which becomes the result of this transformation step.
The next few pages describe the configuration and functioning of the SelectorCommand and ProjectorCommand in some detail.
