ruote 2.1.7 → 2.1.8

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 (74) hide show
  1. data/CHANGELOG.txt +13 -0
  2. data/CREDITS.txt +2 -1
  3. data/Rakefile +4 -3
  4. data/TODO.txt +11 -1
  5. data/lib/ruote/exp/fe_concurrence.rb +26 -2
  6. data/lib/ruote/exp/fe_participant.rb +1 -1
  7. data/lib/ruote/exp/merge.rb +16 -9
  8. data/lib/ruote/fei.rb +1 -1
  9. data/lib/ruote/parser.rb +2 -1
  10. data/lib/ruote/part/smtp_participant.rb +22 -41
  11. data/lib/ruote/part/storage_participant.rb +20 -1
  12. data/lib/ruote/part/template.rb +34 -18
  13. data/lib/ruote/storage/base.rb +2 -20
  14. data/lib/ruote/storage/composite_storage.rb +134 -0
  15. data/lib/ruote/storage/fs_storage.rb +15 -1
  16. data/lib/ruote/util/dollar.rb +1 -0
  17. data/lib/ruote/util/misc.rb +5 -1
  18. data/lib/ruote/util/time.rb +13 -3
  19. data/lib/ruote/version.rb +1 -1
  20. data/lib/ruote/worker.rb +2 -2
  21. data/ruote.gemspec +18 -12
  22. data/test/bm/seq_thousand.rb +1 -1
  23. data/test/functional/base.rb +6 -8
  24. data/test/functional/crunner.rb +2 -0
  25. data/test/functional/eft_0_process_definition.rb +2 -2
  26. data/test/functional/eft_10_cancel_process.rb +2 -2
  27. data/test/functional/eft_11_wait.rb +3 -3
  28. data/test/functional/eft_12_listen.rb +1 -1
  29. data/test/functional/eft_13_iterator.rb +14 -14
  30. data/test/functional/eft_14_cursor.rb +12 -12
  31. data/test/functional/eft_15_loop.rb +2 -2
  32. data/test/functional/eft_16_if.rb +11 -15
  33. data/test/functional/eft_17_equals.rb +2 -2
  34. data/test/functional/eft_18_concurrent_iterator.rb +35 -8
  35. data/test/functional/eft_1_echo.rb +1 -1
  36. data/test/functional/eft_21_restore.rb +3 -3
  37. data/test/functional/eft_22_noop.rb +1 -1
  38. data/test/functional/eft_23_apply.rb +9 -9
  39. data/test/functional/eft_25_command.rb +1 -1
  40. data/test/functional/eft_27_inc.rb +12 -12
  41. data/test/functional/eft_28_when.rb +4 -4
  42. data/test/functional/eft_2_sequence.rb +3 -3
  43. data/test/functional/eft_3_participant.rb +5 -5
  44. data/test/functional/eft_4_set.rb +12 -12
  45. data/test/functional/eft_5_subprocess.rb +8 -8
  46. data/test/functional/eft_6_concurrence.rb +17 -4
  47. data/test/functional/eft_7_forget.rb +1 -1
  48. data/test/functional/eft_8_undo.rb +3 -3
  49. data/test/functional/ft_0_worker.rb +17 -1
  50. data/test/functional/ft_10_dollar.rb +9 -9
  51. data/test/functional/ft_11_recursion.rb +2 -2
  52. data/test/functional/ft_13_variables.rb +4 -4
  53. data/test/functional/ft_17_conditional.rb +5 -5
  54. data/test/functional/ft_19_alias.rb +1 -1
  55. data/test/functional/ft_1_process_status.rb +1 -1
  56. data/test/functional/ft_20_storage_participant.rb +2 -0
  57. data/test/functional/ft_22_process_definitions.rb +11 -4
  58. data/test/functional/ft_24_block_participants.rb +13 -4
  59. data/test/functional/ft_27_var_indirection.rb +5 -5
  60. data/test/functional/ft_28_null_noop_participants.rb +1 -1
  61. data/test/functional/ft_29_part_template.rb +4 -23
  62. data/test/functional/ft_30_smtp_participant.rb +50 -4
  63. data/test/functional/ft_32_fs_history.rb +4 -8
  64. data/test/functional/ft_33_participant_subprocess_priority.rb +1 -1
  65. data/test/functional/ft_36_storage_history.rb +2 -2
  66. data/test/functional/ft_5_on_error.rb +5 -5
  67. data/test/functional/ft_8_participant_consumption.rb +2 -2
  68. data/test/path_helper.rb +1 -1
  69. data/test/test_helper.rb +17 -0
  70. data/test/unit/storage.rb +2 -17
  71. data/test/unit/ut_19_part_template.rb +76 -0
  72. data/test/unit/ut_1_fei.rb +13 -0
  73. data/test/unit/ut_20_composite_storage.rb +34 -0
  74. metadata +111 -55
