Oddjob

from rgordon.co.uk

Developing With Oddjob

Under the covers Oddjob is a big rip off of Apache Ant, so please read their documentation Developing With Ant which is much better than this. Oddjob does have some difference which I'll come to later, but If you understand Ant then understanding Oddjob will be easy.

Contents:

Components

Oddjob, like many application frameworks, uses the idea of a component. There are many definitions of a component, but in Oddjob it is simply something which can be dynamically created and added to other components. In oddjob a component is any java.lang.Object with a no argument constructor.

Writing your Own component

  package learn.oddjob;
	
  public class FirstComponent {
  }	

Which isn't to hard. You can fire this up with oddjob using the following configuration:

  <oddjob>
    <anything class="learn.oddjob.FirstComponent"/>
  </oddjob>	

Oddjob will instantiate your component and then attempt to do a lot of things which won't have any affect. Most noteably it hasn't run anything because our component isn't a Job, which means it doesn't implement the java.lang.Runnable interface.

Jobs really come later but as you want to see some stuff happen we'll now extend our first component to be our first job.

  package learn.oddjob;
  
  public class FirstJob implements Runnable {

    public void run() {
      System.out.println("Hello World!");
    }
  }
 

Change the configuration to:

  <oddjob>
    <anything class="learn.oddjob.FirstJob"/>
  </oddjob>

And now when you run oddjob we get the expected "Hello World!" displayed on the console. Enough about jobs until later because we still haven't finished with components...

The Lifecycle of a component.

