ruote 2.1.11 → 2.2.0

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 (217) hide show
  1. data/CHANGELOG.txt +60 -0
  2. data/CREDITS.txt +22 -4
  3. data/LICENSE.txt +1 -1
  4. data/README.rdoc +6 -7
  5. data/Rakefile +58 -59
  6. data/TODO.txt +137 -65
  7. data/couch_url.txt +1 -0
  8. data/jruby_issue.txt +32 -0
  9. data/lib/ruote.rb +1 -1
  10. data/lib/ruote/context.rb +12 -10
  11. data/lib/ruote/engine.rb +280 -145
  12. data/lib/ruote/engine/process_error.rb +5 -5
  13. data/lib/ruote/engine/process_status.rb +47 -28
  14. data/lib/ruote/exp/command.rb +7 -10
  15. data/lib/ruote/exp/commanded.rb +2 -2
  16. data/lib/ruote/exp/condition.rb +130 -43
  17. data/lib/ruote/exp/fe_add_branches.rb +2 -2
  18. data/lib/ruote/exp/fe_apply.rb +1 -1
  19. data/lib/ruote/exp/fe_cancel_process.rb +3 -3
  20. data/lib/ruote/exp/fe_command.rb +3 -3
  21. data/lib/ruote/exp/fe_concurrence.rb +4 -4
  22. data/lib/ruote/exp/fe_concurrent_iterator.rb +17 -5
  23. data/lib/ruote/exp/fe_cron.rb +3 -3
  24. data/lib/ruote/exp/fe_cursor.rb +5 -5
  25. data/lib/ruote/exp/fe_define.rb +3 -3
  26. data/lib/ruote/exp/fe_echo.rb +3 -3
  27. data/lib/ruote/exp/fe_equals.rb +2 -2
  28. data/lib/ruote/exp/fe_error.rb +2 -2
  29. data/lib/ruote/exp/fe_filter.rb +519 -0
  30. data/lib/ruote/exp/fe_forget.rb +9 -2
  31. data/lib/ruote/exp/fe_given.rb +154 -0
  32. data/lib/ruote/exp/fe_if.rb +16 -13
  33. data/lib/ruote/exp/fe_inc.rb +3 -3
  34. data/lib/ruote/exp/fe_iterator.rb +4 -4
  35. data/lib/ruote/exp/fe_let.rb +75 -0
  36. data/lib/ruote/exp/fe_listen.rb +68 -12
  37. data/lib/ruote/exp/fe_lose.rb +110 -0
  38. data/lib/ruote/exp/fe_noop.rb +1 -1
  39. data/lib/ruote/exp/{fe_when.rb → fe_once.rb} +25 -21
  40. data/lib/ruote/exp/fe_participant.rb +14 -17
  41. data/lib/ruote/exp/fe_redo.rb +10 -6
  42. data/lib/ruote/exp/fe_ref.rb +1 -1
  43. data/lib/ruote/exp/fe_registerp.rb +112 -0
  44. data/lib/ruote/exp/fe_reserve.rb +3 -3
  45. data/lib/ruote/exp/fe_restore.rb +2 -2
  46. data/lib/ruote/exp/fe_save.rb +2 -2
  47. data/lib/ruote/exp/fe_sequence.rb +3 -4
  48. data/lib/ruote/exp/fe_set.rb +16 -7
  49. data/lib/ruote/exp/fe_subprocess.rb +23 -1
  50. data/lib/ruote/exp/fe_that.rb +92 -0
  51. data/lib/ruote/exp/fe_undo.rb +3 -3
  52. data/lib/ruote/exp/fe_unregisterp.rb +71 -0
  53. data/lib/ruote/exp/fe_wait.rb +2 -2
  54. data/lib/ruote/exp/flowexpression.rb +153 -78
  55. data/lib/ruote/exp/iterator.rb +2 -2
  56. data/lib/ruote/exp/merge.rb +2 -2
  57. data/lib/ruote/exp/ro_attributes.rb +14 -12
  58. data/lib/ruote/exp/ro_filters.rb +136 -0
  59. data/lib/ruote/exp/ro_persist.rb +51 -35
  60. data/lib/ruote/exp/ro_variables.rb +18 -27
  61. data/lib/ruote/fei.rb +73 -33
  62. data/lib/ruote/id/mnemo_wfid_generator.rb +1 -1
  63. data/lib/ruote/id/wfid_generator.rb +11 -4
  64. data/lib/ruote/log/default_history.rb +122 -0
  65. data/lib/ruote/log/pretty.rb +36 -8
  66. data/lib/ruote/log/storage_history.rb +37 -5
  67. data/lib/ruote/log/test_logger.rb +26 -24
  68. data/lib/ruote/log/wait_logger.rb +5 -3
  69. data/lib/ruote/part/block_participant.rb +22 -11
  70. data/lib/ruote/part/engine_participant.rb +6 -7
  71. data/lib/ruote/part/local_participant.rb +6 -12
  72. data/lib/ruote/part/no_op_participant.rb +4 -4
  73. data/lib/ruote/part/null_participant.rb +4 -4
  74. data/lib/ruote/part/smtp_participant.rb +4 -4
  75. data/lib/ruote/part/storage_participant.rb +40 -20
  76. data/lib/ruote/part/template.rb +4 -4
  77. data/lib/ruote/participant.rb +0 -1
  78. data/lib/ruote/{parser.rb → reader.rb} +30 -25
  79. data/lib/ruote/{parser → reader}/ruby_dsl.rb +28 -11
  80. data/lib/ruote/{parser → reader}/xml.rb +6 -5
  81. data/lib/ruote/receiver/base.rb +35 -13
  82. data/lib/ruote/storage/base.rb +20 -18
  83. data/lib/ruote/storage/composite_storage.rb +10 -10
  84. data/lib/ruote/storage/fs_storage.rb +17 -10
  85. data/lib/ruote/storage/hash_storage.rb +29 -18
  86. data/lib/ruote/svc/dispatch_pool.rb +41 -14
  87. data/lib/ruote/svc/dollar_sub.rb +50 -17
  88. data/lib/ruote/svc/error_handler.rb +19 -11
  89. data/lib/ruote/svc/expression_map.rb +4 -4
  90. data/lib/ruote/svc/participant_list.rb +105 -100
  91. data/lib/ruote/svc/tracker.rb +58 -18
  92. data/lib/ruote/svc/treechecker.rb +51 -24
  93. data/lib/ruote/tree_dot.rb +4 -4
  94. data/lib/ruote/util/filter.rb +440 -0
  95. data/lib/ruote/util/hashdot.rb +4 -4
  96. data/lib/ruote/util/look.rb +2 -6
  97. data/lib/ruote/util/lookup.rb +9 -7
  98. data/lib/ruote/util/misc.rb +40 -8
  99. data/lib/ruote/util/ometa.rb +1 -1
  100. data/lib/ruote/util/serializer.rb +4 -4
  101. data/lib/ruote/util/subprocess.rb +29 -9
  102. data/lib/ruote/util/time.rb +4 -4
  103. data/lib/ruote/util/tree.rb +3 -3
  104. data/lib/ruote/version.rb +2 -2
  105. data/lib/ruote/worker.rb +55 -32
  106. data/lib/ruote/workitem.rb +64 -11
  107. data/ruote.gemspec +31 -302
  108. data/test/bm/launch_bench.rb +37 -0
  109. data/test/functional/base.rb +60 -18
  110. data/test/functional/concurrent_base.rb +2 -2
  111. data/test/functional/ct_0_concurrence.rb +1 -1
  112. data/test/functional/ct_1_iterator.rb +1 -1
  113. data/test/functional/ct_2_cancel.rb +1 -1
  114. data/test/functional/eft_0_process_definition.rb +2 -2
  115. data/test/functional/eft_10_cancel_process.rb +1 -1
  116. data/test/functional/eft_11_wait.rb +19 -11
  117. data/test/functional/eft_12_listen.rb +79 -13
  118. data/test/functional/eft_13_iterator.rb +13 -10
  119. data/test/functional/eft_14_cursor.rb +98 -9
  120. data/test/functional/eft_15_loop.rb +6 -4
  121. data/test/functional/eft_16_if.rb +12 -0
  122. data/test/functional/eft_18_concurrent_iterator.rb +31 -32
  123. data/test/functional/eft_19_reserve.rb +4 -4
  124. data/test/functional/eft_1_echo.rb +9 -0
  125. data/test/functional/eft_20_save.rb +4 -4
  126. data/test/functional/{eft_28_when.rb → eft_28_once.rb} +33 -7
  127. data/test/functional/eft_30_ref.rb +17 -2
  128. data/test/functional/eft_31_registerp.rb +130 -0
  129. data/test/functional/eft_32_lose.rb +93 -0
  130. data/test/functional/eft_33_let.rb +31 -0
  131. data/test/functional/eft_34_given.rb +123 -0
  132. data/test/functional/eft_35_filter.rb +269 -0
  133. data/test/functional/eft_3_participant.rb +4 -6
  134. data/test/functional/eft_4_set.rb +16 -2
  135. data/test/functional/eft_5_subprocess.rb +2 -4
  136. data/test/functional/eft_6_concurrence.rb +29 -29
  137. data/test/functional/eft_8_undo.rb +39 -3
  138. data/test/functional/eft_9_redo.rb +94 -2
  139. data/test/functional/ft_10_dollar.rb +81 -2
  140. data/test/functional/ft_11_recursion.rb +13 -17
  141. data/test/functional/ft_12_launchitem.rb +9 -5
  142. data/test/functional/ft_13_variables.rb +7 -9
  143. data/test/functional/ft_14_re_apply.rb +6 -9
  144. data/test/functional/ft_15_timeout.rb +18 -18
  145. data/test/functional/ft_16_participant_params.rb +1 -3
  146. data/test/functional/ft_17_conditional.rb +25 -2
  147. data/test/functional/ft_18_kill.rb +65 -12
  148. data/test/functional/ft_1_process_status.rb +147 -71
  149. data/test/functional/ft_20_storage_participant.rb +0 -1
  150. data/test/functional/ft_21_forget.rb +82 -1
  151. data/test/functional/{ft_24_block_participants.rb → ft_24_block_participant.rb} +42 -11
  152. data/test/functional/ft_25_receiver.rb +47 -17
  153. data/test/functional/{ft_26_participant_timeout.rb → ft_26_participant_rtimeout.rb} +56 -19
  154. data/test/functional/ft_29_part_template.rb +6 -5
  155. data/test/functional/ft_2_errors.rb +21 -37
  156. data/test/functional/ft_30_smtp_participant.rb +1 -1
  157. data/test/functional/ft_31_part_blocking.rb +8 -6
  158. data/test/functional/ft_34_cursor_rewind.rb +13 -10
  159. data/test/functional/ft_35_add_service.rb +1 -1
  160. data/test/functional/ft_36_storage_history.rb +24 -1
  161. data/test/functional/ft_37_default_history.rb +109 -0
  162. data/test/functional/ft_38_participant_more.rb +10 -10
  163. data/test/functional/ft_39_wait_for.rb +12 -9
  164. data/test/functional/ft_3_participant_registration.rb +111 -32
  165. data/test/functional/ft_40_wait_logger.rb +2 -1
  166. data/test/functional/ft_41_participants.rb +30 -4
  167. data/test/functional/ft_43_participant_on_reply.rb +6 -23
  168. data/test/functional/ft_45_participant_accept.rb +4 -4
  169. data/test/functional/ft_46_launch_single.rb +36 -2
  170. data/test/functional/ft_47_wfid_generator.rb +54 -0
  171. data/test/functional/ft_48_lose.rb +112 -0
  172. data/test/functional/ft_49_engine_on_error.rb +201 -0
  173. data/test/functional/ft_4_cancel.rb +66 -6
  174. data/test/functional/ft_50_engine_config.rb +22 -0
  175. data/test/functional/ft_51_misc.rb +67 -0
  176. data/test/functional/ft_52_case.rb +134 -0
  177. data/test/functional/ft_53_engine_on_terminate.rb +95 -0
  178. data/test/functional/ft_54_patterns.rb +104 -0
  179. data/test/functional/{ft_37_engine_participant.rb → ft_55_engine_participant.rb} +4 -5
  180. data/test/functional/ft_56_filter_attribute.rb +259 -0
  181. data/test/functional/ft_5_on_error.rb +77 -30
  182. data/test/functional/ft_6_on_cancel.rb +66 -11
  183. data/test/functional/ft_7_tags.rb +94 -5
  184. data/test/functional/ft_8_participant_consumption.rb +36 -5
  185. data/test/functional/ft_9_subprocesses.rb +10 -10
  186. data/test/functional/rt_1_listen.rb +3 -3
  187. data/test/functional/{rt_3_when.rb → rt_3_once.rb} +4 -4
  188. data/test/functional/storage_helper.rb +15 -13
  189. data/test/functional/test.rb +1 -3
  190. data/test/test_helper.rb +0 -8
  191. data/test/unit/storage.rb +154 -10
  192. data/test/unit/{ut_0_ruby_parser.rb → ut_0_ruby_reader.rb} +61 -11
  193. data/test/unit/ut_11_lookup.rb +7 -0
  194. data/test/unit/ut_13_serializer.rb +1 -1
  195. data/test/unit/ut_15_util.rb +23 -0
  196. data/test/unit/{ut_16_parser.rb → ut_16_reader.rb} +11 -13
  197. data/test/unit/ut_1_fei.rb +57 -10
  198. data/test/unit/ut_20_composite_storage.rb +25 -11
  199. data/test/unit/ut_21_participant_list.rb +47 -0
  200. data/test/unit/ut_22_filter.rb +903 -0
  201. data/test/unit/ut_3_wait_logger.rb +2 -6
  202. data/test/unit/ut_6_condition.rb +164 -17
  203. data/test/unit/ut_7_workitem.rb +28 -0
  204. data/test/unit/ut_8_tree_to_dot.rb +1 -1
  205. data/test/unit/{ut_9_xml_parser.rb → ut_9_xml_reader.rb} +5 -5
  206. metadata +108 -84
  207. data/.gitignore +0 -4
  208. data/examples/barley.rb +0 -391
  209. data/examples/flickr_report.rb +0 -107
  210. data/examples/pong.rb +0 -37
  211. data/examples/ruote_quickstart.rb +0 -43
  212. data/examples/web_first_page.rb +0 -68
  213. data/lib/ruote/part/hash_participant.rb +0 -91
  214. data/test/README.rdoc +0 -15
  215. data/test/functional/crunner.sh +0 -19
  216. data/test/pdef.xml +0 -7
  217. data/test/unit/ut_2_wfidgen.rb +0 -21
