ruote 2.2.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -22,883 +22,18 @@
22
22
  # Made in Japan.
23
23
  #++
24
24
 
25
- require 'ruote/context'
26
- require 'ruote/engine/process_status'
27
- require 'ruote/receiver/base'
25
+ require 'ruote/dashboard'
28
26
 
29
27
 
30
28
  module Ruote
31
29
 
32
30
  #
33
- # This class holds the 'engine' name, perhaps 'dashboard' would have been
34
- # a better name. Anyway, the methods here allow to launch processes
35
- # and to query about their status. There are also methods for fixing
36
- # issues with stalled processes or processes stuck in errors.
31
+ # This class has been replaced by Ruote::Dashboard.
37
32
  #
38
- # NOTE : the methods #launch and #reply are implemented in
39
- # Ruote::ReceiverMixin (this Engine class has all the methods of a Receiver).
33
+ # It will be slowly phased out as documentation and tutorials move
34
+ # from Ruote::Engine to Ruote::Dashboard.
40
35
  #
41
- class Engine
42
-
43
- include ReceiverMixin
44
-
45
- attr_reader :context
46
- attr_reader :variables
47
-
48
- # Creates an engine using either worker or storage.
49
- #
50
- # If a storage instance is given as the first argument, the engine will be
51
- # able to manage processes (for example, launch and cancel workflows) but
52
- # will not actually run any workflows.
53
- #
54
- # If a worker instance is given as the first argument and the second
55
- # argument is true, engine will start the worker and will be able to both
56
- # manage and run workflows.
57
- #
58
- # If the second options is set to { :join => true }, the worker wil
59
- # be started and run in the current thread.
60
- #
61
- def initialize(worker_or_storage, opts=true)
62
-
63
- @context = worker_or_storage.context
64
- @context.engine = self
65
-
66
- @variables = EngineVariables.new(@context.storage)
67
-
68
- if @context.worker
69
- if opts == true
70
- @context.worker.run_in_thread
71
- # runs worker in its own thread
72
- elsif opts == { :join => true }
73
- @context.worker.run
74
- # runs worker in current thread (and doesn't return)
75
- #else
76
- # worker is not run
77
- end
78
- #else
79
- # no worker
80
- end
81
- end
82
-
83
- # Returns the storage this engine works with passed at engine
84
- # initialization.
85
- #
86
- def storage
87
-
88
- @context.storage
89
- end
90
-
91
- # Returns the worker nested inside this engine (passed at initialization).
92
- # Returns nil if this engine is only linked to a storage (and the worker
93
- # is running somewhere else (hopefully)).
94
- #
95
- def worker
96
-
97
- @context.worker
98
- end
99
-
100
- # A shortcut for engine.context.history
101
- #
102
- def history
103
-
104
- @context.history
105
- end
106
-
107
- # Quick note : the implementation of launch is found in the module
108
- # Ruote::ReceiverMixin that the engine includes.
109
- #
110
- # Some processes have to have one and only one instance of themselves
111
- # running, these are called 'singles' ('singleton' is too object-oriented).
112
- #
113
- # When called, this method will check if an instance of the pdef is
114
- # already running (it uses the process definition name attribute), if
115
- # yes, it will return without having launched anything. If there is no
116
- # such process running, it will launch it (and register it).
117
- #
118
- # Returns the wfid (workflow instance id) of the running single.
119
- #
120
- def launch_single(process_definition, fields={}, variables={})
121
-
122
- tree = @context.reader.read(process_definition)
123
- name = tree[1]['name'] || (tree[1].find { |k, v| v.nil? } || []).first
124
-
125
- raise ArgumentError.new(
126
- 'process definition is missing a name, cannot launch as single'
127
- ) unless name
128
-
129
- singles = @context.storage.get('variables', 'singles') || {
130
- '_id' => 'singles', 'type' => 'variables', 'h' => {}
131
- }
132
- wfid, timestamp = singles['h'][name]
133
-
134
- return wfid if wfid && (ps(wfid) || Time.now.to_f - timestamp < 1.0)
135
- # return wfid if 'singleton' process is already running
136
-
137
- wfid = @context.wfidgen.generate
138
-
139
- singles['h'][name] = [ wfid, Time.now.to_f ]
140
-
141
- r = @context.storage.put(singles)
142
-
143
- return launch_single(tree, fields, variables) unless r.nil?
144
- #
145
- # the put failed, back to the start...
146
- #
147
- # all this to prevent races between multiple engines,
148
- # multiple launch_single calls (from different Ruby runtimes)
149
-
150
- # ... green for launch
151
-
152
- @context.storage.put_msg(
153
- 'launch',
154
- 'wfid' => wfid,
155
- 'tree' => tree,
156
- 'workitem' => { 'fields' => fields },
157
- 'variables' => variables)
158
-
159
- wfid
160
- end
161
-
162
- # Given a workitem or a fei, will do a cancel_expression,
163
- # else it's a wfid and it does a cancel_process.
164
- #
165
- def cancel(wi_or_fei_or_wfid)
166
-
167
- target = Ruote.extract_id(wi_or_fei_or_wfid)
168
-
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
174
- end
175
-
176
- alias cancel_process cancel
177
- alias cancel_expression cancel
178
-
179
- # Given a workitem or a fei, will do a kill_expression,
180
- # else it's a wfid and it does a kill_process.
181
- #
182
- def kill(wi_or_fei_or_wfid)
183
-
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
191
- end
192
-
193
- alias kill_process kill
194
- alias kill_expression kill
195
-
196
- # Replays at a given error (hopefully you fixed the cause of the error
197
- # before replaying...)
198
- #
199
- def replay_at_error(err)
200
-
201
- msg = err.msg.dup
202
- action = msg.delete('action')
203
-
204
- msg['replay_at_error'] = true
205
- # just an indication
206
-
207
- if msg['tree'] && fei = msg['fei']
208
- #
209
- # nukes the expression in case of [re]apply
210
- #
211
- exp = Ruote::Exp::FlowExpression.fetch(@context, fei)
212
- exp.unpersist_or_raise if exp
213
- end
214
-
215
- @context.storage.delete(err.to_h) # remove error
216
-
217
- @context.storage.put_msg(action, msg) # trigger replay
218
- end
219
-
220
- # Re-applies an expression (given via its FlowExpressionId).
221
- #
222
- # That will cancel the expression and, once the cancel operation is over
223
- # (all the children have been cancelled), the expression will get
224
- # re-applied.
225
- #
226
- # == options
227
- #
228
- # :tree is used to completely change the tree of the expression at re_apply
229
- #
230
- # engine.re_apply(fei, :tree => [ 'participant', { 'ref' => 'bob' }, [] ])
231
- #
232
- # :fields is used to replace the fields of the workitem at re_apply
233
- #
234
- # engine.re_apply(fei, :fields => { 'customer' => 'bob' })
235
- #
236
- # :merge_in_fields is used to add / override fields
237
- #
238
- # engine.re_apply(fei, :merge_in_fields => { 'customer' => 'bob' })
239
- #
240
- def re_apply(fei, opts={})
241
-
242
- @context.storage.put_msg('cancel', 'fei' => fei.to_h, 're_apply' => opts)
243
- end
244
-
245
- # Returns a ProcessStatus instance describing the current status of
246
- # a process instance.
247
- #
248
- def process(wfid)
249
-
250
- statuses([ wfid ], {}).first
251
- end
252
-
253
- # Returns an array of ProcessStatus instances.
254
- #
255
- # WARNING : this is an expensive operation, but it understands :skip
256
- # and :limit, so pagination is our friend.
257
- #
258
- # Please note, if you're interested only in processes that have errors,
259
- # Engine#errors is a more efficient means.
260
- #
261
- # To simply list the wfids of the currently running, Engine#process_wfids
262
- # is way cheaper to call.
263
- #
264
- def processes(opts={})
265
-
266
- wfids = @context.storage.expression_wfids(opts)
267
-
268
- opts[:count] ? wfids.size : statuses(wfids, opts)
269
- end
270
-
271
- # Returns a list of processes or the process status of a given process
272
- # instance.
273
- #
274
- def ps(wfid=nil)
275
-
276
- wfid == nil ? processes : process(wfid)
277
- end
278
-
279
- # Returns an array of current errors (hashes)
280
- #
281
- # Can be called in two ways :
282
- #
283
- # engine.errors(wfid)
284
- #
285
- # and
286
- #
287
- # engine.errors(:skip => 100, :limit => 100)
288
- #
289
- def errors(wfid=nil)
290
-
291
- wfid, options = wfid.is_a?(Hash) ? [ nil, wfid ] : [ wfid, {} ]
292
-
293
- errs = wfid.nil? ?
294
- @context.storage.get_many('errors', nil, options) :
295
- @context.storage.get_many('errors', wfid)
296
-
297
- return errs if options[:count]
298
-
299
- errs.collect { |err| ProcessError.new(err) }
300
- end
301
-
302
- # Returns an array of schedules. Those schedules are open structs
303
- # with various properties, like target, owner, at, put_at, ...
304
- #
305
- # Introduced mostly for ruote-kit.
306
- #
307
- # Can be called in two ways :
308
- #
309
- # engine.schedules(wfid)
310
- #
311
- # and
312
- #
313
- # engine.schedules(:skip => 100, :limit => 100)
314
- #
315
- def schedules(wfid=nil)
316
-
317
- wfid, options = wfid.is_a?(Hash) ? [ nil, wfid ] : [ wfid, {} ]
318
-
319
- scheds = wfid.nil? ?
320
- @context.storage.get_many('schedules', nil, options) :
321
- @context.storage.get_many('schedules', /!#{wfid}-\d+$/)
322
-
323
- return scheds if options[:count]
324
-
325
- scheds.collect { |s| Ruote.schedule_to_h(s) }.sort_by { |s| s['wfid'] }
326
- end
327
-
328
- # Returns a [sorted] list of wfids of the process instances currently
329
- # running in the engine.
330
- #
331
- # This operation is substantially less costly than Engine#processes (though
332
- # the 'how substantially' depends on the storage chosen).
333
- #
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...
363
-
364
- (wis + ers + scs).reject { |doc| wfids.include?(doc['fei']['wfid']) }
365
- end
366
-
367
- # Shuts down the engine, mostly passes the shutdown message to the other
368
- # services and hope they'll shut down properly.
369
- #
370
- def shutdown
371
-
372
- @context.shutdown
373
- end
374
-
375
- # This method expects there to be a logger with a wait_for method in the
376
- # context, else it will raise an exception.
377
- #
378
- # *WARNING* : wait_for() is meant for environments where there is a unique
379
- # worker and that worker is nested in this engine. In a multiple worker
380
- # environment wait_for doesn't see events handled by 'other' workers.
381
- #
382
- # This method is only useful for test/quickstart/examples environments.
383
- #
384
- # engine.wait_for(:alpha)
385
- # # will make the current thread block until a workitem is delivered
386
- # # to the participant named 'alpha'
387
- #
388
- # engine.wait_for('123432123-9043')
389
- # # will make the current thread block until the processed whose
390
- # # wfid is given (String) terminates or produces an error.
391
- #
392
- # engine.wait_for(5)
393
- # # will make the current thread block until 5 messages have been
394
- # # processed on the workqueue...
395
- #
396
- # engine.wait_for(:empty)
397
- # # will return as soon as the engine/storage is empty, ie as soon
398
- # # as there are no more processes running in the engine (no more
399
- # # expressions placed in the storage)
400
- #
401
- # It's OK to wait for multiple wfids :
402
- #
403
- # engine.wait_for('20100612-bezerijozo', '20100612-yakisoba')
404
- #
405
- def wait_for(*items)
406
-
407
- logger = @context['s_logger']
408
-
409
- raise(
410
- "can't wait_for, there is no logger that responds to that call"
411
- ) unless logger.respond_to?(:wait_for)
412
-
413
- logger.wait_for(items)
414
- end
415
-
416
- # Joins the worker thread. If this engine has no nested worker, calling
417
- # this method will simply return immediately.
418
- #
419
- def join
420
-
421
- worker.join if worker
422
- end
423
-
424
- # Loads (and turns into a tree) the process definition at the given path.
425
- #
426
- def load_definition(path)
427
-
428
- @context.reader.read(path)
429
- end
430
-
431
- # Registers a participant in the engine.
432
- #
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.
444
- #
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
461
- #
462
- # engine.register_participant 'compute_sum' do |wi|
463
- # wi.fields['sum'] = wi.fields['articles'].inject(0) do |s, (c, v)|
464
- # s + c * v # sum + count * value
465
- # end
466
- # # a block participant implicitely replies to the engine immediately
467
- # end
468
- #
469
- # class MyParticipant
470
- # def initialize(opts)
471
- # @name = opts['name']
472
- # end
473
- # def consume(workitem)
474
- # workitem.fields['rocket_name'] = @name
475
- # send_to_the_moon(workitem)
476
- # end
477
- # def cancel(fei, flavour)
478
- # # do nothing
479
- # end
480
- # end
481
- #
482
- # engine.register_participant(
483
- # /^moon-.+/, MyParticipant, 'name' => 'Saturn-V')
484
- #
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)
495
- # end
496
- # end
497
- # engine.register_participant 'total', TotalParticipant
498
- #
499
- # Remember that the options (the hash that follows the class name), must be
500
- # serializable via JSON.
501
- #
502
- #
503
- # == require_path and load_path
504
- #
505
- # It's OK to register a participant by passing its full classname as a
506
- # String.
507
- #
508
- # engine.register_participant(
509
- # 'auditor', 'AuditParticipant', 'require_path' => 'part/audit.rb')
510
- # engine.register_participant(
511
- # 'auto_decision', 'DecParticipant', 'load_path' => 'part/dec.rb')
512
- #
513
- # Note the option load_path / require_path that point to the ruby file
514
- # containing the participant implementation. 'require' will load and eval
515
- # the ruby code only once, 'load' each time.
516
- #
517
- def register_participant(regex, participant=nil, opts={}, &block)
518
-
519
- if participant.is_a?(Hash)
520
- opts = participant
521
- participant = nil
522
- end
523
-
524
- pa = @context.plist.register(regex, participant, opts, block)
525
-
526
- @context.storage.put_msg(
527
- 'participant_registered',
528
- 'regex' => regex.is_a?(Regexp) ? regex.inspect : regex.to_s)
529
-
530
- pa
531
- end
532
-
533
- # A shorter version of #register_participant
534
- #
535
- # engine.register 'alice', MailParticipant, :target => 'alice@example.com'
536
- #
537
- # or a block registering mechanism.
538
- #
539
- # engine.register do
540
- # alpha 'Participants::Alpha', 'flavour' => 'vanilla'
541
- # participant 'bravo', 'Participants::Bravo', :flavour => 'peach'
542
- # catchall ParticipantCharlie, 'flavour' => 'coconut'
543
- # end
544
- #
545
- # Originally implemented in ruote-kit by Torsten Schoenebaum.
546
- #
547
- def register(*args, &block)
548
-
549
- if args.size > 0
550
- register_participant(*args, &block)
551
- else
552
- proxy = ParticipantRegistrationProxy.new(self)
553
- block.arity < 1 ? proxy.instance_eval(&block) : block.call(proxy)
554
- end
555
- end
556
-
557
- # Removes/unregisters a participant from the engine.
558
- #
559
- def unregister_participant(name_or_participant)
560
-
561
- re = @context.plist.unregister(name_or_participant)
562
-
563
- raise(ArgumentError.new('participant not found')) unless re
564
-
565
- @context.storage.put_msg(
566
- 'participant_unregistered',
567
- 'regex' => re.to_s)
568
- end
569
-
570
- alias :unregister :unregister_participant
571
-
572
- # Returns a list of Ruote::ParticipantEntry instances.
573
- #
574
- # engine.register_participant :alpha, MyParticipant, 'message' => 'hello'
575
- #
576
- # # interrogate participant list
577
- # #
578
- # list = engine.participant_list
579
- # participant = list.first
580
- # p participant.regex
581
- # # => "^alpha$"
582
- # p participant.classname
583
- # # => "MyParticipant"
584
- # p participant.options
585
- # # => {"message"=>"hello"}
586
- #
587
- # # update participant list
588
- # #
589
- # participant.regex = '^alfred$'
590
- # engine.participant_list = list
591
- #
592
- def participant_list
593
-
594
- @context.plist.list
595
- end
596
-
597
- # Accepts a list of Ruote::ParticipantEntry instances or a list of
598
- # [ regex, [ classname, opts ] ] arrays.
599
- #
600
- # See Engine#participant_list
601
- #
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)
613
-
614
- @context.plist.list = pl
615
- end
616
-
617
- # A convenience method for
618
- #
619
- # sp = Ruote::StorageParticipant.new(engine)
620
- #
621
- # simply do
622
- #
623
- # sp = engine.storage_participant
624
- #
625
- def storage_participant
626
-
627
- @storage_participant ||= Ruote::StorageParticipant.new(self)
628
- end
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
-
638
- # Adds a service locally (will not get propagated to other workers).
639
- #
640
- # tracer = Tracer.new
641
- # @engine.add_service('tracer', tracer)
642
- #
643
- # or
644
- #
645
- # @engine.add_service('tracer', 'ruote/exp/tracer', 'Ruote::Exp::Tracer')
646
- #
647
- # This method returns the service instance it just bound.
648
- #
649
- def add_service(name, path_or_instance, classname=nil, opts=nil)
650
-
651
- @context.add_service(name, path_or_instance, classname, opts)
652
- end
653
-
654
- # Sets a configuration option. Examples:
655
- #
656
- # # allow remote workflow definitions (for subprocesses or when launching
657
- # # processes)
658
- # @engine.configure('remote_definition_allowed', true)
659
- #
660
- # # allow ruby_eval
661
- # @engine.configure('ruby_eval_allowed', true)
662
- #
663
- def configure(config_key, value)
664
-
665
- @context[config_key] = value
666
- end
667
-
668
- # Returns a configuration value.
669
- #
670
- # engine.configure('ruby_eval_allowed', true)
671
- #
672
- # p engine.configuration('ruby_eval_allowed')
673
- # # => true
674
- #
675
- def configuration(config_key)
676
-
677
- @context[config_key]
678
- end
679
-
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' })
768
- end
769
-
770
- # A debug helper :
771
- #
772
- # engine.noisy = true
773
- #
774
- # will let the engine (in fact the worker) pour all the details of the
775
- # executing process instances to STDOUT.
776
- #
777
- def noisy=(b)
778
-
779
- @context.logger.noisy = b
780
- end
781
-
782
- protected
783
-
784
- # Used by #process and #processes
785
- #
786
- def statuses(wfids, opts)
787
-
788
- swfids = wfids.collect { |wfid| /!#{wfid}-\d+$/ }
789
-
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...
795
-
796
- errs = errs.collect { |err| ProcessError.new(err) }
797
- schs = schs.collect { |sch| Ruote.schedule_to_h(sch) }
798
-
799
- by_wfid = {}
800
-
801
- exps.each do |exp|
802
- (by_wfid[exp['fei']['wfid']] ||= [ [], [], [], [] ])[0] << exp
803
- end
804
- swis.each do |swi|
805
- (by_wfid[swi['fei']['wfid']] ||= [ [], [], [], [] ])[1] << swi
806
- end
807
- errs.each do |err|
808
- (by_wfid[err.wfid] ||= [ [], [], [], [] ])[2] << err
809
- end
810
- schs.each do |sch|
811
- (by_wfid[sch['wfid']] ||= [ [], [], [], [] ])[3] << sch
812
- end
813
-
814
- wfids = by_wfid.keys.sort
815
- wfids = wfids.reverse if opts[:descending]
816
- # re-adjust list of wfids, only take what was found
817
-
818
- wfids.inject([]) { |a, wfid|
819
- info = by_wfid[wfid]
820
- a << ProcessStatus.new(@context, *info) if info
821
- a
822
- }
823
- end
824
- end
825
-
826
- #
827
- # A wrapper class giving easy access to engine variables.
828
- #
829
- # There is one instance of this class for an Engine instance. It is
830
- # returned when calling Engine#variables.
831
- #
832
- class EngineVariables
833
-
834
- def initialize(storage)
835
-
836
- @storage = storage
837
- end
838
-
839
- def [](k)
840
-
841
- @storage.get_engine_variable(k)
842
- end
843
-
844
- def []=(k, v)
845
-
846
- @storage.put_engine_variable(k, v)
847
- end
848
- end
849
-
850
- #
851
- # Engine#register uses this proxy when it's passed a block.
852
- #
853
- # Originally written by Torsten Schoenebaum for ruote-kit.
854
- #
855
- class ParticipantRegistrationProxy
856
-
857
- def initialize(engine)
858
-
859
- @engine = engine
860
- end
861
-
862
- def participant(name, klass=nil, options={}, &block)
863
-
864
- @engine.register_participant(name, klass, options, &block)
865
- end
866
-
867
- def catchall(*args)
868
-
869
- klass = args.empty? ? Ruote::StorageParticipant : args.first
870
- options = args[1] || {}
871
-
872
- participant('.+', klass, options)
873
- end
874
-
875
- # Maybe a bit audacious...
876
- #
877
- def method_missing(method_name, *args)
878
-
879
- participant(method_name, *args)
880
- end
881
- end
882
-
883
- # Refines a schedule as found in the ruote storage into something a bit
884
- # easier to present.
885
- #
886
- def self.schedule_to_h(sched)
887
-
888
- h = sched.dup
889
-
890
- h.delete('_rev')
891
- h.delete('type')
892
- msg = h.delete('msg')
893
- owner = h.delete('owner')
894
-
895
- h['wfid'] = owner['wfid']
896
- h['action'] = msg['action']
897
- h['type'] = msg['flavour']
898
- h['owner'] = Ruote::FlowExpressionId.new(owner)
899
- h['target'] = Ruote::FlowExpressionId.new(msg['fei'])
900
-
901
- h
36
+ class Engine < Dashboard
902
37
  end
903
38
  end
904
39