| previous section: Understanding Almo 2.0: The Almo data model |
next section: Understanding Almo 2.0: The settings of Almo elements |
Understanding Almo 2.0: The Almo API
In this section we will build an Almo WorkflowConfiguration, just using the API. You will get introduced to the abstract classes of Almo, which you have to use to get the Almo elements. Furthermore, you will get an overview of all settings and the 'include / exclude' features of Almo.
Content
- Building a WorkflowConfiguration
- Implementation of Almo elements
- Adding more behaviour to your WorkflowConfiguration
- Creating a WorkflowInstance
- Writing and reading the WorkflowConfiguration
- Summary
Building a WorkflowConfiguration
To get an empty WorkflowConfiguration, use the following code:
WorkflowConfiguration w = AlmoFactory.createWorkflow(false,null);![]()
![]() |
The first argument is used to configure log4j. When the WorkflowConfiguration is created,
you can decide whether to enable log4j messages generated by Almo. Passing true, will configure log4j
to print logging messages to the console. Passing false, will not configure log4j, instead you can
configure log4j to your own needs.
The second argument is used to to initialize the Almo elements with an arbitrary object, which you might need to use when the WorkflowInstance is executed, e.g. a database connection (see later section for more information). |
Now we will add some elements to the WorkflowConfiguration. At this point you do not need to worry about their implementation. We will cover that in the next section.
try {
Task t1 = new DefaultTask("t1");
w.addTask(t1);
Operation o2 = new Operation2("o2");
o2.setPackage("org.ontoware.almo.example");
o2.setClass("Operation2");
t1.addElement(o2);
Task t2 = new Task2("t2");
t2.setPackage("org.ontoware.almo.example");
t2.setClass("Task2");
w.addTask(t2);
Container c1 = new DefaultContainer("c1");
t2.addElement(c1);
Operation o3 = new Operation3("o3");
o3.setPackage("org.ontoware.almo.example");
o3.setClass("Operation3");
c1.addElement(o3);
Operation o4 = new Operation4("o4");
o4.setPackage("org.ontoware.almo.example");
o4.setClass("Operation3");
c1.addElement(o4);
} catch (IllegalLabelException e) {
e.printStackTrace();
} catch (IllegalSettingsException e) {
e.printStackTrace();
}
![]() |
The first Task is the DefaultTask offered by Almo. |
|
The add methods will throw two Exceptions:
|
|
This Operation is not implemented by Almo, therefore you have to specify its package and class. |
|
This Task is not implemented by Almo, therefore you have to specify its package and class. |
|
This Container is implemented by Almo. |
As result we get the following WorkflowConfiguration tree:
The lock indicates that all these elements are required. We will deal with the difference between required and optional elements later.
Implementation of Almo elements
In the previous section we have used some implementations of Almo elements to form our WorkflowConfiguration. Now we will take a look at how to implement the abstract Almo classes.
Task
Implementing a Task is done by deriving a class from the abstract super-class org.ontoware.almo.api.Task. The following methods have to be implemented:
- public abstract Task loopTo() - Returns the Task, which shall be started after this Task is finished. If null is returned, the next Task will be started.
- public abstract void initObject(Object o) - Can be used to convey an arbitrary Object to the Task (see later).
Let us take a look at the code of Task2:
public class Task2 extends Task {
int max;
int count;
public Task2(String s) {
super(s);
count = 1;
max = 2;
}
public Task loopTo() {
if (count < max) {
count++;
Workflow w = (Workflow) this.getRoot();
Task t = (Task) w.getElementByLabel("t1");
this.print("looped to: t1");
return t;
}
return null;
}
public void initObject(Object o) {
}
}
![]() |
You have to implement a constructor which sets the label of the Task. |
![]() |
Returns the first Task when this Task is finished for the first time, after that returns null. |
![]() ![]() |
You can get the first Task by using the getElementByLabel method from the parent Workflow object. |
Container
Implementing a Container is done by deriving a class from the abstract super-class org.ontoware.almo.api.Container. The following methods have to be implemented:
- public abstract void startElement() - How is the Container started?
- public abstract void stopElement() - How is the Container stopped?
- public abstract void elementFinished() - This method is called by a child element, when it is finished. What happens?
- public abstract void initObject(Object o) - Can be used to convey an arbitrary Object to the Container (see later).
- public abstract void initPanel() - If Almo's GUI is used within an application, you can customize the panel of the Container with this method.
In our example we have used the DefaultContainer. It might give you some ideas, how to implement your Containers:
public class DefaultContainer extends Container {
public DefaultContainer(String label) {
super(label,"org.ontoware.almo.api.defaults","DefaultContainer");
}
public void startElement() {
this.start();
this.setIndeterminate(true);
for (int i=0;i<this.getChildCount();i++) {
WorkflowElement e = (WorkflowElement) this.getChildAt(i);
e.startElement();
}
}
public void stopElement() {
this.stop();
for (int i=0;i<this.getChildCount();i++) {
WorkflowElement e = (WorkflowElement) this.getChildAt(i);
e.stopElement();
}
}
public void elementFinished() {
boolean finished = true;
for (int i=0;i<this.getChildCount();i++) {
WorkflowElement e = (WorkflowElement) this.getChildAt(i);
if (!e.isFinished()) {
finished = false;
break;
}
}
if (finished)
this.finish();
}
public void initPanel() {
}
public void initObject(Object o) {
}
}
![]() |
The constructor sets the label, the package and the class of the DefaultContainer. |
![]() |
The DefaultContainer is started, by calling the start method from the super-class WorflowElement. The progressbar of the GUI is set to indeterminate, after that all elements are started. |
![]() |
The DefaultContainer is stopped, by calling the stop method from the super-class WorflowElement. After that all elements are stopped. |
![]() |
If one element is finished, the DefaultContainer checks if all elements are finished. If this is the case, then the DefaultContainer itself is finished by calling the finish method from the super-class WorkflowElement. |
![]() ![]() |
These methods are not used by the DefaultContainer. |
Operation
Implementing an Operation is done by deriving a class from the abstract super-class org.ontoware.almo.api.Operation. The following methods have to be implemented:
- public abstract void startOperation() - What happens when the Operation is started?
- public abstract void stopOperation() - What happens when the Operation is stopped?
- public void run() - Since Operations have to be implemented as Thread, the run method has to be overriden.
- public abstract void initObject(Object o) - Can be used to convey an arbitrary Object to the Operation (see later).
- public abstract void initPanel() - If Almo's GUI is used within an application, you can customize the panel of the Container with this method.
In our example we have used some implementations of Operations. Let us take a look at Operation2:
public class Operation2 extends Operation {
public Operation2(String s) {
super(s);
}
public void startOperation() {
this.setMaximum(10);
}
public void run() {
int i=0;
while (this.isRunning()) {
i++;
this.startWaiting();
try {
Thread.sleep(100);
}
catch (InterruptedException ex) {
}
this.stopWaiting();
try {
Thread.sleep(200);
}
catch (InterruptedException ex) {
}
if (i==2) {
this.error("error at 2");
}
this.setProgress(i);
this.print(this.getLabel()+": "+i);
if (i==10)
this.stopThread();
}
this.finishThread();
}
public void stopOperation() {
}
public void initObject(Object o) {
}
public void initPanel() {
}
}
![]() |
The constructor sets the label of the Operation. |
![]() |
When the Operation is started, the maximum of the GUI's progressbar is set to 10. |
![]() |
The Thread of the Operation simulates a process. |
![]() |
The isRunning method controls whether the Thread is still running. |
![]() |
The startWaiting method conveys to the Almo GUI that the Thread is waiting on some ressources. It has to be followed by the stopWaiting method. |
![]() |
The stopWaiting method conveys to the Almo GUI that the Thread is no longer waiting on some ressources. |
![]() |
The error method can be used to notify Almo about an error (see later). |
![]() |
The setProgress method sets the value of the progressbar of the Almo GUI. |
![]() |
The print method can be used to print messages (see later). |
![]() |
After 10 iterations the Thread is finished. Calling the stopThread method, will cause the isRunning method to return false. |
![]() |
The finishThread method has to be called at the end of the run method. Only if the stopThread method was called beforehand, the Operation is finished. |
![]() ![]() ![]() |
These methods are not used by this Operation. |
At the above code, you see that it can be a bit tricky to control the Thread of the Operation. Here is an overview of all relevant methods for controlling the Thread.
| Method | Purpose |
|---|---|
| isRunning | Indicates, whether the Thread of the Operation is running or not. |
| startThread | Starts the Thread associated with this Operation. |
| stopThread | Stops the Thread associated with this Operation. The isRunning method will return false after calling this method. |
| pauseThread | Pauses the Thread associated with this Operation. The isRunning method will return false after calling this method. |
| finishThread | This method has to be called at the end of the overriden run method. Only if the stopThread was called beforehand, the Operation will be finished. |
Furthermore you can call the error methods to convey an error to Almo. Depending on the error handling property of the Operation the error is ignored, collected or leads to the abortion of the Workflow (see Operation settings).
public void error(String msg)
Used to print and store an occurring error.
public void error(String msg, Exception ex)
In addition to only printing a message, you can also store the Exception that caused the error. This is useful, if you want to collect more detailed information about the error.
Other methods of elements
In addition to the methods you have to override, Almo offers some other methods that might be useful.
public void print(String s)
If the Almo GUI is used, this method prints the String on the DefaultTaskPanel. If the verbose value of the WorkflowInstance is set to 1, the String will be printed on the console.
public void initObject(Object o)
This method was mentioned above, but what the use of it? Imagine you have to access some ressources within your application via a Java object. You can use the initObject method to initialize this object. Example:
class DBOperation extends Operation {
DBConnection con;
...
public void initObject(Object o) {
this.con = (DBConnection)o;
}
...
public void run() {
...
this.con.doesSoemthing();
...
}
}
![]() |
You have an instance variable to access a database within your Operation class. |
![]() |
The initObject method initializes your instance variable. |
![]() |
So you can use it later. |
But this will only work, if you convey the initialized Java object to the createWorkflow methods of the class AlmoFactory.
DBConnection db = new DBConnection(...);
WorkflowConfiguration w = AlmoFactory.createWorkflow(false,db);
...
Operation op = new DBOperation("label");
someTask.addElement(op);
...
Adding more behaviour to your WorkflowConfiguration
So far, we have created a very simple WorkflowConfiguration, with some Tasks, Containers and Operations, that are all required. But Almo supports more functions. You can add optional elements to your WorkflowConfiguration, and by adding and removing them you allow the user of your workflow to build a flexible WorkflowInstance. Now we will take a look about the concepts of required and optional elements.
Required and optional elements
Required elements are the skeleton of your configuration. They may never be removed. But if you want to include elements, that may be added or removed, you can add optional elements. This is done by setting the usage property to optional.
Let us create a new Task and add two optional Operations to it.
...
Task t3 = new DefaultTask("t3");
w.addTask(t3);
Operation o5 = new Operation5("o5");
o5.setPackage("org.ontoware.almo.example");
o5.setClass("Operation5");
o5.setUsage(Constants.USAGE_OPTIONAL);
t3.add(o5);
Operation o6 = new Operation6("o6");
o6.setPackage("org.ontoware.almo.example");
o6.setClass("Operation6");
o6.setUsage(Constants.USAGE_OPTIONAL);
t3.add(o6);
...
![]() ![]() |
The setUsage method accepts a String with the new value for the usage. If the String is illegal an IllegalSettingsException is thrown. Make sure you use the Strings you find in the class Constants. |
The resulting WorkflowConfiguration looks like this:
If you use the Almo GUI in your application, you could remove and add the elements "o5" and "o6" now.
Include and exclude actions
Optional elements offer another important feature. As author of a workflow, you first build a skeleton of required elements, then you add optional elements to your workflow. But what happens if certain optional elements require or prohibit the use of other elements? In applications you should not confront the user with such problems. Instead, the user should just add and remove optional elements, and those elements, which should or should not be part of the workflow, are automatically included or excluded, respectively. Almo provides such a functionality.
If you load the WorkflowConfiguration we have created so far, you will only get the skeleton of required elements, unless you add initial optional elements to required parent elements. Let us do that for our last Task "t3".
t3.add_OnAddIncludeElement(o5);
Now the Operation "o5" will be added to Task "t3", when the WorkflowConfiguration is loaded.
But what happens, if either Operation "o5" or Operation "o6" shall be part of the Workflow? These elements should include and exclude themselves, respectively. So if "o5" is added, "o6" shall be removed and if "o6" is added, "o5" shall be removed. But we do not want the user to leave Task "t3" without any Operations. So if he decides to remove an Operation rather than to add it, we should cover this, too. So if "o5" is removed, then "o6" shall be added and if "o6" is removed, then "o5" shall be added. Here is the code for that:
o5.add_OnAddExcludeElement(o6); o6.add_OnAddExcludeElement(o5); o5.add_OnRemoveIncludeElement(o6); o6.add_OnRemoveIncludeElement(o5);
All the actions that should be carried out, when optional elements are added or removed, can be easily edited with the Almo application, but this should just give you an idea about this Almo feature, which can be important if you want to create a WorkflowInstance with the Almo API.
Creating a WorkflowInstance
One concept that might be confusing is that you can not create a WorkflowInstance from scratch. You have to build a WorkflowConfiguration first and derive a WorkflowInstance from that. The idea is that because of the distinction between required and optional elements and the actions that are performed, when optional elements are added or removed, the WorkflowInstance might be different every time.
Imagine that in your application you have some criterion that decides whether to use one element or another. Let us do that with our example and our optional Operations.
if (Math.random()>0.5) {
t3.removeElement(o6);
}
else {
t3.removeElement(o5);
}
The resulting WorkflowConfiguration either is:
| Math.random()>0.5 | OR | Math.random()<=0.5 |
![]() |
![]() |
From a given WorkflowConfiguration it is very easy to create a WorkflowInstance (Remember that a WorkflowInstance can only be generated, if all parent elements have at least one child element):
try {
w.createInstance();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalInstanceException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalSettingsException e) {
e.printStackTrace();
}
![]() |
The method createInstance creates the WorkflowInstance of the WorkflowConfiguration. The various Exceptions are thrown if "something" goes wrong. See the Javadoc Pages for more information. |
Once you have created the WorkflowInstance, you can get it by the getInstance method from the WorkflowConfiguration object. If you set the verbose value to 1, you will get all messages conveyed to the print method of the elements.
try {
WorkflowInstance i = w.getInstance();
i.setVerbose(1);
i.startWorkflow();
} catch (NoInstanceAvailableException e) {
e.printStackTrace();
}
![]() |
If no WorkflowInstance is available, the NoInstanceAvailableException is thrown by the getInstance method. |
![]() |
If the WorkflowInstance is available, execute the Workflow. |
Writing and reading the WorkflowConfiguration
The WorkflowConfiguration is written to an OutputStream by the write method. If the WorkflowConfiguration has a WorkflowInstance it is written to the OutputStream as well.
try {
w.write(new FileOutputStream("examples/tutorial_1.almo"),true);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
![]() |
The first argument of the write method is the OutputStream to write to, the second argument determines whether the OutputStream should be closed after writing. |
Reading a WorkflowConfiguration is done with the createWorkflow method from the class AlmoFactory. If a WorkflowInstance is available, you can get it with the getInstance method from the returned WorkflowConfiguration object.
try {
WorkflowConfiguration w = AlmoFactory.createWorkflow(false,new FileInputStream("examples/a.almo"),null);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalLabelException e) {
e.printStackTrace();
} catch (IllegalSettingsException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalInstanceException e) {
e.printStackTrace();
}
![]() |
The first argument of the createWorkflow method is used to configure log4j or not, the second argument is the InputStream to read from and the third argument is the Object used for the initObject method (see previous section). For more details about the various Exceptions consult the Javadoc Pages. |
Summary
In this section you have learnt the basics of the Almo API. Of course everything that we did here (apart from the implementation of Almo elements) can be done with the Almo application, but this section was meant to give you a brief overview. The next sections will summarize the settings of all elements and the basic methods that you need to add and remove elements from the Workflow.
| previous section: Understanding Almo 2.0: The Almo data model |
next section: Understanding Almo 2.0: The settings of Almo elements |


