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.
@@ -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
+