bumbleworks 0.0.48 → 0.0.50

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/.gitignore +4 -1
  2. data/lib/bumbleworks.rb +9 -8
  3. data/lib/bumbleworks/configuration.rb +8 -5
  4. data/lib/bumbleworks/entity.rb +88 -0
  5. data/lib/bumbleworks/participant.rb +6 -7
  6. data/lib/bumbleworks/participant/base.rb +11 -0
  7. data/lib/bumbleworks/participant/entity_interactor.rb +29 -0
  8. data/lib/bumbleworks/participant/error_handler.rb +14 -0
  9. data/lib/bumbleworks/participant/local_participant.rb +11 -0
  10. data/lib/bumbleworks/participant/storage_participant.rb +11 -0
  11. data/lib/bumbleworks/process.rb +64 -0
  12. data/lib/bumbleworks/ruote.rb +12 -4
  13. data/lib/bumbleworks/support.rb +3 -5
  14. data/lib/bumbleworks/task.rb +6 -4
  15. data/lib/bumbleworks/{tasks → task}/base.rb +1 -1
  16. data/lib/bumbleworks/task/finder.rb +39 -3
  17. data/lib/bumbleworks/version.rb +1 -1
  18. data/spec/fixtures/entities/furby.rb +30 -0
  19. data/spec/integration/entity_spec.rb +49 -0
  20. data/spec/integration/history_storage_spec.rb +4 -4
  21. data/spec/integration/sample_application_spec.rb +8 -2
  22. data/spec/lib/bumbleworks/entity_spec.rb +208 -0
  23. data/spec/lib/bumbleworks/{participant_spec.rb → participant/base_spec.rb} +3 -3
  24. data/spec/lib/bumbleworks/participant/entity_interactor_spec.rb +91 -0
  25. data/spec/lib/bumbleworks/{error_handler_participant_spec.rb → participant/error_handler_spec.rb} +1 -1
  26. data/spec/lib/bumbleworks/{local_participant_spec.rb → participant/local_participant_spec.rb} +1 -1
  27. data/spec/lib/bumbleworks/process_spec.rb +147 -0
  28. data/spec/lib/bumbleworks/ruote/exp/broadcast_event_expression_spec.rb +1 -1
  29. data/spec/lib/bumbleworks/ruote/exp/wait_for_event_expression_spec.rb +3 -3
  30. data/spec/lib/bumbleworks/ruote_spec.rb +28 -21
  31. data/spec/lib/bumbleworks/task/finder_spec.rb +9 -0
  32. data/spec/lib/bumbleworks/task_spec.rb +98 -3
  33. data/spec/lib/bumbleworks_spec.rb +14 -7
  34. data/spec/spec_helper.rb +0 -1
  35. data/spec/support/process_testing_helpers.rb +9 -0
  36. metadata +34 -13
  37. data/lib/bumbleworks/error_handler_participant.rb +0 -12
  38. data/lib/bumbleworks/local_participant.rb +0 -9
  39. data/lib/bumbleworks/storage_participant.rb +0 -9
data/.gitignore CHANGED
@@ -14,4 +14,7 @@ spec/reports
14
14
  test/tmp
15
15
  test/version_tmp
16
16
  tmp
17
- .bash_color
17
+ .bash_color
18
+ *.sublime-*
19
+ default.vim
20
+ default.vim.lock
data/lib/bumbleworks.rb CHANGED
@@ -3,17 +3,18 @@ require "bumbleworks/version"
3
3
  require "bumbleworks/configuration"
4
4
  require "bumbleworks/support"
5
5
  require "bumbleworks/process_definition"
6
+ require "bumbleworks/process"
6
7
  require "bumbleworks/task"
7
8
  require "bumbleworks/participant_registration"
8
9
  require "bumbleworks/ruote"
9
10
  require "bumbleworks/hash_storage"
10
- require "bumbleworks/simple_logger"
11
- require "bumbleworks/storage_participant"
12
- require "bumbleworks/local_participant"
13
- require "bumbleworks/participant"
14
11
  require "bumbleworks/error_handler"
12
+ require "bumbleworks/entity"
13
+ require "bumbleworks/participant"
14
+
15
+ # default implementations
16
+ require "bumbleworks/simple_logger"
15
17
  require "bumbleworks/error_logger"
16
- require "bumbleworks/error_handler_participant"
17
18
 
18
19
  module Bumbleworks