data/CHANGELOG.txt CHANGED
@@ -2,6 +2,19 @@
2
2
  = ruote - CHANGELOG.txt
3
3
 
4
4
 
5
+ == ruote - 2.1.8 released 2010/03/15
6
+
7
+ - participant#schedule_timeout workaround for issue with JRuby 1.4.0 (1.8.7)
8
+ - implemented Ruote::CompositeStorage
9
+ - leveraging rufus-cloche 0.1.16 and the 'cloche_nolock' option (FsStorage)
10
+ - SmtpParticipant and ruote/part/template.rb reorganization
11
+ - StorageParticipant when returned by engine#register was unusable. Fixed.
12
+ - string keys for SmtpParticipant. Thanks Gonzalo
13
+ - fixed every('10m') bug. Thanks Gonzalo Suarez
14
+ - Ruote::FlowExpressionId.from_id(s) more permissive
15
+ - concurrence (and concurrent-iterator) :merge_type => :stack
16
+
17
+
5
18
  == ruote - 2.1.7 released 2010/02/15
6
19
 
7
20
  - now works on WinXP, Ruby 1.8.7
data/CREDITS.txt CHANGED
@@ -38,7 +38,8 @@ Richard Jennings
38
38
  Feedback
39
39
  --------
40
40
 
41
- Francisco Kiko - "is set" issue
41
+ Gonzalo Suarez - many help
42
+ Francisco Kiko - many help
42
43
  David Goldhirsch - EM participant block
43
44
  dlamotte - set :val => nil
44
45
  jpr5 + asm
data/Rakefile CHANGED
@@ -24,15 +24,16 @@ ruote is an open source ruby workflow engine.
24
24
  gem.test_file = 'test/test.rb'
25
25
 
26
26
  gem.add_dependency 'rufus-json', '>= 0.2.0'
27
- gem.add_dependency 'rufus-cloche', '>= 0.1.14'
27
+ gem.add_dependency 'rufus-cloche', '>= 0.1.16'
28
28
  gem.add_dependency 'rufus-dollar'
29
29
  gem.add_dependency 'rufus-lru'
30
30
  gem.add_dependency 'rufus-mnemo', '>= 1.1.0'
31
- gem.add_dependency 'rufus-scheduler', '>= 2.0.3'
31
+ gem.add_dependency 'rufus-scheduler', '>= 2.0.5'
32
32
  gem.add_dependency 'rufus-treechecker', '>= 1.0.3'
33
33
 
34
- gem.add_development_dependency 'json'
34
+ gem.add_development_dependency 'rake'
35
35
  gem.add_development_dependency 'yard'
36
+ gem.add_development_dependency 'json'
36
37
  gem.add_development_dependency 'builder'
37
38
  gem.add_development_dependency 'mailtrap'
38
39
  gem.add_development_dependency 'jeweler'
data/TODO.txt CHANGED
@@ -191,6 +191,8 @@
191
191
  [o] StorageParticipant#query(wfid, participant_name, {fields})
