bpmn 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpotFlow
4
+ module Bpmn
5
+ class Extension
6
+ include ActiveModel::Model
7
+ end
8
+ end
9
+
10
+ module Zeebe
11
+ class AssignmentDefinition < Bpmn::Extension
12
+ attr_accessor :assignee, :candidate_groups, :candidate_users
13
+ end
14
+
15
+ class CalledElement < Bpmn::Extension
16
+ attr_accessor :process_id, :propagate_all_child_variables, :propagate_all_parent_variables
17
+
18
+ def initialize(attributes = {})
19
+ super(attributes.except(:propagate_all_child_variables))
20
+
21
+ @propagate_all_parent_variables = true
22
+ @propagate_all_parent_variables = attributes[:propagate_all_parent_variables] == "true" if attributes[:propagate_all_parent_variables].present?
23
+ @propagate_all_child_variables = attributes[:propagate_all_child_variables] == "true"
24
+ end
25
+ end
26
+
27
+ class CalledDecision < Bpmn::Extension
28
+ attr_accessor :decision_id, :result_variable
29
+ end
30
+
31
+ class FormDefinition < Bpmn::Extension
32
+ attr_accessor :form_key
33
+ end
34
+
35
+ class IoMapping < Bpmn::Extension
36
+ attr_reader :inputs, :outputs
37
+
38
+ def initialize(attributes = {})
39
+ super(attributes.except(:input, :output))
40
+
41
+ @inputs = Array.wrap(attributes[:input]).map { |atts| Parameter.new(atts) } if attributes[:input].present?
42
+ @outputs = Array.wrap(attributes[:output]).map { |atts| Parameter.new(atts) } if attributes[:output].present?
43
+ end
44
+ end
45
+
46
+ class Parameter < Bpmn::Extension
47
+ attr_accessor :source, :target
48
+ end
49
+
50
+ class Script < Bpmn::Extension
51
+ attr_accessor :expression, :result_variable
52
+ end
53
+
54
+ class Subscription < Bpmn::Extension
55
+ attr_accessor :correlation_key
56
+ end
57
+
58
+ class TaskDefinition < Bpmn::Extension
59
+ attr_accessor :type, :retries
60
+ end
61
+
62
+ class TaskHeaders < Bpmn::Extension
63
+ attr_accessor :headers
64
+
65
+ def initialize(attributes = {})
66
+ super(attributes.except(:header))
67
+
68
+ @headers = HashWithIndifferentAccess.new
69
+ Array.wrap(attributes[:header]).each { |header| @headers[header[:key]] = header[:value] }
70
+ end
71
+ end
72
+
73
+ class TaskSchedule < Bpmn::Extension
74
+ attr_accessor :due_date, :follow_up_date
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpotFlow
4
+ module Bpmn
5
+ class Flow < Element
6
+ attr_accessor :source_ref, :target_ref
7
+ attr_accessor :source, :target
8
+
9
+ def initialize(attributes = {})
10
+ super(attributes.except(:source_ref, :target_ref))
11
+
12
+ @source_ref = attributes[:source_ref]
13
+ @target_ref = attributes[:target_ref]
14
+ @source = nil
15
+ @target = nil
16
+ end
17
+
18
+ def inspect
19
+ parts = ["#<#{self.class.name.gsub(/SpotFlow::/, "")} @id=#{id.inspect}"]
20
+ parts << "@source_ref=#{source_ref.inspect}" if source_ref
21
+ parts << "@target_ref=#{target_ref.inspect}" if target_ref
22
+ parts.join(" ") + ">"
23
+ end
24
+ end
25
+
26
+ class Association < Flow
27
+ end
28
+
29
+ class SequenceFlow < Flow
30
+ attr_accessor :condition
31
+
32
+ def initialize(attributes = {})
33
+ super(attributes.except(:condition))
34
+
35
+ @condition = attributes[:condition_expression]
36
+ end
37
+
38
+ def evaluate(execution)
39
+ return true unless condition
40
+ execution.evaluate_condition(condition)
41
+ end
42
+ end
43
+
44
+ class TextAnnotation < Flow
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpotFlow
4
+ module Bpmn
5
+ class Gateway < Step
6
+
7
+ def execute(execution)
8
+ if converging?
9
+ if is_enabled?(execution)
10
+ return leave(execution)
11
+ else
12
+ execution.wait
13
+ end
14
+ else
15
+ return leave(execution)
16
+ end
17
+ end
18
+
19
+ #
20
+ # Algorithm from https://researcher.watson.ibm.com/researcher/files/zurich-hvo/bpm2010-1.pdf
21
+ #
22
+ def is_enabled?(execution)
23
+ filled = []
24
+ empty = []
25
+
26
+ incoming.each { |flow| execution.tokens_in.include?(flow.id) ? filled.push(flow) : empty.push(flow) }
27
+
28
+ # Filled slots don't need to be searched for tokens
29
+ index = 0
30
+ while (index < filled.length)
31
+ current_flow = filled[index]
32
+ current_flow.source.incoming.each do |incoming_flow|
33
+ filled.push(incoming_flow) unless filled.include?(incoming_flow) || incoming_flow.target == self
34
+ end
35
+ index = index + 1
36
+ end
37
+
38
+ # Empty slots need to be searched for tokens
39
+ index = 0
40
+ while (index < empty.length)
41
+ current_flow = empty[index]
42
+ current_flow.source.incoming.each do |incoming_flow|
43
+ empty.push(incoming_flow) unless filled.include?(incoming_flow) || empty.include?(incoming_flow) || incoming_flow.target == self
44
+ end
45
+ index = index + 1
46
+ end
47
+
48
+ empty_ids = empty.map { |g| g.id }
49
+
50
+ # If there are empty slots with tokens we need to wait
51
+ return false if (empty_ids & execution.parent.tokens).length > 0
52
+ return true
53
+ end
54
+ end
55
+
56
+ class ExclusiveGateway < Gateway
57
+ # RULE: Only one flow is taken
58
+ def outgoing_flows(step_execution)
59
+ flows = super
60
+ return [flows.first]
61
+ end
62
+ end
63
+
64
+ class ParallelGateway < Gateway
65
+ # RULE: All flows are taken
66
+ end
67
+
68
+ class InclusiveGateway < Gateway
69
+ # RULE: All valid flows are taken
70
+ # NOTE: Camunda 8 only support diverging but not converging inclusive gateways
71
+ end
72
+
73
+ class EventBasedGateway < Gateway
74
+ #
75
+ # RULE: when an event created from an event gateway is caught,
76
+ # all other waiting events must be canceled.
77
+ #
78
+ def cancel_waiting_events(execution)
79
+ execution.targets.each do |target_execution|
80
+ target_execution.terminate unless target_execution.ended?
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpotFlow
4
+ module Bpmn
5
+ class Process < Step
6
+ attr_accessor :is_executable
7
+
8
+ attr_accessor :start_events, :end_events, :intermediate_catch_events, :intermediate_throw_events, :boundary_events
9
+ attr_accessor :tasks, :user_tasks, :service_tasks, :script_tasks, :send_tasks, :receive_tasks, :manual_tasks, :business_rule_tasks, :call_activities
10
+ attr_accessor :sub_processes, :ad_hoc_sub_processes
11
+ attr_accessor :sequence_flows, :message_flows, :associations, :data_associations, :data_inputs, :data_outputs
12
+ attr_accessor :data_objects, :data_stores, :data_stores_references
13
+ attr_accessor :gateways, :exclusive_gateways, :parallel_gateways, :inclusive_gateways, :event_based_gateways, :complex_gateways
14
+
15
+ attr_accessor :parent
16
+
17
+ def initialize(attributes = {})
18
+ super(attributes.slice(:id, :name, :extension_elements, :incoming, :outgoing, :default))
19
+
20
+ @is_executable = attributes[:is_executable] == ("true" || true)
21
+
22
+ @start_events = Array.wrap(attributes[:start_event]).map { |se| StartEvent.new(se) }
23
+ @end_events = Array.wrap(attributes[:end_event]).map { |ee| EndEvent.new(ee) }
24
+ @intermediate_catch_events = Array.wrap(attributes[:intermediate_catch_event]).map { |ice| IntermediateCatchEvent.new(ice) }
25
+ @intermediate_throw_events = Array.wrap(attributes[:intermediate_throw_event]).map { |ite| IntermediateThrowEvent.new(ite) }
26
+ @boundary_events = Array.wrap(attributes[:boundary_event]).map { |be| BoundaryEvent.new(be) }
27
+ @tasks = Array.wrap(attributes[:task]).map { |t| Task.new(t) }
28
+ @user_tasks = Array.wrap(attributes[:user_task]).map { |ut| UserTask.new(ut) }
29
+ @service_tasks = Array.wrap(attributes[:service_task]).map { |st| ServiceTask.new(st) }
30
+ @script_tasks = Array.wrap(attributes[:script_task]).map { |st| ScriptTask.new(st) }
31
+ @business_rule_tasks = Array.wrap(attributes[:business_rule_task]).map { |brt| BusinessRuleTask.new(brt) }
32
+ @call_activities = Array.wrap(attributes[:call_activity]).map { |ca| CallActivity.new(ca) }
33
+ @sub_processes = Array.wrap(attributes[:sub_process]).map { |sp| SubProcess.new(sp) }
34
+ @ad_hoc_sub_processes = Array.wrap(attributes[:ad_hoc_sub_processe]).map { |ahsp| AdHocSubProcess.new(ahsp) }
35
+ @exclusive_gateways = Array.wrap(attributes[:exclusive_gateway]).map { |eg| ExclusiveGateway.new(eg) }
36
+ @parallel_gateways = Array.wrap(attributes[:parallel_gateway]).map { |pg| ParallelGateway.new(pg) }
37
+ @inclusive_gateways = Array.wrap(attributes[:inclusive_gateway]).map { |ig| InclusiveGateway.new(ig) }
38
+ @event_based_gateways = Array.wrap(attributes[:event_based_gateway]).map { |ebg| EventBasedGateway.new(ebg) }
39
+ @sequence_flows = Array.wrap(attributes[:sequence_flow]).map { |sf| SequenceFlow.new(sf) }
40
+ end
41
+
42
+ def elements
43
+ @elements ||= {}.tap do |elements|
44
+ elements.merge!(start_events.index_by(&:id))
45
+ elements.merge!(end_events.index_by(&:id))
46
+ elements.merge!(intermediate_catch_events.index_by(&:id))
47
+ elements.merge!(intermediate_throw_events.index_by(&:id))
48
+ elements.merge!(boundary_events.index_by(&:id))
49
+ elements.merge!(tasks.index_by(&:id))
50
+ elements.merge!(user_tasks.index_by(&:id))
51
+ elements.merge!(service_tasks.index_by(&:id))
52
+ elements.merge!(script_tasks.index_by(&:id))
53
+ elements.merge!(business_rule_tasks.index_by(&:id))
54
+ elements.merge!(call_activities.index_by(&:id))
55
+ elements.merge!(sub_processes.index_by(&:id))
56
+ elements.merge!(ad_hoc_sub_processes.index_by(&:id))
57
+ elements.merge!(exclusive_gateways.index_by(&:id))
58
+ elements.merge!(parallel_gateways.index_by(&:id))
59
+ elements.merge!(inclusive_gateways.index_by(&:id))
60
+ elements.merge!(event_based_gateways.index_by(&:id))
61
+ elements.merge!(sequence_flows.index_by(&:id))
62
+ end
63
+ end
64
+
65
+ def wire_references(definitions)
66
+ elements.values.each do |element|
67
+ if element.is_a?(Step)
68
+ element.incoming = element.incoming.map { |id| element_by_id(id) }
69
+ element.outgoing = element.outgoing.map { |id| element_by_id(id) }
70
+
71
+ if element.is_a?(Event)
72
+
73
+ element.event_definitions.each do |event_definition|
74
+ if event_definition.is_a?(MessageEventDefinition)
75
+ event_definition.message = definitions.message_by_id(event_definition.message_ref)
76
+ elsif event_definition.is_a?(SignalEventDefinition)
77
+ event_definition.signal = definitions.signal_by_id(event_definition.signal_ref)
78
+ elsif event_definition.is_a?(ErrorEventDefinition)
79
+ event_definition.error = definitions.error_by_id(event_definition.error_ref)
80
+ end
81
+ end
82
+
83
+ if element.is_a?(BoundaryEvent)
84
+ host_element = element_by_id(element.attached_to_ref)
85
+ host_element.attachments << element
86
+ element.attached_to = host_element
87
+ end
88
+
89
+ end
90
+
91
+ if element.is_a?(Gateway)
92
+ element.default = element_by_id(element.default_ref)
93
+ end
94
+
95
+ if element.is_a?(SubProcess)
96
+ element.wire_references(definitions)
97
+ end
98
+
99
+ elsif element.is_a?(SequenceFlow)
100
+ element.source = element_by_id(element.source_ref)
101
+ element.target = element_by_id(element.target_ref)
102
+ end
103
+
104
+ # Not handled participant process ref
105
+ end
106
+ end
107
+
108
+ def element_by_id(id)
109
+ elements[id]
110
+ end
111
+
112
+ def elements_by_type(type)
113
+ elements.select { |e| e.class == type }
114
+ end
115
+
116
+ def default_start_event
117
+ start_events.find { |se| se.event_definitions.empty? }
118
+ end
119
+
120
+ def execute(execution)
121
+ start_event = execution.start_event_id ? element_by_id(execution.start_event_id) : default_start_event
122
+ raise ExecutionErrorNew.new("Process must have at least one start event.") if start_event.blank?
123
+ execution.execute_step(start_event)
124
+ end
125
+
126
+ def inspect
127
+ parts = ["#<#{self.class.name.gsub(/SpotFlow::/, "")} @id=#{id.inspect} @name=#{name.inspect} @is_executable=#{is_executable.inspect}"]
128
+ parts << "@parent=#{parent.inspect}" if parent
129
+ event_attrs = (start_events + intermediate_catch_events + intermediate_throw_events + boundary_events + end_events).compact
130
+ parts << "@events=#{event_attrs.inspect}" unless event_attrs.blank?
131
+ activity_attrs = (tasks + user_tasks + service_tasks + script_tasks + business_rule_tasks + call_activities).compact
132
+ parts << "@activities=#{activity_attrs.inspect}" unless activity_attrs.blank?
133
+ gateway_attrs = (exclusive_gateways + parallel_gateways + inclusive_gateways + event_based_gateways).compact
134
+ parts << "@gateways=#{gateway_attrs.inspect}" unless gateway_attrs.blank?
135
+ sub_process_attrs = (sub_processes + ad_hoc_sub_processes).compact
136
+ parts << "@sub_processes=#{sub_process_attrs.inspect}" unless sub_process_attrs.blank?
137
+ parts << "@sequence_flows=#{sequence_flows.inspect}" unless sequence_flows.blank?
138
+ parts.join(" ") + ">"
139
+ end
140
+ end
141
+
142
+ class SubProcess < Process
143
+ attr_accessor :triggered_by_event
144
+
145
+ def initialize(attributes = {})
146
+ super(attributes.except(:triggered_by_event))
147
+
148
+ @is_executable = false
149
+ @sub_processes = []
150
+ @triggered_by_event = attributes[:triggered_by_event]
151
+ end
152
+
153
+ def execution_ended(execution)
154
+ leave(execution)
155
+ end
156
+ end
157
+
158
+ class AdHocSubProcess < SubProcess
159
+
160
+ end
161
+
162
+ class CallActivity < Activity
163
+ attr_reader :process_id
164
+
165
+ def execute(execution)
166
+ if extension_elements&.called_element&.process_id&.start_with?("=")
167
+ @process_id = SpotFeel.evaluate(extension_elements&.called_element&.process_id, variables: execution.variables)
168
+ else
169
+ @process_id = extension_elements&.called_element&.process_id
170
+ end
171
+
172
+ execution.wait
173
+
174
+ process = execution.context.process_by_id(@process_id) if @process_id
175
+ execution.execute_step(process.default_start_event) if process
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpotFlow
4
+ module Bpmn
5
+ class Step < Element
6
+ attr_accessor :incoming, :outgoing, :default, :default_ref
7
+
8
+ def initialize(attributes = {})
9
+ super(attributes.except(:incoming, :outgoing, :default))
10
+
11
+ @incoming = Array.wrap(attributes[:incoming]) || []
12
+ @outgoing = Array.wrap(attributes[:outgoing]) || []
13
+ @default_ref = attributes[:default]
14
+ end
15
+
16
+ def diverging?
17
+ outgoing.length > 1
18
+ end
19
+
20
+ def converging?
21
+ incoming.length > 1
22
+ end
23
+
24
+ def leave(execution)
25
+ execution.end(false)
26
+ execution.take_all(outgoing_flows(execution))
27
+ end
28
+
29
+ def outgoing_flows(execution)
30
+ flows = []
31
+ outgoing.each do |flow|
32
+ result = flow.evaluate(execution) unless default&.id == flow.id
33
+ flows.push flow if result
34
+ end
35
+ flows = [default] if flows.empty? && default
36
+ return flows
37
+ end
38
+
39
+ def input_mappings
40
+ extension_elements&.io_mapping&.inputs || []
41
+ end
42
+
43
+ def output_mappings
44
+ extension_elements&.io_mapping&.outputs || []
45
+ end
46
+ end
47
+
48
+ class Activity < Step
49
+ attr_accessor :attachments
50
+
51
+ def initialize(attributes = {})
52
+ super(attributes.except(:attachments))
53
+
54
+ @attachments = []
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpotFlow
4
+ module Bpmn
5
+ class Task < Activity
6
+
7
+ def is_automated?
8
+ false
9
+ end
10
+
11
+ def is_manual?
12
+ true
13
+ end
14
+
15
+ def execute(execution)
16
+ execution.wait
17
+ end
18
+
19
+ def signal(execution)
20
+ leave(execution)
21
+ end
22
+
23
+ def result_to_variables(result)
24
+ if result_variable
25
+ return { "#{result_variable}": result }
26
+ else
27
+ if result.is_a? Hash
28
+ result
29
+ else
30
+ {}.tap { |h| h[id.underscore] = result }
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ class UserTask < Task
37
+
38
+ def form_key
39
+ extension_elements&.form_definition&.form_key
40
+ end
41
+
42
+ def assignee
43
+ extension_elements&.assignment_definition&.assignee
44
+ end
45
+
46
+ def candidate_groups
47
+ extension_elements&.assignment_definition&.candidate_groups
48
+ end
49
+
50
+ def candidate_users
51
+ extension_elements&.assignment_definition&.candidate_users
52
+ end
53
+
54
+ def due_date
55
+ extension_elements&.task_schedule&.due_date
56
+ end
57
+
58
+ def follow_up_date
59
+ extension_elements&.task_schedule&.follow_up_date
60
+ end
61
+ end
62
+
63
+ class ServiceTask < Task
64
+ attr_accessor :service
65
+
66
+ def is_automated?
67
+ true
68
+ end
69
+
70
+ def is_manual?
71
+ false
72
+ end
73
+
74
+ def execute(execution)
75
+ execution.wait
76
+ end
77
+
78
+ def task_type
79
+ extension_elements&.task_definition&.type
80
+ end
81
+
82
+ def task_retries
83
+ extension_elements&.task_definition&.retries
84
+ end
85
+
86
+ def headers
87
+ extension_elements&.task_headers&.headers
88
+ end
89
+
90
+ def run(execution)
91
+ if defined?(task_type)
92
+ klass = task_type.constantize
93
+ klass.new.call(execution.parent.variables, headers || {})
94
+ end
95
+ end
96
+ end
97
+
98
+ class ScriptTask < ServiceTask
99
+
100
+ def script
101
+ extension_elements&.script&.expression
102
+ end
103
+
104
+ def result_variable
105
+ extension_elements&.script&.result_variable
106
+ end
107
+
108
+ def run(execution)
109
+ SpotFeel.evaluate(script.delete_prefix("="), variables: execution.parent.variables)
110
+ end
111
+ end
112
+
113
+ class BusinessRuleTask < ServiceTask
114
+
115
+ def decision_id
116
+ extension_elements&.called_decision&.decision_id
117
+ end
118
+
119
+ def result_variable
120
+ extension_elements&.called_decision&.result_variable
121
+ end
122
+
123
+ def run(execution)
124
+ SpotFeel.decide(decision_id, definitions: execution.context.dmn_definitions_by_decision_id(decision_id), variables: execution.parent.variables)
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "bpmn/element"
4
+ require_relative "bpmn/extensions"
5
+ require_relative "bpmn/extension_elements"
6
+ require_relative "bpmn/step"
7
+ require_relative "bpmn/flow"
8
+ require_relative "bpmn/event_definition"
9
+ require_relative "bpmn/event"
10
+ require_relative "bpmn/gateway"
11
+ require_relative "bpmn/task"
12
+ require_relative "bpmn/process"
13
+ require_relative "bpmn/definitions"
14
+
15
+ module SpotFlow
16
+ module Bpmn
17
+ end
18
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpotFlow
4
+ class Context
5
+ attr_reader :sources, :processes, :decisions, :services, :executions
6
+ attr_reader :bpmn_definitions, :dmn_definitions
7
+
8
+ def initialize(sources = [], processes:[], decisions:[], services: {})
9
+ @sources = Array.wrap(sources)
10
+ @processes = Array.wrap(processes)
11
+ @bpmn_definitions = []
12
+ @dmn_definitions = []
13
+ @decisions = Array.wrap(decisions)
14
+ @services = HashWithIndifferentAccess.new((SpotFlow.config.services || {}).merge(services))
15
+
16
+ @sources.each do |source|
17
+ if source.include?("http://www.omg.org/spec/DMN/20180521/DC/")
18
+ definitions = SpotFeel.definitions_from_xml(source)
19
+ @dmn_definitions << definitions
20
+ @decisions += definitions.decisions
21
+ else
22
+ @processes += SpotFlow.processes_from_xml(source)
23
+ end
24
+ end
25
+
26
+ @executions = []
27
+ end
28
+
29
+ def start(process_id: nil, start_event_id: nil, variables: {})
30
+ process = process_id ? process_by_id(process_id) : default_process
31
+ raise ExecutionError.new(process_id ? "Process #{process_id} not found." : "No default process found.") if process.blank?
32
+ execution = Execution.start(context: self, process: process, start_event_id: start_event_id, variables: variables)
33
+ executions << execution
34
+ execution
35
+ end
36
+
37
+ def start_with_message(message_name:, variables: {})
38
+ [].tap do |executions|
39
+ processes.map do |process|
40
+ process.start_events.map do |start_event|
41
+ start_event.message_event_definitions.map do |message_event_definition|
42
+ if message_name == message_event_definition.message_name
43
+ Execution.start(context: self, process: process, variables: variables, start_event_id: start_event.id).tap { |execution| executions.push execution }
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ def restore(execution_state)
52
+ Execution.deserialize(execution_state, context: self).tap do |execution|
53
+ executions << execution
54
+ end
55
+ end
56
+
57
+ def notify_listener(*args)
58
+ SpotFlow.config.listener&.call(args)
59
+ end
60
+
61
+ def default_process
62
+ raise "Multiple processes defined, must identify process" if processes.size > 1
63
+ processes.first
64
+ end
65
+
66
+ def process_by_id(id)
67
+ processes.each do |process|
68
+ return process if process.id == id
69
+ process.sub_processes.each do |sub_process|
70
+ return sub_process if sub_process.id == id
71
+ end
72
+ end
73
+ nil
74
+ end
75
+
76
+ def element_by_id(id)
77
+ processes.each do |process|
78
+ element = process.element_by_id(id)
79
+ return element if element
80
+ end
81
+ nil
82
+ end
83
+
84
+ def execution_by_id(id)
85
+ executions.find { |e| e.id == id }
86
+ end
87
+
88
+ def execution_by_step_id(step_id)
89
+ executions.find { |e| e.step.id == step_id }
90
+ end
91
+
92
+ def decision_by_id(id)
93
+ decisions.find { |d| d.id == id }
94
+ end
95
+
96
+ def dmn_definitions_by_decision_id(decision_id)
97
+ dmn_definitions.find { |definitions| definitions.decisions.find { |decision| decision.id == decision_id } }
98
+ end
99
+
100
+ def inspect
101
+ parts = ["#<Context"]
102
+ parts << "@processes=#{processes.inspect}" if processes.present?
103
+ parts << "@decisions=#{decisions.inspect}" if decisions.present?
104
+ parts << "@executions=#{executions.inspect}" if executions.present?
105
+ parts.join(" ") + ">"
106
+ end
107
+ end
108
+ end