A few paragraphs and examples about each of the expressions found in OpenWFEru.
For each expression, a link is provided to its “rdoc”, which might be pretty “empty”.
Usage examples are given in XML and / or as Ruby process definitions.
Expression names in Ruby process definitions are often preceded with an ’_’ (underscore) to help differentiate them from actual Ruby keywords and identifiers.
It’s also worth to have a look at how OpenWFEru implements each of the workflow control patterns.
Expressions are enumerated in the alphabetical order, but here is some kind of ‘importance order’ :
a‘a’ is a shortcut for the attribute expression.
attributeMakes for ugly process definitions, but anyway… With this expression, a complex value (like a list or a map) can be described directly in XML or YAML in the process definition.
rdoc for more information
cancel-processAmong the first expressions in the alphabetical order… When the workflow engine encounters this expression, it cancels completely the process instance to which it belongs.
<sequence>
<participant ref="before" />
<cancel-process />
<participant ref="after" />
</sequence>
It this example, the participant named “after” is never reached.
Most of the time, “cancel-process” is used as a consequence to an “if” expression or uses its own ‘if’ attribute, like in :
require 'openwfe/def'
class TestDefinition1 < OpenWFE::ProcessDefinition
sequence do
participant "customer"
_cancel_process :if => "${f:no_thanks} == true"
concurrence do
participant "accounting"
participant "logistics"
end
end
end
If the participant “customer” set the field “no_thanks” to “true”, the flow will never resume to the concurrence.
caseThe ‘case’ expression is an extended ‘if’. It accepts a list of children and expects the first one to be a condition to evaluate. If this child evaluates to true it will evaluate the second child as a consequence and then exit.
If the first child evaluates to false, it will jump to the third child. If there is none, it will exit, if there is one, it will evaluate it (and if it’s a condition…) (this is boring to explain).
require 'openwfe/def'
class DefinitionWithCase < OpenWFE::ProcessDefinition
case do
equals :field => "customer", :val => "El Wasabi"
participant :ref => "salesman_1"
equals :field => "customer", :val => "Rio Coffee"
participant :ref => "salesman_2"
participant :ref => "salesman_3"
end
#
# attributing customers to salesmen.
# salesman number 3 handles anything that is not 'El Wasabi' or
# 'Rio Coffee'
end
concurrenceDivides the process flow into two or more concurrent lanes.
<process-definition name="exp_mydef" revision="0.1">
<concurrence>
<participant ref="alice" />
<participant ref="bob" />
<sequence>
<participant ref="charlie" />
<participant ref="doug" />
</sequence>
</concurrence>
</process-definition>
With OpenWFEru you could also write :
require 'openwfe/def'
class MyProcessDefinition01 < OpenWFE::ProcessDefinition
_concurrence do
_participant :ref => "alice"
_participant "bob"
_sequence do
charlie
doug
end
end
end
Note that in this example, three different ways of ‘invoking’ a participant have been displayed. When used alone, participant names have not been preceded by an underscore.
rdoc (for an explanation on the merge attributes of the concurrence expression)
concurrent-iteratorThis is a cross between a concurrence and an iterator.
It understands the same attributes and behaves as should be : as an iterator spawning parallel copies of its child.
An explanation with some examples
See it in action in the workflow pattern 13, 14 and 15.
Some snippets, in XML :
<concurrent-iterator on-value="sales, logistics, lob2" to-field="p">
<participant field-ref="p" />
</concurrent-iterator>
In Ruby :
sequence do
set :field => f, :value => %w{ Alan, Bob, Clarence }
#...
concurrent_iterator :on_field => "f", :to_field => "p" do
participant "${p}"
end
end
cronA ‘cron’ expression will trigger its child expression (a new copy each time) at a determined frequency.
(Note : the ‘cron’ expression found in OpenWFEru 0.9.18 has a very different behaviour from previous versions)
This frequency can be either specified with a classical cron tab string (see man 5 crontab) or with an ‘every’ frequency, something like “every 2 hours” or “every 5 minutes and 2 seconds”.
<!--
trigger the subprocess 'send-reminder' once per hour from nine to
five, from Monday to Friday.
-->
<cron tab="0 9-17 * * mon-fri">
<send-reminder />
</cron>
#
# trigger the subprocess named 'send_reminder' every ten seconds.
#
cron :every => "10s"
send_reminder
end
The ‘cron’ expression never replies to its parent expression, thus, used a process whose body is simply a cron expression will never terminate (unless cancelled).
The classical usage scenario involves wrapping the cron in a concurrence not expecting its reply.
concurrence :count => 1 do
participant :toto
cron :every => "10m" do
subprocess :ref => "send_reminder" :target => "toto@headlost.org.uk"
end
end
where the subprocess “send_reminder” is triggered every 10 minutes until participant toto is done with his activity. Note that the ‘concurrence’ is set to wait for only 1 reply (it will then cancel the branch that didn’t reply, hence cancel the cron expression, which will get descheduled).
cursorThe ‘cursor’ expression is a sequence with extended capabilities.
It understands the following commands :
Cursor commands accept condition attributes like in :
<cursor>
<participant field-ref="reference_1"
<participant field-ref="reference_2"
<break if="${field:do_not_hire} == true" />
<!-- do not hire if one of the references set the field
'do_not_hire' to true -->
<participant ref="faculty" />
<participant ref="hr_department" />
</cursor>
More documentation of those conditions in the cursor command rdoc
The loop expression is a cursor that loops (you have to explicitely break or cancel it, to exit it).
cursor rdoc cursor command rdoc
The ‘cursor’ expression is used for example in the workflow control pattern 10 :Arbitrary Cycles.
defined <if>
<defined field="customer">
<!-- then -->
<subprocess ref="call_customer" />
</if>
Note that since OpenWFEru 0.9.17, it’s possible to write :
_if :test => "${field:customer} is set" do
sequence do
participant "A"
participant "B"
end
end
equalsThe ‘equals’ expression is mainly used nested within an if expression. It’s used to compare two values. A value may be a plain string, a variable or a workitem field (attribute).
Read more in the rdoc link that follows.
eval
expThe ‘exp’ expression allows to choose at apply time, which expression (or participant or subprocess) to use :
sequence do
my_activity :variant => "sequence"
#
# B works after A
my_activity :variant => "concurrence"
#
# A and B work in parallel
end
process_definition :name => "my_activity" do
exp :name => "${variant}", :default => "sequence" do
participant "A"
participant "B"
end
end
The ‘name’ (and the ‘default’) attribute(s) can refer to an expression, a subprocess name or a participant name, indifferently. See the following rdoc link for a more detailed example.
fIs a shortcut for “field”.
field
filterThe participant expression has a ‘filter’ attribute for filtering in and out the payload of a workitem.
There is a also a ‘filter’ expression for filtering data in and out of process segments.
class MyProcessDefinition001 < ProcessDefinition
sequence do
set :field => "readable", :value => "bible"
set :field => "writable", :value => "sand"
set :field => "randw", :value => "notebook"
set :field => "hidden", :value => "playboy"
alice
filter :name => "filter0" do
sequence
bob
charly
end
end
end
filter_definition :name => "filter0" do
field :regex => "readable", :permissions => "r"
field :regex => "writable", :permissions => "w"
field :regex => "randw", :permissions => "rw"
field :regex => "hidden", :permissions => ""
end
end
In that example, participant ‘alice’ will see and be able to modify all the fields of the workitem, whereas, participants ‘bob’ and ‘charly’ will be restricted to read the fields ‘readable’ and ‘randw’ and to write to the fields ‘sand’ and ‘randw’.
filter-definitionThere is already an example of a filter definition in the description of the filter expression.
A filter definition builds and then binds a filter to an OpenWFE variable. A filter may thus be bound locally (no prefix), at the process level (”/” prefix) or at the engine level (”//” prefix).
Filter definition takes place within a process definition. They may be set directly in the process body or outside of it (still within the process definition).
filter_definition :name => "filter0" do
field :regex => "readable", :permissions => "r"
field :regex => "writable", :permissions => "w"
field :regex => "randw", :permissions => "rw"
field :regex => "hidden", :permissions => ""
end
forgetThe ‘forget’ expression is used to set up dead ends.
It triggers its child expression and then immediately replies to its own parent expression, not caring about any potential reply from the child.
sequence do
participant :a
forget do
sequence do
participant :a0
participant :a1
end
end
participant :b
end
ifSee the exclusive choice workflow control pattern.
iteratorGiven a list and a child expression (one unique child is expected), ‘iterator’ will execute the child expression once per element in the list.
<iterator
on-value="alice, bob, charly"
to-variable="next_participant"
>
<participant ref="${next_participant}" />
</iterator>
is thus equivalent to
<sequence>
<participant ref="alice" />
<participant ref="bob" />
<participant ref="charly" />
</sequence>
Of course, the list could be a bit more dynamic :
iterator :on_value => "${f:participant_list}", :to_variable => "p" do
participant "${p}"
end
and since OpenWFEru 0.9.13, ‘iterator’ understands the same commands as ‘cursor’ (break/cancel, rewind/continue, skip, jump) :
iterator :on_value => "${f:participant_list}", :to_variable => "p" do
sequence do
participant "${p}"
_break :if => "${f:participant_reply} == cancel_meeting"
#
# if one participant wishes to cancel the meeting, no need
# to notify the others
end
end
iterator rdoc cursor command rdoc
listenThis expression is used to ‘listen’ on participant events (apply or reply, by default, at apply time) and triggers a piece of process.
<concurrence>
<listen to="alpha">
<subprocess ref="send-email-notification" />
</listen>
<sequence>
<participant ref="alpha" />
<participant ref="bravo" />
<participant ref="alpha" />
</sequence>
</concurrence>
Each time a workitem will be dispatched to participant “alpha” (applied), a copy of the workitem will be used to run an instance of the subprocess named “send-memail-notification”.
listen :to => "^customer_.*", :upon => "reply", :where => "${f:customer_name} == Acme" do
concurrence do
participant :ref => "operational_sales"
participant :ref => "strategic_sales"
end
end
Makes sure that when the next workitem coming back from any participant whose name begins with “customer_” and where the field customer_name is set to “Acme”, a copy of this workitem is used to trigger a small concurrence between two sales units.
The ‘listen’ expression is not limited to its own process instance, it listens to any participant event in the engine.
Since OpenWFEru 0.9.16, listen expressions without children are possible, then simply block, waiting for a ‘message’ and when it comes in, they let their process instance resume.
sequence do
listen :to => "^customer_.*", :upon => "reply"
subprocess :ref => "order_items"
end
Listen expressions without children only work ‘once’.
OpenWFEru 0.9.16 introduces as well the ‘intercept’ and ‘receive’ aliases for the ‘listen’ expression. The concept behind this expression being “listen to”, an ‘on’ attribute as been introduced has a alias to ‘to’, think “receive on” or “intercept on”.
receive :on => "^customer_.*"
0.9.16 also introduces a “merge” attribute, the default value is ‘false’. When set to true, the workitem used to trigger the listen expression’s nested child will be a merge of the original workitem (the one that activated the listen expression) and the incoming (the listened for) workitem.
listen :to => "^customer_.*", :merge => true
rdoc for more information
logLogs a message to the application log (if not specified otherwise logs/openwferu.log).
loopSee cursor.
loseLoses a process segment. The ‘lose’ expression applies its child expression, but it never replies to its parent expression.
May be useful in concurrence cases where a certain number of answers are expected.
<concurrence count="1">
<!-- this concurrence expects two answers -->
<lose>
<!-- will be applied, but 'lose' will never reply
to the concurrence -->
<participant ref="alice" />
</lose>
<participant ref="bob" />
<participant ref="charly" />
<!-- the concurrence will be over as soon as bob or charly
will have replied -->
</concurrence>
parameter‘parameter’ is not a real flow expression. It is used to state and check which required workitem/launchitem fields at present and valid at launch time.
<process-definition name="exp_x" revision="0">
<!-- required workitem fields -->
<parameter field="customer" />
<parameter field="address" default="" />
<parameter field="zip" type="integer" />
<parameter field="town" />
<parameter field="phone" match="^[0-9]{3}-[0-9]{7}$" />
<parameter field="customer_type" default="2" type="int"/>
<!-- the body of the process definition -->
<sequence>
<participant ref="service0" />
<participant ref="service1" />
<play-the-music/>
</sequence>
<!-- subprocesses -->
<process-definition name="exp_play-the-music">
<participant ref="musician" />
</process-definition>
</process-definition>
parameters (‘param’ is valid as well) have to be located outside of the body of the process definition (as a direct child of the ‘process-definition’ tag).
In that example, the process launch will immediately fail (even if the launch is asynchronous), if the launchitem doesn’t contain the fields (attributes) ‘customer’, ‘zip’, ‘town’ and ‘phone’. If the field ‘zip’ is set to something that can be converted to an integer, the launch will fail. If the ‘phone’ field value doesn’t match the given regular expression, the launch will fail. If the field ‘address’ is missing, it will be replaced by an empty string (the default value for that field). If the field ‘customer_type’ is not explicitely given, its value will be the integer ‘2’.
In a ruby process definition, it looks like :
require 'openwfe/def'
class MyProcessDefinition_01 < OpenWFE::ProcessDefinition
#
# required fields in the launchitem
param :field => "customer"
param :field => "address", default => ""
param :field => "zip", type => "integer"
param :field => "town"
param :field => "phone", match => "^[0-9]{3}-[0-9]{7}$"
param :field => "customer_type", default => 2, type => :int
#
# process definition body
sequence do
service0
service1
play_the_music
end
#
# subprocesses
process_definition :name => "play_the_music"
musician
end
end
participantThis expression is one of the most important in OpenWFE (it’s featured in most of the process definition examples of this page).
A participant is usually located outside of the engine and waits for workitem to be delivered by the engine.
Participants are registered in the participant-map of the engine.
Multiple process definitions may share the same participants (processes != organizational chart).
A classic participant may be a workitem store, queried by a web application for human participants in business processes.
participant implementations shipping with OpenWFEru
rdoc for more information
process-definitionUsually the “process_definition” name is used for this expression, but “define” and “workflow_definition” are usable as well.
process_definition :name => "my_process", :revision => "y"
sequence do
participant "alpha"
participant "bravo"
end
end
The same definition, in XML :
<process-definition name="my_process" revision="y">
<sequence>
<participant ref="alpha" />
<participant ref="bravo" />
</sequence>
</process-definition>
In a Ruby process definition, this is also OK :
class MyDef0 < OpenWFE::ProcessDefinition
sequence do
sub0
sub1
end
process_definition :name => "sub0" do
participant "cold"
end
definition "sub1" do
#
# ':name => ' not used
participant "not cold"
end
end
print(only used for testing and debugging purposes)
qIs a shortcut for “quote”.
quote
redoEvery expression accepts an implicit ‘tag’ attribute which defines a name under which a copy of the expression before evaluation is kept along with the workitem as applied (as received by the expression tagged).
The name (the tag) can then be used by a ‘redo’ or an ‘undo’ expression.
sequence do
sequence :tag => "core_job" do
participant "alice"
participant "bob"
end
participant "charly"
_if :test => "${f:charly_not_happy} == true" do
_redo :ref => "core_job"
end
end
Maybe this example should be ‘implemented’ with a
related : using tags for signalling ‘process state’
reserveThis expression ensures that segments of a process instance are not run simultaneously.
It uses a mutex variable. A variable bound at engine level (prefixed with ”//”) can be used to prevent segments of totally different processes to execute simultaneously.
concurrence do
sequence do
activity_a
reserve :mutex => :m do
activity_b
end
activity_c
end
sequence do
activity_d
reserve :mutex => :m do
sequence do
activity_e
activity_f
end
end
end
end
In this example, the while the activity_b is performed, the sequence of activities e and f cannot be performed.
Note that for short, a symbol :m was used instead of the string “m”. OpenWFEru turns the symbol into the corresponding string.
<!-- process A -->
<process_definition name="A" revision="0.0">
<sequence>
<participant ref="alice" />
<reserve mutex="//guard0">
<participant ref="alfred" />
</reserve>
</sequence>
</process_definition>
<!-- process B -->
<process_definition name="B" revision="0.0">
<sequence>
<participant ref="bob" />
<reserve mutex="//guard0">
<participant ref="berthe" />
</reserve>
</sequence>
</process_definition>
In this XML example, the engine will prevent ‘alfred’ and ‘berthe’ from performing their task simultaneously, in processes A and B respectively. This is achieved by registering the mutex at the engine level (prefix ”//”).
See the interleaved parallel routing workflow control pattern
restoreRestores the payload of a workitem previously saved in a variable or copies the value of a [sub] field as the top value of the workitem payload.
There are many options for this expression. Check the rdoc for a detailed explanation.
Since OpenWFEru 0.9.17, ‘restore’ has a ‘set-fields’ alias. It can be quite convenient for setting the payload of a workitem at the beginning of a business process.
class Registration < OpenWFE::ProcessDefinition
set_fields :value => {
"name" => "",
"address" => "",
"email" => ""
}
sequence do
approve_registration
perform_registration :if => "${approved} == true"
notify_customer
end
end
(The usual technique for pre-filling a workitem is by providing a launchitem when launching the business process
li = OpenWFE::LaunchItem.new "http://process.server.com/definitions/xyz.xml"
li.customer = { "name" => "Toto", "age" => 34 }
li.request_type = "credit"
openwferu_engine.launch li
)
revalThis expression evals the Ruby code nested within it.
<sequence>
<reval>$i = 0</reval>
<loop>
<participant ref="toto" />
<reval>$i = $i + 1</reval>
<break if="${r:$i == 10}" />
<!-- a variant :
<break rif="$i == 10" />
-->
</loop>
</sequence>
Within a ‘reval’ piece of code, the two principal hooks within the engine ‘bowels’ are self which points to the RawExpression instance itself and workitem
class Reval4 < OpenWFE::ProcessDefinition
sequence do
reval """
engine = self.application_context['__tracer']
workitem.currently_active_processes =
engine.list_processes.join('\n')
'done' # will be placed in the workitem field '__result__'
"""
participant :toto
end
end
The result of ‘reval’ is placed in the field named
__result__as a String.
The code is evaluated at a SAFE level of 3 (see http://www.rubycentral.com/book/taint.html for more information about those levels).
You have to explicitely set the :ruby_eval_allowed param to true in the application context of the engine else the ‘reval’ expression will raise an exception.
engine.application_context[:ruby_eval_allowed] = true
saveSaves a workitem to a variable or save the attributes of a workitem in a field (of that same workitem).
Is used in conjunction with restore.
sequenceSimply chaining other flow expressions.
<process-definition name="exp_my_definition" revision="0">
<sequence>
<participant ref="alpha" />
<participant ref="bravo" />
<participant ref="charly" />
</sequence>
</process-definition>
In Ruby :
require 'openwfe/def'
class MyDefinition0 < OpenWFE::ProcessDefinition
sequence do
participant "alpha"
participant "bravo"
participant "charly"
end
end
setIs used to set the value of a variable or the value of a workitem field (attribute).
It may placed outside of the body of a process definition, it will be evaluated before the body gets applied (executed).
require 'openwfe/def'
class MyDefinition0 < OpenWFE::ProcessDefinition
sequence do
participant "alpha"
end
set :field => "city", :value => "Kyoto"
set :field => "country", :value => "Japan"
end
The participant ‘alpha’ will recevive a workitem with fields ‘city’ and ‘country’ set to ‘Kyoto’ and ‘Japan’ respectively. This technique is useful for keeping long settings out of the process ‘logic’ itself.
The unset cannot be placed outside of a process body. Filter definitions can.
Shorter version of the attributes are OK :
require 'openwfe/def'
class MyDefinition0 < OpenWFE::ProcessDefinition
sequence do
set :f => "customer_name", :val => "(fill this field)"
participant "alpha"
end
end
The ‘set’ expression accepts an ‘escape’ attribute to prevent dollar substituation from occurring.
set :v => "v0", :val => "my ${template} thing", escape => true
set-fields“set-fields” is an alias for restore.
sleepMakes a process segment sleep for a while.
<sequence>
<sleep for="3d" />
<!-- sleeping for 3 days -->
<sleep until="Mon Dec 03 10:48:03 +0900 2007" />
</sequence>
sequence do
sleep :for => "3m10s"
# sleeping for 3 minutes and 10 seconds
sleep :until => "Mon Dec 03 10:48:03 +0900 2007"
# sleeping until some point in time
sleep "3M10d"
# sleeping for 3 months and 10 days
end
subprocessThis expression is used to trigger a subprocess.
<process-definition name="chores" revision="0">
<sequence>
<subprocess ref="take_out_garbage" />
<subprocess ref="do_the_laundry" type="white" />
<subprocess ref="do_the_laundry" type="colors" />
</sequence>
<process-definition name="take_out_garbage">
<concurrence>
<participant ref="al" activity="take out old paper" />
<participant ref="bob" activity="take out garbage" />
</concurrence>
</process-definition>
<process-definition name="do_the_laundry">
<sequence>
<participant ref="al" activity="sort ${type}" />
<participant ref="bob" activity="clean ${type}" />
<sleep for="1h30m" />
<participant ref="al" activity="hang ${type}" />
<participant ref="al" activity="iron ${type}" />
</sequence>
</process-definition>
</process-definition>
Note how the variable ‘type’ was set to ‘white’ and then ‘colors’ and used within the “do_the_laundry” subprocess.
class Chores0 < OpenWFE::ProcessDefinition
sequence do
subprocess :ref => "take_out_garbage"
do_the_laundry :type => "white"
subprocess :ref => "do_the_laundry", :type => "colors"
end
process_definition :name => "take_out_garbage" do
concurrence do
participant :ref => "al", :activity => "take out old paper"
bob :activity => "take out garbage"
end
end
process_definition :name => "do_the_laundry" do
sequence do
participant :ref => "al", :activity => "sort ${type}"
participant :ref => "bob", :activity => "clean ${type}"
sleep :for => "1h30m"
al :activity => "hang ${type}"
al :activity => "iron ${type}"
end
end
end
Note how the subprocess name (and the participant names) can be used as expression names for a shorter syntax. Of course names with white spaces and other niceties won’t work for this shorter syntax.
If the engine :remote_definition_allowed parameter has been set to true, it’s OK to reference process definitions via their URL :
sequence do
participant "al"
subprocess :ref => "http://company.process.server/defs/defXYZ.xml"
end
timeoutA generic timeout mechanism : sets a timeout for a whole process segment, not just a single participant or when expression.
<timeout after="1w">
<concurrence>
<participant ref="alpha" />
<subprocess ref="do_the_homework" />
</concurrence>
</timeout>
_timeout :after => "2d" do
sequence do
participant :ref => "alpha", :timeout => "1d"
# of course individal timeout settings are still ok
participant :ref => "bravo"
end
end
Note that when using a Ruby process definition, you’d better escape the ‘timeout’ expression with an underscore at the front to avoid collisions with the ‘timeout’ method provided by the timeout.rb standard Ruby library.
undefined <if>
<undefined field="customer">
<!-- then -->
<subprocess ref="determine_customer" />
</if>
Note that since OpenWFEru 0.9.17, it’s possible to write :
_if :test => "${field:customer} is not set" do
sequence do
participant "A"
participant "B"
end
end
undoEvery expression accepts an implicit ‘tag’ attribute which defines a name under which a copy of the expression before evaluation is kept along with the workitem as applied (as received by the expression tagged).
The name (the tag) can then be used by a ‘redo’ or an ‘undo’ expression.
concurrence do
sequence :tag => "side_job" do
participant "alice"
participant "bob"
end
sequence do
participant "charly"
_undo :ref => "side_job"
end
end
In this example, after participant ‘charly’ did his job, the first sequence (tagged ‘side_job’) will get cancelled (and its reply to the parent ‘concurrence’ will get triggered).
See also the redo expression.
related : using tags for signalling ‘process state’
unset
vIs a shortcut for “variable”.
variable
whenThe ‘when’ expression polls a conditional statement and triggers the nested process segment when the condition evaluates to true.
<concurrence>
<participant ref="alpha" />
<when test="${for_bravo} == true">
<participant ref="bravo" />
</when>
<participant ref="charly" />
</concurrence>
When the variable “for_bravo” will contain the value “true”, participant “bravo” will receive a workitem.
By default ‘when’ evaluates the condition until true every 10 seconds.
concurrence do
alpha
when :test => "${for_bravo} == true", :frequency => "1h", :timeout => "13d4h" do
bravo
end
charly
end
In this Ruby process definition (where all the participant expressions where simplified to just their names), the when condition is checked only every hour and after thirteen days and four hours, the ‘when’ is cancelled (and participant bravo will not receive a workitem).
Note that in both examples, the concurrence will wait until the when expression resumes the process.
waitThis expression is very similar to when, but it doesn’t accept a nested expression. It blocks until its ‘until’ condition evaluates to true or until it times out.
May make some process definitions more readable.
rdoc for more information