192
192
  [x] break fs_history, prepare for dm_history
193
193
  [o] part = engine.register_participant :alpha, StorageParticipant should work...
194
+ [o] concurrence :merge_type => 'stack'
195
+ [o] CompositeStorage.new('msgs' => AmqpStorage.new(''), ...)
194
196
 
195
197
  [ ] exp : exp (restricted form of eval ?)
196
198
  [ ] exp : case (is it necessary ?)
@@ -301,9 +303,17 @@
301
303
  [ ] implement pause engine
302
304
  [ ] implement pause process
303
305
 
304
- [ ] engine.on_error = 'participant_name'
306
+ [ ] engine.on_error = 'participant_name' // 'subprocess_name'
305
307
 
306
308
  [ ] "business days" plugin
307
309
 
308
310
  [ ] issue with ruote-kit and inpa participants...
309
311
 
312
+ [ ] let the storage participant leverage Ruote::FlowExpressionId.from_id(s)
313
+
314
+ [ ] participant :ref => '${f:nada}', :or => 'xyz'
315
+ (look at OpenWFE manual, this feature already existed in there)
316
+ http://www.openwfe.org/manual/ch06s02.html#expression_participant
317
+ else-ref... list of participants...
318
+ ref="alpha && bravo", ref="alpha||bravo" (|| parallel :( )
319
+
@@ -90,7 +90,7 @@ module Ruote::Exp
90
90
  # highest and lowest refer to the position in the list of branch. It's useful
91
91
  # to set a fixed winner.
92
92
  #
93
- # concurrence :merge => highest do
93
+ # concurrence :merge => :highest do
94
94
  # alpha
95
95
  # bravo
96
96
  # end
@@ -109,6 +109,30 @@ module Ruote::Exp
109
109
  # a new field for each branch. The name of each field is the index of the
110
110
  # branch from '0' to ...
111
111
  #
112
+ # :stack will stack the workitems coming back from the concurrence branches
113
+ # in an array whose order is determined by the :merge attributes. The array
114
+ # is placed in the 'stack' field of the resulting workitem.
115
+ # Note that the :stack merge_type also creates a 'stack_attributes' field
116
+ # and populates it with the expanded attributes of the concurrence.
117
+ #
118
+ # Thus
119
+ #
120
+ # sequence do
121
+ # concurrence :merge => :highest, :merge_type => :stack do
122
+ # reviewer1
123
+ # reviewer2
124
+ # end
125
+ # editor
126
+ # end
127
+ #
128
+ # will see the 'editor' receive a workitem whose fields look like :
129
+ #
130
+ # { 'stack' => [{ ... reviewer1 fields ... }, { ... reviewer2 fields ... }],
131
+ # 'stack_attributes' => { 'merge'=> 'highest', 'merge_type' => 'stack' } }
132
+ #
133
+ # This could prove useful for participant having to deal with multiple merge
134
+ # strategy results.
135
+ #
112
136
  #
113
137
  # === :over_if (and :over_unless)
114
138
  #
@@ -140,7 +164,7 @@ module Ruote::Exp
140
164
  h.ccount = nil if h.ccount < 1
141
165
 
142
166
  h.cmerge = att(:merge, %w[ first last highest lowest ])
143
- h.cmerge_type = att(:merge_type, %w[ override mix isolate ])
167
+ h.cmerge_type = att(:merge_type, %w[ override mix isolate stack ])
144
168
  h.remaining = att(:remaining, %w[ cancel forget ])
145
169
 
146
170
  h.workitems = (h.cmerge == 'first' || h.cmerge == 'last') ? [] : {}
@@ -201,7 +201,7 @@ module Ruote::Exp
201
201
 
202
202
  timeout =
203
203
  attribute(:timeout) ||
204
- (p_info.respond_to?(:timeout) ? p_info.timeout : nil) ||
204
+ (p_info.timeout rescue nil) ||
205
205
  (p_info.is_a?(Array) ? p_info.last['timeout'] : nil)
206
206
 
207
207
  do_schedule_timeout(timeout)
@@ -45,18 +45,25 @@ module Ruote::Exp
45
45
 
46
46
  return source if type == 'override'
47
47
 
48
- source['fields'] = {
49
- index.to_s => source['fields']
50
- } if target == nil && type == 'isolate'
51
- #
52
- # index.to_s since JSON wants string keys.
48
+ if target == nil
49
+ case type
50
+ when 'isolate'
51
+ source['fields'] = { index.to_s => source['fields'] }
52
+ when 'stack'
53
+ source['fields'] = { 'stack' => [ source['fields'] ] }
54
+ end
55
+ end
53
56
 
54
57
  return source unless target
55
58
 
56
- if type == 'mix'
57
- target['fields'].merge!(source['fields'])
58
- else # 'isolate'
59
- target['fields'][index.to_s] = source['fields']
59
+ case type
60
+ when 'mix'
61
+ target['fields'].merge!(source['fields'])
62
+ when 'stack'
63
+ target['fields']['stack'] << source['fields']
64
+ target['fields']['stack_attributes'] = expand_atts
65
+ else # 'isolate'
66
+ target['fields'][index.to_s] = source['fields']
60
67
  end
61
68
 
62
69
  target
data/lib/ruote/fei.rb CHANGED
@@ -92,7 +92,7 @@ module Ruote
92
92
 
93
93
  FlowExpressionId.new(
94
94
  'engine_id' => engine_id,
95
- 'expid' => ss[0], 'sub_wfid' => ss[1], 'wfid' => ss[2])
95
+ 'expid' => ss[-3], 'sub_wfid' => ss[-2], 'wfid' => ss[-1])
96
96
  end