data/couch_url.txt ADDED
@@ -0,0 +1 @@
1
+ http://127.0.0.1:5984
data/jruby_issue.txt ADDED
@@ -0,0 +1,32 @@
1
+
2
+ ** #<ConcurrencyError: Detected invalid array contents due to unsynchronized modifications with concurrent users>
3
+ /Users/jmettraux/w/ruote/lib/ruote/log/test_logger.rb:124:in `check_waiting'
4
+ /Users/jmettraux/w/ruote/lib/ruote/log/test_logger.rb:71:in `notify'
5
+ /Users/jmettraux/w/ruote/lib/ruote/worker.rb:279:in `notify'
6
+ /Users/jmettraux/w/ruote/lib/ruote/worker.rb:276:in `each'
7
+ /Users/jmettraux/w/ruote/lib/ruote/worker.rb:276:in `notify'
8
+ /Users/jmettraux/w/ruote/lib/ruote/worker.rb:261:in `process'
9
+ /Users/jmettraux/w/ruote/lib/ruote/worker.rb:173:in `step'
10
+ /Users/jmettraux/w/ruote/lib/ruote/worker.rb:75:in `run'
11
+ /Users/jmettraux/w/ruote/lib/ruote/worker.rb:87:in `run_in_thread'
12
+ /Users/jmettraux/w/ruote/lib/ruote/worker.rb:87:in `initialize'
13
+ /Users/jmettraux/w/ruote/lib/ruote/worker.rb:87:in `new'
14
+ /Users/jmettraux/w/ruote/lib/ruote/worker.rb:87:in `run_in_thread'
15
+ /Users/jmettraux/w/ruote/lib/ruote/engine.rb:70:in `initialize'
16
+ ./test/functional/base.rb:33:in `new'
17
+ ./test/functional/base.rb:33:in `setup'
18
+ /Users/jmettraux/.rvm/rubies/jruby-1.5.6/lib/ruby/1.8/test/unit/testcase.rb:77:in `run'
19
+ /Users/jmettraux/.rvm/rubies/jruby-1.5.6/lib/ruby/1.8/test/unit/testsuite.rb:34:in `run'
20
+ /Users/jmettraux/.rvm/rubies/jruby-1.5.6/lib/ruby/1.8/test/unit/testsuite.rb:33:in `each'
21
+ /Users/jmettraux/.rvm/rubies/jruby-1.5.6/lib/ruby/1.8/test/unit/testsuite.rb:33:in `run'
22
+ /Users/jmettraux/.rvm/rubies/jruby-1.5.6/lib/ruby/1.8/test/unit/testsuite.rb:34:in `run'
23
+ /Users/jmettraux/.rvm/rubies/jruby-1.5.6/lib/ruby/1.8/test/unit/testsuite.rb:33:in `each'
24
+ /Users/jmettraux/.rvm/rubies/jruby-1.5.6/lib/ruby/1.8/test/unit/testsuite.rb:33:in `run'
25
+ /Users/jmettraux/.rvm/rubies/jruby-1.5.6/lib/ruby/1.8/test/unit/ui/testrunnermediator.rb:46:in `run_suite'
26
+ /Users/jmettraux/.rvm/rubies/jruby-1.5.6/lib/ruby/1.8/test/unit/ui/console/testrunner.rb:67:in `start_mediator'
27
+ /Users/jmettraux/.rvm/rubies/jruby-1.5.6/lib/ruby/1.8/test/unit/ui/console/testrunner.rb:41:in `start'
28
+ /Users/jmettraux/.rvm/rubies/jruby-1.5.6/lib/ruby/1.8/test/unit/ui/testrunnerutilities.rb:29:in `run'
29
+ /Users/jmettraux/.rvm/rubies/jruby-1.5.6/lib/ruby/1.8/test/unit/autorunner.rb:216:in `run'
30
+ /Users/jmettraux/.rvm/rubies/jruby-1.5.6/lib/ruby/1.8/test/unit/autorunner.rb:12:in `run'
31
+ /Users/jmettraux/.rvm/rubies/jruby-1.5.6/lib/ruby/1.8/test/unit.rb:279
32
+
data/lib/ruote.rb CHANGED
@@ -3,5 +3,5 @@ require 'ruote/storage/hash_storage'
3
3
  require 'ruote/worker'
