Understanding the underlying Arooa framework.
The underlying creation and configuration of jobs is code based mainly on code plagerised from the Ant project. It provides a stand alone framework independant of Oddjob. In acknowlegement of it's beginning the framework is called Arooa - A Rip Off Of Ant.
Arooa, like many application frameworks, uses the idea of a component. There are
many definitions of a component, but in Arooa it is simply something which
can be dynamically created and added to other components. In Arooa a component
is any java.lang.Object with a no argument constructor.
Here is a really simple component.
package org.oddjob.devguide;
public class FirstComponent {
}
You can fire this up with oddjob using the following configuration:
<oddjob>
<anything class="learn.oddjob.FirstComponent"/>
</oddjob>
Because our component isn't Runnable, Oddjob isn't treating it as a job, so you get a boring icon and you can see some properties - but not much else.
Despite the fact that Oddjob has very little interst in our component Arooa will configure it, initialise it and finally destroy it. The same lifecycle applies for Jobs, Services or plain old Java Objects.
Lets see what the Arooa framework does with your job as it reads the configuration:
At parse time:
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 the ComponentFactory.
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.
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 you don't need to implement
this method.
constant properties (those which don't contain
${} replacement syntax) are set via the components
setXXX().
Child elements are created via the component's createXXX()
methods or instantiated and added to the component via its addXXX() methods.
This functionality inherited straight from ant but it is preferable to use
the stanadard setters as it makes your job more transportable between
POJO frameworks.
Child components are added via this components addComponentXXX()
or addComponent() method.
init() is called, if it exists.
At run time:
Any element text is set via the addText() method.
No ${} substitution takes place for text at them moment but
this might change.
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.
Properties declared as elements are resolved and set using the
components setXXX(YourType value) or
setXXX(String name, YourType value) method.
Remaining child elements are set with the addConfiguredXXX()
method. Again this method is inherited from ant, but a using a setter
would be the preferred option.
At termination:
If the component has a destroy() method it is called.
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.
A nested property allows complicated types to set using a
setXXX(SomeType value) or
setXXX(String name, SomeType value).
As an example - here's the code for a simple component with a slightly complicated property.
package org.oddjob.devguide;
public class ShoppingComponent {
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 p roperty proxies. For String[] it creates a ListType which takes care of the shopping element.
The ListType uses a factory to create a value type to populate the list. For the element value, it creates a ValueType object.
The value type can be any object but it can optionally
also supply a Object valueFor(Class requested)
method which will be used to perform conversions. This allows
a ValueType to be both a java.lang.String property
as in the above example, and a java.io.InputStream
used in, for instance, a copy job.
These objects that provide the 'values' for properties and are of a 'type' have loosly been termed 'value types'. Not to be confused with ValueType.java which is a paticular instance of a 'value type'.
Arooa creates the value types
either because the type is defined as property proxy
based on the Class of a setter, or
by creating the type from a factory definition. The Arooa framweork
is then using the
valueFor() method, if it exists before setting the property.
This is admittadly quite confusing, but does allow jobs to be written without knowledge of the types that will configure them and does allow for elegant xml. Given this - confusion is a small price to pay.
Once you understand the difference between a 'value type' and
a 'property proxy' defining your own is easy. Take a look at
examples/devguide/shopping2.xml followed by
examples/devguide/shop-oj.xml and
examples/devguide/shopping3.xml for examples. One
day we'll go through them here.
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.
Which method gets called for which bit of XML is slightly confusing so here is a summary.
<comp1 stuff="thing"/>
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>
addMoreStuff(YourType value) or
addConfiguredMoreStuff(YourType value) it will be called with
an object of YourType.createMoreStuff() method then it will
be called expecting to return an Object of YourType.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).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>
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.