ruote 2.1.9 → 2.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. data/CHANGELOG.txt +32 -0
  2. data/CREDITS.txt +3 -0
  3. data/Rakefile +4 -4
  4. data/TODO.txt +55 -11
  5. data/examples/barley.rb +2 -1
  6. data/examples/flickr_report.rb +5 -6
  7. data/examples/web_first_page.rb +11 -0
  8. data/lib/ruote/context.rb +36 -13
  9. data/lib/ruote/engine.rb +88 -56
  10. data/lib/ruote/engine/process_error.rb +13 -0
  11. data/lib/ruote/engine/process_status.rb +33 -1
  12. data/lib/ruote/error_handler.rb +122 -0
  13. data/lib/ruote/evt/tracker.rb +27 -10
  14. data/lib/ruote/exp/fe_apply.rb +69 -0
  15. data/lib/ruote/exp/fe_participant.rb +33 -5
  16. data/lib/ruote/exp/flowexpression.rb +37 -5
  17. data/lib/ruote/exp/ro_persist.rb +8 -4
  18. data/lib/ruote/exp/ro_variables.rb +2 -2
  19. data/lib/ruote/fei.rb +59 -7
  20. data/lib/ruote/log/storage_history.rb +2 -0
  21. data/lib/ruote/log/test_logger.rb +28 -19
  22. data/lib/ruote/log/wait_logger.rb +4 -2
  23. data/lib/ruote/parser.rb +2 -1
  24. data/lib/ruote/part/dispatch_pool.rb +10 -10
  25. data/lib/ruote/part/engine_participant.rb +2 -2
  26. data/lib/ruote/part/local_participant.rb +99 -7
  27. data/lib/ruote/part/participant_list.rb +18 -7
  28. data/lib/ruote/part/storage_participant.rb +9 -6
  29. data/lib/ruote/receiver/base.rb +109 -10
  30. data/lib/ruote/storage/base.rb +118 -41
  31. data/lib/ruote/storage/fs_storage.rb +1 -0
  32. data/lib/ruote/storage/hash_storage.rb +2 -1
  33. data/lib/ruote/util/lookup.rb +22 -2
  34. data/lib/ruote/util/misc.rb +5 -5
  35. data/lib/ruote/version.rb +1 -1
  36. data/lib/ruote/worker.rb +50 -63
  37. data/lib/ruote/workitem.rb +64 -0
  38. data/ruote.gemspec +17 -12
  39. data/test/functional/base.rb +3 -1
  40. data/test/functional/concurrent_base.rb +35 -29
  41. data/test/functional/crunner.sh +19 -0
  42. data/test/functional/ct_0_concurrence.rb +17 -30
  43. data/test/functional/ct_1_iterator.rb +20 -17
  44. data/test/functional/ct_2_cancel.rb +32 -25
  45. data/test/functional/eft_12_listen.rb +2 -1
  46. data/test/functional/eft_23_apply.rb +23 -0
  47. data/test/functional/eft_3_participant.rb +27 -0
  48. data/test/functional/ft_11_recursion.rb +1 -1
  49. data/test/functional/ft_13_variables.rb +22 -0
  50. data/test/functional/ft_14_re_apply.rb +3 -0
  51. data/test/functional/ft_15_timeout.rb +1 -0
  52. data/test/functional/ft_20_storage_participant.rb +20 -2
  53. data/test/functional/ft_21_forget.rb +30 -0
  54. data/test/functional/ft_22_process_definitions.rb +2 -1
  55. data/test/functional/ft_24_block_participants.rb +1 -1
  56. data/test/functional/ft_25_receiver.rb +83 -1
  57. data/test/functional/ft_26_participant_timeout.rb +1 -1
  58. data/test/functional/ft_2_errors.rb +2 -5
  59. data/test/functional/ft_30_smtp_participant.rb +47 -45
  60. data/test/functional/ft_36_storage_history.rb +4 -4
  61. data/test/functional/ft_37_engine_participant.rb +14 -10
  62. data/test/functional/ft_38_participant_more.rb +178 -0
  63. data/test/functional/ft_39_wait_for.rb +100 -0
  64. data/test/functional/ft_40_participant_on_reply.rb +87 -0
  65. data/test/functional/ft_41_participants.rb +65 -0
  66. data/test/functional/ft_42_storage_copy.rb +67 -0
  67. data/test/functional/ft_5_on_error.rb +103 -0
  68. data/test/functional/ft_9_subprocesses.rb +2 -1
  69. data/test/functional/storage_helper.rb +5 -1
  70. data/test/functional/test.rb +4 -1
  71. data/test/functional/vertical.rb +46 -0
  72. data/test/unit/storage.rb +17 -1
  73. data/test/unit/storages.rb +27 -7
  74. data/test/unit/ut_11_lookup.rb +36 -0
  75. data/test/unit/ut_16_parser.rb +43 -0
  76. data/test/unit/ut_1_fei.rb +28 -1
  77. data/test/unit/ut_7_workitem.rb +23 -0
  78. metadata +67 -105
  79. data/lib/ruote/log/fs_history.rb +0 -182
  80. data/test/functional/ft_32_fs_history.rb +0 -188
  81. data/test/mpc_test.rb +0 -29
