Oddjob's extension points.
In this section we take a look at
the many options for making our classes available to Oddjob. We'll
learn how to tell Oddjob to use nice short element names like
<copy>
and we'll learn how to write an Oddball which is an Oddjob plugin.
In the first instance Oddjob finds its classes...
-classpath
option..jar
in the lib
directory..jar
in the opt/lib
directory.opt/classes
directory.We can prove this by copying an example class:
$ mkdir opt/classes/org $ mkdir opt/classes/org/oddjob $ mkdir opt/classes/org/oddjob/devguide $ cp examples/classes/org/oddjob/devguide/HelloWorldJob.class opt/classes/org/oddjob/devguide $ java -jar run-oddjob.jar -f examples/devguide/hello1.xml Hello World!
How can we use our own XML elements instead of boring old
<bean>
elements?
Provide an implementation of an
ArooaDescriptorFactory.
When Oddjob starts it has one of these factory things that scans the classpath
for any number of META-INF/arooa.xml
XML files which it
expects to be able to parse to create another factory. It joins all these little
factories together and creates a big factory (For those that like design patterns
this is of course the classic Composite Pattern).
There's an example of one of these XML files in the examples directory called
helloperson-arooa.xml
. It's for our HelloPersonJob example from the
previous section and it looks like this.
<arooa:descriptor xmlns:arooa="http://rgordon.co.uk/oddjob/arooa"> <components> <arooa:bean-def element="hello" className="org.oddjob.devguide.HelloPersonJob"/> </components> <values> <arooa:bean-def element="person" className="org.oddjob.devguide.Person"/> </values> </arooa:descriptor>Now as Arooa is a Java Bean Framework, it probably comes as no surprise to learn that this is Java Bean implementation of an ArooaDescriptorFactory and it's parsed with... Arooa of course. See <arooa:descriptor> in the reference for more on how to configure this bean.
If we copy the descriptor:
$ cp examples/devguide/helloperson-arooa.xml examples/classes/META-INF/arooa.xml
We can modify our original configuration to become:
<oddjob> <job> <hello formal="true"> <person> <person title="Mr" firstname="John" surname="Smith"/> </person> </hello> </job> </oddjob>
And run it:
$ java -jar run-oddjob.jar -cp examples/classes -f examples/devguide/helloperson2.xml Hello Mr Smith.
Once a component has been given an element to class mapping, Oddjob Designer will automatically create a form for it:
And for person too:
Ok, not pretty - but it can make a pleasant change from XML.
A nested Oddjob job within Oddjob can also be configured with it's own class loader and descriptor factory.
<oddjob id="this"> <job> <oddjob file="${this.dir}/helloperson2.xml"> <descriptorFactory> <import file="${this.dir}/helloperson-arooa.xml"/> </descriptorFactory> <classLoader> <url-class-loader> <files> <file file="${this.dir}/../classes"/> </files> </url-class-loader> </classLoader> </oddjob> </job> </oddjob>
This does the same as the above. No classpath required.
$ java -jar run-oddjob.jar -f examples/devguide/helloperson-main.xml Hello Mr Smith.
The difference is that a new class loader is created by Oddjob and used
when processing the descriptor factory to
load the HelloPersonJob
class.
Oddballs are Oddjob's plugin architecture.
Oddballs extends the above features of Oddjob modularity by defining a recognised directory structure for deploying a set of job classes and their dependent libraries.
An Oddball is any directory in Oddjob's oddballs
directory.
Oddballs find their classes...
.jar
in the lib
directory.classes
directory.(Familiar to those who know Servlets. But there's no
web.xml
, instead...)
When the Oddball loads it scans this classpath
for any number of META-INF/arooa.xml
XML file from which it
creates it's ArooaDescriptorFactory.
Pre-Installed Oddballs
Oddjob comes pre-installed with some Oddballs for Ant tasks, mail, FTP, and an HSQL database server. Here's a screenshot of a deployed Oddjob and it's Oddballs:
Developing an Oddball
In the deployed Oddballs above, the META-INF/arooa.xml
file is combined with Oddjob
job beans in a jar for neat deployment. While developing an Oddball
ensuring the project directory also adheres to the Oddball layout
allows for testing without any deployment:
Launching with Odballs not In The Oddballs Directory
Here's an eclipse launch configuration used to Launch Oddjob with all it's Oddballs:
<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication"> <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS"> <listEntry value="/projects.oddjob/src/java/org/oddjob/Main.java"/> </listAttribute> <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES"> <listEntry value="1"/> </listAttribute> <stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="org.oddjob.Main"/> <stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-op ${workspace_loc:projects.oj-ant};${workspace_loc:projects.oj-examples};${workspace_loc:projects.oj-hsql};${workspace_loc:projects.oj-mail};${workspace_loc:projects.oj-net};${workspace_loc:projects.oj-spring}/target;"/> <stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="projects.oddjob"/> </launchConfiguration>
The -op
argument stands for 'Oddball Path'. Notice that
the Spring Oddball (oj-spring
) points to a 'target'
directory. This is because it is built with Maven. Fortunately
a Maven Install creates a directory structure that is Oddball conformant too!
The Spring Oddball
The Spring Oddball is a separate download. It's available from Sourceforge
here. To deploy the Spring Oddball simply
download the binary and unzip it into the Oddjob's
oddballs
directory, and restart Oddjob.
Un-Installing Oddballs
To un-install an Oddball, simply move it out of the oddballs
directory, and restart Oddjob.
Using Oddball Jars In Your Own App Classpath
If you want to use an Oddball jar with your own application classpath,
you can can just drop it in with your own libs but the arooa.xml
will not be automatically loaded. To load it we can use use a
ClassPathDescriptorFactory.html. Here's an example of using the
And Oddball jars without it being an Oddball.
<oddjob id="this"> <job> <oddjob file="${this.dir}/hello-from-ant.xml"> <descriptorFactory> <bean class="org.oddjob.arooa.deploy.ClassPathDescriptorFactory" excludeParent="true"/> </descriptorFactory> <classLoader> <url-class-loader> <files> <files> <list> <file file="${my-app.dir}/classes"/> <files files="${my-app.dir}/lib/*.jar"/> </list> </files> </files> </url-class-loader> </classLoader> </oddjob> </job> </oddjob>
Explicitly Loading Oddballs For a Nested Oddjob
An Oddball or many Oddballs can be explicitly loaded using the <oddballs> type
<oddjob id="this"> <job> <oddjob file="${this.dir}/echo-ant-classpath.xml"> <descriptorFactory> <oddballs> <files> <file file="${my-app.dir}"/> </files> </oddballs> </descriptorFactory> </oddjob> </job> </oddjob>
Extending an Oddballs Classpath
If you want to extend an Oddballs classpath you can do so by using a <url-class-loader> and refering to the class loader of the component as the parent class loader.
<oddjob id="this"> <job> <ant id="my-ant" baseDir="${this.dir}"> <tasks> <xml> <tasks> <whichresource resource="/org/oddjob/devguide/MyAntTask.class" property="my-task.url"/> <echo message="${my-task.url}"/> </tasks> </xml> </tasks> <output> <stdout/> </output> <classLoader> <url-class-loader> <files> <file file="${this.dir}/../classes"/> </files> <parent> <value value="${my-ant.class.classLoader}"/> </parent> </url-class-loader> </classLoader> </ant> </job> </oddjob>
The author was hoping to show the running of a custom task using
our extended classpath - However, although our task can be found by Ant, as,
if you run this example you will see, Ant's Taskdef doesn't use an Ant's
projects own core classloader! Hopefully though you get the point. Oddjob's
build process (oj-assembly
in the source distribution) uses
this technique to ensure that the ant-junit.jar is available during the
build.