| 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 |