@@ -2,6 +2,38 @@
2
2
  = ruote - CHANGELOG.txt
3
3
 
4
4
 
5
+ == ruote - 2.1.10 released 2010/06/15
6
+
7
+ - storage#copy_to(other_storage) implemented
8
+ - #launch moved from Engine to ReceiverMixin
9
+ - participants without initialize(opts) are now allowed
10
+ - engine.wait_for(:inactive)
11
+ - engine.wait_for(*interests) unlocked
12
+ - engine.wait_for(:empty)
13
+ - fixed issue with participant 'x' and :on_error. Thanks Oleg.
14
+ - receiver : reply and reply_to_engine : from aliases to wrappers
15
+ - Ruote::StorageParticipant more flexibility for method args
16
+ - Ruote::FlowExpressionId .extract_h and .extract
17
+ - dropped fs_history (storage_history is better)
18
+ - parser to_xml _if 'x == b' --> <if test="x == b">
19
+ - workitem.sid shortcut for workitem.fei.to_storage_id
20
+ - workitem.wfid shorcut for workitem.fei.wfid
21
+ - new error_handler service
22
+ - Receiver.new(x), x can be worker, engine, context or storage
23
+ - Participant#on_reply(workitem) manipulating workitems when they come back
24
+ - set '${v:customers.0.name}' => 'x' now OK, was limited to fields. Thanks Oleg
25
+ - LocalParticipant#put(fei, hash) #get(fei, key) for stashing info
26
+ - LocalParticipant#re_dispatch(wi, opts)
27
+ - bug in HashStorage, apply over apply didn't raise a persist error. Fixed.
28
+ - keeping track of workitem fields as they were right before a participant error
29
+ - Workitem.error holds the error when on_error. Thanks Oleg.
30
+ - Workitem#error and Workitem#timed_out shortcuts
31
+ - participant :on_error => 'x' broken. Fixed. Thanks Oleg.
32
+ - Engine#workitem(fei) for advanced users
33
+ - LocalParticpant : added a reject(workitem) method
34
+ - participant exp : dispatched = true set right after dispatch
35
+
36
+
5
37
  == ruote - 2.1.9 released 2010/03/22
6
38
 
7
39
  - made participant.cancel occur asynchronously (as should be)
@@ -16,6 +16,7 @@ Torsten Schoenebaum - http://github.com/tosch
16
16
  Contributors
17
17
  ------------
18
18
 
19
+ Oleg Pudeyev - http://github.com/p
19
20
  Brett Anthoine - http://github.com/anb
20
21
  Matt Nichols - http://github.com/mattnichols
21
22
  Nicholas Faiz - http://github.com/biv
@@ -38,6 +39,8 @@ Richard Jennings
38
39
  Feedback
39
40
  --------
40
41
 