4
4
  require 'ruote/engine'
5
5
  require 'ruote/participant'
6
- require 'ruote/parser/ruby_dsl'
6
+ require 'ruote/reader/ruby_dsl'
7
7
 
data/lib/ruote/context.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2005-2010, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2005-2011, John Mettraux, jmettraux@gmail.com
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  # of this software and associated documentation files (the "Software"), to deal
@@ -29,7 +29,7 @@ module Ruote
29
29
 
30
30
  #
31
31
  # A sort of internal registry, via a shared instance of this class, the worker
32
- # and the engine can access subservices like parser, treechecker,
32
+ # and the engine can access subservices like reader, treechecker,
33
33
  # wfid_generator and so on.
34
34
  #
35
35
  class Context
@@ -40,7 +40,7 @@ module Ruote
40
40
  attr_accessor :worker
41
41
  attr_accessor :engine
42
42
 
43
- def initialize (storage, worker=nil)
43
+ def initialize(storage, worker=nil)
44
44
 
45
45
  @storage = storage
46
46
  @storage.context = self
@@ -80,14 +80,14 @@ module Ruote
80
80
  # # ...
81
81
  # end
82
82
  #
83
- def [] (key)
83
+ def [](key)
84
84
 
85
85
  SERVICE_PREFIX.match(key) ? @services[key] : get_conf[key]
