Class: BPMN::Execution

Inherits:
Object
  • Object
show all
Defined in:
lib/bpmn/execution.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(attributes = {}) ⇒ Execution

Returns a new instance of Execution.



42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/bpmn/execution.rb', line 42

def initialize(attributes={})
  attributes.each do |k, v|
    send("#{k}=", v)
  end
  @id ||= gen_uid
  @status ||= "activated"
  @variables = @variables&.with_indifferent_access || {}.with_indifferent_access
  @tokens_in ||= []
  @tokens_out ||= []
  @message_names ||= []
  @error_names ||= []
  @escalation_names ||= []
  @children ||= []
end

Instance Attribute Details

#attached_to_idObject

Returns the value of attribute attached_to_id.



6
7
8
# File 'lib/bpmn/execution.rb', line 6

def attached_to_id
  @attached_to_id
end

#childrenObject

Returns the value of attribute children.



6
7
8
# File 'lib/bpmn/execution.rb', line 6

def children
  @children
end

#conditionObject

Returns the value of attribute condition.



5
6
7
# File 'lib/bpmn/execution.rb', line 5

def condition
  @condition
end

#contextObject

Returns the value of attribute context.



6
7
8
# File 'lib/bpmn/execution.rb', line 6

def context
  @context
end

#ended_atObject

Returns the value of attribute ended_at.



5
6
7
# File 'lib/bpmn/execution.rb', line 5

def ended_at
  @ended_at
end

#error_namesObject

Returns the value of attribute error_names.



5
6
7
# File 'lib/bpmn/execution.rb', line 5

def error_names
  @error_names
end

#escalation_namesObject

Returns the value of attribute escalation_names.



5
6
7
# File 'lib/bpmn/execution.rb', line 5

def escalation_names
  @escalation_names
end

#idObject

Returns the value of attribute id.



5
6
7
# File 'lib/bpmn/execution.rb', line 5

def id
  @id
end

#message_namesObject

Returns the value of attribute message_names.



5
6
7
# File 'lib/bpmn/execution.rb', line 5

def message_names
  @message_names
end

#parentObject

Returns the value of attribute parent.



6
7
8
# File 'lib/bpmn/execution.rb', line 6

def parent
  @parent
end

#start_event_idObject

Returns the value of attribute start_event_id.



5
6
7
# File 'lib/bpmn/execution.rb', line 5

def start_event_id
  @start_event_id
end

#started_atObject

Returns the value of attribute started_at.



5
6
7
# File 'lib/bpmn/execution.rb', line 5

def started_at
  @started_at
end

#statusObject

Returns the value of attribute status.



5
6
7
# File 'lib/bpmn/execution.rb', line 5

def status
  @status
end

#stepObject

Returns the value of attribute step.



6
7
8
# File 'lib/bpmn/execution.rb', line 6

def step
  @step
end

#timer_expires_atObject

Returns the value of attribute timer_expires_at.



5
6
7
# File 'lib/bpmn/execution.rb', line 5

def timer_expires_at
  @timer_expires_at
end

#tokens_inObject

Returns the value of attribute tokens_in.



5
6
7
# File 'lib/bpmn/execution.rb', line 5

def tokens_in
  @tokens_in
end

#tokens_outObject

Returns the value of attribute tokens_out.



5
6
7
# File 'lib/bpmn/execution.rb', line 5

def tokens_out
  @tokens_out
end

#variablesObject

Returns the value of attribute variables.



5
6
7
# File 'lib/bpmn/execution.rb', line 5

def variables
  @variables
end

Class Method Details

.deserialize(json, context:) ⇒ Object



17
18
19
20
21
22
23
24
# File 'lib/bpmn/execution.rb', line 17

def self.deserialize(json, context:)
  if json.is_a?(String)
    attributes = JSON.parse(json)
  else
    attributes = json
  end
  Execution.from_json(attributes, context: context)
end

.from_json(attributes, context:) ⇒ Object



26
27
28
29
30
31
32
33
34
35
36
# File 'lib/bpmn/execution.rb', line 26

def self.from_json(attributes, context:)
  step_id = attributes.delete("step_id")
  step_type = attributes.delete("step_type")
  step = step_type == "Process" ? context.process_by_id(step_id) : context.element_by_id(step_id)
  child_attributes = attributes.delete("children")
  Execution.new(attributes.merge(step: step, context: context)).tap do |execution|
    execution.children = child_attributes.map do |ca|
      Execution.from_json(ca, context: context).tap { |child| child.parent = execution }
    end if child_attributes
  end
