ruote 2.1.6 → 2.1.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/CHANGELOG.txt +14 -0
  2. data/CREDITS.txt +4 -3
  3. data/Rakefile +4 -3
  4. data/TODO.txt +9 -6
  5. data/lib/ruote/context.rb +29 -13
  6. data/lib/ruote/engine.rb +14 -0
  7. data/lib/ruote/exp/flowexpression.rb +7 -0
  8. data/lib/ruote/fei.rb +15 -7
  9. data/lib/ruote/id/wfid_generator.rb +3 -0
  10. data/lib/ruote/log/fs_history.rb +3 -3
  11. data/lib/ruote/log/storage_history.rb +13 -7
  12. data/lib/ruote/log/test_logger.rb +6 -0
  13. data/lib/ruote/parser.rb +12 -3
  14. data/lib/ruote/parser/ruby_dsl.rb +53 -0
  15. data/lib/ruote/part/participant_list.rb +10 -0
  16. data/lib/ruote/part/storage_participant.rb +74 -3
  17. data/lib/ruote/storage/base.rb +9 -6
  18. data/lib/ruote/storage/hash_storage.rb +7 -2
  19. data/lib/ruote/util/misc.rb +4 -0
  20. data/lib/ruote/version.rb +1 -1
  21. data/lib/ruote/workitem.rb +35 -48
  22. data/ruote.gemspec +12 -9
  23. data/test/README.rdoc +3 -3
  24. data/test/functional/eft_18_concurrent_iterator.rb +8 -11
  25. data/test/functional/eft_24_add_branches.rb +18 -6
  26. data/test/functional/eft_27_inc.rb +2 -1
  27. data/test/functional/eft_6_concurrence.rb +9 -4
  28. data/test/functional/ft_20_storage_participant.rb +63 -29
  29. data/test/functional/ft_24_block_participants.rb +6 -0
  30. data/test/functional/ft_30_smtp_participant.rb +2 -2
  31. data/test/functional/ft_32_fs_history.rb +15 -10
  32. data/test/functional/ft_36_storage_history.rb +3 -3
  33. data/test/functional/ft_3_participant_registration.rb +8 -0
  34. data/test/functional/storage_helper.rb +2 -0
  35. data/test/functional/test.rb +9 -2
  36. data/test/unit/storage.rb +2 -1
  37. data/test/unit/test.rb +18 -2
  38. data/test/unit/ut_0_ruby_parser.rb +7 -0
  39. data/test/unit/ut_16_parser.rb +1 -1
  40. data/test/unit/ut_1_fei.rb +60 -2
  41. data/test/unit/ut_3_wait_logger.rb +2 -0
  42. data/test/unit/ut_7_workitem.rb +29 -2
  43. metadata +15 -4
@@ -27,16 +27,69 @@ module Ruote
27
27
 
28
28
  # Not really a parser, more an AST builder.
29
29
  #
30
+ # pdef = Ruote.define :name => 'take_out_garbage' do
31
+ # sequence do
32
+ # take_out_regular_garbage
33
+ # take_out_glass
34
+ # take_out_paper
35
+ # end
36
+ # end
37
+ #
38
+ # engine.launch(pdef)
39
+ #
30
40
  def self.define (*attributes, &block)
31
41
 
32
42
  RubyDsl.create_branch('define', attributes, &block)
33
43
  end
34
44
 
45
+ # Same as Ruote.define()
46
+ #
47
+ # pdef = Ruote.process_definition :name => 'take_out_garbage' do
48
+ # sequence do
49
+ # take_out_regular_garbage
50
+ # take_out_paper
51
+ # end
52
+ # end
53
+ #
54
+ # engine.launch(pdef)
55
+ #
35
56
  def self.process_definition (*attributes, &block)
36
57
 
37
58
  define(*attributes, &block)
38
59
  end
39
60
 