At parse time:

  1. The component is created using a no argument constructor either from a class name given as the class attribute or by looking up the tag name in the oddjob.properties file.
  2. If the configuration element contains an id attribute the component is registered so that it's properties can be gotten at later with the ${} replacement syntax.
  3. If the component has a setContext(ArooaXMLContext context) method it will be called with the context used during parsing. Amongs other things the context contians your components very own RuntimeConfigurable object. This allows your component to configure itself. The framework will configure the component for you so unless you want very special behavour you can forget about this.
  4. constant properties (those which don't contain ${} replacement syntax) are set via the components setXXX().
  5. Child elements are created via this component's createXXX() methods or instantiated and added to this component via its addXXX() methods.
  6. Child components are added via this components addComponentXXX() or addComponent() method.
  7. init() is called, if it exists.

At run time:

  1. Any element text is set via the addText() method. No ${} substitution takes place for text at them moment but this might change.
  2. Dynamic properties are set. The ${ } values are resolved and the resulting object is set via the setXXX() method in the component and all child elements and values.
  3. Properties declared as elements are resolved and set using the components setXXX(YourType value) or setXXX(String name, YourType value) method.
  4. Remaining child elements are set with the addConfiguredXXX() method.

At termination:

  1. If the component has a destroy() method it is called.

Changing the Lifecycle

Your own component can change the default configuration for all it's child elements by implementing a method with the signature ArooaHandler handlerFor(ArooaXMLContext context). The handler you provide will get direct access to the underlying XML configuration.

Alternatively providing a method with the signature ArooaHandler handlerForXXX(ArooaXMLContext context) will receive configuration information for just you components XXX element.

As an example, the ForEach job provides it's own hander which records the child job definition so that it can be used to create a new child job for each of it's values later, when it is executed.

Elements, Components and Properties

Elements are Objects created for nested XML elements which your component uses to do it's stuff. The Ant manual is very good on this subject and Oddjob behaves exactly like Ant with regard to nested elements so I won't ellaberate more here.

A nested component is also a nested XML element, except that it's created by the framework and registered so that others can get and set it's properties and even execute and stop it. And when it does execute you have very little idea about what it's doing.

In the analogy of the family home the nested element is like your power tools - you have complete control. Your nested component is like your own child. You have responsibilities (see below) but very little control!

A nested property allows complicated types to set using a setXXX(SomeType value) or setXXX(String name, SomeType value).

Properties in Detail

As an example - here's the code for a simple component with a slightly complicated property.

		
  package learn.oddjob;
	
  public class ShoopingComponent {
    private String[] shopping;
    public void setShopping(String[] shopping) {
      this.shopping = shopping;
    }
    public String[] getShopping() {
      return shopping;
    }
  }	

And this would configure the ShoppingComponent:

  <oddjob>
    <anything class="learn.oddjob.ShoppingComponent">
      <shopping>
        <value value="beer"/>
        <value value="wine"/>
        <value value="piza"/>
      </shopping>
    <anything/>
  </oddjob>

Oddjob has a list of types and the Objects to create to interpret the XML. These are known as property proxies. For String[] it creates a ListType which takes care of the value elements.

The type can be any object just like an element but it can optionally also supply a Object valueFor(Class required) method which will be used to perform conversions. This allows a ValueType to be both a String and an InputStream used in a copy job..

Values are of two overlapping sorts. The types that act as property proxies for an attribute type are defined in proxies.properties. The types that are created via a factory from their tag name such as <value> (which is ValueType) are declared in types.properties.

Property Conversions

When a property gets resolved using ${x.y.z} notation Oddjob uses BeanUtils to do this. It also uses the BeanUtils converters which enable string to be converted to number etc. Please see the BeanUtils documentation for more information.

XML to Java Object Summary

Which method gets called for which bit of XML is slightly confusing so here is a summary.

<comp1 stuff="thing"/>
	
  • If your component has a setStuff(String value) or setStuff(File value) (or other type supported by BeanUtils conversions) then it will be called with the value "thing".
<comp1>
	<moreStuff stuff="thing"/>
</comp1>
	
  • If your component has a addMoreStuff(YourType value) or addConfiguredMoreStuff(YourType value) it will be called with an object of YourType.
  • If your component has a createMoreStuff() method then it will be called expecting to return an Object of YourType.
  • If your component has a setMoreStuff(YourType value) and YourType has been maped to an Object YourProxy using Oddjobs propertyProxy property, then an Object of YourProxy will be created and if it has a valueFor(Class required) method then whatever this method returns will be used as the value. If YourProxy doesn't have a valueFor method then it will be used as the value itself - and so must be of type YourType. (For instance the mapping for ScheduleElement is ScheduleElement).
  • If your component has a addComponentMoreStuff(YourType component) and moreStuff has been maped to YourType using Oddjobs componentType property then an object of YourType will be created and the method will be called with the created component.

If your component has more than one of the above then which is called is undefined.

<comp1>
  <moreStuff>
    <stuff name="fred" stuff="thin"/>
  </moreStuff>
</comp1>
	
  • If your component has a setMoreStuff(String name, YourType value) method and stuff has been mapped to YourType using Oddjobs valueType property, or mapped to a property proxy using Oddjobs propertyProxy property then the type YourType will be created and the method called with the given name.
  • All of the above methods also apply where moreStuff is assumed to require a nested stuff element which then has all of this same logic applied to it.

The Differences with Ant

Values not References - Ant allows you to declare a type which you then refer to by a reference. Oddjob uses Values to achieve this.

Properties - In Ant properties are something in their own right. In Oddjob properties are only ever properties of a Component.

Task vs Components - Ant has addTask, Oddjob has addComponent. Conceptually they are very similar.

Briefly - here are some of the reasons for there being differences.

  • All tasks in Ant depend on the project. I wanted Oddjob components to stand on their own.
  • Properties in Ant can't be complicated types. Complicated types must be retrieved by reference using the project. Code is required in a task to resolve a reference and avoid circular references. I wanted a component to be as simple as possible.
  • I wanted to link the properties of one object with another, like Struts - including nested properties.

But the main reason for the difference is Oddjob and Ant are aimed at very different problem domains. Ant is a build tool. Oddjob is a scheduler.

Writing Your Own Job

We already mentioned a job is a component which implements java.lang.Runnable which means a job is any Runnable with a no-arg constructor.

While this is the simplest definition of a job, a job can be so much more by implementing certain interfaces, listed below, which provide great flexibility to how your job behaves. Nine time out ten, however you would like you job to behave like any other job and to help you do this quickly there is SimpleJob.

SimpleJob

A job can be so much more, very quickly by extending org.oddjob.framework.SimpleJob. To achieve this you need only implement an execute() method. Thus something much better than our first job is:

  package learn.oddjob;
  
  import org.oddjob.jobs.AbstractJob;
  
  public class BetterJob extends SimpleJob {

    protected int execute() {
      System.out.println("Hello World!");
	  return 0;
    }
  }

The main advantage to extending SimpleJob are seen in Oddjob Explorer where an icon and tool tip are displayed representing execution state. Log messages are available, and state properties are displayed. Persisting the completion state of our BetterJob is also possible where it wouldn't have been with FirstJob. To really see how to use SimpleJob take a look at the code, because nearly all jobs in Oddjob extend SimpleJob.

From The Ground Up

If SimpleJob doesn't suit your needs for whatever reason then the other approach is to build a Job using any combination of these interfaces:

The Responsibilities of Being a Parent

If your job provides a addComponent() or an addComponentXXX() then a child or children may have been created and will have been handed over for your sole care. As such:

  • If you run the job, you should not return from your own method until your children have finished.
  • So if you're being stopped you either have to wait for your children before actually stopping or attempt to stop your children.
  • If you are being reset, you will probably want to reset your children.
  • If you are being destroy, you absolutely must destroy your children.

As an aside this responsibility issue is why there is no support for an inline attrribute child. This would be a child which is set using a setXXX method but declared inline in the XML. The set method has no idea where the job came from and so your job can take no responsibility for it and so the job wouldn't get stopped or destroyed and so it's not allowed.

Threading

An Oddjob job should be designed so that a job is only run by one thread at a time. If you extend SimpleJob then this is taken care of for you, however you might need to protected properties from changing during execution by using the protected lock field.

A job can be run from a thread which was not the thread which set it's properties. Because of this and the java memory model you need to take precautions to ensure a consisitent state. Options include

  • Stop a property from being set after initilisation by throwing an excpetion.
  • Synchronize getters and setters or delcare the property volatile.
  • Synchronize on any other object which will cause a memory flush. Using the lock field in SimpleJob achieves this.

The last option is the easiest if extending SimpleJob. This also protects against properties changing when the job is executing.

What We've Missed

Persistance, Using Oddjob jobs in your own code, coming soon...