end

.start(context:, process:, variables: {}, start_event_id: nil, parent: nil) ⇒ Object



10
11
12
13
14
15
# File 'lib/bpmn/execution.rb', line 10

def self.start(context:, process:, variables: {}, start_event_id: nil, parent: nil)
  Execution.new(context: context, step: process, variables: variables, start_event_id: start_event_id, parent: parent).tap do |execution|
    context.executions.push execution
    execution.start
  end
end

Instance Method Details

#activated?Boolean

Returns:

  • (Boolean)


65
66
67
# File 'lib/bpmn/execution.rb', line 65

def activated?
  status == "activated"
end

#as_json(_options = {}) ⇒ Object



290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# File 'lib/bpmn/execution.rb', line 290

def as_json(_options = {})
  {
    id: id,
    step_id: step&.id,
    step_type: step&.class&.name&.demodulize,
    attached_to_id: attached_to_id,
    status: status,
    started_at: started_at,
    ended_at: ended_at,
    variables: variables.as_json,
    tokens_in: tokens_in,
    tokens_out: tokens_out,
    message_names: message_names,
    error_names: error_names,
    escalation_names: escalation_names,
    timer_expires_at: timer_expires_at,
    condition: condition,
    children: children.map { |child| child.as_json },
  }.transform_values(&:presence).compact
end

#attached_toObject



253
254
255
# File 'lib/bpmn/execution.rb', line 253

def attached_to
  @attached_to ||= parent.children.find { |child| child.id == attached_to_id } if parent
end

#call(process) ⇒ Object



241
242
243
# File 'lib/bpmn/execution.rb', line 241

def call(process)
  execute_step(process, attached_to: self)
end

#check_expired_timersObject



196
197
198
199
200
201
202
# File 'lib/bpmn/execution.rb', line 196

def check_expired_timers
  if timer_expired?
    signal
  else
    children.each { |child| child.check_expired_timers }
  end
end

#child_by_step_id(id) ⇒ Object



257
258
259
# File 'lib/bpmn/execution.rb', line 257

def child_by_step_id(id)
  children.reverse.find { |child| child.step.id == id }
end

#children_by_step_id(id) ⇒ Object



261
262
263
# File 'lib/bpmn/execution.rb', line 261

def children_by_step_id(id)
  children.select { |child| child.step.id == id }
end

#completed?Boolean

Returns:

  • (Boolean)


73
74
75
# File 'lib/bpmn/execution.rb', line 73

def completed?
  status == "completed"
end

#continueObject



105
106
107
# File 'lib/bpmn/execution.rb', line 105

def continue
  step.execute(self)
end

#end(notify_parent = false) ⇒ Object



119
120
121
122
123
124
125
126
127
128
# File 'lib/bpmn/execution.rb', line 119

def end(notify_parent = false)
  @status = "completed" unless status == "terminated"
  map_output_variables if step&.output_mappings.present?
  parent.variables.merge!(variables) if parent && variables.present?
  @ended_at = Time.zone.now
  context.notify_listener(:execution_ended, execution: self)
  children.each { |child| child.terminate unless child.ended? }
  parent.children.each { |child| child.terminate if child.attached_to == self && child.waiting? } if parent
  parent.has_ended(self) if parent && notify_parent
end

#ended?Boolean

Returns:

  • (Boolean)


61
62
63
# File 'lib/bpmn/execution.rb', line 61

def ended?
  ended_at.present?
end

#evaluate_condition(condition) ⇒ Object



211
212
213
# File 'lib/bpmn/execution.rb', line 211

def evaluate_condition(condition)
  evaluate_expression(condition) == true
end

#evaluate_expression(expression, variables: parent&.variables || {}.with_indifferent_access) ⇒ Object



215
216
217
218
219
220
221
222
223
# File 'lib/bpmn/execution.rb', line 215

def evaluate_expression(expression, variables: parent&.variables || {}.with_indifferent_access)
  return nil if expression.nil?

  if expression.start_with?("=")
    DMN.evaluate(expression.delete_prefix("="), variables: variables)
  else
    expression
  end
end

#execute_step(step, attached_to: nil, sequence_flow: nil) ⇒ Object



85
86
87
88
89
90
# File 'lib/bpmn/execution.rb', line 85