86
86
  end
87
87
 
88
88
  # Mostly used by engine#configure
89
89
  #
90
- def []= (key, value)
90
+ def []=(key, value)
91
91
 
92
92
  raise(
93
93
  ArgumentError.new('use context#add_service to register services')
@@ -105,7 +105,7 @@ module Ruote
105
105
  get_conf.keys
106
106
  end
107
107
 
108
- def add_service (key, *args)
108
+ def add_service(key, *args)
109
109
 
110
110
  path, klass, opts = args
111
111
 
@@ -141,8 +141,8 @@ module Ruote
141
141
  #
142
142
  def shutdown
143
143
 
144
- @storage.shutdown if @storage.respond_to?(:shutdown)
145
144
  @worker.shutdown if @worker
145
+ @storage.shutdown if @storage.respond_to?(:shutdown)
146
146
 
147
147
  @services.values.each { |s| s.shutdown if s.respond_to?(:shutdown) }
148
148
  end
@@ -168,8 +168,8 @@ module Ruote
168
168
 
169
169
  { 's_wfidgen' => [
170
170
  'ruote/id/mnemo_wfid_generator', 'Ruote::MnemoWfidGenerator' ],
171
- 's_parser' => [
172
- 'ruote/parser', 'Ruote::Parser' ],
171
+ 's_reader' => [
172
+ 'ruote/reader', 'Ruote::Reader' ],
173
173
  's_treechecker' => [
174
174
  'ruote/svc/treechecker', 'Ruote::TreeChecker' ],
175
175
  's_expmap' => [
@@ -185,7 +185,9 @@ module Ruote
185
185
  's_error_handler' => [
186
186
  'ruote/svc/error_handler', 'Ruote::ErrorHandler' ],
187
187
  's_logger' => [
188
- 'ruote/log/wait_logger', 'Ruote::WaitLogger' ] }
188
+ 'ruote/log/wait_logger', 'Ruote::WaitLogger' ],
189
+ 's_history' => [
190
+ 'ruote/log/default_history', 'Ruote::DefaultHistory' ] }
189
191
  end
190
192
  end
191
193
  end
data/lib/ruote/engine.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2005-2010, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2005-2011, John Mettraux, jmettraux@gmail.com
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  # of this software and associated documentation files (the "Software"), to deal
@@ -58,7 +58,7 @@ module Ruote
58
58
  # If the second options is set to { :join => true }, the worker wil
59
59
  # be started and run in the current thread.
60
60
  #
61
- def initialize (worker_or_storage, opts=true)
61
+ def initialize(worker_or_storage, opts=true)
62
62
 
63
63
  @context = worker_or_storage.context
64
64
  @context.engine = self
@@ -97,6 +97,13 @@ module Ruote
97
97
  @context.worker
98
98
  end
99
99
 
100
+ # A shortcut for engine.context.history
101
+ #
102
+ def history
103
+
104
+ @context.history
105
+ end
106
+
100
107
  # Quick note : the implementation of launch is found in the module
101
108
  # Ruote::ReceiverMixin that the engine includes.
102
109
  #
@@ -110,9 +117,9 @@ module Ruote
110
117
  #
111
118
  # Returns the wfid (workflow instance id) of the running single.
112
119
  #
113
- def launch_single (process_definition, fields={}, variables={})
120
+ def launch_single(process_definition, fields={}, variables={})
114
121
 
115
- tree = @context.parser.parse(process_definition)
122
+ tree = @context.reader.read(process_definition)
116
123
  name = tree[1]['name'] || (tree[1].find { |k, v| v.nil? } || []).first
117
124
 
118
125
  raise ArgumentError.new(
@@ -124,10 +131,8 @@ module Ruote
124
131
  }
125
132
  wfid, timestamp = singles['h'][name]
126
133
 
127
- if wfid && (timestamp + 1.0 < Time.now.to_f || process(wfid) != nil)
128
- return wfid
129
- end
130
- # process is already running
134
+ return wfid if wfid && (ps(wfid) || Time.now.to_f - timestamp < 1.0)
135
+ # return wfid if 'singleton' process is already running
131
136
 
132
137
  wfid = @context.wfidgen.generate
133
138
 
@@ -154,45 +159,44 @@ module Ruote
154
159
  wfid
155
160
  end
156
161
 
