OSWorkflow is very unique compared to other workflow engines one might be familiar with. In order to completely grasp OSWorkflow and properly harness the features available, it is important that one understand the core concepts that form the foundation for OSWorkflow.
Any particular workflow instance has one and only one status at any given moment. That status is actually the status of the current step for that workflow instance. Currently OSWorkflow allows for only three possible status's: Queued, Underway, and Finished. The typical lifecycle of a step goes from queued, to underway, and finally to finished. Once a step is finished, it usually results in a new current step being created, which keeps the workflow going.
This behavior is possible via actions. Actions are nothing more than different possible transitions from one state (which is nothing more than a step/status combination) to another state. The transitions are decided by the end user, from a selection of all possible actions. Actions can be restricted to particular groups and users, as well as restrictions based on the current state of the workflow. Each action must have one unconditional result, as well as zero or more conditional results.
For every action, it is required that there exist one result, called the unconditional-result. A result is nothing more than a series of directives that tell OSWorkflow what the next task to do is. This involves making a transition from one state to the next state in the state machine that makes up a given workflow. Required for a result is the new step ID and the step status. If the status is "Underway", then a third requirement is the owner of the new step. Without an owner, the step will fall back to "Queued". Besides holding information about the next state, results also can specify validators and post-functions. These will be discussed below.
A conditional result is an extension of an unconditional result. It is the same exact thing, except it requires one extra input: condition. This condition is written in the BeanShell language and is discussed here. Basically, the first conditional result that has a true condition expression will dictate the result of any given action taken by the user.
OSWorkflow defines a standard way for business logic and services that our outside the realm of workflow-specific needs to be executed. This is accomplished by using "functions". There are two types of functions: pre and post step functions. Pre functions are functions that are executed before the workflow makes a particular transition. An example is a pre function which sets up the name of the caller to use as the result for the transition that is about to be made; or a function which updates the most recent caller of an action. Both of these are provided as standard utility functions that are very useful for practical workflows.
Post functions have the same range of applicability as pre functions, except that they are executed after the transition has been made. An example of post function is one which sends out an email to interested parties that the workflow has had a particular action performed on it. For example, when a document in the 'research' step has a 'markReadyForReview' action taken, the reviewers group is emailed.
There are many reasons for including pre and post functions. One is that if the user were to click the "done" button twice and to send out two "execute action" calls, and that action had a pre function that took a long time to finish, then it is possible the long function could get called multiple times, because the transition hasn't been made yet, and OSWorkflow thinks the second call to do the action is valid. So changing that function to be a post function is what has to happen. Generally pre functions are for simple, quick executions, and post are where the "meat" goes.
Functions can be implemented as either BeanShell scripts or Java objects available directly via a ClassLoader or indirectly via JNDI. Java functions must implement the com.opensymphony.workflow.FunctionProvider interface to be properly registered. Functions that implement the FunctionProvider interface are given three Maps to work with: inputs, args, and variables (in that order).
The inputs Map is the exact Map passed by the client code that called Workflow.doAction(). This is useful for functions that behave differently based on user input when the action is finished.
The args Map is a map that contains all the <arg/> tags embedded in the <function/> tag. These arguments are all of type String and have been parsed for any variable interpolation. This means that <arg name="foo">this is ${someVar}</arg> would result in a mapping from "foo" to "this is [contents of someVar]".
The variables Map contains all the persistent variables associated with the workflow instance. It also contains all the variables configured in Registers (see below) as well as the following two special variables: entry (com.opensymphony.workflow.persistence.WorkflowEntry) and context (com.opensymphony.workflow.WorkflowContext).
BeanShell functions have no arguments. They can access the inputs map via the variable inputs. All other variables, including registers and special variables, are automatically found in the BeanShell scope.
Trigger functions are just like any other function, except that they aren't
associated with only one action. They are also identified with a unique ID that
is used at a later time (when a trigger is fired) to be executed by the Quartz
job scheduler. These functions usually run under the context of a system user
and not a regular user working in the workflow. More information about triggers
will be explained in the section, Utility Functions.
A validator is nothing more than some code that validates the input that can be paired with an action. If the input is deemed to be valid, according to the validator, the action will be executed. If the input is invalid, the InvalidInputException will be thrown back to the calling client -- usually a JSP or servlet.
Validators, like functions, can be in the form of a Java object (class or JNDI access) or a BeanShell script.
A Java-based validator must implement com.opensymphony.workflow.Validator. Java-based validators can exceptions, while BeanShell validators actually return the exception. Java-based validators are given two arguments, as explained above in the section about functions: a Map of inputs and a Map of variables. BeanShell validators can access the input map by accessing the variable inputs.
A register is a Java object (BeanShell is not supported here) that registers objects not related to workflow in the BeanShell scope or variable Map (for Class and JNDI functions) so that it may be used in functions, validators, and conditional expressions. A register class must implement com.opensymphony.workflow.Register. The object being registered can be any kind of object. Typical examples of objects being registered are: Document, Metadata, Issue, and Task.
Permissions can be assigned to users and/or groups based on the state of the workflow instance. These permissions are unrelated to the functionality of the workflow engine, but they are useful to have for applications that implement OSWorkflow. For example, a document management system might have the permission name "file-write-permission" enabled for a particular group only during the "Document Edit" stage of the workflow. That way your application can use the API to determine if files can be modified or not.
Permissions and actions both use the concept of restrictions. A restriction lists the step and status that the workflow instance must be in for the permission or action to be valid. The restriction also contains a list of groups that the user must have at least one membership to be able to execute an action or receive a permission. The owner of the current step in the workflow can be set to be explicitly allowed, explicitly denied, or neither ("use_groups").
Because OSWorkflow is not an out-of-the-box solution, some development work is required to make your project work correctly with OSWorkflow. It is recommended that your core entity, such as "Document" or "Order", be given a new attribute: workflowId. That way when a new Document or Order is created, it can be associated with a workflow instance also. Then your code can look up that workflow instance and retrieve workflow information and/or issue workflow actions via the OSWorkflow API.