def execute_step(step, attached_to: nil, sequence_flow: nil)
  child_execution = children.find { |child| child.step.id == step.id && !child.ended? }
  child_execution = Execution.new(context: context, step: step, parent: self, attached_to_id: attached_to&.id).tap { |ce| children.push ce } unless child_execution
  child_execution.tokens_in += [sequence_flow.id] if sequence_flow
  child_execution.start
end

#execute_steps(steps) ⇒ Object



81
82
83
# File 'lib/bpmn/execution.rb', line 81

def execute_steps(steps)
  steps.each { |step| execute_step(step) }
end

#gen_uidObject



38
39
40
# File 'lib/bpmn/execution.rb', line 38

def gen_uid
  rand(36**8).to_s(36)
end

#has_ended(_child) ⇒ Object

Called by the child step executors when they have ended



248
249
250
251
# File 'lib/bpmn/execution.rb', line 248

def has_ended(_child)
  step.leave(self) if step.is_a?(BPMN::SubProcess) || step.is_a?(BPMN::CallActivity)
  self.end(true)
end

#inspectObject



311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
# File 'lib/bpmn/execution.rb', line 311

def inspect
  parts = ["#<Execution @id=#{id.inspect}"]
  parts << "@step_type=#{step&.class&.name&.demodulize}" if step
  parts << "@step_id=#{step.id.inspect}" if step
  parts << "@status=#{status.inspect}" if status
  parts << "@started_at=#{started_at.inspect}" if started_at
  parts << "@ended_at=#{ended_at.inspect}" if ended_at
  parts << "@attached_to_id=#{attached_to_id.inspect}" if attached_to_id
  parts << "@variables=#{variables.inspect}" if variables.present?
  # parts << "@tokens_in=#{tokens_in.inspect}" if tokens_in.present?
  # parts << "@tokens_out=#{tokens_out.inspect}" if tokens_out.present?
  parts << "@message_names=#{message_names.inspect}" if message_names.present?
  parts << "@error_names=#{error_names.inspect}" if error_names.present?
  parts << "@escalation_names=#{escalation_names.inspect}" if escalation_names.present?
  parts << "@timer_expires_at=#{timer_expires_at.inspect}" if timer_expires_at
  parts << "@condition=#{condition.inspect}" if condition
  parts << "@children=#{children.inspect}" if children.present?
  parts.join(" ") + ">"
end

#invoke_listeners(type, sequence_flow = nil) ⇒ Object



92
93
94
# File 'lib/bpmn/execution.rb', line 92

def invoke_listeners(type, sequence_flow = nil)
  context.listeners.each { |listener| listener[type].call(self, sequence_flow) if listener[type] }
end

#next_timer_expires_atObject



204
205
206
207
208
209
# File 'lib/bpmn/execution.rb', line 204

def next_timer_expires_at
  times = []
  times << timer_expires_at if waiting? && timer_expires_at
  children.each { |child| child_time = child.next_timer_expires_at; times << child_time if child_time }
  times.min
end

#runObject



229
230
231
232
233
234
235
236
237
238
239
# File 'lib/bpmn/execution.rb', line 229

def run
  return unless step.is_automated?

  result = step.run(self)

  if result.present?
    signal(result)
  else
    wait
  end
end

#run_automated_tasksObject



225
226
227
# File 'lib/bpmn/execution.rb', line 225

def run_automated_tasks
  waiting_automated_tasks.each { |child| child.run }
end

#serializeObject



286
287
288
# File 'lib/bpmn/execution.rb', line 286

def serialize(...)
  to_json(...)
end

#signal(result = nil) ⇒ Object

Raises:



142
143
144
145
146
# File 'lib/bpmn/execution.rb', line 142

def signal(result = nil)
  @variables.merge!(result_to_variables(result)) if result.present?
  raise ExecutionError.new("Cannot signal a step execution that has ended.") if ended?
  step.signal(self)
end

#startObject



96
97
98
99
100
101
102
103
# File 'lib/bpmn/execution.rb', line 96

def start
  @status = "started"
  @started_at = Time.zone.now
  map_input_variables if step&.input_mappings.present?
  context.notify_listener(:execution_started, execution: self)
  step.attachments.each { |attachment| parent.execute_step(attachment, attached_to: self) } if step.is_a?(BPMN::Activity)
  continue
end

#started?Boolean

Returns:

  • (Boolean)


57
58
59
# File 'lib/bpmn/execution.rb', line 57

def started?
  started_at.present?
