ruote 2.2.0 → 2.3.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 (305) hide show
  1. data/CHANGELOG.txt +166 -1
  2. data/CREDITS.txt +36 -17
  3. data/LICENSE.txt +1 -1
  4. data/README.rdoc +1 -7
  5. data/Rakefile +38 -29
  6. data/TODO.txt +93 -52
  7. data/lib/ruote-fs.rb +3 -0
  8. data/lib/ruote.rb +5 -1
  9. data/lib/ruote/context.rb +140 -35
  10. data/lib/ruote/dashboard.rb +1247 -0
  11. data/lib/ruote/{engine → dboard}/process_error.rb +22 -2
  12. data/lib/ruote/dboard/process_status.rb +587 -0
  13. data/lib/ruote/engine.rb +6 -871
  14. data/lib/ruote/exp/command.rb +7 -2
  15. data/lib/ruote/exp/commanded.rb +2 -2
  16. data/lib/ruote/exp/condition.rb +38 -13
  17. data/lib/ruote/exp/fe_add_branches.rb +1 -1
  18. data/lib/ruote/exp/fe_apply.rb +1 -1
  19. data/lib/ruote/exp/fe_await.rb +357 -0
  20. data/lib/ruote/exp/fe_cancel_process.rb +17 -3
  21. data/lib/ruote/exp/fe_command.rb +8 -4
  22. data/lib/ruote/exp/fe_concurrence.rb +218 -18
  23. data/lib/ruote/exp/fe_concurrent_iterator.rb +71 -10
  24. data/lib/ruote/exp/fe_cron.rb +3 -10
  25. data/lib/ruote/exp/fe_cursor.rb +14 -4
  26. data/lib/ruote/exp/fe_define.rb +3 -1
  27. data/lib/ruote/exp/fe_echo.rb +1 -1
  28. data/lib/ruote/exp/fe_equals.rb +1 -1
  29. data/lib/ruote/exp/fe_error.rb +1 -1
  30. data/lib/ruote/exp/fe_filter.rb +163 -4
  31. data/lib/ruote/exp/fe_forget.rb +21 -4
  32. data/lib/ruote/exp/fe_given.rb +1 -1
  33. data/lib/ruote/exp/fe_if.rb +1 -1
  34. data/lib/ruote/exp/fe_inc.rb +102 -35
  35. data/lib/ruote/exp/fe_iterator.rb +47 -12
  36. data/lib/ruote/exp/fe_listen.rb +96 -11
  37. data/lib/ruote/exp/fe_lose.rb +31 -4
  38. data/lib/ruote/exp/fe_noop.rb +1 -1
  39. data/lib/ruote/exp/fe_on_error.rb +109 -0
  40. data/lib/ruote/exp/fe_once.rb +10 -19
  41. data/lib/ruote/exp/fe_participant.rb +90 -28
  42. data/lib/ruote/exp/fe_read.rb +69 -0
  43. data/lib/ruote/exp/fe_redo.rb +3 -2
  44. data/lib/ruote/exp/fe_ref.rb +57 -27
  45. data/lib/ruote/exp/fe_registerp.rb +1 -3
  46. data/lib/ruote/exp/fe_reserve.rb +1 -1
  47. data/lib/ruote/exp/fe_restore.rb +6 -6
  48. data/lib/ruote/exp/fe_save.rb +12 -19
  49. data/lib/ruote/exp/fe_sequence.rb +38 -2
  50. data/lib/ruote/exp/fe_set.rb +143 -40
  51. data/lib/ruote/exp/{fe_let.rb → fe_stall.rb} +7 -38
  52. data/lib/ruote/exp/fe_subprocess.rb +8 -2
  53. data/lib/ruote/exp/fe_that.rb +1 -1
  54. data/lib/ruote/exp/fe_undo.rb +40 -4
  55. data/lib/ruote/exp/fe_unregisterp.rb +1 -3
  56. data/lib/ruote/exp/fe_wait.rb +12 -25
  57. data/lib/ruote/exp/{flowexpression.rb → flow_expression.rb} +375 -229
  58. data/lib/ruote/exp/iterator.rb +2 -2
  59. data/lib/ruote/exp/merge.rb +78 -17
  60. data/lib/ruote/exp/ro_attributes.rb +46 -36
  61. data/lib/ruote/exp/ro_filters.rb +34 -8
  62. data/lib/ruote/exp/ro_on_x.rb +431 -0
  63. data/lib/ruote/exp/ro_persist.rb +19 -7
  64. data/lib/ruote/exp/ro_timers.rb +123 -0
  65. data/lib/ruote/exp/ro_variables.rb +90 -29
  66. data/lib/ruote/fei.rb +57 -3
  67. data/lib/ruote/fs.rb +3 -0
  68. data/lib/ruote/id/mnemo_wfid_generator.rb +30 -7
  69. data/lib/ruote/id/wfid_generator.rb +17 -38
  70. data/lib/ruote/log/default_history.rb +23 -9
  71. data/lib/ruote/log/fancy_printing.rb +265 -0
  72. data/lib/ruote/log/storage_history.rb +23 -13
  73. data/lib/ruote/log/wait_logger.rb +224 -17
  74. data/lib/ruote/observer.rb +82 -0
  75. data/lib/ruote/part/block_participant.rb +65 -28
  76. data/lib/ruote/part/code_participant.rb +81 -0
  77. data/lib/ruote/part/engine_participant.rb +7 -2
  78. data/lib/ruote/part/local_participant.rb +221 -21
  79. data/lib/ruote/part/no_op_participant.rb +1 -1
  80. data/lib/ruote/part/null_participant.rb +1 -1
  81. data/lib/ruote/part/participant.rb +50 -0
  82. data/lib/ruote/part/rev_participant.rb +178 -0
  83. data/lib/ruote/part/smtp_participant.rb +2 -2
  84. data/lib/ruote/part/storage_participant.rb +228 -60
  85. data/lib/ruote/part/template.rb +1 -1
  86. data/lib/ruote/participant.rb +2 -0
  87. data/lib/ruote/reader.rb +205 -68
  88. data/lib/ruote/reader/json.rb +49 -0
  89. data/lib/ruote/reader/radial.rb +303 -0
  90. data/lib/ruote/reader/ruby_dsl.rb +44 -9
  91. data/lib/ruote/reader/xml.rb +11 -8
  92. data/lib/ruote/receiver/base.rb +98 -45
  93. data/lib/ruote/storage/base.rb +104 -35
  94. data/lib/ruote/storage/composite_storage.rb +50 -60
  95. data/lib/ruote/storage/fs_storage.rb +25 -34
  96. data/lib/ruote/storage/hash_storage.rb +38 -36
  97. data/lib/ruote/svc/dispatch_pool.rb +104 -35
  98. data/lib/ruote/svc/dollar_sub.rb +10 -8
  99. data/lib/ruote/svc/error_handler.rb +108 -52
  100. data/lib/ruote/svc/expression_map.rb +3 -3
  101. data/lib/ruote/svc/participant_list.rb +160 -55
  102. data/lib/ruote/svc/tracker.rb +31 -31
  103. data/lib/ruote/svc/treechecker.rb +28 -16
  104. data/lib/ruote/tree_dot.rb +1 -1
  105. data/lib/ruote/util/deep.rb +143 -0
  106. data/lib/ruote/util/filter.rb +125 -18
  107. data/lib/ruote/util/hashdot.rb +15 -13
  108. data/lib/ruote/util/look.rb +1 -1
  109. data/lib/ruote/util/lookup.rb +60 -22
  110. data/lib/ruote/util/misc.rb +63 -18
  111. data/lib/ruote/util/mpatch.rb +53 -0
  112. data/lib/ruote/util/ometa.rb +1 -2
  113. data/lib/ruote/util/process_observer.rb +177 -0
  114. data/lib/ruote/util/subprocess.rb +1 -1
  115. data/lib/ruote/util/time.rb +2 -2
  116. data/lib/ruote/util/tree.rb +64 -2
  117. data/lib/ruote/version.rb +3 -2
  118. data/lib/ruote/worker.rb +421 -92
  119. data/lib/ruote/workitem.rb +157 -22
  120. data/ruote.gemspec +15 -9
  121. data/test/bm/ci.rb +0 -2
  122. data/test/bm/ici.rb +0 -2
  123. data/test/bm/load_26c.rb +0 -3
  124. data/test/bm/mega.rb +0 -2
  125. data/test/functional/base.rb +57 -43
  126. data/test/functional/concurrent_base.rb +16 -13
  127. data/test/functional/ct_0_concurrence.rb +7 -11
  128. data/test/functional/ct_1_iterator.rb +9 -11
  129. data/test/functional/ct_2_cancel.rb +28 -17
  130. data/test/functional/eft_0_flow_expression.rb +35 -0
  131. data/test/functional/eft_10_cancel_process.rb +1 -1
  132. data/test/functional/eft_11_wait.rb +13 -13
  133. data/test/functional/eft_12_listen.rb +199 -66
  134. data/test/functional/eft_13_iterator.rb +95 -29
  135. data/test/functional/eft_14_cursor.rb +74 -24
  136. data/test/functional/eft_15_loop.rb +7 -7
  137. data/test/functional/eft_16_if.rb +1 -1
  138. data/test/functional/eft_17_equals.rb +1 -1
  139. data/test/functional/eft_18_concurrent_iterator.rb +156 -68
  140. data/test/functional/eft_19_reserve.rb +15 -15
  141. data/test/functional/eft_1_echo.rb +1 -1
  142. data/test/functional/eft_20_save.rb +51 -9
  143. data/test/functional/eft_21_restore.rb +1 -1
  144. data/test/functional/eft_22_noop.rb +1 -1
  145. data/test/functional/eft_23_apply.rb +1 -1
  146. data/test/functional/eft_24_add_branches.rb +7 -8
  147. data/test/functional/eft_25_command.rb +1 -1
  148. data/test/functional/eft_26_error.rb +11 -11
  149. data/test/functional/eft_27_inc.rb +111 -67
  150. data/test/functional/eft_28_once.rb +16 -16
  151. data/test/functional/eft_29_cron.rb +9 -9
  152. data/test/functional/eft_2_sequence.rb +23 -4
  153. data/test/functional/eft_30_ref.rb +36 -24
  154. data/test/functional/eft_31_registerp.rb +24 -24
  155. data/test/functional/eft_32_lose.rb +46 -20
  156. data/test/functional/eft_34_given.rb +1 -1
  157. data/test/functional/eft_35_filter.rb +161 -7
  158. data/test/functional/eft_36_read.rb +97 -0
  159. data/test/functional/{eft_0_process_definition.rb → eft_37_process_definition.rb} +4 -4
  160. data/test/functional/eft_38_on_error.rb +195 -0
  161. data/test/functional/eft_39_stall.rb +35 -0
  162. data/test/functional/eft_3_participant.rb +77 -22
  163. data/test/functional/eft_40_await.rb +297 -0
  164. data/test/functional/eft_4_set.rb +110 -11
  165. data/test/functional/eft_5_subprocess.rb +27 -5
  166. data/test/functional/eft_6_concurrence.rb +299 -60
  167. data/test/functional/eft_7_forget.rb +24 -22
  168. data/test/functional/eft_8_undo.rb +52 -15
  169. data/test/functional/eft_9_redo.rb +18 -20
  170. data/test/functional/ft_0_worker.rb +122 -13
  171. data/test/functional/ft_10_dollar.rb +77 -16
  172. data/test/functional/ft_11_recursion.rb +9 -9
  173. data/test/functional/ft_12_launchitem.rb +7 -9
  174. data/test/functional/ft_13_variables.rb +125 -22
  175. data/test/functional/ft_14_re_apply.rb +112 -56
  176. data/test/functional/ft_15_timeout.rb +64 -33
  177. data/test/functional/ft_16_participant_params.rb +59 -6
  178. data/test/functional/ft_17_conditional.rb +68 -2
  179. data/test/functional/ft_18_kill.rb +48 -30
  180. data/test/functional/ft_19_participant_code.rb +67 -0
  181. data/test/functional/ft_1_process_status.rb +222 -150
  182. data/test/functional/ft_20_storage_participant.rb +445 -44
  183. data/test/functional/ft_21_forget.rb +21 -26
  184. data/test/functional/ft_22_process_definitions.rb +8 -6
  185. data/test/functional/ft_23_load_defs.rb +29 -5
  186. data/test/functional/ft_24_block_participant.rb +199 -20
  187. data/test/functional/ft_25_receiver.rb +98 -46
  188. data/test/functional/ft_26_participant_rtimeout.rb +34 -26
  189. data/test/functional/ft_27_var_indirection.rb +40 -5
  190. data/test/functional/ft_28_null_noop_participants.rb +5 -5
  191. data/test/functional/ft_29_part_template.rb +2 -2
  192. data/test/functional/ft_2_errors.rb +106 -74
  193. data/test/functional/ft_30_smtp_participant.rb +7 -7
  194. data/test/functional/ft_31_part_blocking.rb +11 -11
  195. data/test/functional/ft_32_scope.rb +50 -0
  196. data/test/functional/ft_33_participant_subprocess_priority.rb +3 -3
  197. data/test/functional/ft_34_cursor_rewind.rb +14 -14
  198. data/test/functional/ft_35_add_service.rb +67 -9
  199. data/test/functional/ft_36_storage_history.rb +92 -24
  200. data/test/functional/ft_37_default_history.rb +35 -23
  201. data/test/functional/ft_38_participant_more.rb +189 -32
  202. data/test/functional/ft_39_wait_for.rb +25 -25
  203. data/test/functional/ft_3_participant_registration.rb +235 -107
  204. data/test/functional/ft_40_wait_logger.rb +105 -18
  205. data/test/functional/ft_41_participants.rb +13 -12
  206. data/test/functional/ft_42_storage_copy.rb +12 -12
  207. data/test/functional/ft_43_participant_on_reply.rb +85 -11
  208. data/test/functional/ft_44_var_participant.rb +5 -5
  209. data/test/functional/ft_45_participant_accept.rb +3 -3
  210. data/test/functional/ft_46_launch_single.rb +17 -17
  211. data/test/functional/ft_47_wfids.rb +41 -0
  212. data/test/functional/ft_48_lose.rb +19 -25
  213. data/test/functional/ft_49_engine_on_error.rb +54 -70
  214. data/test/functional/ft_4_cancel.rb +84 -26
  215. data/test/functional/ft_50_engine_config.rb +4 -4
  216. data/test/functional/ft_51_misc.rb +12 -12
  217. data/test/functional/ft_52_case.rb +17 -17
  218. data/test/functional/ft_53_engine_on_terminate.rb +18 -21
  219. data/test/functional/ft_54_patterns.rb +18 -16
  220. data/test/functional/ft_55_engine_participant.rb +55 -55
  221. data/test/functional/ft_56_filter_attribute.rb +90 -52
  222. data/test/functional/ft_57_rev_participant.rb +252 -0
  223. data/test/functional/ft_58_workitem.rb +150 -0
  224. data/test/functional/ft_59_pause.rb +329 -0
  225. data/test/functional/ft_5_on_error.rb +430 -77
  226. data/test/functional/ft_60_code_participant.rb +65 -0
  227. data/test/functional/ft_61_trailing_fields.rb +34 -0
  228. data/test/functional/ft_62_exp_name_and_dollar_substitution.rb +35 -0
  229. data/test/functional/ft_63_participants_221.rb +458 -0
  230. data/test/functional/ft_64_stash.rb +41 -0
  231. data/test/functional/ft_65_timers.rb +313 -0
  232. data/test/functional/ft_66_flank.rb +133 -0
  233. data/test/functional/ft_67_radial_misc.rb +34 -0
  234. data/test/functional/ft_68_reput.rb +72 -0
  235. data/test/functional/ft_69_worker_info.rb +56 -0
  236. data/test/functional/ft_6_on_cancel.rb +189 -36
  237. data/test/functional/ft_70_take_and_discard_attributes.rb +94 -0
  238. data/test/functional/ft_71_retries.rb +144 -0
  239. data/test/functional/ft_72_on_terminate.rb +60 -0
  240. data/test/functional/ft_73_raise_msg.rb +107 -0
  241. data/test/functional/ft_74_respark.rb +106 -0
  242. data/test/functional/ft_75_context.rb +66 -0
  243. data/test/functional/ft_76_observer.rb +53 -0
  244. data/test/functional/ft_77_process_observer.rb +157 -0
  245. data/test/functional/ft_78_part_participant.rb +37 -0
  246. data/test/functional/ft_7_tags.rb +238 -50
  247. data/test/functional/ft_8_participant_consumption.rb +27 -21
  248. data/test/functional/ft_9_subprocesses.rb +48 -18
  249. data/test/functional/restart_base.rb +4 -6
  250. data/test/functional/rt_0_wait.rb +10 -10
  251. data/test/functional/rt_1_listen.rb +6 -6
  252. data/test/functional/rt_2_errors.rb +12 -12
  253. data/test/functional/rt_3_once.rb +17 -12
  254. data/test/functional/rt_4_cron.rb +17 -17
  255. data/test/functional/rt_5_timeout.rb +13 -13
  256. data/test/functional/signals.rb +103 -0
  257. data/test/functional/storage.rb +730 -0
  258. data/test/functional/storage_helper.rb +48 -35
  259. data/test/functional/test.rb +6 -2
  260. data/test/misc/idle.rb +21 -0
  261. data/test/misc/light.rb +29 -0
  262. data/test/path_helper.rb +1 -1
  263. data/test/test.rb +2 -5
  264. data/test/test_helper.rb +13 -0
  265. data/test/unit/test.rb +1 -4
  266. data/test/unit/ut_0_ruby_reader.rb +25 -9
  267. data/test/unit/ut_10_participants.rb +47 -0
  268. data/test/unit/ut_11_lookup.rb +59 -2
  269. data/test/unit/ut_12_wait_logger.rb +123 -0
  270. data/test/unit/ut_14_is_uri.rb +1 -1
  271. data/test/unit/ut_15_util.rb +1 -1
  272. data/test/unit/ut_16_reader.rb +136 -14
  273. data/test/unit/ut_17_merge.rb +155 -0
  274. data/test/unit/ut_19_part_template.rb +1 -1
  275. data/test/unit/ut_1_fei.rb +11 -2
  276. data/test/unit/ut_20_composite_storage.rb +27 -1
  277. data/test/unit/{ut_21_participant_list.rb → ut_21_svc_participant_list.rb} +2 -3
  278. data/test/unit/ut_22_filter.rb +231 -10
  279. data/test/unit/ut_23_svc_tracker.rb +48 -0
  280. data/test/unit/ut_24_radial_reader.rb +458 -0
  281. data/test/unit/ut_25_process_status.rb +143 -0
  282. data/test/unit/ut_26_deep.rb +131 -0
  283. data/test/unit/ut_2_dashboard.rb +114 -0
  284. data/test/unit/ut_3_worker.rb +54 -0
  285. data/test/unit/ut_4_expmap.rb +1 -1
  286. data/test/unit/ut_5_tree.rb +23 -23
  287. data/test/unit/ut_6_condition.rb +71 -29
  288. data/test/unit/ut_7_workitem.rb +18 -4
  289. data/test/unit/ut_8_tree_to_dot.rb +1 -1
  290. data/test/unit/ut_9_xml_reader.rb +1 -1
  291. metadata +142 -63
  292. data/jruby_issue.txt +0 -32
  293. data/lib/ruote/engine/process_status.rb +0 -403
  294. data/lib/ruote/log/pretty.rb +0 -165
  295. data/lib/ruote/log/test_logger.rb +0 -204
  296. data/lib/ruote/util/serializer.rb +0 -103
  297. data/phil.txt +0 -14
  298. data/test/functional/eft_33_let.rb +0 -31
  299. data/test/functional/ft_19_alias.rb +0 -33
  300. data/test/functional/ft_47_wfid_generator.rb +0 -54
  301. data/test/unit/storage.rb +0 -403
  302. data/test/unit/storages.rb +0 -37
  303. data/test/unit/ut_13_serializer.rb +0 -65
  304. data/test/unit/ut_18_engine.rb +0 -47
  305. data/test/unit/ut_3_wait_logger.rb +0 -39
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2005-2011, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2005-2012, 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
@@ -30,6 +30,12 @@ module Ruote
30
30
  #