19
20
  class UnsupportedMode < StandardError; end
@@ -23,7 +24,6 @@ module Bumbleworks
23
24
 
24
25
  class << self
25
26
  extend Forwardable
26
- attr_accessor :env
27
27
 
28
28
  Configuration.defined_settings.each do |setting|
29
29
  def_delegators :configuration, setting, "#{setting.to_s}="
@@ -136,7 +136,8 @@ module Bumbleworks
136
136
  #
137
137
  def launch!(process_definition_name, *args)
138
138
  extract_entity_from_fields!(args.first || {})
139
- Bumbleworks::Ruote.launch(process_definition_name, *args)
139
+ pid = Bumbleworks::Ruote.launch(process_definition_name, *args)
140
+ Bumbleworks::Process.new(pid)
140
141
  end
141
142
 
142
143
  private
@@ -149,7 +150,7 @@ module Bumbleworks
149
150
  raise InvalidEntity, "Entity#id must be non-null" unless fields[:entity_id]
150
151
  end
151
152
  rescue NoMethodError => e
152
- raise InvalidEntity, "Entity must respond to #id and #class.name"
153
+ raise InvalidEntity, "Entity must respond to #identifier (or #id) and #class.name"
153
154
  end
154
155
  end
155
156
  end
@@ -115,9 +115,11 @@ module Bumbleworks
115
115
  # default: 5 seconds
116
116
  define_setting :timeout
117
117
 
118
- # When errors occur during the exection of a process, errors are captured and dispatched to
119
- # the registerd error handlers. an error handler derives from the Bumbleworks::ErrorHandler
120
- # and will receive the error information through the #on_error method.
118
+ # When errors occur during the execution of a process, errors are captured and dispatched to
119
+ # the registered error handlers. An error handler must take a single initialization argument
120
+ # (the workitem at the point of error), and implement the #on_error method. You can subclass the
121
+ # Bumbleworks::ErrorHandler class for the initializer and workitem entity storage. The default
122
+ # handler (Bumbleworks::ErrorLogger) will simply send the configured logger an ERROR log entry.
121
123
  #
122
124
  # class MySpecialHandler < Bumbleworks::ErrorHandler
123
125
  # def on_error
@@ -126,11 +128,12 @@ module Bumbleworks
126
128
  # end
127
129
  #
128
130
  # For exclusive use:
129
- # Bumbleworks.error_handlers = [MySpeicalHandler, MySpecialHandler2]
131
+ # Bumbleworks.error_handlers = [MySpecialHandler, MySpecialHandler2]
130
132
  #
131
133
  # To append to exisiting handlers:
132
- # Bumbleworks.error_handlers << MySpeicalHandler
134
+ # Bumbleworks.error_handlers << MySpecialHandler
133
135
  #
136
+ # default: Bumbleworks::ErrorLogger
134
137
  define_setting :error_handlers
135
138
 
136
139
  # If #store_history is true, all messages will be logged in the storage under a special