97
97
 
98
98
  # Returns the last number in the expid. For instance, if the expid is
data/lib/ruote/parser.rb CHANGED
@@ -65,7 +65,8 @@ module Ruote
65
65
  end
66
66
 
67
67
  raise ArgumentError.new(
68
- "doesn't know how to parse definition (#{definition.class})")
68
+ "doesn't know how to parse definition (#{definition.class}) " +
69
+ "or error in process definition")
69
70
  end
70
71
 
71
72
  # Class method for parsing process definition (XML, Ruby, from file or
@@ -31,8 +31,7 @@ require 'ruote/part/template'
31
31
  module Ruote
32
32
 
33
33
  #
34
- # A very stupid SMTP participant, doesn't even care about formatting its
35
- # messages. This class is meant as a base for more complex email participants.
34
+ # A very stupid SMTP participant.
36
35
  #
37
36
  # == options
38
37
  #
@@ -43,10 +42,11 @@ module Ruote
43
42
  # * :template - a String template for the mail message
44
43
  # * :notification - when set to true, the flow will resume immediately after having sent the email
45
44
  #
45
+ #
46
46
  # == :template
47
47
  #
48
48
  # @engine.register_participant(
49
- # :no_good_notification
49
+ # :no_good_notification,
50
50
  # Ruote::SmtpParticipant,
51
51
  # :server => 'smtp.example.com'
52
52
  # :port => 25,
@@ -59,33 +59,18 @@ module Ruote
59
59
  # process definitions (in this example, the workitem field email_subject will
60
60
  # be used as the subject of the email...)
61
61
  #
62
- # == block template
63
- #
64
- # Whereas the :template option accepts a String, the block template may
65
- # be useful when more complex templates are to be computed.
66
62
  #
67
- # @engine.register_participant(
68
- # :no_good_notification
69
- # Ruote::SmtpParticipant,
70
- # :server => 'smtp.example.com'
71
- # :port => 25,
72
- # :to => 'toto@example.com',
73
- # :from => 'john@example.com',
74
- # :notification => true
75
- # ) do
63
+ # == :to or workitem.fields['email_target']
76
64
  #