31
31
  class ProcessError
32
32
 
33
+ # When this instance was returned by Ruote::Dashboard#ps or
34
+ # Ruote::Dashboard#process, this attribute will point to the flow
35
+ # expression where the error occurred.
36
+ #
37
+ attr_accessor :flow_expression
38
+
33
39
  def initialize(h)
34
40
  @h = h
35
41
  end
@@ -46,6 +52,10 @@ module Ruote
46
52
  @h['msg']
47
53
  end
48
54
 
55
+ def details
56
+ @h['details']
57
+ end
58
+
49
59
  def fei
50
60
  Ruote::FlowExpressionId.new(msg['fei'])
51
61
  end
@@ -73,6 +83,8 @@ module Ruote
73
83
  @h
74
84
  end
75
85
 
86
+ alias h to_h
87
+
76
88
  # 'apply', 'reply', 'receive', ... Indicates in which "direction" the
77
89
  # error occured.
78
90
  #
@@ -86,12 +98,20 @@ module Ruote
86
98
  @h['msg']['workitem'] && @h['msg']['workitem']['fields']
87
99
  end
88
100
 
89
- # Returns an instance of Ruote::Workitem
101
+ # Returns an instance of Ruote::Workitem (the workitem as it was
102
+ # at the error point)
90
103
  #
