This document explains how and when to customize TinyWorkflow components like functions, parameter descriptions, conditions, meta-attributes.
This document will not explain the basics of TinyWorkflow, please read the 'Getting Started with TinyWorkflow' document for an introduction.
Some components of the TinyWorkflow API are customizable. They are the components allowing binding the workflow with your business code.
They are:
For each of those components type, the workflow API provides a set of pre-defined implementation. There are, in addition, several mechanisms that allow extending the functionalities of those pre-defined implementations:
All the methods are not available for all types of components. The following table summarizes the availability of extension mechanism along with the name of interface to implement.
Component type | Beanshell | Peer method | Java Interface | Custom XML element |
Function | <beanshellFunction> | <peerCall> | <proxyFunction> with class implementing: org.tinyWorkflow.definition.function.WorkflowExternalFunction | Class implementing: org.tinyWorkflow.definition.function.WorkflowFunction and org.tinyWorkflow.utils.xml.serialize.XMLSerializable |
Condition | <beanshellCondition> | <peerCondition> | <proxyCondition> with class implementing: org.tinyWorkflow.definition.condition.WorkflowExternalCondition | Class implementing: org.tinyWorkflow.definition.condition.WorkflowCondition and org.tinyWorkflow.utils.xml.serialize.XMLSerializable |
Parameter Description | - | - | <proxyParameter> with class implementing: org.tinyWorkflow.definition.parameter.TransitionParameterExternalDefinition | Class implementing: org.tinyWorkflow.definition.parameter.TransitionParameterDefinition and org.tinyWorkflow.utils.xml.serialize.XMLSerializable |
Meta-attribute | - | - | - | Class implementing: org.tinyWorkflow.definition.metadata.MetaData and org.tinyWorkflow.utils.xml.serialize.XMLSerializable |
PriorityCalculator | - | <peerPriority> | <proxyPriority> with class implementing: org.tinyWorkflow.definition.priority.ExternalPriorityCalculator | Class implementing: org.tinyWorkflow.definition.priority.PriorityCalculator and org.tinyWorkflow.utils.xml.serialize.XMLSerializable |
WorkflowFlag | - | <peerBooleanFlag> | <proxyFlag> with class implementing: org.tinyWorkflow.definition.flags.ExternalWorkflowFalg | Class implementing: org.tinyWorkflow.definition.flags.WorkflowFlag and org.tinyWorkflow.utils.xml.serialize.XMLSerializable |
In the next chapter of this document we will show how to implement the same function in the 4 different ways supported by TinyWorkflow. As the mechanism of extension is the same for every type of component, it will be easy to apply what you have learned to customize other component types.
Note that all the predefined components provided by the TinyWorkflow API (see WorkflowXmlReference.html for an exhaustive list) are implemented as 'Custom XML elements'. So by looking at the code of those components you can have examples of this type of extension for all components.
Let's define a very simple function.
Our function will:
It's very basic but this it demonstrate the two major functionalities of functions: run any code and interact with the transition context.
The advantage of beanshell is that you can put all the logic doing the 'glue' between the workflow and the business object inside the XML definition. So you can have you business code residing in your java business objects and you can use them from the workflow without having those business objects depending on the workflow.
Beanshell implementation of the function is straightforward:
So the beanshell function call is simply:
... <preFunctions> <beanshellFunction> System.out.println(" * Hello from beanshell function"); context.setParam("done", Boolean.TRUE); </beanshellFunction> </preFunctions> ...
When your workflow is associated to a peer object, you can simply call a method on this peer. It's the simplest way to call code on business object.
Just use the <peerCall> function in the XML definition to specify which method is called:
... <preFunctions> <peerCall method="myFunction1" resultParamId="done1"/> <peerCall method="myFunction2"/> </preFunctions> ...
For this example we use 2 <peerCall> to illustrate the various ways of calling method on a peer object:
The first <peerCall> calls the following method:
public boolean myFunction1() { System.out.println(" * Hello from peer function 1"); return true; }
In this case, the transition context is not passed to the method. The return value is put in the transition context with the id specified by the 'resultParamId' attribute.
The second <peerCall> calls the following method:
public void myFunction2(TransitionContext context) { System.out.println(" * Hello from peer function 2"); context.setParam("done2", Boolean.TRUE); }
In this case, the transition context is given as a parameter of the method. This method doesn't return a value but put itself the 'done2' parameter in the transition context.
With java class implementation you can easily make a reusable function that can be parameterized from the XML definition. All of this without having to bother about XML parsing.
Inside the XML definition, you will use a <proxyFunction> element mapped to a ProxyFunction object. The ProxyFunction handle the XML parsing, it allows also specifying meta-data. In the XML, you also specify the class or your reusable function. The proxy will then delegate the actual function execution to this class.
You just have to implement the org.tinyWorkflow.definition.function.WorkflowExternalFunction interface:
public class MyExternalFunction implements WorkflowExternalFunction { /** Id of the message property passed to the function */ public static final String MESSAGE_PROPERTY_ID = "message"; private String message; public MyExternalFunction() { } public void init(ProxyFunction proxy) throws SAXException { message = (String) proxy.getMetaData(MESSAGE_PROPERTY_ID); if (message == null) { throw new SAXException("The '"+MESSAGE_PROPERTY_ID+"' meta-data is mandatory"); } } public Object execute(TransitionContext context) throws WorkflowException { System.out.println(message); return Boolean.TRUE; } }
The external function can be parameterized with the init method. The init method gives you a ProxyFunction containing all the meta-data objects declared in the XML. Here we use a simple property meta-data holding the message to display. The init method is called during parsing so you can validate the expected properties and can throw a SAXException if something is invalid or missing.
The implementation of the function is the same as usual. As the transition context is given as parameter, you have the choice to put the result directly in the context or to return it and specify the 'resultParamId' attribute in the XML (just as for the <peerCall> example). Here we have chosen the second possibility.
We can then use the class with a <proxyFunction> XML element:
... <preFunctions> <proxyFunction class="org.tinyWorkflow.examples.customisation.MyExternalFunction" resultParamId="done"> <meta> <property id="message" value=" * Hello from the External function"/> </meta> </proxyFunction> </preFunctions> ...
So far we shown how to add you own reusable functions but having to pass the full class name in the XML the entire the configuration as meta-data it tedious work. It is also error-prone because you only have few syntactical checks.
In this section, we will go further. You will learn how to define your own XML elements to extend the set of pre-defined elements.
Adding a new XML element involves:
To explain how to do those points, we will implement a custom function as defined in previous section.
The XML parsing is done with a light customization of the SAX parsing API. It uses the standard SAX parser provided with java (Xerces). So, no special XML API is required.
See (TODO) for explanation of the parsing mechanism.
public class MyFunction implements WorkflowFunction, XMLSerializable { /** Tag for class function definition in XML */ public static final String MY_URI = "http://myCompnay.org/my-2005"; /** Tag for class function definition in XML */ public static final String MY_FUNCTION_TAG = "myFunction"; /** Name of the XML attribute holding the message displayed by the function */ private static final String MESSAGE_ATTRIBUTE = "message"; private String message; public MyFunction() { } public void execute(TransitionContext context) throws WorkflowException { System.out.println(message); context.setParam("done", Boolean.TRUE); } public void startElement(String uri, String localName, XMLEventParser parser) throws SAXException { if (MY_URI.equals(uri)) { if (localName.equals(MY_FUNCTION_TAG)) { message = parser.getMandatoryAttributeValue(MESSAGE_ATTRIBUTE); } else { throw new SAXParseException("Unexpected xml element <" + localName + ">", parser.getLocator()); } } else { throw new SAXParseException("Unexpected xml uri '" + uri + "'", parser.getLocator()); } } public void endElement(String uri, String localName, XMLEventParser parser) throws SAXException { if (MY_URI.equals(uri) && localName.equals(MY_FUNCTION_TAG)) { parser.endParsing(this); } } public void serialize(XMLSerializer serializer) throws XMLSerializeException { // TODO } }
As you cannot modify the TinyWorkflow schema directly, you have to extend it using another namespace. (see next section)
So now you can directly use the new XML element with it custom parameters in the XML definition.
Note that the new elements will be in a new XML namespace (this is the regular way to do schema extension), so don't forget to declare you new namespace before to use it (It's a good practice to declare it in the top XML element).
<?xml version="1.0" encoding="ISO-8859-1" ?> <workflow xmlns="http://org.tinyWorkflow/2005/WorkflowDefinition" xmlns:my="http://myCompnay.org/my-2005" id="myWorkflow" version="1.0" peer="org.tinyWorkflow.examples.customisation.TestPeer"> ... <preFunctions> <my:myFunction message=" * Hello from myFunction"/> </preFunctions> ...
To extend the schema, you have to declare your new element in a new schema (using a new namespace). Your element has to be declared as part of a substitution group so it's possible to use it everywhere the other members of the substitution group are used. This implies that you declare your element as a global element of your namespace and that the type of this element extends the substitution group head element type.
See W3C XML Schema for more explanation about schemas. In particular the XML Schema Part 0: Primer Second Edition is a good technical introduction.
Don't forget to import the TinyWorkflow XML schema to be able to use the elements/types it declares.
Component type | Substitution group head element | Substitution group head type |
Function | function | baseFunctionType |
Condition | condition | baseConditionType |
Parameter Description | parameter | baseParameterType |
Meta-attribute | metaData | baseMetaDataType |
WorkflowFlag | flag | baseFlagType |
For our custom function the resulting schema is:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://myCompnay.org/my-2005" xmlns:wf="http://org.tinyWorkflow/2005/WorkflowDefinition" targetNamespace="http://myCompnay.org/my-2005" elementFormDefault="qualified"> <xs:import namespace="http://org.tinyWorkflow/2005/WorkflowDefinition"/> <xs:element name="myFunction" type="myFunctionType" substitutionGroup="wf:function"/> <xs:complexType name="myFunctionType"> <xs:complexContent> <xs:extension base="wf:baseFunctionType"> <xs:attribute name="message" type="xs:string" use="required"/> </xs:extension> </xs:complexContent> </xs:complexType> </xs:schema>
The repository has to be customized of know the new XML elements. The simplest way to do it is to create a new repository class extending the default repository (org.tinyWorkflow.definition.WorkflowDefinitionRepository).
You can simply add this mapping by overriding the method creating the DefinitionObjectFactory:
protected DefinitionObjectFactory createFactory() { DefinitionObjectFactory factory = super.createFactory(); factory.defineElement(MyFunction.MY_URI, MyFunction.MY_FUNCTION_TAG, MyFunction.class.getName()); return factory; }
You can simply add the new schema location to the method building the schema locations list:
protected String getSchemaLocations() { String schemaLocations = super.getSchemaLocations(); URL schemaUrl = getClass().getResource("MyWorkflowExtension.xsd"); if (schemaUrl == null) { throw new IllegalStateException("Cannot find workflow definition schema in classpath: /" + getClass().getPackage().getName().replace('.', '/') + "/MyWorkflowExtension.xsd"); } return schemaLocations + " " + MyFunction.MY_URI + " " + schemaUrl.toExternalForm(); }
Note: another way is to disable schema validation by calling setSchemaValidation(false) on the repository. If you do that, you don't have to provide schema for your new elements. Of course, then, you just have the very basic XML validation coded in the java objects (In general the object themselves only check what is crucial for them to have a valid state, everything else is ignored).
TODO