77
- # s = []
78
- # s << "From: the boss"
79
- # s << "Date: ${r:Time.now.rfc2822}"
80
- # s << "Subject: ${f:email_subject}\n"
81
- # s << ""
82
- # 3.times { s << "this is no good." }
65
+ # The target of the email is either given via the workitem field
66
+ # 'email_target', either by the option :to. The workitem field takes
67
+ # precedence if both are present.
83
68
  #
84
- # s.join("\n")
85
- # end
69
+ # This parameter/option may be either a single (string) email address, either
70
+ # an array of (string) email addresses.
86
71
  #
87
72
  #
88
- # == mail listener
73
+ # == final note : mail listener
89
74
  #
90
75
  # This participant cannot read POP/IMAP accounts for you. You have to
91
76
  # use a mail listener or get a web reply by placing a link in the message...
@@ -95,33 +80,29 @@ module Ruote
95
80
  include LocalParticipant
96
81
  include TemplateMixin
97
82
 
98
- def initialize (opts={}, &block)
99
-
100
- @server = opts[:server] || '127.0.0.1'
101
- @port = opts[:port] || 25
83
+ def initialize (opts)
102
84
 
103
- @from = opts[:from]
104
- @to = opts[:to]
105
-
106
- @template = opts[:template]
107
- @block_template = block
108
-
109
- @notification = opts[:notification]
85
+ @opts = opts.inject({}) { |h, (k, v)| h[k.to_s] = v; h }
110
86
  end
111
87
 
112
88
  def consume (workitem)
113
89
 
114
- to = workitem.fields['email_target'] || @to
90
+ to = workitem.fields['email_target'] || @opts['to']
115
91
  to = Array(to)
116
92
 
117
93
  text = render_template(
118
- Ruote::Exp::FlowExpression.fetch(@context, workitem.fei.to_h), workitem)
94
+ @opts['template'],
95
+ Ruote::Exp::FlowExpression.fetch(@context, workitem.fei.to_h),
96
+ workitem)
97
+
98
+ server = @opts['server'] || '127.0.0.1'
99
+ port = @opts['port'] || 25
119
100
 
120
- Net::SMTP.start(@server, @port) do |smtp|
121
- smtp.send_message(text, @from, *to)
101
+ Net::SMTP.start(server, port) do |smtp|
102
+ smtp.send_message(text, @opts['from'] || 'ruote@example.org', *to)
122
103
  end
123
104
 
124
- reply_to_engine(workitem) if @notification
105
+ reply_to_engine(workitem) if @opts['notification']
125
106
  end
126
107
 
127
108
  def cancel (fei, flavour)
@@ -31,7 +31,24 @@ module Ruote
31
31
  # A participant that stores the workitem in the same storage used by the
32
32
  # engine and the worker(s).
33
33
  #
34
- # Does not thread by default.
34
+ # part = engine.register_participant 'alfred', Ruote::StorageParticipant
35
+ #
36
+ # # ... a bit later
37
+ #
38
+ # puts "workitems still open : "
39
+ # part.each do |workitem|
40
+ # puts "#{workitem.fei.wfid} - #{workitem.fields['params']['task']}"
41
+ # end
42
+ #
43
+ # # ... when done with a workitem
44
+ #
45
+ # part.reply(workitem)
46
+ # # this will remove the workitem from the storage and hand it back
47
+ # # to the engine
48
+ #
49
+ # Does not thread by default (the engine will not spawn a dedicated thread
50
+ # to handle the delivery to this participant, the workitem will get stored
51
+ # via the main engine thread and basta).
35
52
  #
36
53
  class StorageParticipant
37
54
 
@@ -44,6 +61,8 @@ module Ruote
44
61
 
45
62
  if engine_or_options.respond_to?(:context)
46
63
  @context = engine_or_options.context
64
+ elsif engine_or_options.is_a?(Ruote::Context)
65
+ @context = engine_or_options
47
66
  else
48
67
  options = engine_or_options
49
68
  end