91
104
  def workitem
92
105
  Ruote::Workitem.new(msg['workitem'])
93
106
  end
94
107
 
108
+ # Returns an array of deviations (see the 'filter' expression) if the
109
+ # error is a Ruote::ValidationError.
110
+ #
111
+ def deviations
112
+ @h['deviations']
113
+ end
114
+
95
115
  protected
96
116
 
97
117
  def to_dot(opts)
@@ -0,0 +1,587 @@
1
+ #--
2
+ # Copyright (c) 2005-2012, 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/tree'
26
+ require 'ruote/dboard/process_error'
27
+
28
+
29
+ module Ruote
30
+
31
+ #
32
+ # A 'view' on the status of a process instance.
33
+ #
34
+ # Returned by the #process and the #processes methods of Ruote::Dashboard.
35
+ #
36
+ class ProcessStatus
37
+
38
+ # The expressions that compose the process instance.
39
+ #
40
+ attr_reader :expressions
41
+
42
+ # Returns the expression at the root of the process instance.
43
+ #
44
+ attr_reader :root_expression
45
+
46
+ # An array of the workitems currently in the storage participant for this
47
+ # process instance.
48
+ #
49
+ # Do not confuse with #workitems
50
+ #
51
+ attr_reader :stored_workitems
52
+
53
+ # An array of errors currently plaguing the process instance. Hopefully,
54
+ # this array is empty.
55
+ #
56
+ attr_reader :errors
57
+
58
+ # An array of schedules (open structs yielding information about the
59
+ # schedules of this process)
60
+ #
61
+ attr_reader :schedules
62
+
63
+ # TODO
64
+ #
65
+ attr_reader :trackers
66
+
67
+ # Called by Ruote::Dashboard#processes or Ruote::Dashboard#process.
68
+ #
69
+ def initialize(context, expressions, sworkitems, errors, schedules, trackers)
70
+
71
+ #
72
+ # preparing data
73
+
74
+ @expressions = expressions.collect { |e|
75
+ Ruote::Exp::FlowExpression.from_h(context, e)
76
+ }.sort_by { |e|
77
+ e.fei.expid
78
+ }
79
+
80
+ @stored_workitems = sworkitems.map { |h| Ruote::Workitem.new(h) }
81
+
82
+ @errors = errors.sort! { |a, b| a.fei.expid <=> b.fei.expid }
83
+ @schedules = schedules.sort! { |a, b| a['owner'].sid <=> b['owner'].sid }
84
+
85
+ @root_expression = root_expressions.first
86
+
87
+ #
88
+ # linking errors and expressions for easy navigation
89
+
90
+ @errors.each do |err|
91
+ err.flow_expression = @expressions.find { |fexp| fexp.fei == err.fei }
92
+ err.flow_expression.error = err if err.flow_expression
93
+ end
94
+
95
+ @trackers = trackers
96
+ end
97
+
98
+ # Returns a list of all the expressions that have no parent expression.
99
+ # The list is sorted with the deeper (closer to the original root) first.
100
+ #
101
+ def root_expressions
102
+
103
+ roots = @expressions.select { |e| e.h.parent_id == nil }
104
+
105
+ roots = roots.each_with_object({}) { |e, h|
106
+ h["#{e.h.fei['expid']}__#{e.h.fei['subid']}"] = e
107
+ }
108
+
109
+ roots.keys.sort.collect { |k| roots[k] }
110
+ end
111
+
112
+ # Given an expression id, returns the root (top ancestor) for its
113
+ # expression.
114
+ #
115
+ def root_expression_for(fei)
116
+
117
+ sfei = Ruote.sid(fei)
118
+
119
+ exp = @expressions.find { |fe| sfei == Ruote.sid(fe.fei) }
120
+
121
+ return nil unless exp
122
+ return exp if exp.parent_id.nil?
123
+
124
+ root_expression_for(exp.parent_id)
125
+ end
126
+
127
+ # Returns the process variables set for this process instance.
128
+ #
129
+ # Returns nil if there is no defined root expression.
130
+ #
131
+ def variables
132
+
133
+ @root_expression && @root_expression.variables
134
+ end
135
+
136
+ # Returns a hash fei => variable_hash containing all the variable bindings
137
+ # (expression by expression) of the process instance.
138
+ #
139
+ def all_variables
140
+
141
+ return nil if @expressions.empty?
142
+
143
+ @expressions.each_with_object({}) do |exp, h|
144
+ h[exp.fei] = exp.variables if exp.variables
145
+ end
146
+ end
147
+
148
+ # Returns a hash tagname => fei of tags set at the root of the process
149
+ # instance.
150
+ #
151
+ # Returns nil if there is no defined root expression.
152
+ #
153
+ def tags
154
+
155
+ if variables
156
+ Hash[variables.select { |k, v| FlowExpressionId.is_a_fei?(v) }]
157
+ else
158
+ nil
159
+ end
160
+ end
161
+
162
+ # Returns a hash tagname => array of feis of all the tags set in the process
163
+ # instance.
164
+ #
165
+ def all_tags
166
+
167
+ all_variables.remap do |(fei, vars), h|
168
+ vars.each { |k, v| (h[k] ||= []) << v if FlowExpressionId.is_a_fei?(v) }
169
+ end
170
+ end
171
+
172
+ # Returns the list of "past tags", tags that have been entered and left.
173
+ #
174
+ # The list elements look like:
175
+ #
176
+ # [ full_tagname, fei_as_string, nil_or_left_status, variables ]
177
+ #
178
+ # For example:
179
+ #
180
+ # [ 'a', '0_1_0!8f233fb935c!20120106-jagitepi', nil, {} ]
181
+ #
182
+ # or
183
+ #
184
+ # [ 'stage0/stage1', '0_1_0!8fb935c666d!20120106-jagitepi', 'cancelling', nil ]
185
+ #
186
+ # The second to last entry is nil when the tag (its expression) replied
187
+ # normally, if it was cancelled or something else, the entry contains
188
+ # a string describing the reason ('cancelling' here).
189
+ # The last entry is the variables as they were at the tag point when
190
+ # the execution left the tag.
191
+ #
192
+ def past_tags
193
+
194
+ (@root_expression ?
195
+ @root_expression.variables['__past_tags__'] : nil
196
+ ) || []
197
+ end
198
+
199
+ # Returns the unique identifier for this process instance.
200
+ #
201
+ def wfid
202
+
203
+ l = [ @expressions, @errors, @stored_workitems ].find { |l| l.any? }
204
+
205
+ l ? l.first.fei.wfid : nil
206
+ end
207
+
208
+ # For a process
209
+ #
210
+ # Ruote.process_definition :name => 'review', :revision => '0.1' do
211
+ # author
212
+ # reviewer
213
+ # end
214
+ #
215
+ # will yield 'review'.
216
+ #
217
+ def definition_name
218
+
219
+ @root_expression && (
220
+ @root_expression.attribute('name') ||
221
+ @root_expression.attribute_text)
222
+ end
223
+
224
+ # For a process
225
+ #
226
+ # Ruote.process_definition :name => 'review', :revision => '0.1' do
227
+ # author
228
+ # reviewer
229
+ # end
230
+ #
231
+ # will yield '0.1'.
232
+ #
233
+ def definition_revision
234
+
235
+ @root_expression && (
236
+ @root_expression.attribute('revision') ||
237
+ @root_expression.attribute('rev'))
238
+ end
239
+
240
+ # Returns the 'position' of the process.
241
+ #
242
+ # pdef = Ruote.process_definition do
243
+ # alpha :task => 'clean car'
244
+ # end
245
+ # wfid = engine.launch(pdef)
246
+ #
247
+ # sleep 0.500
248
+ #
249
+ # engine.process(wfid) # => [["0_0", "alpha", {"task"=>"clean car"}]]
250
+ #
251
+ # A process with concurrent branches will yield multiple 'positions'.
252
+ #
253
+ # It uses #workitems underneath.
254
+ #
255
+ # If you want to list all the expressions where the "flow currently is"
256
+ # regardless they are participant expressions or errors, look at the
257
+ # #leaves method.
258
+ #
259
+ def position
260
+
261
+ workitems.collect { |wi|
262
+
263
+ r = [ wi.fei.sid, wi.participant_name ]
264
+
265
+ params = (wi.fields['params'] || {}).dup
266
+ params.delete('ref')
267
+
268
+ if err = errors.find { |e| e.fei == wi.fei }
269
+ params['error'] = err.message
270
+ end
271
+
272
+ r << params
273
+ r
274
+ }
275
+ end
276
+
277
+ # Returns the expressions where the flow is currently, ak the leaves
278
+ # of the execution tree.
279
+ #
280
+ # Whereas #position only looks at participant expressions (and errors),
281
+ # #leaves looks at any expressions that is a leave (which has no
282
+ # child at this point).
283
+ #
284
+ # Returns an array of FlowExpression instances. (Note that they may
285
+ # have their attribute #error set).
286
+ #
287
+ def leaves
288
+
289
+ expressions.inject([]) { |a, exp|
290
+ a.select { |e| ! exp.ancestor?(e.fei) } + [ exp ]
291
+ }
292
+ end
293
+
294
+ # Returns the workitem as was applied at the root expression.
295
+ #
296
+ # Returns nil if no root expression could be found.
297
+ #
298
+ def root_workitem
299
+
300
+ return nil unless root_expression
301
+
302
+ Ruote::Workitem.new(root_expression.h.applied_workitem)
303
+ end
304
+
305
+ # Returns a list of the workitems currently 'out' to participants
306
+ #
307
+ # For example, with an instance of
308
+ #
309
+ # Ruote.process_definition do
310
+ # concurrence do
311
+ # alpha :task => 'clean car'
312
+ # bravo :task => 'sell car'
313
+ # end
314
+ # end
315
+ #
316
+ # calling engine.process(wfid).workitems will yield two workitems
317
+ # (alpha and bravo).
318
+ #
319
+ # Warning : do not confuse the workitems here with the workitems held
320
+ # in a storage participant or equivalent.
321
+ #
322
+ def workitems
323
+
324
+ @expressions.select { |fexp|
325
+ #fexp.is_a?(Ruote::Exp::ParticipantExpression)
326
+ fexp.h.name == 'participant'
327
+ }.collect { |fexp|
328
+ Ruote::Workitem.new(fexp.h.applied_workitem)
329
+ }
330
+ end
331
+
332
+ # Returns a parseable UTC datetime string which indicates when the process
333
+ # was last active.
334
+ #
335
+ def last_active
336
+
337
+ @expressions.collect { |fexp| fexp.h.put_at }.max
338
+ end
339
+
340
+ # Returns the process definition tree as it was when this process instance
341
+ # was launched.
342
+ #
343
+ def original_tree
344
+
345
+ @root_expression && @root_expression.original_tree
346
+ end
347
+
348
+ # Returns a Time instance indicating when the process instance was launched.
349
+ #
350
+ def launched_time
351
+
352
+ @root_expression && @root_expression.created_time
353
+ end
354
+
355
+ def to_s
356
+
357
+ '(' + [
358
+ "process_status wfid '#{wfid}'",
359
+ "expressions #{@expressions.size}",
360
+ "stored_workitems #{@stored_workitems.size}",
361
+ "errors #{@errors.size}",
362
+ "schedules #{@schedules.size}",
363
+ "trackers #{@trackers.size}"
364
+ ].join(', ') + ')'
365
+ end
366
+
367
+ def hinspect(indent, h)
368
+
369
+ if h
370
+ h.collect { |k, v|
371
+ s << "#{' ' * indent}#{k.inspect}: #{v.inspect}"
372
+ }.join("\n")
373
+ else
374
+ "#{' ' * indent}(nil)"
375
+ end
376
+ end
377
+
378
+ def inspect
379
+
380
+ vars = variables rescue nil
381
+ avars = (all_variables || {}).remap { |(k, v), h| h[Ruote.sid(k)] = v }
382
+
383
+ s = [ "== #{self.class} ==" ]
384
+ s << ''
385
+ s << " wfid: #{wfid}"
386
+ s << " name: #{definition_name}"
387
+ s << " revision: #{definition_revision}"
388
+ s << " last_active: #{last_active}"
389
+ s << " launched_time: #{launched_time}"
390
+ s << ''
391
+ s << " expressions: #{@expressions.size}"
392
+ s << ''
393
+ @expressions.each do |e|
394
+ s << " #{e.fei.to_storage_id}"
395
+ s << " | #{e.name}"
396
+ s << " | _rev: #{e.h._rev.inspect}"
397
+ s << " | * #{e.state} *" if e.state
398
+ s << " | #{e.attributes.inspect}"
399
+ e.children.each do |ce|
400
+ s << " | . child-> #{Ruote.sid(ce)}"
401
+ end if e.children.any?
402
+ s << " | timers: #{e.h.timers.collect { |t| t[1] }}" if e.h.timers
403
+ s << " | (flanking)" if e.h.flanking
404
+ s << " `-parent--> #{e.h.parent_id ? e.parent_id.to_storage_id : 'nil'}"
405
+ end
406
+ s << ''
407
+ s << " schedules: #{@schedules.size}"
408
+ if @schedules.size > 0
409
+ @schedules.each do |sched|
410
+ s << " * #{sched['original']}"
411
+ s << " #{sched['flavour']} #{sched['at']}"
412
+ s << " #{sched['action']}"
413
+ s << " #{Ruote.sid(sched['target']) rescue '** no target **'}"
414
+ end
415
+ s << ''
416
+ end
417
+ s << " stored workitems: #{@stored_workitems.size}"
418
+
419
+ s << ''
420
+ s << " initial workitem fields:"
421
+ if @root_expression
422
+ s << hinspect(4, @root_expression.h.applied_workitem['fields'])
423
+ else
424
+ s << " (no root expression identified)"
425
+ end
426
+
427
+ s << ''
428
+ s << " variables:"; s << hinspect(4, vars)
429
+ s << ''
430
+ s << " all_variables:"; s << hinspect(4, avars)
431
+ s << ''
432
+ s << " errors: #{@errors.size}"
433
+ @errors.each do |e|
434
+ s << " ***"
435
+ s << " #{e.fei.to_storage_id} :" if e.fei
436
+ s << " action: #{e.action}"
437
+ s << " message: #{e.message}"
438
+ s << " trace:"
439
+ e.trace.split("\n").each do |line|
440
+ s << " #{line}"
441
+ end
442
+ s << " details:"
443
+ (e.details || '').split("\n").each do |line|
444
+ s << " #{line}"
445
+ end
446
+ if e.respond_to?(:deviations)
447
+ s << " deviations:"
448
+ (e.deviations || []).each do |line|
449
+ s << " #{line.inspect}"
450
+ end
451
+ end
452
+ s << " fields:"; s << hinspect(6, e.fields)
453
+ end
454
+
455
+ # TODO: add trackers
456
+
457
+ s.join("\n") + "\n"
458
+ end
459
+
460
+ # Returns a 'dot' representation of the process. A graph describing
461
+ # the tree of flow expressions that compose the process.
462
+ #
463
+ def to_dot(opts={})
464
+
465
+ s = [ "digraph \"process wfid #{wfid}\" {" ]
466
+ @expressions.each { |e| s.push(*e.send(:to_dot, opts)) }
467
+ @errors.each { |e| s.push(*e.send(:to_dot, opts)) }
468
+ s << '}'
469
+
470
+ s.join("\n")
471
+ end
472
+
473
+ # Outputs the process status as a hash (easily JSONifiable).
474
+ #
475
+ def to_h
476
+
477
+ %w[
478
+ expressions errors stored_workitems schedules trackers
479
+ ].each_with_object({}) do |a, h|
480
+
481
+ k = a == 'stored_workitems' ? 'workitems' : a
482
+
483
+ v = self.send(a)
484
+ v = v.collect { |e| e.respond_to?(:h) ? e.h : e }
485
+
486
+ h[k] = v
487
+ end
488
+ end
489
+
490
+ # Returns the current version of the process definition tree. If no
491
+ # manipulation (gardening) was performed on the tree, this method yields
492
+ # the same result as the #original_tree method.
493
+ #
494
+ # Returns nil if there are no expressions (happens in the case of an
495
+ # orphan workitem)
496
+ #
497
+ def current_tree
498
+
499
+ return nil if @expressions.empty?
500
+
501
+ h = Ruote.decompose_tree(original_tree)
502
+
503
+ @expressions.sort { |e0, e1|
504
+
505
+ e0.fei.expid <=> e1.fei.expid
506
+
507
+ }.each { |e|
508
+
509
+ trigger = e.tree[1]['_triggered']
510
+
511
+ tree = if trigger && trigger != 'on_re_apply'
512
+ t = original_tree_from_parent(e).dup
513
+ t[1]['_triggered'] = trigger
514
+ t
515
+ else
516
+ e.tree
517
+ end
518
+
519
+ h.merge!(Ruote.decompose_tree(tree, e.fei.expid))
520
+ }
521
+
522
+ Ruote.recompose_tree(h)
523
+ end
524
+
525
+ # Used by Ruote::Dashboard#process and #processes
526
+ #
527
+ def self.fetch(context, wfids, opts)
528
+
529
+ swfids = wfids.collect { |wfid| /!#{wfid}-\d+$/ }
530
+
531
+ batch = { 'id' => "#{Thread.current.object_id}-#{Time.now.to_f}" }
532
+ #
533
+ # some storages may optimize when they can distinguish
534
+ # which get_many fit in the same batch...
535
+
536
+ exps = context.storage.get_many(
537
+ 'expressions', wfids, :batch => batch).compact
538
+ swis = context.storage.get_many(
539
+ 'workitems', wfids, :batch => batch).compact
540
+ errs = context.storage.get_many(
541
+ 'errors', wfids, :batch => batch).compact
542
+ schs = context.storage.get_many(
543
+ 'schedules', swfids, :batch => batch).compact
544
+ #
545
+ # some slow storages need the compaction... couch...
546
+
547
+ errs = errs.collect { |err| ProcessError.new(err) }
548
+ schs = schs.collect { |sch| Ruote.schedule_to_h(sch) }
549
+
550
+ by_wfid = {}
551
+ as = lambda { [ [], [], [], [], [] ] }
552
+
553
+ exps.each { |exp| (by_wfid[exp['fei']['wfid']] ||= as.call)[0] << exp }
554
+ swis.each { |swi| (by_wfid[swi['fei']['wfid']] ||= as.call)[1] << swi }
555
+ errs.each { |err| (by_wfid[err.wfid] ||= as.call)[2] << err }
556
+ schs.each { |sch| (by_wfid[sch['wfid']] ||= as.call)[3] << sch }
557
+ # TODO: trackers
558
+
559
+ wfids = by_wfid.keys.sort
560
+ wfids = wfids.reverse if opts[:descending]
561
+ # re-adjust list of wfids, only take what was found
562
+
563
+ wfids.collect { |wfid|
564
+ info = by_wfid[wfid]
565
+ info ? self.new(context, *info) : nil
566
+ }.compact
567
+ end
568
+
569
+ #--
570
+ #def self.from_h(h)
571
+ # self.new(
572
+ # nil,
573
+ # *%w[ expressions workitems errors schedules trackers ].map { |k| h[k] })
574
+ #end
575
+ #++
576
+
577
+ protected
578
+
579
+ def original_tree_from_parent(e)
580
+
581
+ parent = @expressions.find { |exp| exp.fei == e.parent_id }
582
+
583
+ parent ? parent.tree[2][e.fei.child_id] : e.tree
584
+ end
585
+ end
586
+ end
587
+