157
- # Given a process identifier (wfid), cancels this process.
162
+ # Given a workitem or a fei, will do a cancel_expression,
163
+ # else it's a wfid and it does a cancel_process.
158
164
  #
159
- def cancel_process (wfid)
165
+ def cancel(wi_or_fei_or_wfid)
160
166
 
161
- @context.storage.put_msg('cancel_process', 'wfid' => wfid)
162
- end
167
+ target = Ruote.extract_id(wi_or_fei_or_wfid)
163
168
 
164
- # Given a process identifier (wfid), kills this process. Killing is
165
- # equivalent to cancelling, but when killing, :on_cancel attributes
166
- # are not triggered.
167
- #
168
- def kill_process (wfid)
169
-
170
- @context.storage.put_msg('kill_process', 'wfid' => wfid)
169
+ if target.is_a?(String)
170
+ @context.storage.put_msg('cancel_process', 'wfid' => target)
171
+ else
172
+ @context.storage.put_msg('cancel', 'fei' => target)
173
+ end
171
174
  end
172
175
 
173
- # Cancels a segment of process instance. Since expressions are nodes in
174
- # processes instances, cancelling an expression, will cancel the expression
175
- # and all its children (the segment of process).
176
- #
177
- def cancel_expression (fei)
178
-
179
- fei = fei.to_h if fei.respond_to?(:to_h)
180
- @context.storage.put_msg('cancel', 'fei' => fei)
181
- end
176
+ alias cancel_process cancel
177
+ alias cancel_expression cancel
182
178
 
183
- # Like #cancel_expression, but :on_cancel attributes (of the expressions)
184
- # are not triggered.
179
+ # Given a workitem or a fei, will do a kill_expression,
180
+ # else it's a wfid and it does a kill_process.
185
181
  #
186
- def kill_expression (fei)
182
+ def kill(wi_or_fei_or_wfid)
187
183
 
188
- fei = fei.to_h if fei.respond_to?(:to_h)
189
- @context.storage.put_msg('cancel', 'fei' => fei, 'flavour' => 'kill')
184
+ target = Ruote.extract_id(wi_or_fei_or_wfid)
185
+
186
+ if target.is_a?(String)
187
+ @context.storage.put_msg('kill_process', 'wfid' => target)
188
+ else
189
+ @context.storage.put_msg('cancel', 'fei' => target, 'flavour' => 'kill')
190
+ end
190
191
  end
191
192
 
193
+ alias kill_process kill
194
+ alias kill_expression kill
195
+
192
196
  # Replays at a given error (hopefully you fixed the cause of the error
193
197
  # before replaying...)
194
198
  #
195
- def replay_at_error (err)
199
+ def replay_at_error(err)
196
200
 
197
201
  msg = err.msg.dup
198
202
  action = msg.delete('action')
@@ -233,7 +237,7 @@ module Ruote
233
237
  #
234
238
  # engine.re_apply(fei, :merge_in_fields => { 'customer' => 'bob' })
235
239
  #
236
- def re_apply (fei, opts={})
240
+ def re_apply(fei, opts={})
237
241
 
238
242
  @context.storage.put_msg('cancel', 'fei' => fei.to_h, 're_apply' => opts)
239
243
  end
@@ -241,9 +245,9 @@ module Ruote
241
245
  # Returns a ProcessStatus instance describing the current status of
242
246
  # a process instance.
243
247
  #
244
- def process (wfid)
248
+ def process(wfid)
245
249
 
246
- list_processes([ wfid ], {}).first
250
+ statuses([ wfid ], {}).first
247
251
  end
248
252
 
249
253
  # Returns an array of ProcessStatus instances.
@@ -257,18 +261,19 @@ module Ruote
257
261
  # To simply list the wfids of the currently running, Engine#process_wfids
258
262
  # is way cheaper to call.
259
263
  #
260
- def processes (opts={})
264
+ def processes(opts={})
261
265
 
262
- wfids = nil
266
+ wfids = @context.storage.expression_wfids(opts)
263
267
 
264
- if opts.size > 0
265
-
266
- wfids = @context.storage.expression_wfids(opts)
268
+ opts[:count] ? wfids.size : statuses(wfids, opts)
269
+ end
267
270
 
268
- return wfids.size if opts[:count]
269
- end
271
+ # Returns a list of processes or the process status of a given process
272
+ # instance.
273
+ #
274
+ def ps(wfid=nil)
270
275
 
271
- list_processes(wfids, opts)
276
+ wfid == nil ? processes : process(wfid)
272
277
  end
273
278
 
274
279
  # Returns an array of current errors (hashes)
@@ -281,7 +286,7 @@ module Ruote
281
286
  #
282
287
  # engine.errors(:skip => 100, :limit => 100)
283
288
  #
284
- def errors (wfid=nil)
289
+ def errors(wfid=nil)
285
290
 
286
291
  wfid, options = wfid.is_a?(Hash) ? [ nil, wfid ] : [ wfid, {} ]
287
292
 
@@ -307,7 +312,7 @@ module Ruote
307
312
  #
308
313
  # engine.schedules(:skip => 100, :limit => 100)
309
314
  #
310
- def schedules (wfid=nil)
315
+ def schedules(wfid=nil)
311
316
 
312
317
  wfid, options = wfid.is_a?(Hash) ? [ nil, wfid ] : [ wfid, {} ]
313
318
 
@@ -317,7 +322,7 @@ module Ruote
317
322
 
318
323
  return scheds if options[:count]
319
324
 
320
- scheds.collect { |sched| Ruote.schedule_to_h(sched) }
325
+ scheds.collect { |s| Ruote.schedule_to_h(s) }.sort_by { |s| s['wfid'] }
321
326
  end
322
327
 
323
328
  # Returns a [sorted] list of wfids of the process instances currently
@@ -326,11 +331,37 @@ module Ruote
326
331
  # This operation is substantially less costly than Engine#processes (though
327
332
  # the 'how substantially' depends on the storage chosen).
328
333
  #