end

#take(sequence_flow) ⇒ Object



134
135
136
137
138
139
140
# File 'lib/bpmn/execution.rb', line 134

def take(sequence_flow)
  to_step = sequence_flow.target
  tokens_out.push sequence_flow.id
  tokens_out.uniq!
  context.notify_listener(:flow_taken, execution: self, sequence_flow: sequence_flow)
  parent.execute_step(to_step, sequence_flow: sequence_flow)
end

#take_all(sequence_flows) ⇒ Object



130
131
132
# File 'lib/bpmn/execution.rb', line 130

def take_all(sequence_flows)
  sequence_flows.each { |sequence_flow| take(sequence_flow) }
end

#terminateObject



114
115
116
117
# File 'lib/bpmn/execution.rb', line 114

def terminate
  @status = "terminated"
  self.end
end

#terminated?Boolean

Returns:

  • (Boolean)


77
78
79
# File 'lib/bpmn/execution.rb', line 77

def terminated?
  status == "terminated"
end

#throw_error(error_name, variables: {}) ⇒ Object



159
160
161
162
163
164
165
166
167
168
# File 'lib/bpmn/execution.rb', line 159

def throw_error(error_name, variables: {})
  waiting_children.each do |child|
    step = child.step
    if step.is_a?(BPMN::Event) && step.error_event_definitions.any? { |error_event_definition| error_event_definition.error_name == error_name }
      child.signal(variables)
      break
    end
  end
  context.notify_listener(:error_thrown, execution: self, error_name: error_name)
end

#throw_escalation(escalation_name, variables: {}) ⇒ Object



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/bpmn/execution.rb', line 170

def throw_escalation(escalation_name, variables: {})
  caught = false
  waiting_children.each do |child|
    step = child.step
    if step.is_a?(BPMN::Event) && step.escalation_event_definitions.any? { |eed| eed.escalation_name == escalation_name }
      child.signal(variables)
      caught = true
      break
    end
  end
  unless caught
    waiting_children.each do |child|
      step = child.step
      if step.is_a?(BPMN::Event) && step.escalation_event_definitions.any? { |eed| eed.escalation_name.nil? }
        child.signal(variables)
        break
      end
    end
  end
  context.notify_listener(:escalation_thrown, execution: self, escalation_name: escalation_name)
end

#throw_message(message_name, variables: {}) ⇒ Object



148
149
150
151
152
153
154
155
156
157
# File 'lib/bpmn/execution.rb', line 148

def throw_message(message_name, variables: {})
  waiting_children.each do |child|
    step = child.step
    if step.is_a?(BPMN::Event) && step.message_event_definitions.any? { |message_event_definition| message_event_definition.message_name == message_name }
      child.signal(variables)
      break
    end
  end
  context.notify_listener(:message_thrown, execution: self, message_name: message_name)
end

#timer_expired?Boolean

Returns:

  • (Boolean)


192
193
194
# File 'lib/bpmn/execution.rb', line 192

def timer_expired?
  waiting? && timer_expires_at.present? && Time.zone.now > timer_expires_at
end

#tokens(active_tokens = []) ⇒ Object



277
278
279
280
281
282
283
284
# File 'lib/bpmn/execution.rb', line 277

def tokens(active_tokens = [])
  children.each do |child|
    active_tokens = active_tokens + child.tokens_out
    active_tokens = active_tokens - child.tokens_in if child.ended?
    active_tokens = active_tokens + child.tokens(active_tokens)
  end
  active_tokens.uniq
end

#waitObject



109
110
111
112
# File 'lib/bpmn/execution.rb', line 109

def wait
  @status = "waiting"
  context.notify_listener(:execution_waited, execution: self)
end

#waiting?Boolean

Returns:

  • (Boolean)


69
70
71
# File 'lib/bpmn/execution.rb', line 69

def waiting?
  status == "waiting"
end

#waiting_automated_tasksObject



273
274
275
# File 'lib/bpmn/execution.rb', line 273

def waiting_automated_tasks
  waiting_tasks.select { |child| child.step.is_automated? }
end

#waiting_childrenObject



265
266
267
# File 'lib/bpmn/execution.rb', line 265

def waiting_children
  children.filter { |child| child.waiting? }
end

#waiting_tasksObject



269
270
271
# File 'lib/bpmn/execution.rb', line 269

def waiting_tasks
  waiting_children.select { |child| child.step.is_a?(BPMN::Task) }
end