ruote 2.1.4 → 2.1.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -69,13 +69,10 @@ module Ruote::Exp
69
69
 
70
70
  return r if r
71
71
 
72
- if h.has_error
73
-
74
- err = @context.storage.get(
75
- 'errors', "err_#{Ruote.to_storage_id(h.fei)}")
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
  #
@@ -43,6 +43,7 @@ module Ruote
43
43
  @context.worker.subscribe(:all, self) if @context.worker
44
44
 
45
45
  @noisy = false
46
+ @count = -1
46
47
  end
47
48
 
48
49
  def notify (msg)
@@ -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 (options={})
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
- # Makes sure to remove the workitem from the in-memory hash.
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::WorkItem.new(doc) : nil
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
- # Return all workitems for the specified participant
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 by_participant( part )
180
+ def by_field (field, value=nil)
139
181
 
140
- all.select { |wi| wi.participant_name == part }
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
- # Clean this participant out completely
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\_#{@store_name}::/ : nil
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
- sid = Ruote.to_storage_id(fei)
215
+ a = [ Ruote.to_storage_id(fei) ]
216
+
217
+ a.unshift(@store_name) if @store_name
162
218
 
163
- sid = @store_name ? "#{store_name}::#{sid}" : sid
219
+ a.unshift('wi')
164
220
 
165
- "wi_#{sid}"
221
+ a.join('!')
166
222
  end
167
223
  end
168
224
  end
@@ -3,4 +3,5 @@ require 'ruote/part/hash_participant'
3
3
  require 'ruote/part/storage_participant'
4
4
  require 'ruote/part/no_op_participant'
5
5
  require 'ruote/part/null_participant'
6
+ require 'ruote/part/engine_participant'
6
7
 
@@ -96,7 +96,7 @@ module Ruote
96
96
 
97
97
  def find_root_expression (wfid)
98
98
 
99
- get_many('expressions', /#{wfid}$/).sort { |a, b|
99
+ get_many('expressions', /!#{wfid}$/).sort { |a, b|
100
100
  a['fei']['expid'] <=> b['fei']['expid']
101
101
  }.select { |e|
102
102
  e['parent_id'].nil?
@@ -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
+