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
@@ -2,6 +2,20 @@
2
2
  = ruote - CHANGELOG.txt
3
3
 
4
4
 
5
+ == ruote - 2.1.7 released 2010/02/15
6
+
7
+ - now works on WinXP, Ruby 1.8.7
8
+ - reformed msgs ids and adapted Ruote::StorageHistory
9
+ - engine.register_participant(x, Ruote::StorageParticipant) now returning
10
+ a instance of the participant, for easy query
11
+ - StorageParticipant, added #query(criteria), thanks Torsten and Brett
12
+ - Ruote::Workitem #lookup('deep.field') and #set_field('deep.field') are back
13
+ - Ruote::Workitem added == and hash (list.uniq friendly), thanks Brett
14
+ - Engine#configure(key, value), thanks Torsten
15
+ - Ruote.to_tree (lightweight version of Ruote.process_definition)
16
+ - ParticipantList#names, thanks Kenneth
17
+
18
+
5
19
  == ruote - 2.1.6 released 2010/02/08
6
20
 
7
21
  - welcoming ruote-dm (datamapper persistency)
@@ -8,14 +8,15 @@ Ruote (OpenWFEru) is an open source Ruby workflow (and sometimes BPM) engine.
8
8
  The main project team
9
9
  ---------------------
10
10
 
11
- John Mettraux http://jmettraux.wordpress.com
12
- Kenneth Kalmer http://www.opensourcery.co.za
11
+ John Mettraux - http://jmettraux.wordpress.com
12
+ Kenneth Kalmer - http://www.opensourcery.co.za
13
+ Torsten Schoenebaum - http://github.com/tosch
13
14
 
14
15
 
15
16
  Contributors
16
17
  ------------
17
18
 
18
- Torsten Schoenebaum - ActiveResourceParticipant and much more
19
+ Brett Anthoine - http://github.com/anb
19
20
  Matt Nichols - http://github.com/mattnichols
20
21
  Nicholas Faiz - http://github.com/biv
21
22
  Chris Beer - http://github.com/cbeer
data/Rakefile CHANGED
@@ -19,12 +19,12 @@ ruote is an open source ruby workflow engine.
19
19
  }
20
20
  gem.email = 'jmettraux@gmail.com'
21
21
  gem.homepage = 'http://ruote.rubyforge.org'
22
- gem.authors = [ 'John Mettraux', 'Kenneth Kalmer' ]
22
+ gem.authors = [ 'John Mettraux', 'Kenneth Kalmer', 'Torsten Schoenebaum' ]
23
23
  gem.rubyforge_project = 'ruote'
24
24
  gem.test_file = 'test/test.rb'
25
25
 
26
- gem.add_dependency 'rufus-json'
27
- gem.add_dependency 'rufus-cloche', '>= 0.1.13'
26
+ gem.add_dependency 'rufus-json', '>= 0.2.0'
27
+ gem.add_dependency 'rufus-cloche', '>= 0.1.14'
28
28
  gem.add_dependency 'rufus-dollar'
29
29
  gem.add_dependency 'rufus-lru'
30
30
  gem.add_dependency 'rufus-mnemo', '>= 1.1.0'
@@ -35,6 +35,7 @@ ruote is an open source ruby workflow engine.
35
35
  gem.add_development_dependency 'yard'
36
36
  gem.add_development_dependency 'builder'
37
37
  gem.add_development_dependency 'mailtrap'
38
+ gem.add_development_dependency 'jeweler'
38
39
 
39
40
  # Gem::Specification http://www.rubygems.org/read/chapter/20
40
41
  end
data/TODO.txt CHANGED
@@ -184,6 +184,13 @@
184
184
  [o] process_status.to_dot
185
185
  [o] EngineParticipant : don't wait in case of forget (reply could NEVER come !)
186
186
  [x] align :forget behaviour on EngineParticipant forget... OK as it is
187
+ [o] engine.re_apply(fei, wi) (thanks Torsten)... :wi => x, :tree => y...
188
+ [o] ruote-dm 2.1
189
+ [o] :tree => Ruote.to_tree { participant 'alpha' }
190
+ [o] implement == eql? hash for workitem
191
+ [o] StorageParticipant#query(wfid, participant_name, {fields})
192
+ [x] break fs_history, prepare for dm_history
193
+ [o] part = engine.register_participant :alpha, StorageParticipant should work...
187
194
 
188
195
  [ ] exp : exp (restricted form of eval ?)
189
196
  [ ] exp : case (is it necessary ?)
@@ -264,8 +271,6 @@
264
271
 
265
272
  [ ] engine.force_reply_to_parent(fei) ?
266
273
 
267
- [ ] break fs_history, prepare for dm_history
268
-
269
274
  [ ] :on_timeout => :rewind (break, jump to x)...