42
+ Oleg (foenixx) - many suggestions and bug reports
43
+ Avishai Shalom - discussion and ideas about participant/worker locality
41
44
  Gonzalo Suarez - many help
42
45
  Francisco Kiko - many help
43
46
  David Goldhirsch - EM participant block
data/Rakefile CHANGED
@@ -23,8 +23,8 @@ ruote is an open source ruby workflow engine.
23
23
  gem.rubyforge_project = 'ruote'
24
24
  gem.test_file = 'test/test.rb'
25
25
 
26
- gem.add_dependency 'rufus-json', '>= 0.2.0'
27
- gem.add_dependency 'rufus-cloche', '>= 0.1.16'
26
+ gem.add_dependency 'rufus-json', '>= 0.2.2'
27
+ gem.add_dependency 'rufus-cloche', '>= 0.1.17'
28
28
  gem.add_dependency 'rufus-dollar'
29
29
  gem.add_dependency 'rufus-lru'
30
30
  gem.add_dependency 'rufus-mnemo', '>= 1.1.0'
@@ -48,7 +48,7 @@ end
48
48
  begin
49
49
  require 'yard'
50
50
  YARD::Rake::YardocTask.new do |doc|
51
- doc.options = [ '-o', 'ruote_rdoc', '--title', "ruote #{Ruote::VERSION}" ]
51
+ doc.options = [ '-o', 'rdoc', '--title', "ruote #{Ruote::VERSION}" ]
52
52
  end
53
53
  rescue LoadError
54
54
  task :yard do
@@ -65,7 +65,7 @@ desc 'Upload the documentation to rubyforge'
65
65
  task :upload_rdoc => :yard do
66
66
  sh %{
67
67
  rsync -azv -e ssh \
68
- ruote_rdoc \
68
+ rdoc \
69
69
  jmettraux@rubyforge.org:/var/www/gforge-projects/ruote/
70
70
  }
71
71
  end
data/TODO.txt CHANGED
@@ -193,6 +193,27 @@
193
193
  [o] part = engine.register_participant :alpha, StorageParticipant should work...
194
194
  [o] concurrence :merge_type => 'stack'
195
195
  [o] CompositeStorage.new('msgs' => AmqpStorage.new(''), ...)
196
+ [x] let the storage participant leverage Ruote::FlowExpressionId.from_id(s)
197
+ [o] Andrew's technique http://groups.google.com/group/openwferu-users/browse_thread/thread/c2aa4b53d1664d45/8523a1a5ee98fd71
198
+ [o] Avishai : LocalParticipant : repost dispatch message
199
+ [o] Rdoc Ruote::Engine.register_participant -> passing a block to a participant
200
+ and perhaps also on
201
+ http://ruote.rubyforge.org/implementing_participants.html (Avish)
202
+ [o] wrap workitem in process error ? for on_error consumption (thanks Oleg)
203
+ doing workitem.fields['__error__'] = [ fei, time, error_message ]
204
+ [o] HashStorage should emit 'init persist fail' messages as well !
205
+ [o] Oleg's idea about participant on_reply
206
+ http://groups.google.com/group/openwferu-users/browse_thread/thread/2e6a95708c10847b
207
+ on_reply should be done in the receive action, not in Receiver
208
+ thus, in the ParticipantExpression
209
+ [x] exp : step (jump to cursor tag ?) : there is already the jump expression
210
+ [x] auto-participant re-apply
211
+ [o] receiver should be OK with a storage or a context
212
+ [x] Avishai : Worker : hook for rejecting the dispatch message
213
+ [o] receiver / local participant : reply/forward/proceed/... mess : fix
214
+ [o] storage participant : accept string for fei
215
+ [o] => Ruote::FlowExpressionId.extract(x)
216
+ [o] fei : place engine id in fei.to_storage_id (and back)
196
217
 
197
218
  [ ] exp : exp (restricted form of eval ?)
198
219
  [ ] exp : case (is it necessary ?)
@@ -200,19 +221,15 @@
200
221
  [ ] exp : filter-definition