@@ -0,0 +1,88 @@
1
+ module Bumbleworks
2
+ module Entity
3
+ def self.included(klass)
4
+ klass.extend ClassMethods
5
+ end
6
+
7
+ def launch_process(process_name, options = {})
8
+ identifier_column = self.class.processes[process_name.to_sym][:column]
9
+ if (options[:force] == true || (process_identifier = self.send(identifier_column)).nil?)
10
+ workitem_fields = process_fields(process_name.to_sym)
11
+ variables = process_variables(process_name.to_sym)
12
+ process_identifier = Bumbleworks.launch!(process_name.to_s, workitem_fields, variables).wfid
13
+ persist_process_identifier(identifier_column.to_sym, process_identifier)
14
+ end
15
+ Bumbleworks::Process.new(process_identifier)
16
+ end
17
+
18
+ def persist_process_identifier(identifier_column, process_identifier)
19
+ if self.respond_to?(:update)
20
+ update(identifier_column => process_identifier)
21
+ else
22
+ raise "Entity must define #persist_process_identifier method if missing #update method."
23
+ end
24
+ end
25
+
26
+ def processes_by_name
27
+ return {} unless self.class.processes
28
+ process_names = self.class.processes.keys
29
+ process_names.inject({}) do |memo, name|
30
+ pid = self.send(self.class.processes[name][:column])
31
+ memo[name] = pid ? Bumbleworks::Process.new(pid) : nil
32
+ memo
33
+ end
34
+ end
35
+
36
+ def processes
37
+ processes_by_name.values.compact
38
+ end
39
+
40
+ def cancel_all_processes!
41
+ processes.each do |process|
42
+ process.cancel!
43
+ end
44
+ end
45
+
46
+ def tasks(nickname = nil)
47
+ finder = Bumbleworks::Task.for_entity(self)
48
+ finder = finder.by_nickname(nickname) if nickname
49
+ finder
50
+ end
51
+
52
+ def process_fields(process_name = nil)
53
+ { :entity => self }
54
+ end
55
+
56
+ def process_variables(process_name = nil)
57
+ {}
58
+ end
59
+
60
+ def subscribed_events
61
+ processes.values.compact.map(&:subscribed_events).flatten.uniq
62
+ end
63
+
64
+ def is_waiting_for?(event)
65
+ subscribed_events.include? event.to_s
66
+ end
67
+
68
+ module ClassMethods
69
+ attr_reader :processes
70
+
71
+ def process(process_name, options = {})
72
+ options[:column] ||= process_identifier_column(process_name)
73
+ (@processes ||= {})[process_name.to_sym] = options
74
+ end
75
+
76
+ def entity_type
77
+ Bumbleworks::Support.tokenize(name)
78
+ end
79
+
80
+ def process_identifier_column(process_name)
81
+ identifier_column = "#{process_name}_process_identifier"
82
+ identifier_column.gsub!(/^#{entity_type}_/, '')
83
+ identifier_column.gsub!(/process_process/, 'process')
84
+ identifier_column.to_sym
85
+ end
86
+ end
87
+ end
88
+ end
@@ -1,11 +1,10 @@
1
- require "bumbleworks/local_participant"
1
+ require "bumbleworks/participant/storage_participant"
2
+ require "bumbleworks/participant/local_participant"
3
+ require "bumbleworks/participant/base"
4
+ require "bumbleworks/participant/error_handler"
5
+ require "bumbleworks/participant/entity_interactor"
2
6
 
3
7
  module Bumbleworks
4
- class Participant
5
- include LocalParticipant
6
-
7
- def on_cancel
8
- # default no-op method
9
- end
8
+ module Participant
10
9
  end
11
10
  end
@@ -0,0 +1,11 @@
1
+ module Bumbleworks
2
+ module Participant
3
+ class Base
4
+ include LocalParticipant
5
+
6
+ def on_cancel
7
+ # default no-op method
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,29 @@
1
+ module Bumbleworks
2
+ module Participant
3
+ class EntityInteractor < Bumbleworks::Participant::Base
4
+ def on_workitem
5
+ method_name = workitem.fields['params']['method'] ||
6
+ workitem.fields['params']['to'] ||
7
+ workitem.fields['params']['for']
8
+ result_field = workitem.fields['params']['and_save_as']
9
+ arguments = workitem.fields['params']['arguments'] ||
10
+ workitem.fields['params']['with']
11
+ result = call_method(method_name, :save_as => result_field, :args => arguments)
12
+ reply
13
+ end
14
+
15
+ def call_method(method_name, options = {})
16
+ result = if options[:args]
17
+ options[:args] = [options[:args]] if options[:args].is_a?(Hash)
18
+ entity.send(method_name, *options[:args])
19
+ else
20
+ entity.send(method_name)
21
+ end
22
+ if result && options[:save_as]
23
+ workitem.fields[options[:save_as]] = result
24
+ end
25
+ result
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,14 @@
1
+ module Bumbleworks
2
+ module Participant
3
+ class ErrorHandler < Bumbleworks::Participant::Base
4
+ def on_workitem
5
+ return if (error_handlers = Bumbleworks.error_handlers).empty?
6
+
7
+ error_handlers.each do |error_handler|
8
+ error_handler.new(workitem).on_error
9
+ end
10
+ reply
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ require "ruote/part/local_participant"
2
+ require "bumbleworks/workitem_entity_storage"
3
+
4
+ module Bumbleworks
5
+ module Participant
6
+ module LocalParticipant
7
+ include ::Ruote::LocalParticipant
8
+ include WorkitemEntityStorage
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Bumbleworks
2
+ module Participant
3
+ class StorageParticipant < ::Ruote::StorageParticipant
4
+ def on_workitem
5
+ return_value = super
6
+ Bumbleworks::Task.new(self[workitem.sid]).on_dispatch
7
+ return return_value
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,64 @@
1
+ module Bumbleworks
2
+ class Process
3
+ attr_reader :id
4
+
5
+ def initialize(wfid)
6
+ @id = wfid
7
+ end
8
+
9
+ alias_method :wfid, :id
10
+
11
+ def ==(other)
12
+ wfid == other.wfid
13
+ end
14
+
15
+ def tasks
16
+ Bumbleworks::Task.for_process(wfid)
17
+ end
18
+
19
+ def trackers
20
+ Bumbleworks.dashboard.get_trackers.values.select { |attrs|
21
+ attrs['msg']['fei'] && attrs['msg']['fei']['wfid'] == id
22
+ }
23
+ end
24
+
25
+ def all_subscribed_tags
26
+ events = trackers.inject({}) do |memo, t|
27
+ if t['wfid'].nil?
28
+ (memo[:global] ||= []).concat t['conditions']['tag']
29
+ else
30
+ (memo[t['wfid']] ||= []).concat t['conditions']['tag']
31
+ end
32
+ memo
33
+ end
34
+ end
35
+
36
+ def subscribed_events
37
+ all_subscribed_tags[:global]
38
+ end
39
+
40
+ def is_waiting_for?(event)
41
+ subscribed_events.include? event.to_s
42
+ end
43
+
44
+ def kill!
45
+ Bumbleworks.kill_process!(wfid)
46
+ end
47
+
48
+ def cancel!
49
+ Bumbleworks.cancel_process!(wfid)
50
+ end
51
+
52
+ def process_status
53
+ Bumbleworks.dashboard.process(id)
54
+ end
55
+
56
+ def method_missing(method, *args)
57
+ ps = process_status
58
+ if ps.respond_to?(method)
59
+ return ps.send(method, *args)
60
+ end
61
+ super
62
+ end
63
+ end
64
+ end
@@ -56,7 +56,7 @@ module Bumbleworks
56
56
  while dashboard.process(wfid)
57
57
  if (Time.now - start_time) > options[:timeout]
58
58
  error_type = options[:method] == :cancel ? CancelTimeout : KillTimeout
59
- raise error_type, "Process #{options[:method]} taking too long - #{dashboard.processes.count} processes remain. Errors: #{dashboard.errors}"
59
+ raise error_type, "Process #{options[:method]} for wfid '#{wfid}' taking too long. Errors: #{dashboard.errors(wfid)}"
60
60
  end
61
61
  sleep 0.1
62
62
  end
@@ -100,14 +100,22 @@ module Bumbleworks
100
100
 
101
101
  def register_participants(&block)
102
102
  dashboard.register(&block) if block
103
+ register_entity_interactor
103
104
  register_error_handler
104
105
  set_catchall_if_needed
105
106
  dashboard.participant_list
106
107
  end
107
108
 
109
+ def register_entity_interactor
110
+ unless dashboard.participant_list.any? { |part| part.regex == "^(ask|tell)_entity$" }
111
+ entity_interactor = ::Ruote::ParticipantEntry.new(["^(ask|tell)_entity$", ["Bumbleworks::Participant::EntityInteractor", {}]])
112
+ dashboard.participant_list = dashboard.participant_list.unshift(entity_interactor)
113
+ end
114
+ end
115
+
108
116
  def register_error_handler
109
117
  unless dashboard.participant_list.any? { |part| part.regex == '^error_handler_participant$' }
110
- error_handler_participant = ::Ruote::ParticipantEntry.new(['^error_handler_participant$', ["Bumbleworks::ErrorHandlerParticipant", {}]])
118
+ error_handler_participant = ::Ruote::ParticipantEntry.new(['^error_handler_participant$', ["Bumbleworks::Participant::ErrorHandler", {}]])
111
119
  dashboard.participant_list = dashboard.participant_list.unshift(error_handler_participant)
112
120
  dashboard.on_error = 'error_handler_participant'
113
121
  end
@@ -122,8 +130,8 @@ module Bumbleworks
122
130
  def set_catchall_if_needed
123
131
  last_participant = dashboard.participant_list.last
124
132
  unless last_participant && last_participant.regex == "^.+$" &&
125
- ["Ruote::StorageParticipant", "Bumbleworks::StorageParticipant"].include?(last_participant.classname)
126
- catchall = ::Ruote::ParticipantEntry.new(["^.+$", ["Bumbleworks::StorageParticipant", {}]])
133
+ ["Ruote::StorageParticipant", "Bumbleworks::Participant::StorageParticipant"].include?(last_participant.classname)
134
+ catchall = ::Ruote::ParticipantEntry.new(["^.+$", ["Bumbleworks::Participant::StorageParticipant", {}]])
127
135
  dashboard.participant_list = dashboard.participant_list.push(catchall)
128
136
  end
129
137
  end
@@ -24,11 +24,9 @@ module Bumbleworks
24
24
  constant = Object
25
25
 
26
26
  name_parts.each do |name_part|
27
- constant_defined = if Module.method(:const_defined?).arity == 1
28
- constant.const_defined?(name_part)
29
- else
30
- constant.const_defined?(name_part, false)
31
- end
27
+ const_defined_args = [name_part]
28
+ const_defined_args << false unless Module.method(:const_defined?).arity == 1
29
+ constant_defined = constant.const_defined?(*const_defined_args)
32
30
  constant = constant_defined ? constant.const_get(name_part) : constant.const_missing(name_part)
33
31
  end
34
32
  constant
@@ -1,5 +1,5 @@
1
- require "bumbleworks/tasks/base"
2
1
  require "bumbleworks/workitem_entity_storage"
2
+ require "bumbleworks/task/base"
3
3
  require "bumbleworks/task/finder"
4
4
 
5
5
  module Bumbleworks
@@ -80,9 +80,11 @@ module Bumbleworks
80
80
  end
81
81
 
82
82
  def extend_module
83
- extend Bumbleworks::Tasks::Base
84
- extend task_module if nickname
85
- rescue NameError
83
+ extend Bumbleworks::Task::Base
84
+ begin
85
+ extend task_module if nickname
86
+ rescue NameError
87
+ end
86
88
  end
87
89
 
88
90
  def task_module
@@ -1,5 +1,5 @@
1
1
  module Bumbleworks
2
- module Tasks
2
+ class Task
3
3
  module Base
4
4
  def before_update(params); end
5
5
  def after_update(params); end
@@ -5,7 +5,14 @@ module Bumbleworks
5
5
 
6
6
  def initialize(queries = [], task_class = Bumbleworks::Task)
7
7
  @queries = queries
8
+ @queries << proc { |wi| wi['fields']['params']['task'] }
8
9
  @task_class = task_class
10
+ @task_filters = []
11
+ @wfids = nil
12
+ end
13
+
14
+ def available
15
+ unclaimed.completable
9
16
  end
10
17
 
11
18
  def by_nickname(nickname)
@@ -46,13 +53,33 @@ module Bumbleworks
46
53
  self
47
54
  end
48
55
 
56
+ def for_processes(processes)
57
+ process_ids = (processes || []).map { |p|
58
+ p.is_a?(Bumbleworks::Process) ? p.wfid : p
59
+ }
60
+ @wfids = process_ids
61
+ self
62
+ end
63
+
64
+ def for_process(process)
65
+ for_processes([process])
66
+ end
67
+
49
68
  def all
50
- workitems = Bumbleworks.dashboard.storage_participant.send(:do_select, {}) { |wi|
69
+ return [] if @wfids == []
70
+ workitems = Bumbleworks.dashboard.context.storage.get_many('workitems', @wfids).select { |wi|
51
71
  @queries.all? { |q| q.call(wi) }
72
+ }.collect { |wi|
73
+ ::Ruote::Workitem.new(wi)
52
74
  }
53
75
  from_workitems(workitems)
54
76
  end
55
77
 
78
+ def completable
79
+ @task_filters << proc { |task| task.completable? }
80
+ self
81
+ end
82
+
56
83
  def each(&block)
57
84
  all.each(&block)
58
85
  end
@@ -76,10 +103,19 @@ module Bumbleworks
76
103
 
77
104
  private
78
105
 
106
+ def filter_tasks(tasks)
107
+ @task_filters.empty? ? tasks :
108
+ tasks.select { |task|
109
+ @task_filters.all? { |f| f.call(task) }
110
+ }
111
+ end
112
+
79
113
  def from_workitems(workitems)
80
- workitems.map { |wi|
81
- @task_class.new(wi) if wi.params['task']
114
+ tasks = workitems.map { |wi|
115
+ @task_class.new(wi)
82
116
  }.compact
117
+
118
+ filter_tasks(tasks)
83
119
  end
84
120
  end
85
121
  end