14 private static $instance;
18 trigger_error(
'Clone in not allowed.', E_USER_ERROR);
23 $this->serviceInstanceId = uniqid(
"",
true);
24 $this->useGZipCompression = \CBPWorkflowTemplateLoader::useGZipCompression();
32 if (!isset(self::$instance))
35 self::$instance =
new $c;
38 return self::$instance;
41 private function retrieveWorkflow($instanceId, $silent =
false)
47 if (!$silent && !$this->lock($instanceId))
49 throw new Exception(
GetMessage(
"BPCGWP_WF_LOCKED"), \CBPRuntime::EXCEPTION_CODE_INSTANCE_LOCKED);
53 "SELECT WORKFLOW, WORKFLOW_RO, case when ". $queryCondition .
" then 'Y' else 'N' end as UPDATEABLE ".
54 "FROM b_bp_workflow_instance ".
55 "WHERE ID = '".
$DB->ForSql($instanceId).
"' "
59 if (
$arResult[
"UPDATEABLE"] ==
"Y" && !$silent)
61 $sqlUpdate =
$DB->PrepareUpdate(
62 'b_bp_workflow_instance',
64 'OWNER_ID' => $this->serviceInstanceId,
65 'OWNED_UNTIL' => Main\Type\DateTime::createFromTimestamp($this->GetOwnershipTimeout()),
70 'UPDATE b_bp_workflow_instance SET ' . $sqlUpdate .
' WHERE ID = \''.
$DB->ForSql($instanceId).
'\' '
75 $this->unlock($instanceId);
76 throw new Exception(GetMessage("BPCGWP_WF_LOCKED"), \CBPRuntime::EXCEPTION_CODE_INSTANCE_LOCKED);
81 $this->unlock($instanceId);
84 return [$arResult["WORKFLOW"], $arResult["WORKFLOW_RO"]];
89 $this->unlock($instanceId);
92 throw new Exception(GetMessage("BPCGWP_INVALID_WF"), \CBPRuntime::EXCEPTION_CODE_INSTANCE_NOT_FOUND);
95 protected function insertWorkflow($id, $buffer, $status, $bUnlocked, array $creationData = [])
99 $queryCondition = $this->getLockerQueryCondition();
101 if ($status == CBPWorkflowStatus::Completed || $status == CBPWorkflowStatus::Terminated)
104 "DELETE FROM b_bp_workflow_instance ".
105 "WHERE ID = '".$DB->ForSql($id)."'"
110 $dbResult = $DB->Query(
111 "SELECT ID, case when " . $queryCondition . " then 'Y
' else 'N
' end as UPDATEABLE ".
112 "FROM b_bp_workflow_instance ".
113 "WHERE ID = '".$DB->ForSql($id)."' "
115 if ($arResult = $dbResult->Fetch())
117 if ($arResult["UPDATEABLE"] == "Y")
119 $sqlUpdate = $DB->PrepareUpdate(
120 'b_bp_workflow_instance
',
122 'WORKFLOW
' => $buffer,
123 'STATUS
' => (int)$status,
124 'MODIFIED
' => new Main\Type\DateTime(),
125 'OWNER_ID
' => $bUnlocked ? false : $this->serviceInstanceId,
126 'OWNED_UNTIL
' => $bUnlocked ? false : Main\Type\DateTime::createFromTimestamp($this->GetOwnershipTimeout()),
131 'UPDATE b_bp_workflow_instance SET
' . $sqlUpdate . ' WHERE ID = \
''.$DB->ForSql($id).
'\' '
136 throw new Exception(GetMessage('BPCGWP_WF_LOCKED
'), \CBPRuntime::EXCEPTION_CODE_INSTANCE_LOCKED);
141 $status = (int) $status;
142 $ownerId = ($bUnlocked ? false : $this->serviceInstanceId);
143 $ownedUntil = ($bUnlocked ? false : Main\Type\DateTime::createFromTimestamp($this->GetOwnershipTimeout()));
145 $moduleId = isset($creationData['MODULE_ID
']) ? $creationData['MODULE_ID
'] : '';
146 $entity = isset($creationData['ENTITY
']) ? $creationData['ENTITY
'] : '';
147 $documentId = isset($creationData['DOCUMENT_ID
']) ? $creationData['DOCUMENT_ID
'] : '';
148 $tplId = isset($creationData['WORKFLOW_TEMPLATE_ID
']) ? (int) $creationData['WORKFLOW_TEMPLATE_ID
'] : 0;
149 $startedBy = isset($creationData['STARTED_BY
']) ? (int) $creationData['STARTED_BY
'] : 0;
150 $startedEventType = isset($creationData['STARTED_EVENT_TYPE
']) ? (int) $creationData['STARTED_EVENT_TYPE
'] : 0;
151 $ro = isset($creationData['RO
']) ? $creationData['RO
'] : false;
152 $nowTime = new Main\Type\DateTime();
154 $sqlInsert = $DB->PrepareInsert(
155 'b_bp_workflow_instance
',
158 'WORKFLOW
' => $buffer,
159 'WORKFLOW_RO
' => $ro,
161 'MODIFIED
' => $nowTime,
162 'OWNER_ID
' => $ownerId,
163 'OWNED_UNTIL
' => $ownedUntil,
164 'MODULE_ID
' => $moduleId,
166 'DOCUMENT_ID
' => $documentId,
167 'WORKFLOW_TEMPLATE_ID
' => $tplId,
168 'STARTED
' => $nowTime,
169 'STARTED_BY
' => $startedBy,
170 'STARTED_EVENT_TYPE
' => $startedEventType,
175 'INSERT INTO b_bp_workflow_instance (
' . $sqlInsert[0] . ') VALUES (
' . $sqlInsert[1] .')
'
181 protected function getOwnershipTimeout()
183 return time() + $this->ownershipDelta;
186 public function loadWorkflow($instanceId, $silent = false)
188 [$state, $ro] = $this->RetrieveWorkflow($instanceId, $silent);
192 return $this->RestoreFromSerializedForm($state, $ro);
195 throw new Exception("WorkflowNotFound");
198 protected function restoreFromSerializedForm($buffer, $ro)
200 if ($this->useGZipCompression)
202 $buffer = gzuncompress($buffer);
203 $ro = $ro ? gzuncompress($ro) : null;
208 throw new Exception("EmptyWorkflowInstance");
212 $activity = CBPActivity::Load($buffer);
216 $ro = Main\Web\Json::decode($ro);
219 $activity->setReadOnlyData($ro);
226 public function saveWorkflow(CBPActivity $rootActivity, $bUnlocked)
229 if ($rootActivity->workflow->isNew())
231 $dt = $rootActivity->GetDocumentId();
232 $creationData['MODULE_ID
'] = $dt[0];
233 $creationData['ENTITY
'] = $dt[1];
234 $creationData['DOCUMENT_ID
'] = $dt[2];
235 $creationData['WORKFLOW_TEMPLATE_ID
'] = $rootActivity->GetWorkflowTemplateId();
236 $creationData['STARTED_EVENT_TYPE
'] = $rootActivity->getDocumentEventType();
238 $startedBy = $rootActivity->{\CBPDocument::PARAM_TAGRET_USER};
241 $creationData['STARTED_BY
'] = \CBPHelper::StripUserPrefix($startedBy);
244 $creationData['RO
'] = $this->getJsonCompressed($rootActivity->pullReadOnlyData());
249 $rootActivity->pullReadOnlyData();
252 $workflowStatus = $rootActivity->GetWorkflowStatus();
254 if ($rootActivity->workflow->isAbandoned())
256 $workflowStatus = CBPWorkflowStatus::Completed;
260 if (($workflowStatus != CBPWorkflowStatus::Completed) && ($workflowStatus != CBPWorkflowStatus::Terminated))
262 $buffer = $this->GetSerializedForm($rootActivity);
265 $this->InsertWorkflow($rootActivity->GetWorkflowInstanceId(), $buffer, $workflowStatus, $bUnlocked, $creationData);
268 protected function getSerializedForm(CBPActivity $rootActivity)
270 $buffer = $rootActivity->Save();
272 if ($this->useGZipCompression)
273 $buffer = gzcompress($buffer, 9);
277 private function getJsonCompressed($data): string
279 $buffer = Main\Web\Json::encode($data);
281 if ($this->useGZipCompression)
283 $buffer = gzcompress($buffer, 9);
288 public function unlockWorkflow(CBPActivity $rootActivity)
292 if ($rootActivity == null)
293 throw new Exception("rootActivity");
296 "UPDATE b_bp_workflow_instance SET ".
297 " OWNER_ID = NULL, ".
298 " OWNED_UNTIL = NULL ".
299 "WHERE ID = '
".$DB->ForSql($rootActivity->GetWorkflowInstanceId())."' ".
301 " (OWNER_ID = '".$DB->ForSql($this->serviceInstanceId)."' ".
302 " AND OWNED_UNTIL >= ".$DB->CurrentTimeFunction().") ".
304 " (OWNER_ID IS NULL) ".
306 " (OWNER_ID IS NOT NULL ".
307 " AND OWNED_UNTIL < ".$DB->CurrentTimeFunction().") ".
312 protected function getLockerQueryCondition()
316 return "(OWNER_ID IS NULL OR OWNER_ID = '".$DB->ForSql($this->serviceInstanceId)."')";
319 private function lock(string $workflowId): bool
321 if (!$this->useDbLock())
326 return $this->lockDb($workflowId);
329 private function unlock(string $workflowId): bool
331 if (!$this->useDbLock())
336 return $this->lockDb($workflowId, true);
339 private function useDbLock()
345 $use = (Main\Config\Option::get('bizproc
', 'workflow_dblock
', 'N
') === 'Y
');
351 private function lockDb(string $workflowId, bool $release = false): bool
353 $name = 'bizproc_
' . $workflowId;
354 $connection = Main\Application::getInstance()->getConnection();
358 return $connection->unlock($name);
361 return $connection->lock($name);