ruote 2.1.10 → 2.1.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. data/CHANGELOG.txt +51 -1
  2. data/CREDITS.txt +9 -0
  3. data/README.rdoc +13 -0
  4. data/Rakefile +50 -21
  5. data/TODO.txt +42 -4
  6. data/examples/pong.rb +37 -0
  7. data/lib/ruote/context.rb +19 -9
  8. data/lib/ruote/engine/process_error.rb +10 -0
  9. data/lib/ruote/engine/process_status.rb +140 -41
  10. data/lib/ruote/engine.rb +394 -27
  11. data/lib/ruote/exp/command.rb +2 -0
  12. data/lib/ruote/exp/fe_concurrence.rb +8 -0
  13. data/lib/ruote/exp/fe_concurrent_iterator.rb +3 -0
  14. data/lib/ruote/exp/fe_cursor.rb +48 -4
  15. data/lib/ruote/exp/fe_iterator.rb +40 -0
  16. data/lib/ruote/exp/fe_listen.rb +3 -3
  17. data/lib/ruote/exp/fe_participant.rb +30 -12
  18. data/lib/ruote/exp/fe_ref.rb +126 -0
  19. data/lib/ruote/exp/fe_subprocess.rb +20 -1
  20. data/lib/ruote/exp/fe_wait.rb +4 -1
  21. data/lib/ruote/exp/fe_when.rb +7 -10
  22. data/lib/ruote/exp/flowexpression.rb +23 -12
  23. data/lib/ruote/exp/ro_attributes.rb +5 -8
  24. data/lib/ruote/exp/ro_variables.rb +4 -2
  25. data/lib/ruote/fei.rb +2 -0
  26. data/lib/ruote/id/wfid_generator.rb +1 -1
  27. data/lib/ruote/log/pretty.rb +137 -0
  28. data/lib/ruote/log/storage_history.rb +1 -1
  29. data/lib/ruote/log/test_logger.rb +51 -126
  30. data/lib/ruote/log/wait_logger.rb +8 -13
  31. data/lib/ruote/parser/ruby_dsl.rb +4 -4
  32. data/lib/ruote/parser.rb +2 -2
  33. data/lib/ruote/part/block_participant.rb +1 -1
  34. data/lib/ruote/part/engine_participant.rb +1 -1
  35. data/lib/ruote/part/storage_participant.rb +27 -28
  36. data/lib/ruote/part/template.rb +8 -3
  37. data/lib/ruote/receiver/base.rb +24 -6
  38. data/lib/ruote/storage/base.rb +76 -11
  39. data/lib/ruote/storage/fs_storage.rb +10 -0
  40. data/lib/ruote/storage/hash_storage.rb +19 -8
  41. data/lib/ruote/{part → svc}/dispatch_pool.rb +3 -2
  42. data/lib/ruote/svc/dollar_sub.rb +265 -0
  43. data/lib/ruote/{error_handler.rb → svc/error_handler.rb} +6 -1
  44. data/lib/ruote/{exp → svc}/expression_map.rb +31 -37
  45. data/lib/ruote/{part → svc}/participant_list.rb +165 -25
  46. data/lib/ruote/{evt → svc}/tracker.rb +0 -0
  47. data/lib/ruote/{util → svc}/treechecker.rb +0 -0
  48. data/lib/ruote/util/look.rb +4 -1
  49. data/lib/ruote/util/ometa.rb +21 -5
  50. data/lib/ruote/{subprocess.rb → util/subprocess.rb} +0 -0
  51. data/lib/ruote/version.rb +1 -1
  52. data/lib/ruote/worker.rb +29 -69
  53. data/lib/ruote/workitem.rb +28 -1
  54. data/ruote.gemspec +26 -22
  55. data/test/functional/base.rb +3 -0
  56. data/test/functional/concurrent_base.rb +1 -0
  57. data/test/functional/crunner.sh +1 -1
  58. data/test/functional/ct_0_concurrence.rb +6 -0
  59. data/test/functional/ct_1_iterator.rb +3 -0
  60. data/test/functional/ct_2_cancel.rb +5 -0
  61. data/test/functional/eft_13_iterator.rb +39 -4
  62. data/test/functional/eft_14_cursor.rb +39 -0
  63. data/test/functional/eft_30_ref.rb +140 -0
  64. data/test/functional/eft_3_participant.rb +25 -23
  65. data/test/functional/ft_10_dollar.rb +17 -1
  66. data/test/functional/ft_14_re_apply.rb +76 -0
  67. data/test/functional/ft_1_process_status.rb +170 -29
  68. data/test/functional/ft_20_storage_participant.rb +14 -0
  69. data/test/functional/ft_24_block_participants.rb +1 -1
  70. data/test/functional/ft_26_participant_timeout.rb +93 -0
  71. data/test/functional/ft_2_errors.rb +24 -17
  72. data/test/functional/ft_30_smtp_participant.rb +7 -2
  73. data/test/functional/ft_38_participant_more.rb +15 -0
  74. data/test/functional/ft_39_wait_for.rb +34 -1
  75. data/test/functional/ft_3_participant_registration.rb +270 -2
  76. data/test/functional/ft_40_wait_logger.rb +61 -0
  77. data/test/functional/ft_42_storage_copy.rb +4 -0
  78. data/test/functional/{ft_40_participant_on_reply.rb → ft_43_participant_on_reply.rb} +17 -0
  79. data/test/functional/ft_44_var_participant.rb +35 -0
  80. data/test/functional/ft_45_participant_accept.rb +64 -0
  81. data/test/functional/ft_46_launch_single.rb +49 -0
  82. data/test/functional/ft_5_on_error.rb +39 -1
  83. data/test/functional/storage_helper.rb +7 -1
  84. data/test/test_helper.rb +1 -1
  85. data/test/unit/storage.rb +105 -32
  86. data/test/unit/ut_0_ruby_parser.rb +31 -1
  87. data/test/unit/ut_16_parser.rb +20 -0
  88. data/test/unit/ut_19_part_template.rb +11 -1
  89. data/test/unit/ut_20_composite_storage.rb +1 -1
  90. data/test/unit/ut_4_expmap.rb +1 -1
  91. data/test/unit/ut_6_condition.rb +2 -2
  92. metadata +112 -74
  93. data/lib/ruote/exp/raw.rb +0 -44
  94. data/lib/ruote/util/dollar.rb +0 -193