329
- def process_wfids
334
+ def process_ids
335
+
336
+ @context.storage.expression_wfids({})
337
+ end
338
+
339
+ alias process_wfids process_ids
340
+
341
+ # Warning : expensive operation.
342
+ #
343
+ # Leftovers are workitems, errors and schedules belonging to process
344
+ # instances for which there are no more expressions left.
345
+ #
346
+ # Better delete them or investigate why they are left here.
347
+ #
348
+ # The result is a list of documents (hashes) as found in the storage. Each
349
+ # of them might represent a workitem, an error or a schedule.
350
+ #
351
+ # If you want to delete one of them you can do
352
+ #
353
+ # engine.storage.delete(doc)
354
+ #
355
+ def leftovers
356
+
357
+ wfids = @context.storage.expression_wfids({})
358
+
359
+ wis = @context.storage.get_many('workitems').compact
360
+ ers = @context.storage.get_many('errors').compact
361
+ scs = @context.storage.get_many('schedules').compact
362
+ # some slow storages need the compaction... [c]ouch...
330
363
 
331
- @context.storage.ids('expressions').collect { |sfei|
332
- sfei.split('!').last
333
- }.uniq.sort
364
+ (wis + ers + scs).reject { |doc| wfids.include?(doc['fei']['wfid']) }
334
365
  end
335
366
 
336
367
  # Shuts down the engine, mostly passes the shutdown message to the other
@@ -371,7 +402,7 @@ module Ruote
371
402
  #
372
403
  # engine.wait_for('20100612-bezerijozo', '20100612-yakisoba')
373
404
  #
374
- def wait_for (*items)
405
+ def wait_for(*items)
375
406
 
376
407
  logger = @context['s_logger']
377
408
 
@@ -390,20 +421,43 @@ module Ruote
390
421
  worker.join if worker
391
422
  end
392
423
 
393
- # Loads and parses the process definition at the given path.
424
+ # Loads (and turns into a tree) the process definition at the given path.
394
425
  #
395
- def load_definition (path)
426
+ def load_definition(path)
396
427
 
397
- @context.parser.parse(path)
428
+ @context.reader.read(path)
398
429
  end
399
430
 
400
- # Registers a participant in the engine. Returns the participant instance.
431
+ # Registers a participant in the engine.
401
432
  #
402
- # Some examples :
433
+ # Takes the form
434
+ #
435
+ # engine.register_participant name_or_regex, klass, opts={}
436
+ #
437
+ # With the form
438
+ #
439
+ # engine.register_participant name_or_regex do |workitem|
440
+ # # ...
441
+ # end
442
+ #
443
+ # A BlockParticipant is automatically created.
403
444
  #
404
- # require 'ruote/part/hash_participant'
405
- # alice = engine.register_participant 'alice', Ruote::HashParticipant
406
- # # register an in-memory (hash) store for Alice's workitems
445
+ #
446
+ # == name or regex
447
+ #
448
+ # When registering participants, strings or regexes are accepted. Behind
449
+ # the scenes, a regex is kept.
450
+ #
451
+ # Passing a string like "alain" will get ruote to automatically turn it
452
+ # into the following regex : /^alain$/.
453
+ #
454
+ # For finer control over this, pass a regex directly
455
+ #
456
+ # engine.register_participant /^user-/, MyParticipant
457
+ # # will match all workitems whose participant name starts with "user-"
458
+ #
459
+ #
460
+ # == some examples
407
461
  #
408
462
  # engine.register_participant 'compute_sum' do |wi|
409
463
  # wi.fields['sum'] = wi.fields['articles'].inject(0) do |s, (c, v)|
@@ -413,57 +467,37 @@ module Ruote
413
467
  # end
414
468
  #
415
469
  # class MyParticipant
416
- # def initialize (name)
417
- # @name = name
470
+ # def initialize(opts)
471
+ # @name = opts['name']
418
472
  # end
419
- # def consume (workitem)
473
+ # def consume(workitem)
420
474
  # workitem.fields['rocket_name'] = @name
421
475
  # send_to_the_moon(workitem)
422
476
  # end
423
- # def cancel (fei, flavour)
477
+ # def cancel(fei, flavour)
424
478
  # # do nothing
425
479
  # end
426
480
  # end
427
- # engine.register_participant /^moon-.+/, MyParticipant.new('Saturn-V')
428
481
  #
482
+ # engine.register_participant(
483
+ # /^moon-.+/, MyParticipant, 'name' => 'Saturn-V')
429
484
  #
430
- # == 'stateless' participants are preferred over 'stateful' ones
431
- #
432
- # Ruote 2.1 is OK with 1 storage and 1+ workers. The workers may be
433
- # in other ruby runtimes. This implies that if you have registered a
434
- # participant instance (instead of passing its classname and options),
435
- # that participant will only run in the worker 'embedded' in the engine
436
- # where it was registered... Let me rephrase it, participants instantiated
437
- # at registration time (and that includes block participants) only runs
438
- # in one worker, always the same.
439
- #
440
- # 'stateless' participants, instantiated at each dispatch, are preferred.
441
- # Any worker can handle them.
442
- #
443
- # Block participants are still fine for demos (where the worker is included
444
- # in the engine (see all the quickstarts). And small engines with 1 worker
445
- # are not that bad, not everybody is building huge systems).
446
- #
447
- # Here is a 'stateless' participant example :
448
- #
449
- # class MyStatelessParticipant
450
- # def initialize (opts)
451
- # @opts = opts
452
- # end
453
- # def consume (workitem)
454
- # workitem.fields['rocket_name'] = @opts['name']
455
- # send_to_the_moon(workitem)
456
- # end
457
- # def cancel (fei, flavour)
458
- # # do nothing
485
+ # # computing the total for a invoice being passed in the workitem.
486
+ # #
487
+ # class TotalParticipant
488
+ # include Ruote::LocalParticipant
489
+ #
490
+ # def consume(workitem)
491
+ # workitem['total'] = workitem.fields['items'].inject(0.0) { |t, item|
492
+ # t + item['count'] * PricingService.lookup(item['id'])
493
+ # }
494
+ # reply_to_engine(workitem)
459
495
  # end
460
496
  # end
461
- #
462
- # engine.register_participant(
463
- # 'moon', MyStatelessParticipant, 'name' => 'saturn5')
497
+ # engine.register_participant 'total', TotalParticipant
464
498
  #
465
499
  # Remember that the options (the hash that follows the class name), must be
