%PDF- %PDF-
Direktori : /home/bitrix/www/bitrix/modules/bizproc/classes/general/ |
Current File : /home/bitrix/www/bitrix/modules/bizproc/classes/general/workflow.php |
<? IncludeModuleLangFile(__FILE__); /** * Workflow instance. */ class CBPWorkflow { private $isNew = false; private $instanceId = ""; /** @var CBPRuntime|null $runtime */ private $runtime = null; private $rootActivity = null; private $activitiesQueue = array(); private $eventsQueue = array(); private $activitiesNamesMap = array(); /************************ PROPERTIES *******************************/ public function GetInstanceId() { return $this->instanceId; } /** * @return CBPRuntime */ public function GetRuntime() { return $this->runtime; } private function GetWorkflowStatus() { return $this->rootActivity->GetWorkflowStatus(); } private function SetWorkflowStatus($newStatus) { $this->rootActivity->SetWorkflowStatus($newStatus); $this->GetRuntime()->onWorkflowStatusChanged($this->GetInstanceId(), $newStatus); } public function GetService($name) { return $this->runtime->GetService($name); } public function GetDocumentId() { return $this->rootActivity->GetDocumentId(); } /************************ CONSTRUCTORS ****************************************************/ /** * Public constructor initializes a new workflow instance with the specified ID. * * @param mixed $instanceId - ID of the new workflow instance. * @param mixed $runtime - Runtime object. * @return CBPWorkflow */ public function __construct($instanceId, CBPRuntime $runtime) { if (strlen($instanceId) <= 0) throw new Exception("instanceId"); if (!$runtime) throw new Exception("runtime"); $this->instanceId = $instanceId; $this->runtime = $runtime; } /** * Remove workflow object from serialized data * @return array */ public function __sleep() { return array(); } /************************ CREATE / LOAD WORKFLOW ****************************************/ public function Initialize(CBPActivity $rootActivity, $documentId, $workflowParameters = array(), $workflowVariablesTypes = array(), $workflowParametersTypes = array(), $workflowTemplateId = 0) { $this->rootActivity = $rootActivity; $rootActivity->SetWorkflow($this); if (method_exists($rootActivity, 'SetWorkflowTemplateId')) $rootActivity->SetWorkflowTemplateId($workflowTemplateId); $arDocumentId = CBPHelper::ParseDocumentId($documentId); $rootActivity->SetDocumentId($arDocumentId); $documentService = $this->GetService("DocumentService"); $documentType = isset($workflowParameters[CBPDocument::PARAM_DOCUMENT_TYPE]) ? $workflowParameters[CBPDocument::PARAM_DOCUMENT_TYPE] : $documentService->GetDocumentType($arDocumentId); unset($workflowParameters[CBPDocument::PARAM_DOCUMENT_TYPE]); if ($documentType !== null) { $rootActivity->SetDocumentType($documentType); $rootActivity->SetFieldTypes($documentService->GetDocumentFieldTypes($documentType)); } $rootActivity->SetProperties($workflowParameters); $rootActivity->SetVariablesTypes($workflowVariablesTypes); if (is_array($workflowVariablesTypes)) { foreach ($workflowVariablesTypes as $k => $v) $rootActivity->SetVariable($k, $v["Default"]); } $rootActivity->SetPropertiesTypes($workflowParametersTypes); } public function Reload(CBPActivity $rootActivity) { $this->rootActivity = $rootActivity; $rootActivity->SetWorkflow($this); switch ($this->GetWorkflowStatus()) { case CBPWorkflowStatus::Completed: case CBPWorkflowStatus::Terminated: throw new Exception("InvalidAttemptToLoad"); } } public function OnRuntimeStopped() { $workflowStatus = $this->GetWorkflowStatus(); if ($workflowStatus == CBPWorkflowStatus::Suspended) return; if ($this->rootActivity->executionStatus == CBPActivityExecutionStatus::Closed) { $this->SetWorkflowStatus(CBPWorkflowStatus::Completed); } else { $workflowStatus = $this->GetWorkflowStatus(); if ($workflowStatus == CBPWorkflowStatus::Running) $this->SetWorkflowStatus(CBPWorkflowStatus::Suspended); } $persister = CBPWorkflowPersister::GetPersister(); $persister->SaveWorkflow($this->rootActivity, true); } /************************ EXECUTE WORKFLOW ************************************************/ /** * Starts new workflow instance. * */ public function Start() { if ($this->GetWorkflowStatus() != CBPWorkflowStatus::Created) throw new Exception("CanNotStartInstanceTwice"); $this->isNew = true; $this->SetWorkflowStatus(CBPWorkflowStatus::Running); try { $this->InitializeActivity($this->rootActivity); $this->ExecuteActivity($this->rootActivity); $this->RunQueue(); } catch (Exception $e) { $this->Terminate($e); throw $e; } if ($this->rootActivity->executionStatus == CBPActivityExecutionStatus::Closed) { $this->SetWorkflowStatus(CBPWorkflowStatus::Completed); } else { $workflowStatus = $this->GetWorkflowStatus(); if ($workflowStatus == CBPWorkflowStatus::Running) $this->SetWorkflowStatus(CBPWorkflowStatus::Suspended); } $persister = CBPWorkflowPersister::GetPersister(); $persister->SaveWorkflow($this->rootActivity, true); } /** * Resume existing workflow. * */ public function Resume() { if ($this->GetWorkflowStatus() != CBPWorkflowStatus::Suspended) throw new Exception("CanNotResumeInstance"); $this->SetWorkflowStatus(CBPWorkflowStatus::Running); try { $this->RunQueue(); } catch (Exception $e) { $this->Terminate($e); throw $e; } if ($this->rootActivity->executionStatus == CBPActivityExecutionStatus::Closed) { $this->SetWorkflowStatus(CBPWorkflowStatus::Completed); } else { $workflowStatus = $this->GetWorkflowStatus(); if ($workflowStatus == CBPWorkflowStatus::Running) $this->SetWorkflowStatus(CBPWorkflowStatus::Suspended); } $persister = CBPWorkflowPersister::GetPersister(); $persister->SaveWorkflow($this->rootActivity, true); } public function isNew() { return $this->isNew; } /********************** EXTERNAL EVENTS **************************************************************/ /** * Resume the workflow instance and transfer the specified event to it. * * @param mixed $eventName - Event name. * @param mixed $arEventParameters - Event parameters. */ public function SendExternalEvent($eventName, $arEventParameters = array()) { $this->AddEventToQueue($eventName, $arEventParameters); $this->Resume(); } /*********************** SEARCH ACTIVITY BY NAME ****************************************************/ private function FillNameActivityMapInternal(CBPActivity $activity) { $this->activitiesNamesMap[$activity->GetName()] = $activity; if (is_a($activity, "CBPCompositeActivity")) { $arSubActivities = $activity->CollectNestedActivities(); foreach ($arSubActivities as $subActivity) $this->FillNameActivityMapInternal($subActivity); } } private function FillNameActivityMap() { if (!is_array($this->activitiesNamesMap)) $this->activitiesNamesMap = array(); if (count($this->activitiesNamesMap) > 0) return; $this->FillNameActivityMapInternal($this->rootActivity); } /** * Returns activity by its name. * * @param mixed $activityName - Activity name. * @return CBPActivity - Returns activity object or null if activity is not found. */ public function GetActivityByName($activityName) { if (strlen($activityName) <= 0) throw new Exception("activityName"); $activity = null; $this->FillNameActivityMap(); if (array_key_exists($activityName, $this->activitiesNamesMap)) $activity = $this->activitiesNamesMap[$activityName]; return $activity; } /************************ ACTIVITY EXECUTION *************************************************/ /** * Initializes the specified activity by calling its method Initialize. * * @param CBPActivity $activity */ public function InitializeActivity(CBPActivity $activity) { if ($activity == null) throw new CBPArgumentNullException("activity"); if ($activity->executionStatus != CBPActivityExecutionStatus::Initialized) throw new Exception("InvalidInitializingState"); $activity->Initialize(); } /** * Plans specified activity for execution. * * @param CBPActivity $activity - Activity object. * @param mixed $arEventParameters - Optional parameters. */ public function ExecuteActivity(CBPActivity $activity, $arEventParameters = array()) { if ($activity == null) throw new Exception("activity"); if ($activity->executionStatus != CBPActivityExecutionStatus::Initialized) throw new Exception("InvalidExecutionState"); $activity->SetStatus(CBPActivityExecutionStatus::Executing, $arEventParameters); $this->AddItemToQueue(array($activity, CBPActivityExecutorOperationType::Execute)); } /** * Close specified activity. * * @param CBPActivity $activity - Activity object. * @param mixed $arEventParameters - Optional parameters. */ public function CloseActivity(CBPActivity $activity, $arEventParameters = array()) { switch ($activity->executionStatus) { case CBPActivityExecutionStatus::Executing: $activity->MarkCompleted($arEventParameters); return; case CBPActivityExecutionStatus::Canceling: $activity->MarkCanceled($arEventParameters); return; case CBPActivityExecutionStatus::Closed: return; case CBPActivityExecutionStatus::Faulting: $activity->MarkFaulted($arEventParameters); return; } throw new Exception("InvalidClosingState"); } /** * Cancel specified activity. * * @param CBPActivity $activity - Activity object. * @param mixed $arEventParameters - Optional parameters. */ public function CancelActivity(CBPActivity $activity, $arEventParameters = array()) { if ($activity == null) throw new Exception("activity"); if ($activity->executionStatus != CBPActivityExecutionStatus::Executing) throw new Exception("InvalidCancelingState"); $activity->SetStatus(CBPActivityExecutionStatus::Canceling, $arEventParameters); $this->AddItemToQueue(array($activity, CBPActivityExecutorOperationType::Cancel)); } public function FaultActivity(CBPActivity $activity, Exception $e, $arEventParameters = array()) { if ($activity == null) throw new Exception("activity"); if ($activity->executionStatus == CBPActivityExecutionStatus::Closed) { if ($activity->parent == null) $this->Terminate($e); else $this->FaultActivity($activity->parent, $e, $arEventParameters); } else { $activity->SetStatus(CBPActivityExecutionStatus::Faulting); $this->AddItemToQueue(array($activity, CBPActivityExecutorOperationType::HandleFault, $e)); } } /************************ ACTIVITIES QUEUE ***********************************************/ private function AddItemToQueue($item) { array_push($this->activitiesQueue, $item); } private function RunQueue() { while (true) { $this->ProcessQueuedEvents(); $item = array_shift($this->activitiesQueue); if ($item == null) return; try { $this->RunQueuedItem($item[0], $item[1], (count($item) > 2 ? $item[2] : null)); } catch (Exception $e) { $this->FaultActivity($item[0], $e); if ($this->GetWorkflowStatus() == CBPWorkflowStatus::Terminated) return; } } } private function RunQueuedItem(CBPActivity $activity, $activityOperation, Exception $exception = null) { /** @var $trackingService CBPTrackingService */ if ($activityOperation == CBPActivityExecutorOperationType::Execute) { if ($activity->executionStatus == CBPActivityExecutionStatus::Executing) { try { $trackingService = $this->GetService("TrackingService"); $trackingService->Write($this->GetInstanceId(), CBPTrackingType::ExecuteActivity, $activity->GetName(), $activity->executionStatus, $activity->executionResult, ($activity->IsPropertyExists("Title") ? $activity->Title : ""), ""); $newStatus = $activity->Execute(); if ($newStatus == CBPActivityExecutionStatus::Closed) $this->CloseActivity($activity); elseif ($newStatus != CBPActivityExecutionStatus::Executing) throw new Exception("InvalidExecutionStatus"); } catch (Exception $e) { throw $e; } } } elseif ($activityOperation == CBPActivityExecutorOperationType::Cancel) { if ($activity->executionStatus == CBPActivityExecutionStatus::Canceling) { try { $trackingService = $this->GetService("TrackingService"); $trackingService->Write($this->GetInstanceId(), CBPTrackingType::CancelActivity, $activity->GetName(), $activity->executionStatus, $activity->executionResult, ($activity->IsPropertyExists("Title") ? $activity->Title : ""), ""); $newStatus = $activity->Cancel(); if ($newStatus == CBPActivityExecutionStatus::Closed) $this->CloseActivity($activity); elseif ($newStatus != CBPActivityExecutionStatus::Canceling) throw new Exception("InvalidExecutionStatus"); } catch (Exception $e) { throw $e; } } } elseif ($activityOperation == CBPActivityExecutorOperationType::HandleFault) { if ($activity->executionStatus == CBPActivityExecutionStatus::Faulting) { try { $trackingService = $this->GetService("TrackingService"); $trackingService->Write($this->GetInstanceId(), CBPTrackingType::FaultActivity, $activity->GetName(), $activity->executionStatus, $activity->executionResult, ($activity->IsPropertyExists("Title") ? $activity->Title : ""), ($exception != null ? ($exception->getCode()? "[".$exception->getCode()."] " : '').$exception->getMessage() : "")); $newStatus = $activity->HandleFault($exception); if ($newStatus == CBPActivityExecutionStatus::Closed) $this->CloseActivity($activity); elseif ($newStatus != CBPActivityExecutionStatus::Faulting) throw new Exception("InvalidExecutionStatus"); } catch (Exception $e) { throw $e; } } } } public function Terminate(Exception $e = null, $stateTitle = '') { /** @var CBPTaskService $taskService */ $taskService = $this->GetService("TaskService"); $taskService->DeleteAllWorkflowTasks($this->GetInstanceId()); $this->SetWorkflowStatus(CBPWorkflowStatus::Terminated); $persister = CBPWorkflowPersister::GetPersister(); $persister->SaveWorkflow($this->rootActivity, true); /** @var CBPStateService $stateService */ $stateService = $this->GetService("StateService"); $stateService->SetState( $this->instanceId, array( "STATE" => "Terminated", "TITLE" => $stateTitle ? $stateTitle : GetMessage("BPCGWF_TERMINATED"), "PARAMETERS" => array() ), false//array() ); if ($e != null) { $trackingService = $this->GetService("TrackingService"); $trackingService->Write($this->instanceId, CBPTrackingType::FaultActivity, "none", CBPActivityExecutionStatus::Faulting, CBPActivityExecutionResult::Faulted, "Exception", ($e->getCode()? "[".$e->getCode()."] " : '').$e->getMessage()); } } /** * @param CBPActivity $activity * @throws CBPArgumentNullException * @throws Exception */ public function FinalizeActivity(CBPActivity $activity) { if ($activity == null) throw new CBPArgumentNullException("activity"); //if ($activity->executionStatus != CBPActivityExecutionStatus::Closed) // throw new Exception("InvalidFinalizingState"); $activity->Finalize(); } /************************ EVENTS QUEUE ********************************************************/ private function AddEventToQueue($eventName, $arEventParameters = array()) { array_push($this->eventsQueue, array($eventName, $arEventParameters)); } private function ProcessQueuedEvents() { while (true) { $arEvent = array_shift($this->eventsQueue); if ($arEvent == null) return; $eventName = $arEvent[0]; $arEventParameters = $arEvent[1]; $this->ProcessQueuedEvent($eventName, $arEventParameters); } } private function ProcessQueuedEvent($eventName, $arEventParameters = array()) { if (!array_key_exists($eventName, $this->rootActivity->arEventsMap)) return; foreach ($this->rootActivity->arEventsMap[$eventName] as $eventHandler) { if (is_a($eventHandler, "IBPActivityExternalEventListener")) $eventHandler->OnExternalEvent($arEventParameters); } } /** * Add new event handler to the specified event. * * @param mixed $eventName - Event name. * @param IBPActivityExternalEventListener $eventHandler - Event handler. */ public function AddEventHandler($eventName, IBPActivityExternalEventListener $eventHandler) { if (!is_array($this->rootActivity->arEventsMap)) $this->rootActivity->arEventsMap = array(); if (!array_key_exists($eventName, $this->rootActivity->arEventsMap)) $this->rootActivity->arEventsMap[$eventName] = array(); $this->rootActivity->arEventsMap[$eventName][] = $eventHandler; } /** * Remove the event handler from the specified event. * * @param mixed $eventName - Event name. * @param IBPActivityExternalEventListener $eventHandler - Event handler. */ public function RemoveEventHandler($eventName, IBPActivityExternalEventListener $eventHandler) { if (!is_array($this->rootActivity->arEventsMap)) $this->rootActivity->arEventsMap = array(); if (!array_key_exists($eventName, $this->rootActivity->arEventsMap)) $this->rootActivity->arEventsMap[$eventName] = array(); $idx = array_search($eventHandler, $this->rootActivity->arEventsMap[$eventName], true); if ($idx !== false) unset($this->rootActivity->arEventsMap[$eventName][$idx]); if (count($this->rootActivity->arEventsMap[$eventName]) <= 0) unset($this->rootActivity->arEventsMap[$eventName]); } /******************* UTILITIES ***************************************************************/ /** * Returns available events for current state of state machine workflow activity. * */ public function GetAvailableStateEvents() { if (!is_a($this->rootActivity, "CBPStateMachineWorkflowActivity")) throw new Exception("NotAStateMachineWorkflow"); return $this->rootActivity->GetAvailableStateEvents(); } }