If you take a look at the second of the behaviours relevant to TaskInstances
from the list above, you’ll notice something we haven’t shown you yet:
package net.enilink.komma.example.behaviour.runtime;
import net.enilink.composition.annotations.Precedes;
import net.enilink.composition.traits.Behaviour;
import net.enilink.komma.example.behaviour.IActivityInstance;
import net.enilink.komma.example.behaviour.ITaskInstance;
import net.enilink.komma.example.behaviour.model.TaskInstance;
@Precedes(ActivityInstanceSupport.class)
public abstract class TaskInstanceSupport implements TaskInstance,
Behaviour<TaskInstance>, ITaskInstance {
@Override
public boolean execute() {
// this is a wait-state, not a normal activity
setState(STATE_AWAITING_COMPLETION);
// returning true here signals the end of the method chaining; in other
// words, ActivityInstanceSupport.execute() will NOT be called
return true;
}
@Override
public boolean complete(String transitionName) {
if (!STATE_AWAITING_COMPLETION.equals(getState())) {
throw new IllegalStateException("Task " + getURI()
+ " is not awaiting completion.");
}
((IActivityInstance) getBehaviourDelegate()).leave(transitionName);
return true;
}
}
This behaviour uses the @Precedes annotation to impose an order on the
otherwise unordered set of behaviours available for an entity. As detailed in
the Object Composition documentation,
this will affect the method chaining mechanism by enforcing an invocation
order. For our specific case, this means that a call to a method of
IActivityInstance will execute the appropriate method of
TaskInstanceSupport before that of ActivityInstanceSupport (this is due to
ITaskInstance extending IActivityInstance, as noted above).
An important aspect of the method chaining is the influence of the return
value. Returning something non-null (or a boolean true) results in the chain
being stopped, whereas returning null (or false) causes the chain to continue
with the method on the next behaviour.
Note
|
Imagine the return value as a kind of flag indicating something like
"We’re done here, I managed to come up with the answer".
|
With that being said, let’s look at the third behaviour relevant to
ActivityInstances (and therefore, as mentioned, to TaskInstances as well)
from the list above. This is more interesting, because it relies on @Precedes
as well as on the subtleties of return value and invocation chain:
package net.enilink.komma.example.behaviour.runtime;
import net.enilink.composition.annotations.Precedes;
import net.enilink.composition.traits.Behaviour;
import net.enilink.komma.example.behaviour.IActivityInstance;
import net.enilink.komma.example.behaviour.IProcessInstance;
import net.enilink.komma.example.behaviour.model.ActivityInstance;
@Precedes(ActivityInstanceSupport.class)
public abstract class EndInstanceSupport implements ActivityInstance,
Behaviour<ActivityInstance>, IActivityInstance {
@Override
public boolean execute() {
// simplicity: name=="end" designates the end activity
if (!"end".equals(getUsesDefinition().getName())) {
// returning false causes the invocation of this method on the next
// behaviour in the chain (here: ActivityInstanceSupport.execute())
return false;
}
setState(STATE_COMPLETED);
// end activities just terminate the enclosing process instance
// they cannot be left as there are no outgoing transitions
((IProcessInstance) getProcessInstance()).end();
return true;
}
}
Prior to explaining this in detail, let us review our example.
In an effort to keep the process model simple, End and Start are just plain
activities, only distinguished by their names ("start" or "end", respectively).
For the implementation, this means that any special behaviour needs to be bound
to the base entity ActivityInstance and as such results in it being applied
to all such instances, including those for Tasks.
Note
|
*,** Hopefully, this also clears up any questions about why this special
behaviour for End should be relevant to Tasks at all.
|
There’s nothing special to Start though, it has outgoing transitions like
other activities and it really just happens to be the initial activity in a
process (see ProcessInstanceSupport.start()), so it uses the default
behaviour. An End activity, however, is the final activity in a process,
which it should end(), and it also lacks outgoing transitions, which are the
reasons for binding this third behaviour class to ActivityInstance.
As for the execute() method, consider what you learned about @Precedes and
the method chaining. EndInstanceSupport precedes ActivityInstanceSupport
(no particular order with respect to TaskInstanceSupport given) and is going
to be called for all activities. It is, therefore, imperative that the
execute method does its special handling only when it actually deals with an
End activity, and falls back to the chain in all other cases (remember that
returning something null or a boolean false means that the chain continues with
- or falls back to - the next implementation up the chain).