466
- # serialisable via JSON.
500
+ # serializable via JSON.
467
501
  #
468
502
  #
469
503
  # == require_path and load_path
@@ -480,14 +514,18 @@ module Ruote
480
514
  # containing the participant implementation. 'require' will load and eval
481
515
  # the ruby code only once, 'load' each time.
482
516
  #
483
- def register_participant (regex, participant=nil, opts={}, &block)
517
+ def register_participant(regex, participant=nil, opts={}, &block)
518
+
519
+ if participant.is_a?(Hash)
520
+ opts = participant
521
+ participant = nil
522
+ end
484
523
 
485
524
  pa = @context.plist.register(regex, participant, opts, block)
486
525
 
487
526
  @context.storage.put_msg(
488
527
  'participant_registered',
489
- 'regex' => regex.to_s,
490
- 'engine_worker_only' => (pa != nil))
528
+ 'regex' => regex.is_a?(Regexp) ? regex.inspect : regex.to_s)
491
529
 
492
530
  pa
493
531
  end
@@ -506,7 +544,7 @@ module Ruote
506
544
  #
507
545
  # Originally implemented in ruote-kit by Torsten Schoenebaum.
508
546
  #
509
- def register (*args, &block)
547
+ def register(*args, &block)
510
548
 
511
549
  if args.size > 0
512
550
  register_participant(*args, &block)
@@ -518,7 +556,7 @@ module Ruote
518
556
 
519
557
  # Removes/unregisters a participant from the engine.
520
558
  #
521
- def unregister_participant (name_or_participant)
559
+ def unregister_participant(name_or_participant)
522
560
 
523
561
  re = @context.plist.unregister(name_or_participant)
524
562
 
@@ -556,11 +594,22 @@ module Ruote
556
594
  @context.plist.list
557
595
  end
558
596
 
559
- # Accepts a list of Ruote::ParticipantEntry instances.
597
+ # Accepts a list of Ruote::ParticipantEntry instances or a list of
598
+ # [ regex, [ classname, opts ] ] arrays.
560
599
  #
561
600
  # See Engine#participant_list
562
601
  #
563
- def participant_list= (pl)
602
+ # Some examples :
603
+ #
604
+ # engine.participant_list = [
605
+ # [ '^charly$', [ 'Ruote::StorageParticipant', {} ] ],
606
+ # [ '.+', [ 'MyDefaultParticipant', { 'default' => true } ]
607
+ # ]
608
+ #
609
+ # This method writes the participant list in one go, it might be easier to
610
+ # use than to register participant one by ones.
611
+ #
612
+ def participant_list=(pl)
564
613
 
565
614
  @context.plist.list = pl
566
615
  end
@@ -578,6 +627,14 @@ module Ruote
578
627
  @storage_participant ||= Ruote::StorageParticipant.new(self)
579
628
  end
580
629
 
630
+ # Returns an instance of the participant registered under the given name.
631
+ # Returns nil if there is no participant registered for that name.
632
+ #
633
+ def participant(name)
634
+
635
+ @context.plist.lookup(name, nil)
636
+ end
637
+
581
638
  # Adds a service locally (will not get propagated to other workers).
582
639
  #
583
640
  # tracer = Tracer.new
@@ -589,7 +646,7 @@ module Ruote
589
646
  #
590
647
  # This method returns the service instance it just bound.
591
648
  #
592
- def add_service (name, path_or_instance, classname=nil, opts=nil)
649
+ def add_service(name, path_or_instance, classname=nil, opts=nil)
593
650
 
594
651
  @context.add_service(name, path_or_instance, classname, opts)
595
652
  end
@@ -603,30 +660,111 @@ module Ruote
603
660
  # # allow ruby_eval
604
661
  # @engine.configure('ruby_eval_allowed', true)
605
662
  #
606
- def configure (config_key, value)
663
+ def configure(config_key, value)
607
664
 
608
665
  @context[config_key] = value
609
666
  end
610
667
 
611
- # A convenience methods for advanced users (like Oleg).
668
+ # Returns a configuration value.
612
669
  #
613
- # Given a fei (flow expression id), fetches the workitem as stored in
614
- # the expression with that fei.
615
- # This is the "applied workitem", if the workitem is currently handed to
616
- # a participant, this method will return the workitem as applied, not
617
- # the workitem as saved by the participant/user in whatever worklist it
618
- # uses. If you need that workitem, do the vanilla thing and ask it to
619
- # the [storage] participant or its worklist.
670
+ # engine.configure('ruby_eval_allowed', true)
620
671
  #
621
- # The fei might be a string fei (result of fei.to_storage_id), a
622
- # FlowExpressionId instance or a hash.
672
+ # p engine.configuration('ruby_eval_allowed')
673
+ # # => true
623
674
  #
624
- def workitem (fei)
675
+ def configuration(config_key)
625
676
 
626
- fexp = Ruote::Exp::FlowExpression.fetch(
627
- @context, Ruote::FlowExpressionId.extract_h(fei))
677
+ @context[config_key]
678
+ end
628
679
 
