diff --git a/ProcessMaker/Http/Kernel.php b/ProcessMaker/Http/Kernel.php index 4430954207..9865b838a5 100644 --- a/ProcessMaker/Http/Kernel.php +++ b/ProcessMaker/Http/Kernel.php @@ -45,6 +45,12 @@ class Kernel extends HttpKernel // API Middleware is defined with routeMiddleware below. // See routes/api.php ], + 'engine' => [ + 'auth:api', + 'setlocale', + 'bindings', + 'sanitize', + ], ]; /** diff --git a/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php b/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php index f3b14b6b55..3276417cae 100644 --- a/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php +++ b/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php @@ -48,8 +48,8 @@ public function triggerStartEvent(Definitions $definitions, StartEventInterface 'signal_events' => [], ]); - // Create triggered - // TO DO: + // Serialize instance + $state = $this->serializeState($request); // Dispatch start process action $this->dispatchAction([ @@ -59,25 +59,20 @@ public function triggerStartEvent(Definitions $definitions, StartEventInterface 'instance_id' => $request->uuid, 'request_id' => $request->getKey(), 'element_id' => $event->getId(), - 'data'=> $data, + 'data' => $data, 'extra_properties' => [ 'user_id' => $userId, 'process_id' => $definitions->id, 'request_id' => $request->getKey(), ], ], - 'state' => [ - 'requests' => [ - $request->uuid => [ - 'id' => $request->uuid, - 'callable_id' => $request->callable_id, - 'data' => $request->data, - 'tokens' => [], - ] - ], + 'state' => $state, + 'session' => [ + 'user_id' => $userId, ], ]); + //Return the instance created return $request; } @@ -99,10 +94,37 @@ public function completeTask(Definitions $definitions, ExecutionInstanceInterfac // Get complementary information $version = $definitions->getLatestVersion(); + $userId = $this->getCurrentUserId(); + $state = $this->serializeState($instance); + + // Dispatch complete task action + $this->dispatchAction([ + 'bpmn' => $version->getKey(), + 'action' => self::ACTION_COMPLETE_TASK, + 'params' => [ + 'request_id' => $token->process_request_id, + 'token_id' => $token->uuid, + 'element_id' => $token->element_id, + 'data' => $data, + ], + 'state' => $state, + 'session' => [ + 'user_id' => $userId, + ], + ]); + } + /** + * Build a state object + * + * @param ProcessRequest $instance + * @return array + */ + private function serializeState(ProcessRequest $instance) + { // Get open tokens $tokensRows = []; - $tokens = $instance->tokens()->where('status', '!=', 'CLOSED')->get(); + $tokens = $instance->tokens()->where('status', '!=', 'CLOSED')->where('status', '!=', 'TRIGGERED')->get(); foreach ($tokens as $token) { $tokensRows[] = array_merge($token->token_properties ?: [], [ 'id' => $token->uuid, @@ -112,27 +134,16 @@ public function completeTask(Definitions $definitions, ExecutionInstanceInterfac ]); } - // Dispatch complete task action - $this->dispatchAction([ - 'bpmn' => $version->getKey(), - 'action' => self::ACTION_COMPLETE_TASK, - 'params' => [ - 'request_id' => $token->process_request_id, - 'token_id' => $token->uuid, - 'element_id' => $token->element_id, - 'data'=> $data, - ], - 'state' => [ - 'requests' => [ - [ - 'id' => $instance->uuid, - 'callable_id' => $instance->callable_id, - 'data' => $instance->data, - 'tokens' => $tokensRows, - ] + return [ + 'requests' => [ + [ + 'id' => $instance->uuid, + 'callable_id' => $instance->callable_id, + 'data' => $instance->data, + 'tokens' => $tokensRows, ], - ] - ]); + ], + ]; } /** diff --git a/ProcessMaker/Nayra/MessageBrokers/ServiceKafka.php b/ProcessMaker/Nayra/MessageBrokers/ServiceKafka.php index 6536ed9c23..5791c6af20 100644 --- a/ProcessMaker/Nayra/MessageBrokers/ServiceKafka.php +++ b/ProcessMaker/Nayra/MessageBrokers/ServiceKafka.php @@ -4,7 +4,7 @@ use Junges\Kafka\Facades\Kafka; use Junges\Kafka\Contracts\KafkaConsumerMessage; -use ProcessMaker\Nayra\Repositories\EntityRepositoryFactory; +use ProcessMaker\Nayra\Repositories\PersistenceHandler; class ServiceKafka { @@ -65,11 +65,11 @@ public function worker() // Get transactions $transactions = $message->getBody(); - // Store transsactions + // Store transactions $this->storeData($transactions); })->build(); - // Consume incomming messages + // Consume incoming messages $consumer->consume(); } @@ -80,8 +80,9 @@ public function worker() */ private function storeData(array $transactions) { + $handler = new PersistenceHandler(); foreach ($transactions as $transaction) { - EntityRepositoryFactory::createRepository($transaction['entity'])->save($transaction); + $handler->save($transaction); } } } diff --git a/ProcessMaker/Nayra/MessageBrokers/ServiceRabbitMq.php b/ProcessMaker/Nayra/MessageBrokers/ServiceRabbitMq.php index 0ea16bd9fa..66e2e77a95 100644 --- a/ProcessMaker/Nayra/MessageBrokers/ServiceRabbitMq.php +++ b/ProcessMaker/Nayra/MessageBrokers/ServiceRabbitMq.php @@ -4,7 +4,7 @@ use PhpAmqpLib\Connection\AMQPStreamConnection; use PhpAmqpLib\Message\AMQPMessage; -use ProcessMaker\Nayra\Repositories\EntityRepositoryFactory; +use ProcessMaker\Nayra\Repositories\PersistenceHandler; class ServiceRabbitMq { @@ -123,8 +123,9 @@ public function worker(): void */ private function storeData(array $transactions): void { + $handler = new PersistenceHandler(); foreach ($transactions as $transaction) { - EntityRepositoryFactory::createRepository($transaction['entity'])->save($transaction); + $handler->save($transaction); } } } diff --git a/ProcessMaker/Nayra/Repositories/Deserializer.php b/ProcessMaker/Nayra/Repositories/Deserializer.php new file mode 100644 index 0000000000..62c1a9ec14 --- /dev/null +++ b/ProcessMaker/Nayra/Repositories/Deserializer.php @@ -0,0 +1,243 @@ +instanceRepository = new ExecutionInstanceRepository(); + $this->instanceRepository->setAbortIfInstanceNotFound(false); + // Do not load all the active tokens to improve persistence performance + $this->instanceRepository->setLoadTokens(false); + $this->tokenRepository = new TokenRepository($this->instanceRepository); + $this->factory = new DefinitionsRepository(); + } + + /** + * Find process definition + * + * @param int $modelId + * @return BpmnDocument + */ + private function findProcessDefinition($modelId): BpmnDocument + { + if (!isset($this->definitions[$modelId])) { + $version = ProcessVersion::find($modelId); + $model = $version->process; + + $definition = new BpmnDocument($model); + $definition->setFactory($this->factory); + $definition->loadXML($version->bpmn); + + $this->definitions[$modelId] = $definition; + } + + return $this->definitions[$modelId]; + } + + /** + * Find process request + * + * @param int|string $instanceId + * @param BpmnDocument $definition + * @param string $processId + * @return ProcessRequest + * + * @throws Exception + */ + private function findRequest($instanceId, BpmnDocument $definition, $processId): ProcessRequest + { + // Return process request if already stored + if (isset($this->requests[$instanceId])) { + return $this->requests[$instanceId]; + } + + // Load process request + $instance = $this->instanceRepository->loadExecutionInstanceByUid($instanceId, $definition); + + // If not exists, create a new one + if (!$instance && !is_numeric($instanceId)) { + $instance = $this->instanceRepository->createExecutionInstance(); + $instance->setId($instanceId); + $instance->uuid = $instanceId; + $instance->setProcess($definition->getProcess($processId)); + $dataStore = $this->factory->createDataStore(); + $instance->setDataStore($dataStore); + } elseif (!$instance) { + throw new Exception("ProcessRequest {$instanceId} not found."); + } + + // Store process request finded + $this->requests[$instance->getId()] = $instance; + $this->requests[$instance->uuid] = $instance; + + return $instance; + } + + /** + * Find process request token + * + * @param int|string $tokenId + * @param int|string $instanceId + * @param string $elementId + * @param BpmnDocument $definition + * @return ProcessRequestToken + * + * @throws Exception + */ + private function findRequestToken($tokenId, $instanceId, $elementId, BpmnDocument $definition): ProcessRequestToken + { + // Return process request token if already stored + if (isset($this->tokens[$tokenId])) { + return $this->tokens[$tokenId]; + } + + // Load process request + $element = $definition->getElementInstanceById($elementId); + $process = $element->getProcess(); + $instance = $this->findRequest($instanceId, $definition, $process->getId()); + + // Load tokens of process request + $tokens = $instance->getTokens(); + $token = $tokens->findFirst(function ($token) use ($tokenId) { + return $token->getId() === $tokenId; + }); + + // Store and return process request token if finded + if ($token) { + $this->tokens[$token->getId()] = $token; + $this->tokens[$token->uuid] = $token; + + return $token; + } + + // Load process request token + $token = $this->tokenRepository->loadTokenByUid($tokenId); + + // If not exists, create a new one + if (!$token && !is_numeric($tokenId)) { + $token = $this->tokenRepository->createTokenInstance(); + $token->setId($tokenId); + $token->uuid = $tokenId; + } elseif ($token) { + $tokenInfo = [ + 'id' => $token->getKey(), + 'status' => $token->status, + 'index' => $token->element_index, + 'element_ref' => $token->element_id, + ]; + $properties = array_merge($token->token_properties ?: [], $tokenInfo); + $token->setProperties($properties); + } else { + throw new Exception("ProcessRequestToken {$tokenId} not found."); + } + + // Set process request to the token + $token->setInstance($instance); + + // Store process request token finded + $this->tokens[$token->getId()] = $token; + $this->tokens[$token->uuid] = $token; + + return $token; + } + + /** + * Return a process request from serialized data + * + * @param array $serialized + * @return ExecutionInstanceInterface + */ + public function unserializeInstance(array $serialized): ExecutionInstanceInterface + { + // Extract properties + $definition = $this->findProcessDefinition($serialized['model_id']); + $instance = $this->findRequest($serialized['id'], $definition, $serialized['process_id']); + $properties = $serialized['properties'] ?? []; + + // Remove the id + unset($properties['id']); + + // Set process request properties + $properties = array_merge($instance->getProperties(), $properties); + $instance->setProperties($properties); + + return $instance; + } + + /** + * Return a process request token from serialized data + * + * @param array $serialized + * @return TokenInterface + */ + public function unserializeToken(array $serialized): TokenInterface + { + // Extract properties + $definition = $this->findProcessDefinition($serialized['model_id']); + $token = $this->findRequestToken($serialized['token_id'], $serialized['instance_id'], $serialized['element_id'], $definition); + $properties = $serialized['properties'] ?? []; + + // Remove the id + unset($properties['id']); + + // Set process request token properties + $properties = array_merge($token->getProperties(), $properties); + $token->setProperties($properties); + + return $token; + } + + /** + * Return entity from serialized data + * + * @param array $serialized + * @return EntityInterface + */ + public function unserializeEntity(array $serialized): EntityInterface + { + $definition = $this->findProcessDefinition($serialized['model_id']); + return $definition->getElementInstanceById($serialized['id']); + } + + /** + * Return tokens collection from serialized data + * + * @param array $serialized + * @return CollectionInterface + */ + public function unserializeTokensCollection(array $serialized): CollectionInterface + { + $collection = new Collection(); + foreach ($serialized as $item) { + $collection->push($this->unserializeToken($item)); + } + + return $collection; + } +} diff --git a/ProcessMaker/Nayra/Repositories/PersistenceHandler.php b/ProcessMaker/Nayra/Repositories/PersistenceHandler.php new file mode 100644 index 0000000000..2ed1f1a56b --- /dev/null +++ b/ProcessMaker/Nayra/Repositories/PersistenceHandler.php @@ -0,0 +1,109 @@ +deserializer = new Deserializer(); + $this->instanceRepository = new ExecutionInstanceRepository(); + $this->tokenRepository = new TokenRepository($this->instanceRepository); + } + + /** + * Save data + * + * @param array $transaction + * + * @throws Exception + */ + public function save(array $transaction) + { + // Initialize session + if (isset($transaction['session']) && !empty($transaction['session']['user_id']) && Auth::id() !== $transaction['session']['user_id']) { + Auth::loginUsingId($transaction['session']['user_id']); + } + + // Save data according to the type + switch ($transaction['type']) { + case 'activity_activated': + $this->persistActivityActivated($transaction); + break; + case 'activity_exception': + $this->persistActivityException($transaction); + break; + case 'activity_completed': + $this->persistActivityCompleted($transaction); + break; + case 'activity_closed': + $this->persistActivityClosed($transaction); + break; + case 'throw_event_token_arrives': + $this->persistThrowEventTokenArrives($transaction); + break; + case 'throw_event_token_consumed': + $this->persistThrowEventTokenConsumed($transaction); + break; + case 'throw_event_token_passed': + $this->persistThrowEventTokenPassed($transaction); + break; + case 'gateway_token_arrives': + $this->persistGatewayTokenArrives($transaction); + break; + case 'gateway_token_consumed': + $this->persistGatewayTokenConsumed($transaction); + break; + case 'gateway_token_passed': + $this->persistGatewayTokenPassed($transaction); + break; + case 'catch_event_token_arrives': + $this->persistCatchEventTokenArrives($transaction); + break; + case 'catch_event_token_consumed': + $this->persistCatchEventTokenConsumed($transaction); + break; + case 'catch_event_token_passed': + $this->persistCatchEventTokenPassed($transaction); + break; + case 'catch_event_message_arrives': + $this->persistCatchEventMessageArrives($transaction); + break; + case 'catch_event_message_consumed': + $this->persistCatchEventMessageConsumed($transaction); + break; + case 'start_event_triggered': + $this->persistStartEventTriggered($transaction); + break; + case 'event_based_gateway_activated': + $this->persistEventBasedGatewayActivated($transaction); + break; + case 'instance_created': + $this->persistInstanceCreated($transaction); + break; + case 'instance_completed': + $this->persistInstanceCompleted($transaction); + break; + case 'instance_collaboration': + $this->persistInstanceCollaboration($transaction); + break; + default: + throw new Exception('Unknown transaction type ' . $transaction['type']); + } + } +} diff --git a/ProcessMaker/Nayra/Repositories/PersistenceRequestTrait.php b/ProcessMaker/Nayra/Repositories/PersistenceRequestTrait.php new file mode 100644 index 0000000000..574662d42b --- /dev/null +++ b/ProcessMaker/Nayra/Repositories/PersistenceRequestTrait.php @@ -0,0 +1,45 @@ +deserializer->unserializeInstance($transaction['instance']); + $this->instanceRepository->persistInstanceCreated($instance); + } + + /** + * Store data related to the event Process Instance Completed + * + * @param array $transaction + */ + public function persistInstanceCompleted(array $transaction) + { + $instance = $this->deserializer->unserializeInstance($transaction['instance']); + $this->instanceRepository->persistInstanceCompleted($instance); + } + + /** + * Store collaboration data + * + * @param array $transaction + */ + public function persistInstanceCollaboration(array $transaction) + { + // Return elements from serialized data + $targetInstance = $this->deserializer->unserializeInstance($transaction['target_instance']); + $targetParticipant = $this->deserializer->unserializeEntity($transaction['target_participant']); + $sourceInstance = $this->deserializer->unserializeInstance($transaction['source_instance']); + $sourceParticipant = $this->deserializer->unserializeEntity($transaction['source_participant']); + + // Persists collaboration between two instances + $this->instanceRepository->persistInstanceCollaboration($targetInstance, $targetParticipant, $sourceInstance, $sourceParticipant); + } +} diff --git a/ProcessMaker/Nayra/Repositories/PersistenceTokenTrait.php b/ProcessMaker/Nayra/Repositories/PersistenceTokenTrait.php new file mode 100644 index 0000000000..c05cc193cb --- /dev/null +++ b/ProcessMaker/Nayra/Repositories/PersistenceTokenTrait.php @@ -0,0 +1,211 @@ +deserializer->unserializeEntity($transaction['activity']); + $token = $this->deserializer->unserializeToken($transaction['token']); + $this->tokenRepository->persistActivityActivated($activity, $token); + } + + /** + * Persists instance and token data when a token within an activity change to error state + * + * @param array $transaction + */ + public function persistActivityException(array $transaction) + { + $activity = $this->deserializer->unserializeEntity($transaction['activity']); + $token = $this->deserializer->unserializeToken($transaction['token']); + $this->tokenRepository->persistActivityException($activity, $token); + } + + /** + * Persists instance and token data when a token is completed within an activity + * + * @param array $transaction + */ + public function persistActivityCompleted(array $transaction) + { + $activity = $this->deserializer->unserializeEntity($transaction['activity']); + $token = $this->deserializer->unserializeToken($transaction['token']); + $this->tokenRepository->persistActivityCompleted($activity, $token); + } + + /** + * Persists instance and token data when a token is closed by an activity + * + * @param array $transaction + */ + public function persistActivityClosed(array $transaction) + { + $activity = $this->deserializer->unserializeEntity($transaction['activity']); + $token = $this->deserializer->unserializeToken($transaction['token']); + $this->tokenRepository->persistActivityClosed($activity, $token); + } + + /** + * Persists instance and token data when a throw event token arrives + * + * @param array $transaction + */ + public function persistThrowEventTokenArrives(array $transaction) + { + $event = $this->deserializer->unserializeEntity($transaction['event']); + $token = $this->deserializer->unserializeToken($transaction['token']); + $this->tokenRepository->persistThrowEventTokenArrives($event, $token); + } + + /** + * Persists instance and token data when a throw event token is consumed + * + * @param array $transaction + */ + public function persistThrowEventTokenConsumed(array $transaction) + { + $event = $this->deserializer->unserializeEntity($transaction['event']); + $token = $this->deserializer->unserializeToken($transaction['token']); + $this->tokenRepository->persistThrowEventTokenConsumed($event, $token); + } + + /** + * Persists instance and token data when a throw event token is passed + * + * @param array $transaction + */ + public function persistThrowEventTokenPassed(array $transaction) + { + $event = $this->deserializer->unserializeEntity($transaction['event']); + $token = $this->deserializer->unserializeToken($transaction['token']); + $this->tokenRepository->persistThrowEventTokenPassed($event, $token); + } + + /** + * Persists instance and token data when a gateway token arrives + * + * @param array $transaction + */ + public function persistGatewayTokenArrives(array $transaction) + { + $gateway = $this->deserializer->unserializeEntity($transaction['gateway']); + $token = $this->deserializer->unserializeToken($transaction['token']); + $this->tokenRepository->persistGatewayTokenArrives($gateway, $token); + } + + /** + * Persists instance and token data when a gateway token is consumed + * + * @param array $transaction + */ + public function persistGatewayTokenConsumed(array $transaction) + { + $gateway = $this->deserializer->unserializeEntity($transaction['gateway']); + $token = $this->deserializer->unserializeToken($transaction['token']); + $this->tokenRepository->persistGatewayTokenConsumed($gateway, $token); + } + + /** + * Persists instance and token data when a gateway token is passed + * + * @param array $transaction + */ + public function persistGatewayTokenPassed(array $transaction) + { + $gateway = $this->deserializer->unserializeEntity($transaction['gateway']); + $token = $this->deserializer->unserializeToken($transaction['token']); + $this->tokenRepository->persistGatewayTokenPassed($gateway, $token); + } + + /** + * Persists instance and token data when a catch event token arrives + * + * @param array $transaction + */ + public function persistCatchEventTokenArrives(array $transaction) + { + $event = $this->deserializer->unserializeEntity($transaction['catch_event']); + $token = $this->deserializer->unserializeToken($transaction['token']); + $this->tokenRepository->persistCatchEventTokenArrives($event, $token); + } + + /** + * Persists instance and token data when a catch event token is consumed + * + * @param array $transaction + */ + public function persistCatchEventTokenConsumed(array $transaction) + { + $event = $this->deserializer->unserializeEntity($transaction['catch_event']); + $token = $this->deserializer->unserializeToken($transaction['token']); + $this->tokenRepository->persistCatchEventTokenConsumed($event, $token); + } + + /** + * Persists instance and token data when a catch event token is passed + * + * @param array $transaction + */ + public function persistCatchEventTokenPassed(array $transaction) + { + $event = $this->deserializer->unserializeEntity($transaction['catch_event']); + $consumedTokens = $this->deserializer->unserializeTokensCollection($transaction['consumed_tokens']); + $this->tokenRepository->persistCatchEventTokenPassed($event, $consumedTokens); + } + + /** + * Persists instance and token data when a catch event message arrives + * + * @param array $transaction + */ + public function persistCatchEventMessageArrives(array $transaction) + { + $event = $this->deserializer->unserializeEntity($transaction['catch_event']); + $token = $this->deserializer->unserializeToken($transaction['token']); + $this->tokenRepository->persistCatchEventMessageArrives($event, $token); + } + + /** + * Persists instance and token data when a catch event message is consumed + * + * @param array $transaction + */ + public function persistCatchEventMessageConsumed(array $transaction) + { + $event = $this->deserializer->unserializeEntity($transaction['catch_event']); + $token = $this->deserializer->unserializeToken($transaction['token']); + $this->tokenRepository->persistCatchEventMessageConsumed($event, $token); + } + + /** + * Persists tokens that triggered a Start Event + * + * @param array $transaction + */ + public function persistStartEventTriggered(array $transaction) + { + $event = $this->deserializer->unserializeEntity($transaction['start_event']); + $consumedTokens = $this->deserializer->unserializeTokensCollection($transaction['consumed_tokens']); + $this->tokenRepository->persistStartEventTriggered($event, $consumedTokens); + } + + /** + * Persists instance and token data when a token is consumed in a event based gateway + * + * @param array $transaction + */ + public function persistEventBasedGatewayActivated(array $transaction) + { + $gateway = $this->deserializer->unserializeEntity($transaction['gateway']); + $passedToken = $this->deserializer->unserializeToken($transaction['passed_token']); + $consumedTokens = $this->deserializer->unserializeTokensCollection($transaction['consumed_tokens']); + $this->tokenRepository->persistEventBasedGatewayActivated($gateway, $passedToken, $consumedTokens); + } +} diff --git a/ProcessMaker/Nayra/Repositories/ProcessRequestRepository.php b/ProcessMaker/Nayra/Repositories/ProcessRequestRepository.php deleted file mode 100644 index 99e3ceb48c..0000000000 --- a/ProcessMaker/Nayra/Repositories/ProcessRequestRepository.php +++ /dev/null @@ -1,101 +0,0 @@ - $properties['process_id'], - 'data' => $properties['data'], - 'status' => $properties['status'], - 'user_id' => $properties['user_id'], - 'callable_id' => $properties['callable_id'], - 'name' => $properties['name'], - 'process_version_id' => $properties['process_version_id'], - ]); - - // Store temporally the relation between uid and id - $this->storeUid($request->uuid, $request->id); - - return $request; - } catch (Exception $e) { - // Log the error - Log::error("Cannot create request: {$e->getMessage()}"); - return null; - } - } - - /** - * Update a request - * - * @param array $transaction - * @return \ProcessMaker\Models\ProcessRequest - */ - public function update(array $transaction): ? Model - { - try { - // Get the id mapped - $id = $this->resolveId($transaction['id']); - - // If not exists throws an error - if (!$id) { - throw new Exception("Cannot find id for uid {$transaction['id']}"); - } - - // Update the request - $properties = $transaction['properties']; - $model = ProcessRequest::find($id); - $model->fill($properties); - $model->save(); - - // Notify to engine - $model->notifyProcessUpdated('ACTIVITY_ACTIVATED'); - - // Trigger event - if ($model->status === 'COMPLETED') { - event(new ProcessCompleted($model)); - } - - return $model; - } catch (Exception $e) { - // Log the error - Log::error("Cannot update request: {$e->getMessage()}"); - return null; - } - } - - /** - * Save the request - * - * @param array $transaction - * @return \ProcessMaker\Models\ProcessRequest - */ - public function save(array $transaction): ? Model - { - if ($transaction['type'] === 'create') { - return $this->create($transaction); - } - if ($transaction['type'] === 'update') { - return $this->update($transaction); - } - } -} diff --git a/ProcessMaker/Nayra/Repositories/ProcessRequestTokenRepository.php b/ProcessMaker/Nayra/Repositories/ProcessRequestTokenRepository.php deleted file mode 100644 index 799ac7ae7e..0000000000 --- a/ProcessMaker/Nayra/Repositories/ProcessRequestTokenRepository.php +++ /dev/null @@ -1,115 +0,0 @@ -first(); - - // Overrides the request id with the correct value - $properties['request_id'] = $request->getKey(); - - // Complete missing values - if (!isset($properties['user_id'])) { - $properties['user_id'] = $request->user_id; - } - if (!isset($properties['process_id'])) { - $properties['process_id'] = $request->process_id; - } - } - - try { - // Create new request token - $token = ProcessRequestToken::create([ - 'user_id' => $properties['user_id'] ?? null, - 'process_id' => $properties['process_id'] ?? null, - 'process_request_id' => $properties['request_id'], - 'element_id' => $properties['element_id'], - 'element_name' => $properties['element_name'], - 'element_type' => $properties['element_type'], - 'status' => $properties['status'], - // TO DO: - //'due_at' => '', - //'riskchanges_at' => '', - 'self_service_groups' => [], - 'token_properties' => [], - ]); - - // Store temporally the relation between uid and id - $this->storeUid($token->uuid, $token->id); - - return $token; - } catch (Exception $e) { - // Log the error - Log::error("Cannot create token: {$e->getMessage()}"); - return null; - } - } - - /** - * Update a request token - * - * @param array $transaction - * @return \ProcessMaker\Models\ProcessRequestToken - */ - public function update(array $transaction): ? Model - { - try { - // Get the id mapped - $id = $this->resolveId($transaction['id']); - - // If not exists throws an error - if (!$id) { - throw new Exception("Cannot find id for uid {$transaction['id']}"); - } - - // Update the request token - $properties = $transaction['properties']; - $model = ProcessRequestToken::find($id); - $model->fill($properties); - $model->save(); - - return $model; - } catch (Exception $e) { - // Log the error - Log::error("Cannot update token: {$e->getMessage()}"); - return null; - } - } - - /** - * Save the request token - * - * @param array $transaction - * @return \ProcessMaker\Models\ProcessRequestToken - */ - public function save(array $transaction): ? Model - { - if ($transaction['type'] === 'create') { - return $this->create($transaction); - } - if ($transaction['type'] === 'update') { - return $this->update($transaction); - } - } -} diff --git a/ProcessMaker/Providers/RouteServiceProvider.php b/ProcessMaker/Providers/RouteServiceProvider.php index e200d3b6f7..921bc4e692 100644 --- a/ProcessMaker/Providers/RouteServiceProvider.php +++ b/ProcessMaker/Providers/RouteServiceProvider.php @@ -61,6 +61,8 @@ public function map() $this->mapApiRoutes(); $this->mapWebRoutes(); + + $this->mapEngineRoutes(); } /** @@ -89,4 +91,17 @@ protected function mapApiRoutes() Route::middleware('api') ->group(base_path('routes/api.php')); } + + /** + * Define the "engine" routes for the application. + * + * These routes are typically stateless and high performant. + * + * @return void + */ + protected function mapEngineRoutes() + { + Route::middleware('engine') + ->group(base_path('routes/engine.php')); + } } diff --git a/ProcessMaker/Repositories/ExecutionInstanceRepository.php b/ProcessMaker/Repositories/ExecutionInstanceRepository.php index ca95546334..ef5c47b0c5 100644 --- a/ProcessMaker/Repositories/ExecutionInstanceRepository.php +++ b/ProcessMaker/Repositories/ExecutionInstanceRepository.php @@ -5,7 +5,6 @@ use Carbon\Carbon; use ProcessMaker\Models\ProcessCollaboration; use ProcessMaker\Models\ProcessRequest; -use ProcessMaker\Models\ProcessRequest as Instance; use ProcessMaker\Nayra\Contracts\Bpmn\ParticipantInterface; use ProcessMaker\Nayra\Contracts\Engine\ExecutionInstanceInterface; use ProcessMaker\Nayra\Contracts\Repositories\ExecutionInstanceRepositoryInterface; @@ -20,14 +19,37 @@ class ExecutionInstanceRepository implements ExecutionInstanceRepositoryInterfac { use RepositoryTrait; + private bool $abortIfInstanceNotFound = true; + private bool $loadTokens = true; + + /** + * Set not found flag + * + * @param bool $abortIfInstanceNotFound + */ + public function setAbortIfInstanceNotFound(bool $abortIfInstanceNotFound) + { + $this->abortIfInstanceNotFound = $abortIfInstanceNotFound; + } + + /** + * Set load tokens flag + * + * @param bool $loadTokens + */ + public function setLoadTokens(bool $loadTokens) + { + $this->loadTokens = $loadTokens; + } + /** * Create an execution instance. * * @return ExecutionInstanceInterface */ - public function createExecutionInstance() + public function createExecutionInstance(): ExecutionInstanceInterface { - $instance = new Instance(); + $instance = new ProcessRequest(); $instance->setId(uniqid('request', true)); return $instance; @@ -37,25 +59,48 @@ public function createExecutionInstance() * Load an execution instance from a persistent storage. * * @param string $instanceId + * @param StorageInterface $storage * - * @return \ProcessMaker\Nayra\Contracts\Engine\ExecutionInstanceInterface + * @return ExecutionInstanceInterface */ - public function loadExecutionInstanceByUid($instanceId, StorageInterface $storage) + public function loadExecutionInstanceByUid($instanceId, StorageInterface $storage): ExecutionInstanceInterface { - $instance = Instance::find($instanceId); - if (!$instance) { + // Get process request + if (is_numeric($instanceId)) { + $instance = ProcessRequest::find($instanceId); + } else { + $instance = ProcessRequest::where('uuid', $instanceId)->first(); + } + + // Finish if process request not exists + if (!$instance && $this->abortIfInstanceNotFound) { abort(404, 'Instance not found'); + } elseif (!$instance) { + return null; } + + // Get process $callableId = $instance->callable_id; $process = $storage->getProcess($callableId); + + // Set data store $dataStore = $storage->getFactory()->createDataStore(); $dataStore->setData($instance->data); - $instance->setId($instanceId); + + // Set process request properties + $instance->setId($instance->getKey()); $instance->setProcess($process); $instance->setDataStore($dataStore); + + // Get transitions $process->getTransitions($storage->getFactory()); - //Load tokens: + // Finish if is not necessary load the tokens + if (!$this->loadTokens) { + return $instance; + } + + // Load tokens $tokens = $instance->tokens()->where('status', '!=', 'CLOSED')->get(); foreach ($tokens as $token) { $tokenInfo = [ @@ -76,12 +121,10 @@ public function loadExecutionInstanceByUid($instanceId, StorageInterface $storag * Create or update an execution instance to a persistent storage. * * @param \ProcessMaker\Nayra\Contracts\Engine\ExecutionInstanceInterface $instance - * - * @return $this */ public function storeExecutionInstance(ExecutionInstanceInterface $instance) { - // TODO: Implement store() method. or Remove from Interface + // TODO: Implement store() method or Remove from Interface } /** @@ -93,18 +136,21 @@ public function storeExecutionInstance(ExecutionInstanceInterface $instance) */ public function persistInstanceCreated(ExecutionInstanceInterface $instance) { - //Get instance data + // Get instance data $data = $instance->getDataStore()->getData(); - //Get the process + + // Get the process $process = $instance->getProcess(); - //Get process definition + + // Get process definition $definition = $process->getOwnerDocument()->getModel(); + // Do nothing if is not persistent if ($process->isNonPersistent()) { return; } - //Save the row + // Save process request $instance->callable_id = $process->getId(); $instance->process_id = $definition->getKey(); $instance->process_version_id = $definition->getLatestVersion()->getKey(); @@ -115,39 +161,31 @@ public function persistInstanceCreated(ExecutionInstanceInterface $instance) $instance->do_not_sanitize = SanitizeHelper::getDoNotSanitizeFields($definition); $instance->data = $data; $instance->saveOrFail(); + + // Set id $instance->setId($instance->getKey()); + // Persists collaboration $this->persistCollaboration($instance); } - private function findParticipantFor(ExecutionInstanceInterface $instance) - { - $collaboration = $instance->getProcess()->getEngine()->getEventDefinitionBus()->getCollaboration(); - if (!$collaboration) { - return; - } - foreach ($collaboration->getParticipants() as $participant) { - if ($participant->getProcess()->getId() === $instance->getProcess()->getId()) { - return $participant; - } - } - } - /** * Persists instance when an error occurs * * @param ExecutionInstanceInterface $instance - * * @return mixed */ public function persistInstanceError(ExecutionInstanceInterface $instance) { + // Get process $process = $instance->getProcess(); + + // Do nothing if is not persistent if ($process->isNonPersistent()) { return; } - //Save instance + // Save instance with error $instance->status = 'ERROR'; $instance->mergeLatestStoredData(); $instance->saveOrFail(); @@ -157,16 +195,19 @@ public function persistInstanceError(ExecutionInstanceInterface $instance) * Persists instance data related to the event Process Instance Completed * * @param ExecutionInstanceInterface $instance - * * @return mixed */ public function persistInstanceUpdated(ExecutionInstanceInterface $instance) { + // Get process $process = $instance->getProcess(); + + // Do nothing if is not persistent if ($process->isNonPersistent()) { return; } - //Save instance + + // Save updated instance $instance->status = 'ACTIVE'; $instance->mergeLatestStoredData(); $instance->saveOrFail(); @@ -176,16 +217,19 @@ public function persistInstanceUpdated(ExecutionInstanceInterface $instance) * Persists instance data related to the event Process Instance Completed * * @param ExecutionInstanceInterface $instance - * * @return mixed */ public function persistInstanceCompleted(ExecutionInstanceInterface $instance) { + // Get process $process = $instance->getProcess(); + + // Do nothing if is not persistent if ($process->isNonPersistent()) { return; } - //Save instance + + // Save completed instance $instance->status = 'COMPLETED'; $instance->completed_at = Carbon::now(); $instance->mergeLatestStoredData(); @@ -198,14 +242,19 @@ public function persistInstanceCompleted(ExecutionInstanceInterface $instance) * @param ExecutionInstanceInterface $instance Target instance * @param ParticipantInterface $participant Participant related to the target instance * @param ExecutionInstanceInterface $source Source instance - * @param ParticipantInterface $sourceParticipant + * @param ParticipantInterface $sourceParticipant Source participant */ public function persistInstanceCollaboration(ExecutionInstanceInterface $instance, ParticipantInterface $participant = null, ExecutionInstanceInterface $source, ParticipantInterface $sourceParticipant = null) { + // Get process $process = $instance->getProcess(); + + // Do nothing if is not persistent if ($process->isNonPersistent()) { return; } + + // Get collaboration id if not exists if ($source->process_collaboration_id === null) { $collaboration = new ProcessCollaboration(); $collaboration->process_id = $instance->process->getKey(); @@ -213,6 +262,8 @@ public function persistInstanceCollaboration(ExecutionInstanceInterface $instanc $source->process_collaboration_id = $collaboration->getKey(); $source->saveOrFail(); } + + // Save collaboration $instance->process_collaboration_id = $source->process_collaboration_id; $instance->participant_id = $participant ? $participant->getId() : null; $instance->saveOrFail(); @@ -221,15 +272,17 @@ public function persistInstanceCollaboration(ExecutionInstanceInterface $instanc /** * Persist current collaboration. * - * @param ProcessRequest $instance - * @return void + * @param ProcessRequest $request */ private function persistCollaboration(ProcessRequest $request) { + // Get valid engine $engine = $request->getProcess()->getEngine(); if (count($engine->getExecutionInstances()) <= 1) { return; } + + // Get current collaboration $collaboration = null; foreach ($engine->getExecutionInstances() as $instance) { if ($instance->collaboration) { @@ -237,11 +290,15 @@ private function persistCollaboration(ProcessRequest $request) break; } } + + // If not exists a collaboration, create a new one if (!$collaboration) { $collaboration = new ProcessCollaboration(); $collaboration->process_id = $request->process->getKey(); $collaboration->saveOrFail(); } + + // Update collaboration id $request->process_collaboration_id = $collaboration->id; $request->saveOrFail(); } diff --git a/ProcessMaker/Repositories/TokenRepository.php b/ProcessMaker/Repositories/TokenRepository.php index d9d35cb433..a839075a91 100644 --- a/ProcessMaker/Repositories/TokenRepository.php +++ b/ProcessMaker/Repositories/TokenRepository.php @@ -5,9 +5,7 @@ use Carbon\Carbon; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Log; -use ProcessMaker\Exception\InvalidUserAssignmentException; use ProcessMaker\Models\ProcessCollaboration; -use ProcessMaker\Models\ProcessRequest; use ProcessMaker\Models\ProcessRequest as Instance; use ProcessMaker\Models\ProcessRequestToken as Token; use ProcessMaker\Models\User; @@ -18,7 +16,6 @@ use ProcessMaker\Nayra\Contracts\Bpmn\CatchEventInterface; use ProcessMaker\Nayra\Contracts\Bpmn\CollectionInterface; use ProcessMaker\Nayra\Contracts\Bpmn\EventBasedGatewayInterface; -use ProcessMaker\Nayra\Contracts\Bpmn\FlowInterface; use ProcessMaker\Nayra\Contracts\Bpmn\GatewayInterface; use ProcessMaker\Nayra\Contracts\Bpmn\ScriptTaskInterface; use ProcessMaker\Nayra\Contracts\Bpmn\ServiceTaskInterface; @@ -61,8 +58,12 @@ public function createTokenInstance(): TokenInterface return $token; } - public function loadTokenByUid($uid): TokenInterface + public function loadTokenByUid($uid): ? TokenInterface { + if (is_numeric($uid)) { + return Token::find($uid); + } + return Token::where('uuid', $uid)->first(); } /** @@ -519,19 +520,19 @@ private function removeParentFromData(Instance $instance) private function getActivityType($activity) { - if ($activity instanceof ScriptTaskInterface) { + if ($activity instanceof ScriptTaskInterface) { return 'scriptTask'; } - if ($activity instanceof ServiceTaskInterface) { + if ($activity instanceof ServiceTaskInterface) { return 'serviceTask'; } - if ($activity instanceof CallActivityInterface) { + if ($activity instanceof CallActivityInterface) { return 'callActivity'; } - if ($activity instanceof ActivityInterface) { + if ($activity instanceof ActivityInterface) { return 'task'; } diff --git a/routes/api.php b/routes/api.php index f657894495..735ea1a8e2 100644 --- a/routes/api.php +++ b/routes/api.php @@ -34,22 +34,6 @@ use ProcessMaker\Http\Controllers\Process\ModelerController; use ProcessMaker\Http\Controllers\TestStatusController; -// Engine -Route::middleware('auth:api', 'setlocale', 'bindings', 'sanitize')->prefix('api/1.0')->name('api.')->group(function () { - - // List of Processes that the user can start - Route::get('start_processes', [ProcessController::class, 'startProcesses'])->name('processes.start'); // Filtered in controller - - // Start a process - Route::post('process_events/{process}', [ProcessController::class, 'triggerStartEvent'])->name('process_events.trigger')->middleware('can:start,process'); - - // Update task - Route::put('tasks/{task}', [TaskController::class, 'update'])->name('tasks.update')->middleware('can:update,task'); - - // Trigger intermediate event - Route::post('requests/{request}/events/{event}', [ProcessRequestController::class, 'activateIntermediateEvent'])->name('requests.update,request'); -}); - Route::middleware('auth:api', 'setlocale', 'bindings', 'sanitize')->prefix('api/1.0')->name('api.')->group(function () { // Users Route::get('users', [UserController::class, 'index'])->name('users.index'); // Permissions handled in the controller diff --git a/routes/engine.php b/routes/engine.php new file mode 100644 index 0000000000..92f67def38 --- /dev/null +++ b/routes/engine.php @@ -0,0 +1,21 @@ +name('api.')->group(function () { + // List of Processes that the user can start + Route::get('start_processes', [ProcessController::class, 'startProcesses'])->name('processes.start'); // Filtered in controller + + // Start a process + Route::post('process_events/{process}', [ProcessController::class, 'triggerStartEvent'])->name('process_events.trigger')->middleware('can:start,process'); + + // Update task + Route::put('tasks/{task}', [TaskController::class, 'update'])->name('tasks.update')->middleware('can:update,task'); + + // Trigger intermediate event + Route::post('requests/{request}/events/{event}', [ProcessRequestController::class, 'activateIntermediateEvent'])->name('requests.update,request'); +});