ruote-maestrodev 2.2.1

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 (265) hide show
  1. data/CHANGELOG.txt +290 -0
  2. data/CREDITS.txt +99 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.rdoc +88 -0
  5. data/Rakefile +108 -0
  6. data/TODO.txt +488 -0
  7. data/lib/ruote.rb +7 -0
  8. data/lib/ruote/context.rb +194 -0
  9. data/lib/ruote/engine.rb +1062 -0
  10. data/lib/ruote/engine/process_error.rb +122 -0
  11. data/lib/ruote/engine/process_status.rb +448 -0
  12. data/lib/ruote/exp/command.rb +87 -0
  13. data/lib/ruote/exp/commanded.rb +69 -0
  14. data/lib/ruote/exp/condition.rb +227 -0
  15. data/lib/ruote/exp/fe_add_branches.rb +138 -0
  16. data/lib/ruote/exp/fe_apply.rb +154 -0
  17. data/lib/ruote/exp/fe_cancel_process.rb +78 -0
  18. data/lib/ruote/exp/fe_command.rb +156 -0
  19. data/lib/ruote/exp/fe_concurrence.rb +321 -0
  20. data/lib/ruote/exp/fe_concurrent_iterator.rb +219 -0
  21. data/lib/ruote/exp/fe_cron.rb +141 -0
  22. data/lib/ruote/exp/fe_cursor.rb +324 -0
  23. data/lib/ruote/exp/fe_define.rb +112 -0
  24. data/lib/ruote/exp/fe_echo.rb +60 -0
  25. data/lib/ruote/exp/fe_equals.rb +115 -0
  26. data/lib/ruote/exp/fe_error.rb +82 -0
  27. data/lib/ruote/exp/fe_filter.rb +648 -0
  28. data/lib/ruote/exp/fe_forget.rb +88 -0
  29. data/lib/ruote/exp/fe_given.rb +154 -0
  30. data/lib/ruote/exp/fe_if.rb +127 -0
  31. data/lib/ruote/exp/fe_inc.rb +205 -0
  32. data/lib/ruote/exp/fe_iterator.rb +234 -0
  33. data/lib/ruote/exp/fe_let.rb +75 -0
  34. data/lib/ruote/exp/fe_listen.rb +304 -0
  35. data/lib/ruote/exp/fe_lose.rb +110 -0
  36. data/lib/ruote/exp/fe_noop.rb +45 -0
  37. data/lib/ruote/exp/fe_once.rb +215 -0
  38. data/lib/ruote/exp/fe_participant.rb +287 -0
  39. data/lib/ruote/exp/fe_read.rb +69 -0
  40. data/lib/ruote/exp/fe_redo.rb +82 -0
  41. data/lib/ruote/exp/fe_ref.rb +152 -0
  42. data/lib/ruote/exp/fe_registerp.rb +110 -0
  43. data/lib/ruote/exp/fe_reserve.rb +126 -0
  44. data/lib/ruote/exp/fe_restore.rb +102 -0
  45. data/lib/ruote/exp/fe_save.rb +72 -0
  46. data/lib/ruote/exp/fe_sequence.rb +59 -0
  47. data/lib/ruote/exp/fe_set.rb +154 -0
  48. data/lib/ruote/exp/fe_subprocess.rb +211 -0
  49. data/lib/ruote/exp/fe_that.rb +92 -0
  50. data/lib/ruote/exp/fe_undo.rb +67 -0
  51. data/lib/ruote/exp/fe_unregisterp.rb +69 -0
  52. data/lib/ruote/exp/fe_wait.rb +95 -0
  53. data/lib/ruote/exp/flowexpression.rb +886 -0
  54. data/lib/ruote/exp/iterator.rb +81 -0
  55. data/lib/ruote/exp/merge.rb +118 -0
  56. data/lib/ruote/exp/ro_attributes.rb +212 -0
  57. data/lib/ruote/exp/ro_filters.rb +136 -0
  58. data/lib/ruote/exp/ro_persist.rb +154 -0
  59. data/lib/ruote/exp/ro_variables.rb +189 -0
  60. data/lib/ruote/exp/ro_vf.rb +68 -0
  61. data/lib/ruote/fei.rb +260 -0
  62. data/lib/ruote/id/mnemo_wfid_generator.rb +43 -0
  63. data/lib/ruote/id/wfid_generator.rb +81 -0
  64. data/lib/ruote/log/default_history.rb +122 -0
  65. data/lib/ruote/log/pretty.rb +176 -0
  66. data/lib/ruote/log/storage_history.rb +159 -0
  67. data/lib/ruote/log/test_logger.rb +208 -0
  68. data/lib/ruote/log/wait_logger.rb +64 -0
  69. data/lib/ruote/part/block_participant.rb +137 -0
  70. data/lib/ruote/part/code_participant.rb +81 -0
  71. data/lib/ruote/part/engine_participant.rb +189 -0
  72. data/lib/ruote/part/local_participant.rb +138 -0
  73. data/lib/ruote/part/no_op_participant.rb +60 -0
  74. data/lib/ruote/part/null_participant.rb +54 -0
  75. data/lib/ruote/part/rev_participant.rb +169 -0
  76. data/lib/ruote/part/smtp_participant.rb +116 -0
  77. data/lib/ruote/part/storage_participant.rb +392 -0
  78. data/lib/ruote/part/template.rb +84 -0
  79. data/lib/ruote/participant.rb +7 -0
  80. data/lib/ruote/reader.rb +278 -0
  81. data/lib/ruote/reader/json.rb +49 -0
  82. data/lib/ruote/reader/radial.rb +290 -0
  83. data/lib/ruote/reader/ruby_dsl.rb +186 -0
  84. data/lib/ruote/reader/xml.rb +99 -0
  85. data/lib/ruote/receiver/base.rb +212 -0
  86. data/lib/ruote/storage/base.rb +364 -0
  87. data/lib/ruote/storage/composite_storage.rb +121 -0
  88. data/lib/ruote/storage/fs_storage.rb +139 -0
  89. data/lib/ruote/storage/hash_storage.rb +211 -0
  90. data/lib/ruote/svc/dispatch_pool.rb +158 -0
  91. data/lib/ruote/svc/dollar_sub.rb +298 -0
  92. data/lib/ruote/svc/error_handler.rb +138 -0
  93. data/lib/ruote/svc/expression_map.rb +97 -0
  94. data/lib/ruote/svc/participant_list.rb +397 -0
  95. data/lib/ruote/svc/tracker.rb +172 -0
  96. data/lib/ruote/svc/treechecker.rb +141 -0
  97. data/lib/ruote/tree_dot.rb +85 -0
  98. data/lib/ruote/util/filter.rb +525 -0
  99. data/lib/ruote/util/hashdot.rb +79 -0
  100. data/lib/ruote/util/look.rb +128 -0
  101. data/lib/ruote/util/lookup.rb +127 -0
  102. data/lib/ruote/util/misc.rb +167 -0
  103. data/lib/ruote/util/ometa.rb +71 -0
  104. data/lib/ruote/util/serializer.rb +103 -0
  105. data/lib/ruote/util/subprocess.rb +88 -0
  106. data/lib/ruote/util/time.rb +100 -0
  107. data/lib/ruote/util/tree.rb +58 -0
  108. data/lib/ruote/version.rb +29 -0
  109. data/lib/ruote/worker.rb +386 -0
  110. data/lib/ruote/workitem.rb +394 -0
  111. data/phil.txt +14 -0
  112. data/ruote.gemspec +44 -0
  113. data/test/bm/ci.rb +55 -0
  114. data/test/bm/ici.rb +71 -0
  115. data/test/bm/juuman.rb +54 -0
  116. data/test/bm/launch_bench.rb +37 -0
  117. data/test/bm/load_26c.rb +97 -0
  118. data/test/bm/mega.rb +64 -0
  119. data/test/bm/seq_thousand.rb +31 -0
  120. data/test/bm/t.rb +35 -0
  121. data/test/functional/base.rb +247 -0
  122. data/test/functional/concurrent_base.rb +98 -0
  123. data/test/functional/crunner.rb +31 -0
  124. data/test/functional/ct_0_concurrence.rb +65 -0
  125. data/test/functional/ct_1_iterator.rb +67 -0
  126. data/test/functional/ct_2_cancel.rb +81 -0
  127. data/test/functional/eft_0_process_definition.rb +65 -0
  128. data/test/functional/eft_10_cancel_process.rb +46 -0
  129. data/test/functional/eft_11_wait.rb +109 -0
  130. data/test/functional/eft_12_listen.rb +500 -0
  131. data/test/functional/eft_13_iterator.rb +342 -0
  132. data/test/functional/eft_14_cursor.rb +456 -0
  133. data/test/functional/eft_15_loop.rb +69 -0
  134. data/test/functional/eft_16_if.rb +183 -0
  135. data/test/functional/eft_17_equals.rb +55 -0
  136. data/test/functional/eft_18_concurrent_iterator.rb +410 -0
  137. data/test/functional/eft_19_reserve.rb +136 -0
  138. data/test/functional/eft_1_echo.rb +68 -0
  139. data/test/functional/eft_20_save.rb +116 -0
  140. data/test/functional/eft_21_restore.rb +61 -0
  141. data/test/functional/eft_22_noop.rb +28 -0
  142. data/test/functional/eft_23_apply.rb +168 -0
  143. data/test/functional/eft_24_add_branches.rb +98 -0
  144. data/test/functional/eft_25_command.rb +28 -0
  145. data/test/functional/eft_26_error.rb +77 -0
  146. data/test/functional/eft_27_inc.rb +280 -0
  147. data/test/functional/eft_28_once.rb +135 -0
  148. data/test/functional/eft_29_cron.rb +64 -0
  149. data/test/functional/eft_2_sequence.rb +58 -0
  150. data/test/functional/eft_30_ref.rb +155 -0
  151. data/test/functional/eft_31_registerp.rb +130 -0
  152. data/test/functional/eft_32_lose.rb +93 -0
  153. data/test/functional/eft_33_let.rb +31 -0
  154. data/test/functional/eft_34_given.rb +123 -0
  155. data/test/functional/eft_35_filter.rb +375 -0
  156. data/test/functional/eft_36_read.rb +95 -0
  157. data/test/functional/eft_3_participant.rb +149 -0
  158. data/test/functional/eft_4_set.rb +296 -0
  159. data/test/functional/eft_5_subprocess.rb +163 -0
  160. data/test/functional/eft_6_concurrence.rb +304 -0
  161. data/test/functional/eft_7_forget.rb +61 -0
  162. data/test/functional/eft_8_undo.rb +114 -0
  163. data/test/functional/eft_9_redo.rb +138 -0
  164. data/test/functional/ft_0_worker.rb +65 -0
  165. data/test/functional/ft_10_dollar.rb +304 -0
  166. data/test/functional/ft_11_recursion.rb +109 -0
  167. data/test/functional/ft_12_launchitem.rb +43 -0
  168. data/test/functional/ft_13_variables.rb +151 -0
  169. data/test/functional/ft_14_re_apply.rb +324 -0
  170. data/test/functional/ft_15_timeout.rb +226 -0
  171. data/test/functional/ft_16_participant_params.rb +98 -0
  172. data/test/functional/ft_17_conditional.rb +102 -0
  173. data/test/functional/ft_18_kill.rb +138 -0
  174. data/test/functional/ft_19_participant_code.rb +67 -0
  175. data/test/functional/ft_1_process_status.rb +796 -0
  176. data/test/functional/ft_20_storage_participant.rb +543 -0
  177. data/test/functional/ft_21_forget.rb +153 -0
  178. data/test/functional/ft_22_process_definitions.rb +90 -0
  179. data/test/functional/ft_23_load_defs.rb +79 -0
  180. data/test/functional/ft_24_block_participant.rb +235 -0
  181. data/test/functional/ft_25_receiver.rb +207 -0
  182. data/test/functional/ft_26_participant_rtimeout.rb +179 -0
  183. data/test/functional/ft_27_var_indirection.rb +128 -0
  184. data/test/functional/ft_28_null_noop_participants.rb +51 -0
  185. data/test/functional/ft_29_part_template.rb +60 -0
  186. data/test/functional/ft_2_errors.rb +380 -0
  187. data/test/functional/ft_30_smtp_participant.rb +122 -0
  188. data/test/functional/ft_31_part_blocking.rb +72 -0
  189. data/test/functional/ft_33_participant_subprocess_priority.rb +32 -0
  190. data/test/functional/ft_34_cursor_rewind.rb +101 -0
  191. data/test/functional/ft_35_add_service.rb +56 -0
  192. data/test/functional/ft_36_storage_history.rb +150 -0
  193. data/test/functional/ft_37_default_history.rb +109 -0
  194. data/test/functional/ft_38_participant_more.rb +193 -0
  195. data/test/functional/ft_39_wait_for.rb +136 -0
  196. data/test/functional/ft_3_participant_registration.rb +574 -0
  197. data/test/functional/ft_40_wait_logger.rb +62 -0
  198. data/test/functional/ft_41_participants.rb +91 -0
  199. data/test/functional/ft_42_storage_copy.rb +71 -0
  200. data/test/functional/ft_43_participant_on_reply.rb +87 -0
  201. data/test/functional/ft_44_var_participant.rb +35 -0
  202. data/test/functional/ft_45_participant_accept.rb +64 -0
  203. data/test/functional/ft_46_launch_single.rb +83 -0
  204. data/test/functional/ft_47_wfid_generator.rb +54 -0
  205. data/test/functional/ft_48_lose.rb +112 -0
  206. data/test/functional/ft_49_engine_on_error.rb +201 -0
  207. data/test/functional/ft_4_cancel.rb +132 -0
  208. data/test/functional/ft_50_engine_config.rb +22 -0
  209. data/test/functional/ft_51_misc.rb +67 -0
  210. data/test/functional/ft_52_case.rb +134 -0
  211. data/test/functional/ft_53_engine_on_terminate.rb +95 -0
  212. data/test/functional/ft_54_patterns.rb +104 -0
  213. data/test/functional/ft_55_engine_participant.rb +303 -0
  214. data/test/functional/ft_56_filter_attribute.rb +259 -0
  215. data/test/functional/ft_57_rev_participant.rb +252 -0
  216. data/test/functional/ft_58_workitem.rb +69 -0
  217. data/test/functional/ft_59_pause.rb +343 -0
  218. data/test/functional/ft_5_on_error.rb +384 -0
  219. data/test/functional/ft_60_code_participant.rb +45 -0
  220. data/test/functional/ft_61_trailing_fields.rb +34 -0
  221. data/test/functional/ft_62_exp_name_and_dollar_substitution.rb +35 -0
  222. data/test/functional/ft_6_on_cancel.rb +221 -0
  223. data/test/functional/ft_7_tags.rb +177 -0
  224. data/test/functional/ft_8_participant_consumption.rb +124 -0
  225. data/test/functional/ft_9_subprocesses.rb +146 -0
  226. data/test/functional/restart_base.rb +34 -0
  227. data/test/functional/rt_0_wait.rb +55 -0
  228. data/test/functional/rt_1_listen.rb +56 -0
  229. data/test/functional/rt_2_errors.rb +56 -0
  230. data/test/functional/rt_3_once.rb +70 -0
  231. data/test/functional/rt_4_cron.rb +64 -0
  232. data/test/functional/rt_5_timeout.rb +60 -0
  233. data/test/functional/rtest.rb +8 -0
  234. data/test/functional/storage_helper.rb +93 -0
  235. data/test/functional/test.rb +44 -0
  236. data/test/functional/vertical.rb +46 -0
  237. data/test/path_helper.rb +15 -0
  238. data/test/test.rb +13 -0
  239. data/test/test_helper.rb +28 -0
  240. data/test/unit/storage.rb +428 -0
  241. data/test/unit/storages.rb +37 -0
  242. data/test/unit/test.rb +28 -0
  243. data/test/unit/ut_0_ruby_reader.rb +223 -0
  244. data/test/unit/ut_11_lookup.rb +122 -0
  245. data/test/unit/ut_13_serializer.rb +65 -0
  246. data/test/unit/ut_14_is_uri.rb +28 -0
  247. data/test/unit/ut_15_util.rb +57 -0
  248. data/test/unit/ut_16_reader.rb +225 -0
  249. data/test/unit/ut_18_engine.rb +47 -0
  250. data/test/unit/ut_19_part_template.rb +86 -0
  251. data/test/unit/ut_1_fei.rb +165 -0
  252. data/test/unit/ut_20_composite_storage.rb +74 -0
  253. data/test/unit/ut_21_svc_participant_list.rb +46 -0
  254. data/test/unit/ut_22_filter.rb +1094 -0
  255. data/test/unit/ut_23_svc_tracker.rb +48 -0
  256. data/test/unit/ut_24_radial_reader.rb +332 -0
  257. data/test/unit/ut_25_merge.rb +113 -0
  258. data/test/unit/ut_3_wait_logger.rb +39 -0
  259. data/test/unit/ut_4_expmap.rb +20 -0
  260. data/test/unit/ut_5_tree.rb +54 -0
  261. data/test/unit/ut_6_condition.rb +303 -0
  262. data/test/unit/ut_7_workitem.rb +99 -0
  263. data/test/unit/ut_8_tree_to_dot.rb +72 -0
  264. data/test/unit/ut_9_xml_reader.rb +61 -0
  265. metadata +504 -0