@@ -22,41 +22,57 @@
22
22
  # Made in Japan.
23
23
  #++
24
24
 
25
+ require 'rufus/json'
26
+ require 'ruote/util/dollar'
27
+
25
28
 
26
29
  module Ruote
27
30
 
28
31
  #
29
- # This mixin expects that the class that includes it has a @template
30
- # or a @block_template instance variable.
32
+ # Template rendering helper.
33
+ #
34
+ # (Currently only used by the SmtpParticipant, could prove useful for
35
+ # custom participants)
31
36
  #
32
37
  module TemplateMixin
33
38
 
34
- def render_template (flow_expression, workitem)
39
+ def render_template (template, flow_expression, workitem)
35
40
 
36
- template = if @block_template
41
+ template = (File.read(template) rescue nil) if is_a_file?(template)
37
42
 
38
- case @block_template.arity
39
- when 1 then @block_template.call(workitem)
40
- when 2 then @block_template.call(workitem, flow_expression)
41
- else @block_template.call(workitem, flow_expression, self)
42
- end
43
+ return render_default_template(workitem) unless template
43
44
 
44
- elsif @template
45
+ template = template.to_s
46
+ workitem = workitem.to_h if workitem.respond_to?(:to_h)
45
47
 
46
- @template.is_a?(File) ? @template.read : @template.to_s
48
+ Ruote.dosub(template, flow_expression, workitem)
49
+ end
47
50
 
48
- else
51
+ # Simply returns a pretty-printed view of the workitem
52
+ #
53
+ def render_default_template (workitem)
49
54
 
50
- nil
55
+ workitem = workitem.to_h if workitem.respond_to?(:to_h)
56
+
57
+ s = []
58
+ s << "workitem for #{workitem['participant_name']}"
59
+ s << ''
60
+ s << Rufus::Json.encode(workitem['fei'])
61
+ s << ''
62
+ workitem['fields'].keys.sort.each do |key|
63
+ s << " - '#{key}' ==> #{Rufus::Json.encode(workitem['fields'][key])}"
51
64
  end
65
+ s.join("\n")
66
+ end
52
67
 
53
- raise(
54
- ArgumentError.new('no @template or @block_template found')
55
- ) unless template
68
+ protected
56
69
 
57
- workitem = workitem.to_h if workitem.respond_to?(:to_h)
70
+ def is_a_file? (s)
58
71
 
59
- Ruote.dosub(template, flow_expression, workitem)
72
+ return false unless s
73
+ return false if s.index("\n")
74
+
75
+ File.exist?(s)
60
76
  end
61
77
  end
62
78
  end
@@ -32,12 +32,6 @@ module Ruote
32
32
  #
33
33
  module StorageBase
34
34
 
35
- def reserve (doc)
36
-
37
- #(delete(doc) != true)
38
- delete(doc).nil?
39
- end
40
-
41
35
  #--
42
36
  # configurations
43
37
  #++
@@ -143,8 +137,8 @@ module Ruote
143
137
  def put_schedule (flavour, owner_fei, s, msg)
144
138
 
145
139
  at = if s.is_a?(Time) # at or every
146
- at
147
- elsif is_cron_string(s) # cron
140
+ s
141
+ elsif Ruote.is_cron_string(s) # cron
148
142
  Rufus::CronLine.new(s).next_time(Time.now + 1)
149
143
  else # at or every
150
144
  Ruote.s_to_at(s)
@@ -210,18 +204,6 @@ module Ruote
210
204
 
211
205
  scheds.select { |sched| sched['at'] <= now }
212
206
  end
213
-
214
- # Waiting for a better implementation of it in rufus-scheduler 2.0.4
215
- #
216
- def is_cron_string (s)
217
-
218
- ss = s.split(' ')
219
-
220
- return false if ss.size < 5 || ss.size > 6
221
- return false if s.match(/\d{4}/)
222
-
223
- true
224
- end
225
207
  end
226
208
  end
227
209