201
222
  [x] exp : lose ?
202
223
  [x] exp : parameter
203
- [ ] exp : log
224
+ [ ] exp : log : or could it be a participant ?
204
225
 
205
226
  [ ] exp : defined (not really necessary)
206
227
  [ ] exp : quote (not really necessary)
207
228
  [ ] exp : field / attribute (not really necessary)
208
229
  [ ] exp : variable (not really necessary)
209
230
 
210
- [ ] exp : step (jump to cursor tag ?)
211
-
212
231
  [ ] conditional : rprefix ! ${r:x} is perhaps sufficient
213
232
 
214
- [ ] auto-participant re-apply
215
-
216
233
  [ ] define without name (__result__)
217
234
 
218
235
  [ ] pooltool.ru
@@ -234,6 +251,9 @@
234
251
 
235
252
  [ ] pause engine
236
253
  [ ] pause process instance
254
+ |
255
+ would it mean something like placing a paused list in the storage
256
+ and fetching it all the time ?
237
257
 
238
258
  [ ] file/fs_listener [example] ?
239
259
 
@@ -293,24 +313,25 @@
293
313
 
294
314
  [ ] empty iterator or concurrent-iterator, log ? crash ? empty while...
295
315
  [ ] at expression ?
296
- [ ] listen to participants/errors/tags {in|out}
297
316
 
298
317
  [ ] remove abort_on_exception=true
299
318
 
300
319
  [ ] shell ? irb ? Shell.new(storage)
301
320
  [ ] focus on fulldup or json.dup (via fulldup ?)
302
321
 