@@ -0,0 +1,92 @@
1
+ #--
2
+ # Copyright (c) 2005-2011, John Mettraux, jmettraux@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+ require 'ruote/exp/fe_sequence'
26
+
27
+
28
+ module Ruote::Exp
29
+
30
+ #
31
+ # The 'that' and the 'of' expressions are used in conjuction with
32
+ # the 'given' expression (GivenExpression).
33
+ #
34
+ # In can be used 'standalone', it thus becomes then merely an 'if'.
35
+ #
36
+ # The children of the that/of are executed if the condition evaluates to
37
+ # true. The children are executed one by one, as if the that/of were
38
+ # a sequence.
39
+ #
40
+ # given '${status}' do
41
+ # that '${location} == CH' do
42
+ # set 'f:bank' => 'UBS'
43
+ # subprocess 'buy_chocolate'
44
+ # end
45
+ # of 'ready' do
46
+ # participant 'saleshead', :msg => 'customer ready'
47
+ # participant 'salesman', :task => 'visiter customer'
48
+ # end
49
+ # of 'over' do
50
+ # participant 'manager', :msg => 'process over'
51
+ # end
52
+ # end
53
+ #
54
+ # (Yes, I know, it's a poor example).
55
+ #
56
+ class ThatExpression < SequenceExpression
57
+
58
+ names :that, :of
59
+
60
+ def reply(workitem)
61
+
62
+ if workitem['fei'] == h.fei # apply --> reply
63
+
64
+ cond = attribute(:t) || attribute(:test) || attribute_text
65
+
66
+ if name == 'of'
67
+ comparator = cond.match(/^\s*\/.*\/\s*$/) ? '=~' : '=='
68
+ cond = "#{workitem['fields']['__given__']} #{comparator} #{cond}"
69
+ end
70
+
71
+ h.result = Condition.true?(cond)
72
+
73
+ if h.result
74
+ apply_child(0, workitem)
75
+ else
76
+ reply_to_parent(workitem)
77
+ end
78
+
79
+ else # reply from child
80
+
81
+ super
82
+ end
83
+ end
84
+
85
+ def reply_to_parent(workitem)
86
+
87
+ workitem['fields']['__result__'] = h.result
88
+ super
89
+ end
90
+ end
91
+ end
92
+
@@ -0,0 +1,67 @@
1
+ #--
2
+ # Copyright (c) 2005-2011, John Mettraux, jmettraux@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+
26
+ module Ruote::Exp
27
+
28
+ #
29
+ # Undoes (cancels) another expression referred by its tag.
30
+ #
31
+ # pdef = Ruote.process_definition do
32
+ # concurrence do
33
+ # alpha :tag => 'kilroy'
34
+ # undo :ref => 'kilroy'
35
+ # end
36
+ # end
37
+ #
38
+ # This example is rather tiny, but it shows a process branch (undo) cancelling
39
+ # another (alpha).
40
+ #
41
+ # == cancel
42
+ #
43
+ # This expression is aliased to 'cancel'
44
+ #
45
+ # cancel :ref => 'invoicing_stage'
46
+ #
47
+ class UndoExpression < FlowExpression
48
+
49
+ names :undo, :cancel
50
+
51
+ def apply
52
+
53
+ ref = attribute(:ref) || attribute_text
54
+ tag = ref ? lookup_variable(ref) : nil
55
+
56
+ @context.storage.put_msg('cancel', 'fei' => tag) if Ruote.is_a_fei?(tag)
57
+
58
+ reply_to_parent(h.applied_workitem)
59
+ end
60
+
61
+ def reply(workitem)
62
+
63
+ # never called
64
+ end
65
+ end
66
+ end
67
+
@@ -0,0 +1,69 @@
1
+ #--
2
+ # Copyright (c) 2005-2011, John Mettraux, jmettraux@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+ require 'ruote/exp/fe_registerp'
26
+
27
+
28
+ module Ruote::Exp
29
+
30
+ #
31
+ # Unregisters a participant.
32
+ #
33
+ # Ruote.process_definition do
34
+ # unregisterp 'alfred'
35
+ # unregisterp :name => 'bob'
36
+ # end
37
+ #
38
+ # Shows the same behaviour as
39
+ #
40
+ # engine.unregister_participant 'alfred'
41
+ # engine.unregister_participant 'bob'
42
+ #
43
+ # The expression 'registerp' can be used to register participants from
44
+ # a process definition.
45
+ #
46
+ class UnregisterpExpression < RegisterpExpression
47
+
48
+ names :unregisterp
49
+
50
+ def apply
51
+
52
+ registerp_allowed?
53
+
54
+ name = attribute(:name) || attribute_text
55
+
56
+ result = begin
57
+ context.engine.unregister_participant(name)
58
+ true
59
+ rescue
60
+ false
61
+ end
62
+
63
+ h.applied_workitem['fields']['__result__'] = result
64
+
65
+ reply_to_parent(h.applied_workitem)
66
+ end
67
+ end
68
+ end
69
+
@@ -0,0 +1,95 @@
1
+ #--
2
+ # Copyright (c) 2005-2011, John Mettraux, jmettraux@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+
26
+ module Ruote::Exp
27
+
28
+ #
29
+ # Waits (sleeps) for a given period of time before resuming the flow.
30
+ #
31
+ # sequence do
32
+ # accounting :task => 'invoice'
33
+ # wait '30d' # 30 days
34
+ # accounting :task => 'check if customer paid'
35
+ # end
36
+ #
37
+ # '_sleep' is also OK
38
+ #
39
+ # _sleep '7d10h' # 7 days and 10 hours
40
+ #
41
+ # (the underscore prevents collision with Ruby's sleep method)
42
+ #
43
+ # == :for and :until
44
+ #
45
+ # 'wait' accepts as well :
46
+ #
47
+ # wait :for => '30d' # wait for 30 days
48
+ # wait :until => '2011/12/10 12:00:00' # any parseable date/time format
49
+ #
50
+ class WaitExpression < FlowExpression
51
+
52
+ names :wait, :sleep
53
+
54
+ def apply
55
+
56
+ h.for = attribute(:for) || attribute_text
57
+ h.until = attribute(:until)
58
+
59
+ s = h.for
60
+ s = h.until if s == ''
61
+
62
+ h.at = s
63
+
64
+ if h.at
65
+
66
+ h.schedule_id = @context.storage.put_schedule(
67
+ 'at',
68
+ h.fei,
69
+ h.at,
70
+ 'action' => 'reply',
71
+ 'fei' => h.fei,
72
+ 'workitem' => h.applied_workitem)
73
+
74
+ persist_or_raise
75
+
76
+ else
77
+
78
+ reply_to_parent(h.applied_workitem)
79
+ end
80
+ end
81
+
82
+ #--
83
+ # no need to override, simply reply to parent expression.
84
+ #def reply (workitem)
85
+ #end
86
+ #++
87
+
88
+ def cancel(flavour)
89
+
90
+ @context.storage.delete_schedule(h.schedule_id)
91
+ reply_to_parent(h.applied_workitem)
92
+ end
93
+ end
94
+ end
95
+
@@ -0,0 +1,886 @@
1
+ #--
2
+ # Copyright (c) 2005-2011, John Mettraux, jmettraux@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+ require 'ruote/util/time'
26
+ require 'ruote/util/ometa'
27
+ require 'ruote/util/hashdot'
28
+
29
+
30
+ module Ruote::Exp
31
+
32
+ #
33
+ # Ruote is a process definition interpreter. It doesn't directly "read"
34
+ # process definitions, it relies on a parser/generator to produce "abstract
35
+ # syntax trees" that look like
36
+ #
37
+ # [ expression_name, { ... attributes ... }, [ children_expressions ] ]
38
+ #
39
+ # The nodes (and leaves) in the trees are expressions. This is the base
40
+ # class for all expressions.
41
+ #
42
+ # The most visible expressions are "define", "sequence" and "participant".
43
+ # Think :
44
+ #
45
+ # pdef = Ruote.process_definition do
46
+ # sequence do
47
+ # participant :ref => 'customer'
48
+ # participant :ref => 'accounting'
49
+ # participant :ref => 'logistics'
50
+ # end
51
+ # end
52
+ #
53
+ # Each node is an expression...
54
+ #
55
+ class FlowExpression
56
+
57
+ include Ruote::WithH
58
+ include Ruote::WithMeta
59
+
60
+ require 'ruote/exp/ro_persist'
61
+ require 'ruote/exp/ro_attributes'
62
+ require 'ruote/exp/ro_variables'
63
+ require 'ruote/exp/ro_filters'
64
+ require 'ruote/exp/ro_vf'
65
+
66
+ COMMON_ATT_KEYS = %w[
67
+ if unless forget timeout on_error on_cancel on_timeout ]
68
+
69
+ attr_reader :h
70
+
71
+ h_reader :variables
72
+ h_reader :created_time
73
+ h_reader :original_tree
74
+ h_reader :updated_tree
75
+
76
+ h_reader :children
77
+ h_reader :state
78
+
79
+ h_reader :on_error
80
+ h_reader :on_cancel
81
+ h_reader :on_timeout
82
+
83
+ attr_reader :context
84
+
85
+ # Mostly used when the expression is returned via Ruote::Engine#ps(wfid) or
86
+ # Ruote::Engine#processes(). If an error occurred for this flow expression,
87
+ # #ps will set this error field so that it yields the ProcessError.
88
+ #
89
+ # So, for short, usually, this attribute yields nil.
90
+ #
91
+ attr_accessor :error
92
+
93
+ def initialize(context, h)
94
+
95
+ @context = context
96
+
97
+ @msg = nil
98
+ # contains generally the msg the expression got instantiated for
99
+
100
+ self.h = h
101
+
102
+ h._id ||= Ruote.to_storage_id(h.fei)
103
+ h['type'] ||= 'expressions'
104
+ h.name ||= self.class.expression_names.first
105
+ h.children ||= []
106
+ h.applied_workitem['fei'] = h.fei
107
+ h.created_time ||= Ruote.now_to_utc_s
108
+
109
+ h.on_cancel ||= attribute(:on_cancel)
110
+ h.on_error ||= attribute(:on_error)
111
+ h.on_timeout ||= attribute(:on_timeout)
112
+ end
113
+
114
+ def h=(hash)
115
+ @h = hash
116
+ class << h
117
+ include Ruote::HashDot
118
+ end
119
+ end
120
+
121
+ # Returns the Ruote::FlowExpressionId for this expression.
122
+ #
123
+ def fei
124
+
125
+ Ruote::FlowExpressionId.new(h.fei)
126
+ end
127
+
128
+ # Returns the Ruote::FlowExpressionIf of the parent expression, or nil
129
+ # if there is no parent expression.
130
+ #
131
+ def parent_id
132
+
133
+ h.parent_id ? Ruote::FlowExpressionId.new(h.parent_id) : nil
134
+ end
135
+
136
+ # Fetches the parent expression, or returns nil if there is no parent
137
+ # expression.
138
+ #
139
+ def parent
140
+
141
+ Ruote::Exp::FlowExpression.fetch(@context, h.parent_id)
142
+ end
143
+
144
+ # Turns this FlowExpression instance into a Hash (well, just hands back
145
+ # the base hash behind it).
146
+ #
147
+ def to_h
148
+
149
+ @h
150
+ end
151
+
152
+ # Instantiates expression back from hash.
153
+ #
154
+ def self.from_h(context, h)
155
+
156
+ exp_class = context.expmap.expression_class(h['name'])
157
+
158
+ exp_class.new(context, h)
159
+ end
160
+
161
+ # Fetches an expression from the storage and readies it for service.
162
+ #
163
+ def self.fetch(context, fei)
164
+
165
+ return nil if fei.nil?
166
+
167
+ fexp = context.storage.get('expressions', Ruote.to_storage_id(fei))
168
+
169
+ fexp ? from_h(context, fexp) : nil
170
+ end
171
+
172
+ #--
173
+ # META
174
+ #++
175
+
176
+ # Keeping track of names and aliases for the expression
177
+ #
178
+ def self.names(*exp_names)
179
+
180
+ exp_names = exp_names.collect { |n| n.to_s }
181
+ meta_def(:expression_names) { exp_names }
182
+ end
183
+
184
+ #--
185
+ # apply/reply
186
+ #++
187
+
188
+ # Called by the worker when it has something to do for a FlowExpression.
189
+ #
190
+ def self.do_action(context, msg)
191
+
192
+ fei = msg['fei']
193
+ action = msg['action']
194
+
195
+ #p msg unless fei
196
+
197
+ if action == 'reply' && fei['engine_id'] != context.engine_id
198
+ #
199
+ # the reply has to go to another engine, let's locate the
200
+ # 'engine participant' and give it the workitem/reply
201
+ #
202
+ # see ft_37 for a test/example
203
+
204
+ engine_participant =
205
+ context.plist.lookup(fei['engine_id'], msg['workitem'])
206
+
207
+ raise(
208
+ "no EngineParticipant found under name '#{fei['engine_id']}'"
209
+ ) unless engine_participant
210
+
211
+ engine_participant.reply(fei, msg['workitem'])
212
+ return
213
+ end
214
+
215
+ # normal case
216
+
217
+ fexp = nil
218
+
219
+ 3.times do
220
+ fexp = fetch(context, msg['fei'])
221
+ break if fexp
222
+ sleep 0.028
223
+ end
224
+ # this retry system is only useful with ruote-couch
225
+
226
+ fexp.send("do_#{action}", msg) if fexp
227
+ end
228
+
229
+ # Called by the worker when it has just created this FlowExpression and
230
+ # wants to apply it.
231
+ #
232
+ def do_apply(msg)
233
+
234
+ @msg = Ruote.fulldup(msg)
235
+
236
+ if not Condition.apply?(attribute(:if), attribute(:unless))
237
+
238
+ return reply_to_parent(h.applied_workitem)
239
+ end
240
+
241
+ if attribute(:forget).to_s == 'true'
242
+
243
+ pi = h.parent_id
244
+ wi = Ruote.fulldup(h.applied_workitem)
245
+
246
+ h.variables = compile_variables
247
+ h.parent_id = nil
248
+ h.forgotten = true
249
+
250
+ @context.storage.put_msg('reply', 'fei' => pi, 'workitem' => wi) if pi
251
+ # reply to parent immediately (if there is a parent)
252
+
253
+ elsif attribute(:lose).to_s == 'true'
254
+
255
+ h.lost = true
256
+ end
257
+
258
+ filter
259
+
260
+ consider_tag
261
+ consider_timeout
262
+
263
+ apply
264
+ end
265
+
266
+ # FlowExpression call this method when they're done and they want their
267
+ # parent expression to take over (it will end up calling the #reply of
268
+ # the parent expression).
269
+ #
270
+ def reply_to_parent(workitem, delete=true)
271
+
272
+ filter(workitem)
273
+
274
+ if h.tagname
275
+
276
+ unset_variable(h.tagname)
277
+
278
+ Ruote::Workitem.remove_tag(workitem, h.tagname)
279
+
280
+ @context.storage.put_msg(
281
+ 'left_tag',
282
+ 'tag' => h.tagname,
283
+ 'fei' => h.fei,
284
+ 'workitem' => workitem)
285
+ end
286
+
287
+ if h.timeout_schedule_id && h.state != 'timing_out'
288
+
289
+ @context.storage.delete_schedule(h.timeout_schedule_id)
290
+ end
291
+
292
+ if h.state == 'failing' # on_error is implicit (#fail got called)
293
+
294
+ trigger('on_error', workitem)
295
+
296
+ elsif h.state == 'cancelling' and h.on_cancel
297
+
298
+ trigger('on_cancel', workitem)
299
+
300
+ elsif h.state == 'cancelling' and h.on_re_apply
301
+
302
+ trigger('on_re_apply', workitem)
303
+
304
+ elsif h.state == 'timing_out' and h.on_timeout
305
+
306
+ trigger('on_timeout', workitem)
307
+
308
+ elsif h.lost and h.state == nil
309
+
310
+ # do not reply, sit here (and wait for cancellation probably)
311
+
312
+ else # vanilla reply
313
+
314
+ (do_unpersist || return) if delete
315
+ # remove expression from storage
316
+
317
+ if h.parent_id
318
+
319
+ @context.storage.put_msg(
320
+ 'reply',
321
+ 'fei' => h.parent_id,
322
+ 'workitem' => workitem.merge!('fei' => h.fei),
323
+ 'updated_tree' => h.updated_tree) # nil most of the time
324
+ else
325
+
326
+ @context.storage.put_msg(
327
+ h.forgotten ? 'ceased' : 'terminated',
328
+ 'wfid' => h.fei['wfid'],
329
+ 'fei' => h.fei,
330
+ 'workitem' => workitem)
331
+ end
332
+ end
333
+ end
334
+
335
+ # Wraps #reply (does the administrative part of the reply work).
336
+ #
337
+ def do_reply(msg)
338
+
339
+ @msg = Ruote.fulldup(msg)
340
+ # keeping the message, for 'retry' in collision cases
341
+
342
+ workitem = msg['workitem']
343
+ fei = workitem['fei']
344
+
345
+ if ut = msg['updated_tree']
346
+ ct = tree.dup
347
+ ct.last[Ruote::FlowExpressionId.child_id(fei)] = ut
348
+ update_tree(ct)
349
+ end
350
+
351
+ h.children.delete(fei)
352
+ # accept without any check ?
353
+
354
+ if h.state == 'paused'
355
+
356
+ (h['paused_replies'] ||= []) << msg
357
+
358
+ do_persist
359
+
360
+ elsif h.state != nil # failing or timing out ...
361
+
362
+ if h.children.size < 1
363
+ reply_to_parent(workitem)
364
+ else
365
+ persist_or_raise # for the updated h.children
366
+ end
367
+
368
+ else # vanilla reply
369
+
370
+ reply(workitem)
371
+ end
372
+ end
373
+
374
+ # (only makes sense for the participant expression though)
375
+ #
376
+ alias :do_receive :do_reply
377
+
378
+ # A default implementation for all the expressions.
379
+ #
380
+ def reply(workitem)
381
+
382
+ reply_to_parent(workitem)
383
+ end
384
+
385
+ # The raw handling of messages passed to expressions (the fine handling
386
+ # is done in the #cancel method).
387
+ #
388
+ def do_cancel(msg)
389
+
390
+ @msg = Ruote.fulldup(msg)
391
+
392
+ flavour = msg['flavour']
393
+
394
+ return if h.state == 'cancelling' && flavour != 'kill'
395
+ # cancel on cancel gets discarded
396
+
397
+ return if h.state == 'failed' && flavour == 'timeout'
398
+ # do not timeout expressions that are "in error" (failed)
399
+
400
+ @msg = Ruote.fulldup(msg)
401
+
402
+ h.state = case flavour
403
+ when 'kill' then 'dying'
404
+ when 'timeout' then 'timing_out'
405
+ else 'cancelling'
406
+ end
407
+
408
+ h.applied_workitem['fields']['__timed_out__'] = [
409
+ h.fei, Ruote.now_to_utc_s, tree.first, compile_atts
410
+ ] if h.state == 'timing_out'
411
+
412
+ if h.state == 'cancelling'
413
+
414
+ if t = msg['on_cancel']
415
+
416
+ h.on_cancel = t
417
+
418
+ elsif hra = msg['re_apply']
419
+
420
+ hra = {} if hra == true
421
+
422
+ h.on_re_apply = hra['tree'] || tree
423
+
424
+ if fs = hra['fields']
425
+ h.applied_workitem['fields'] = fs
426
+ end
427
+ if mfs = hra['merge_in_fields']
428
+ h.applied_workitem['fields'].merge!(mfs)
429
+ end
430
+ end
431
+ end
432
+
433
+ cancel(flavour)
434
+ end
435
+
436
+ # This default implementation cancels all the [registered] children
437
+ # of this expression.
438
+ #
439
+ def cancel(flavour)
440
+
441
+ return reply_to_parent(h.applied_workitem) if h.children.empty?
442
+ #
443
+ # there are no children, nothing to cancel, let's just reply to
444
+ # the parent expression
445
+
446
+ do_persist || return
447
+ #
448
+ # before firing the cancel message to the children
449
+ #
450
+ # if the do_persist returns false, it means it failed, implying this
451
+ # expression is stale, let's return, thus discarding this cancel message
452
+
453
+ children.each do |cfei|
454
+ #
455
+ # let's send a cancel message to each of the children
456
+ #
457
+ # maybe some of them are gone or have not yet been applied, anyway,
458
+ # the message are sent
459
+
460
+ @context.storage.put_msg(
461
+ 'cancel',
462
+ 'fei' => cfei,
463
+ 'parent_id' => h.fei, # indicating that this is a "cancel child"
464
+ 'flavour' => flavour)
465
+ end
466
+
467
+ #if ! children.find { |i| Ruote::Exp::FlowExpression.fetch(@context, i) }
468
+ # #
469
+ # # since none of the children could be found in the storage right now,
470
+ # # it could mean that all children are already done or it could mean
471
+ # # that they are not yet applied...
472
+ # #
473
+ # # just to be sure let's send a new cancel message to this expression
474
+ # #
475
+ # # it's very important, since if there is no child to cancel the parent
476
+ # # the flow might get stuck here
477
+ # @context.storage.put_msg(
478
+ # 'cancel',
479
+ # 'fei' => h.fei,
480
+ # 'flavour' => flavour)
481
+ #end
482
+ end
483
+
484
+ # Called when handling an on_error, will place itself in a 'failing' state
485
+ # and cancel the children (when the reply from the children comes back,
486
+ # the on_reply will get triggered).
487
+ #
488
+ def do_fail(msg)
489
+
490
+ @msg = Ruote.fulldup(msg)
491
+
492
+ @h['state'] = 'failing'
493
+ @h['applied_workitem'] = msg['workitem']
494
+
495
+ if h.children.size < 1
496
+ reply_to_parent(@h['applied_workitem'])
497
+ else
498
+ persist_or_raise
499
+ h.children.each { |i| @context.storage.put_msg('cancel', 'fei' => i) }
500
+ end
501
+ end
502
+
503
+ # Expression received a "pause" message. Will put the expression in the
504
+ # "paused" state and then pass the message to the children.
505
+ #
506
+ # If the expression is in a non-nil state (failed, timed_out, ...), the
507
+ # message will be ignored.
508
+ #
509
+ def do_pause(msg)
510
+
511
+ return if h.state != nil
512
+
513
+ h['state'] = 'paused'
514
+
515
+ do_persist || return
516
+
517
+ h.children.each { |i|
518
+ @context.storage.put_msg('pause', 'fei' => i)
519
+ } unless msg['breakpoint']
520
+ end
521
+
522
+ # Will "unpause" the expression (if it was paused), and trigger any
523
+ # 'paused_replies' (replies that came while the expression was paused).
524
+ #
525
+ def do_resume(msg)
526
+
527
+ return if h.state != 'paused'
528
+
529
+ h['state'] = nil
530
+ replies = h.delete('paused_replies') || []
531
+
532
+ do_persist || return
533
+
534
+ h.children.each { |i| @context.storage.put_msg('resume', 'fei' => i) }
535
+ # resume children
536
+
537
+ replies.each { |m| @context.storage.put_msg(m.delete('action'), m) }
538
+ # trigger replies
539
+ end
540
+
541
+ #--
542
+ # misc
543
+ #++
544
+
545
+ # Launches a subprocesses (usually called from the #apply of certain
546
+ # expression implementations.
547
+ #
548
+ def launch_sub(pos, subtree, opts={})
549
+
550
+ i = h.fei.merge(
551
+ 'subid' => Ruote.generate_subid(h.fei.inspect),
552
+ 'expid' => pos)
553
+
554
+ #p '=== launch_sub ==='
555
+ #p [ :launcher, h.fei['expid'], h.fei['subid'], h.fei['wfid'] ]
556
+ #p [ :launched, i['expid'], i['subid'], i['wfid'] ]
557
+
558
+ forget = opts[:forget]
559
+
560
+ register_child(i) unless forget
561
+
562
+ variables = (
563
+ forget ? compile_variables : {}
564
+ ).merge!(opts[:variables] || {})
565
+
566
+ @context.storage.put_msg(
567
+ 'launch',
568
+ 'fei' => i,
569
+ 'parent_id' => forget ? nil : h.fei,
570
+ 'tree' => subtree,
571
+ 'workitem' => opts[:workitem] || h.applied_workitem,
572
+ 'variables' => variables,
573
+ 'forgotten' => forget)
574
+ end
575
+
576
+ # Returns true if the given fei points to an expression in the parent
577
+ # chain of this expression.
578
+ #
579
+ def ancestor?(fei)
580
+
581
+ fei = fei.to_h if fei.respond_to?(:to_h)
582
+
583
+ return false unless h.parent_id
584
+ return true if h.parent_id == fei
585
+
586
+ parent.ancestor?(fei)
587
+ end
588
+
589
+ # Looks up "on_error" attribute
590
+ #
591
+ def lookup_on_error
592
+
593
+ if h.on_error
594
+
595
+ self
596
+
597
+ elsif h.parent_id
598
+
599
+ par = parent
600
+ # :( get_parent would probably be a better name for #parent
601
+
602
+ #if par.nil? && ($DEBUG || ARGV.include?('-d'))
603
+ # puts "~~"
604
+ # puts "parent gone for"
605
+ # puts "fei #{Ruote.sid(h.fei)}"
606
+ # puts "tree #{tree.inspect}"
607
+ # puts "replying to #{Ruote.sid(h.parent_id)}"
608
+ # puts "~~"
609
+ #end
610
+ # is sometimes helpful during debug sessions
611
+
612
+ par ? par.lookup_on_error : nil
613
+
614
+ else
615
+
616
+ nil
617
+ end
618
+ end
619
+
620
+ # Looks up parent with on_error attribute and triggers it
621
+ #
622
+ def handle_on_error(msg, error)
623
+
624
+ return false if h.state == 'failing'
625
+
626
+ oe_parent = lookup_on_error
627
+
628
+ return false unless oe_parent
629
+ # no parent with on_error attribute found
630
+
631
+ handler = oe_parent.on_error.to_s
632
+
633
+ return false if handler == ''
634
+ # empty on_error handler nullifies ancestor's on_error
635
+
636
+ workitem = msg['workitem']
637
+
638
+ workitem['fields']['__error__'] = {
639
+ 'fei' => fei,
640
+ 'at' => Ruote.now_to_utc_s,
641
+ 'class' => error.class.to_s,
642
+ 'message' => error.message,
643
+ 'trace' => error.backtrace
644
+ }
645
+
646
+ @context.storage.put_msg(
647
+ 'fail',
648
+ 'fei' => oe_parent.h.fei,
649
+ 'workitem' => workitem)
650
+
651
+ true # yes, error is being handled.
652
+ end
653
+
654
+ #--
655
+ # TREE
656
+ #++
657
+
658
+ # Returns the current version of the tree (returns the updated version
659
+ # if it got updated.
660
+ #
661
+ def tree
662
+ h.updated_tree || h.original_tree
663
+ end
664
+
665
+ # Updates the tree of this expression
666
+ #
667
+ # update_tree(t)
668
+ #
669
+ # will set the updated tree to t
670
+ #
671
+ # update_tree
672
+ #
673
+ # will copy (deep copy) the original tree as the updated_tree.
674
+ #
675
+ # Adding a child to a sequence expression :
676
+ #
677
+ # seq.update_tree
678
+ # seq.updated_tree[2] << [ 'participant', { 'ref' => 'bob' }, [] ]
679
+ # seq.do_persist
680
+ #
681
+ def update_tree(t=nil)
682
+ h.updated_tree = t || Ruote.fulldup(h.original_tree)
683
+ end
684
+
685
+ # Returns the name of this expression, like 'sequence', 'participant',
686
+ # 'cursor', etc...
687
+ #
688
+ def name
689
+
690
+ tree[0]
691
+ end
692
+
693
+ # Returns the attributes of this expression (like { 'ref' => 'toto' } or
694
+ # { 'timeout' => '2d' }.
695
+ #
696
+ def attributes
697
+
698
+ tree[1]
699
+ end
700
+
701
+ # Returns the "AST" view on the children of this expression...
702
+ #
703
+ def tree_children
704
+
705
+ tree[2]
706
+ end
707
+
708
+ protected
709
+
710
+ # Returns a Graphviz dot string representing this expression (and its
711
+ # children).
712
+ #
713
+ def to_dot(opts)
714
+
715
+ i = fei()
716
+
717
+ label = "#{[ i.wfid, i.subid, i.expid].join(' ')} #{tree.first}"
718
+ label += " (#{h.state})" if h.state
719
+
720
+ a = []
721
+ a << "\"#{i.to_storage_id}\" [ label=\"#{label}\" ];"
722
+
723
+ # parent
724
+
725
+ if h.parent_id
726
+ a << "\"#{i.to_storage_id}\" -> \"#{parent_id.to_storage_id}\";"
727
+ end
728
+
729
+ # children
730
+
731
+ h.children.each do |cfei|
732
+ a << "\"#{i.to_storage_id}\" -> \"#{Ruote.to_storage_id(cfei)}\";"
733
+ end
734
+
735
+ a
736
+ end
737
+
738
+ # Used locally but also by ConcurrenceExpression, when preparing children
739
+ # before they get applied.
740
+ #
741
+ def pre_apply_child(child_index, workitem, forget)
742
+
743
+ child_fei = h.fei.merge(
744
+ 'expid' => "#{h.fei['expid']}_#{child_index}",
745
+ 'subid' => Ruote.generate_subid(h.fei.inspect))
746
+
747
+ h.children << child_fei unless forget
748
+
749
+ msg = {
750
+ 'fei' => child_fei,
751
+ 'tree' => tree.last[child_index],
752
+ 'parent_id' => forget ? nil : h.fei,
753
+ 'variables' => forget ? compile_variables : nil,
754
+ 'workitem' => workitem
755
+ }
756
+ msg['forgotten'] = true if forget
757
+
758
+ msg
759
+ end
760
+
761
+ # Used by expressions when, well, applying a child expression of theirs.
762
+ #
763
+ def apply_child(child_index, workitem, forget=false)
764
+
765
+ msg = pre_apply_child(child_index, workitem, forget)
766
+
767
+ persist_or_raise unless forget
768
+ # no need to persist the parent (this) if the child is to be forgotten
769
+
770
+ @context.storage.put_msg('apply', msg)
771
+ end
772
+
773
+ # Some expressions have to keep track of their (instantiated) children,
774
+ # this method does the registration (of the child's fei).
775
+ #
776
+ def register_child(fei)
777
+
778
+ h.children << fei
779
+ persist_or_raise
780
+ end
781
+
782
+ # Called to check if the expression has a :tag attribute. If yes,
783
+ # will register the tag in a variable (and in the workitem).
784
+ #
785
+ def consider_tag
786
+
787
+ if h.tagname = attribute(:tag)
788
+
789
+ set_variable(h.tagname, h.fei)
790
+
791
+ Ruote::Workitem.add_tag(h.applied_workitem, h.tagname)
792
+
793
+ @context.storage.put_msg(
794
+ 'entered_tag',
795
+ 'tag' => h.tagname,
796
+ 'fei' => h.fei,
797
+ 'workitem' => h.applied_workitem)
798
+ end
799
+ end
800
+
801
+ # Called by do_apply. Overriden in ParticipantExpression and RefExpression.
802
+ #
803
+ def consider_timeout
804
+
805
+ do_schedule_timeout(attribute(:timeout))
806
+ end
807
+
808
+ # Called by consider_timeout (FlowExpression) and schedule_timeout
809
+ # (ParticipantExpression).
810
+ #
811
+ def do_schedule_timeout(timeout)
812
+
813
+ timeout = timeout.to_s
814
+
815
+ return if timeout.strip == ''
816
+
817
+ h.timeout_schedule_id = @context.storage.put_schedule(
818
+ 'at',
819
+ h.fei,
820
+ timeout,
821
+ 'action' => 'cancel',
822
+ 'fei' => h.fei,
823
+ 'flavour' => 'timeout')
824
+ end
825
+
826
+ # (Called by trigger_on_cancel & co)
827
+ #
828
+ def supplant_with(tree, opts)
829
+
830
+ # at first, nuke self
831
+
832
+ r = try_unpersist
833
+
834
+ raise(
835
+ "failed to remove exp to supplant "+
836
+ "#{Ruote.to_storage_id(h.fei)} #{tree.first}"
837
+ ) if r.respond_to?(:keys)
838
+
839
+ # then re-apply
840
+
841
+ if t = opts['trigger']
842
+ tree[1]['_triggered'] = t.to_s
843
+ end
844
+
845
+ @context.storage.put_msg(
846
+ 'apply',
847
+ { 'fei' => h.fei,
848
+ 'parent_id' => h.parent_id,
849
+ 'tree' => tree,
850
+ 'workitem' => h.applied_workitem,
851
+ 'variables' => h.variables
852
+ }.merge!(opts))
853
+ end
854
+
855
+ # 'on_{error|timeout|cancel|re_apply}' triggering
856
+ #
857
+ def trigger(on, workitem)
858
+
859
+ hon = h[on]
860
+
861
+ t = hon.is_a?(String) ? [ hon, {}, [] ] : hon
862
+
863
+ if on == 'on_error'
864
+
865
+ if hon == 'redo' or hon == 'retry'
866
+
867
+ t = tree
868
+
869
+ elsif hon == 'undo' or hon == 'pass'
870
+
871
+ h.state = 'failed'
872
+ reply_to_parent(workitem)
873
+
874
+ return
875
+ end
876
+
877
+ elsif on == 'on_timeout'
878
+
879
+ t = tree if hon == 'redo' or hon == 'retry'
880
+ end
881
+
882
+ supplant_with(t, 'trigger' => on)
883
+ end
884
+ end
885
+ end
886
+