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,112 @@
1
+ #--
2
+ # Copyright (c) 2005-2011, John Mettraux, jmettraux@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+
26
+ module Ruote::Exp
27
+
28
+ #
29
+ # The main names for this expression are 'define' and 'process_definition'.
30
+ # It simply encloses a process definition (and gives it a name and revision
31
+ # if needed).
32
+ #
33
+ # pdef = Ruote.process_definition :name => 'test', :revision => '0' do
34
+ # sequence do
35
+ # participant :ref => 'alice'
36
+ # participant :ref => 'bob'
37
+ # end
38
+ # end
39
+ #
40
+ # It's used for subprocess definitions as well.
41
+ #
42
+ # pdef = Ruote.process_definition :name => 'test', :revision => '0' do
43
+ # sequence do
44
+ # buy_food
45
+ # cook_food
46
+ # end
47
+ # define 'buy_food' do
48
+ # participant :ref => 'alice'
49
+ # end
50
+ # define :name => 'cook_food' do
51
+ # participant :ref => 'bob'
52
+ # end
53
+ # end
54
+ #
55
+ # == like a sequence
56
+ #
57
+ # Ruote 2.0 treats the child expressions of a 'define' expression like a
58
+ # 'sequence' expression does. Thus, this
59
+ #
60
+ # pdef = Ruote.process_definition :name => 'test' do
61
+ # sequence do
62
+ # buy_food
63
+ # cook_food
64
+ # end
65
+ # end
66
+ #
67
+ # is equivalent to
68
+ #
69
+ # pdef = Ruote.process_definition :name => 'test' do
70
+ # buy_food
71
+ # cook_food
72
+ # end
73
+ #
74
+ class DefineExpression < FlowExpression
75
+
76
+ names :define, :process_definition, :workflow_definition
77
+
78
+ def apply
79
+
80
+ t = self.class.reorganize(tree).last
81
+
82
+ name = attribute(:name) || attribute_text
83
+
84
+ set_variable(name, [ h.fei['expid'], t ]) if name
85
+ #
86
+ # fei.expid : keeping track of the expid/branch for the subprocess
87
+ # (so that graphical representations match)
88
+
89
+ reply_to_parent(h.applied_workitem)
90
+ end
91
+
92
+ # Returns true if the tree's root expression is a definition
93
+ # (define, process_definition, ...)
94
+ #
95
+ def self.is_definition?(tree)
96
+
97
+ self.expression_names.include?(tree.first)
98
+ end
99
+
100
+ # Used by instances of this class and also the expression pool,
101
+ # when launching a new process instance.
102
+ #
103
+ def self.reorganize(tree)
104
+
105
+ definitions, bodies = tree[2].partition { |b| is_definition?(b) }
106
+ name = tree[1]['name'] || tree[1].keys.find { |k| tree[1][k] == nil }
107
+
108
+ [ name, [ 'define', tree[1], definitions + bodies ] ]
109
+ end
110
+ end
111
+ end
112
+
@@ -0,0 +1,60 @@
1
+ #--
2
+ # Copyright (c) 2005-2011, John Mettraux, jmettraux@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+
26
+ module Ruote::Exp
27
+
28
+ #
29
+ # An expression for echoing text to STDOUT or to a :s_tracer service
30
+ # (if there is one bound in the engine context).
31
+ #
32
+ # sequence do
33
+ # participant :ref => 'toto'
34
+ # echo 'toto replied'
35
+ # end
36
+ #
37
+ class EchoExpression < FlowExpression
38
+
39
+ names :echo
40
+
41
+ def apply
42
+
43
+ text = "#{attribute(:text) || attribute_text}\n"
44
+
45
+ if t = @context['s_tracer']
46
+ t << text
47
+ else
48
+ print(text)
49
+ end
50
+
51
+ reply_to_parent(h.applied_workitem)
52
+ end
53
+
54
+ def reply(workitem)
55
+
56
+ # never called
57
+ end
58
+ end
59
+ end
60
+
@@ -0,0 +1,115 @@
1
+ #--
2
+ # Copyright (c) 2005-2011, John Mettraux, jmettraux@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+
26
+ module Ruote::Exp
27
+
28
+ #
29
+ # This expression fell out of favour a long ago. At first it was used with
30
+ # the 'if' expression :
31
+ #
32
+ # _if do
33
+ # equals :field_value => 'customer', :other_value => 'British Petroleum'
34
+ # participant :ref => 'Allister'
35
+ # end
36
+ #
37
+ # but lately, the :test attribute of the 'if' expression is used :
38
+ #
39
+ # _if :test => '${f:customer} == British Petroleum' do
40
+ # participant :ref => 'Allister'
41
+ # end
42
+ #
43
+ # In some cases, the 'if' expression vanishes and the :if attribute shared
44
+ # by all expressions is used :
45
+ #
46
+ # participant :ref => 'Al', :if => '${f:customer} == British Petroleum'
47
+ #
48
+ #
49
+ # == attributes
50
+ #
51
+ # The 'equals' expression accepts those attributes :
52
+ #
53
+ # * :value
54
+ # * :field_value
55
+ # * :variable_value
56
+ # * :val
57
+ # * :field_val
58
+ # * :variable_val
59
+ #
60
+ # and
61
+ #
62
+ # * :other_value
63
+ # * :other_field_value
64
+ # * :other_variable_value
65
+ # * :other_val
66
+ # * :other_field_val
67
+ # * :other_variable_val
68
+ #
69
+ # With a bit of luck, they make sense on their own.
70
+ #
71
+ class EqualsExpression < FlowExpression
72
+
73
+ names :equals
74
+
75
+ def apply
76
+
77
+ vals = grab_values
78
+
79
+ h.applied_workitem['fields']['__result__'] = if vals.size < 2
80
+ false
81
+ else
82
+ (vals.first == vals.last)
83
+ end
84
+
85
+ reply_to_parent(h.applied_workitem)
86
+ end
87
+
88
+ protected
89
+
90
+ OTHER_REGEX = /^other\_(.+)$/
91
+
92
+ def grab_values
93
+
94
+ keys = attributes.keys.select { |k| ! COMMON_ATT_KEYS.include?(k) }
95
+
96
+ keys.collect { |k| grab_value(k) }
97
+ end
98
+
99
+ def grab_value(k)
100
+
101
+ attval = attribute(k)
102
+
103
+ if m = OTHER_REGEX.match(k)
104
+ k = m[1]
105
+ end
106
+
107
+ case k
108
+ when /^f/ then h.applied_workitem['fields'][attval]
109
+ when /^var/ then lookup_variable(attval)
110
+ when /^val/ then attval
111
+ end
112
+ end
113
+ end
114
+ end
115
+
@@ -0,0 +1,82 @@
1
+ #--
2
+ # Copyright (c) 2005-2011, John Mettraux, jmettraux@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+
26
+ module Ruote
27
+
28
+ #
29
+ # This error is used by the 'error' expression, when an error is triggered
30
+ # from the process definition.
31
+ #
32
+ class ForcedError < RuntimeError
33
+ end
34
+ end
35
+
36
+ module Ruote::Exp
37
+
38
+ #
39
+ # Triggers an error directly from the process definition.
40
+ #
41
+ # Ruote.process_definition :name => 'log1' do
42
+ # sequence do
43
+ # perform_inventory
44
+ # error 'inventory issue', :if => '${f:level} < 1'
45
+ # order_new_stuff
46
+ # store_new_stuff
47
+ # end
48
+ # end
49
+ #
50
+ # Replaying the error will 'unlock' the process.
51
+ #
52
+ class ErrorExpression < FlowExpression
53
+
54
+ names :error
55
+
56
+ def apply
57
+
58
+ # making the error occurs in the reply() phase
59
+ # so that the replay_at_error targets the reply and not the apply
60
+
61
+ @context.storage.put_msg(
62
+ 'reply',
63
+ 'fei' => h.fei,
64
+ 'workitem' => h.applied_workitem)
65
+ end
66
+
67
+ def reply(workitem)
68
+
69
+ return reply_to_parent(workitem) if h.triggered
70
+
71
+ msg = attribute(:msg) || attribute(:message) || attribute_text
72
+ msg = 'error triggered from process definition' if msg.strip == ''
73
+
74
+ h.triggered = true
75
+
76
+ persist_or_raise # to keep track of h.triggered
77
+
78
+ raise(Ruote::ForcedError.new(msg))
79
+ end
80
+ end
81
+ end
82
+
@@ -0,0 +1,648 @@
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/filter'
26
+
27
+
28
+ module Ruote::Exp
29
+
30
+ #
31
+ # Filter is a one-way filter expression. It filters workitem fields.
32
+ # Validations and Transformations are possible.
33
+ #
34
+ # Validations will raise errors (that'll block the process segment
35
+ # unless an :on_error attribute somehow deals with the problem).
36
+ #
37
+ # Transformations will copy values around fields.
38
+ #
39
+ # There are two ways to use it. With a single rule or with an array of
40
+ # rules.
41
+ #
42
+ # filter 'x', :type => 'string'
43
+ # # will raise an error if the field 'x' doesn't contain a String
44
+ #
45
+ # or
46
+ #
47
+ # filter :in => [
48
+ # { :field => 'x', :type => 'string' },
49
+ # { :field => 'y', :type => 'number' }
50
+ # ]
51
+ #
52
+ # For the remainder of this piece of documentation, the one rule filter
53
+ # will be used.
54
+ #
55
+ # == filtering targets (field names)
56
+ #
57
+ # Top level field names are OK :
58
+ #
59
+ # filter 'customer_id', :type => 'string'
60
+ # filter 'invoice_id', :type => 'number'
61
+ #
62
+ # Pointing to fields lying deeper is OK :
63
+ #
64
+ # filter 'customer.id', :type => 'number'
65
+ # filter 'customer.name', :type => 'string'
66
+ # filter 'invoice', :type => 'array'
67
+ # filter 'invoice.0.id', :type => 'number'
68
+ #
69
+ # (Note the dollar notation is also OK with such dotted identifiers)
70
+ #
71
+ # It's possible to target multiple fields by passing a list of field names
72
+ # or a regular expression.
73
+ #
74
+ # filter 'city, region, country', :type => 'string'
75
+ # # will make sure that those 3 fields hold a string value
76
+ #
77
+ # filter '/^address\.x_/', :type => number
78
+ # filter '/^address!x_/', :type => number
79
+ # # fields whosename start with x_ in the address hash should be numbers
80
+ #
81
+ # Note the "!" used as a shortcut for "\." in the second line.
82
+ #
83
+ # Passing a | separated list of field also works :
84
+ #
85
+ # filter 'city|region|country', :type => 'string'
86
+ # # will make sure that at least of one those field is present
87
+ # # any of the three that is present must hold a string
88
+ #
89
+ #
90
+ # == validations
91
+ #
92
+ # === 'type'
93
+ #
94
+ # Ruote is a Ruby library, it adopts Ruby "laissez-faire" for workitem
95
+ # fields, but sometimes, some type oriented validation is necessary.
96
+ # Ruote limits itself to the types found in the JSON specification with
97
+ # one or two additions.
98
+ #
99
+ # filter 'x', :type => 'string'
100
+ # filter 'x', :type => 'number'
101
+ # filter 'x', :type => 'bool'
102
+ # filter 'x', :type => 'boolean'
103
+ # filter 'x', :type => 'null'
104
+ #
105
+ # filter 'x', :type => 'array'
106
+ #
107
+ # filter 'x', :type => 'object'
108
+ # filter 'x', :type => 'hash'
109
+ # # 'object' and 'hash' are equivalent
110
+ #
111
+ # It's OK to pass multiple types for a field
112
+ #
113
+ # filter 'x', :type => 'bool,number'
114
+ # filter 'x', :type => [ 'string', 'array' ]
115
+ #
116
+ # filter 'x', :type => 'string,null'
117
+ # # a string or null or not set
118
+ #
119
+ # The array and the object/hash types accept a subtype for their values
120
+ # (a hash/object must have string keys anyway).
121
+ #
122
+ # filter 'x', :type => 'array<number>'
123
+ # filter 'x', :type => 'array<string>'
124
+ # filter 'x', :type => 'array<array<string>>'
125
+ #
126
+ # filter 'x', :type => 'array<string,number>'
127
+ # # an array of strings or numbers (both)
128
+ # filter 'x', :type => 'array<string>,array<number>'
129
+ # # an array of strings or an array of numbers
130
+ #
131
+ # === 'match' and 'smatch'
132
+ #
133
+ # 'match' will check if a field, when turned into a string, matches
134
+ # a given regular expression.
135
+ #
136
+ # filter 'x', :match => '1'
137
+ # # will match "11", 1, 1.0, "212"
138
+ #
139
+ # 'smatch' works the same but only accepts fields that are strings.
140
+ #
141
+ # filter 'x', :smatch => '^user_'
142
+ # # valid only if x's value is a string that starts with "user_"
143
+ #
144
+ # === 'size' and 'empty'
145
+ #
146
+ # 'size' is valid for values that respond to the #size method (strings
147
+ # hashes and arrays).
148
+ #
149
+ # filter 'x', :size => 4
150
+ # # will be valid of values like [ 1, 2, 3, 4 ], "toto" or
151
+ # # { 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4 }
152
+ #
153
+ # filter 'x', :size => [ 4, 5 ]
154
+ # filter 'x', :size => '4,5'
155
+ # # four to five elements
156
+ #
157
+ # filter 'x', :size => [ 4 ]
158
+ # filter 'x', :size => [ 4, nil ]
159
+ # filter 'x', :size => '4,'
160
+ # # four or more elements
161
+ #
162
+ # filter 'x', :size => [ nil, 4 ]
163
+ # filter 'x', :size => ',4'
164
+ # # four elements or less
165
+ #
166
+ # Similarly, the 'empty' check will evaluate to true (ie not raise an
167
+ # exception) if the value responds to #empty? and is, well, not empty.
168
+ #
169
+ # filter 'x', :empty => true
170
+ #
171
+ # === 'is'
172
+ #
173
+ # Checks if a field holds the given value.
174
+ #
175
+ # filter 'x', :is => true
176
+ # filter 'x', :is => [ 'a', 2, 3 ]
177
+ #
178
+ # === 'in'
179
+ #
180
+ # Checks if a value is in a given set of values.
181
+ #
182
+ # filter 'x', :in => [ 1, 2, 3 ]
183
+ # filter 'x', :in => "john, jeff, jim"
184
+ #
185
+ # === 'has'
186
+ #
187
+ # Checks if an array contains certain values
188
+ #
189
+ # filter 'x', :has => 1
190
+ # filter 'x', :has => "x"
191
+ # filter 'x', :has => [ 1, 7, 12 ]
192
+ # filter 'x', :has => "abraham, bob, charly"
193
+ #
194
+ # Also checks if a hash has certain keys (strings only of course)
195
+ #
196
+ # filter 'x', :has => "x"
197
+ # filter 'x', :has => "abraham, bob, charly"
198
+ #
199
+ # === 'includes'
200
+ #
201
+ # Checks if an array includes a given value. Works with Hash values as well.
202
+ #
203
+ # filter 'x', :includes => 1
204
+ #
205
+ # Whereas 'has' accepts multiple values, 'includes' only accepts one (like
206
+ # Ruby's Array#include?).
207
+ #
208
+ # === 'valid'
209
+ #
210
+ # Sometimes, it's better to immediately say 'true' or 'false'.
211
+ #
212
+ # filter 'x', :valid => 'true'
213
+ # filter 'x', :valid => 'false'
214
+ #
215
+ # Not very useful...
216
+ #
217
+ # In fact, it's meant to be used with the dollar notation
218
+ #
219
+ # filter 'x', :valid => '${other.field}'
220
+ # # will be valid if ${other.field} evaluates to 'true'...
221
+ #
222
+ # === cumulating validations
223
+ #
224
+ # As seen before, type validations can be cumulated.
225
+ #
226
+ # filter 'x', :type => 'bool,number'
227
+ #
228
+ # Validations can be cumulated as well
229
+ #
230
+ # filter 'x', :type => 'array<number>', :has => [ 1, 2 ]
231
+ # # will be valid if the field 'x' holds an array of numbers
232
+ # # and that array has 1 and 2 among its elements
233
+ #
234
+ # === validation errors
235
+ #
236
+ # By defaults a validation error will result in a process error (ie the
237
+ # process instance will have to be manually fixed and resumed, or there
238
+ # is a :on_error somewhere dealing automatically with errors).
239
+ #
240
+ # It's possible to prevent raising an error and simply record the validation
241
+ # errors.
242
+ #
243
+ # filter 'x', :type => 'bool,number', :record => true
244
+ #
245
+ # will enumerate validation errors in the '__validation_errors__' workitem
246
+ # field.
247
+ #
248
+ # filter 'y', :type => 'bool,number', :record => 'verrors'
249
+ #
250
+ # will enumerate validation errors in teh 'verrors' workitem field.
251
+ #
252
+ # To flush the recording field, use :flush => true
253
+ #
254
+ # sequence do
255
+ # filter 'x', :type => 'string', :record => true
256
+ # filter 'y', :type => 'number', :record => true, :flush => true
257
+ # participant 'after'
258
+ # end
259
+ #
260
+ # the participant 'after' will only see the result of the second filter.
261
+ #
262
+ # For complex filters, if the first rule has :record => true, the
263
+ # 'recording' will happen for the whole filter.
264
+ #
265
+ # sequence do
266
+ # filter :in => [
267
+ # { :field => 'x', :type => 'string', :record => true },
268
+ # { :field => 'y', :type => 'number' } ]
269
+ # participant 'after'
270
+ # end
271
+ #
272
+ #
273
+ # == transformations
274
+ #
275
+ # So far, only the validation aspect of filter was shown. They can also be
276
+ # used to transform the workitem.
277
+ #
278
+ # filter 'x', :type => 'string', :or => 'missing'
279
+ # # will replace the value of x by 'missing' if it's not a string
280
+ #
281
+ # filter 'z', :remove => true
282
+ # # will remove the workitem field z
283
+ #
284
+ # filter 'a,b,c', 'set' => '---'
285
+ # # sets the field a, b and c to '---'
286
+ #
287
+ # === 'remove'
288
+ #
289
+ # Removes a field (or a subfield).
290
+ #
291
+ # filter 'z', :remove => true
292
+ #
293
+ # === 'default'
294
+ #
295
+ # If there is no value for a field, sets it
296
+ #
297
+ # filter 'x', 'default' => 0
298
+ # # will set x to 0, if it's not set or its value is nil
299
+ #
300
+ # filter '/^user-.+/', 'default' => 'nemo'
301
+ # # will set any 'user-...' field to 'nemo' if its value is nil
302
+ #
303
+ # === 'or'
304
+ #
305
+ # 'or' combines with a condition. The 'or' value is set if the condition
306
+ # evaluates to false.
307
+ #
308
+ # Using 'or' without a condition makes it equivalent to a 'default'.
309
+ #
310
+ # filter 'x', 'or' => 0
311
+ # # will set x to 0, if it's not set or its value is nil
312
+ #
313
+ # filter 'x', 'type' => 'number', 'or' => 0
314
+ # # if x is not set or is not a number, will set it to 0
315
+ #
316
+ # Multiple conditions are OK
317
+ #
318
+ # filter 'x', 't' => 'array', 'has' => 'cat', 'or' => []
319
+ # # if x is an array and has the 'cat' element, nothing will happen.
320
+ # # Else x will be set to [].
321
+ #
322
+ # === 'and'
323
+ #
324
+ # 'and' is much like 'or', but it triggers if the condition evaluates to true.
325
+ #
326
+ # filter 'x', 'type' => number, 'and' => '*removed*'
327
+ # # if x is a number, it will replace it with '*removed*'
328
+ #
329
+ # === 'set'
330
+ #
331
+ # Like 'remove' removes unconditionally, 'set' sets a field unconditionally.
332
+ #
333
+ # filter 'x', 'set' => 'blue'
334
+ # # sets the field x to 'blue'
335
+ #
336
+ # === copy, merge, migrate / to, from
337
+ #
338
+ # # in : { 'x' => 'y' }
339
+ # filter 'x', 'copy_to' => 'z'
340
+ # # out : { 'x' => 'y', 'z' => 'y' }
341
+ #
342
+ # # in : { 'x' => 'y' }
343
+ # filter 'z', 'copy_from' => 'x'
344
+ # # out : { 'x' => 'y', 'z' => 'y' }
345
+ #
346
+ # # in : { 'x' => 'y' }
347
+ # filter 'z', 'copy_from' => 'x'
348
+ # # out : { 'x' => 'y', 'z' => 'y' }
349
+ #
350
+ # # in : { 'a' => %w[ x y ]})
351
+ # filter '/a\.(.+)/', 'copy_to' => 'b\1'
352
+ # # out : { 'a' => %w[ x y ], 'b0' => 'x', 'b1' => 'y' },
353
+ #
354
+ # # in : { 'a' => %w[ x y ]})
355
+ # filter '/a!(.+)/', 'copy_to' => 'b\1'
356
+ # # out : { 'a' => %w[ x y ], 'b0' => 'x', 'b1' => 'y' },
357
+ # #
358
+ # # '!' is used as a replacement for '\.' in regexes
359
+ #
360
+ # # in : { 'a' => 'b', 'c' => 'd', 'source' => [ 7 ] })
361
+ # filter '/^.$/', 'copy_from' => 'source.0'
362
+ # # out : { 'a' => 7, 'c' => 7, 'source' => [ 7 ] },
363
+ #
364
+ # ...
365
+ #
366
+ # 'copy_to' and 'copy_from' copy whole fields. 'move_to' and 'move_from'
367
+ # move fields.
368
+ #
369
+ # 'merge_to' and 'merge_from' merge hashes (or add values to
370
+ # arrays), 'push_to' and 'push_from' are aliases for 'merge_to' and
371
+ # 'merge_from' respectively.
372
+ #
373
+ # 'migrate_to' and 'migrate_from' act like 'merge_to' and 'merge_from' but
374
+ # delete the merge source afterwards (like 'move').
375
+ #
376
+ # All those hash/array filter operations understand the '.' field, meaning
377
+ # the hash being filtered itself.
378
+ #
379
+ # # in : { 'x' => { 'a' => 1, 'b' => 2 } })
380
+ # filter 'x', 'merge_to' => '.'
381
+ # # out : { 'x' => { 'a' => 1, 'b' => 2 }, 'a' => 1, 'b' => 2 },
382
+ #
383
+ # === access to 'previous versions' with ~ and ~~
384
+ #
385
+ # Before a filter is applied, a copy of the hash to filter is placed under
386
+ # the '~' key in the hash itself.
387
+ #
388
+ # this filter will at first set the field x to 0, and then reset it to its
389
+ # original value :
390
+ #
391
+ # filter :in => [
392
+ # { :field => 'x', :set => 0 },
393
+ # { :field => 'x', :copy_from => '~.x' }
394
+ # ]
395
+ #
396
+ # For the 'filter' expression, '~~' contains the same thing as '~', but
397
+ # for the :filter attribute, it contains the hash (workitem fields) as
398
+ # it was when the expression with the :filter attribute got reached (applied).
399
+ #
400
+ # === 'restore' and 'restore_from'
401
+ #
402
+ # Since these two filter operations leverage '~~', they're not very useful
403
+ # for the 'filter' expression. But they make lots of sense for the :filter
404
+ # attribute.
405
+ #
406
+ # # in : { 'x' => 'a', 'y' => 'a' },
407
+ # filter :in => [
408
+ # { 'field' => 'x', 'set' => 'X' },
409
+ # { 'field' => 'y', 'set' => 'Y' },
410
+ # { 'field' => '/^.$/', 'restore' => true } ]
411
+ # # out : { 'x' => 'a', 'y' => 'a' },
412
+ #
413
+ # # in : { 'x' => 'a', 'y' => 'a' },
414
+ # filter :in => [
415
+ # { 'field' => 'A', 'set' => {} },
416
+ # { 'field' => '.', 'merge_to' => 'A' },
417
+ # { 'field' => 'x', 'set' => 'X' },
418
+ # { 'field' => 'y', 'set' => 'Y' },
419
+ # { 'field' => '/^[a-z]$/', 'restore_from' => 'A' },
420
+ # { 'field' => 'A', 'delete' => true } ]
421
+ # # out : { 'x' => 'a', 'y' => 'a' })
422
+ #
423
+ #
424
+ # == short forms
425
+ #
426
+ # Could help make filters a bit more compact.
427
+ #
428
+ # * 'size', 'sz'
429
+ # * 'empty', 'e'
430
+ # * 'in', 'i'
431
+ # * 'has', 'h'
432
+ # * 'type', 't'
433
+ # * 'match', 'm'
434
+ # * 'smatch', 'sm'
435
+ # * 'valid', 'v'
436
+ #
437
+ # * 'remove', 'rm', 'delete', 'del'
438
+ # * 'set', 's'
439
+ # * 'copy_to', 'cp_to'
440
+ # * 'move_to', 'mv_to'
441
+ # * 'merge_to', 'mg_to'
442
+ # * 'migrate_to', 'mi_to'
443
+ # * 'restore', 'restore_from', 'rs'
444
+ #
445
+ #
446
+ # == top-level 'or'
447
+ #
448
+ # Filters may be used to transform hashes or to validate them. In both cases
449
+ # the filters seen until now were like chained by a big AND.
450
+ #
451
+ # It's OK to write
452
+ #
453
+ # filter :in => [
454
+ # { 'field' => 'server_href', 'smatch' => '^https?:\/\/' },
455
+ # 'or',
456
+ # { 'field' => 'nickname', 'type' => 'string' } ]
457
+ #
458
+ # Granted, this is mostly for validation purposes, but it also works
459
+ # with transformations (as soon as an 'or' child succeeds it's returned
460
+ # and the other children are not evaluated).
461
+ #
462
+ #
463
+ # == compared to the :filter attribute
464
+ #
465
+ # The :filter attribute accepts participant names, but for this filter
466
+ # expression, it makes no sense accepting partipants... Simply invoke
467
+ # the participant as usual.
468
+ #
469
+ # The 'restore' operation makes lots of sense for the :filter attribute
470
+ # though.
471
+ #
472
+ #
473
+ # == filtering with rules in a block
474
+ #
475
+ # This filter
476
+ #
477
+ # filter :in => [
478
+ # { :field => 'x', :type => 'string' },
479
+ # { :field => 'y', :type => 'number' }
480
+ # ]
481
+ #
482
+ # can be rewritten as
483
+ #
484
+ # filter do
485
+ # field 'x', :type => 'string'
486
+ # field 'y', :type => 'number'
487
+ # end
488
+ #
489
+ # The field names can be passed directly as head of each rule :
490
+ #
491
+ # filter do
492
+ # x :type => 'string'
493
+ # y :type => 'number'
494
+ # end
495
+ #
496
+ class FilterExpression < FlowExpression
497
+
498
+ names :filter
499
+
500
+ def apply
501
+
502
+ filter =
503
+ referenced_filter || complete_filter || one_line_filter || block_filter
504
+
505
+ record = filter.first.delete('record') rescue nil
506
+ flush = filter.first.delete('flush') rescue nil
507
+
508
+ record = '__validation_errors__' if record == true
509
+
510
+ opts = {
511
+ :double_tilde => parent_id ?
512
+ (parent.h.applied_workitem['fields'] rescue nil) : nil,
513
+ :no_raise => record
514
+ }
515
+ #
516
+ # parent_fields are placed in the ^^ available to the filter
517
+
518
+ fields = Ruote.filter(filter, h.applied_workitem['fields'], opts)
519
+
520
+ if record and fields.is_a?(Array)
521
+ #
522
+ # validation failed, :record requested, list deviations in
523
+ # the given field name
524
+
525
+ (flush ?
526
+ h.applied_workitem['fields'][record] = [] :
527
+ h.applied_workitem['fields'][record] ||= []
528
+ ).concat(fields)
529
+
530
+ reply_to_parent(h.applied_workitem)
531
+
532
+ else
533
+ #
534
+ # filtering successful
535
+
536
+ reply_to_parent(h.applied_workitem.merge('fields' => fields))
537
+ end
538
+ end
539
+
540
+ def reply(workitem)
541
+
542
+ # never called
543
+ end
544
+
545
+ protected
546
+
547
+ # Filter is passed in a block (which is not evaluted as a ruote branch
548
+ # but immediately translated into a filter.
549
+ #
550
+ # pdef = Ruote.process_definition do
551
+ # filter do
552
+ # field 'x', :type => 'string'
553
+ # field 'y', :type => 'number'
554
+ # end
555
+ # end
556
+ #
557
+ # Note : 'or' is OK
558
+ #
559
+ # pdef = Ruote.process_definition do
560
+ # filter do
561
+ # field 'x', :type => 'string'
562
+ # _or
563
+ # field 'y', :type => 'number'
564
+ # end
565
+ # end
566
+ #
567
+ def block_filter
568
+
569
+ return nil if tree.last.empty?
570
+
571
+ tree.last.collect { |line|
572
+
573
+ next 'or' if line.first == 'or'
574
+
575
+ rule = line[1].inject({}) { |h, (k, v)|
576
+ if v == nil
577
+ h['field'] = k
578
+ else
579
+ h[k] = v
580
+ end
581
+ h
582
+ }
583
+
584
+ rule['field'] ||= line.first
585
+
586
+ rule
587
+ }
588
+ end
589
+
590
+ # Filter is somewhere else (process variable or workitem field)
591
+ #
592
+ def referenced_filter
593
+
594
+ prefix, key = attribute_text.split(':')
595
+
596
+ return nil unless %w[ v var variable f field ].include?(prefix)
597
+
598
+ filter = prefix.match(/^v/) ?
599
+ lookup_variable(key) : Ruote.lookup(h.applied_workitem['fields'], key)
600
+
601
+ if filter.is_a?(Hash) and i = filter['in']
602
+ return i
603
+ end
604
+
605
+ filter
606
+ end
607
+
608
+ # Filter is passed with an :in attribute.
609
+ #
610
+ # Ruote.process_definition do
611
+ # filter :in => [
612
+ # { :field => 'x', :type => 'string' },
613
+ # { :field => 'y', :type => 'number' }
614
+ # ]
615
+ # end
616
+ #
617
+ def complete_filter
618
+
619
+ return nil if attribute_text != ''
620
+
621
+ attribute(:in)
622
+ end
623
+
624
+ # Filter thanks to the attributes of the expression.
625
+ #
626
+ # pdef = Ruote.process_definition do
627
+ # filter 'x', :type => 'string', :record => true
628
+ # filter 'y', :type => 'number', :record => true
629
+ # end
630
+ #
631
+ def one_line_filter
632
+
633
+ if (attributes.keys - COMMON_ATT_KEYS - %w[ ref original_ref ]).empty?
634
+ return nil
635
+ end
636
+
637
+ [ attributes.inject({}) { |h, (k, v)|
638
+ if v.nil?
639
+ h['field'] = k
640
+ else
641
+ h[k] = v
642
+ end
643
+ h
644
+ } ]
645
+ end
646
+ end
647
+ end
648
+