ruote 2.1.11 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
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