270
275
  [ ] rewind 'x' where x is a tagname (command x)
271
276
 
@@ -293,14 +298,12 @@
293
298
  [ ] shell ? irb ? Shell.new(storage)
294
299
  [ ] focus on fulldup or json.dup (via fulldup ?)
295
300
 
296
- [ ] ruote-dm 2.1
297
-
298
301
  [ ] implement pause engine
299
302
  [ ] implement pause process
300
303
 
301
304
  [ ] engine.on_error = 'participant_name'
302
305
 
303
- [ ] "open days" plugin / service / lambda ? is_open_day?(d)
306
+ [ ] "business days" plugin
304
307
 
305
- [ ] engine.re_apply(fei, wi) (thanks Torsten)... :wi => x, :tree => y...
308
+ [ ] issue with ruote-kit and inpa participants...
306
309
 
@@ -34,6 +34,8 @@ module Ruote
34
34
  #
35
35
  class Context
36
36
 
37
+ SERVICE_PREFIX = /^s\_/
38
+
37
39
  attr_reader :storage
38
40
  attr_accessor :worker
39
41
  attr_accessor :engine
@@ -41,7 +43,6 @@ module Ruote
41
43
  def initialize (storage, worker_or_engine)
42
44
 
43
45
  @storage = storage
44
- @conf = default_conf.merge(@storage.get_configuration('engine') || {})
45
46
 
46
47
  @worker, @engine = if worker_or_engine.kind_of?(Ruote::Engine)
47
48
  [ worker_or_engine.worker, worker_or_engine ]
@@ -54,29 +55,37 @@ module Ruote
54
55
 
55
56
  def engine_id
56
57
 
57
- @conf['engine_id'] || 'engine'
58
+ get_conf['engine_id'] || 'engine'
58
59
  end
59
60
 
60
61
  def [] (key)
61
62
 
62
- @conf[key]
63
+ SERVICE_PREFIX.match(key) ? @services[key] : get_conf[key]
63
64
  end
64
65
 
65
66
  def []= (key, value)
66
67
 
67
- @conf[key] = value
68
+ raise(
69
+ ArgumentError.new('use context#add_service to register services')
70
+ ) if SERVICE_PREFIX.match(key)
71
+
72
+ cf = get_conf
73
+ cf[key] = value
74
+ @storage.put(cf)
75
+
76
+ value
68
77
  end
69
78
 
70
79
  def keys
71
80
 
72
- @conf.keys
81
+ get_conf.keys
73
82
  end
74
83
 
75
84
  def add_service (key, *args)
76
85
 
77
86
  path, klass, opts = args
78
87
 
79
- key = "s_#{key}" unless key.match(/^s\_/)
88
+ key = "s_#{key}" unless SERVICE_PREFIX.match(key)
80
89
 
81
90
  service = if klass
82
91
 
@@ -85,13 +94,13 @@ module Ruote
85
94
  aa = [ self ]
86
95
  aa << opts if opts
87
96
 
88
- @conf[key] = Ruote.constantize(klass).new(*aa)
97
+ @services[key] = Ruote.constantize(klass).new(*aa)
89
98
  else
90
99
 
91
- @conf[key] = path
100
+ @services[key] = path
92
101
  end
93
102
 
