ruote 2.1.4 → 2.1.5
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 +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
|
+
|