A few paragraphs and examples about each of the expressions found in Ruote (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 Ruote 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.
See the restore expression for some examples.
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.
1 <sequence> 2 <participant ref="before" /> 3 <cancel-process /> 4 <participant ref="after" /> 5 </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 :
1 require 'openwfe/def' 2 3 class TestDefinition1 < OpenWFE::ProcessDefinition 4 sequence do 5 participant "customer" 6 _cancel_process :if => "${f:no_thanks} == true" 7 concurrence do 8 participant "accounting" 9 participant "logistics" 10 end 11 end 12 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).
1 require 'openwfe/def' 2 3 class DefinitionWithCase < OpenWFE::ProcessDefinition 4 case do 5 equals :field => "customer", :val => "El Wasabi" 6 participant :ref => "salesman_1" 7 equals :field => "customer", :val => "Rio Coffee" 8 participant :ref => "salesman_2" 9 participant :ref => "salesman_3" 10 end 11 # 12 # attributing customers to salesmen. 13 # salesman number 3 handles anything that is not 'El Wasabi' or 14 # 'Rio Coffee' 15 end
concurrenceDivides the process flow into two or more concurrent lanes.
1 <process-definition name="exp_mydef" revision="0.1"> 2 <concurrence> 3 <participant ref="alice" /> 4 <participant ref="bob" /> 5 <sequence> 6 <participant ref="charlie" /> 7 <participant ref="doug" /> 8 </sequence> 9 </concurrence> 10 </process-definition>
With Ruote you could also write :
1 require 'openwfe/def' 2 3 class MyProcessDefinition01 < OpenWFE::ProcessDefinition 4 concurrence do 5 participant :ref => "alice" 6 participant "bob" 7 sequence do 8 charlie 9 doug 10 end 11 end 12 end
Note that in this example, three different ways of ‘invoking’ a participant have been displayed.
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 :
1 <concurrent-iterator on-value="sales, logistics, lob2" to-field="p"> 2 <participant field-ref="p" /> 3 </concurrent-iterator>
In Ruby :
1 sequence do 2 set :field => f, :value => %w{ Alan, Bob, Clarence } 3 #... 4 concurrent_iterator :on_field => "f", :to_field => "p" do 5 participant "${f:p}" 6 end 7 end
cronA ‘cron’ expression will trigger its child expression (a new copy each time) at a determined frequency.
(Note : the ‘cron’ expression found in Ruote 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”.
1 <!-- 2 trigger the subprocess 'send-reminder' once per hour from nine to 3 five, from Monday to Friday. 4 --> 5 <cron tab="0 9-17 * * mon-fri"> 6 <send-reminder /> 7 </cron>
1 # 2 # trigger the subprocess named 'send_reminder' every ten seconds. 3 # 4 cron :every => "10s" 5 send_reminder 6 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.
1 concurrence :count => 1 do 2 participant :toto 3 cron :every => "10m" do 4 subprocess :ref => "send_reminder" :target => "toto@headlost.org.uk" 5 end 6 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 :
1 <cursor> 2 <participant field-ref="reference_1" /> 3 <participant field-ref="reference_2" /> 4 5 <break if="${field:do_not_hire} == true" /> 6 <!-- do not hire if one of the references set the field 7 'do_not_hire' to true --> 8 9 <participant ref="faculty" /> 10 <participant ref="hr_department" /> 11 </cursor>
More documentation of those conditions in the cursor command rdoc
Since Ruote 0.9.20, the cursor (and the loop) expressions have ‘rewind-if’ / ‘break-if’ attributes.
1 class Pr0 < OpenWFE::ProcessDefinition 2 #OpenWFE.process_definition :name => 'pr', :revision => '0' 3 4 # at launch time, the fields 'manager', 'initiator' and 'pr_url' are set 5 # by the initiator 6 7 cursor :rewind_if => '${f:rework}', :break_if => '${f:rejected}' do 8 9 participant '${f:initiator}', :activity => 'prepare pr', :if => '${f:rejected}' 10 # when entering the cursor, the field 'rejected' will not have been 11 # set so the flow will jump directly to the manager. 12 13 manager :activity => 'pr approval' 14 15 cfo :activity => 'pr approval', :if => '${f:dollar_value} > 10000' 16 # only goes to the CFO if the dollar value is high enough 17 18 accounting :activity => 'pr approval' 19 participant :field_ref => 'initiator' 20 end 21 end
In this example, the process follows a serie of participant and each time one of the replies, it checks the fields ‘rework’ and ‘rejected’. The cursor exits if the field ‘rejected’ is set to true, or gets rewound if the field ‘rework’ holds a true value.
The attributes ‘rewind_unless’ and ‘break_unless’ are understood as well.
Original is at http://gist.github.com/73813
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.
defined1 <if> 2 <defined field="customer"> 3 <!-- then --> 4 <subprocess ref="call_customer" /> 5 </if>
Note that since Ruote 0.9.17, it’s possible to write :
1 _if :test => "${field:customer} is set" do 2 sequence do 3 participant "A" 4 participant "B" 5 end 6 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.
errorThe ‘error’ expression is used to trigger/force an error in a process instance. The error will thus interrupt the process or ‘redirect’ it to an error handler if one is present.
This example process will get interrupted if the field customer is empty or not present. Note the usage of the optional :if attribute :
1 sequence do 2 participant :ref => 'unit1' 3 error 'missing info', :if => "${f:customer} == ''" 4 participant :ref => 'unit2' 5 end
This XML example will route the process (in fact the whole ‘sequence’ block, the one adorned with an ‘on_error’ attribute) to an “fail_path” subprocess :
1 <process-definition name="myprocess" revision="0"> 2 3 <sequence on-error="fail_path"> 4 <participant ref="unit1" /> 5 <error unless="${f:customer}">missing field customer</error> 6 <participant ref="unit2" /> 7 </sequence> 8 9 <process-definition name="fail_path"> 10 <participant ref="special_cases_unit" /> 11 </process-definition> 12 13 </process-definition>
evalA dangerous expression, it takes the value of a field or a variable and evaluates it as a segment of process definition.
At some point in this example, the workitem field ‘code’ is filled with a segment of [Ruby] process definition, that gets executed later :
1 <process-definition name="myprocess" revision="0"> 2 <sequence> 3 4 <set field="code"> 5 concurrence do 6 participant 'alpha' 7 participant 'toto' 8 end 9 </set> 10 11 <!-- a bit later... --> 12 13 <eval field-def="code" /> 14 15 </sequence> 16 </process-definition>
One could think about many scenarii, for instance, evaluating fragments of process definitions coming back from a participant (if it’s trusted).
This expression will raise an error if the configuration parameter :dynamic_eval_allowed isn’t set to ‘true’. You have to explicitely set this parameter, it defaults to ‘false’.
Eval works with fields and variables (or nested values).
expThe ‘exp’ expression allows to choose at apply time, which expression (or participant or subprocess) to use :
1 2 sequence do 3 4 my_activity :variant => "sequence" 5 # 6 # B works after A 7 8 my_activity :variant => "concurrence" 9 # 10 # A and B work in parallel 11 end 12 13 process_definition :name => "my_activity" do 14 15 exp :name => "${variant}", :default => "sequence" do 16 17 participant "A" 18 participant "B" 19 end 20 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.
1 class MyProcessDefinition001 < ProcessDefinition 2 sequence do 3 4 set :field => "readable", :value => "bible" 5 set :field => "writable", :value => "sand" 6 set :field => "randw", :value => "notebook" 7 set :field => "hidden", :value => "playboy" 8 9 alice 10 11 filter :name => "filter0" do 12 sequence 13 bob 14 charly 15 end 16 end 17 end 18 19 filter_definition :name => "filter0" do 20 field :regex => "readable", :permissions => "r" 21 field :regex => "writable", :permissions => "w" 22 field :regex => "randw", :permissions => "rw" 23 field :regex => "hidden", :permissions => "" 24 end 25 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).
1 filter_definition :name => "filter0" do 2 field :regex => "readable", :permissions => "r" 3 field :regex => "writable", :permissions => "w" 4 field :regex => "randw", :permissions => "rw" 5 field :regex => "hidden", :permissions => "" 6 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.
1 sequence do 2 participant :a 3 forget do 4 sequence do 5 participant :a0 6 participant :a1 7 end 8 end 9 participant :b 10 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.
1 <iterator 2 on-value="alice, bob, charly" 3 to-variable="next_participant" 4 > 5 <participant ref="${next_participant}" /> 6 </iterator>
is thus equivalent to
1 <sequence> 2 <participant ref="alice" /> 3 <participant ref="bob" /> 4 <participant ref="charly" /> 5 </sequence>
Of course, the list could be a bit more dynamic :
1 iterator :on_value => "${f:participant_list}", :to_variable => "p" do 2 participant "${p}" 3 end
and since Ruote 0.9.13, ‘iterator’ understands the same commands as ‘cursor’ (break/cancel, rewind/continue, skip, jump) :
1 iterator :on_value => "${f:participant_list}", :to_variable => "p" do 2 sequence do 3 participant "${p}" 4 _break :if => "${f:participant_reply} == cancel_meeting" 5 # 6 # if one participant wishes to cancel the meeting, no need 7 # to notify the others 8 end 9 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.
1 <concurrence> 2 3 <listen to="alpha"> 4 <subprocess ref="send-email-notification" /> 5 </listen> 6 7 <sequence> 8 <participant ref="alpha" /> 9 <participant ref="bravo" /> 10 <participant ref="alpha" /> 11 </sequence> 12 13 </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-email-notification”.
1 listen :to => "^customer_.*", :upon => "reply", :where => "${f:customer_name} == Acme" do 2 concurrence do 3 participant :ref => "operational_sales" 4 participant :ref => "strategic_sales" 5 end 6 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 Ruote 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.
1 sequence do 2 listen :to => "^customer_.*", :upon => "reply" 3 subprocess :ref => "order_items" 4 end
Listen expressions without children only work ‘once’.
The ‘intercept’ and ‘receive’ aliases for the ‘listen’ expression are available. 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”.
1 receive :on => "^customer_.*"
The “merge” attribute has a default value of ‘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.
1 listen :to => "^customer_.*", :merge => true
(Ruote 0.9.20) When the attribute ‘wfid’ is set to ‘current’ or :current, only participant events in the same process instance will be listened to.
1 listen :to => "^customer_.*", :wfid => 'current'
rdoc for more information
logLogs a message to the application log (if not specified otherwise logs/openwferu.log).
loop‘repeat’ is an alias for ‘loop’.
See 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.
1 <concurrence count="1"> 2 <!-- this concurrence expects two answers --> 3 4 <lose> 5 <!-- will be applied, but 'lose' will never reply 6 to the concurrence --> 7 <participant ref="alice" /> 8 </lose> 9 10 <participant ref="bob" /> 11 <participant ref="charly" /> 12 <!-- the concurrence will be over as soon as bob or charly 13 will have replied --> 14 15 </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.
1 <process-definition name="exp_x" revision="0"> 2 3 <!-- required workitem fields --> 4 5 <parameter field="customer" /> 6 <parameter field="address" default="" /> 7 <parameter field="zip" type="integer" /> 8 <parameter field="town" /> 9 <parameter field="phone" match="^[0-9]{3}-[0-9]{7}$" /> 10 <parameter field="customer_type" default="2" type="int"/> 11 12 <!-- the body of the process definition --> 13 14 <sequence> 15 <participant ref="service0" /> 16 <participant ref="service1" /> 17 <play-the-music/> 18 </sequence> 19 20 <!-- subprocesses --> 21 22 <process-definition name="exp_play-the-music"> 23 <participant ref="musician" /> 24 </process-definition> 25 26 </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 :
1 require 'openwfe/def' 2 3 class MyProcessDefinition_01 < OpenWFE::ProcessDefinition 4 5 # 6 # required fields in the launchitem 7 8 param :field => "customer" 9 param :field => "address", default => "" 10 param :field => "zip", type => "integer" 11 param :field => "town" 12 param :field => "phone", match => "^[0-9]{3}-[0-9]{7}$" 13 param :field => "customer_type", default => 2, type => :int 14 15 # 16 # process definition body 17 18 sequence do 19 service0 20 service1 21 play_the_music 22 end 23 24 # 25 # subprocesses 26 27 process_definition :name => "play_the_music" 28 musician 29 end 30 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 Ruote (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.
1 process_definition :name => "my_process", :revision => "y" 2 sequence do 3 participant "alpha" 4 participant "bravo" 5 end 6 end
The same definition, in XML :
1 <process-definition name="my_process" revision="y"> 2 <sequence> 3 <participant ref="alpha" /> 4 <participant ref="bravo" /> 5 </sequence> 6 </process-definition>
In a Ruby process definition, this is also OK :
1 class MyDef0 < OpenWFE::ProcessDefinition 2 sequence do 3 sub0 4 sub1 5 end 6 7 process_definition :name => "sub0" do 8 participant "cold" 9 end 10 11 definition "sub1" do 12 # 13 # ':name => ' not used 14 15 participant "not cold" 16 end 17 end
print(only used for testing and debugging purposes)
qIs a shortcut for “quote”.
quote
redoThis expression is a complement to undo
Whereas ‘undo’ simply cancels an expression given its tag name, redo will cancel it and restart it.
Note that as soon as the tagged expression is done, you can’t redo it.
In a Ruby process definition, take care to prefix this expression with an underscore “_redo” to make sure Ruby won’t take it for it’s own “redo” expression.
It’s OK to append a :if or :unless attribute to a ‘_redo’ expression.
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.
1 concurrence do 2 sequence do 3 activity_a 4 reserve :mutex => :m do 5 activity_b 6 end 7 activity_c 8 end 9 sequence do 10 activity_d 11 reserve :mutex => :m do 12 sequence do 13 activity_e 14 activity_f 15 end 16 end 17 end 18 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”. Ruote turns the symbol into the corresponding string.
1 <!-- process A --> 2 3 <process_definition name="A" revision="0.0"> 4 <sequence> 5 <participant ref="alice" /> 6 <reserve mutex="//guard0"> 7 <participant ref="alfred" /> 8 </reserve> 9 </sequence> 10 </process_definition> 11 12 <!-- process B --> 13 14 <process_definition name="B" revision="0.0"> 15 <sequence> 16 <participant ref="bob" /> 17 <reserve mutex="//guard0"> 18 <participant ref="berthe" /> 19 </reserve> 20 </sequence> 21 </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 Ruote 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.
1 class Registration < OpenWFE::ProcessDefinition 2 set_fields :value => { 3 "name" => "", 4 "address" => "", 5 "email" => "" 6 } 7 sequence do 8 approve_registration 9 perform_registration :if => "${approved} == true" 10 notify_customer 11 end 12 end
(The usual technique for pre-filling a workitem is by providing a launchitem when launching the business process
1 li = OpenWFE::LaunchItem.new "http://process.server.com/definitions/xyz.xml" 2 li.customer = { "name" => "Toto", "age" => 34 } 3 li.request_type = "credit" 4 5 openwferu_engine.launch li
)
In XML, it looks like :
1 <process-definition name="test" revision="44b9"> 2 <set-fields> 3 <a> 4 <hash> 5 <entry> 6 <string>name</string><string></string> 7 </entry> 8 <entry> 9 <string>address</string><string></string> 10 </entry> 11 <entry> 12 <string>email</string><string></string> 13 </entry> 14 </hash> 15 </a> 16 </set-fields> 17 <sequence> 18 <participant ref="approve_registration" /> 19 <participant ref="perform_registration if="${approved} == true" /> 20 <participant ref="notify_customer" /> 21 </sequence> 22 </process-definition>
Note that the passing JSON (or YAML) is OK :
1 <set-fields> 2 <a> 3 { "customer": { "name": "Zigue", "age": 34 }, "approved": false } 4 </a> 5 </set-fields>
revalThis expression evals the Ruby code nested within it.
1 <sequence> 2 <reval>$i = 0</reval> 3 <loop> 4 <participant ref="toto" /> 5 <reval>$i = $i + 1</reval> 6 <break if="${r:$i == 10}" /> 7 <!-- a variant : 8 <break rif="$i == 10" /> 9 --> 10 </loop> 11 </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
1 class Reval4 < OpenWFE::ProcessDefinition 2 sequence do 3 reval """ 4 engine = self.application_context['__tracer'] 5 6 workitem.currently_active_processes = 7 engine.list_processes.join('\n') 8 9 'done' # will be placed in the workitem field '__result__' 10 """ 11 participant :toto 12 end 13 end
The result of ‘reval’ is placed in the field named
resultas 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.
1 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.
1 <process-definition name="exp_my_definition" revision="0"> 2 <sequence> 3 <participant ref="alpha" /> 4 <participant ref="bravo" /> 5 <participant ref="charly" /> 6 </sequence> 7 </process-definition>
In Ruby :
1 require 'openwfe/def' 2 3 class MyDefinition0 < OpenWFE::ProcessDefinition 4 sequence do 5 participant "alpha" 6 participant "bravo" 7 participant "charly" 8 end 9 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).
1 require 'openwfe/def' 2 3 class MyDefinition0 < OpenWFE::ProcessDefinition 4 sequence do 5 participant "alpha" 6 end 7 8 set :field => "city", :value => "Kyoto" 9 set :field => "country", :value => "Japan" 10 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 :
1 require 'openwfe/def' 2 3 class MyDefinition0 < OpenWFE::ProcessDefinition 4 sequence do 5 set :f => "customer_name", :val => "(fill this field)" 6 participant "alpha" 7 end 8 end
The ‘set’ expression accepts an ‘escape’ attribute to prevent dollar substituation from occurring.
1 set :v => "v0", :val => "my ${template} thing", escape => true
set-fields“set-fields” is an alias for restore.
sleep‘sleep’ got merged into ‘wait’ since Ruote 0.9.20.
stepThis expression was first implemented as a subprocess in the trouble ticket blog post.
It’s now part of Ruote and accept participants and subprocesses as “state” as well as for “transitions”.
Note that all the attributes to the “step” expression itself will be passed as atribute for the state participant or subprocess.
The “transition” after the “state” is determined by the [string] value found in the workitem field named “outcome”. The participant or the subprocess in the “state” is meant to set this field to a meaningful value (see :outcomes and :default after the code example).
1 # this 'step' expression will first hand the workitem to the "qa" participant 2 # (or subprocess) and then call the participant or subprocess whose name can 3 # be found in the workitem field named "outcome". 4 # Then the flow will resume after the step expression, in this example with 5 # the participant "dev". 6 # 7 # If the outcome field is not set, the 'step' expression will resume 8 # immediately to participant "dev". 9 # 10 sequence do 11 step 'qa', :desc => 'reproduce problem' 12 participant 'dev' 13 end
Here is the “trouble ticket” example implemented with the “step” expression (note that the “define” alias to “process-definition” was used) :
1 class TroubleTicket02 < OpenWFE::ProcessDefinition 2 3 # The root of the process 4 # 5 sequence do 6 7 # the first activity, customer support 8 # 9 cs :activity => "enter details" 10 11 # initiating the first step 12 # 13 step "qa", :desc => "reproduce problem" 14 end 15 16 # 17 # The 'outputs' of the activities 18 # 19 20 # QA 'reproduce problem' outputs 21 22 define "out:cannot_reproduce" do 23 step "cs", :desc => "correct report" 24 end 25 define "out:known_solution" do 26 finalsteps 27 end 28 define "out:duplicate" do 29 step "qa", :desc => "verify" 30 end 31 define "out:reproduced" do 32 step "dev", :desc => "resolution" 33 end 34 35 # Customer Support 'correct report' outputs 36 37 define "out:submit" do 38 step "qa", :desc => "reproduce problem" 39 end 40 define "out:give_up" do 41 finalsteps 42 end 43 44 # QA 'verify' outputs 45 46 define "out:qa_fixed" do 47 finalsteps 48 end 49 define "out:not_fixed" do 50 step "dev", :desc => "resolution" 51 end 52 53 # dev 'resolution' outputs 54 55 define "out:dev_fixed" do 56 step "qa", :desc => "verify" 57 end 58 59 set :var => "out:not_a_bug", :variable_value => "out:dev_fixed" 60 # "not_a_bug" is an alias to "dev_fixed" 61 62 # the final steps 63 64 define "finalsteps" do 65 concurrence do 66 cs :activity => "communicate results" 67 qa :activity => "audit" 68 end 69 end 70 71 end
The “step” expression also accepts two attributes : “outcomes” and “default”. “outcomes” accepts a list of transition names, while “default” names one default transition to use.
1 <step ref="one" outcomes="two, three" default="two" />
In Ruby :
1 step :one, :outcomes => [ :two, :three ], :default => :two
subprocessThis expression is used to trigger a subprocess.
1 <process-definition name="chores" revision="0"> 2 3 <sequence> 4 <subprocess ref="take_out_garbage" /> 5 <subprocess ref="do_the_laundry" type="white" /> 6 <subprocess ref="do_the_laundry" type="colors" /> 7 </sequence> 8 9 <process-definition name="take_out_garbage"> 10 <concurrence> 11 <participant ref="al" activity="take out old paper" /> 12 <participant ref="bob" activity="take out garbage" /> 13 </concurrence> 14 </process-definition> 15 16 <process-definition name="do_the_laundry"> 17 <sequence> 18 <participant ref="al" activity="sort ${type}" /> 19 <participant ref="bob" activity="clean ${type}" /> 20 <sleep for="1h30m" /> 21 <participant ref="al" activity="hang ${type}" /> 22 <participant ref="al" activity="iron ${type}" /> 23 </sequence> 24 </process-definition> 25 </process-definition>
Note how the variable ‘type’ was set to ‘white’ and then ‘colors’ and used within the “do_the_laundry” subprocess.
1 class Chores0 < OpenWFE::ProcessDefinition 2 3 sequence do 4 subprocess :ref => "take_out_garbage" 5 do_the_laundry :type => "white" 6 subprocess :ref => "do_the_laundry", :type => "colors" 7 end 8 9 process_definition :name => "take_out_garbage" do 10 concurrence do 11 participant :ref => "al", :activity => "take out old paper" 12 bob :activity => "take out garbage" 13 end 14 end 15 16 process_definition :name => "do_the_laundry" do 17 sequence do 18 participant :ref => "al", :activity => "sort ${type}" 19 participant :ref => "bob", :activity => "clean ${type}" 20 _sleep :for => "1h30m" 21 al :activity => "hang ${type}" 22 al :activity => "iron ${type}" 23 end 24 end 25 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 :
1 sequence do 2 participant "al" 3 subprocess :ref => "http://company.process.server/defs/defXYZ.xml" 4 end
timeoutA generic timeout mechanism : sets a timeout for a whole process segment, not just a single participant or when expression.
1 <timeout after="1w"> 2 <concurrence> 3 <participant ref="alpha" /> 4 <subprocess ref="do_the_homework" /> 5 </concurrence> 6 </timeout>
1 _timeout :after => "2d" do 2 sequence do 3 4 participant :ref => "alpha", :timeout => "1d" 5 # of course individal timeout settings are still ok 6 7 participant :ref => "bravo" 8 end 9 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.
undefined1 <if> 2 <undefined field="customer"> 3 <!-- then --> 4 <subprocess ref="determine_customer" /> 5 </if>
Note that since Ruote 0.9.17, it’s possible to write :
1 _if :test => "${field:customer} is not set" do 2 sequence do 3 participant "A" 4 participant "B" 5 end 6 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.
1 concurrence do 2 sequence :tag => "side_job" do 3 participant "alice" 4 participant "bob" 5 end 6 sequence do 7 participant "charly" 8 _undo :ref => "side_job" 9 end 10 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).
If the ‘alice / bob’ sequence is already over, the ‘undo’ will have no effect (the tag will have vanished).
It’s OK to append a :if or :unless attribute to an ‘undo’ expression.
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.
1 <concurrence> 2 <participant ref="alpha" /> 3 <when test="${for_bravo} == true"> 4 <participant ref="bravo" /> 5 </when> 6 <participant ref="charly" /> 7 </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.
1 concurrence do 2 alpha 3 when :test => "${for_bravo} == true", :frequency => "1h", :timeout => "13d4h" do 4 bravo 5 end 6 charly 7 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.
waitSince Ruote 0.9.20, ‘sleep’ got merged into the ‘wait’ expression.
This 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.
1 <concurrence> 2 <sequence> 3 <wait until="${done}" /> 4 <!-- as soon as one review is ready, start the sorting job ... --> 5 <alice activity="sort reviews" /> 6 </sequence> 7 <sequence> 8 <bob activity="do review" /> 9 <set var="done" val="true" /> 10 </sequence> 11 <sequence> 12 <charly activity="do review" /> 13 <set var="done" val="true" /> 14 </sequence> 15 </concurrence>
1 wait :until => "${reached} == 'stage 2'", :frequency => '30m' 2 # wait until the variable 'reached' is set to 'stage 2' (somewhere 3 # else in the process) 4 # check every thirty minutes.
This expression sees more usage in its ‘sleep’ variant.
Makes a process segment sleep for a while.
1 <sequence> 2 3 <sleep for="3d" /> 4 <!-- sleeping for 3 days --> 5 6 <sleep until="Mon Dec 03 10:48:03 +0900 2007" /> 7 8 </sequence>
In a Ruby process definition :
1 sequence do 2 3 _sleep :for => "3m10s" 4 # sleeping for 3 minutes and 10 seconds 5 6 _sleep :until => "Mon Dec 03 10:48:03 +0900 2007" 7 # sleeping until some point in time 8 9 _sleep "3M10d" 10 # sleeping for 3 months and 10 days 11 end
‘wait’ might be more readable (and sound more businessy) though :
1 sequence do 2 3 wait :for => "3m10s" 4 # wait for 3 minutes and 10 seconds 5 6 wait :until => "Mon Dec 04 10:48:03 +0900 2007" 7 # wait until some point in time 8 9 wait "3M10d" 10 # wait for 3 months and 10 days 11 end
Note that when using a Ruby process definition, you’d better escape the ‘sleep’ expression with an underscore at the front to avoid collisions with the ‘sleep’ method provided by the Kernel class in Ruby.
rdoc for more information