94
- self.class.class_eval %{ def #{key[2..-1]}; @conf['#{key}']; end }
103
+ self.class.class_eval %{ def #{key[2..-1]}; @services['#{key}']; end }
95
104
 
96
105
  service
97
106
  end
@@ -101,7 +110,7 @@ module Ruote
101
110
  @storage.shutdown if @storage.respond_to?(:shutdown)
102
111
  @worker.shutdown if @worker
103
112
 
104
- @conf.values.each do |s|
113
+ @services.values.each do |s|
105
114
 
106
115
  s.shutdown if s.respond_to?(:shutdown)
107
116
  end
@@ -109,13 +118,20 @@ module Ruote
109
118
 
110
119
  protected
111
120
 
121
+ def get_conf
122
+
123
+ @storage.get_configuration('engine') || {}
124
+ end
125
+
112
126
  def initialize_services
113
127
 
114
- @conf.keys.each do |key|
128
+ @services = {}
129
+
130
+ default_conf.merge(get_conf).each do |key, value|
115
131
 
116
- next unless key.match(/^s\_/)
132
+ next unless SERVICE_PREFIX.match(key)
117
133
 
118
- add_service(key, *@conf[key])
134
+ add_service(key, *value)
119
135
  end
120
136
  end
121
137
 
@@ -319,6 +319,20 @@ module Ruote
319
319
 
320
320
  @context.add_service(name, path_or_instance, classname, opts)
321
321
  end
322
+
323
+ # Sets a configuration option. Examples:
324
+ #
325
+ # # allow remote workflow definitions (for subprocesses or when launching
326
+ # # processes)
327
+ # @engine.configure('remote_definition_allowed', true)
328
+ #
329
+ # # allow ruby_eval
330
+ # @engine.configure('ruby_eval_allowed', true)
331
+ #
332
+ def configure (config_key, value)
333
+
334
+ @context[config_key] = value
335
+ end
322
336
  end
323
337
 
324
338
  #
@@ -507,6 +507,10 @@ module Ruote::Exp
507
507
  tree[2]
508
508
  end
509
509
 
510
+ # A tiny class-bound counter used when generating subprocesses ids.
511
+ #
512
+ @@sub_wfid_counter = -1
513
+
510
514
  # Generates a sub_wfid, without hitting storage.
511
515
  #
512
516
  # There's a better implementation for sure...
@@ -517,6 +521,9 @@ module Ruote::Exp
517
521
  $$, Time.now.to_f.to_s, self.hash.to_s, @h['fei'].inspect
518
522
  ].join('-').hash
519
523
 
524
+ @@sub_wfid_counter = (@@sub_wfid_counter + 1) % 1000
525
+ i = i * 1000 + (@@sub_wfid_counter)
526
+
520
527
  (i < 0 ? "1#{i * -1}" : "0#{i}").to_s
521
528
  end
522
529
 
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2009, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2005-2010, John Mettraux, jmettraux@gmail.com
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  # of this software and associated documentation files (the "Software"), to deal
@@ -39,7 +39,20 @@ module Ruote
39
39
  end
40
40
 
41
41
  #
42
- # TODO : document me
42
+ # The FlowExpressionId (fei for short) is an process expression identifier.
43
+ # Each expression when instantiated gets a unique fei.
44
+ #
45
+ # Feis are also used in workitems, where the fei is the fei of the
46
+ # [participant] expression that emitted the workitem.
47
+ #
48
+ # Feis can thus indicate the position of a workitem in a process tree.
49
+ #
50
+ # Feis contain four pieces of information :
51
+ #
52
+ # * wfid : workflow instance id, the identifier for the process instance
53
+ # * sub_wfid : the identifier for the sub process within the main instance
54
+ # * expid : the expression id, where in the process tree
55
+ # * engine_id : only relevant in multi engine scenarii (defaults to 'engine')
43
56
  #
44
57
  class FlowExpressionId
45
58
 
@@ -66,12 +79,10 @@ module Ruote
66
79
  end
67
80
 
68
81
  def to_storage_id
69
-
70
82
  "#{@h['expid']}!#{@h['sub_wfid']}!#{@h['wfid']}"
71
83
  end
72
84
 
73
85
  def self.to_storage_id (hfei)
74
-
75
86
  "#{hfei['expid']}!#{hfei['sub_wfid']}!#{hfei['wfid']}"
76
87
  end
77
88
 
@@ -88,12 +99,10 @@ module Ruote
88
99
  # '0_5_7', the child_id will be '7'.
89
100
  #
90
101
  def child_id
91
-
92
102
  h.expid.split(CHILD_SEP).last.to_i
93
103
  end
94
104
 
95
105
  def hash
96
-
97
106
  to_storage_id.hash
98
107
  end
99
108
 
@@ -117,7 +126,6 @@ module Ruote
117
126
  # Returns child_id... For an expid of '0_1_4', this will be 4.
118
127
  #
119
128
  def self.child_id (h)
120
-
121
129
  h['expid'].split(CHILD_SEP).last.to_i
122
130
  end
123
131
 
@@ -47,7 +47,10 @@ module Ruote
47
47
 
48
48
  def get_raw
49
49
 
50
+ lraw = @last['raw'] + 0.01
51
+
50
52
  raw = Time.now.utc
53
+ while raw.to_f <= lraw; raw = raw + 0.01; end
51
54
 
52
55
  @last['raw'] = raw.to_f
53
56
 
@@ -122,7 +122,7 @@ module Ruote
122
122
  #
123
123
  def by_date (date)
124
124
 
125
- date = Time.parse(date.to_s).strftime('%F')
125
+ date = Time.parse(date.to_s).strftime('%Y-%m-%d')
126
126
 
127
127
  lines = File.readlines(File.join(@path, "history_#{date}.json")) rescue []
128
128
 
@@ -158,7 +158,7 @@ module Ruote
158
158
  rotate_if_necessary
159
159
 
160
160
  @file.puts(Rufus::Json.encode(
161
- [ "#{@last.strftime('%F %T')}.#{"%06d" % @last.usec}", msg ]))
161
+ [ "#{@last.strftime('%Y-%m-%d %T')}.#{"%06d" % @last.usec}", msg ]))
162
162
  @file.flush
