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.
- data/CHANGELOG.txt +290 -0
- data/CREDITS.txt +99 -0
- data/LICENSE.txt +21 -0
- data/README.rdoc +88 -0
- data/Rakefile +108 -0
- data/TODO.txt +488 -0
- data/lib/ruote.rb +7 -0
- data/lib/ruote/context.rb +194 -0
- data/lib/ruote/engine.rb +1062 -0
- data/lib/ruote/engine/process_error.rb +122 -0
- data/lib/ruote/engine/process_status.rb +448 -0
- data/lib/ruote/exp/command.rb +87 -0
- data/lib/ruote/exp/commanded.rb +69 -0
- data/lib/ruote/exp/condition.rb +227 -0
- data/lib/ruote/exp/fe_add_branches.rb +138 -0
- data/lib/ruote/exp/fe_apply.rb +154 -0
- data/lib/ruote/exp/fe_cancel_process.rb +78 -0
- data/lib/ruote/exp/fe_command.rb +156 -0
- data/lib/ruote/exp/fe_concurrence.rb +321 -0
- data/lib/ruote/exp/fe_concurrent_iterator.rb +219 -0
- data/lib/ruote/exp/fe_cron.rb +141 -0
- data/lib/ruote/exp/fe_cursor.rb +324 -0
- data/lib/ruote/exp/fe_define.rb +112 -0
- data/lib/ruote/exp/fe_echo.rb +60 -0
- data/lib/ruote/exp/fe_equals.rb +115 -0
- data/lib/ruote/exp/fe_error.rb +82 -0
- data/lib/ruote/exp/fe_filter.rb +648 -0
- data/lib/ruote/exp/fe_forget.rb +88 -0
- data/lib/ruote/exp/fe_given.rb +154 -0
- data/lib/ruote/exp/fe_if.rb +127 -0
- data/lib/ruote/exp/fe_inc.rb +205 -0
- data/lib/ruote/exp/fe_iterator.rb +234 -0
- data/lib/ruote/exp/fe_let.rb +75 -0
- data/lib/ruote/exp/fe_listen.rb +304 -0
- data/lib/ruote/exp/fe_lose.rb +110 -0
- data/lib/ruote/exp/fe_noop.rb +45 -0
- data/lib/ruote/exp/fe_once.rb +215 -0
- data/lib/ruote/exp/fe_participant.rb +287 -0
- data/lib/ruote/exp/fe_read.rb +69 -0
- data/lib/ruote/exp/fe_redo.rb +82 -0
- data/lib/ruote/exp/fe_ref.rb +152 -0
- data/lib/ruote/exp/fe_registerp.rb +110 -0
- data/lib/ruote/exp/fe_reserve.rb +126 -0
- data/lib/ruote/exp/fe_restore.rb +102 -0
- data/lib/ruote/exp/fe_save.rb +72 -0
- data/lib/ruote/exp/fe_sequence.rb +59 -0
- data/lib/ruote/exp/fe_set.rb +154 -0
- data/lib/ruote/exp/fe_subprocess.rb +211 -0
- data/lib/ruote/exp/fe_that.rb +92 -0
- data/lib/ruote/exp/fe_undo.rb +67 -0
- data/lib/ruote/exp/fe_unregisterp.rb +69 -0
- data/lib/ruote/exp/fe_wait.rb +95 -0
- data/lib/ruote/exp/flowexpression.rb +886 -0
- data/lib/ruote/exp/iterator.rb +81 -0
- data/lib/ruote/exp/merge.rb +118 -0
- data/lib/ruote/exp/ro_attributes.rb +212 -0
- data/lib/ruote/exp/ro_filters.rb +136 -0
- data/lib/ruote/exp/ro_persist.rb +154 -0
- data/lib/ruote/exp/ro_variables.rb +189 -0
- data/lib/ruote/exp/ro_vf.rb +68 -0
- data/lib/ruote/fei.rb +260 -0
- data/lib/ruote/id/mnemo_wfid_generator.rb +43 -0
- data/lib/ruote/id/wfid_generator.rb +81 -0
- data/lib/ruote/log/default_history.rb +122 -0
- data/lib/ruote/log/pretty.rb +176 -0
- data/lib/ruote/log/storage_history.rb +159 -0
- data/lib/ruote/log/test_logger.rb +208 -0
- data/lib/ruote/log/wait_logger.rb +64 -0
- data/lib/ruote/part/block_participant.rb +137 -0
- data/lib/ruote/part/code_participant.rb +81 -0
- data/lib/ruote/part/engine_participant.rb +189 -0
- data/lib/ruote/part/local_participant.rb +138 -0
- data/lib/ruote/part/no_op_participant.rb +60 -0
- data/lib/ruote/part/null_participant.rb +54 -0
- data/lib/ruote/part/rev_participant.rb +169 -0
- data/lib/ruote/part/smtp_participant.rb +116 -0
- data/lib/ruote/part/storage_participant.rb +392 -0
- data/lib/ruote/part/template.rb +84 -0
- data/lib/ruote/participant.rb +7 -0
- data/lib/ruote/reader.rb +278 -0
- data/lib/ruote/reader/json.rb +49 -0
- data/lib/ruote/reader/radial.rb +290 -0
- data/lib/ruote/reader/ruby_dsl.rb +186 -0
- data/lib/ruote/reader/xml.rb +99 -0
- data/lib/ruote/receiver/base.rb +212 -0
- data/lib/ruote/storage/base.rb +364 -0
- data/lib/ruote/storage/composite_storage.rb +121 -0
- data/lib/ruote/storage/fs_storage.rb +139 -0
- data/lib/ruote/storage/hash_storage.rb +211 -0
- data/lib/ruote/svc/dispatch_pool.rb +158 -0
- data/lib/ruote/svc/dollar_sub.rb +298 -0
- data/lib/ruote/svc/error_handler.rb +138 -0
- data/lib/ruote/svc/expression_map.rb +97 -0
- data/lib/ruote/svc/participant_list.rb +397 -0
- data/lib/ruote/svc/tracker.rb +172 -0
- data/lib/ruote/svc/treechecker.rb +141 -0
- data/lib/ruote/tree_dot.rb +85 -0
- data/lib/ruote/util/filter.rb +525 -0
- data/lib/ruote/util/hashdot.rb +79 -0
- data/lib/ruote/util/look.rb +128 -0
- data/lib/ruote/util/lookup.rb +127 -0
- data/lib/ruote/util/misc.rb +167 -0
- data/lib/ruote/util/ometa.rb +71 -0
- data/lib/ruote/util/serializer.rb +103 -0
- data/lib/ruote/util/subprocess.rb +88 -0
- data/lib/ruote/util/time.rb +100 -0
- data/lib/ruote/util/tree.rb +58 -0
- data/lib/ruote/version.rb +29 -0
- data/lib/ruote/worker.rb +386 -0
- data/lib/ruote/workitem.rb +394 -0
- data/phil.txt +14 -0
- data/ruote.gemspec +44 -0
- data/test/bm/ci.rb +55 -0
- data/test/bm/ici.rb +71 -0
- data/test/bm/juuman.rb +54 -0
- data/test/bm/launch_bench.rb +37 -0
- data/test/bm/load_26c.rb +97 -0
- data/test/bm/mega.rb +64 -0
- data/test/bm/seq_thousand.rb +31 -0
- data/test/bm/t.rb +35 -0
- data/test/functional/base.rb +247 -0
- data/test/functional/concurrent_base.rb +98 -0
- data/test/functional/crunner.rb +31 -0
- data/test/functional/ct_0_concurrence.rb +65 -0
- data/test/functional/ct_1_iterator.rb +67 -0
- data/test/functional/ct_2_cancel.rb +81 -0
- data/test/functional/eft_0_process_definition.rb +65 -0
- data/test/functional/eft_10_cancel_process.rb +46 -0
- data/test/functional/eft_11_wait.rb +109 -0
- data/test/functional/eft_12_listen.rb +500 -0
- data/test/functional/eft_13_iterator.rb +342 -0
- data/test/functional/eft_14_cursor.rb +456 -0
- data/test/functional/eft_15_loop.rb +69 -0
- data/test/functional/eft_16_if.rb +183 -0
- data/test/functional/eft_17_equals.rb +55 -0
- data/test/functional/eft_18_concurrent_iterator.rb +410 -0
- data/test/functional/eft_19_reserve.rb +136 -0
- data/test/functional/eft_1_echo.rb +68 -0
- data/test/functional/eft_20_save.rb +116 -0
- data/test/functional/eft_21_restore.rb +61 -0
- data/test/functional/eft_22_noop.rb +28 -0
- data/test/functional/eft_23_apply.rb +168 -0
- data/test/functional/eft_24_add_branches.rb +98 -0
- data/test/functional/eft_25_command.rb +28 -0
- data/test/functional/eft_26_error.rb +77 -0
- data/test/functional/eft_27_inc.rb +280 -0
- data/test/functional/eft_28_once.rb +135 -0
- data/test/functional/eft_29_cron.rb +64 -0
- data/test/functional/eft_2_sequence.rb +58 -0
- data/test/functional/eft_30_ref.rb +155 -0
- data/test/functional/eft_31_registerp.rb +130 -0
- data/test/functional/eft_32_lose.rb +93 -0
- data/test/functional/eft_33_let.rb +31 -0
- data/test/functional/eft_34_given.rb +123 -0
- data/test/functional/eft_35_filter.rb +375 -0
- data/test/functional/eft_36_read.rb +95 -0
- data/test/functional/eft_3_participant.rb +149 -0
- data/test/functional/eft_4_set.rb +296 -0
- data/test/functional/eft_5_subprocess.rb +163 -0
- data/test/functional/eft_6_concurrence.rb +304 -0
- data/test/functional/eft_7_forget.rb +61 -0
- data/test/functional/eft_8_undo.rb +114 -0
- data/test/functional/eft_9_redo.rb +138 -0
- data/test/functional/ft_0_worker.rb +65 -0
- data/test/functional/ft_10_dollar.rb +304 -0
- data/test/functional/ft_11_recursion.rb +109 -0
- data/test/functional/ft_12_launchitem.rb +43 -0
- data/test/functional/ft_13_variables.rb +151 -0
- data/test/functional/ft_14_re_apply.rb +324 -0
- data/test/functional/ft_15_timeout.rb +226 -0
- data/test/functional/ft_16_participant_params.rb +98 -0
- data/test/functional/ft_17_conditional.rb +102 -0
- data/test/functional/ft_18_kill.rb +138 -0
- data/test/functional/ft_19_participant_code.rb +67 -0
- data/test/functional/ft_1_process_status.rb +796 -0
- data/test/functional/ft_20_storage_participant.rb +543 -0
- data/test/functional/ft_21_forget.rb +153 -0
- data/test/functional/ft_22_process_definitions.rb +90 -0
- data/test/functional/ft_23_load_defs.rb +79 -0
- data/test/functional/ft_24_block_participant.rb +235 -0
- data/test/functional/ft_25_receiver.rb +207 -0
- data/test/functional/ft_26_participant_rtimeout.rb +179 -0
- data/test/functional/ft_27_var_indirection.rb +128 -0
- data/test/functional/ft_28_null_noop_participants.rb +51 -0
- data/test/functional/ft_29_part_template.rb +60 -0
- data/test/functional/ft_2_errors.rb +380 -0
- data/test/functional/ft_30_smtp_participant.rb +122 -0
- data/test/functional/ft_31_part_blocking.rb +72 -0
- data/test/functional/ft_33_participant_subprocess_priority.rb +32 -0
- data/test/functional/ft_34_cursor_rewind.rb +101 -0
- data/test/functional/ft_35_add_service.rb +56 -0
- data/test/functional/ft_36_storage_history.rb +150 -0
- data/test/functional/ft_37_default_history.rb +109 -0
- data/test/functional/ft_38_participant_more.rb +193 -0
- data/test/functional/ft_39_wait_for.rb +136 -0
- data/test/functional/ft_3_participant_registration.rb +574 -0
- data/test/functional/ft_40_wait_logger.rb +62 -0
- data/test/functional/ft_41_participants.rb +91 -0
- data/test/functional/ft_42_storage_copy.rb +71 -0
- data/test/functional/ft_43_participant_on_reply.rb +87 -0
- data/test/functional/ft_44_var_participant.rb +35 -0
- data/test/functional/ft_45_participant_accept.rb +64 -0
- data/test/functional/ft_46_launch_single.rb +83 -0
- data/test/functional/ft_47_wfid_generator.rb +54 -0
- data/test/functional/ft_48_lose.rb +112 -0
- data/test/functional/ft_49_engine_on_error.rb +201 -0
- data/test/functional/ft_4_cancel.rb +132 -0
- data/test/functional/ft_50_engine_config.rb +22 -0
- data/test/functional/ft_51_misc.rb +67 -0
- data/test/functional/ft_52_case.rb +134 -0
- data/test/functional/ft_53_engine_on_terminate.rb +95 -0
- data/test/functional/ft_54_patterns.rb +104 -0
- data/test/functional/ft_55_engine_participant.rb +303 -0
- data/test/functional/ft_56_filter_attribute.rb +259 -0
- data/test/functional/ft_57_rev_participant.rb +252 -0
- data/test/functional/ft_58_workitem.rb +69 -0
- data/test/functional/ft_59_pause.rb +343 -0
- data/test/functional/ft_5_on_error.rb +384 -0
- data/test/functional/ft_60_code_participant.rb +45 -0
- data/test/functional/ft_61_trailing_fields.rb +34 -0
- data/test/functional/ft_62_exp_name_and_dollar_substitution.rb +35 -0
- data/test/functional/ft_6_on_cancel.rb +221 -0
- data/test/functional/ft_7_tags.rb +177 -0
- data/test/functional/ft_8_participant_consumption.rb +124 -0
- data/test/functional/ft_9_subprocesses.rb +146 -0
- data/test/functional/restart_base.rb +34 -0
- data/test/functional/rt_0_wait.rb +55 -0
- data/test/functional/rt_1_listen.rb +56 -0
- data/test/functional/rt_2_errors.rb +56 -0
- data/test/functional/rt_3_once.rb +70 -0
- data/test/functional/rt_4_cron.rb +64 -0
- data/test/functional/rt_5_timeout.rb +60 -0
- data/test/functional/rtest.rb +8 -0
- data/test/functional/storage_helper.rb +93 -0
- data/test/functional/test.rb +44 -0
- data/test/functional/vertical.rb +46 -0
- data/test/path_helper.rb +15 -0
- data/test/test.rb +13 -0
- data/test/test_helper.rb +28 -0
- data/test/unit/storage.rb +428 -0
- data/test/unit/storages.rb +37 -0
- data/test/unit/test.rb +28 -0
- data/test/unit/ut_0_ruby_reader.rb +223 -0
- data/test/unit/ut_11_lookup.rb +122 -0
- data/test/unit/ut_13_serializer.rb +65 -0
- data/test/unit/ut_14_is_uri.rb +28 -0
- data/test/unit/ut_15_util.rb +57 -0
- data/test/unit/ut_16_reader.rb +225 -0
- data/test/unit/ut_18_engine.rb +47 -0
- data/test/unit/ut_19_part_template.rb +86 -0
- data/test/unit/ut_1_fei.rb +165 -0
- data/test/unit/ut_20_composite_storage.rb +74 -0
- data/test/unit/ut_21_svc_participant_list.rb +46 -0
- data/test/unit/ut_22_filter.rb +1094 -0
- data/test/unit/ut_23_svc_tracker.rb +48 -0
- data/test/unit/ut_24_radial_reader.rb +332 -0
- data/test/unit/ut_25_merge.rb +113 -0
- data/test/unit/ut_3_wait_logger.rb +39 -0
- data/test/unit/ut_4_expmap.rb +20 -0
- data/test/unit/ut_5_tree.rb +54 -0
- data/test/unit/ut_6_condition.rb +303 -0
- data/test/unit/ut_7_workitem.rb +99 -0
- data/test/unit/ut_8_tree_to_dot.rb +72 -0
- data/test/unit/ut_9_xml_reader.rb +61 -0
- metadata +504 -0
data/lib/ruote/worker.rb
ADDED
@@ -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
|
+
|