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,386 @@
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/fei'
26
+
27
+
28
+ module Ruote
29
+
30
+ #
31
+ # Workers fetch 'msgs' and 'schedules' from the storage and process them.
32
+ #
33
+ # Read more at http://ruote.rubyforge.org/configuration.html
34
+ #
35
+ class Worker
36
+
37
+ EXP_ACTIONS = %w[ reply cancel fail receive dispatched pause resume ]
38
+ # 'apply' is comprised in 'launch'
39
+ # 'receive' is a ParticipantExpression alias for 'reply'
40
+
41
+ PROC_ACTIONS = %w[ cancel kill pause resume ].collect { |a| a + '_process' }
42
+ DISP_ACTIONS = %w[ dispatch dispatch_cancel dispatch_pause dispatch_resume ]
43
+
44
+ attr_reader :storage
45
+ attr_reader :context
46
+
47
+ attr_reader :run_thread
48
+ attr_reader :running
49
+
50
+ # Given a storage, creates a new instance of a Worker.
51
+ #
52
+ def initialize(storage)
53
+
54
+ @subscribers = []
55
+ # must be ready before the storage is created
56
+ # services like Logger to subscribe to the worker
57
+
58
+ @storage = storage
59
+ @context = Ruote::Context.new(storage, self)
60
+
61
+ @last_time = Time.at(0.0).utc # 1970...
62
+
63
+ @running = true
64
+ @run_thread = nil
65
+
66
+ @msgs = []
67
+ @sleep_time = 0.000
68
+ end
69
+
70
+ # Runs the worker in the current thread. See #run_in_thread for running
71
+ # in a dedicated thread.
72
+ #
73
+ def run
74
+
75
+ step while @running
76
+ end
77
+
78
+ # Triggers the run method of the worker in a dedicated thread.
79
+ #
80
+ def run_in_thread
81
+
82
+ Thread.abort_on_exception = true
83
+ # TODO : remove me at some point
84
+
85
+ @running = true
86
+
87
+ @run_thread = Thread.new { run }
88
+ end
89
+
90
+ # Joins the run thread of this worker (if there is no such thread, this
91
+ # method will return immediately, without any effect).
92
+ #
93
+ def join
94
+
95
+ @run_thread.join if @run_thread
96
+ end
97
+
98
+ # Loggers and trackers call this method when subscribing for events /
99
+ # actions in this worker.
100
+ #
101
+ def subscribe(actions, subscriber)
102
+
103
+ @subscribers << [ actions, subscriber ]
104
+ end
105
+
106
+ # Shuts down this worker (makes sure it won't fetch further messages
107
+ # and schedules).
108
+ #
109
+ def shutdown(join=true)
110
+
111
+ @running = false
112
+
113
+ if join
114
+ begin
115
+ @run_thread.join
116
+ rescue Exception => e
117
+ end
118
+ else
119
+ sleep(3)
120
+ end
121
+ end
122
+
123
+ # Returns true if the engine system is inactive, ie if all the process
124
+ # instances are terminated or are stuck in an error.
125
+ #
126
+ # NOTE : for now, if a branch of a process is in error while another is
127
+ # still running, this method will consider the process instance inactive
128
+ # (and it will return true if all the processes are considered inactive).
129
+ #
130
+ def inactive?
131
+
132
+ # the cheaper tests first
133
+
134
+ return false if @msgs.size > 0
135
+ return false unless @context.storage.empty?('schedules')
136
+ return false unless @context.storage.empty?('msgs')
137
+
138
+ wfids = @context.storage.get_many('expressions').collect { |exp|
139
+ exp['fei']['wfid']
140
+ }
141
+
142
+ error_wfids = @context.storage.get_many('errors').collect { |err|
143
+ err['fei']['wfid']
144
+ }
145
+
146
+ (wfids - error_wfids == [])
147
+ end
148
+
149
+ protected
150
+
151
+ # One worker step, fetches schedules and triggers those whose time has
152
+ # came, then fetches msgs and processes them.
153
+ #
154
+ def step
155
+
156
+ now = Time.now.utc
157
+ delta = now - @last_time
158
+
159
+ if delta >= 0.8
160
+ #
161
+ # at most once per second, deal with 'ats' and 'crons'
162
+
163
+ @last_time = now
164
+
165
+ @storage.get_schedules(delta, now).each { |sche| trigger(sche) }
166
+ end
167
+
168
+ # msgs
169
+
170
+ @msgs = @storage.get_msgs if @msgs.empty?
171
+
172
+ processed = 0
173
+ collisions = 0
174
+
175
+ while msg = @msgs.shift
176
+
177
+ r = process(msg)
178
+
179
+ if r != false
180
+ processed += 1
181
+ else
182
+ collisions += 1
183
+ end
184
+
185
+ if collisions > 2
186
+ @msgs = @msgs[(@msgs.size / 2)..-1] || []
187
+ end
188
+
189
+ #@msgs.concat(@storage.get_local_msgs)
190
+
191
+ #print r == false ? '*' : '.'
192
+
193
+ break if Time.now.utc - @last_time >= 0.8
194
+ end
195
+
196
+ #p processed
197
+
198
+ if processed == 0
199
+ @sleep_time += 0.001
200
+ @sleep_time = 0.499 if @sleep_time > 0.499
201
+ sleep(@sleep_time)
202
+ else
203
+ @sleep_time = 0.000
204
+ end
205
+ end
206
+
207
+ # Given a schedule, attempts to trigger it.
208
+ #
209
+ # It first tries to
210
+ # reserve the schedule. If the reservation fails (another worker
211
+ # was successful probably), false is returned. The schedule is
212
+ # triggered if the reservation was successful, true is returned.
213
+ #
214
+ def trigger(schedule)
215
+
216
+ msg = Ruote.fulldup(schedule['msg'])
217
+
218
+ return false unless @storage.reserve(schedule)
219
+
220
+ @storage.put_msg(msg.delete('action'), msg)
221
+
222
+ true
223
+ end
224
+
225
+ # Processes one msg.
226
+ #
227
+ # Will return false immediately if the msg reservation failed (another
228
+ # worker grabbed the message.
229
+ #
230
+ # Else will execute the action ordered in the msg, and return true.
231
+ #
232
+ # Exceptions in execution are intercepted here and passed to the
233
+ # engine's (context's) error_handler.
234
+ #
235
+ def process(msg)
236
+
237
+ return false unless @storage.reserve(msg)
238
+
239
+ begin
240
+
241
+ action = msg['action']
242
+
243
+ if msg['tree']
244
+ #
245
+ # warning here, it could be a reply, with a 'tree' key...
246
+
247
+ launch(msg)
248
+
249
+ elsif EXP_ACTIONS.include?(action)
250
+
251
+ Ruote::Exp::FlowExpression.do_action(@context, msg)
252
+
253
+ elsif DISP_ACTIONS.include?(action)
254
+
255
+ @context.dispatch_pool.handle(msg)
256
+
257
+ elsif PROC_ACTIONS.include?(action)
258
+
259
+ self.send(action, msg)
260
+
261
+ #else
262
+ # msg got deleted, might still be interesting for a subscriber
263
+ end
264
+
265
+ notify(msg)
266
+
267
+ rescue => exception
268
+
269
+ @context.error_handler.msg_handle(msg, exception)
270
+ end
271
+
272
+ true
273
+ end
274
+
275
+ # Given a successfully executed msg, now notifies all the subscribers
276
+ # interested in the kind of action the msg ordered.
277
+ #
278
+ def notify(msg)
279
+
280
+ @subscribers.each do |actions, subscriber|
281
+
282
+ if actions == :all || actions.include?(msg['action'])
283
+ subscriber.notify(msg)
284
+ end
285
+ end
286
+ end
287
+
288
+ # Works for both the 'launch' and the 'apply' msgs.
289
+ #
290
+ # Creates a new expression, gives and applies it with the
291
+ # workitem contained in the msg.
292
+ #
293
+ def launch(msg)
294
+
295
+ tree = msg['tree']
296
+ variables = msg['variables']
297
+ wi = msg['workitem']
298
+
299
+ exp_class = @context.expmap.expression_class(tree.first)
300
+
301
+ # msg['wfid'] only : it's a launch
302
+ # msg['fei'] : it's a sub launch (a supplant ?)
303
+
304
+ wi['wf_name'] ||= (
305
+ tree[1]['name'] || tree[1].keys.find { |k| tree[1][k] == nil })
306
+
307
+ wi['wf_revision'] ||= (
308
+ tree[1]['revision'] || tree[1]['rev'])
309
+
310
+ exp_hash = {
311
+ 'fei' => msg['fei'] || {
312
+ 'engine_id' => @context.engine_id,
313
+ 'wfid' => msg['wfid'],
314
+ 'subid' => Ruote.generate_subid(msg.inspect),
315
+ 'expid' => '0' },
316
+ 'parent_id' => msg['parent_id'],
317
+ 'original_tree' => tree,
318
+ 'variables' => variables,
319
+ 'applied_workitem' => wi,
320
+ 'forgotten' => msg['forgotten']
321
+ }
322
+
323
+ if not exp_class
324
+
325
+ exp_class = Ruote::Exp::RefExpression
326
+
327
+ elsif is_launch?(msg, exp_class)
328
+
329
+ def_name, tree = Ruote::Exp::DefineExpression.reorganize(tree)
330
+ variables[def_name] = [ '0', tree ] if def_name
331
+ exp_class = Ruote::Exp::SequenceExpression
332
+ end
333
+
334
+ exp = exp_class.new(@context, exp_hash.merge!('original_tree' => tree))
335
+
336
+ exp.initial_persist
337
+ exp.do_apply(msg)
338
+ end
339
+
340
+ # Returns true if the msg is a "launch" (ie not a simply "apply").
341
+ #
342
+ def is_launch?(msg, exp_class)
343
+
344
+ return false if exp_class != Ruote::Exp::DefineExpression
345
+ return true if msg['action'] == 'launch'
346
+ (msg['trigger'] == 'on_re_apply')
347
+ end
348
+
349
+ # Handles a 'cancel_process' msg (translates it into a "cancel root
350
+ # expression of that process" msg).
351
+ #
352
+ # Also works for 'kill_process' msgs.
353
+ #
354
+ def cancel_process(msg)
355
+
356
+ root = @storage.find_root_expression(msg['wfid'])
357
+
358
+ return unless root
359
+
360
+ @storage.put_msg(
361
+ 'cancel',
362
+ 'fei' => root['fei'],
363
+ 'wfid' => msg['wfid'], # indicates this was triggered by cancel_process
364
+ 'flavour' => msg['action'] == 'kill_process' ? 'kill' : nil)
365
+ end
366
+
367
+ alias kill_process cancel_process
368
+
369
+ # Handles 'pause_process' and 'resume_process'.
370
+ #
371
+ def pause_process(msg)
372
+
373
+ root = @storage.find_root_expression(msg['wfid'])
374
+
375
+ return unless root
376
+
377
+ @storage.put_msg(
378
+ msg['action'] == 'pause_process' ? 'pause' : 'resume',
379
+ 'fei' => root['fei'],
380
+ 'wfid' => msg['wfid']) # it was triggered by {pause|resume}_process
381
+ end
382
+
383
+ alias resume_process pause_process
384
+ end
385
+ end
386
+
@@ -0,0 +1,394 @@
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/misc'
26
+ require 'ruote/util/lookup'
27
+ require 'ruote/util/hashdot'
28
+
29
+
30
+ module Ruote
31
+
32
+ #
33
+ # A workitem can be thought of an "execution token", but with a payload
34
+ # (fields).
35
+ #
36
+ # The payload/fields MUST be JSONifiable.
37
+ #
38
+ class Workitem
39
+
40
+ attr_reader :h
41
+
42
+ def initialize(h)
43
+
44
+ @h = h
45
+ class << @h; include Ruote::HashDot; end
46
+
47
+ #class << @h['fields']
48
+ # alias_method :__get, :[]
49
+ # alias_method :__set, :[]=
50
+ # def [](key)
51
+ # __get(key.to_s)
52
+ # end
53
+ # def []=(key, value)
54
+ # __set(key.to_s, value)
55
+ # end
56
+ #end
57
+ # indifferent access, not activated for now
58
+ end
59
+
60
+ # Returns the underlying Hash instance.
61
+ #
62
+ def to_h
63
+
64
+ @h
65
+ end
66
+
67
+ # Returns the String id for this workitem (something like
68
+ # "0_0!!20100507-wagamama").
69
+ #
70
+ # It's in fact a shortcut for
71
+ #
72
+ # Ruote::FlowExpressionId.to_storage_id(h.fei)
73
+ #
74
+ def sid
75
+
76
+ Ruote::FlowExpressionId.to_storage_id(h.fei)
77
+ end
78
+
79
+ # Returns the "workflow instance id" (unique process instance id) of
80
+ # the process instance which issued this workitem.
81
+ #
82
+ def wfid
83
+
84
+ h.fei['wfid']
85
+ end
86
+
87
+ # Returns a Ruote::FlowExpressionId instance.
88
+ #
89
+ def fei
90
+
91
+ FlowExpressionId.new(@h['fei'])
92
+ end
93
+
94
+ # Returns a complete copy of this workitem.
95
+ #
96
+ def dup
97
+
98
+ Workitem.new(Rufus::Json.dup(@h))
99
+ end
100
+
101
+ # The participant for which this item is destined. Will be nil when
102
+ # the workitem is transiting inside of its process instance (as opposed
103
+ # to when it's being delivered outside of the engine).
104
+ #
105
+ def participant_name
106
+
107
+ @h['participant_name']
108
+ end
109
+
110
+ # Returns the name of the workflow to which this workitem belongs, or nil.
111
+ #
112
+ def wf_name
113
+
114
+ @h['wf_name']
115
+ end
116
+
117
+ alias definition_name wf_name
118
+
119
+ # Returns the revision of the workflow to which this workitem belongs,
120
+ # or nil.
121
+ #
122
+ def wf_revision
123
+
124
+ @h['wf_revision']
125
+ end
126
+
127
+ alias definition_revision wf_revision
128
+
129
+ # Returns the payload, ie the fields hash.
130
+ #
131
+ def fields
132
+
133
+ @h['fields']
134
+ end
135
+
136
+ # Sets all the fields in one sweep.
137
+ #
138
+ # Remember : the fields must be a JSONifiable hash.
139
+ #
140
+ def fields=(fields)
141
+
142
+ @h['fields'] = fields
143
+ end
144
+
145
+ # A shortcut to the value in the field named __result__
146
+ #
147
+ # This field is used by the if expression for instance to determine
148
+ # if it should branch to its 'then' or its 'else'.
149
+ #
150
+ def result
151
+
152
+ fields['__result__']
153
+ end
154
+
155
+ # Sets the value of the 'special' field __result__
156
+ #
157
+ # See #result
158
+ #
159
+ def result=(r)
160
+
161
+ fields['__result__'] = r
162
+ end
163
+
164
+ # When was this workitem dispatched ?
165
+ #
166
+ def dispatched_at
167
+
168
+ fields['dispatched_at']
169
+ end
170
+
171
+ # Warning : equality is based on fei and not on payload !
172
+ #
173
+ def ==(other)
174
+
175
+ return false if other.class != self.class
176
+ self.h['fei'] == other.h['fei']
177
+ end
178
+
179
+ alias eql? ==
180
+
181
+ # Warning : hash is fei's hash.
182
+ #
183
+ def hash
184
+
185
+ self.h['fei'].hash
186
+ end
187
+
188
+ # For a simple key
189
+ #
190
+ # workitem.lookup('toto')
191
+ #
192
+ # is equivalent to
193
+ #
194
+ # workitem.fields['toto']
195
+ #
196
+ # but for a complex key
197
+ #
198
+ # workitem.lookup('toto.address')
199
+ #
200
+ # is equivalent to
201
+ #
202
+ # workitem.fields['toto']['address']
203
+ #
204
+ def lookup(key, container_lookup=false)
205
+
206
+ Ruote.lookup(@h['fields'], key, container_lookup)
207
+ end
208
+
209
+ # 'lf' for 'lookup field'
210
+ #
211
+ alias :lf :lookup
212
+
213
+ # Like #lookup allows for nested lookups, #set_field can be used
214
+ # to set sub fields directly.
215
+ #
216
+ # workitem.set_field('customer.address.city', 'Pleasantville')
217
+ #
218
+ # Warning : if the customer and address field and subfield are not present
219
+ # or are not hashes, set_field will simply create a "customer.address.city"
220
+ # field and set its value to "Pleasantville".
221
+ #
222
+ def set_field(key, value)
223
+
224
+ Ruote.set(@h['fields'], key, value)
225
+ end
226
+
227
+ # Shortcut for wi.fields['__timed_out__']
228
+ #
229
+ def timed_out
230
+
231
+ @h['fields']['__timed_out__']
232
+ end
233
+
234
+ # Shortcut for wi.fields['__error__']
235
+ #
236
+ def error
237
+
238
+ @h['fields']['__error__']
239
+ end
240
+
241
+ # Shortcut for wi.fields['params']
242
+ #
243
+ # When a participant is invoked like in
244
+ #
245
+ # participant :ref => 'toto', :task => 'x"
246
+ #
247
+ # then
248
+ #
249
+ # p workitem.params
250
+ # # => { 'ref' => 'toto', 'task' => 'x' }
251
+ #
252
+ def params
253
+
254
+ @h['fields']['params'] || {}
255
+ end
256
+
257
+ # When a participant is invoked like in
258
+ #
259
+ # accounting 'do_invoice', :customer => 'acme corp'
260
+ #
261
+ # then
262
+ #
263
+ # p workitem.params
264
+ # # => { 'ref' => 'accounting', 'do_invoice' => nil, 'customer' => 'acme corp' }
265
+ #
266
+ # and
267
+ #
268
+ # p workitem.param_text
269
+ # # => 'do_invoice'
270
+ #
271
+ # It returns nil when there is no text passed directly.
272
+ #
273
+ def param_text
274
+
275
+ (params.find { |k, v| v.nil? } || []).first
276
+ end
277
+
278
+ # Sometimes a value is passed as a[n expression] parameter or as a
279
+ # workitem field, with priority to the parameter.
280
+ #
281
+ # sequence do
282
+ # set 'f:country' => 'uruguay'
283
+ # participant 'toto'
284
+ # # in toto, workitem.param_or_field(:country) will yield 'uruguay'
285
+ # participant 'toto', :country => 'argentina'
286
+ # # workitem.param_or_field(:country) will yield 'argentina'
287
+ # end
288
+ #
289
+ def param_or_field(key)
290
+
291
+ key = key.to_s
292
+
293
+ (@h['fields']['params'] || {})[key] || @h['fields'][key]
294
+ end
295
+
296
+ # Like #param_or_field, but priority is given to the field.
297
+ #
298
+ def field_or_param(key)
299
+
300
+ key = key.to_s
301
+
302
+ @h['fields'][key] || (@h['fields']['params'] || {})[key]
303
+ end
304
+
305
+ # Shortcut to the temporary/trailing fields
306
+ #
307
+ # http://groups.google.com/group/openwferu-users/browse_thread/thread/981dba6204f31ccc
308
+ #
309
+ def t
310
+ @h['fields']['t'] ||= {}
311
+ end
312
+
313
+ # (advanced)
314
+ #
315
+ # Shortcut for wi.fields['__command__']
316
+ #
317
+ # __command__ is read by the 'cursor' and the 'iterator' expressions
318
+ # when a workitem reaches it (apply and reply).
319
+ #
320
+ def commmand
321
+
322
+ @h['fields']['__command__']
323
+ end
324
+
325
+ # (advanced)
326
+ #
327
+ # Shortcut for wi.fields['__command__'] = x
328
+ #
329
+ # __command__ is read by the 'cursor' and the 'iterator' expressions
330
+ # when a workitem reaches it (apply and reply).
331
+ #
332
+ def command=(com)
333
+
334
+ com = com.is_a?(Array) ? com : com.split(' ')
335
+ com = [ com.first, com.last ]
336
+ com[1] = com[1].to_i if com[1] and com[0] != 'jump'
337
+
338
+ @h['fields']['__command__'] = com
339
+ end
340
+
341
+ # Shortcut for wi.fields['__tags__']
342
+ #
343
+ def tags
344
+
345
+ @h['fields']['__tags__'] || []
346
+ end
347
+
348
+ # Used by FlowExpression when entering a tag.
349
+ #
350
+ def self.add_tag(hworkitem, tag)
351
+
352
+ (hworkitem['fields']['__tags__'] ||= []) << tag
353
+ end
354
+
355
+ # Used by FlowExpression when leaving a tag.
356
+ #
357
+ def self.remove_tag(hworkitem, tag)
358
+
359
+ # it's a bit convoluted... trying to cope with potential inconsistencies
360
+ #
361
+ # normally, it should only be a tags.pop(), but since user have
362
+ # access to the workitem and its fields... better be safe than sorry
363
+
364
+ tags = (hworkitem['fields']['__tags__'] || [])
365
+
366
+ if index = tags.rindex(tag)
367
+ tags.delete_at(index)
368
+ end
369
+ end
370
+
371
+ # Encodes this workitem as JSON. If pretty is set to true, will output
372
+ # prettified JSON.
373
+ #
374
+ def as_json(pretty=false)
375
+
376
+ pretty ? Rufus::Json.pretty_encode(@h) : Rufus::Json.encode(@h)
377
+ end
378
+
379
+ # Given a JSON String, decodes and returns a Ruote::Workitem instance.3
380
+ # If the decode thing is not an object/hash, will raise an ArgumentError.
381
+ #
382
+ def self.from_json(json)
383
+
384
+ h = Rufus::Json.decode(json)
385
+
386
+ raise ArgumentError(
387
+ "Arg not a JSON hash/object, but a #{h.class}. Cannot create workitem"
388
+ ) unless h.is_a?(Hash)
389
+
390
+ self.new(h)
391
+ end
392
+ end
393
+ end
394
+