ruote 2.1.6 → 2.1.7

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