303
- [ ] implement pause engine
304
- [ ] implement pause process
322
+ [ ] listen to participants/errors/tags {in|out}
323
+
324
+ [x] engine.on_error = 'participant_name' // 'subprocess_name'
325
+ done at : http://github.com/jmettraux/ruote/commit/50292d954ff877f1f6615022216f346a7001b483
326
+ `--> reverting that for now, too dangerous
305
327
 
306
- [ ] engine.on_error = 'participant_name' // 'subprocess_name'
328
+ [ ] should __error__ contain the tree ?
329
+ [ ] engine.on_cancel = 'participant_name' // 'subprocess_name'
307
330
 
308
331
  [ ] "business days" plugin
309
332
 
310
333
  [ ] issue with ruote-kit and inpa participants...
311
334
 
312
- [ ] let the storage participant leverage Ruote::FlowExpressionId.from_id(s)
313
-
314
335
  [ ] participant :ref => '${f:nada}', :or => 'xyz'
315
336
  (look at OpenWFE manual, this feature already existed in there)
316
337
  http://www.openwfe.org/manual/ch06s02.html#expression_participant
@@ -323,3 +344,26 @@
323
344
 
324
345
  [ ] find better solution than "get all schedules"
325
346
 
347
+ [ ] worker : minuteman, make it cron triggerable
348
+ trap SIGUSR1 or USR2
349
+ maybe it's expensive to fire a [worker] process each minute
350
+ have to write the $$ (pid) somewhere for cron to pick it up
351
+
352
+ [ ] detach / attach segments of processes
353
+ [ ] clone process ? (could be used by {de|at}tach)
354
+
355
+ [ ] dollar.rb ${timestamp} ?
356
+
357
+ [ ] toto :task => 'maw the lawn', :within => '3d'
358
+
359
+ [ ] solve the ps#root_expression_for(fei) dilemma
360
+
361
+ [ ] engine.noisy = true shortcut
362
+
363
+ [ ] re_apply_stalled
364
+ http://groups.google.com/group/openwferu-users/browse_thread/thread/ff29f26d6b5fd135
365
+
366
+ [ ] wait_for(:inactive) blocks until worker is inactive
367
+
368
+ [ ] storage0.copy_to(storage1) / migrate_to as requested by Matt Nichols
369
+
@@ -91,7 +91,8 @@ require 'cgi'
91
91
  require 'sinatra'
92
92
  require 'haml'
93
93
 
94
- use_in_file_templates!
94
+ #use_in_file_templates! # sinatra 0.9.x
95
+ enable :inline_templates # sinatra 1.0
95
96
 
96
97
  def h (s)
97
98
  Rack::Utils.escape_html(s)
@@ -2,14 +2,14 @@
2
2
  $:.unshift('lib')
3
3
 
4
4
  require 'rubygems'
5
- require 'ruote/engine' # sudo gem install ruote
5
+ require 'ruote' # sudo gem install ruote
6
6
  require 'atom/feed' # sudo gem install atom-tools
7
7
  require 'prawn' # sudo gem install prawn
8
8
 
9
9
  #
10
10
  # starting a transient engine (no need to make it persistent)
11
11
 
12
- engine = Ruote::Engine.new(:definition_in_launchitem_allowed => true)
12
+ engine = Ruote::Engine.new(Ruote::Worker.new(Ruote::HashStorage.new()))
13
13
 
14
14
  #
15
15
  # a process that fetches the latest pictures from flickr.com and submits
@@ -47,7 +47,7 @@ engine.register_participant :get_pictures do |workitem|
47
47
 
48
48
  workitem.fields['pictures'] = feed.entries.inject([]) do |a, entry|
49
49
  a << [
50
- entry.title,
50
+ entry.title.to_s,
51
51
  entry.authors.first.name,
52
52
  entry.links.last.href
53
53
  ]
@@ -94,10 +94,9 @@ end
94
94
  #
95
95
  # launching the process, requesting pictures tagged 'cat' and 'fish'
96
96
 
97
- li = Ruote::Launchitem.new(pdef)
98
- li.fields['tags'] = [ 'cat', 'fish' ]
97
+ initial_workitem_fields = { 'tags' => [ 'cat', 'fish' ] }
99
98
 
100
- fei = engine.launch(li)
99
+ fei = engine.launch(pdef, initial_workitem_fields)
101
100
 
102
101
  #
103
102
  # workflow engines are asynchronous beasts, have to wait for them
@@ -1,4 +1,15 @@
1
1
 
2
+ #
3
+ # This is not a runnable example, it's just a ruby source file that contains
4
+ # the samples found at
5
+ #
6
+ # http://ruote.rubyforge.org/index.html
7
+ #
8
+ # NOTE : this example need some rework. The use of block participants will
9
+ # probably be avoided
10
+ #
11
+
12
+ require 'rubygems'
2
13
  require 'ruote'
3
14
 
4
15
  pdef = Ruote.process_definition :name => 'work' do
@@ -40,29 +40,53 @@ module Ruote
40
40
  attr_accessor :worker
41
41
  attr_accessor :engine
42
42
 
43
- def initialize (storage, worker_or_engine)
43
+ def initialize (storage, worker=nil)
44
44
 
45
45
  @storage = storage
46
+ @storage.context = self
46
47
 
47
- @worker, @engine = if worker_or_engine.kind_of?(Ruote::Engine)
48
- [ worker_or_engine.worker, worker_or_engine ]
49
- else
50
- [ worker_or_engine, nil ]
51
- end
48
+ @engine = nil
49
+ @worker = worker
50
+
51
+ @services = {}
52
52
 
53
53
  initialize_services
54
54
  end
55
55
 
56
+ # A trick : in order to avoid
57
+ #
58
+ # @context = o.respond_to?(:context) ? o.context : o
59
+ # # or
60
+ # #@context = o.is_a?(Ruote::Context) ? o : o.context
61
+ #
62
+ # simply letting a context say its context is itself.
63
+ #
64
+ def context
65
+
66
+ self
67
+ end
68
+
69
+ # Returns the engine_id (as set in the configuration under the key
70
+ # "engine_id"), or, by default, "engine".
71
+ #
56
72
  def engine_id
57
73
 
58
74
  get_conf['engine_id'] || 'engine'
59
75
  end
60
76
 
77
+ # Used for things like
78
+ #
79
+ # if @context['ruby_eval_allowed']
80
+ # # ...
81
+ # end
82
+ #
61
83
  def [] (key)
62
84
 
63
85
  SERVICE_PREFIX.match(key) ? @services[key] : get_conf[key]
64
86
  end
65
87
 
88
+ # Mostly used by engine#configure
89
+ #
66
90
  def []= (key, value)
67
91
 
68
92
  raise(
@@ -105,15 +129,14 @@ module Ruote
105
129
  service
106
130
  end
107
131
 
132
+ # Takes care of shutting down every service registered in this context.
133
+ #
108
134
  def shutdown
109
135
 
110
136
  @storage.shutdown if @storage.respond_to?(:shutdown)
111
137
  @worker.shutdown if @worker
112
138
 
113
- @services.values.each do |s|
114
-
115
- s.shutdown if s.respond_to?(:shutdown)
116
- end
139
+ @services.values.each { |s| s.shutdown if s.respond_to?(:shutdown) }
117
140
  end
118
141
 
119
142
  protected
@@ -125,8 +148,6 @@ module Ruote
125
148
 
126
149
  def initialize_services
127
150
 
128
- @services = {}
129
-
130
151
  default_conf.merge(get_conf).each do |key, value|
131
152
 
132
153
  next unless SERVICE_PREFIX.match(key)
@@ -152,7 +173,9 @@ module Ruote
152
173
  's_dispatch_pool' => [
153
174
  'ruote/part/dispatch_pool', 'Ruote::DispatchPool' ],
154
175
  's_logger' => [
155
- 'ruote/log/wait_logger', 'Ruote::WaitLogger' ] }
176
+ 'ruote/log/wait_logger', 'Ruote::WaitLogger' ],
177
+ 's_error_handler' => [
178
+ 'ruote/error_handler', 'Ruote::ErrorHandler' ] }
156
179
  end
157
180
  end
158
181
  end
@@ -29,69 +29,63 @@ require 'ruote/receiver/base'
29
29
 
30
30
  module Ruote
31
31
 
32
+ #
33
+ # This class holds the 'engine' name, perhaps 'dashboard' would have been
34
+ # a better name. Anyway, the methods here allow to launch processes
35
+ # and to query about their status. There are also methods for fixing
36
+ # issues with stalled processes or processes stuck in errors.
37
+ #
38
+ # NOTE : the methods #launch and #reply are implemented in
39
+ # Ruote::ReceiverMixin
40
+ #
32
41
  class Engine
33
42
 
34
43
  include ReceiverMixin
35
44
 
36
- attr_reader :storage
37
- attr_reader :worker
38
45
  attr_reader :context
39
46
  attr_reader :variables
40
47
 
41
48
  def initialize (worker_or_storage, run=true)
42
49
 
43
- if worker_or_storage.respond_to?(:storage)
50
+ @context = worker_or_storage.context
51
+ @context.engine = self
44
52
 
45
- @worker = worker_or_storage
46
- @storage = @worker.storage
47
- @context = @worker.context
48
- @context.engine = self
49
- else
53
+ @variables = EngineVariables.new(@context.storage)
50
54
 
51
- @worker = nil
52
- @storage = worker_or_storage
53
- @context = Ruote::Context.new(@storage, self)
54
- end
55
-
56
- @variables = EngineVariables.new(@storage)
57
-
58
- @worker.run_in_thread if @worker && run
55
+ @context.worker.run_in_thread if @context.worker && run
56
+ # launch the worker if there is one
59
57
  end
60
58
 
61
- def launch (process_definition, fields={}, variables={})
59
+ def storage
62
60
 
63
- wfid = @context.wfidgen.generate
61
+ @context.storage
62
+ end
64
63
 
65
- @storage.put_msg(
66
- 'launch',
67
- 'wfid' => wfid,
68
- 'tree' => @context.parser.parse(process_definition),
69
- 'workitem' => { 'fields' => fields },
70
- 'variables' => variables)
64
+ def worker
71
65
 
72
- wfid
66
+ @context.worker
73
67
  end
74
68
 
75
69
  def cancel_process (wfid)
76
70
 
77
- @storage.put_msg('cancel_process', 'wfid' => wfid)
71
+ @context.storage.put_msg('cancel_process', 'wfid' => wfid)
78
72
  end
79
73
 
80
74
  def kill_process (wfid)
81
75
 
82
- @storage.put_msg('kill_process', 'wfid' => wfid)
76
+ @context.storage.put_msg('kill_process', 'wfid' => wfid)
83
77
  end
84
78
 
85
79
  def cancel_expression (fei)
86
80
 
87
81
  fei = fei.to_h if fei.respond_to?(:to_h)
88
- @storage.put_msg('cancel', 'fei' => fei)
82
+ @context.storage.put_msg('cancel', 'fei' => fei)
89
83
  end
90
84
 
91
85
  def kill_expression (fei)
92
86
 
93
87
  fei = fei.to_h if fei.respond_to?(:to_h)
94
- @storage.put_msg('cancel', 'fei' => fei, 'flavour' => 'kill')
88
+ @context.storage.put_msg('cancel', 'fei' => fei, 'flavour' => 'kill')
95
89
  end
96
90
 
97
91
  # Replays at a given error (hopefully you fixed the cause of the error
@@ -113,9 +107,9 @@ module Ruote
113
107
  exp.unpersist_or_raise if exp
114
108
  end
115
109
 
116
- @storage.delete(err.to_h) # remove error
110
+ @context.storage.delete(err.to_h) # remove error
117
111
 
118
- @storage.put_msg(action, msg) # trigger replay
112
+ @context.storage.put_msg(action, msg) # trigger replay
119
113
  end
120
114
 
121
115
  # Re-applies an expression (given via its FlowExpressionId).
@@ -148,7 +142,7 @@ module Ruote
148
142
  #
149
143
  def process (wfid)
150
144
 
151
- exps = @storage.get_many('expressions', /!#{wfid}$/)
145
+ exps = @context.storage.get_many('expressions', /!#{wfid}$/)
152
146
  errs = self.errors( wfid )
153
147
 
154
148
  return nil if exps.empty? && errs.empty?
@@ -162,7 +156,7 @@ module Ruote
162
156
  #
163
157
  def processes
164
158
 
165
- exps = @storage.get_many('expressions')
159
+ exps = @context.storage.get_many('expressions')
166
160
  errs = self.errors
167
161
 
168
162
  by_wfid = {}
@@ -181,10 +175,13 @@ module Ruote
181
175
  #
182
176
  def errors( wfid = nil )
183
177
  wfid.nil? ?
184
- @storage.get_many('errors') :
185
- @storage.get_many('errors', /!#{wfid}$/)
178
+ @context.storage.get_many('errors') :
179
+ @context.storage.get_many('errors', /!#{wfid}$/)
186
180
  end
187
181
 
182
+ # Shuts down the engine, mostly passes the shutdown message to the other
183
+ # services and hope they'll shut down properly.
184
+ #
188
185
  def shutdown
189
186
 
190
187
  @context.shutdown
@@ -207,7 +204,16 @@ module Ruote
207
204
  # # will make the current thread block until 5 messages have been
208
205
  # # processed on the workqueue...
209
206
  #
210
- def wait_for (item)
207
+ # engine.wait_for(:empty)
208
+ # # will return as soon as the engine/storage is empty, ie as soon
209
+ # # as there are no more processes running in the engine (no more
210
+ # # expressions placed in the storage)
211
+ #
212
+ # It's OK to wait for multiple wfids :
213
+ #
214
+ # engine.wait_for('20100612-bezerijozo', '20100612-yakisoba')
215
+ #
216
+ def wait_for (*items)
211
217
 
212
218
  logger = @context['s_logger']
213
219
 
@@ -215,7 +221,7 @@ module Ruote
215
221
  "can't wait_for, there is no logger that responds to that call"
216
222
  ) unless logger.respond_to?(:wait_for)
217
223
 
218
- logger.wait_for(item)
224
+ logger.wait_for(items)
219
225
  end
220
226
 
221
227
  # Loads and parses the process definition at the given path.
@@ -255,37 +261,42 @@ module Ruote
255
261
  # engine.register_participant /^moon-.+/, MyParticipant.new('Saturn-V')
256
262
  #
257
263
  #
258
- # == passing a block to a participant
264
+ # == 'stateless' participants are preferred over 'stateful' ones
259
265
  #
260
- # Usually only the BlockParticipant cares about being passed a block :
266
+ # Ruote 2.1 is OK with 1 storage and 1+ workers. The workers may be
267
+ # in other ruby runtimes. This implies that if you have registered a
268
+ # participant instance (instead of passing its classname and options),
269
+ # that participant will only run in the worker 'embedded' in the engine
270
+ # where it was registered... Let me rephrase it, participants instantiated
271
+ # at registration time (and that includes block participants) only runs
272
+ # in one worker, always the same.
261
273
  #
262
- # engine.register_participant 'compute_sum' do |workitem|
263
- # workitem.fields['kilroy'] = 'was here'
264
- # end
274
+ # 'stateless' participants, instantiated at each dispatch, are preferred.
275
+ # Any worker can handle them.
265
276
  #
266
- # But it's OK to pass a block to a custom participant :
277
+ # Block participants are still fine for demos (where the worker is included
278
+ # in the engine (see all the quickstarts). And small engines with 1 worker
279
+ # are not that bad, not everybody is building huge systems).
267
280
  #
268
- # require 'ruote/part/local_participant'
281
+ # Here is a 'stateless' participant example :
269
282
  #
270
- # class MyParticipant
271
- # include Ruote::LocalParticipant
283
+ # class MyStatelessParticipant
272
284
  # def initialize (opts)
273
- # @name = opts[:name]
274
- # @block = opts[:block]
285
+ # @opts = opts
275
286
  # end
276
287
  # def consume (workitem)
277
- # workitem.fields['prestamp'] = Time.now
278
- # workitem.fields['author'] = @name
279
- # @block.call(workitem)
280
- # reply_to_engine(workitem)
288
+ # workitem.fields['rocket_name'] = @opts['name']
289
+ # send_to_the_moon(workitem)
290
+ # end
291
+ # def cancel (fei, flavour)
292
+ # # do nothing
281
293
  # end
282
294
  # end
283
295
  #
284
- # engine.register_participant 'al', MyParticipant, :name => 'toto' do |wi|
285
- # wi.fields['nada'] = surf
286
- # end
296
+ # engine.register_participant 'moon', MyStatelessParticipant, 'name' => 'saturn5'
287
297
  #
288
- # The block is available under the :block option.
298
+ # Remember that the options (the hash that follows the class name), must be
299
+ # serialisable via JSON.
289
300
  #
290
301
  def register_participant (regex, participant=nil, opts={}, &block)
291
302
 
@@ -341,6 +352,27 @@ module Ruote
341
352
 
342
353
  @context[config_key] = value
343
354
  end
355
+
356
+ # A convenience methods for advanced users (like Oleg).
357
+ #
358
+ # Given a fei (flow expression id), fetches the workitem as stored in
359
+ # the expression with that fei.
360
+ # This is the "applied workitem", if the workitem is currently handed to
361
+ # a participant, this method will return the workitem as applied, not
362
+ # the workitem as saved by the participant/user in whatever worklist it
363
+ # uses. If you need that workitem, do the vanilla thing and ask it to
364
+ # the [storage] participant or its worklist.
365
+ #
366
+ # The fei might be a string fei (result of fei.to_storage_id), a
367
+ # FlowExpressionId instance or a hash.
368
+ #
369
+ def workitem (fei)
370
+
371
+ fexp = Ruote::Exp::FlowExpression.fetch(
372
+ @context, Ruote::FlowExpressionId.extract_h(fei))
373
+
374
+ Ruote::Workitem.new(fexp.h.applied_workitem)
375
+ end
344
376
  end
345
377
 
346
378
  #