629
- Ruote::Workitem.new(fexp.h.applied_workitem)
680
+ # Returns the process tree that is triggered in case of error.
681
+ #
682
+ # Note that this 'on_error' doesn't trigger if an on_error is defined
683
+ # in the process itself.
684
+ #
685
+ # Returns nil if there is no 'on_error' set.
686
+ #
687
+ def on_error
688
+
689
+ @context.storage.get_trackers['trackers']['on_error']['msg']['tree']
690
+
691
+ rescue
692
+ nil
693
+ end
694
+
695
+ # Returns the process tree that is triggered in case of process termination.
696
+ #
697
+ # Note that a termination process doesn't raise a termination process when
698
+ # it terminates itself.
699
+ #
700
+ # Returns nil if there is no 'on_terminate' set.
701
+ #
702
+ def on_terminate
703
+
704
+ @context.storage.get_trackers['trackers']['on_terminate']['msg']['tree']
705
+
706
+ rescue
707
+ nil
708
+ end
709
+
710
+ # Sets a participant or subprocess to be triggered when an error occurs
711
+ # in a process instance.
712
+ #
713
+ # engine.on_error = participant_name
714
+ #
715
+ # engine.on_error = subprocess_name
716
+ #
717
+ # engine.on_error = Ruote.process_definition do
718
+ # alpha
719
+ # end
720
+ #
721
+ # Note that this 'on_error' doesn't trigger if an on_error is defined
722
+ # in the process itself.
723
+ #
724
+ def on_error=(target)
725
+
726
+ @context.tracker.add_tracker(
727
+ nil, # do not track a specific wfid
728
+ 'error_intercepted', # react on 'error_intercepted' msgs
729
+ 'on_error', # the identifier
730
+ nil, # no specific condition
731
+ { 'action' => 'launch',
732
+ 'wfid' => 'replace',
733
+ 'tree' => target.is_a?(String) ?
734
+ [ 'define', {}, [ [ target, {}, [] ] ] ] : target,
735
+ 'workitem' => 'replace',
736
+ 'variables' => 'compile' })
737
+ end
738
+
739
+ # Sets a participant or a subprocess that is to be launched/called whenever
740
+ # a regular process terminates.
741
+ #
742
+ # engine.on_terminate = participant_name
743
+ #
744
+ # engine.on_terminate = subprocess_name
745
+ #
746
+ # engine.on_terminate = Ruote.define do
747
+ # alpha
748
+ # bravo
749
+ # end
750
+ #
751
+ # Note that a termination process doesn't raise a termination process when
752
+ # it terminates itself.
753
+ #
754
+ # on_terminate processes are not triggered for on_error processes.
755
+ # on_error processes are triggered for on_terminate processes as well.
756
+ #
757
+ def on_terminate=(target)
758
+
759
+ @context.tracker.add_tracker(
760
+ nil, # do not track a specific wfid
761
+ 'terminated', # react on 'error_intercepted' msgs
762
+ 'on_terminate', # the identifier
763
+ nil, # no specific condition
764
+ { 'action' => 'launch',
765
+ 'tree' => target.is_a?(String) ?
766
+ [ 'define', {}, [ [ target, {}, [] ] ] ] : target,
767
+ 'workitem' => 'replace' })
630
768
  end
631
769
 
632
770
  # A debug helper :
@@ -636,7 +774,7 @@ module Ruote
636
774
  # will let the engine (in fact the worker) pour all the details of the
637
775
  # executing process instances to STDOUT.
638
776
  #
639
- def noisy= (b)
777
+ def noisy=(b)
640
778
 
641
779
  @context.logger.noisy = b
642
780
  end
@@ -645,14 +783,15 @@ module Ruote
645
783
 
646
784
  # Used by #process and #processes
647
785
  #
648
- def list_processes (wfids, opts)
786
+ def statuses(wfids, opts)
649
787
 
650
- swfids = wfids ? wfids.collect { |wfid| /!#{wfid}-\d+$/ } : nil
788
+ swfids = wfids.collect { |wfid| /!#{wfid}-\d+$/ }
651
789
 
652
- exps = @context.storage.get_many('expressions', wfids)
653
- swis = @context.storage.get_many('workitems', wfids)
654
- errs = @context.storage.get_many('errors', wfids)
655
- schs = @context.storage.get_many('schedules', swfids)
790
+ exps = @context.storage.get_many('expressions', wfids).compact
791
+ swis = @context.storage.get_many('workitems', wfids).compact
792
+ errs = @context.storage.get_many('errors', wfids).compact
793
+ schs = @context.storage.get_many('schedules', swfids).compact
794
+ # some slow storages need the compaction... couch...
656
795
 
657
796
  errs = errs.collect { |err| ProcessError.new(err) }
658
797
  schs = schs.collect { |sch| Ruote.schedule_to_h(sch) }
@@ -672,13 +811,9 @@ module Ruote
672
811
  (by_wfid[sch['wfid']] ||= [ [], [], [], [] ])[3] << sch
673
812
  end
674
813
 
675
- wfids = if wfids
676
- wfids
677
- else
678
- wfids = by_wfid.keys.sort
679
- wfids = wfids.reverse if opts[:descending]
680
- wfids
681
- end
814
+ wfids = by_wfid.keys.sort
815
+ wfids = wfids.reverse if opts[:descending]
816
+ # re-adjust list of wfids, only take what was found
682
817
 
683
818
  wfids.inject([]) { |a, wfid|
684
819
  info = by_wfid[wfid]
@@ -696,17 +831,17 @@ module Ruote
696
831
  #
697
832
  class EngineVariables
698
833
 
699
- def initialize (storage)
834
+ def initialize(storage)
700
835
 
701
836
  @storage = storage
702
837
  end
703
838
 
704
- def [] (k)
839
+ def [](k)
705
840
 
706
841
  @storage.get_engine_variable(k)
707
842
  end
708
843
 
709
- def []= (k, v)
844
+ def []=(k, v)
710
845
 
711
846
  @storage.put_engine_variable(k, v)
712
847
  end
@@ -719,17 +854,17 @@ module Ruote
719
854
  #
720
855
  class ParticipantRegistrationProxy
721
856
 
722
- def initialize (engine)
857
+ def initialize(engine)
723
858
 
724
859
  @engine = engine
725
860
  end
726
861
 
727
- def participant (name, klass, options={})
862
+ def participant(name, klass=nil, options={}, &block)
728
863
 
729
- @engine.register_participant(name, klass, options)
864
+ @engine.register_participant(name, klass, options, &block)
730
865
  end
731
866
 
732
- def catchall (*args)
867
+ def catchall(*args)
733
868
 
734
869
  klass = args.empty? ? Ruote::StorageParticipant : args.first
735
870
  options = args[1] || {}
@@ -739,7 +874,7 @@ module Ruote
739
874
 
740
875
  # Maybe a bit audacious...
741
876
  #
742
- def method_missing (method_name, *args)
877
+ def method_missing(method_name, *args)
743
878
 
744
879
  participant(method_name, *args)
745
880
  end
@@ -748,7 +883,7 @@ module Ruote
748
883
  # Refines a schedule as found in the ruote storage into something a bit
749
884
  # easier to present.
750
885
  #
751
- def self.schedule_to_h (sched)
886
+ def self.schedule_to_h(sched)
752
887
 
753
888
  h = sched.dup
754
889