61
+ # Similar in purpose to Ruote.define and Ruote.process_definition but
62
+ # instead of returning a [process] definition, returns the tree.
63
+ #
64
+ # tree = Ruote.process_definition :name => 'take_out_garbage' do
65
+ # sequence do
66
+ # take_out_regular_garbage
67
+ # take_out_paper
68
+ # end
69
+ # end
70
+ #
71
+ # p tree
72
+ # # => [ 'sequence', {}, [ [ 'take_out_regular_garbage', {}, [] ], [ 'take_out_paper', {}, [] ] ] ],
73
+ #
74
+ # This is useful when modifying a process instance via methods like re_apply :
75
+ #
76
+ # engine.re_apply(
77
+ # fei,
78
+ # :tree => Ruote.to_tree {
79
+ # sequence do
80
+ # participant 'alfred'
81
+ # participant 'bob'
82
+ # end
83
+ # })
84
+ # #
85
+ # # cancels the segment of process at fei and replaces it with
86
+ # # a simple alfred-bob sequence.
87
+ #
88
+ def self.to_tree (&block)
89
+
90
+ RubyDsl.create_branch('x', {}, &block).last.first
91
+ end
92
+
40
93
  # :nodoc:
41
94
  #
42
95
  module RubyDsl
@@ -93,6 +93,9 @@ module Ruote
93
93
 
94
94
  else
95
95
 
96
+ return Ruote::StorageParticipant.new(@context) \
97
+ if entry.last.first == 'Ruote::StorageParticipant'
98
+
96
99
  nil
97
100
  end
98
101
  end
@@ -177,6 +180,13 @@ module Ruote
177
180
  pa
178
181
  end
179
182
 
183
+ # Return a list of names (regex) for the registered participants
184
+ #
185
+ def names
186
+
187
+ get_list['list'].map { |re, pa| re }
188
+ end
189
+
180
190
  # Shuts down the 'instantiated participants' (engine worker participants)
181
191
  # if they respond to #shutdown.
182
192
  #
@@ -146,16 +146,17 @@ module Ruote
146
146
 
147
147
  # Return all workitems for the specified wfid
148
148
  #
149
- def by_wfid( wfid )
149
+ def by_wfid (wfid)
150
150
 