@@ -52,7 +52,7 @@ module Ruote
52
52
 
53
53
  def by_process (wfid)
54
54
 
55
- @context.storage.get_many('history', /!#{wfid}$/)
55
+ @context.storage.get_many('history', wfid)
56
56
  end
57
57
  alias :by_wfid :by_process
58
58
 
@@ -22,13 +22,15 @@
22
22
  # Made in Japan.
23
23
  #++
24
24
 
25
- #require 'ruote/util/tree'
25
+ require 'ruote/log/pretty'
26
26
 
27
27
 
28
28
  module Ruote
29
29
 
30
30
  class TestLogger
31
31
 
32
+ include PrettyLogging
33
+
32
34
  attr_reader :seen
33
35
  attr_reader :log
34
36
 
@@ -52,20 +54,15 @@ module Ruote
52
54
 
53
55
  @seen = []
54
56
  @log = []
55
- @waiting = nil
57
+ @waiting = []
56
58
 
57
59
  @count = -1
58
60
  @color = 33
59
61
  @noisy = false
60
-
61
- # NOTE
62
- # in case of troubles, why not have the wait_for has an event ?
63
62
  end
64
63
 
65
64
  def notify (msg)
66
65
 
67
- #@context.storage.put(event.merge('type' => 'archived_msgs'))
68
-
69
66
  puts(pretty_print(msg)) if @noisy
70
67
 
71
68
  @seen << msg
@@ -74,13 +71,26 @@ module Ruote
74
71
  check_waiting
75
72
  end
76
73
 
74
+ # Blocks until one or more interests are satisfied.
75
+ #
76
+ # interests must be an array of interests. Please refer to
77
+ # Engine#wait_for documentation for allowed values of each interest.
78
+ #
79
+ # If multiple interests are given, wait_for blocks until
80
+ # all of the interests are satisfied.
81
+ #
82
+ # wait_for may only be used by one thread at a time. If one
83
+ # thread calls wait_for and later another thread calls wait_for
84
+ # while the first thread is waiting, the first thread's
85
+ # interests are lost and the first thread will never wake up.
86
+ #
77
87
  def wait_for (interests)
78
88
 
79
- @waiting = [ Thread.current, interests ]
89
+ @waiting << [ Thread.current, interests ]
80
90
 
81
91
  check_waiting
82
92
 
83
- Thread.stop if @waiting
93
+ Thread.stop if @waiting.find { |w| w.first == Thread.current }
84
94
 
85
95
  # and when this thread gets woken up, go on and return __result__
86
96
 
@@ -109,39 +119,52 @@ module Ruote
109
119
 
110
120
  def check_waiting
111
121
 
112
- return unless @waiting
122
+ return if @waiting.size < 1
113
123
 
114
124
  while msg = @seen.shift
115
-
116
- break if check_msg(msg)
125
+ check_msg(msg)
117
126
  end
118
127
  end
119
128
 
120
129
  def check_msg (msg)
121
130
 
122
- if check_interest(msg)
131
+ wakeup = []
123
132
 
124
- thread = @waiting.first
125
- @waiting = nil
126
- thread['__result__'] = msg
127
- thread.wakeup
133
+ @waiting.each do |thread, interests|
128
134
 
129
- true
130
- else
135
+ wakeup << thread if matches(interests, msg)
136
+ end
131
137
 
132
- false
138
+ @waiting.delete_if { |t, i| i.size < 1 }
139
+
140
+ wakeup.each do |thread|
141
+
142
+ thread['__result__'] = msg
143
+ thread.wakeup
133
144
  end
134
145
  end
135
146
 
136
147
  FINAL_ACTIONS = %w[ terminated ceased error_intercepted ]
137
148
 
138
- def check_interest (msg)
149
+ # Checks whether message msg matches any of interests being waited for.
150
+ #
151
+ # Some interests look for actions on particular workflows (e.g.,
152
+ # waiting for some workflow to finish). Other interests are not
153
+ # attached to any particular workflow (e.g., :inactive waits until
154
+ # the engine finishes processing all active and pending workflows)
155
+ # but are still satisfied when actions happen on workflows (e.g.,
156
+ # the last workflow being run finishes).
157
+ #
158
+ # Returns true if all interests being waited for have been satisfied,
159
+ # false otherwise.
160
+ #
161
+ def matches (interests, msg)
139
162
 
140
163
  action = msg['action']
141
164
 
142
- @waiting.last.each do |interest|
165
+ interests.each do |interest|
143
166
 
144
- satisfied = if interest == :inactive
167
+ satisfied = if interest == :inactive
145
168
 
146
169
  (FINAL_ACTIONS.include?(action) && @context.worker.inactive?)
147
170
 
@@ -155,9 +178,10 @@ module Ruote
155
178
 
156
179
  elsif interest.is_a?(Fixnum)
157
180
 
158
- @waiting[-1] = @waiting[-1] - [ interest ]
181
+ interests.delete(interest)
182
+
159
183
  if (interest > 1)
160
- @waiting[-1] << (interest - 1)
184
+ interests << (interest - 1)
161
185
  false
162
186
  else
163
187
  true
@@ -168,109 +192,10 @@ module Ruote
168
192
  (FINAL_ACTIONS.include?(action) && msg['wfid'] == interest)
169
193
  end
170
194
 
171
- @waiting[-1] = @waiting[-1] - [ interest ] if satisfied
172
- end
173
-
174
- @waiting.last.size < 1
175
- end
176
-
177
- # <ESC>[{attr1};...;{attrn}m
178
- #
179
- # 0 Reset all attributes
180
- # 1 Bright
181
- # 2 Dim
182
- # 4 Underscore
183
- # 5 Blink
184
- # 7 Reverse
185
- # 8 Hidden
186
- #
187
- # Foreground Colours
188
- # 30 Black
189
- # 31 Red
190
- # 32 Green
191
- # 33 Yellow
192
- # 34 Blue
193
- # 35 Magenta
194
- # 36 Cyan
195
- # 37 White
196
- #
197
- # Background Colours
198
- # 40 Black
199
- # 41 Red
200
- # 42 Green
201
- # 43 Yellow
202
- # 44 Blue
203
- # 45 Magenta
204
- # 46 Cyan
205
- # 47 White
206
-
207
- def color (mod, s, clear=false)
208
-
209
- return s if Ruote::WIN
210
- return s unless STDOUT.tty?
211
-
212
- "[#{mod}m#{s}#{clear ? '' : "[#{@color}m"}"
213
- end
214
-
215
- def pretty_print (msg)
216
-
217
- @count += 1
218
- @count = 0 if @count > 9
219
-
220
- ei = self.object_id.to_s[-2..-1]
221
-
222
- fei = msg['fei']
223
- depth = fei ? fei['expid'].split('_').size : 0
224
-
225
- i = fei ?
226
- [ fei['wfid'], fei['sub_wfid'], fei['expid'] ].join(' ') :
227
- msg['wfid']
228
-
229
- rest = msg.dup
230
- %w[
231
- _id put_at _rev
232
- type action
233
- fei wfid variables
234
- ].each { |k| rest.delete(k) }
235
-
236
- if v = rest['parent_id']
237
- rest['parent_id'] = Ruote.to_storage_id(v)
238
- end
239
- if v = rest.delete('workitem')
240
- rest[:wi] = [
241
- v['fei'] ? Ruote.to_storage_id(v['fei']) : nil,
242
- v['fields'].size ]
243
- end
244
-
245
- { 'tree' => :t, 'parent_id' => :pi }.each do |k0, k1|
246
- if v = rest.delete(k0)
247
- rest[k1] = v
248
- end
249
- end
250
-
251
- action = msg['action'][0, 2]
252
- action = case msg['action']
253
- when 'receive' then 'rc'
254
- when 'dispatched' then 'dd'
255
- when 'dispatch_cancel' then 'dc'
256
- else action
257
- end
258
- action = case action
259
- when 'la' then color('4;32', action)
260
- when 'te' then color('4;31', action)
261
- when 'ce' then color('31', action)
262
- when 'ca' then color('31', action)
263
- when 'rc' then color('4;33', action)
264
- when 'di' then color('4;33', action)
265
- when 'dd' then color('4;33', action)
266
- when 'dc' then color('4;31', action)
267
- else action
195
+ interests.delete(interest) if satisfied
268
196
  end
269
197
 
270
- color(
271
- @color,
272
- "#{@count} #{ei} #{' ' * depth}#{action} * #{i} #{rest.inspect}",
273
- true)
198
+ interests.size < 1
274
199
  end
275
200
  end
276
201
  end
@@ -37,33 +37,28 @@ module Ruote
37
37
  def initialize (context)
38
38
 
39
39
  @context = context
40
- @waiting = nil
41
40
  @color = 33
42
41
 
43
42
  @context.worker.subscribe(:all, self) if @context.worker
44
43
 
45
44
  @noisy = false
46
45
  @count = -1
46
+
47
+ @seen = []
48
+ @waiting = []
47
49
  end
48
50
 
49
51
  def notify (msg)
50
52
 
51
53
  puts(pretty_print(msg)) if @noisy
52
54
 
53
- return unless @waiting
54
-
55
- check_msg(msg)
56
- end
57
-
58
- def wait_for (interests)
59
-
60
- @waiting = [ Thread.current, interests ]
61
-
62
- Thread.stop
55
+ #return if @waiting.size < 1
56
+ #check_msg(msg)
63
57
 
64
- # and when this thread gets woken up, go on and return __result__
58
+ @seen << msg
59
+ @seen.shift if @seen.size > 147
65
60
 
66
- Thread.current['__result__']
61
+ check_waiting
67
62
  end
68
63
  end
69
64
  end
@@ -22,6 +22,8 @@
22
22
  # Made in Japan.
23
23
  #++
24
24
 
25
+ require 'ruote/util/ometa'
26
+
25
27
 
26
28
  module Ruote
27
29
 
@@ -94,7 +96,7 @@ module Ruote
94
96
  #
95
97
  module RubyDsl
96
98
 
97
- class BranchContext
99
+ class BranchContext < Ruote::BlankSlate
98
100
 
99
101
  def initialize (name, attributes)
100
102
 
@@ -117,9 +119,7 @@ module Ruote
117
119
 
118
120
  def self.create_branch (name, attributes, &block)
119
121
 
120
- while name[0, 1] == '_'
121
- name = name[1..-1]
122
- end
122
+ name = name[1..-1] while name[0, 1] == '_'
123
123
 
124
124
  h = attributes.inject({}) { |h1, a|
125
125
  a.is_a?(Hash) ? h1.merge!(a) : h1[a] = nil
data/lib/ruote/parser.rb CHANGED
@@ -55,7 +55,7 @@ module Ruote
55
55
  (return Rufus::Json.decode(definition)) rescue nil
56
56
  (return ruby_eval(definition)) rescue nil
57
57
 
58
- if definition.index("\n") == nil
58
+ if definition.index("\n").nil? && definition.index(' ').nil?
59
59
 
60
60
  raise ArgumentError.new(
61
61
  "remote process definitions are not allowed"
@@ -77,7 +77,7 @@ module Ruote
77
77
  unless @parser
78
78
 
79
79
  require 'ostruct'
80
- require 'ruote/util/treechecker'
80
+ require 'ruote/svc/treechecker'
81
81
 
82
82
  @parser = Ruote::Parser.new(
83
83
  OpenStruct.new('treechecker' => Ruote::TreeChecker.new({})))
@@ -45,7 +45,7 @@ module Ruote
45
45
  # end
46
46
  #
47
47
  #
48
- # == do_not_thead
48
+ # == do_not_thread
49
49
  #
50
50
  # By default, this participant (like most other participants) is executed
51
51
  # in its own thread (in a Ruby runtime where EventMachine is running,
@@ -22,7 +22,7 @@
22
22
  # Made in Singapore.
23
23
  #++
24
24
 
25
- require 'ruote/subprocess'
25
+ require 'ruote/util/subprocess'
26
26
  require 'ruote/part/local_participant'
27
27
 
28
28
 
@@ -138,7 +138,7 @@ module Ruote
138
138
  #
139
139
  def size
140
140
 
141
- fetch_all.size
141
+ fetch_all(:count => true)
142
142
  end
143
143
 
144
144
  # Iterates over the workitems stored in here.
@@ -150,9 +150,9 @@ module Ruote
150
150
 
151
151
  # Returns all the workitems stored in here.
152
152
  #
153
- def all
153
+ def all (opts={})
154
154
 
155
- fetch_all.map { |hwi| Ruote::Workitem.new(hwi) }
155
+ fetch_all(opts).map { |hwi| Ruote::Workitem.new(hwi) }
156
156
  end
157
157
 
158
158
  # A convenience method (especially when testing), returns the first
@@ -169,22 +169,24 @@ module Ruote
169
169
  #
170
170
  def by_wfid (wfid)
171
171
 
172
- @context.storage.get_many('workitems', /!#{wfid}$/).map { |hwi|
172
+ @context.storage.get_many('workitems', wfid).collect { |hwi|
173
173
  Ruote::Workitem.new(hwi)
174
174
  }
175
175
  end
176
176
 
177
177
  # Returns all workitems for the specified participant name
178
178
  #
179
- def by_participant (participant_name)
179
+ def by_participant (participant_name, opts={})
180
180
 
181
181
  hwis = if @context.storage.respond_to?(:by_participant)
182
182
 
183
- @context.storage.by_participant('workitems', participant_name)
183
+ @context.storage.by_participant('workitems', participant_name, opts)
184
184
 
185
185
  else
186
186
 
187
- fetch_all.select { |wi| wi['participant_name'] == participant_name }
187
+ fetch_all(opts).select { |wi|
188
+ wi['participant_name'] == participant_name
189
+ }
188
190
  end
189
191
 
190
192
  hwis.collect { |hwi| Ruote::Workitem.new(hwi) }
@@ -230,7 +232,7 @@ module Ruote
230
232
  # constraints for fields.
231
233
  #
232
234
  # 'offset' and 'limit' are reserved as well. They should prove useful
233
- # for pagination.
235
+ # for pagination. 'skip' can be used instead of 'offset'.
234
236
  #
235
237
  # Note : the criteria is AND only, you'll have to do ORs (aggregation)
236
238
  # by yourself.
@@ -239,32 +241,28 @@ module Ruote
239
241
 
240
242
  cr = criteria.inject({}) { |h, (k, v)| h[k.to_s] = v; h }
241
243
 
242
- return @context.storage.query_workitems(cr).collect { |h|
243
- Ruote::Workitem.new(h)
244
- } if @context.storage.respond_to?(:query_workitems)
244
+ if @context.storage.respond_to?(:query_workitems)
245
+ return @context.storage.query_workitems(cr)
246
+ end
245
247
 
246
- offset = cr.delete('offset')
247
- limit = cr.delete('limit')
248
+ opts = {}
249
+ opts[:skip] = cr.delete('offset') || cr.delete('skip')
250
+ opts[:limit] = cr.delete('limit')
251
+ opts[:count] = cr.delete('count')
248
252
 
249
253
  wfid = cr.delete('wfid')
250
254
  pname = cr.delete('participant_name') || cr.delete('participant')
251
255
 
252
- hwis = if wfid
253
- @context.storage.get_many('workitems', /!#{wfid}$/)
254
- else
255
- fetch_all
256
- end
256
+ hwis = wfid ?
257
+ @context.storage.get_many('workitems', wfid, opts) : fetch_all(opts)
257
258
 
258
- hwis = hwis.select { |hwi|
259
+ return hwis if opts[:count]
260
+
261
+ hwis.select { |hwi|
259
262
  Ruote::StorageParticipant.matches?(hwi, pname, cr)
260
263
  }.collect { |hwi|
261
264
  Ruote::Workitem.new(hwi)
262
265
  }
263
-
264
- offset = offset || 0
265
- limit = limit || hwis.length
266
-
267
- hwis[offset, limit]
268
266
  end
269
267
 
270
268
  # Cleans this participant out completely
@@ -294,11 +292,12 @@ module Ruote
294
292
  # Fetches all the workitems. If there is a @store_name, will only fetch
295
293
  # the workitems in that store.
296
294
  #
297
- def fetch_all
298
-
299
- key = @store_name ? /^wi!#{@store_name}::/ : nil
295
+ def fetch_all (opts={})
300
296
 
301
- @context.storage.get_many('workitems', key)
297
+ @context.storage.get_many(
298
+ 'workitems',
299
+ @store_name ? /^wi!#{@store_name}::/ : nil,
300
+ opts)
302
301
  end
303
302
 
304
303
  # Computes the id for the document representing the document in the storage.
@@ -23,7 +23,6 @@
23
23
  #++
24
24
 
25
25
  require 'rufus/json'
26
- require 'ruote/util/dollar'
27
26
 
28
27
 
29
28
  module Ruote
@@ -34,8 +33,14 @@ module Ruote
34
33
  # (Currently only used by the SmtpParticipant, could prove useful for
35
34
  # custom participants)
36
35
  #
36
+ # This module expects to find the ruote @context available (probably
37
+ # accessible thanks to the Ruote::LocalParticipant module, see
38
+ # Ruote::SmtpParticipant as an example).
39
+ #
37
40
  module TemplateMixin
38
41
 
42
+ # Do the rendering.
43
+ #
39
44
  def render_template (template, flow_expression, workitem)
40
45
 
41
46
  template = (File.read(template) rescue nil) if is_a_file?(template)
@@ -45,7 +50,7 @@ module Ruote
45
50
  template = template.to_s
46
51
  workitem = workitem.to_h if workitem.respond_to?(:to_h)
47
52
 
48
- Ruote.dosub(template, flow_expression, workitem)
53
+ @context.dollar_sub.s(template, flow_expression, workitem)
49
54
  end
50
55
 
51
56
  # Simply returns a pretty-printed view of the workitem
@@ -57,7 +62,7 @@ module Ruote
57
62
  s = []
58
63
  s << "workitem for #{workitem['participant_name']}"
59
64
  s << ''
60
- s << Rufus::Json.encode(workitem['fei'])
65
+ s << Rufus::Json.pretty_encode(workitem['fei'])
61
66
  s << ''
62
67
  workitem['fields'].keys.sort.each do |key|
63
68
  s << " - '#{key}' ==> #{Rufus::Json.encode(workitem['fields'][key])}"
@@ -55,6 +55,14 @@ module Ruote
55
55
  # This method is mostly used from the Ruote::Engine class (which includes
56
56
  # this mixin).
57
57
  #
58
+ # process_definition must be a result of Ruote.process_definition call
59
+ # or XML or JSON serialized process definition, as accepted by
60
+ # Ruote::Parser#parse.
61
+ #
62
+ # fields are workflow parameters that will be placed in workitem.fields.
63
+ #
64
+ # variables contain engine variables.
65
+ #
58
66
  def launch (process_definition, fields={}, variables={})
59
67
 
60
68
  wfid = @context.wfidgen.generate
@@ -98,16 +106,26 @@ module Ruote
98
106
  self.class.to_s
99
107
  end
100
108
 
101
- protected
102
-
103
- # Convenience method, fetches the flow expression (ParticipantExpression)
104
- # that emitted that workitem.
109
+ # Convenience method, given a workitem or a fei, returns the
110
+ # corresponding flow expession.
105
111
  #
106
- def fetch_flow_expression (workitem)
112
+ def fetch_flow_expression (workitem_or_fei)
107
113
 
108
- Ruote::Exp::FlowExpression.fetch(@context, workitem.fei.to_h)
114
+ Ruote::Exp::FlowExpression.fetch(
115
+ @context,
116
+ Ruote::FlowExpressionId.extract_h(workitem_or_fei))
109
117
  end
110
118
 
119
+ # For example :
120
+ #
121
+ # fexp = engine.fexp(fei)
122
+ # # or
123
+ # fexp = engine.fexp(workitem)
124
+ #
125
+ alias fexp fetch_flow_expression
126
+
127
+ protected
128
+
111
129
  # Stashes values in the participant expression (in the storage).
112
130
  #
113
131
  # put(workitem.fei, 'key' => 'value', 'colour' => 'blue')