ruote 2.1.7 → 2.1.8

Sign up to get free protection for your applications and to get access to all the features.
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