ruote 2.1.4 → 2.1.5
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.txt +26 -6
- data/CREDITS.txt +1 -1
- data/TODO.txt +20 -9
- data/examples/ruote_quickstart.rb +3 -2
- data/lib/ruote.rb +1 -0
- data/lib/ruote/context.rb +5 -0
- data/lib/ruote/engine.rb +5 -12
- data/lib/ruote/engine/process_error.rb +13 -0
- data/lib/ruote/engine/process_status.rb +18 -1
- data/lib/ruote/exp/condition.rb +0 -5
- data/lib/ruote/exp/fe_participant.rb +12 -6
- data/lib/ruote/exp/fe_subprocess.rb +4 -37
- data/lib/ruote/exp/fe_when.rb +1 -1
- data/lib/ruote/exp/flowexpression.rb +59 -28
- data/lib/ruote/exp/ro_persist.rb +4 -7
- data/lib/ruote/exp/ro_variables.rb +6 -2
- data/lib/ruote/fei.rb +9 -0
- data/lib/ruote/log/wait_logger.rb +1 -0
- data/lib/ruote/part/engine_participant.rb +185 -0
- data/lib/ruote/part/storage_participant.rb +69 -13
- data/lib/ruote/participant.rb +1 -0
- data/lib/ruote/storage/base.rb +1 -1
- data/lib/ruote/subprocess.rb +68 -0
- data/lib/ruote/util/dollar.rb +23 -5
- data/lib/ruote/worker.rb +30 -9
- data/ruote.gemspec +5 -2
- data/test/functional/eft_27_inc.rb +6 -6
- data/test/functional/ft_10_dollar.rb +84 -1
- data/test/functional/ft_1_process_status.rb +43 -0
- data/test/functional/ft_20_storage_participant.rb +124 -5
- data/test/functional/ft_2_errors.rb +11 -4
- data/test/functional/ft_37_engine_participant.rb +295 -0
- metadata +5 -2
data/lib/ruote/exp/ro_persist.rb
CHANGED
@@ -69,13 +69,10 @@ module Ruote::Exp
|
|
69
69
|
|
70
70
|
return r if r
|
71
71
|
|
72
|
-
if h.has_error
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
@context.storage.delete(err) if err
|
78
|
-
end
|
72
|
+
#if h.has_error
|
73
|
+
err = @context.storage.get('errors', "err_#{Ruote.to_storage_id(h.fei)}")
|
74
|
+
@context.storage.delete(err) if err
|
75
|
+
#end
|
79
76
|
|
80
77
|
nil
|
81
78
|
end
|
@@ -64,11 +64,15 @@ module Ruote::Exp
|
|
64
64
|
|
65
65
|
if h.variables
|
66
66
|
|
67
|
-
val = h.variables[var]
|
67
|
+
#val = h.variables[var]
|
68
|
+
val = Ruote.lookup(h.variables, var)
|
69
|
+
|
68
70
|
return val if val != nil
|
69
71
|
end
|
70
72
|
|
71
|
-
if h.parent_id
|
73
|
+
if h.parent_id && h.parent_id['engine_id'] == @context.engine_id
|
74
|
+
#
|
75
|
+
# do not lookup variables in a remote engine ...
|
72
76
|
|
73
77
|
return parent.lookup_variable(var, prefix)
|
74
78
|
end
|
data/lib/ruote/fei.rb
CHANGED
@@ -75,6 +75,15 @@ module Ruote
|
|
75
75
|
"#{hfei['expid']}!#{hfei['sub_wfid']}!#{hfei['wfid']}"
|
76
76
|
end
|
77
77
|
|
78
|
+
def self.from_id (s, engine_id='engine')
|
79
|
+
|
80
|
+
ss = s.split('!')
|
81
|
+
|
82
|
+
FlowExpressionId.new(
|
83
|
+
'engine_id' => engine_id,
|
84
|
+
'expid' => ss[0], 'sub_wfid' => ss[1], 'wfid' => ss[2])
|
85
|
+
end
|
86
|
+
|
78
87
|
# Returns the last number in the expid. For instance, if the expid is
|
79
88
|
# '0_5_7', the child_id will be '7'.
|
80
89
|
#
|
@@ -0,0 +1,185 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2005-2010, 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 Singapore.
|
23
|
+
#++
|
24
|
+
|
25
|
+
require 'ruote/subprocess'
|
26
|
+
require 'ruote/part/local_participant'
|
27
|
+
|
28
|
+
|
29
|
+
module Ruote
|
30
|
+
|
31
|
+
#
|
32
|
+
# A participant for pushing the execution of [segments of] processes to
|
33
|
+
# other engines.
|
34
|
+
#
|
35
|
+
# It works by giving the participant the connection information to the storage
|
36
|
+
# of the other engine.
|
37
|
+
#
|
38
|
+
# For instance :
|
39
|
+
#
|
40
|
+
# engine0 =
|
41
|
+
# Ruote::Engine.new(
|
42
|
+
# Ruote::Worker.new(
|
43
|
+
# Ruote::FsStorage.new('work0', 'engine_id' => 'engine0')))
|
44
|
+
# engine1 =
|
45
|
+
# Ruote::Engine.new(
|
46
|
+
# Ruote::Worker.new(
|
47
|
+
# Ruote::FsStorage.new('work1', 'engine_id' => 'engine1')))
|
48
|
+
#
|
49
|
+
# engine0.register_participant('engine1',
|
50
|
+
# Ruote::EngineParticipant,
|
51
|
+
# 'storage_class' => Ruote::FsStorage,
|
52
|
+
# 'storage_path' => 'ruote/storage/fs_storage',
|
53
|
+
# 'storage_args' => 'work1')
|
54
|
+
# engine1.register_participant('engine0',
|
55
|
+
# Ruote::EngineParticipant,
|
56
|
+
# 'storage_class' => Ruote::FsStorage,
|
57
|
+
# 'storage_path' => 'ruote/storage/fs_storage',
|
58
|
+
# 'storage_args' => 'work0')
|
59
|
+
#
|
60
|
+
# In this example, two engines are created (note that their 'engine_id' is
|
61
|
+
# explicitely set (else it would default to 'engine')). Each engine is then
|
62
|
+
# registered as participant in the other engine. The registration parameters
|
63
|
+
# detail the class and the arguments to the storage of the target engine.
|
64
|
+
#
|
65
|
+
# This example is a bit dry / flat. A real world example would perhaps detail
|
66
|
+
# a 'master' engine connected to 'departemental' engines, something more
|
67
|
+
# hierarchical.
|
68
|
+
#
|
69
|
+
# The example also binds reciprocally engines. If the delegated processes
|
70
|
+
# are always 'forgotten', one could imagine not binding the source engine
|
71
|
+
# as a participant in the target engine (not need to answer back).
|
72
|
+
#
|
73
|
+
# There are then two variants for calling a subprocess
|
74
|
+
#
|
75
|
+
# subprocess :ref => 'subprocess_name', :engine => 'engine1'
|
76
|
+
# # or
|
77
|
+
# participant :ref => 'engine1', :pdef => 'subprocess_name'
|
78
|
+
#
|
79
|
+
# It's OK to go for the shorter versions :
|
80
|
+
#
|
81
|
+
# subprocess_name :engine => 'engine1'
|
82
|
+
# # or
|
83
|
+
# participant 'engine1', :pdef => 'subprocess_name'
|
84
|
+
# engine1 :pdef => 'subprocess_name'
|
85
|
+
#
|
86
|
+
# The subprocess is defined in the current process, or it's given via its
|
87
|
+
# URL. The third variant is a subprocess bound as an engine variable.
|
88
|
+
#
|
89
|
+
# engine.variables['variant_3'] = Ruote.process_definition do
|
90
|
+
# participant 'hello_world_3'
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
# pdef = Ruote.process_definition do
|
94
|
+
# sequence do
|
95
|
+
# engine1 :pdef => 'variant_1'
|
96
|
+
# engine1 :pdef => 'http://pdefs.example.com/variant_2.rb'
|
97
|
+
# engine1 :pdef => 'variant_3'
|
98
|
+
# end
|
99
|
+
# define 'variant_1' do
|
100
|
+
# participant 'hello_world_1'
|
101
|
+
# end
|
102
|
+
# end
|
103
|
+
#
|
104
|
+
class EngineParticipant
|
105
|
+
|
106
|
+
include LocalParticipant
|
107
|
+
|
108
|
+
def initialize (opts=nil)
|
109
|
+
|
110
|
+
if pa = opts['storage_path']
|
111
|
+
require pa
|
112
|
+
end
|
113
|
+
|
114
|
+
kl = opts['storage_class']
|
115
|
+
|
116
|
+
raise(ArgumentError.new("missing 'storage_class' parameter")) unless kl
|
117
|
+
|
118
|
+
@storage = Ruote.constantize(kl).new(opts['storage_args'])
|
119
|
+
end
|
120
|
+
|
121
|
+
def consume (workitem)
|
122
|
+
|
123
|
+
wi = workitem.to_h
|
124
|
+
fexp = Ruote::Exp::FlowExpression.fetch(@context, wi['fei'])
|
125
|
+
params = wi['fields'].delete('params')
|
126
|
+
|
127
|
+
forget = (fexp.attribute(:forget).to_s == 'true')
|
128
|
+
|
129
|
+
@storage.put_msg(
|
130
|
+
'launch',
|
131
|
+
'wfid' => wi['fei']['wfid'],
|
132
|
+
'sub_wfid' => fexp.get_next_sub_wfid,
|
133
|
+
'parent_id' => forget ? nil : wi['fei'],
|
134
|
+
'tree' => determine_tree(fexp, params),
|
135
|
+
'workitem' => wi,
|
136
|
+
'variables' => fexp.compile_variables)
|
137
|
+
|
138
|
+
fexp.unpersist if forget
|
139
|
+
#
|
140
|
+
# special behaviour here in case of :forget => true :
|
141
|
+
# parent_id of remote expression is set to nil and local expression
|
142
|
+
# is unpersisted immediately
|
143
|
+
end
|
144
|
+
|
145
|
+
def cancel (fei, flavour)
|
146
|
+
|
147
|
+
exps = @storage.get_many('expressions', /^0![^!]+!#{fei.wfid}$/)
|
148
|
+
|
149
|
+
return true if exps.size < 1
|
150
|
+
# participant expression will reply to its parent
|
151
|
+
|
152
|
+
@storage.put_msg(
|
153
|
+
'cancel',
|
154
|
+
'fei' => exps.first['fei'],
|
155
|
+
'flavour' => flavour)
|
156
|
+
|
157
|
+
false
|
158
|
+
# participant expression will NOT reply to its parent
|
159
|
+
end
|
160
|
+
|
161
|
+
def reply (fei, workitem)
|
162
|
+
|
163
|
+
@storage.put_msg(
|
164
|
+
'reply',
|
165
|
+
'fei' => fei,
|
166
|
+
'workitem' => workitem)
|
167
|
+
end
|
168
|
+
|
169
|
+
protected
|
170
|
+
|
171
|
+
def determine_tree (fexp, params)
|
172
|
+
|
173
|
+
pdef = params['def'] || params['pdef'] || params['tree']
|
174
|
+
|
175
|
+
tree = Ruote.lookup_subprocess(fexp, pdef)
|
176
|
+
|
177
|
+
raise(
|
178
|
+
"couldn't find process definition behind '#{pdef}'"
|
179
|
+
) unless tree
|
180
|
+
|
181
|
+
tree.last
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
@@ -27,6 +27,12 @@ require 'ruote/part/local_participant'
|
|
27
27
|
|
28
28
|
module Ruote
|
29
29
|
|
30
|
+
#
|
31
|
+
# A participant that stores the workitem in the same storage used by the
|
32
|
+
# engine and the worker(s).
|
33
|
+
#
|
34
|
+
# Does not thread by default.
|
35
|
+
#
|
30
36
|
class StorageParticipant
|
31
37
|
|
32
38
|
include LocalParticipant
|
@@ -34,7 +40,15 @@ module Ruote
|
|
34
40
|
|
35
41
|
attr_accessor :context
|
36
42
|
|
37
|
-
def initialize (
|
43
|
+
def initialize (engine_or_options={}, options=nil)
|
44
|
+
|
45
|
+
if engine_or_options.respond_to?(:context)
|
46
|
+
@context = engine_or_options.context
|
47
|
+
else
|
48
|
+
options = engine_or_options
|
49
|
+
end
|
50
|
+
|
51
|
+
options ||= {}
|
38
52
|
|
39
53
|
@store_name = options['store_name']
|
40
54
|
end
|
@@ -47,6 +61,8 @@ module Ruote
|
|
47
61
|
|
48
62
|
doc = workitem.to_h
|
49
63
|
|
64
|
+
doc.delete('_rev')
|
65
|
+
|
50
66
|
doc.merge!(
|
51
67
|
'type' => 'workitems',
|
52
68
|
'_id' => to_id(doc['fei']),
|
@@ -59,13 +75,13 @@ module Ruote
|
|
59
75
|
end
|
60
76
|
alias :update :consume
|
61
77
|
|
62
|
-
#
|
78
|
+
# Removes the document/workitem from the storage
|
63
79
|
#
|
64
80
|
def cancel (fei, flavour)
|
65
81
|
|
66
|
-
doc = fetch(fei)
|
82
|
+
doc = fetch(fei.to_h)
|
67
83
|
|
68
|
-
r = @storage.delete(doc)
|
84
|
+
r = @context.storage.delete(doc)
|
69
85
|
|
70
86
|
cancel(fei, flavour) if r != nil
|
71
87
|
end
|
@@ -74,11 +90,13 @@ module Ruote
|
|
74
90
|
|
75
91
|
doc = fetch(fei)
|
76
92
|
|
77
|
-
doc ? Ruote::
|
93
|
+
doc ? Ruote::Workitem.new(doc) : nil
|
78
94
|
end
|
79
95
|
|
80
96
|
def fetch (fei)
|
81
97
|
|
98
|
+
fei = fei.to_h if fei.respond_to?(:to_h)
|
99
|
+
|
82
100
|
@context.storage.get('workitems', to_id(fei))
|
83
101
|
end
|
84
102
|
|
@@ -133,14 +151,50 @@ module Ruote
|
|
133
151
|
@context.storage.get_many('workitems', /!#{wfid}$/).map { |hwi| Ruote::Workitem.new(hwi) }
|
134
152
|
end
|
135
153
|
|
136
|
-
#
|
154
|
+
# Returns all workitems for the specified participant name
|
155
|
+
#
|
156
|
+
def by_participant (participant_name)
|
157
|
+
|
158
|
+
|
159
|
+
hwis = if @context.storage.respond_to?(:by_participant)
|
160
|
+
|
161
|
+
@context.storage.by_participant('workitems', participant_name)
|
162
|
+
|
163
|
+
else
|
164
|
+
|
165
|
+
fetch_all.select { |wi| wi['participant_name'] == participant_name }
|
166
|
+
end
|
167
|
+
|
168
|
+
hwis.collect { |hwi| Ruote::Workitem.new(hwi) }
|
169
|
+
end
|
170
|
+
|
171
|
+
# field : returns all the workitems with the given field name present.
|
172
|
+
#
|
173
|
+
# field and value : returns all the workitems with the given field name
|
174
|
+
# and the given value for that field.
|
175
|
+
#
|
176
|
+
# Warning : only some storages are optimized for such queries (like
|
177
|
+
# CouchStorage), the others will load all the workitems and then filter
|
178
|
+
# them.
|
137
179
|
#
|
138
|
-
def
|
180
|
+
def by_field (field, value=nil)
|
139
181
|
|
140
|
-
|
182
|
+
hwis = if @context.storage.respond_to?(:by_field)
|
183
|
+
|
184
|
+
@context.storage.by_field('workitems', field, value)
|
185
|
+
|
186
|
+
else
|
187
|
+
|
188
|
+
fetch_all.select { |hwi|
|
189
|
+
hwi['fields'].keys.include?(field) &&
|
190
|
+
(value.nil? || hwi['fields'][field] == value)
|
191
|
+
}
|
192
|
+
end
|
193
|
+
|
194
|
+
hwis.collect { |hwi| Ruote::Workitem.new(hwi) }
|
141
195
|
end
|
142
196
|
|
143
|
-
#
|
197
|
+
# Cleans this participant out completely
|
144
198
|
#
|
145
199
|
def purge!
|
146
200
|
|
@@ -151,18 +205,20 @@ module Ruote
|
|
151
205
|
|
152
206
|
def fetch_all
|
153
207
|
|
154
|
-
key = @store_name ? /^wi
|
208
|
+
key = @store_name ? /^wi!#{@store_name}::/ : nil
|
155
209
|
|
156
210
|
@context.storage.get_many('workitems', key)
|
157
211
|
end
|
158
212
|
|
159
213
|
def to_id (fei)
|
160
214
|
|
161
|
-
|
215
|
+
a = [ Ruote.to_storage_id(fei) ]
|
216
|
+
|
217
|
+
a.unshift(@store_name) if @store_name
|
162
218
|
|
163
|
-
|
219
|
+
a.unshift('wi')
|
164
220
|
|
165
|
-
|
221
|
+
a.join('!')
|
166
222
|
end
|
167
223
|
end
|
168
224
|
end
|
data/lib/ruote/participant.rb
CHANGED
data/lib/ruote/storage/base.rb
CHANGED
@@ -0,0 +1,68 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2005-2010, 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
|
+
# a few helper methods about subprocesses
|
30
|
+
#++
|
31
|
+
|
32
|
+
# This method is used by the 'subprocess' expression and by the
|
33
|
+
# EngineParticipant.
|
34
|
+
#
|
35
|
+
def self.lookup_subprocess (fexp, ref)
|
36
|
+
|
37
|
+
val = fexp.lookup_variable(ref)
|
38
|
+
|
39
|
+
# a classical subprocess stored in a variable ?
|
40
|
+
|
41
|
+
return [ '0', val ] if is_tree?(val)
|
42
|
+
return val if is_pos_tree?(val)
|
43
|
+
|
44
|
+
# maybe subprocess :ref => 'uri'
|
45
|
+
|
46
|
+
subtree = fexp.context.parser.parse(ref) rescue nil
|
47
|
+
|
48
|
+
_, subtree = Ruote::Exp::DefineExpression.reorganize(subtree) \
|
49
|
+
if subtree && Ruote::Exp::DefineExpression.is_definition?(subtree)
|
50
|
+
|
51
|
+
return [ '0', subtree ] if is_tree?(subtree)
|
52
|
+
|
53
|
+
# no luck ...
|
54
|
+
|
55
|
+
raise "no subprocess named '#{ref}' found"
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.is_tree? (a)
|
59
|
+
|
60
|
+
a.is_a?(Array) && a[1].is_a?(Hash) && a.size == 3
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.is_pos_tree? (a)
|
64
|
+
|
65
|
+
a.is_a?(Array) && a.size == 2 && a[0].is_a?(String) && is_tree?(a[1])
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|