Total Complexity | 47 |
Total Lines | 2305 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like PvmExecutionImpl often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use PvmExecutionImpl, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
67 | abstract class PvmExecutionImpl extends CoreExecution implements ActivityExecutionInterface, PvmProcessInstanceInterface |
||
68 | { |
||
69 | //private static final PvmLogger LOG = ProcessEngineLogger.PVM_LOGGER; |
||
70 | |||
71 | protected $processDefinition; |
||
72 | |||
73 | protected $scopeInstantiationContext; |
||
74 | |||
75 | protected $ignoreAsync = false; |
||
76 | |||
77 | /** |
||
78 | * true for process instances in the initial phase. Currently |
||
79 | * this controls that historic variable updates created during this phase receive |
||
80 | * the <code>initial</code> flag (see HistoricVariableUpdateEventEntity#isInitial). |
||
81 | */ |
||
82 | protected $isStarting = false; |
||
83 | |||
84 | // current position ///////////////////////////////////////////////////////// |
||
85 | |||
86 | /** |
||
87 | * current activity |
||
88 | */ |
||
89 | protected $activity; |
||
90 | |||
91 | /** |
||
92 | * the activity which is to be started next |
||
93 | */ |
||
94 | protected $nextActivity; |
||
95 | |||
96 | /** |
||
97 | * the transition that is currently being taken |
||
98 | */ |
||
99 | protected $transition; |
||
100 | |||
101 | /** |
||
102 | * A list of outgoing transitions from the current activity |
||
103 | * that are going to be taken |
||
104 | */ |
||
105 | protected $transitionsToTake = null; |
||
106 | |||
107 | /** |
||
108 | * the unique id of the current activity instance |
||
109 | */ |
||
110 | protected $activityInstanceId; |
||
111 | |||
112 | /** |
||
113 | * the id of a case associated with this execution |
||
114 | */ |
||
115 | protected $caseInstanceId; |
||
116 | |||
117 | protected $replacedBy; |
||
118 | |||
119 | // cascade deletion //////////////////////////////////////////////////////// |
||
120 | |||
121 | protected $deleteRoot; |
||
122 | protected $deleteReason; |
||
123 | protected $externallyTerminated; |
||
124 | |||
125 | //state/type of execution ////////////////////////////////////////////////// |
||
126 | |||
127 | /** |
||
128 | * indicates if this execution represents an active path of execution. |
||
129 | * Executions are made inactive in the following situations: |
||
130 | * <ul> |
||
131 | * <li>an execution enters a nested scope</li> |
||
132 | * <li>an execution is split up into multiple concurrent executions, then the parent is made inactive.</li> |
||
133 | * <li>an execution has arrived in a parallel gateway or join and that join has not yet activated/fired.</li> |
||
134 | * <li>an execution is ended.</li> |
||
135 | * </ul> |
||
136 | */ |
||
137 | protected $isActive = true; |
||
138 | protected $isScope = true; |
||
139 | protected $isConcurrent = false; |
||
140 | protected $isEnded = false; |
||
141 | protected $isEventScope = false; |
||
142 | protected $isRemoved = false; |
||
143 | |||
144 | /** |
||
145 | * transient; used for process instance modification to preserve a scope from getting deleted |
||
146 | */ |
||
147 | protected $preserveScope = false; |
||
148 | |||
149 | /** |
||
150 | * marks the current activity instance |
||
151 | */ |
||
152 | protected $activityInstanceState; |
||
153 | |||
154 | protected $activityInstanceEndListenersFailed = false; |
||
155 | |||
156 | // sequence counter //////////////////////////////////////////////////////// |
||
157 | protected $sequenceCounter = 0; |
||
158 | |||
159 | public function __construct() |
||
160 | { |
||
161 | $this->activityInstanceState = ActivityInstanceState::default()->getStateCode(); |
||
162 | } |
||
163 | |||
164 | // API //////////////////////////////////////////////// |
||
165 | |||
166 | /** |
||
167 | * creates a new execution. properties processDefinition, processInstance and activity will be initialized. |
||
168 | */ |
||
169 | abstract public function createExecution(): PvmExecutionImpl; |
||
170 | |||
171 | public function createSubProcessInstance(PvmProcessDefinitionInterface $processDefinition, ?string $businessKey = null, ?string $caseInstanceId = null): PvmExecutionImpl |
||
172 | { |
||
173 | $subProcessInstance = $this->newExecution(); |
||
174 | |||
175 | // manage bidirectional super-subprocess relation |
||
176 | $subProcessInstance->setSuperExecution($this); |
||
177 | $this->setSubProcessInstance($subProcessInstance); |
||
178 | |||
179 | // Initialize the new execution |
||
180 | $subProcessInstance->setProcessDefinition($processDefinition); |
||
181 | $subProcessInstance->setProcessInstance($subProcessInstance); |
||
182 | $subProcessInstance->setActivity($processDefinition->getInitial()); |
||
183 | |||
184 | if ($businessKey !== null) { |
||
185 | $subProcessInstance->setBusinessKey($businessKey); |
||
186 | } |
||
187 | |||
188 | /*if (caseInstanceId !== null) { |
||
189 | subProcessInstance->setCaseInstanceId(caseInstanceId); |
||
190 | }*/ |
||
191 | |||
192 | return $subProcessInstance; |
||
193 | } |
||
194 | |||
195 | abstract protected function newExecution(): PvmExecutionImpl; |
||
196 | |||
197 | // sub case instance |
||
198 | |||
199 | /*abstract public function CmmnExecution createSubCaseInstance(CmmnCaseDefinition caseDefinition); |
||
200 | |||
201 | @Override |
||
202 | abstract public function CmmnExecution createSubCaseInstance(CmmnCaseDefinition caseDefinition, String businessKey);*/ |
||
203 | |||
204 | abstract public function initialize(): void; |
||
205 | |||
206 | abstract public function initializeTimerDeclarations(): void; |
||
207 | |||
208 | public function executeIoMapping(): void |
||
209 | { |
||
210 | // execute Input Mappings (if they exist). |
||
211 | $currentScope = $this->getScopeActivity(); |
||
212 | if ($currentScope != $currentScope->getProcessDefinition()) { |
||
213 | $currentActivity = $currentScope; |
||
214 | if ($currentActivity !== null && $currentActivity->getIoMapping() !== null && !$this->skipIoMapping) { |
||
215 | $currentActivity->getIoMapping()->executeInputParameters($this); |
||
216 | } |
||
217 | } |
||
218 | } |
||
219 | |||
220 | public function startWithFormProperties(VariableMapInterface $formProperties): void |
||
221 | { |
||
222 | $this->start(null, $formProperties); |
||
223 | } |
||
224 | |||
225 | public function start(?array $variables = [], ?VariableMapInterface $formProperties = null): void |
||
226 | { |
||
227 | $this->initialize(); |
||
228 | |||
229 | $this->fireHistoricProcessStartEvent(); |
||
230 | |||
231 | if (!empty($variables)) { |
||
232 | $this->setVariables($variables); |
||
233 | } |
||
234 | |||
235 | if ($formProperties !== null) { |
||
236 | FormPropertyHelper::initFormPropertiesOnScope($formProperties, $this); |
||
237 | } |
||
238 | |||
239 | $this->initializeTimerDeclarations(); |
||
240 | |||
241 | $this->performOperation(AtomicOperation::processStart()); |
||
242 | } |
||
243 | |||
244 | /** |
||
245 | * perform starting behavior but don't execute the initial activity |
||
246 | * |
||
247 | * @param variables the variables which are used for the start |
||
248 | */ |
||
249 | public function startWithoutExecuting(array $variables): void |
||
250 | { |
||
251 | $this->initialize(); |
||
252 | |||
253 | $this->fireHistoricProcessStartEvent(); |
||
254 | |||
255 | $this->setActivityInstanceId($this->getId()); |
||
256 | $this->setVariables($variables); |
||
257 | |||
258 | $this->initializeTimerDeclarations(); |
||
259 | |||
260 | $this->performOperation(AtomicOperation::fireProcessStart()); |
||
261 | |||
262 | $this->setActivity(null); |
||
263 | } |
||
264 | |||
265 | abstract public function fireHistoricProcessStartEvent(): void; |
||
266 | |||
267 | public function destroy(): void |
||
268 | { |
||
269 | //LOG.destroying(this); |
||
270 | $this->setScope(false); |
||
271 | } |
||
272 | |||
273 | public function removeAllTasks(): void |
||
274 | { |
||
275 | } |
||
276 | |||
277 | protected function removeEventScopes(): void |
||
278 | { |
||
279 | $childExecutions = $this->getEventScopeExecutions(); |
||
280 | foreach ($childExecutions as $childExecution) { |
||
281 | //LOG.removingEventScope(childExecution); |
||
282 | $childExecution->destroy(); |
||
283 | $childExecution->remove(); |
||
284 | } |
||
285 | } |
||
286 | |||
287 | public function clearScope(string $reason, bool $skipCustomListeners, bool $skipIoMappings, bool $externallyTerminated): void |
||
288 | { |
||
289 | $this->skipCustomListeners = $skipCustomListeners; |
||
290 | $this->skipIoMapping = $skipIoMappings; |
||
291 | |||
292 | if ($this->getSubProcessInstance() !== null) { |
||
293 | $this->getSubProcessInstance()->deleteCascade($reason, $skipCustomListeners, $skipIoMappings, $externallyTerminated, false); |
||
294 | } |
||
295 | |||
296 | // remove all child executions and sub process instances: |
||
297 | $executions = $this->getNonEventScopeExecutions(); |
||
298 | foreach ($executions as $childExecution) { |
||
299 | if ($childExecution->getSubProcessInstance() !== null) { |
||
300 | $childExecution->getSubProcessInstance()->deleteCascade($reason, $skipCustomListeners, $skipIoMappings, $externallyTerminated, false); |
||
301 | } |
||
302 | $childExecution->deleteCascade($reason, $skipCustomListeners, $skipIoMappings, $externallyTerminated, false); |
||
303 | } |
||
304 | |||
305 | // fire activity end on active activity |
||
306 | $activity = $this->getActivity(); |
||
307 | if ($this->isActive && $activity !== null) { |
||
308 | // set activity instance state to cancel |
||
309 | if ($this->activityInstanceState != ActivityInstanceState::ending()->getStateCode()) { |
||
310 | $this->setCanceled(true); |
||
311 | $this->performOperation(AtomicOperation::fireActivityEnd()); |
||
312 | } |
||
313 | // set activity instance state back to 'default' |
||
314 | // -> execution will be reused for executing more activities and we want the state to |
||
315 | // be default initially. |
||
316 | $this->activityInstanceState = ActivityInstanceState::default()->getStateCode(); |
||
317 | } |
||
318 | } |
||
319 | |||
320 | /** |
||
321 | * Interrupts an execution |
||
322 | */ |
||
323 | public function interrupt(string $reason, ?bool $skipCustomListeners = false, ?bool $skipIoMappings = false, ?bool $externallyTerminated = false): void |
||
324 | { |
||
325 | //LOG.interruptingExecution(reason, skipCustomListeners); |
||
326 | $this->clearScope($reason, $skipCustomListeners, $skipIoMappings, $externallyTerminated); |
||
327 | } |
||
328 | |||
329 | /** |
||
330 | * Ends an execution. Invokes end listeners for the current activity and notifies the flow scope execution |
||
331 | * of this happening which may result in the flow scope ending. |
||
332 | * |
||
333 | * @param completeScope true if ending the execution contributes to completing the BPMN 2.0 scope |
||
334 | */ |
||
335 | public function end(bool $completeScope): void |
||
336 | { |
||
337 | $this->setCompleteScope($completeScope); |
||
338 | |||
339 | $this->isActive = false; |
||
340 | $this->isEnded = true; |
||
341 | |||
342 | if ($this->hasReplacedParent()) { |
||
343 | $this->getParent()->replacedBy = null; |
||
344 | } |
||
345 | |||
346 | $this->performOperation(AtomicOperation::activityNotifyListenerEnd()); |
||
347 | } |
||
348 | |||
349 | public function endCompensation(): void |
||
350 | { |
||
351 | $this->performOperation(AtomicOperation::fireActivityEnd()); |
||
352 | $this->remove(); |
||
353 | |||
354 | $parent = $this->getParent(); |
||
355 | |||
356 | if ($parent->getActivity() === null) { |
||
357 | $parent->setActivity($this->getActivity()->getFlowScope()); |
||
358 | } |
||
359 | |||
360 | $parent->signal("compensationDone", null); |
||
361 | } |
||
362 | |||
363 | /** |
||
364 | * <p>Precondition: execution is already ended but this has not been propagated yet.</p> |
||
365 | * <p> |
||
366 | * <p>Propagates the ending of this execution to the flowscope execution; currently only supports |
||
367 | * the process instance execution</p> |
||
368 | */ |
||
369 | public function propagateEnd(): void |
||
370 | { |
||
371 | if (!$this->isEnded()) { |
||
372 | throw new ProcessEngineException($this->__toString() . " must have ended before ending can be propagated"); |
||
373 | } |
||
374 | |||
375 | if ($this->isProcessInstanceExecution()) { |
||
376 | $this->performOperation(AtomicOperation::processEnd()); |
||
377 | } else { |
||
378 | // not supported yet |
||
379 | } |
||
380 | } |
||
381 | |||
382 | public function remove(): void |
||
383 | { |
||
384 | $parent = $this->getParent(); |
||
385 | if ($parent !== null) { |
||
386 | $parent->removeExecution($this); |
||
387 | |||
388 | // if the sequence counter is greater than the |
||
389 | // sequence counter of the parent, then set |
||
390 | // the greater sequence counter on the parent. |
||
391 | $parentSequenceCounter = $parent->getSequenceCounter(); |
||
392 | $mySequenceCounter = $this->getSequenceCounter(); |
||
393 | if ($mySequenceCounter > $parentSequenceCounter) { |
||
394 | $parent->setSequenceCounter($mySequenceCounter); |
||
395 | } |
||
396 | |||
397 | // propagate skipping configuration upwards, if it was not initially set on |
||
398 | // the root execution |
||
399 | $parent->skipCustomListeners |= $this->skipCustomListeners; |
||
400 | $parent->skipIoMapping |= $this->skipIoMapping; |
||
401 | } |
||
402 | |||
403 | $this->isActive = false; |
||
404 | $this->isEnded = true; |
||
405 | $this->isRemoved = true; |
||
406 | |||
407 | if ($this->hasReplacedParent()) { |
||
408 | $this->getParent()->replacedBy = null; |
||
409 | } |
||
410 | $this->removeEventScopes(); |
||
411 | } |
||
412 | |||
413 | abstract protected function removeExecution(PvmExecutionImpl $execution): void; |
||
414 | |||
415 | abstract protected function addExecution(PvmExecutionImpl $execution): void; |
||
416 | |||
417 | public function isRemoved(): bool |
||
418 | { |
||
419 | return $this->isRemoved; |
||
420 | } |
||
421 | |||
422 | public function createConcurrentExecution(): PvmExecutionImpl |
||
423 | { |
||
424 | if (!$this->isScope()) { |
||
425 | throw new ProcessEngineException("Cannot create concurrent execution for " . $this); |
||
426 | } |
||
427 | |||
428 | // The following covers the three cases in which a concurrent execution may be created |
||
429 | // (this execution is the root in each scenario). |
||
430 | // |
||
431 | // Note: this should only consider non-event-scope executions. Event-scope executions |
||
432 | // are not relevant for the tree structure and should remain under their original parent. |
||
433 | // |
||
434 | // |
||
435 | // (1) A compacted tree: |
||
436 | // |
||
437 | // Before: After: |
||
438 | // ------- ------- |
||
439 | // | e1 | | e1 | |
||
440 | // ------- ------- |
||
441 | // / \ |
||
442 | // ------- ------- |
||
443 | // | e2 | | e3 | |
||
444 | // ------- ------- |
||
445 | // |
||
446 | // e2 replaces e1; e3 is the new root for the activity stack to instantiate |
||
447 | // |
||
448 | // |
||
449 | // (2) A single child that is a scope execution |
||
450 | // Before: After: |
||
451 | // ------- ------- |
||
452 | // | e1 | | e1 | |
||
453 | // ------- ------- |
||
454 | // | / \ |
||
455 | // ------- ------- ------- |
||
456 | // | e2 | | e3 | | e4 | |
||
457 | // ------- ------- ------- |
||
458 | // | |
||
459 | // ------- |
||
460 | // | e2 | |
||
461 | // ------- |
||
462 | // |
||
463 | // |
||
464 | // e3 is created and is concurrent; |
||
465 | // e4 is the new root for the activity stack to instantiate |
||
466 | // |
||
467 | // (3) Existing concurrent execution(s) |
||
468 | // Before: After: |
||
469 | // ------- --------- |
||
470 | // | e1 | | e1 | |
||
471 | // ------- --------- |
||
472 | // / \ / | \ |
||
473 | // ------- ------- ------- ------- ------- |
||
474 | // | e2 | .. | eX | | e2 |..| eX | | eX+1| |
||
475 | // ------- ------- ------- ------- ------- |
||
476 | // |
||
477 | // eX+1 is concurrent and the new root for the activity stack to instantiate |
||
478 | $children = $this->getNonEventScopeExecutions(); |
||
479 | |||
480 | // whenever we change the set of child executions we have to force an update |
||
481 | // on the scope executions to avoid concurrent modifications (e.g. tree compaction) |
||
482 | // that go unnoticed |
||
483 | $this->forceUpdate(); |
||
484 | |||
485 | if (empty($children)) { |
||
486 | // (1) |
||
487 | $replacingExecution = $this->createExecution(); |
||
488 | $replacingExecution->setConcurrent(true); |
||
489 | $replacingExecution->setScope(false); |
||
490 | $replacingExecution->replace($this); |
||
491 | $this->inactivate(); |
||
492 | $this->setActivity(null); |
||
493 | } elseif (count($children) == 1) { |
||
494 | // (2) |
||
495 | $child = $children[0]; |
||
496 | |||
497 | $concurrentReplacingExecution = $this->createExecution(); |
||
498 | $concurrentReplacingExecution->setConcurrent(true); |
||
499 | $concurrentReplacingExecution->setScope(false); |
||
500 | $concurrentReplacingExecution->setActive(false); |
||
501 | $concurrentReplacingExecution->onConcurrentExpand($this); |
||
502 | $child->setParent($concurrentReplacingExecution); |
||
503 | $this->leaveActivityInstance(); |
||
504 | $this->setActivity(null); |
||
505 | } |
||
506 | |||
507 | // (1), (2), and (3) |
||
508 | $concurrentExecution = $this->createExecution(); |
||
509 | $concurrentExecution->setConcurrent(true); |
||
510 | $concurrentExecution->setScope(false); |
||
511 | |||
512 | return $concurrentExecution; |
||
513 | } |
||
514 | |||
515 | public function tryPruneLastConcurrentChild(): bool |
||
516 | { |
||
517 | if (count($this->getNonEventScopeExecutions()) == 1) { |
||
518 | $lastConcurrent = $this->getNonEventScopeExecutions()[0]; |
||
519 | if ($lastConcurrent->isConcurrent()) { |
||
520 | if (!$lastConcurrent->isScope()) { |
||
521 | $this->setActivity($lastConcurrent->getActivity()); |
||
522 | $this->setTransition($lastConcurrent->getTransition()); |
||
523 | $this->replace($lastConcurrent); |
||
524 | |||
525 | // Move children of lastConcurrent one level up |
||
526 | if ($lastConcurrent->hasChildren()) { |
||
527 | foreach ($lastConcurrent->getExecutionsAsCopy() as $childExecution) { |
||
528 | $childExecution->setParent($this); |
||
529 | } |
||
530 | } |
||
531 | |||
532 | // Make sure parent execution is re-activated when the last concurrent |
||
533 | // child execution is active |
||
534 | if (!$this->isActive() && $lastConcurrent->isActive()) { |
||
535 | $this->setActive(true); |
||
536 | } |
||
537 | |||
538 | $lastConcurrent->remove(); |
||
539 | } else { |
||
540 | // legacy behavior |
||
541 | LegacyBehavior::pruneConcurrentScope($lastConcurrent); |
||
542 | } |
||
543 | return true; |
||
544 | } |
||
545 | } |
||
546 | |||
547 | return false; |
||
548 | } |
||
549 | |||
550 | public function deleteCascade( |
||
551 | string $deleteReason, |
||
552 | ?bool $skipCustomListeners = false, |
||
553 | ?bool $skipIoMappings = false, |
||
554 | ?bool $externallyTerminated = false, |
||
555 | ?bool $skipSubprocesses = false |
||
556 | ): void { |
||
557 | $this->deleteReason = $deleteReason; |
||
558 | $this->setDeleteRoot(true); |
||
559 | $this->isEnded = true; |
||
560 | $this->skipCustomListeners = $skipCustomListeners; |
||
561 | $this->skipIoMapping = $skipIoMappings; |
||
562 | $this->externallyTerminated = $externallyTerminated; |
||
563 | $this->skipSubprocesses = $skipSubprocesses; |
||
564 | $this->performOperation(AtomicOperation::deleteCascade()); |
||
565 | } |
||
566 | |||
567 | public function executeEventHandlerActivity(ActivityImpl $eventHandlerActivity): void |
||
568 | { |
||
569 | // the target scope |
||
570 | $flowScope = $eventHandlerActivity->getFlowScope(); |
||
571 | |||
572 | // the event scope (the current activity) |
||
573 | $eventScope = $eventHandlerActivity->getEventScope(); |
||
574 | |||
575 | if ( |
||
576 | $eventHandlerActivity->getActivityStartBehavior() == ActivityStartBehavior::CONCURRENT_IN_FLOW_SCOPE |
||
577 | && $flowScope != $eventScope |
||
578 | ) { |
||
579 | // the current scope is the event scope of the activity |
||
580 | $this->findExecutionForScope($eventScope, $flowScope)->executeActivity($eventHandlerActivity); |
||
581 | } else { |
||
582 | $this->executeActivity(eventHandlerActivity); |
||
583 | } |
||
584 | } |
||
585 | |||
586 | // tree compaction & expansion /////////////////////////////////////////// |
||
587 | |||
588 | /** |
||
589 | * <p>Returns an execution that has replaced this execution for executing activities in their shared scope.</p> |
||
590 | * <p>Invariant: this execution and getReplacedBy() execute in the same scope.</p> |
||
591 | */ |
||
592 | abstract public function getReplacedBy(): ?PvmExecutionImpl; |
||
593 | |||
594 | /** |
||
595 | * Instead of {@link #getReplacedBy()}, which returns the execution that this execution was directly replaced with, |
||
596 | * this resolves the chain of replacements (i.e. in the case the replacedBy execution itself was replaced again) |
||
597 | */ |
||
598 | public function resolveReplacedBy(): ?PvmExecutionImpl |
||
599 | { |
||
600 | // follow the links of execution replacement; |
||
601 | // note: this can be at most two hops: |
||
602 | // case 1: |
||
603 | // this execution is a scope execution |
||
604 | // => tree may have expanded meanwhile |
||
605 | // => scope execution references replacing execution directly (one hop) |
||
606 | // |
||
607 | // case 2: |
||
608 | // this execution is a concurrent execution |
||
609 | // => tree may have compacted meanwhile |
||
610 | // => concurrent execution references scope execution directly (one hop) |
||
611 | // |
||
612 | // case 3: |
||
613 | // this execution is a concurrent execution |
||
614 | // => tree may have compacted/expanded/compacted/../expanded any number of times |
||
615 | // => the concurrent execution has been removed and therefore references the scope execution (first hop) |
||
616 | // => the scope execution may have been replaced itself again with another concurrent execution (second hop) |
||
617 | // note that the scope execution may have a long "history" of replacements, but only the last replacement is relevant here |
||
618 | $replacingExecution = $this->getReplacedBy(); |
||
619 | |||
620 | if ($replacingExecution !== null) { |
||
621 | $secondHopReplacingExecution = $replacingExecution->getReplacedBy(); |
||
622 | if ($secondHopReplacingExecution !== null) { |
||
623 | $replacingExecution = $secondHopReplacingExecution; |
||
624 | } |
||
625 | } |
||
626 | |||
627 | return $replacingExecution; |
||
628 | } |
||
629 | |||
630 | public function hasReplacedParent(): bool |
||
631 | { |
||
632 | return $this->getParent() !== null && $this->getParent()->getReplacedBy() == $this; |
||
633 | } |
||
634 | |||
635 | public function isReplacedByParent(): bool |
||
636 | { |
||
637 | return $this->getReplacedBy() !== null && $this->getReplacedBy() == $this->getParent(); |
||
638 | } |
||
639 | |||
640 | /** |
||
641 | * <p>Replace an execution by this execution. The replaced execution has a pointer ({@link #getReplacedBy()}) to this execution. |
||
642 | * This pointer is maintained until the replaced execution is removed or this execution is removed/ended.</p> |
||
643 | * <p> |
||
644 | * <p>This is used for two cases: Execution tree expansion and execution tree compaction</p> |
||
645 | * <ul> |
||
646 | * <li><b>expansion</b>: Before: |
||
647 | * <pre> |
||
648 | * ------- |
||
649 | * | e1 | scope |
||
650 | * ------- |
||
651 | * </pre> |
||
652 | * After: |
||
653 | * <pre> |
||
654 | * ------- |
||
655 | * | e1 | scope |
||
656 | * ------- |
||
657 | * | |
||
658 | * ------- |
||
659 | * | e2 | cc (no scope) |
||
660 | * ------- |
||
661 | * </pre> |
||
662 | * e2 replaces e1: it should receive all entities associated with the activity currently executed |
||
663 | * by e1; these are tasks, (local) variables, jobs (specific for the activity, not the scope) |
||
664 | * </li> |
||
665 | * <li><b>compaction</b>: Before: |
||
666 | * <pre> |
||
667 | * ------- |
||
668 | * | e1 | scope |
||
669 | * ------- |
||
670 | * | |
||
671 | * ------- |
||
672 | * | e2 | cc (no scope) |
||
673 | * ------- |
||
674 | * </pre> |
||
675 | * After: |
||
676 | * <pre> |
||
677 | * ------- |
||
678 | * | e1 | scope |
||
679 | * ------- |
||
680 | * </pre> |
||
681 | * e1 replaces e2: it should receive all entities associated with the activity currently executed |
||
682 | * by e2; these are tasks, (all) variables, all jobs |
||
683 | * </li> |
||
684 | * </ul> |
||
685 | * |
||
686 | * @see #createConcurrentExecution() |
||
687 | * @see #tryPruneLastConcurrentChild() |
||
688 | */ |
||
689 | public function replace(PvmExecutionImpl $execution): void |
||
690 | { |
||
691 | // activity instance id handling |
||
692 | $this->activityInstanceId = $execution->getActivityInstanceId(); |
||
693 | $this->isActive = $execution->isActive; |
||
694 | |||
695 | $this->replacedBy = null; |
||
696 | $execution->replacedBy = $this; |
||
697 | |||
698 | $this->transitionsToTake = $execution->transitionsToTake; |
||
699 | |||
700 | $execution->leaveActivityInstance(); |
||
701 | } |
||
702 | |||
703 | /** |
||
704 | * Callback on tree expansion when this execution is used as the concurrent execution |
||
705 | * where the argument's children become a subordinate to. Note that this case is not the inverse |
||
706 | * of replace because replace has the semantics that the replacing execution can be used to continue |
||
707 | * execution of this execution's activity instance. |
||
708 | */ |
||
709 | public function onConcurrentExpand(PvmExecutionImpl $scopeExecution): void |
||
710 | { |
||
711 | // by default, do nothing |
||
712 | } |
||
713 | |||
714 | // methods that translate to operations ///////////////////////////////////// |
||
715 | |||
716 | public function signal(string $signalName, $signalData): void |
||
717 | { |
||
718 | if ($this->getActivity() === null) { |
||
719 | throw new PvmException("cannot signal execution " . $this->id . ": it has no current activity"); |
||
720 | } |
||
721 | |||
722 | $activityBehavior = $this->activity->getActivityBehavior(); |
||
723 | try { |
||
724 | $activityBehavior->signal($this, $signalName, $signalData); |
||
725 | } catch (\Exception $e) { |
||
726 | throw new PvmException("couldn't process signal '" . $signalName . "' on activity '" . $this->activity->getId() . "': " . $e->getMessage(), $e); |
||
727 | } |
||
728 | } |
||
729 | |||
730 | public function take(): void |
||
731 | { |
||
732 | if ($this->transition === null) { |
||
733 | throw new PvmException($this->__toString() . ": no transition to take specified"); |
||
734 | } |
||
735 | $transitionImpl = $this->transition; |
||
736 | $this->setActivity($transitionImpl->getSource()); |
||
737 | // while executing the transition, the activityInstance is 'null' |
||
738 | // (we are not executing an activity) |
||
739 | $this->setActivityInstanceId(null); |
||
740 | $this->setActive(true); |
||
741 | $this->performOperation(AtomicOperation::transitionNotifyListenerTake()); |
||
742 | } |
||
743 | |||
744 | /** |
||
745 | * Execute an activity which is not contained in normal flow (having no incoming sequence flows). |
||
746 | * Cannot be called for activities contained in normal flow. |
||
747 | * <p> |
||
748 | * First, the ActivityStartBehavior is evaluated. |
||
749 | * In case the start behavior is not ActivityStartBehavior#DEFAULT, the corresponding start |
||
750 | * behavior is executed before executing the activity. |
||
751 | * <p> |
||
752 | * For a given activity, the execution on which this method must be called depends on the type of the start behavior: |
||
753 | * <ul> |
||
754 | * <li>CONCURRENT_IN_FLOW_SCOPE: scope execution for PvmActivity#getFlowScope()</li> |
||
755 | * <li>INTERRUPT_EVENT_SCOPE: scope execution for PvmActivity#getEventScope()</li> |
||
756 | * <li>CANCEL_EVENT_SCOPE: scope execution for PvmActivity#getEventScope()</li> |
||
757 | * </ul> |
||
758 | * |
||
759 | * @param activity the activity to start |
||
760 | */ |
||
761 | public function executeActivity(PvmActivityInterface $activity): void |
||
762 | { |
||
763 | if (!empty($activity->getIncomingTransitions())) { |
||
764 | throw new ProcessEngineException("Activity is contained in normal flow and cannot be executed using executeActivity()."); |
||
765 | } |
||
766 | |||
767 | $activityStartBehavior = $activity->getActivityStartBehavior(); |
||
768 | if (!$this->isScope() && ActivityStartBehavior::DEFAULT != $this->activityStartBehavior) { |
||
769 | throw new ProcessEngineException("Activity '" . $activity . "' with start behavior '" . $activityStartBehavior . "'" |
||
770 | . "cannot be executed by non-scope execution."); |
||
771 | } |
||
772 | |||
773 | $activityImpl = $activity; |
||
774 | $this->isEnded = false; |
||
775 | $this->isActive = true; |
||
776 | |||
777 | switch ($activityStartBehavior) { |
||
778 | case ActivityStartBehavior::CONCURRENT_IN_FLOW_SCOPE: |
||
779 | $this->nextActivity = $activityImpl; |
||
780 | $this->performOperation(AtomicOperation::activityStartConcurrent()); |
||
781 | break; |
||
782 | |||
783 | case ActivityStartBehavior::CANCEL_EVENT_SCOPE: |
||
784 | $this->nextActivity = $activityImpl; |
||
785 | $this->performOperation(AtomicOperation::activityStartCancelScope()); |
||
786 | break; |
||
787 | |||
788 | case ActivityStartBehavior::INTERRUPT_EVENT_SCOPE: |
||
789 | $this->nextActivity = $activityImpl; |
||
790 | $this->performOperation(AtomicOperation::activityStartInterruptScope()); |
||
791 | break; |
||
792 | |||
793 | default: |
||
794 | $this->setActivity($activityImpl); |
||
795 | $this->setActivityInstanceId(null); |
||
796 | $this->performOperation(AtomicOperation::activityStartCreateScope()); |
||
797 | break; |
||
798 | } |
||
799 | } |
||
800 | |||
801 | /** |
||
802 | * Instantiates the given activity stack under this execution. |
||
803 | * Sets the variables for the execution responsible to execute the most deeply nested |
||
804 | * activity. |
||
805 | * |
||
806 | * @param activityStack The most deeply nested activity is the last element in the list |
||
807 | */ |
||
808 | public function executeActivitiesConcurrent( |
||
809 | array &$activityStack, |
||
810 | ?PvmActivityInterface $targetActivity = null, |
||
811 | ?PvmTransitionInterface $targetTransition = null, |
||
812 | ?array $variables = [], |
||
813 | ?array $localVariables = [], |
||
814 | ?bool $skipCustomListeners = false, |
||
815 | ?bool $skipIoMappings = false |
||
816 | ): void { |
||
817 | $flowScope = null; |
||
818 | if (!empty($activityStack)) { |
||
819 | $flowScope = $activityStack[0]->getFlowScope(); |
||
820 | } elseif ($targetActivity !== null) { |
||
821 | $flowScope = $targetActivity->getFlowScope(); |
||
822 | } elseif ($targetTransition !== null) { |
||
823 | $flowScope = $targetTransition->getSource()->getFlowScope(); |
||
824 | } |
||
825 | |||
826 | $propagatingExecution = null; |
||
827 | if ($flowScope->getActivityBehavior() instanceof ModificationObserverBehaviorInterface) { |
||
828 | $flowScopeBehavior = $flowScope->getActivityBehavior(); |
||
829 | $propagatingExecution = $flowScopeBehavior->createInnerInstance($this); |
||
830 | } else { |
||
831 | $propagatingExecution = $this->createConcurrentExecution(); |
||
832 | } |
||
833 | |||
834 | $propagatingExecution->executeActivities( |
||
835 | $activityStack, |
||
836 | $targetActivity, |
||
837 | $targetTransition, |
||
838 | $variables, |
||
839 | $localVariables, |
||
840 | $skipCustomListeners, |
||
841 | $skipIoMappings |
||
842 | ); |
||
843 | } |
||
844 | |||
845 | /** |
||
846 | * Instantiates the given set of activities and returns the execution for the bottom-most activity |
||
847 | */ |
||
848 | public function instantiateScopes(array &$activityStack, bool $skipCustomListeners, bool $skipIoMappings): array |
||
849 | { |
||
850 | if (empty($activityStack)) { |
||
851 | return []; |
||
852 | } |
||
853 | |||
854 | $this->skipCustomListeners = $skipCustomListeners; |
||
855 | $this->skipIoMapping = $skipIoMappings; |
||
856 | |||
857 | $executionStartContext = new ScopeInstantiationContext(); |
||
858 | |||
859 | $instantiationStack = new InstantiationStack($activityStack); |
||
860 | $executionStartContext->setInstantiationStack($instantiationStack); |
||
861 | $this->setStartContext($executionStartContext); |
||
862 | |||
863 | $this->performOperation(AtomicOperation::activityInitStackAndReturn()); |
||
864 | |||
865 | $createdExecutions = []; |
||
866 | |||
867 | $currentExecution = $this; |
||
868 | foreach ($activityStack as $instantiatedActivity) { |
||
869 | // there must exactly one child execution |
||
870 | $currentExecution = $currentExecution->getNonEventScopeExecutions()[0]; |
||
871 | if ($currentExecution->isConcurrent()) { |
||
872 | // there may be a non-scope execution that we have to skip (e.g. multi-instance) |
||
873 | $currentExecution = $currentExecution->getNonEventScopeExecutions()[0]; |
||
874 | } |
||
875 | |||
876 | $createdExecutions[] = [$instantiatedActivity, $currentExecution]; |
||
877 | } |
||
878 | |||
879 | return $createdExecutions; |
||
880 | } |
||
881 | |||
882 | /** |
||
883 | * Instantiates the given activity stack. Uses this execution to execute the |
||
884 | * highest activity in the stack. |
||
885 | * Sets the variables for the execution responsible to execute the most deeply nested |
||
886 | * activity. |
||
887 | * |
||
888 | * @param activityStack The most deeply nested activity is the last element in the list |
||
889 | */ |
||
890 | public function executeActivities( |
||
891 | array &$activityStack, |
||
892 | ?PvmActivityInterface $targetActivity = null, |
||
893 | ?PvmTransitionInterface $targetTransition = null, |
||
894 | ?array $variables = [], |
||
895 | ?array $localVariables = [], |
||
896 | ?bool $skipCustomListeners = false, |
||
897 | ?bool $skipIoMappings = false |
||
898 | ): void { |
||
899 | $this->skipCustomListeners = $skipCustomListeners; |
||
900 | $this->skipIoMapping = $skipIoMappings; |
||
901 | $this->activityInstanceId = null; |
||
902 | $this->isEnded = false; |
||
903 | |||
904 | if (!empty($activityStack)) { |
||
905 | $executionStartContext = new ScopeInstantiationContext(); |
||
906 | |||
907 | $instantiationStack = new InstantiationStack($activityStack, $targetActivity, $targetTransition); |
||
908 | $executionStartContext->setInstantiationStack($instantiationStack); |
||
909 | $executionStartContext->setVariables($variables); |
||
910 | $executionStartContext->setVariablesLocal($localVariables); |
||
911 | $this->setStartContext($executionStartContext); |
||
912 | |||
913 | $this->performOperation(AtomicOperation::activityInitStack()); |
||
914 | } elseif ($targetActivity !== null) { |
||
915 | $this->setVariables($variables); |
||
916 | $this->setVariablesLocal($localVariables); |
||
917 | $this->setActivity($targetActivity); |
||
918 | $this->performOperation(AtomicOperation::activityStartCReateScope()); |
||
919 | } elseif ($targetTransition !== null) { |
||
920 | $this->setVariables($variables); |
||
921 | $this->setVariablesLocal($localVariables); |
||
922 | $this->setActivity($targetTransition->getSource()); |
||
923 | $this->setTransition($targetTransition); |
||
924 | $this->performOperation(AtomicOperation::trasitionStartNotifyListenerTake()); |
||
925 | } |
||
926 | } |
||
927 | |||
928 | public function findInactiveConcurrentExecutions(PvmActivityInterface $activity): array |
||
929 | { |
||
930 | $inactiveConcurrentExecutionsInActivity = []; |
||
931 | if ($this->isConcurrent()) { |
||
932 | return $this->getParent()->findInactiveChildExecutions($activity); |
||
933 | } elseif (!$this->isActive()) { |
||
934 | $inactiveConcurrentExecutionsInActivity[] = $this; |
||
935 | } |
||
936 | return $inactiveConcurrentExecutionsInActivity; |
||
937 | } |
||
938 | |||
939 | public function findInactiveChildExecutions(PvmActivityInterface $activity): array |
||
940 | { |
||
941 | $inactiveConcurrentExecutionsInActivity = []; |
||
942 | $concurrentExecutions = $this->getAllChildExecutions(); |
||
943 | foreach ($concurrentExecutions as $concurrentExecution) { |
||
944 | if ($concurrentExecution->getActivity() == $activity && !$concurrentExecution->isActive()) { |
||
945 | $inactiveConcurrentExecutionsInActivity[] = $concurrentExecution; |
||
946 | } |
||
947 | } |
||
948 | |||
949 | return $inactiveConcurrentExecutionsInActivity; |
||
950 | } |
||
951 | |||
952 | protected function getAllChildExecutions(): array |
||
953 | { |
||
954 | $childExecutions = []; |
||
955 | foreach ($this->getExecutions() as $childExecution) { |
||
956 | $childExecutions[] = $childExecution; |
||
957 | $childExecutions = array_merge($childExecutions, $childExecution->getAllChildExecutions()); |
||
958 | } |
||
959 | return $childExecutions; |
||
960 | } |
||
961 | |||
962 | public function leaveActivityViaTransition($outgoingTransition, ?array $_recyclableExecutions = []): void |
||
963 | { |
||
964 | if ($outgoingTransition instanceof PvmTransitionInterface) { |
||
965 | $_transitions = [$outgoingTransition]; |
||
966 | } else { |
||
967 | $_transitions = $outgoingTransition; |
||
968 | } |
||
969 | $recyclableExecutions = []; |
||
970 | if (!empty($_recyclableExecutions)) { |
||
971 | $recyclableExecutions = $_recyclableExecutions; |
||
972 | } |
||
973 | |||
974 | // if recyclable executions size is greater |
||
975 | // than 1, then the executions are joined and |
||
976 | // the activity is left with 'this' execution, |
||
977 | // if it is not not the last concurrent execution. |
||
978 | // therefore it is necessary to remove the local |
||
979 | // variables (event if it is the last concurrent |
||
980 | // execution). |
||
981 | if (count($recyclableExecutions) > 1) { |
||
982 | $this->removeVariablesLocalInternal(); |
||
983 | } |
||
984 | |||
985 | // mark all recyclable executions as ended |
||
986 | // if the list of recyclable executions also |
||
987 | // contains 'this' execution, then 'this' execution |
||
988 | // is also marked as ended. (if 'this' execution is |
||
989 | // pruned, then the local variables are not copied |
||
990 | // to the parent execution) |
||
991 | // this is a workaround to not delete all recyclable |
||
992 | // executions and create a new execution which leaves |
||
993 | // the activity. |
||
994 | foreach ($recyclableExecutions as $execution) { |
||
995 | $execution->setEnded(true); |
||
996 | } |
||
997 | |||
998 | // remove 'this' from recyclable executions to |
||
999 | // leave the activity with 'this' execution |
||
1000 | // (when 'this' execution is the last concurrent |
||
1001 | // execution, then 'this' execution will be pruned, |
||
1002 | // and the activity is left with the scope |
||
1003 | // execution) |
||
1004 | foreach ($recyclableExecutions as $key => $execution) { |
||
1005 | if ($execution == $this) { |
||
1006 | unset($recyclableExecutions[$key]); |
||
1007 | } |
||
1008 | } |
||
1009 | |||
1010 | foreach ($recyclableExecutions as $execution) { |
||
1011 | $execution->end(empty($_transitions)); |
||
1012 | } |
||
1013 | |||
1014 | $propagatingExecution = this; |
||
1015 | if ($this->getReplacedBy() !== null) { |
||
1016 | $propagatingExecution = $this->getReplacedBy(); |
||
1017 | } |
||
1018 | |||
1019 | $propagatingExecution->isActive = true; |
||
1020 | $propagatingExecution->isEnded = false; |
||
1021 | |||
1022 | if (empty($_transitions)) { |
||
1023 | $propagatingExecution->end(!$propagatingExecution->isConcurrent()); |
||
1024 | } else { |
||
1025 | $propagatingExecution->setTransitionsToTake($_transitions); |
||
1026 | $propagatingExecution->performOperation(AtomicOperation::transitionNotifyListenerEnd()); |
||
1027 | } |
||
1028 | } |
||
1029 | |||
1030 | abstract protected function removeVariablesLocalInternal(): void; |
||
1031 | |||
1032 | public function isActive(?string $activityId = null): bool |
||
1033 | { |
||
1034 | if ($activityId !== null) { |
||
1035 | return $this->findExecution($activityId) !== null; |
||
1036 | } |
||
1037 | $this->isActive; |
||
1038 | } |
||
1039 | |||
1040 | public function inactivate(): void |
||
1041 | { |
||
1042 | $this->isActive = false; |
||
1043 | } |
||
1044 | |||
1045 | // executions /////////////////////////////////////////////////////////////// |
||
1046 | |||
1047 | abstract public function getExecutions(): array; |
||
1048 | |||
1049 | abstract public function getExecutionsAsCopy(): array; |
||
1050 | |||
1051 | public function getNonEventScopeExecutions(): array |
||
1052 | { |
||
1053 | $children = $this->getExecutions(); |
||
1054 | $result = []; |
||
1055 | |||
1056 | foreach ($children as $child) { |
||
1057 | if (!$child->isEventScope()) { |
||
1058 | $result[] = $child; |
||
1059 | } |
||
1060 | } |
||
1061 | |||
1062 | return $result; |
||
1063 | } |
||
1064 | |||
1065 | public function getEventScopeExecutions(): array |
||
1066 | { |
||
1067 | $children = $this->getExecutions(); |
||
1068 | $result = []; |
||
1069 | |||
1070 | foreach ($children as $child) { |
||
1071 | if ($child->isEventScope()) { |
||
1072 | $result[] = $child; |
||
1073 | } |
||
1074 | } |
||
1075 | |||
1076 | return $result; |
||
1077 | } |
||
1078 | |||
1079 | public function findExecution(string $activityId): ?PvmExecutionImpl |
||
1080 | { |
||
1081 | if ( |
||
1082 | ($this->getActivity() !== null) |
||
1083 | && ($this->getActivity()->getId() == $activityId) |
||
1084 | ) { |
||
1085 | return $this; |
||
1086 | } |
||
1087 | foreach ($this->getExecutions() as $nestedExecution) { |
||
1088 | $result = $nestedExecution->findExecution($activityId); |
||
1089 | if ($result !== null) { |
||
1090 | return result; |
||
1091 | } |
||
1092 | } |
||
1093 | return null; |
||
1094 | } |
||
1095 | |||
1096 | public function findExecutions(string $activityId): array |
||
1097 | { |
||
1098 | $matchingExecutions = []; |
||
1099 | $this->collectExecutions($activityId, $matchingExecutions); |
||
1100 | |||
1101 | return $matchingExecutions; |
||
1102 | } |
||
1103 | |||
1104 | protected function collectExecutions(string $activityId, array &$executions): array |
||
1105 | { |
||
1106 | if ( |
||
1107 | ($this->getActivity() !== null) |
||
1108 | && ($this->getActivity()->getId() == $activityId) |
||
1109 | ) { |
||
1110 | $executions[] = $this; |
||
1111 | } |
||
1112 | |||
1113 | foreach ($this->getExecutions() as $nestedExecution) { |
||
1114 | $nestedExecution->collectExecutions($activityId, $executions); |
||
1115 | } |
||
1116 | } |
||
1117 | |||
1118 | public function findActiveActivityIds(): array |
||
1119 | { |
||
1120 | $activeActivityIds = []; |
||
1121 | $this->collectActiveActivityIds($activeActivityIds); |
||
1122 | return $activeActivityIds; |
||
1123 | } |
||
1124 | |||
1125 | protected function collectActiveActivityIds(array &$activeActivityIds): void |
||
1126 | { |
||
1127 | $activity = $this->getActivity(); |
||
1128 | if ($this->isActive && $activity !== null) { |
||
1129 | $activeActivityIds[] = $activity->getId(); |
||
1130 | } |
||
1131 | |||
1132 | foreach ($this->getExecutions() as $execution) { |
||
1133 | $execution->collectActiveActivityIds($activeActivityIds); |
||
1134 | } |
||
1135 | } |
||
1136 | |||
1137 | // business key ///////////////////////////////////////// |
||
1138 | |||
1139 | public function getProcessBusinessKey(): ?string |
||
1140 | { |
||
1141 | return $this->getProcessInstance()->getBusinessKey(); |
||
1142 | } |
||
1143 | |||
1144 | public function setProcessBusinessKey(string $businessKey): void |
||
1145 | { |
||
1146 | $processInstance = $this->getProcessInstance(); |
||
1147 | $processInstance->setBusinessKey($businessKey); |
||
1148 | |||
1149 | $historyLevel = Context::getCommandContext()->getProcessEngineConfiguration()->getHistoryLevel(); |
||
1150 | if ($historyLevel->isHistoryEventProduced(HistoryEventTypes::processInstanceUpdate(), $processInstance)) { |
||
1151 | HistoryEventProcessor::processHistoryEvents(new class ($processInstance) extends HistoryEventCreator { |
||
1152 | private $processInstance; |
||
1153 | |||
1154 | public function __construct($processInstance) |
||
1155 | { |
||
1156 | $this->processInstance = $processInstance; |
||
1157 | } |
||
1158 | |||
1159 | public function createHistoryEvent(HistoryEventProducerInterface $producer): HistoryEvent |
||
1160 | { |
||
1161 | return $producer->createProcessInstanceUpdateEvt($this->processInstance); |
||
1162 | } |
||
1163 | }); |
||
1164 | } |
||
1165 | } |
||
1166 | |||
1167 | public function getBusinessKey(): ?string |
||
1168 | { |
||
1169 | if ($this->isProcessInstanceExecution()) { |
||
1170 | return $this->businessKey; |
||
1171 | } |
||
1172 | return $this->getProcessBusinessKey(); |
||
1173 | } |
||
1174 | |||
1175 | // process definition /////////////////////////////////////////////////////// |
||
1176 | |||
1177 | public function setProcessDefinition(ProcessDefinitionImpl $processDefinition): void |
||
1178 | { |
||
1179 | $this->processDefinition = $processDefinition; |
||
1180 | } |
||
1181 | |||
1182 | public function getProcessDefinition(): ProcessDefinitionImpl |
||
1183 | { |
||
1184 | return $this->processDefinition; |
||
1185 | } |
||
1186 | |||
1187 | // process instance ///////////////////////////////////////////////////////// |
||
1188 | |||
1189 | /** |
||
1190 | * ensures initialization and returns the process instance. |
||
1191 | */ |
||
1192 | |||
1193 | abstract public function getProcessInstance(): PvmExecutionImpl; |
||
1194 | |||
1195 | abstract public function setProcessInstance(PvmExecutionImpl $pvmExecutionImpl): void; |
||
1196 | |||
1197 | // case instance id ///////////////////////////////////////////////////////// |
||
1198 | |||
1199 | /*public function getCaseInstanceId(): ?string |
||
1200 | { |
||
1201 | return $this->caseInstanceId; |
||
1202 | } |
||
1203 | |||
1204 | public function setCaseInstanceId(string $caseInstanceId): void |
||
1205 | { |
||
1206 | $this->caseInstanceId = $caseInstanceId; |
||
1207 | }*/ |
||
1208 | |||
1209 | // activity ///////////////////////////////////////////////////////////////// |
||
1210 | |||
1211 | /** |
||
1212 | * ensures initialization and returns the activity |
||
1213 | */ |
||
1214 | public function getActivity(): ?ActivityImpl |
||
1215 | { |
||
1216 | return $this->activity; |
||
1217 | } |
||
1218 | |||
1219 | public function getActivityId(): ?string |
||
1220 | { |
||
1221 | $activity = $this->getActivity(); |
||
1222 | if ($activity !== null) { |
||
1223 | return $activity->getId(); |
||
1224 | } |
||
1225 | return null; |
||
1226 | } |
||
1227 | |||
1228 | public function getCurrentActivityName(): ?string |
||
1229 | { |
||
1230 | $activity = $this->getActivity(); |
||
1231 | if ($activity !== null) { |
||
1232 | return $activity->getName(); |
||
1233 | } |
||
1234 | return null; |
||
1235 | } |
||
1236 | |||
1237 | public function getCurrentActivityId(): ?string |
||
1238 | { |
||
1239 | return $this->getActivityId(); |
||
1240 | } |
||
1241 | |||
1242 | public function setActivity(?PvmActivityInterface $activity = null): void |
||
1243 | { |
||
1244 | $this->activity = $activity; |
||
1245 | } |
||
1246 | |||
1247 | public function enterActivityInstance(): void |
||
1248 | { |
||
1249 | $activity = $this->getActivity(); |
||
1250 | $activityInstanceId = $this->generateActivityInstanceId($activity->getId()); |
||
1251 | |||
1252 | //LOG.debugEnterActivityInstance(this, getParentActivityInstanceId()); |
||
1253 | |||
1254 | // <LEGACY>: in general, io mappings may only exist when the activity is scope |
||
1255 | // however, for multi instance activities, the inner activity does not become a scope |
||
1256 | // due to the presence of an io mapping. In that case, it is ok to execute the io mapping |
||
1257 | // anyway because the multi-instance body already ensures variable isolation |
||
1258 | $this->executeIoMapping(); |
||
1259 | |||
1260 | if ($activity->isScope()) { |
||
1261 | $this->initializeTimerDeclarations(); |
||
1262 | } |
||
1263 | |||
1264 | $this->activityInstanceEndListenersFailed = false; |
||
1265 | } |
||
1266 | |||
1267 | public function activityInstanceStarting(): void |
||
1268 | { |
||
1269 | $this->activityInstanceState = ActivityInstanceState::starting()->getStateCode(); |
||
1270 | } |
||
1271 | |||
1272 | public function activityInstanceStarted(): void |
||
1273 | { |
||
1274 | $this->activityInstanceState = ActivityInstanceState::default()->getStateCode(); |
||
1275 | } |
||
1276 | |||
1277 | public function activityInstanceDone(): void |
||
1278 | { |
||
1279 | $this->activityInstanceState = ActivityInstanceState::ending()->getStateCode(); |
||
1280 | } |
||
1281 | |||
1282 | public function activityInstanceEndListenerFailure(): void |
||
1283 | { |
||
1284 | $this->activityInstanceEndListenersFailed = true; |
||
1285 | } |
||
1286 | |||
1287 | abstract protected function generateActivityInstanceId(string $activityId): string; |
||
1288 | |||
1289 | public function leaveActivityInstance(): void |
||
1290 | { |
||
1291 | if ($this->activityInstanceId !== null) { |
||
1292 | //LOG.debugLeavesActivityInstance(this, activityInstanceId); |
||
1293 | } |
||
1294 | $this->activityInstanceId = $this->getParentActivityInstanceId(); |
||
1295 | |||
1296 | $this->activityInstanceState = ActivityInstanceState::default()->getStateCode(); |
||
1297 | $this->activityInstanceEndListenersFailed = false; |
||
1298 | } |
||
1299 | |||
1300 | public function getParentActivityInstanceId(): ?string |
||
1306 | } |
||
1307 | } |
||
1308 | |||
1309 | public function setActivityInstanceId(?string $activityInstanceId): void |
||
1310 | { |
||
1311 | $this->activityInstanceId = $activityInstanceId; |
||
1312 | } |
||
1313 | |||
1314 | // parent /////////////////////////////////////////////////////////////////// |
||
1315 | |||
1316 | /** |
||
1317 | * ensures initialization and returns the parent |
||
1318 | */ |
||
1319 | abstract public function getParent(): ?PvmExecutionImpl; |
||
1320 | |||
1321 | public function getParentId(): ?string |
||
1322 | { |
||
1323 | $parent = $this->getParent(); |
||
1324 | if ($parent !== null) { |
||
1325 | return $parent->getId(); |
||
1326 | } |
||
1327 | return null; |
||
1328 | } |
||
1329 | |||
1330 | public function hasChildren(): bool |
||
1331 | { |
||
1332 | return !empty($this->getExecutions()); |
||
1333 | } |
||
1334 | |||
1335 | /** |
||
1336 | * Sets the execution's parent and updates the old and new parents' set of |
||
1337 | * child executions |
||
1338 | */ |
||
1339 | public function setParent(PvmExecutionImpl $parent): void |
||
1340 | { |
||
1341 | $currentParent = $this->getParent(); |
||
1342 | |||
1343 | $this->setParentExecution($parent); |
||
1344 | |||
1345 | if ($currentParent !== null) { |
||
1346 | $currentParent->removeExecution($this); |
||
1347 | } |
||
1348 | |||
1349 | if ($parent !== null) { |
||
1350 | $parent->addExecution($this); |
||
1351 | } |
||
1352 | } |
||
1353 | |||
1354 | /** |
||
1355 | * Use #setParent to also update the child execution sets |
||
1356 | */ |
||
1357 | abstract public function setParentExecution(PvmExecutionImpl $parent): void; |
||
1358 | |||
1359 | // super- and subprocess executions ///////////////////////////////////////// |
||
1360 | |||
1361 | abstract public function getSuperExecution(): ?PvmExecutionImpl; |
||
1362 | |||
1363 | abstract public function setSuperExecution(PvmExecutionImpl $superExecution): void; |
||
1364 | |||
1365 | abstract public function getSubProcessInstance(): ?PvmExecutionImpl; |
||
1366 | |||
1367 | abstract public function setSubProcessInstance(?PvmExecutionImpl $subProcessInstance): void; |
||
1368 | |||
1369 | // super case execution ///////////////////////////////////////////////////// |
||
1370 | |||
1371 | //abstract public function CmmnExecution getSuperCaseExecution(); |
||
1372 | |||
1373 | //abstract public function void setSuperCaseExecution(CmmnExecution superCaseExecution); |
||
1374 | |||
1375 | // sub case execution /////////////////////////////////////////////////////// |
||
1376 | |||
1377 | //abstract public function CmmnExecution getSubCaseInstance(); |
||
1378 | |||
1379 | //abstract public function void setSubCaseInstance(CmmnExecution subCaseInstance); |
||
1380 | |||
1381 | // scopes /////////////////////////////////////////////////////////////////// |
||
1382 | |||
1383 | protected function getScopeActivity(): ScopeImpl |
||
1384 | { |
||
1385 | $scope = null; |
||
1386 | // this if condition is important during process instance startup |
||
1387 | // where the activity of the process instance execution may not be aligned |
||
1388 | // with the execution tree |
||
1389 | if ($this->isProcessInstanceExecution()) { |
||
1390 | $scope = $this->getProcessDefinition(); |
||
1391 | } else { |
||
1392 | $scope = $this->getActivity(); |
||
1393 | } |
||
1394 | return $scope; |
||
1395 | } |
||
1396 | |||
1397 | public function isScope(): bool |
||
1398 | { |
||
1399 | return $this->isScope; |
||
1400 | } |
||
1401 | |||
1402 | public function setScope(bool $isScope): void |
||
1403 | { |
||
1404 | $this->isScope = $isScope; |
||
1405 | } |
||
1406 | |||
1407 | /** |
||
1408 | * For a given target flow scope, this method returns the corresponding scope execution. |
||
1409 | * <p> |
||
1410 | * Precondition: the execution is active and executing an activity. |
||
1411 | * Can be invoked for scope and non scope executions. |
||
1412 | * |
||
1413 | * @param targetFlowScope mixed - scope activity or process definition for which the scope execution should be found |
||
1414 | * @return PvmExecutionImpl the scope execution for the provided targetFlowScope |
||
1415 | */ |
||
1416 | public function findExecutionForFlowScope($targetFlowScope): ?PvmExecutionImpl |
||
1417 | { |
||
1418 | if (is_string($targetFlowScope)) { |
||
1419 | $targetScopeId = $targetFlowScope; |
||
1420 | EnsureUtil::ensureNotNull("target scope id", "targetScopeId", "targetScopeId", $targetScopeId); |
||
1421 | |||
1422 | $currentActivity = $this->getActivity(); |
||
1423 | EnsureUtil::ensureNotNull("activity of current execution", "currentActivity", $currentActivity); |
||
1424 | |||
1425 | $walker = new FlowScopeWalker($currentActivity); |
||
1426 | $targetFlowScope = $walker->walkUntil(new class ($targetScopeId) implements WalkConditionInterface { |
||
1427 | |||
1428 | private $targetScopeId; |
||
1429 | |||
1430 | public function __construct(string $targetScopeId) |
||
1431 | { |
||
1432 | $this->targetScopeId = $targetScopeId; |
||
1433 | } |
||
1434 | |||
1435 | public function isFulfilled($scope = null): bool |
||
1436 | { |
||
1437 | return $scope === null || $scope->getId() == $this->targetScopeId; |
||
1438 | } |
||
1439 | }); |
||
1440 | |||
1441 | if ($targetFlowScope === null) { |
||
1442 | //throw LOG.scopeNotFoundException(targetScopeId, this->getId()); |
||
1443 | } |
||
1444 | |||
1445 | return $this->findExecutionForFlowScope($targetFlowScope); |
||
1446 | } elseif ($targetFlowScope instanceof PvmScopeInterface) { |
||
1447 | // if this execution is not a scope execution, use the parent |
||
1448 | $scopeExecution = $this->isScope() ? $this : $this->getParent(); |
||
1449 | |||
1450 | $currentActivity = $this->getActivity(); |
||
1451 | EnsureUtil::ensureNotNull("activity of current execution", "currentActivity", $currentActivity); |
||
1452 | |||
1453 | // if this is a scope execution currently executing a non scope activity |
||
1454 | $currentActivity = $currentActivity->isScope() ? $currentActivity : $currentActivity->getFlowScope(); |
||
1455 | |||
1456 | return $scopeExecution->findExecutionForScope($currentActivity, $targetFlowScope); |
||
1457 | } |
||
1458 | } |
||
1459 | |||
1460 | public function findExecutionForScope(ScopeImpl $currentScope, ScopeImpl $targetScope): ?PvmExecutionImpl |
||
1461 | { |
||
1462 | if (!$targetScope->isScope()) { |
||
1463 | throw new ProcessEngineException("Target scope must be a scope."); |
||
1464 | } |
||
1465 | |||
1466 | $activityExecutionMapping = $this->createActivityExecutionMapping($currentScope); |
||
1467 | $scopeExecution = null; |
||
1468 | foreach ($activityExecutionMapping as $map) { |
||
1469 | if ($map[0] == $targetScope) { |
||
1470 | $scopeExecution = $map[1]; |
||
1471 | } |
||
1472 | } |
||
1473 | if ($scopeExecution === null) { |
||
1474 | // the target scope is scope but no corresponding execution was found |
||
1475 | // => legacy behavior |
||
1476 | $scopeExecution = LegacyBehavior::getScopeExecution(targetScope, activityExecutionMapping); |
||
1477 | } |
||
1478 | return $scopeExecution; |
||
1479 | } |
||
1480 | |||
1481 | protected function getFlowScopeExecution(): PvmExecutionImpl |
||
1482 | { |
||
1483 | if (!$this->isScope || CompensationBehavior::executesNonScopeCompensationHandler($this)) { |
||
1484 | // LEGACY: a correct implementation should also skip a compensation-throwing parent scope execution |
||
1485 | // (since compensation throwing activities are scopes), but this cannot be done for backwards compatibility |
||
1486 | // where a compensation throwing activity was no scope (and we would wrongly skip an execution in that case) |
||
1487 | return $this->getParent()->getFlowScopeExecution(); |
||
1488 | } |
||
1489 | return $this; |
||
1490 | } |
||
1491 | |||
1492 | protected function getFlowScope(): ScopeImpl |
||
1493 | { |
||
1494 | $activity = $this->getActivity(); |
||
1495 | |||
1496 | if ( |
||
1497 | !$activity->isScope() || $this->activityInstanceId === null |
||
1498 | || ($activity->isScope() && !$this->isScope() && $activity->getActivityBehavior() instanceof CompositeActivityBehaviorInterface) |
||
1499 | ) { |
||
1500 | // if |
||
1501 | // - this is a scope execution currently executing a non scope activity |
||
1502 | // - or it is not scope but the current activity is (e.g. can happen during activity end, when the actual |
||
1503 | // scope execution has been removed and the concurrent parent has been set to the scope activity) |
||
1504 | // - or it is asyncBefore/asyncAfter |
||
1505 | |||
1506 | return $activity->getFlowScope(); |
||
1507 | } |
||
1508 | return $activity; |
||
1509 | } |
||
1510 | |||
1511 | /** |
||
1512 | * Creates an extended mapping based on this execution and the given existing mapping. |
||
1513 | * Any entry <code>mapping</code> in mapping that corresponds to an ancestor scope of |
||
1514 | * <code>currentScope</code> is reused. |
||
1515 | */ |
||
1516 | protected function createActivityExecutionMapping( |
||
1517 | ?ScopeImpl $currentScope = null, |
||
1518 | ?array $mapping = null |
||
1519 | ): array { |
||
1520 | if ($currentScope !== null && $mapping !== null) { |
||
1521 | if (!$this->isScope()) { |
||
1522 | throw new ProcessEngineException("Execution must be a scope execution"); |
||
1523 | } |
||
1524 | if (!$currentScope->isScope()) { |
||
1525 | throw new ProcessEngineException("Current scope must be a scope."); |
||
1526 | } |
||
1527 | |||
1528 | // collect all ancestor scope executions unless one is encountered that is already in "mapping" |
||
1529 | $scopeExecutionCollector = new ScopeExecutionCollector(); |
||
1530 | (new ExecutionWalker($this)) |
||
1531 | ->addPreVisitor($scopeExecutionCollector) |
||
1532 | ->walkWhile(new class ($mapping) implements WalkConditionInterface { |
||
1533 | private $mapping; |
||
1534 | |||
1535 | public function __construct(array $mapping) |
||
1536 | { |
||
1537 | $this->mapping = $mapping; |
||
1538 | } |
||
1539 | |||
1540 | public function isFulfilled($element = null): bool |
||
1541 | { |
||
1542 | $contains = false; |
||
1543 | foreach ($this->mapping as $map) { |
||
1544 | if ($map[1] == $element) { |
||
1545 | $contains = true; |
||
1546 | break; |
||
1547 | } |
||
1548 | } |
||
1549 | return $element === null || $contains; |
||
1550 | } |
||
1551 | }); |
||
1552 | $scopeExecutions = $scopeExecutionCollector->getScopeExecutions(); |
||
1553 | |||
1554 | // collect all ancestor scopes unless one is encountered that is already in "mapping" |
||
1555 | $scopeCollector = new ScopeCollector(); |
||
1556 | (new FlowScopeWalker($currentScope)) |
||
1557 | ->addPreVisitor($scopeCollector) |
||
1558 | ->walkWhile(new class ($mapping) implements WalkConditionInterface { |
||
1559 | private $mapping; |
||
1560 | |||
1561 | public function __construct(array $mapping) |
||
1562 | { |
||
1563 | $this->mapping = $mapping; |
||
1564 | } |
||
1565 | |||
1566 | public function isFulfilled($element = null): bool |
||
1567 | { |
||
1568 | $contains = false; |
||
1569 | foreach ($this->mapping as $map) { |
||
1570 | if ($map[0] == $element) { |
||
1571 | $contains = true; |
||
1572 | break; |
||
1573 | } |
||
1574 | } |
||
1575 | return $element === null || $contains; |
||
1576 | } |
||
1577 | }); |
||
1578 | |||
1579 | $scopes = $scopeCollector->getScopes(); |
||
1580 | $outerScope = new \stdClass(); |
||
1581 | $outerScope->scopes = $scopes; |
||
1582 | $outerScope->scopeExecutions = $scopeExecutions; |
||
1583 | |||
1584 | // add all ancestor scopes and scopeExecutions that are already in "mapping" |
||
1585 | // and correspond to ancestors of the topmost previously collected scope |
||
1586 | $topMostScope = $scopes[count($scopes) - 1]; |
||
1587 | (new FlowScopeWalker($topMostScope->getFlowScope())) |
||
1588 | ->addPreVisitor(new class ($outerScope, $mapping) implements TreeVisitorInterface { |
||
1589 | private $outerScope; |
||
1590 | private $mapping; |
||
1591 | |||
1592 | public function __construct($outerScope, $mapping) |
||
1593 | { |
||
1594 | $this->outerScope = $outerScope; |
||
1595 | $this->mapping = $mapping; |
||
1596 | } |
||
1597 | |||
1598 | public function visit($obj): void |
||
1599 | { |
||
1600 | $this->outerScope->scopes[] = $obj; |
||
1601 | |||
1602 | $priorMappingExecution = null; |
||
1603 | foreach ($this->mapping as $map) { |
||
1604 | if ($map[0] == $obj) { |
||
1605 | $priorMappingExecution = $map[1]; |
||
1606 | } |
||
1607 | } |
||
1608 | |||
1609 | $contains = false; |
||
1610 | foreach ($this->outerScope->scopeExecutions as $exec) { |
||
1611 | if ($exec == $priorMappingExecution) { |
||
1612 | $contains = true; |
||
1613 | break; |
||
1614 | } |
||
1615 | } |
||
1616 | |||
1617 | if ($priorMappingExecution !== null && !$contains) { |
||
1618 | $this->outerScope->scopeExecutions[] = $priorMappingExecution; |
||
1619 | } |
||
1620 | } |
||
1621 | }) |
||
1622 | ->walkWhile(); |
||
1623 | |||
1624 | $scopes = $outerScope->scopes; |
||
1625 | $scopeExecutions = $outerScope->scopeExecutions; |
||
1626 | |||
1627 | if (count($scopes) == count($scopeExecutions)) { |
||
1628 | // the trees are in sync |
||
1629 | $result = []; |
||
1630 | for ($i = 0; $i < count($scopes); $i += 1) { |
||
1631 | $result[] = [$scopes[$i], $scopeExecutions[$i]]; |
||
1632 | } |
||
1633 | return $result; |
||
1634 | } else { |
||
1635 | // Wounderful! The trees are out of sync. This is due to legacy behavior |
||
1636 | return LegacyBehavior::createActivityExecutionMapping($scopeExecutions, $scopes); |
||
1637 | } |
||
1638 | } elseif ($currentScope !== null) { |
||
1639 | if (!$this->isScope()) { |
||
1640 | throw new ProcessEngineException("Execution must be a scope execution"); |
||
1641 | } |
||
1642 | if (!$currentScope->isScope()) { |
||
1643 | throw new ProcessEngineException("Current scope must be a scope."); |
||
1644 | } |
||
1645 | |||
1646 | // A single path in the execution tree from a leaf (no child executions) to the root |
||
1647 | // may in fact contain multiple executions that correspond to leaves in the activity instance hierarchy. |
||
1648 | // |
||
1649 | // This is because compensation throwing executions have child executions. In that case, the |
||
1650 | // flow scope hierarchy is not aligned with the scope execution hierarchy: There is a scope |
||
1651 | // execution for a compensation-throwing event that is an ancestor of this execution, |
||
1652 | // while these events are not ancestor scopes of currentScope. |
||
1653 | // |
||
1654 | // The strategy to deal with this situation is as follows: |
||
1655 | // 1. Determine all executions that correspond to leaf activity instances |
||
1656 | // 2. Order the leaf executions in top-to-bottom fashion |
||
1657 | // 3. Iteratively build the activity execution mapping based on the leaves in top-to-bottom order |
||
1658 | // 3.1. For the first leaf, create the activity execution mapping regularly |
||
1659 | // 3.2. For every following leaf, rebuild the mapping but reuse any scopes and scope executions |
||
1660 | // that are part of the mapping created in the previous iteration |
||
1661 | // |
||
1662 | // This process ensures that the resulting mapping does not contain scopes that are not ancestors |
||
1663 | // of currentScope and that it does not contain scope executions for such scopes. |
||
1664 | // For any execution hierarchy that does not involve compensation, the number of iterations in step 3 |
||
1665 | // should be 1, i.e. there are no other leaf activity instance executions in the hierarchy. |
||
1666 | |||
1667 | // 1. Find leaf activity instance executions |
||
1668 | $leafCollector = new LeafActivityInstanceExecutionCollector(); |
||
1669 | (new ExecutionWalker($this))->addPreVisitor($leafCollector)->walkUntil(); |
||
1670 | |||
1671 | $leafCollector->removeLeaf($this); |
||
1672 | $leaves = $leafCollector->getLeaves(); |
||
1673 | |||
1674 | // 2. Order them from top to bottom |
||
1675 | $leaves = array_reverse($leaves); |
||
1676 | |||
1677 | // 3. Iteratively extend the mapping for every additional leaf |
||
1678 | $mapping = []; |
||
1679 | foreach ($leaves as $leaf) { |
||
1680 | $leafFlowScope = $leaf->getFlowScope(); |
||
1681 | $leafFlowScopeExecution = $leaf->getFlowScopeExecution(); |
||
1682 | |||
1683 | $mapping = $leafFlowScopeExecution->createActivityExecutionMapping($leafFlowScope, $mapping); |
||
1684 | } |
||
1685 | |||
1686 | // finally extend the mapping for the current execution |
||
1687 | // (note that the current execution need not be a leaf itself) |
||
1688 | $mapping = $this->createActivityExecutionMapping($currentScope, $mapping); |
||
1689 | |||
1690 | return $mapping; |
||
1691 | } elseif ($currentScope === null && $mapping === null) { |
||
1692 | $currentActivity = $this->getActivity(); |
||
1693 | EnsureUtil::ensureNotNull("activity of current execution", "currentActivity", $currentActivity); |
||
1694 | |||
1695 | $flowScope = $this->getFlowScope(); |
||
1696 | $flowScopeExecution = $this->getFlowScopeExecution(); |
||
1697 | |||
1698 | return $flowScopeExecution->createActivityExecutionMapping($flowScope); |
||
1699 | } |
||
1700 | } |
||
1701 | |||
1702 | // toString ///////////////////////////////////////////////////////////////// |
||
1703 | |||
1704 | public function __toString() |
||
1705 | { |
||
1706 | if ($this->isProcessInstanceExecution()) { |
||
1707 | return "ProcessInstance[" . $this->getToStringIdentity() . "]"; |
||
1708 | } else { |
||
1709 | return ($this->isConcurrent ? "Concurrent" : "") . ($this->isScope ? "Scope" : "") . "Execution[" . $this->getToStringIdentity() . "]"; |
||
1710 | } |
||
1711 | } |
||
1712 | |||
1713 | protected function getToStringIdentity(): string |
||
1714 | { |
||
1715 | return $this->id; |
||
1716 | } |
||
1717 | |||
1718 | // variables //////////////////////////////////////////// |
||
1719 | |||
1720 | public function getVariableScopeKey(): string |
||
1721 | { |
||
1722 | return "execution"; |
||
1723 | } |
||
1724 | |||
1725 | public function getParentVariableScope(): ?AbstractVariableScope |
||
1726 | { |
||
1727 | return $this->getParent(); |
||
1728 | } |
||
1729 | |||
1730 | /** |
||
1731 | * {@inheritDoc} |
||
1732 | */ |
||
1733 | public function setVariable(string $variableName, $value, ?string $targetActivityId = null): void |
||
1734 | { |
||
1735 | $activityId = $this->getActivityId(); |
||
1736 | if (!empty($activityId) && $activityId == $targetActivityId) { |
||
1737 | $this->setVariableLocal($variableName, $value); |
||
1738 | } else { |
||
1739 | $executionForFlowScope = $this->findExecutionForFlowScope($targetActivityId); |
||
1740 | if ($executionForFlowScope !== null) { |
||
1741 | $executionForFlowScope->setVariableLocal($variableName, $value); |
||
1742 | } |
||
1743 | } |
||
1744 | } |
||
1745 | |||
1746 | // sequence counter /////////////////////////////////////////////////////////// |
||
1747 | |||
1748 | public function getSequenceCounter(): int |
||
1749 | { |
||
1750 | return $this->sequenceCounter; |
||
1751 | } |
||
1752 | |||
1753 | public function setSequenceCounter(int $sequenceCounter): void |
||
1754 | { |
||
1755 | $this->sequenceCounter = $sequenceCounter; |
||
1756 | } |
||
1757 | |||
1758 | public function incrementSequenceCounter(): void |
||
1759 | { |
||
1760 | $this->sequenceCounter += 1; |
||
1761 | } |
||
1762 | |||
1763 | // Getter / Setters /////////////////////////////////// |
||
1764 | |||
1765 | public function isExternallyTerminated(): bool |
||
1766 | { |
||
1767 | return $this->externallyTerminated; |
||
1768 | } |
||
1769 | |||
1770 | public function setExternallyTerminated(bool $externallyTerminated): void |
||
1771 | { |
||
1772 | $this->externallyTerminated = $externallyTerminated; |
||
1773 | } |
||
1774 | |||
1775 | public function getDeleteReason(): ?string |
||
1776 | { |
||
1777 | return $this->deleteReason; |
||
1778 | } |
||
1779 | |||
1780 | public function setDeleteReason(string $deleteReason): void |
||
1781 | { |
||
1782 | $this->deleteReason = $deleteReason; |
||
1783 | } |
||
1784 | |||
1785 | public function isDeleteRoot(): bool |
||
1786 | { |
||
1787 | return $this->deleteRoot; |
||
1788 | } |
||
1789 | |||
1790 | public function setDeleteRoot(bool $deleteRoot): void |
||
1791 | { |
||
1792 | $this->deleteRoot = $deleteRoot; |
||
1793 | } |
||
1794 | |||
1795 | public function getTransition(): ?TransitionImpl |
||
1796 | { |
||
1797 | return $this->transition; |
||
1798 | } |
||
1799 | |||
1800 | public function getTransitionsToTake(): array |
||
1801 | { |
||
1802 | return $this->transitionsToTake; |
||
1803 | } |
||
1804 | |||
1805 | public function setTransitionsToTake(array $transitionsToTake): void |
||
1806 | { |
||
1807 | $this->transitionsToTake = $transitionsToTake; |
||
1808 | } |
||
1809 | |||
1810 | public function getCurrentTransitionId(): ?string |
||
1811 | { |
||
1812 | $transition = $this->getTransition(); |
||
1813 | if ($transition !== null) { |
||
1814 | return $transition->getId(); |
||
1815 | } |
||
1816 | return null; |
||
1817 | } |
||
1818 | |||
1819 | public function setTransition(PvmTransitionInterface $transition): void |
||
1820 | { |
||
1821 | $this->transition = $transition; |
||
1822 | } |
||
1823 | |||
1824 | public function isConcurrent(): bool |
||
1825 | { |
||
1826 | return $this->isConcurrent; |
||
1827 | } |
||
1828 | |||
1829 | public function setConcurrent(bool $isConcurrent): void |
||
1830 | { |
||
1831 | $this->isConcurrent = $isConcurrent; |
||
1832 | } |
||
1833 | |||
1834 | public function setActive(bool $isActive): void |
||
1835 | { |
||
1836 | $this->isActive = $isActive; |
||
1837 | } |
||
1838 | |||
1839 | public function setEnded(bool $isEnded): void |
||
1840 | { |
||
1841 | $this->isEnded = $isEnded; |
||
1842 | } |
||
1843 | |||
1844 | public function isEnded(): bool |
||
1845 | { |
||
1846 | return $this->isEnded; |
||
1847 | } |
||
1848 | |||
1849 | public function isCanceled(): bool |
||
1850 | { |
||
1851 | return ActivityInstanceState::canceled()->getStateCode() == $this->activityInstanceState; |
||
1852 | } |
||
1853 | |||
1854 | public function setCanceled(bool $canceled): void |
||
1855 | { |
||
1856 | if ($this->canceled) { |
||
1857 | $this->activityInstanceState = ActivityInstanceState::canceled()->getStateCode(); |
||
1858 | } |
||
1859 | } |
||
1860 | |||
1861 | public function isCompleteScope(): bool |
||
1862 | { |
||
1863 | return ActivityInstanceState::scopeComplete()->getStateCode() == $this->activityInstanceState; |
||
1864 | } |
||
1865 | |||
1866 | public function setCompleteScope(bool $completeScope): void |
||
1867 | { |
||
1868 | if ($completeScope && !$this->isCanceled()) { |
||
1869 | $this->activityInstanceState = ActivityInstanceState::scopeComplete()->getStateCode(); |
||
1870 | } |
||
1871 | } |
||
1872 | |||
1873 | public function setPreserveScope(bool $preserveScope): void |
||
1874 | { |
||
1875 | $this->preserveScope = $preserveScope; |
||
1876 | } |
||
1877 | |||
1878 | public function isPreserveScope(): bool |
||
1879 | { |
||
1880 | return $this->preserveScope; |
||
1881 | } |
||
1882 | |||
1883 | public function getActivityInstanceState(): int |
||
1886 | } |
||
1887 | |||
1888 | public function isInState(ActivityInstanceState $state): bool |
||
1889 | { |
||
1890 | return $this->activityInstanceState == $state->getStateCode(); |
||
1891 | } |
||
1892 | |||
1893 | public function hasFailedOnEndListeners(): bool |
||
1894 | { |
||
1895 | return $this->activityInstanceEndListenersFailed; |
||
1896 | } |
||
1897 | |||
1898 | public function isEventScope(): bool |
||
1899 | { |
||
1900 | return $this->isEventScope; |
||
1901 | } |
||
1902 | |||
1903 | public function setEventScope(bool $isEventScope): void |
||
1904 | { |
||
1905 | $this->isEventScope = $isEventScope; |
||
1906 | } |
||
1907 | |||
1908 | public function getScopeInstantiationContext(): ?ScopeInstantiationContext |
||
1909 | { |
||
1910 | return $this->scopeInstantiationContext; |
||
1911 | } |
||
1912 | |||
1913 | public function disposeScopeInstantiationContext(): void |
||
1914 | { |
||
1915 | $this->scopeInstantiationContext = null; |
||
1916 | |||
1917 | $parent = $this; |
||
1918 | while (($parent = $parent->getParent()) !== null && $parent->scopeInstantiationContext !== null) { |
||
1919 | $parent->scopeInstantiationContext = null; |
||
1920 | } |
||
1921 | } |
||
1922 | |||
1923 | public function getNextActivity(): PvmActivityInterface |
||
1924 | { |
||
1925 | return $this->nextActivity; |
||
1926 | } |
||
1927 | |||
1928 | public function isProcessInstanceExecution(): bool |
||
1929 | { |
||
1930 | return $this->getParent() === null; |
||
1931 | } |
||
1932 | |||
1933 | public function setStartContext(ScopeInstantiationContext $startContext): void |
||
1934 | { |
||
1935 | $this->scopeInstantiationContext = $startContext; |
||
1936 | } |
||
1937 | |||
1938 | public function setIgnoreAsync(bool $ignoreAsync): void |
||
1939 | { |
||
1940 | $this->ignoreAsync = $ignoreAsync; |
||
1941 | } |
||
1942 | |||
1943 | public function isIgnoreAsync(): bool |
||
1944 | { |
||
1945 | return $this->ignoreAsync; |
||
1946 | } |
||
1947 | |||
1948 | public function setStarting(bool $isStarting): void |
||
1949 | { |
||
1950 | $this->isStarting = $isStarting; |
||
1951 | } |
||
1952 | |||
1953 | public function isStarting(): bool |
||
1954 | { |
||
1955 | return $this->isStarting; |
||
1956 | } |
||
1957 | |||
1958 | public function isProcessInstanceStarting(): bool |
||
1959 | { |
||
1960 | return $this->getProcessInstance()->isStarting(); |
||
1961 | } |
||
1962 | |||
1963 | public function setProcessInstanceStarting(bool $starting): void |
||
1964 | { |
||
1965 | $this->getProcessInstance()->setStarting($starting); |
||
1966 | } |
||
1967 | |||
1968 | public function setNextActivity(PvmActivityInterface $nextActivity): void |
||
1969 | { |
||
1970 | $this->nextActivity = $nextActivity; |
||
1971 | } |
||
1972 | |||
1973 | public function getParentScopeExecution(bool $considerSuperExecution): PvmExecutionImpl |
||
1974 | { |
||
1975 | if ($this->isProcessInstanceExecution()) { |
||
1976 | if ($considerSuperExecution && $this->getSuperExecution() !== null) { |
||
1977 | $superExecution = $this->getSuperExecution(); |
||
1978 | if ($superExecution->isScope()) { |
||
1979 | return $superExecution; |
||
1980 | } else { |
||
1981 | return $superExecution->getParent(); |
||
1982 | } |
||
1983 | } else { |
||
1984 | return null; |
||
1985 | } |
||
1986 | } else { |
||
1987 | $parent = $this->getParent(); |
||
1988 | if ($parent->isScope()) { |
||
1989 | return $parent; |
||
1990 | } else { |
||
1991 | return $parent->getParent(); |
||
1992 | } |
||
1993 | } |
||
1994 | } |
||
1995 | |||
1996 | /** |
||
1997 | * Contains the delayed variable events, which will be dispatched on a save point. |
||
1998 | */ |
||
1999 | protected $delayedEvents = []; |
||
2000 | |||
2001 | /** |
||
2002 | * Delays and stores the given DelayedVariableEvent on the process instance. |
||
2003 | * |
||
2004 | * @param delayedVariableEvent the DelayedVariableEvent which should be store on the process instance |
||
2005 | */ |
||
2006 | public function delayEvent($target, ?VariableEvent $variableEvent = null): void |
||
2007 | { |
||
2008 | if ($target instanceof PvmExecutionImpl) { |
||
2009 | $delayedVariableEvent = new DelayedVariableEvent($target, $variableEvent); |
||
2010 | $this->delayEvent($delayedVariableEvent); |
||
2011 | } elseif ($target instanceof DelayedVariableEvent) { |
||
2012 | //if process definition has no conditional events the variable events does not have to be delayed |
||
2013 | $hasConditionalEvents = $this->getProcessDefinition()->getProperties()->get(BpmnProperties::hasConditionalEvents()); |
||
2014 | if ($hasConditionalEvents === null || $hasConditionalEvents != true) { |
||
2015 | return; |
||
2016 | } |
||
2017 | |||
2018 | if ($this->isProcessInstanceExecution()) { |
||
2019 | $this->delayedEvents[] = $target; |
||
2020 | } else { |
||
2021 | $this->getProcessInstance()->delayEvent($target); |
||
2022 | } |
||
2023 | } |
||
2024 | } |
||
2025 | |||
2026 | /** |
||
2027 | * The current delayed variable events. |
||
2028 | * |
||
2029 | * @return a list of DelayedVariableEvent objects |
||
2030 | */ |
||
2031 | public function getDelayedEvents(): array |
||
2032 | { |
||
2033 | if ($this->isProcessInstanceExecution()) { |
||
2034 | return $this->delayedEvents; |
||
2035 | } |
||
2036 | return $this->getProcessInstance()->getDelayedEvents(); |
||
2037 | } |
||
2038 | |||
2039 | /** |
||
2040 | * Cleares the current delayed variable events. |
||
2041 | */ |
||
2042 | public function clearDelayedEvents(): void |
||
2043 | { |
||
2044 | if ($this->isProcessInstanceExecution()) { |
||
2045 | $this->delayedEvents = []; |
||
2046 | } else { |
||
2047 | $this->getProcessInstance()->clearDelayedEvents(); |
||
2048 | } |
||
2049 | } |
||
2050 | |||
2051 | /** |
||
2052 | * Dispatches the current delayed variable events and performs the given atomic operation |
||
2053 | * if the current state was not changed. |
||
2054 | * |
||
2055 | * @param continuation the atomic operation continuation which should be executed |
||
2056 | */ |
||
2057 | public function dispatchDelayedEventsAndPerformOperation($continuation = null): void |
||
2058 | { |
||
2059 | if ($continuation instanceof PvmAtomicOperationInterface) { |
||
2060 | $this->dispatchDelayedEventsAndPerformOperation(new class ($continuation) implements CallbackInterface { |
||
2061 | private $atomicOperation; |
||
2062 | |||
2063 | public function __construct(PvmAtomicOperationInterface $atomicOperation) |
||
2064 | { |
||
2065 | $this->atomicOperation = $atomicOperation; |
||
2066 | } |
||
2067 | |||
2068 | public function callback($param) |
||
2069 | { |
||
2070 | $param->performOperation($this->atomicOperation); |
||
2071 | return null; |
||
2072 | } |
||
2073 | }); |
||
2074 | } elseif ($continuation instanceof CallbackInterface) { |
||
2075 | $execution = $this; |
||
2076 | |||
2077 | if (empty($execution->getDelayedEvents())) { |
||
2078 | $this->continueExecutionIfNotCanceled($continuation, $execution); |
||
2079 | return; |
||
2080 | } |
||
2081 | |||
2082 | $this->continueIfExecutionDoesNotAffectNextOperation(new class ($this) implements CallbackInterface { |
||
2083 | private $scope; |
||
2084 | |||
2085 | public function __construct(PvmExecutionImpl $scope) |
||
2086 | { |
||
2087 | $this->scope = $scope; |
||
2088 | } |
||
2089 | |||
2090 | public function callback(PvmExecutionImpl $execution) |
||
2091 | { |
||
2092 | $this->scope->dispatchScopeEvents($execution); |
||
2093 | return null; |
||
2094 | } |
||
2095 | }, new class () implements CallbackInterface { |
||
2096 | private $scope; |
||
2097 | private $continuation; |
||
2098 | |||
2099 | public function __construct(PvmExecutionImpl $scope, $continuation) |
||
2100 | { |
||
2101 | $this->scope = $scope; |
||
2102 | $this->continuation = $continuation; |
||
2103 | } |
||
2104 | |||
2105 | public function callback(PvmExecutionImpl $execution) |
||
2106 | { |
||
2107 | $this->scope->continueExecutionIfNotCanceled($this->continuation, $execution); |
||
2108 | return null; |
||
2109 | } |
||
2110 | }, $execution); |
||
2111 | } |
||
2112 | } |
||
2113 | |||
2114 | /** |
||
2115 | * Executes the given depending operations with the given execution. |
||
2116 | * The execution state will be checked with the help of the activity instance id and activity id of the execution before and after |
||
2117 | * the dispatching callback call. If the id's are not changed the |
||
2118 | * continuation callback is called. |
||
2119 | * |
||
2120 | * @param dispatching the callback to dispatch the variable events |
||
2121 | * @param continuation the callback to continue with the next atomic operation |
||
2122 | * @param execution the execution which is used for the execution |
||
2123 | */ |
||
2124 | public function continueIfExecutionDoesNotAffectNextOperation( |
||
2125 | CallbackInterface $dispatching, |
||
2126 | CallbackInterface $continuation, |
||
2127 | PvmExecutionImpl $execution |
||
2128 | ): void { |
||
2129 | $lastActivityId = $execution->getActivityId(); |
||
2130 | $lastActivityInstanceId = $this->getActivityInstanceId($execution); |
||
2131 | |||
2132 | $dispatching->callback($execution); |
||
2133 | |||
2134 | $execution = $execution->getReplacedBy() !== null ? $execution->getReplacedBy() : $execution; |
||
2135 | $currentActivityInstanceId = $this->getActivityInstanceId($execution); |
||
2136 | $currentActivityId = $execution->getActivityId(); |
||
2137 | |||
2138 | //if execution was canceled or was changed during the dispatch we should not execute the next operation |
||
2139 | //since another atomic operation was executed during the dispatching |
||
2140 | if (!$execution->isCanceled() && $this->isOnSameActivity($lastActivityInstanceId, $lastActivityId, $currentActivityInstanceId, $currentActivityId)) { |
||
2141 | $continuation->callback($execution); |
||
2142 | } |
||
2143 | } |
||
2144 | |||
2145 | protected function continueExecutionIfNotCanceled(?CallbackInterface $continuation, PvmExecutionImpl $execution): void |
||
2146 | { |
||
2147 | if ($continuation !== null && !$execution->isCanceled()) { |
||
2148 | $continuation->callback($execution); |
||
2149 | } |
||
2150 | } |
||
2151 | |||
2152 | /** |
||
2153 | * Dispatches the current delayed variable events on the scope of the given execution. |
||
2154 | * |
||
2155 | * @param execution the execution on which scope the delayed variable should be dispatched |
||
2156 | */ |
||
2157 | protected function dispatchScopeEvents(PvmExecutionImpl $execution): void |
||
2158 | { |
||
2159 | $scopeExecution = $execution->isScope() ? $execution : $execution->getParent(); |
||
2160 | |||
2161 | $delayedEvents = $scopeExecution->getDelayedEvents(); |
||
2162 | $scopeExecution->clearDelayedEvents(); |
||
2163 | |||
2164 | $activityInstanceIds = []; |
||
2165 | $activityIds = []; |
||
2166 | $this->initActivityIds($delayedEvents, $activityInstanceIds, $activityIds); |
||
2167 | |||
2168 | //For each delayed variable event we have to check if the delayed event can be dispatched, |
||
2169 | //the check will be done with the help of the activity id and activity instance id. |
||
2170 | //That means it will be checked if the dispatching changed the execution tree in a way that we can't dispatch the |
||
2171 | //the other delayed variable events. We have to check the target scope with the last activity id and activity instance id |
||
2172 | //and also the replace pointer if it exist. Because on concurrency the replace pointer will be set on which we have |
||
2173 | //to check the latest state. |
||
2174 | foreach ($delayedEvents as $event) { |
||
2175 | $targetScope = $event->getTargetScope(); |
||
2176 | $replaced = $targetScope->getReplacedBy() !== null ? $targetScope->getReplacedBy() : $targetScope; |
||
2177 | $this->dispatchOnSameActivity($targetScope, $replaced, $activityIds, $activityInstanceIds, $event); |
||
2178 | } |
||
2179 | } |
||
2180 | |||
2181 | /** |
||
2182 | * Initializes the given maps with the target scopes and current activity id's and activity instance id's. |
||
2183 | * |
||
2184 | * @param delayedEvents the delayed events which contains the information about the target scope |
||
2185 | * @param activityInstanceIds the map which maps target scope to activity instance id |
||
2186 | * @param activityIds the map which maps target scope to activity id |
||
2187 | */ |
||
2188 | protected function initActivityIds( |
||
2189 | array $delayedEvents, |
||
2190 | array &$activityInstanceIds, |
||
2191 | array &$activityIds |
||
2192 | ): void { |
||
2193 | foreach ($delayedEvents as $event) { |
||
2194 | $targetScope = $event->getTargetScope(); |
||
2195 | |||
2196 | $targetScopeActivityInstanceId = $this->getActivityInstanceId($targetScope); |
||
2197 | $activityInstanceIds[] = [$targetScope, $targetScopeActivityInstanceId]; |
||
2198 | $activityIds[] = [$targetScope, $targetScope->getActivityId()]; |
||
2199 | } |
||
2200 | } |
||
2201 | |||
2202 | /** |
||
2203 | * Dispatches the delayed variable event, if the target scope and replaced by scope (if target scope was replaced) have the |
||
2204 | * same activity Id's and activity instance id's. |
||
2205 | * |
||
2206 | * @param targetScope the target scope on which the event should be dispatched |
||
2207 | * @param replacedBy the replaced by pointer which should have the same state |
||
2208 | * @param activityIds the map which maps scope to activity id |
||
2209 | * @param activityInstanceIds the map which maps scope to activity instance id |
||
2210 | * @param delayedVariableEvent the delayed variable event which should be dispatched |
||
2211 | */ |
||
2212 | private function dispatchOnSameActivity( |
||
2213 | PvmExecutionImpl $targetScope, |
||
2214 | PvmExecutionImpl $replacedBy, |
||
2215 | array $activityIds, |
||
2216 | array $activityInstanceIds, |
||
2217 | DelayedVariableEvent $delayedVariableEvent |
||
2218 | ): void { |
||
2219 | //check if the target scope has the same activity id and activity instance id |
||
2220 | //since the dispatching was started |
||
2221 | $currentActivityInstanceId = $this->getActivityInstanceId($targetScope); |
||
2222 | $currentActivityId = $targetScope->getActivityId(); |
||
2223 | |||
2224 | $lastActivityInstanceId = null; |
||
2225 | foreach ($activityInstanceIds as $map) { |
||
2226 | if ($map[0] == $targetScope) { |
||
2227 | $lastActivityInstanceId = $map[1]; |
||
2228 | } |
||
2229 | } |
||
2230 | |||
2231 | $lastActivityId = null; |
||
2232 | foreach ($activityIds as $map) { |
||
2233 | if ($map[0] == $targetScope) { |
||
2234 | $lastActivityId = $map[1]; |
||
2235 | } |
||
2236 | } |
||
2237 | |||
2238 | $onSameAct = $this->isOnSameActivity($lastActivityInstanceId, $lastActivityId, $currentActivityInstanceId, $currentActivityId); |
||
2239 | |||
2240 | //If not we have to check the replace pointer, |
||
2241 | //which was set if a concurrent execution was created during the dispatching. |
||
2242 | if ($targetScope != $replacedBy && !$onSameAct) { |
||
2243 | $currentActivityInstanceId = $this->getActivityInstanceId($replacedBy); |
||
2244 | $currentActivityId = $replacedBy->getActivityId(); |
||
2245 | $onSameAct = $this->isOnSameActivity($lastActivityInstanceId, $lastActivityId, $currentActivityInstanceId, $currentActivityId); |
||
2246 | } |
||
2247 | |||
2248 | //dispatching |
||
2249 | if ($onSameAct && $this->isOnDispatchableState($targetScope)) { |
||
2250 | $targetScope->dispatchEvent($delayedVariableEvent->getEvent()); |
||
2251 | } |
||
2252 | } |
||
2253 | |||
2254 | /** |
||
2255 | * Checks if the given execution is on a dispatchable state. |
||
2256 | * That means if the current activity is not a leaf in the activity tree OR |
||
2257 | * it is a leaf but not a scope OR it is a leaf, a scope |
||
2258 | * and the execution is in state DEFAULT, which means not in state |
||
2259 | * Starting, Execute or Ending. For this states it is |
||
2260 | * prohibited to trigger conditional events, otherwise unexpected behavior can appear. |
||
2261 | * |
||
2262 | * @return bool - true if the execution is on a dispatchable state, false otherwise |
||
2263 | */ |
||
2264 | private function isOnDispatchableState(PvmExecutionImpl $targetScope): bool |
||
2265 | { |
||
2266 | $targetActivity = $targetScope->getActivity(); |
||
2267 | return |
||
2268 | //if not leaf, activity id is null -> dispatchable |
||
2269 | $targetScope->getActivityId() === null || |
||
2270 | // if leaf and not scope -> dispatchable |
||
2271 | !$targetActivity->isScope() || |
||
2272 | // if leaf, scope and state in default -> dispatchable |
||
2273 | ($targetScope->isInState(ActivityInstanceState::default())); |
||
2274 | } |
||
2275 | |||
2276 | /** |
||
2277 | * Compares the given activity instance id's and activity id's to check if the execution is on the same |
||
2278 | * activity as before an operation was executed. The activity instance id's can be null on transitions. |
||
2279 | * In this case the activity Id's have to be equal, otherwise the execution changed. |
||
2280 | * |
||
2281 | * @param string|null - lastActivityInstanceId the last activity instance id |
||
2282 | * @param string|null - lastActivityId the last activity id |
||
2283 | * @param string|null - currentActivityInstanceId the current activity instance id |
||
2284 | * @param string|null - currentActivityId the current activity id |
||
2285 | * @return bool - true if the execution is on the same activity, otherwise false |
||
2286 | */ |
||
2287 | private function isOnSameActivity( |
||
2288 | ?string $lastActivityInstanceId, |
||
2289 | ?string $lastActivityId, |
||
2290 | ?string $currentActivityInstanceId, |
||
2291 | ?string $currentActivityId |
||
2292 | ): bool { |
||
2293 | return |
||
2294 | //activityInstanceId's can be null on transitions, so the activityId must be equal |
||
2295 | (($lastActivityInstanceId === null && $lastActivityInstanceId == $currentActivityInstanceId && $lastActivityId == $currentActivityId) |
||
2296 | //if activityInstanceId's are not null they must be equal -> otherwise execution changed |
||
2297 | || ($lastActivityInstanceId !== null && $lastActivityInstanceId == $currentActivityInstanceId |
||
2298 | && ($lastActivityId === null || $lastActivityId == $currentActivityId))); |
||
2299 | } |
||
2300 | |||
2301 | /** |
||
2302 | * Returns the activity instance id for the given execution. |
||
2303 | * |
||
2304 | * @param PvmExecutionImpl|null - targetScope the execution for which the activity instance id should be returned |
||
2305 | * @return string the activity instance id |
||
2306 | */ |
||
2307 | private function getActivityInstanceId(?PvmExecutionImpl $targetScope = null): ?string |
||
2308 | { |
||
2309 | if ($targetScope !== null) { |
||
2310 | if ($targetScope->isConcurrent()) { |
||
2311 | return $targetScope->getActivityInstanceId(); |
||
2312 | } else { |
||
2313 | $targetActivity = $targetScope->getActivity(); |
||
2314 | if (($targetActivity !== null && empty($targetActivity->getActivities()))) { |
||
2315 | return $targetScope->getActivityInstanceId(); |
||
2316 | } |
||
2317 | return $targetScope->getParentActivityInstanceId(); |
||
2318 | } |
||
2319 | } |
||
2320 | return $this->activityInstanceId; |
||
2321 | } |
||
2322 | |||
2323 | /** |
||
2324 | * Returns the newest incident in this execution |
||
2325 | * |
||
2326 | * @param incidentType the type of new incident |
||
2327 | * @param configuration configuration of the incident |
||
2328 | * @return new incident |
||
2329 | */ |
||
2330 | public function createIncident(string $incidentType, string $configuration, ?string $message = null): IncidentInterface |
||
2331 | { |
||
2332 | $incidentContext = $this->createIncidentContext($configuration); |
||
2333 | |||
2334 | return IncidentHandling::createIncident($incidentType, $incidentContext, $message); |
||
2335 | } |
||
2336 | |||
2337 | protected function createIncidentContext(string $configuration): IncidentContext |
||
2338 | { |
||
2339 | $incidentContext = new IncidentContext(); |
||
2340 | |||
2341 | $incidentContext->setTenantId($this->getTenantId()); |
||
2342 | $incidentContext->setProcessDefinitionId($this->getProcessDefinitionId()); |
||
2343 | $incidentContext->setExecutionId($this->getId()); |
||
2344 | $incidentContext->setActivityId($this->getActivityId()); |
||
2345 | $incidentContext->setConfiguration($configuration); |
||
2346 | |||
2347 | return $incidentContext; |
||
2348 | } |
||
2349 | |||
2350 | /** |
||
2351 | * Resolves an incident with given id. |
||
2352 | * |
||
2353 | * @param incidentId |
||
2354 | */ |
||
2355 | public function resolveIncident(string $incidentId): void |
||
2356 | { |
||
2357 | $incident = Context::getCommandContext() |
||
2358 | ->getIncidentManager() |
||
2359 | ->findIncidentById($incidentId); |
||
2360 | |||
2361 | $incidentContext = new IncidentContext($incident); |
||
2362 | IncidentHandling::removeIncidents($incident->getIncidentType(), $incidentContext, true); |
||
2363 | } |
||
2364 | |||
2365 | public function findIncidentHandler(string $incidentType): ?IncidentHandlerInterface |
||
2372 | } |
||
2373 | } |
||
2374 |