163
163
  end
164
164
 
@@ -173,7 +173,7 @@ module Ruote
173
173
 
174
174
  @file.close rescue nil
175
175
 
176
- fn = [ 'history', @last.strftime('%F') ].join('_') + '.json'
176
+ fn = [ 'history', @last.strftime('%Y-%m-%d') ].join('_') + '.json'
177
177
 
178
178
  @file = File.open(File.join(@path, fn), 'a')
179
179
  end
@@ -34,6 +34,8 @@ module Ruote
34
34
  #
35
35
  class StorageHistory
36
36
 
37
+ DATE_REGEX = /!(\d{4}-\d{2}-\d{2})!/
38
+
37
39
  def initialize (context, options={})
38
40
 
39
41
  @context = context
@@ -60,17 +62,20 @@ module Ruote
60
62
 
61
63
  ids = @context.storage.ids('history')
62
64
 
63
- first = Time.parse("#{ids.first.split('!')[0]} 00:00:00 UTC")
64
- last = Time.parse("#{ids.last.split('!')[0]} 00:00:00 UTC") + 24 * 3600
65
+ fm = DATE_REGEX.match(ids.first)[1]
66
+ lm = DATE_REGEX.match(ids.last)[1]
67
+
68
+ first = Time.parse("#{fm} 00:00:00 UTC")
69
+ last = Time.parse("#{lm} 00:00:00 UTC") + 24 * 3600
65
70
 
66
71
  [ first, last ]
67
72
  end
68
73
 
69
74
  def by_date (date)
70
75
 
71
- date = Time.parse(date.to_s).strftime('%F')
76
+ date = Time.parse(date.to_s).strftime('%Y-%m-%d')
72
77
 
73
- @context.storage.get_many('history', /^#{date}!/)
78
+ @context.storage.get_many('history', /!#{date}!/)
74
79
  end
75
80
 
76
81
  #def history_to_tree (wfid)
@@ -101,12 +106,13 @@ module Ruote
101
106
  msg['wfid'] || 'no_wfid'
102
107
  end
103
108
 
104
- t = Time.parse(msg['put_at'])
109
+ _id = msg['_id']
110
+ msg['original_id'] = _id
111
+ msg['_id'] = "#{_id}!#{si}"
105
112
 
106
- msg['original_id'] = msg['_id']
107
- msg['_id'] = "#{t.strftime('%F')}!#{t.to_i}_#{"%06d" % t.usec}!#{si}"
108
113
  msg['type'] = 'history'
109
114
  msg['original_put_at'] = msg['put_at']
115
+
110
116
  msg.delete('_rev')
111
117
 
112
118
  @context.storage.put(msg)
@@ -148,6 +148,11 @@ module Ruote
148
148
  interest = interest - 1
149
149
  @waiting = [ @waiting.first, interest ]
150
150
 
151
+ #@debug ||= {}
152
+ #c = @debug[action] ||= 0
153
+ #@debug[action] = c+1
154
+ #p [ interest, @debug ]
155
+
151
156
  (interest < 1)
152
157
 
153
158
  else # wfid
@@ -194,6 +199,7 @@ module Ruote
194
199
 
195
200
  def color (mod, s, clear=false)
196
201
 
202
+ return s if Ruote::WIN
197
203
  return s unless STDOUT.tty?
198
204
 
199
205
  "[#{mod}m#{s}#{clear ? '' : "[#{@color}m"}"
@@ -35,6 +35,8 @@ module Ruote
35
35
  #
36
36
  # A process definition parser.
37
37
  #
38
+ # Can parse XML, JSON, Ruby (and more) process definition representations.
39
+ #
38
40
  class Parser
39
41
 
40
42
  def initialize (context)
@@ -55,11 +57,9 @@ module Ruote
55
57
 
56
58
  if definition.index("\n") == nil
57
59
 
58
- u = URI.parse(definition)
59
-
60
60
  raise ArgumentError.new(
61
61
  "remote process definitions are not allowed"
62
- ) if u.scheme != nil && @context['remote_definition_allowed'] != true
62
+ ) if Ruote::Parser.remote?(definition) && @context['remote_definition_allowed'] != true
63
63
 
64
64
  return parse(open(definition).read)
65
65
  end
@@ -147,6 +147,15 @@ module Ruote
147
147
  tree.to_json
148
148
  end
149
149
 
150
+ # Returns true if the defintion is a remote URI
151
+ #
152
+ def self.remote? (definition)
153
+
154
+ u = URI.parse(definition)
155
+
156
+ (u.scheme != nil) && ( ! ('A'..'Z').include?(u.scheme))
157
+ end
158
+
150
159
  protected
151
160
 
152
161
  # Evaluates the ruby string in the code, but at fist, thanks to the