151
- @context.storage.get_many('workitems', /!#{wfid}$/).map { |hwi| Ruote::Workitem.new(hwi) }
151
+ @context.storage.get_many('workitems', /!#{wfid}$/).map { |hwi|
152
+ Ruote::Workitem.new(hwi)
153
+ }
152
154
  end
153
155
 
154
156
  # Returns all workitems for the specified participant name
155
157
  #
156
158
  def by_participant (participant_name)
157
159
 
158
-
159
160
  hwis = if @context.storage.respond_to?(:by_participant)
160
161
 
161
162
  @context.storage.by_participant('workitems', participant_name)
@@ -194,6 +195,56 @@ module Ruote
194
195
  hwis.collect { |hwi| Ruote::Workitem.new(hwi) }
195
196
  end
196
197
 
198
+ # Queries the store participant for workitems.
199
+ #
200
+ # Some examples :
201
+ #
202
+ # part.query(:wfid => @wfid).size
203
+ # part.query('place' => 'nara').size
204
+ # part.query('place' => 'heiankyou').size
205
+ # part.query(:wfid => @wfid, :place => 'heiankyou').size
206
+ #
207
+ # There are two 'reserved' criterion : 'wfid' and 'participant'
208
+ # ('participant_name' as well). The rest of the criteria are considered
209
+ # constraints for fields.
210
+ #
211
+ # 'offset' and 'limit' are reserved as well. They should prove useful
212
+ # for pagination.
213
+ #
214
+ # Note : the criteria is AND only, you'll have to do ORs (aggregation)
215
+ # by yourself.
216
+ #
217
+ def query (criteria)
218
+
219
+ cr = criteria.inject({}) { |h, (k, v)| h[k.to_s] = v; h }
220
+
221
+ return @context.storage.query_workitems(cr) \
222
+ if @context.storage.respond_to?(:query_workitems)
223
+
224
+ offset = cr.delete('offset')
225
+ limit = cr.delete('limit')
226
+
227
+ wfid = cr.delete('wfid')
228
+ pname = cr.delete('participant_name') || cr.delete('participant')
229
+
230
+ hwis = if wfid
231
+ @context.storage.get_many('workitems', /!#{wfid}$/)
232
+ else
233
+ fetch_all
234
+ end
235
+
236
+ hwis = hwis.select { |hwi|
237
+ Ruote::StorageParticipant.matches?(hwi, pname, cr)
238
+ }.collect { |hwi|
239
+ Ruote::Workitem.new(hwi)
240
+ }
241
+
242
+ offset = offset || 0
243
+ limit = limit || hwis.length
244
+
245
+ hwis[offset, limit]
246
+ end
247
+
197
248
  # Cleans this participant out completely
198
249
  #
199
250
  def purge!
@@ -201,8 +252,26 @@ module Ruote
201
252
  fetch_all.each { |hwi| @context.storage.delete( hwi ) }
202
253
  end
203
254
 
255
+ # Used by #query when filtering workitems.
256
+ #
257
+ def self.matches? (hwi, pname, criteria)
258
+
259
+ return false if pname && hwi['participant_name'] != pname
260
+
261
+ fields = hwi['fields']
262
+
263
+ criteria.each do |fname, fvalue|
264
+ return false if fields[fname] != fvalue
265
+ end
266
+
267
+ true
268
+ end
269
+
204
270
  protected
205
271
 
272
+ # Fetches all the workitems. If there is a @store_name, will only fetch
273
+ # the workitems in that store.
274
+ #
206
275
  def fetch_all
207
276
 
208
277
  key = @store_name ? /^wi!#{@store_name}::/ : nil
@@ -210,6 +279,8 @@ module Ruote
210
279
  @context.storage.get_many('workitems', key)
211
280
  end
212
281
 
282
+ # Computes the id for the document representing the document in the storage.
283
+ #
213
284
  def to_id (fei)
214
285
 
215
286
  a = [ Ruote.to_storage_id(fei) ]
@@ -55,13 +55,16 @@ module Ruote
55
55
 
56
56
  # merge! is way faster than merge (no object creation probably)
57
57
 
58
- t = Time.now
59
- t = "#{t.to_i}.#{"%06d" % t.usec}"
58
+ @counter ||= 0
60
59
 
61
- msg = options.merge!(
62
- 'type' => 'msgs',
63
- '_id' => "#{$$}-#{Thread.current.object_id}-#{t}",
64
- 'action' => action)
60
+ t = Time.now.utc
61
+ ts = "#{t.strftime('%Y-%m-%d')}!#{t.to_i}.#{'%06d' % t.usec}"
62
+ _id = "#{$$}!#{Thread.current.object_id}!#{ts}!#{'%03d' % @counter}"
63
+
64
+ @counter = (@counter + 1) % 1000
65
+ # some platforms (windows) have shallow usecs, so adding that counter...
66
+
67
+ msg = options.merge!('type' => 'msgs', '_id' => _id, 'action' => action)
65
68
 
66
69
  msg.delete('_rev')
67
70
  # in case of message replay
@@ -30,6 +30,8 @@ require 'monitor'
30
30
 
31
31
  module Ruote
32
32
 
33
+ # An in-memory storage.
34
+ #
33
35
  class HashStorage
34
36
 
35
37
  include StorageBase
@@ -40,14 +42,17 @@ module Ruote
40
42
  def initialize (options={})
41
43
 
42
44
  super()
43
-
44
- @options = options
45
+ # since were including MonitorMixin, this super() is necessary
45
46
 
46
47
  purge!
48
+
49
+ put(options.merge('type' => 'configurations', '_id' => 'engine'))
47
50
  end
48
51
 
49
52
  def put (doc, opts={})
50
53
 
54
+ i = @h.size
55
+
51
56
  synchronize do
52
57
 
53
58
  pre = get(doc['type'], doc['_id'])
@@ -25,6 +25,10 @@
25
25
 
26
26
  module Ruote
27
27
 
28
+ # Will be set to true if the Ruby runtime is on Windows
29
+ #
30
+ WIN = RUBY_PLATFORM.match(/mswin|mingw/) != nil
31
+
28
32
  # Prints the current call stack to stdout
29
33
  #
30
34
  def self.p_caller (*msg)
@@ -23,6 +23,6 @@
23
23
  #++
24
24
 
25
25
  module Ruote
26
- VERSION = '2.1.6'
26
+ VERSION = '2.1.7'
27
27
  end
28
28
 
@@ -29,6 +29,12 @@ require 'ruote/util/hashdot'
29
29
 
30
30
  module Ruote
31
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
+ #
32
38
  class Workitem
33
39
 
34
40
  attr_reader :h
@@ -44,27 +50,41 @@ module Ruote
44
50
  @h
45
51
  end
46
52
 
53
+ # Returns a Ruote::FlowExpressionId instance.
54
+ #
47
55
  def fei
48
56
 
49
57
  FlowExpressionId.new(h.fei)
50
58
  end
51
59
 
60
+ # Returns a complete copy of this workitem.
61
+ #
52
62
  def dup
53
63
 
54
- Ruote.fulldup(self)
64
+ Workitem.new(Rufus::Json.dup(@h))
55
65
  end
56
66
 
67
+ # The participant for which this item is destined. Will be nil when
68
+ # the workitem is transiting inside of its process instance (as opposed
69
+ # to when it's being delivered outside of the engine).
70
+ #
57
71
  def participant_name
58
72
 
59
73
  @h['participant_name']
60
74
  end
61
75
 
76
+ # Returns the payload, ie the fields hash.
77
+ #
62
78
  def fields
63
79
 
64
80
  @h['fields']
65
81
  end
66
82
 
67
- def fields=( fields )
83
+ # Sets all the fields in one sweep.
84
+ #
85
+ # Remember : the fields must be a JSONifiable hash.
86
+ #
87
+ def fields= (fields)
68
88
 
69
89
  @h['fields'] = fields
70
90
  end
@@ -87,25 +107,22 @@ module Ruote
87
107
 
88
108
  fields['__result__'] = r
89
109
  end
90
- end
91
110
 
92
- #
93
- # TODO : clean me out !
94
- #
95
- class BakWorkitem
111
+ # Warning : equality is based on fei and not on payload !
112
+ #
113
+ def == (other)
96
114
 
97
- attr_accessor :fei
98
- attr_accessor :fields
99
- attr_accessor :participant_name
115
+ return false if other.class != self.class
116
+ self.h['fei'] == other.h['fei']
117
+ end
100
118
 
101
- alias :f :fields
102
- alias :attributes :fields
103
- alias :attributes= :fields=
119
+ alias eql? ==
104
120
 
105
- def initialize (fields={})
121
+ # Warning : hash is fei's hash.
122
+ #
123
+ def hash
106
124
 
107
- @fei = nil
108
- @fields = fields
125
+ self.h['fei'].hash
109
126
  end
110
127
 
111
128
  # For a simple key
@@ -126,7 +143,7 @@ module Ruote
126
143
  #
127
144
  def lookup (key, container_lookup=false)
128
145
 
129
- Ruote.lookup(@fields, key, container_lookup)
146
+ Ruote.lookup(@h['fields'], key, container_lookup)
130
147
  end
131
148
 
132
149
  # 'lf' for 'lookup field'
@@ -144,37 +161,7 @@ module Ruote
144
161
  #
145
162
  def set_field (key, value)
146
163
 
147
- Ruote.set(@fields, key, value)
148
- end
149
-
150
- # Returns a deep copy of this workitem instance.
151
- #
152
- def dup
153
-
154
- Ruote.fulldup(self)
155
- end
156
-
157
- # Turns a workitem into a Ruby Hash (useful for JSON serializations)
158
- #
159
- def to_h
160
-
161
- h = {}
162
- h['fei'] = @fei.to_h
163
- h['participant_name'] = @participant_name
164
- h['fields'] = @fields
165
-
166
- h
167
- end
168
-
169
- # Turns back a Ruby Hash into a workitem (well, attempts to)
170
- #
171
- def self.from_h (h)
172
-
173
- wi = Workitem.new(h['fields'])
174
- wi.fei = FlowExpressionId.from_h(h['fei'])
175
- wi.participant_name = h['participant_name']
176
-
177
- wi
164
+ Ruote.set(@h['fields'], key, value)
178
165
  end
179
166
  end
180
167
  end
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{ruote}
8
- s.version = "2.1.6"
8
+ s.version = "2.1.7"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["John Mettraux", "Kenneth Kalmer"]
12
- s.date = %q{2010-02-08}
11
+ s.authors = ["John Mettraux", "Kenneth Kalmer", "Torsten Schoenebaum"]
12
+ s.date = %q{2010-02-15}
13
13
  s.description = %q{
14
14
  ruote is an open source ruby workflow engine.
15
15
  }
@@ -251,8 +251,8 @@ ruote is an open source ruby workflow engine.
251
251
  s.specification_version = 3
252
252
 
253
253
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
254
- s.add_runtime_dependency(%q<rufus-json>, [">= 0"])
255
- s.add_runtime_dependency(%q<rufus-cloche>, [">= 0.1.13"])
254
+ s.add_runtime_dependency(%q<rufus-json>, [">= 0.2.0"])
255
+ s.add_runtime_dependency(%q<rufus-cloche>, [">= 0.1.14"])
256
256
  s.add_runtime_dependency(%q<rufus-dollar>, [">= 0"])
257
257
  s.add_runtime_dependency(%q<rufus-lru>, [">= 0"])
258
258
  s.add_runtime_dependency(%q<rufus-mnemo>, [">= 1.1.0"])
@@ -262,9 +262,10 @@ ruote is an open source ruby workflow engine.
262
262
  s.add_development_dependency(%q<yard>, [">= 0"])
263
263
  s.add_development_dependency(%q<builder>, [">= 0"])
264
264
  s.add_development_dependency(%q<mailtrap>, [">= 0"])
265
+ s.add_development_dependency(%q<jeweler>, [">= 0"])
265
266
  else
266
- s.add_dependency(%q<rufus-json>, [">= 0"])
267
- s.add_dependency(%q<rufus-cloche>, [">= 0.1.13"])
267
+ s.add_dependency(%q<rufus-json>, [">= 0.2.0"])
268
+ s.add_dependency(%q<rufus-cloche>, [">= 0.1.14"])
268
269
  s.add_dependency(%q<rufus-dollar>, [">= 0"])
269
270
  s.add_dependency(%q<rufus-lru>, [">= 0"])
270
271
  s.add_dependency(%q<rufus-mnemo>, [">= 1.1.0"])
@@ -274,10 +275,11 @@ ruote is an open source ruby workflow engine.
274
275
  s.add_dependency(%q<yard>, [">= 0"])
275
276
  s.add_dependency(%q<builder>, [">= 0"])
276
277
  s.add_dependency(%q<mailtrap>, [">= 0"])
278
+ s.add_dependency(%q<jeweler>, [">= 0"])
277
279
  end
278
280
  else
279
- s.add_dependency(%q<rufus-json>, [">= 0"])
280
- s.add_dependency(%q<rufus-cloche>, [">= 0.1.13"])
281
+ s.add_dependency(%q<rufus-json>, [">= 0.2.0"])
282
+ s.add_dependency(%q<rufus-cloche>, [">= 0.1.14"])
281
283
  s.add_dependency(%q<rufus-dollar>, [">= 0"])
282
284
  s.add_dependency(%q<rufus-lru>, [">= 0"])
283
285
  s.add_dependency(%q<rufus-mnemo>, [">= 1.1.0"])
@@ -287,6 +289,7 @@ ruote is an open source ruby workflow engine.
287
289
  s.add_dependency(%q<yard>, [">= 0"])
288
290
  s.add_dependency(%q<builder>, [">= 0"])
289
291
  s.add_dependency(%q<mailtrap>, [">= 0"])
292
+ s.add_dependency(%q<jeweler>, [">= 0"])
290
293
  end
291
294
  end
292
295