Programmatic use
A workflow can also be started and advanced via the FirstSpirit Developer API. This enables instances of a workflow to be run both via scripts within the FirstSpirit SiteArchitect or the FirstSpirit ContentCreator and on the server side via schedule scripts (prior to generation, for example) or via external implementation within a FirstSpirit module.
The WorkflowAgent
Interface: de.espirit.firstspirit.workflow.WorkflowAgent
Access API documentation: WorkflowAgent
An instance of type WorkflowAgent is needed to start and advance a workflow. Such an instance can be requested via a project-specific SpecialistsBroker (or extensions of the interface, e.g., BaseContext) using the requestSpecialist(SpecialistType) and requireSpecialist(SpecialistType) methods (for information on the differences between these methods, see Using FirstSpirit functionality / Obtaining agents). The corresponding SpecialistType, in this case the constant WorkflowAgent.TYPE, is specified as the parameter.
import de.espirit.firstspirit.workflow.WorkflowAgent;
// The variable 'context' used below is of type de.espirit.firstspirit.access.BaseContext,
// an extension interface of the interface de.espirit.firstspirit.agency.SpecialistsBroker.
final WorkflowAgent workflowAgent = context.requireSpecialist(WorkflowAgent.TYPE);
Starting a workflow
Depending on their purpose, workflows can be started either in context (acting on a store element) or without any context.
Workflows without any context
WorkflowProcessContext WorkflowAgent#startWorkflow(Workflow)
A workflow without any context can be started via the startWorkflow(Workflow) method. During execution, the method generates a Task internally. The method returns an instance of type WorkflowProcessContext.
Workflows in context
WorkflowProcessContext WorkflowAgent#startWorkflow(Workflow, IDProvider)
A workflow in context (i.e., acting on an element of type IDProvider) can be started via the startWorkflow(Workflow, IDProvider) method. During execution, the method generates a Task internally. The method returns an instance of type WorkflowProcessContext.
Advancing a workflow
A workflow which has already been started can be advanced via the WorkflowAgent.
Advancing a workflow
WorkflowProcessContext WorkflowAgent#process(Task, Transition)
To advance a workflow which has already been started, the current Task from the WorkflowProcessContext instance used most recently and the Transition to be advanced must be transferred to this method as parameters. The transition must be outgoing from the current workflow state; it can be determined using the Task#getTaskState().getModelState().getTargetTransitions() method.
The WorkflowProcessContext
Interface: de.espirit.firstspirit.workflow.WorkflowAgent$WorkflowProcessContext
Developer API documentation: WorkflowProcessContext
The methods named above each return an instance of type WorkflowProcessContext, which represents a manual activity within the workflow model. The interface provides methods for processing this activity.
Each instance of this type can only be used for one advance operation (via the doTransition(Transition) method), then it will be marked as ended (isActivityProcessed()). This means that a new instance of type WorkflowProcessContext must be obtained after each time a transition is advanced (provided that the workflow has not already reached its end state or an error state), in order to advance the next transition.
Automatic activities do not have to be explicitly advanced via the FirstSpirit API either; instead, they are automatically advanced to the state which follows the respective automatic activity. |
Example
The example below illustrates a code flow, which starts a “Request release” workflow on a FirstSpirit element and advances the transitions from the initial state “Object changed” to the target state “Object not released”.
The script sample below uses the standard workflows that may be installed when creating a new FirstSpirit project. The standard release workflow uses German reference names such as "Freigabe Anfordern" (Request Release) and "anfordern" (request), which appear in the sample script below. In the descriptive text below the script sample, English translations of these reference names are used, followed by the corresponding actual reference name in [square brackets]. The script may be configured as a menu script and assumes that a page element with the reference name example_page exists in the Page Content store. |
//!Beanshell
import de.espirit.firstspirit.workflow.WorkflowAgent;
import de.espirit.firstspirit.agency.StoreAgent;
import de.espirit.firstspirit.access.store.IDProvider;
import de.espirit.firstspirit.access.store.Store;
storeAgent = context.requireSpecialist(StoreAgent.TYPE);
workflowAgent = context.requireSpecialist(WorkflowAgent.TYPE);
pageElement = storeAgent.getStore(Store.Type.PAGESTORE, false)
.getStoreElement("example_page", IDProvider.UidType.PAGESTORE);
releaseWorkflow = storeAgent.getStore(Store.Type.TEMPLATESTORE)
.getWorkflowByName("Freigabe Anfordern");
// START A WORKFLOW
// Obtain a first workflow process context object.
firstWorkflowProcessContext = workflowAgent.startWorkflow(releaseWorkflow, pageElement);
task = firstWorkflowProcessContext.getTask();
print ("=== FIRST Workflow Process Context ===");
for (transition : firstWorkflowProcessContext.getTransitions()) {
if ("anfordern".equals(transition.getUid())) {
// We're only going to work with the transition named "anfordern".
// If we found it, output some info about the current state to the console:
print ("State: " + task.getTaskState().getModelState());
print ("Activity: " + task.getTaskState().getModelActivity());
print ("Outgoing transitions from current activity: "+ firstWorkflowProcessContext.getTransitions());
print ("Transition from current activity to next state (selected by script): " + transition);
print ("Is first activity processed? " + firstWorkflowProcessContext.isActivityProcessed());
// Do transition
firstWorkflowProcessContext.doTransition(transition);
break;
}
}
// ADVANCE A WORKFLOW
// Obtain a new workflow process context object by processing the transition "prüfen",
// which is available on the state "Freigabe angefordert".
secondWorkflowProcessContext = workflowAgent.process(task,
task.getTaskState().getModelState().getTargetTransitions().get(0));
task = secondWorkflowProcessContext.getTask();
print ("=== SECOND Workflow Process Context ===");
for (transition : secondWorkflowProcessContext.getTransitions()) {
if ("nicht erteilen".equals(transition.getUid())) {
// We're only going to work with the transition named "nicht erteilen".
// If we found it, output some info about the current state to the console:
print ("State: " + task.getTaskState().getModelState());
print ("Activity: " + task.getTaskState().getModelActivity());
print ("Outgoing transitions from current activity: "+ secondWorkflowProcessContext.getTransitions());
print ("Transition from current activity to next state (selected by script): " + transition);
print ("Is first activity processed? " + firstWorkflowProcessContext.isActivityProcessed());
print ("Is second activity processed? " + secondWorkflowProcessContext.isActivityProcessed());
// Do transition
secondWorkflowProcessContext.doTransition(transition);
break;
}
}
Initially, the “Request Release [Freigabe Anfordern]” workflow, a page with the UID example_page, on which the workflow is to be started, and a specialist of type WorkflowAgent are obtained. Then, the workflow is started via the workflowAgent.startWorkflow(Workflow, IDProvider) method. Once the method has been executed, the workflow has already exited the “Object changed [Objekt verändert]” state (so the first transition toward the “Request release [Freigabe anfordern]” activity has already been advanced automatically).
The returned instance of type WorkflowProcessContext (variable firstWorkflowProcessContext) represents the next manual activity, “Request release [Freigabe anfordern]”, of the workflow. This activity has not been processed yet (i.e., firstWorkflowProcessContext.isActivityProcessed() delivers false), so the firstWorkflowProcessContext.doTransition(Transition) method can be called up on the instance.
Log output before the “request [anfordern]” transition is advanced:
=== FIRST Workflow Process Context ===
State: Objekt verändert
Activity: null
Outgoing transitions from current activity: [anfordern]
Transition from current activity to next state (selected by script): anfordern
Is first activity processed? false
In the next step, the workflow is advanced via the firstWorkflowProcessContext.doTransition(Transition) method to the “Release requested [Freigabe angefordert]” state.
To reach the next state from the current activity, a transition always needs to be transferred, which connects the activity to be processed and the next desired state. In this example, the “request [anfordern]” transition must be selected to reach the “Release requested [Freigabe angefordert]” state from the “Request release [Freigabe anfordern]” activity. Once the workflow has been started, the internally generated task is still in the “Object changed [Objekt verändert]” state, since the subsequent activity has not been processed yet. At this point, the available transitions cannot be determined via the internally generated task (task.getTaskState().getModelState().getTargetTransitions()); rather, they must be queried via the current WorkflowProcessContext (firstWorkflowProcessContext.getTransitions()). |
The “Release requested [Freigabe angefordert]” state has just one outgoing transition, “check [prüfen]”, which leads to the “check release [Freigabe prüfen]” activity. This transition is advanced via the workflowAgent.process(task, task.getTaskState().getModelState().getTargetTransitions().get(0)) method.
The WorkflowProcessContext object returned after the “check [prüfen]” transition has been processed is reserved as the variable secondWorkflowProcessContext; information on the current (not yet processed) “check release [Freigabe prüfen]” activity can be read out from this variable.
Log output after the “check [prüfen]” transition has been advanced:
=== SECOND Workflow Process Context ===
State: Freigabe angefordert
Activity: Freigabe anfordern
Outgoing transitions from current activity: [nicht erteilen, erteilen]
Transition from current activity to next state (selected by script): nicht erteilen
Is first activity processed? true
Is second activity processed? false