diff --git a/ProcessMaker/Facades/WorkflowManager.php b/ProcessMaker/Facades/WorkflowManager.php index dd2f2b362a..e03bc9223f 100644 --- a/ProcessMaker/Facades/WorkflowManager.php +++ b/ProcessMaker/Facades/WorkflowManager.php @@ -14,6 +14,8 @@ * @method static mixed runServiceTask(\ProcessMaker\Nayra\Contracts\Bpmn\ServiceTaskInterface $serviceTask, Token $token) * @method static void throwSignalEventDefinition(\ProcessMaker\Nayra\Contracts\Bpmn\EventDefinitionInterface $sourceEventDefinition, \ProcessMaker\Nayra\Contracts\Bpmn\TokenInterface $token) * @method static void throwSignalEvent($signalRef, array $data = [], array $exclude = []) + * @method static void throwSignalEventProcess($processId, $signalRef, array $data) + * @method static void throwSignalEventRequest(\ProcessMaker\Models\ProcessRequest $request, $signalRef, array $data) * @method static void throwMessageEvent($instanceId, $elementId, $messageRef, array $payload = []) * @method static void onDataValidation(callable $callback) * @method static void validateData(array $data, $definitions, $element) diff --git a/ProcessMaker/Jobs/BoundaryEvent.php b/ProcessMaker/Jobs/BoundaryEvent.php index f45602e1ba..d5a56a7f0c 100644 --- a/ProcessMaker/Jobs/BoundaryEvent.php +++ b/ProcessMaker/Jobs/BoundaryEvent.php @@ -62,7 +62,7 @@ public function action(BpmnDocumentInterface $definitions, TokenInterface $token } /** - * Updata data for a message event + * Update data for a message event * * If variableName is set, then the event payload will be set to that variable name. * If the data name exists, then the data is merged. diff --git a/ProcessMaker/Jobs/CatchSignalEventRequest.php b/ProcessMaker/Jobs/CatchSignalEventRequest.php index 3fdf5d88c6..0473c4078e 100644 --- a/ProcessMaker/Jobs/CatchSignalEventRequest.php +++ b/ProcessMaker/Jobs/CatchSignalEventRequest.php @@ -6,6 +6,7 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; +use ProcessMaker\Facades\WorkflowManager; use ProcessMaker\Models\ProcessRequest; class CatchSignalEventRequest implements ShouldQueue @@ -34,10 +35,10 @@ public function __construct($chunck, $signalRef, $payload) public function handle() { - $this->payload = unpackTemporalData($this->payload_uid); + $payload = unpackTemporalData($this->payload_uid); foreach ($this->chunck as $requestId) { $request = ProcessRequest::find($requestId); - CatchSignalEventInRequest::dispatchNow($request, $this->payload, $this->signalRef); + WorkflowManager::throwSignalEventRequest($request, $this->signalRef, $payload); } removeTemporalData($this->payload_uid); } diff --git a/ProcessMaker/Jobs/ThrowSignalEvent.php b/ProcessMaker/Jobs/ThrowSignalEvent.php index abba928603..2588c48a99 100644 --- a/ProcessMaker/Jobs/ThrowSignalEvent.php +++ b/ProcessMaker/Jobs/ThrowSignalEvent.php @@ -6,6 +6,7 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; +use ProcessMaker\Facades\WorkflowManager; use ProcessMaker\Models\Process; use ProcessMaker\Models\ProcessRequest; @@ -47,11 +48,11 @@ public function handle() ->pluck('id') ->toArray(); foreach ($processes as $process) { - CatchSignalEventProcess::dispatch( + WorkflowManager::throwSignalEventProcess( $process, $this->signalRef, $this->data - )->onQueue('bpmn'); + ); } $count = ProcessRequest::whereNotIn('id', $this->excludeRequests) ->where('status', 'ACTIVE') diff --git a/ProcessMaker/Models/EnvironmentVariable.php b/ProcessMaker/Models/EnvironmentVariable.php index a5f4605580..07efd060bf 100644 --- a/ProcessMaker/Models/EnvironmentVariable.php +++ b/ProcessMaker/Models/EnvironmentVariable.php @@ -2,6 +2,7 @@ namespace ProcessMaker\Models; +use Illuminate\Support\Facades\Log; use Illuminate\Validation\Rule; use ProcessMaker\Traits\Exportable; @@ -53,7 +54,13 @@ public function setValueAttribute($value) */ public function getValueAttribute() { - return decrypt($this->attributes['value']); + try { + return decrypt($this->attributes['value']); + } catch (\Exception $e) { + Log::error('EnvironmentVariable: ' . $this->attributes['value']. "\n" . $e->getMessage()); + Log::error($e->getTraceAsString()); + return null; + } } public static function rules($existing = null) diff --git a/ProcessMaker/Models/Process.php b/ProcessMaker/Models/Process.php index 09ee450575..325c90ec6e 100644 --- a/ProcessMaker/Models/Process.php +++ b/ProcessMaker/Models/Process.php @@ -27,6 +27,7 @@ use ProcessMaker\Nayra\Contracts\Bpmn\ServiceTaskInterface; use ProcessMaker\Nayra\Contracts\Bpmn\StartEventInterface; use ProcessMaker\Nayra\Contracts\Storage\BpmnDocumentInterface; +use ProcessMaker\Nayra\Managers\WorkflowManagerDefault; use ProcessMaker\Nayra\Storage\BpmnDocument; use ProcessMaker\Package\WebEntry\Models\WebentryRoute; use ProcessMaker\Rules\BPMNValidation; @@ -584,7 +585,7 @@ private function scalateToManagerIfEnabled($user, $activity, $token, $assignment if ($escalateToManager) { $user = WorkflowUserManager::escalateToManager($token, $user); } else { - $res = WorkflowManager::runProcess($assignmentProcess, 'assign', [ + $res = (new WorkflowManagerDefault)->runProcess($assignmentProcess, 'assign', [ 'user_id' => $user, 'process_id' => $this->id, 'request_id' => $token->getInstance()->getId(), diff --git a/ProcessMaker/Models/ProcessCollaboration.php b/ProcessMaker/Models/ProcessCollaboration.php index 004034212c..bfd893c405 100644 --- a/ProcessMaker/Models/ProcessCollaboration.php +++ b/ProcessMaker/Models/ProcessCollaboration.php @@ -2,6 +2,8 @@ namespace ProcessMaker\Models; +use ProcessMaker\Traits\HasUuids; + /** * Represents an Eloquent model of a Request which is an instance of a Process. * @@ -12,6 +14,8 @@ */ class ProcessCollaboration extends ProcessMakerModel { + use HasUuids; + protected $connection = 'processmaker'; /** diff --git a/ProcessMaker/Models/ProcessRequest.php b/ProcessMaker/Models/ProcessRequest.php index 03f8643a57..f963d47589 100644 --- a/ProcessMaker/Models/ProcessRequest.php +++ b/ProcessMaker/Models/ProcessRequest.php @@ -41,6 +41,7 @@ * @property string $name * @property string $status * @property string $data + * @property string $collaboration_uuid * @property \Carbon\Carbon $initiated_at * @property \Carbon\Carbon $completed_at * @property \Carbon\Carbon $updated_at diff --git a/ProcessMaker/Nayra/Managers/WorkflowManagerDefault.php b/ProcessMaker/Nayra/Managers/WorkflowManagerDefault.php index e82a230fd3..46f9674878 100644 --- a/ProcessMaker/Nayra/Managers/WorkflowManagerDefault.php +++ b/ProcessMaker/Nayra/Managers/WorkflowManagerDefault.php @@ -10,6 +10,8 @@ use ProcessMaker\Jobs\BoundaryEvent; use ProcessMaker\Jobs\CallProcess; use ProcessMaker\Jobs\CatchEvent; +use ProcessMaker\Jobs\CatchSignalEventInRequest; +use ProcessMaker\Jobs\CatchSignalEventProcess; use ProcessMaker\Jobs\CompleteActivity; use ProcessMaker\Jobs\RunScriptTask; use ProcessMaker\Jobs\RunServiceTask; @@ -18,6 +20,7 @@ use ProcessMaker\Jobs\ThrowSignalEvent; use ProcessMaker\Models\FormalExpression; use ProcessMaker\Models\Process as Definitions; +use ProcessMaker\Models\ProcessRequest; use ProcessMaker\Models\ProcessRequestToken as Token; use ProcessMaker\Nayra\Contracts\Bpmn\BoundaryEventInterface; use ProcessMaker\Nayra\Contracts\Bpmn\EntityInterface; @@ -29,6 +32,7 @@ use ProcessMaker\Nayra\Contracts\Bpmn\ThrowEventInterface; use ProcessMaker\Nayra\Contracts\Bpmn\TokenInterface; use ProcessMaker\Nayra\Contracts\Engine\ExecutionInstanceInterface; +use ProcessMaker\Repositories\DefinitionsRepository; class WorkflowManagerDefault implements WorkflowManagerInterface { @@ -255,6 +259,38 @@ public function throwSignalEvent($signalRef, array $data = [], array $exclude = ThrowSignalEvent::dispatch($signalRef, $data, $exclude)->onQueue('bpmn'); } + /** + * Throw a signal event by signalRef into a specific process. + * + * @param int $process + * @param string $signalRef + * @param array $data + */ + public function throwSignalEventProcess($processId, $signalRef, array $data) + { + CatchSignalEventProcess::dispatch( + $processId, + $signalRef, + $data + )->onQueue('bpmn'); + } + + /** + * Throw a signal event by signalRef into a specific request. + * + * @param ProcessRequest $request + * @param string $signalRef + * @param array $data + */ + public function throwSignalEventRequest(ProcessRequest $request, $signalRef, array $data) + { + CatchSignalEventInRequest::dispatchSync( + $request, + $data, + $signalRef + ); + } + /** * Catch a signal event. * diff --git a/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php b/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php index 26eebebf4f..53247c6ef6 100644 --- a/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php +++ b/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php @@ -6,24 +6,36 @@ use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Log; use ProcessMaker\Contracts\WorkflowManagerInterface; +use ProcessMaker\Exception\ScriptException; use ProcessMaker\Facades\MessageBrokerService; +use ProcessMaker\Facades\WorkflowManager; use ProcessMaker\GenerateAccessToken; +use ProcessMaker\Managers\DataManager; +use ProcessMaker\Managers\SignalManager; use ProcessMaker\Models\EnvironmentVariable; use ProcessMaker\Models\Process as Definitions; +use ProcessMaker\Models\Process; +use ProcessMaker\Models\ProcessCollaboration; use ProcessMaker\Models\ProcessRequest; +use ProcessMaker\Models\Script; use ProcessMaker\Models\User; +use ProcessMaker\Nayra\Contracts\Bpmn\BoundaryEventInterface; use ProcessMaker\Nayra\Contracts\Bpmn\ScriptTaskInterface; +use ProcessMaker\Nayra\Contracts\Bpmn\ServiceTaskInterface; use ProcessMaker\Nayra\Contracts\Bpmn\StartEventInterface; use ProcessMaker\Nayra\Contracts\Bpmn\TokenInterface; use ProcessMaker\Nayra\Contracts\Engine\ExecutionInstanceInterface; +use Throwable; class WorkflowManagerRabbitMq extends WorkflowManagerDefault implements WorkflowManagerInterface { const ACTION_START_PROCESS = 'START_PROCESS'; - const ACTION_COMPLETE_TASK = 'COMPLETE_TASK'; const ACTION_TRIGGER_INTERMEDIATE_EVENT = 'TRIGGER_INTERMEDIATE_EVENT'; const ACTION_RUN_SCRIPT = 'RUN_SCRIPT'; + const ACTION_TRIGGER_BOUNDARY_EVENT = 'TRIGGER_BOUNDARY_EVENT'; + const ACTION_TRIGGER_MESSAGE_EVENT = 'TRIGGER_MESSAGE_EVENT'; + const ACTION_TRIGGER_SIGNAL_EVENT = 'TRIGGER_SIGNAL_EVENT'; /** * Trigger a start event and return the process request instance. @@ -43,6 +55,9 @@ public function triggerStartEvent(Definitions $definitions, StartEventInterface $userId = $this->getCurrentUserId(); // Create immediately a new process request + $collaboration = ProcessCollaboration::create([ + 'process_id' => $definitions->id, + ]); $request = ProcessRequest::create([ 'process_id' => $definitions->id, 'user_id' => $userId, @@ -54,6 +69,8 @@ public function triggerStartEvent(Definitions $definitions, StartEventInterface 'initiated_at' => Carbon::now(), 'process_version_id' => $version->getKey(), 'signal_events' => [], + 'collaboration_uuid' => $collaboration->uuid, + 'process_collaboration_id' => $collaboration->id, ]); // Serialize instance @@ -101,13 +118,13 @@ public function completeTask(Definitions $definitions, ExecutionInstanceInterfac $this->validateData($data, $definitions, $element); // Get complementary information - $version = $definitions->getLatestVersion(); + $version = $instance->process_version_id; $userId = $this->getCurrentUserId(); $state = $this->serializeState($instance); // Dispatch complete task action $this->dispatchAction([ - 'bpmn' => $version->getKey(), + 'bpmn' => $version, 'action' => self::ACTION_COMPLETE_TASK, 'params' => [ 'request_id' => $token->process_request_id, @@ -139,13 +156,13 @@ public function completeCatchEvent(Definitions $definitions, ExecutionInstanceIn $this->validateData($data, $definitions, $element); // Get complementary information - $version = $definitions->getLatestVersion(); + $version = $instance->process_version_id; $userId = $this->getCurrentUserId(); $state = $this->serializeState($instance); // Dispatch complete task action $this->dispatchAction([ - 'bpmn' => $version->getKey(), + 'bpmn' => $version, 'action' => self::ACTION_TRIGGER_INTERMEDIATE_EVENT, 'params' => [ 'request_id' => $token->process_request_id, @@ -173,13 +190,13 @@ public function runScripTask(ScriptTaskInterface $scriptTask, TokenInterface $to // Get complementary information $instance = $token->processRequest; - $version = $instance->process->getLatestVersion(); + $version = $instance->process_version_id; $userId = $this->getCurrentUserId(); $state = $this->serializeState($instance); // Dispatch complete task action $this->dispatchAction([ - 'bpmn' => $version->getKey(), + 'bpmn' => $version, 'action' => self::ACTION_RUN_SCRIPT, 'params' => [ 'request_id' => $token->process_request_id, @@ -194,6 +211,257 @@ public function runScripTask(ScriptTaskInterface $scriptTask, TokenInterface $to ]); } + /** + * Run a service task. + * + * @param ServiceTaskInterface $serviceTask + * @param TokenInterface $token + */ + public function runServiceTask(ServiceTaskInterface $serviceTask, TokenInterface $token) + { + // Log execution + Log::info('Dispatch a service task: ' . $serviceTask->getId()); + + // Get complementary information + $element = $token->getDefinition(true); + $instance = $token->processRequest; + $processModel = $instance->process; + $version = $instance->process_version_id; + $userId = $this->getCurrentUserId(); + $state = $this->serializeState($instance); + + // Exit if the task was completed or closed + if (!$token || !$element) { + return; + } + + // Get service task configuration + $implementation = $element->getImplementation(); + $configuration = json_decode($element->getProperty('config'), true); + $errorHandling = json_decode($element->getProperty('errorHandling'), true); + + // Check to see if we've failed parsing. If so, let's convert to empty array. + if ($configuration === null) { + $configuration = []; + } + + try { + if (empty($implementation)) { + throw new ScriptException('Service task implementation not defined'); + } else { + $script = Script::where('key', $implementation)->first(); + } + + // Check if service task implementation exists + $existsImpl = WorkflowManager::existsServiceImplementation($implementation); + if (!$existsImpl && empty($script)) { + throw new ScriptException('Service task not implemented: ' . $implementation); + } + + // Get data + $dataManager = new DataManager(); + $data = $dataManager->getData($token); + + // Run implementation/script + if ($existsImpl) { + $response = [ + 'output' => WorkflowManager::runServiceImplementation($implementation, $data, $configuration, $token->getId(), $errorHandling), + ]; + } else { + $response = $script->runScript($data, $configuration, $token->getId(), $errorHandling); + } + + // Update data + if (is_array($response['output'])) { + // Validate data + $this->validateData($response['output'], $processModel, $element); + $dataManager = new DataManager(); + $dataManager->updateData($token, $response['output']); + } + + // Dispatch complete task action + $this->dispatchAction([ + 'bpmn' => $version, + 'action' => self::ACTION_COMPLETE_TASK, + 'params' => [ + 'request_id' => $token->process_request_id, + 'token_id' => $token->uuid, + 'element_id' => $token->element_id, + 'data' => $response['output'], + ], + 'state' => $state, + 'session' => [ + 'user_id' => $userId, + ], + ]); + } catch (Throwable $exception) { + // Change to error status + $token->setStatus(ServiceTaskInterface::TOKEN_STATE_FAILING); + $error = $element->getRepository()->createError(); + $error->setName($exception->getMessage()); + $token->setProperty('error', $error); + + // Log message errors + Log::info('Service task failed: ' . $implementation . ' - ' . $exception->getMessage()); + Log::error($exception->getTraceAsString()); + } + } + + /** + * Trigger a boundary event + * + * @param Definitions $definitions + * @param ExecutionInstanceInterface $instance + * @param TokenInterface $token + * @param BoundaryEventInterface $boundaryEvent + * @param array $data + * + * @return void + */ + public function triggerBoundaryEvent( + Definitions $definitions, + ExecutionInstanceInterface $instance, + TokenInterface $token, + BoundaryEventInterface $boundaryEvent, + array $data + ) { + //Validate data + $this->validateData($data, $definitions, $boundaryEvent); + + // Get complementary information + $version = $instance->process_version_id; + $userId = $this->getCurrentUserId(); + $state = $this->serializeState($instance); + + // Dispatch complete task action + $this->dispatchAction([ + 'bpmn' => $version, + 'action' => self::ACTION_TRIGGER_BOUNDARY_EVENT, + 'params' => [ + 'request_id' => $token->process_request_id, + 'token_id' => $token->uuid, + 'element_id' => $boundaryEvent->getId(), + 'data' => [], + ], + 'state' => $state, + 'session' => [ + 'user_id' => $userId, + ], + ]); + } + + /** + * Triggers a message event in the process instance based on provided parameters. + * + * @param $instanceId of the process instance that is to be triggered + * @param $elementId of the catch message event element + * @param $messageRef of the message event that is to be triggered + * @param $payload (optional) array of key-value pairs that are to be stored in the data store + */ + public function throwMessageEvent($instanceId, $elementId, $messageRef, array $payload = []) + { + // Get complementary information + $instance = ProcessRequest::find($instanceId); + $version = $instance->process_version_id; + $userId = $this->getCurrentUserId(); + $state = $this->serializeState($instance); + + // Dispatch complete task action + $this->dispatchAction([ + 'bpmn' => $version, + 'action' => self::ACTION_TRIGGER_MESSAGE_EVENT, + 'params' => [ + 'instance_id' => $instanceId, + 'element_id' => $elementId, + 'message_ref' => $messageRef, + 'data' => $payload, + ], + 'state' => $state, + 'session' => [ + 'user_id' => $userId, + ], + ]); + } + + /** + * Throw a signal event by signalRef into a specific process. + * + * @param int $processId + * @param string $signalRef + * @param array $data + */ + public function throwSignalEventProcess($processId, $signalRef, array $data) + { + // Get complementary information + $userId = $this->getCurrentUserId(); + // get process variable + $process = Process::find($processId); + $definitions = $process->getDefinitions(); + $catches = SignalManager::getSignalCatchEvents($signalRef, $definitions); + $processVariable = ''; + foreach ($catches as $catch) { + $processVariable = $definitions->getStartEvent($catch['id'])->getBpmnElement()->getAttribute('pm:config'); + } + if ($processVariable) { + $data = [ + $processVariable => $data, + ]; + } + // Dispatch complete task action + $this->dispatchAction([ + 'bpmn' => $process->getLatestVersion()->getKey(), + 'action' => self::ACTION_TRIGGER_SIGNAL_EVENT, + 'params' => [ + 'signal_ref' => $signalRef, + 'data' => $data, + ], + 'session' => [ + 'user_id' => $userId, + ], + ]); + } + + /** + * Throw a signal event by signalRef into a specific request. + * + * @param ProcessRequest $request + * @param string $signalRef + * @param array $data + */ + public function throwSignalEventRequest(ProcessRequest $request, $signalRef, array $data) + { + // Get complementary information + $userId = $this->getCurrentUserId(); + $state = $this->serializeState($request); + // Get process variable + $definitions = $request->processVersion->getDefinitions(); + $catches = SignalManager::getSignalCatchEvents($signalRef, $definitions); + $processVariable = ''; + foreach ($catches as $catch) { + $processVariable = $definitions->getStartEvent($catch['id'])->getBpmnElement()->getAttribute('pm:config'); + } + if ($processVariable) { + $data = [ + $processVariable => $data, + ]; + } + // Dispatch complete task action + $this->dispatchAction([ + 'bpmn' => $request->process_version_id, + 'action' => self::ACTION_TRIGGER_SIGNAL_EVENT, + 'params' => [ + 'instance_id' => $request->uuid, + 'request_id' => $request->id, + 'signal_ref' => $signalRef, + 'data' => $data, + ], + 'state' => $state, + 'session' => [ + 'user_id' => $userId, + ], + ]); + } + /** * Build a state object. * @@ -202,27 +470,36 @@ public function runScripTask(ScriptTaskInterface $scriptTask, TokenInterface $to */ private function serializeState(ProcessRequest $instance) { - // Get open tokens - $tokensRows = []; - $tokens = $instance->tokens()->where('status', '!=', 'CLOSED')->where('status', '!=', 'TRIGGERED')->get(); - foreach ($tokens as $token) { - $tokensRows[] = array_merge($token->token_properties ?: [], [ - 'id' => $token->uuid, - 'status' => $token->status, - 'index' => $token->element_index, - 'element_id' => $token->element_id, - ]); + if ($instance->collaboration) { + $requests = $instance->collaboration->requests()->where('status', 'ACTIVE')->get(); + } else { + $requests = collect([$instance]); } + $requests = $requests->map(function ($request) { + // Get open tokens + $tokensRows = []; + $tokens = $request->tokens()->where('status', '!=', 'CLOSED')->where('status', '!=', 'TRIGGERED')->get(); + foreach ($tokens as $token) { + $tokensRows[] = array_merge($token->token_properties ?: [], [ + 'id' => $token->uuid, + 'status' => $token->status, + 'index' => $token->element_index, + 'element_id' => $token->element_id, + 'created_at' => $token->created_at->getTimestamp(), + ]); + } + + return [ + 'id' => $request->uuid, + 'callable_id' => $request->callable_id, + 'collaboration_uuid' => $request->collaboration_uuid, + 'data' => $request->data, + 'tokens' => $tokensRows, + ]; + }); return [ - 'requests' => [ - [ - 'id' => $instance->uuid, - 'callable_id' => $instance->callable_id, - 'data' => $instance->data, - 'tokens' => $tokensRows, - ], - ], + 'requests' => $requests->toArray(), ]; } diff --git a/ProcessMaker/Nayra/MessageBrokers/ServiceRabbitMq.php b/ProcessMaker/Nayra/MessageBrokers/ServiceRabbitMq.php index b621030626..7408d9457e 100644 --- a/ProcessMaker/Nayra/MessageBrokers/ServiceRabbitMq.php +++ b/ProcessMaker/Nayra/MessageBrokers/ServiceRabbitMq.php @@ -61,16 +61,15 @@ public function disconnect(): void public function sendMessage(string $subject, string $collaborationId, mixed $body): void { // Connect to RabbitMQ - $this->connect(); + if (!($this->connection instanceof AMQPStreamConnection) || !$this->connection->isConnected()) { + $this->connect(); + } // Prepare the message to send $message = new AMQPMessage(json_encode(['data' => $body, 'collaboration_id' => $collaborationId])); // Publish the message $this->channel->basic_publish($message, '', self::QUEUE_NAME_PUBLISH); - - // Close connection to RabbitMQ - $this->disconnect(); } /** @@ -114,7 +113,9 @@ public function worker(): void } // Disconnect from service - $this->disconnect(); + if ($this->connection->isConnected()) { + $this->disconnect(); + } } /** diff --git a/ProcessMaker/Nayra/Repositories/Deserializer.php b/ProcessMaker/Nayra/Repositories/Deserializer.php index c2c019dc6d..002f7031ad 100644 --- a/ProcessMaker/Nayra/Repositories/Deserializer.php +++ b/ProcessMaker/Nayra/Repositories/Deserializer.php @@ -9,6 +9,7 @@ use ProcessMaker\Nayra\Bpmn\Collection; use ProcessMaker\Nayra\Contracts\Bpmn\CollectionInterface; use ProcessMaker\Nayra\Contracts\Bpmn\EntityInterface; +use ProcessMaker\Nayra\Contracts\Bpmn\EventDefinitionInterface; use ProcessMaker\Nayra\Contracts\Bpmn\TokenInterface; use ProcessMaker\Nayra\Contracts\Engine\ExecutionInstanceInterface; use ProcessMaker\Repositories\BpmnDocument; @@ -19,10 +20,15 @@ class Deserializer { private $definitions = []; + private $requests = []; + private $tokens = []; + private ExecutionInstanceRepository $instanceRepository; + private TokenRepository $tokenRepository; + private DefinitionsRepository $factory; /** @@ -202,7 +208,7 @@ public function unserializeInstance(array $serialized): ExecutionInstanceInterfa * Return a process request token from serialized data * * @param array $serialized - * @return TokenInterface + * @return TokenInterface|ProcessRequestToken */ public function unserializeToken(array $serialized): TokenInterface { @@ -230,6 +236,7 @@ public function unserializeToken(array $serialized): TokenInterface public function unserializeEntity(array $serialized): EntityInterface { $definition = $this->findProcessDefinition($serialized['model_id']); + return $definition->getElementInstanceById($serialized['id']); } @@ -248,4 +255,21 @@ public function unserializeTokensCollection(array $serialized): CollectionInterf return $collection; } + + /** + * Return event definition from serialized data + * + * @param array $serialized + * @return EventDefinitionInterface + */ + public function unserializeEventDefinition(array $serialized): EventDefinitionInterface + { + $definition = $this->findProcessDefinition($serialized['model_id']); + $element = $definition->getElementInstanceById($serialized['element_id']); + $node = $element->getBpmnElement(); + $childNode = $node->childNodes->item($serialized['index']); + $eventDefinition = $childNode->getBpmnElementInstance(); + + return $eventDefinition; + } } diff --git a/ProcessMaker/Nayra/Repositories/PersistenceHandler.php b/ProcessMaker/Nayra/Repositories/PersistenceHandler.php index 8f1595b1fa..d66b07ded5 100644 --- a/ProcessMaker/Nayra/Repositories/PersistenceHandler.php +++ b/ProcessMaker/Nayra/Repositories/PersistenceHandler.php @@ -4,6 +4,10 @@ use Exception; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Log; +use ProcessMaker\Listeners\BpmnSubscriber; +use ProcessMaker\Managers\TaskSchedulerManager; +use ProcessMaker\Models\ProcessRequest; use ProcessMaker\Repositories\ExecutionInstanceRepository; use ProcessMaker\Repositories\TokenRepository; @@ -11,11 +15,16 @@ class PersistenceHandler { use PersistenceRequestTrait; use PersistenceTokenTrait; + use PersistenceTimerEventsTrait; protected Deserializer $deserializer; + protected ExecutionInstanceRepository $instanceRepository; + protected TokenRepository $tokenRepository; + protected TaskSchedulerManager $taskSchedulerManager; + /** * PersistenceHandler constructor */ @@ -24,6 +33,7 @@ public function __construct() $this->deserializer = new Deserializer(); $this->instanceRepository = new ExecutionInstanceRepository(); $this->tokenRepository = new TokenRepository($this->instanceRepository); + $this->taskSchedulerManager = new TaskSchedulerManager(); } /** @@ -40,73 +50,104 @@ public function save(array $transaction) 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; - case 'instance_updated': - $this->persistInstanceUpdated($transaction); - break; - default: - throw new Exception('Unknown transaction type ' . $transaction['type']); + try { + // 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; + case 'instance_updated': + $this->persistInstanceUpdated($transaction); + break; + case 'schedule_date': + $this->persistScheduleDate($transaction); + break; + case 'schedule_cycle': + $this->persistScheduleCycle($transaction); + break; + case 'schedule_duration': + $this->persistScheduleDuration($transaction); + break; + case 'service_task_activated': + // Get object instances + $token = $this->deserializer->unserializeToken($transaction['token']); + $serviceTask = $this->deserializer->unserializeEntity($transaction['activity']); + + // Trigger service task listener + $subscriber = new BpmnSubscriber(); + $subscriber->onServiceTaskActivated($serviceTask, $token); + break; + default: + throw new Exception('Unknown transaction type ' . $transaction['type']); + } + } catch (Exception $error) { + Log::error($error->getMessage()); + if ($transaction['token']) { + $token = $this->deserializer->unserializeToken($transaction['token']); + $request = $token->getInstance(); + if ($request && $request instanceof ProcessRequest) { + $request->logError($error); + $request->status = 'ERROR'; + $request->save(); + } + } } } } diff --git a/ProcessMaker/Nayra/Repositories/PersistenceRequestTrait.php b/ProcessMaker/Nayra/Repositories/PersistenceRequestTrait.php index b38e6de995..bb0f3d64c8 100644 --- a/ProcessMaker/Nayra/Repositories/PersistenceRequestTrait.php +++ b/ProcessMaker/Nayra/Repositories/PersistenceRequestTrait.php @@ -7,6 +7,7 @@ trait PersistenceRequestTrait { protected ExecutionInstanceRepository $instanceRepository; + protected Deserializer $deserializer; /** * Store data related to the event Process Instance Created diff --git a/ProcessMaker/Nayra/Repositories/PersistenceTimerEventsTrait.php b/ProcessMaker/Nayra/Repositories/PersistenceTimerEventsTrait.php new file mode 100644 index 0000000000..cdbcfd65c0 --- /dev/null +++ b/ProcessMaker/Nayra/Repositories/PersistenceTimerEventsTrait.php @@ -0,0 +1,42 @@ +deserializer->unserializeEventDefinition($transaction['event_definition']); + $element = $this->deserializer->unserializeEntity($transaction['element']); + $token = $transaction['token'] ? $this->deserializer->unserializeToken($transaction['token']) : null; + $this->taskSchedulerManager->scheduleDate($dateTime, $eventDefinition, $element, $token); + } + + public function persistScheduleCycle(array $transaction) + { + $cycle = unserialize($transaction['cycle']); + $eventDefinition = $this->deserializer->unserializeEventDefinition($transaction['event_definition']); + $element = $this->deserializer->unserializeEntity($transaction['element']); + $token = $transaction['token'] ? $this->deserializer->unserializeToken($transaction['token']) : null; + $this->taskSchedulerManager->scheduleCycle($cycle, $eventDefinition, $element, $token); + } + + public function persistScheduleDuration(array $transaction) + { + $duration = unserialize($transaction['duration']); + $eventDefinition = $this->deserializer->unserializeEventDefinition($transaction['event_definition']); + $element = $this->deserializer->unserializeEntity($transaction['element']); + $token = $transaction['token'] ? $this->deserializer->unserializeToken($transaction['token']) : null; + $this->taskSchedulerManager->scheduleDuration($duration, $eventDefinition, $element, $token); + } +} diff --git a/ProcessMaker/Repositories/ExecutionInstanceRepository.php b/ProcessMaker/Repositories/ExecutionInstanceRepository.php index ef5c47b0c5..281d8d21f3 100644 --- a/ProcessMaker/Repositories/ExecutionInstanceRepository.php +++ b/ProcessMaker/Repositories/ExecutionInstanceRepository.php @@ -61,9 +61,9 @@ public function createExecutionInstance(): ExecutionInstanceInterface * @param string $instanceId * @param StorageInterface $storage * - * @return ExecutionInstanceInterface + * @return ExecutionInstanceInterface|null */ - public function loadExecutionInstanceByUid($instanceId, StorageInterface $storage): ExecutionInstanceInterface + public function loadExecutionInstanceByUid($instanceId, StorageInterface $storage): ?ExecutionInstanceInterface { // Get process request if (is_numeric($instanceId)) { @@ -152,6 +152,7 @@ public function persistInstanceCreated(ExecutionInstanceInterface $instance) // Save process request $instance->callable_id = $process->getId(); + $instance->collaboration_uuid = $instance->getProperty('collaboration_uuid', null); $instance->process_id = $definition->getKey(); $instance->process_version_id = $definition->getLatestVersion()->getKey(); $instance->user_id = pmUser() ? pmUser()->getKey() : null; @@ -208,7 +209,9 @@ public function persistInstanceUpdated(ExecutionInstanceInterface $instance) } // Save updated instance - $instance->status = 'ACTIVE'; + if (!$instance->status) { + $instance->status = 'ACTIVE'; + } $instance->mergeLatestStoredData(); $instance->saveOrFail(); } @@ -278,28 +281,40 @@ private function persistCollaboration(ProcessRequest $request) { // Get valid engine $engine = $request->getProcess()->getEngine(); - if (count($engine->getExecutionInstances()) <= 1) { - return; - } + if ($engine) { + if (count($engine->getExecutionInstances()) <= 1) { + return; + } - // Get current collaboration - $collaboration = null; - foreach ($engine->getExecutionInstances() as $instance) { - if ($instance->collaboration) { - $collaboration = $instance->collaboration; - break; + // Get current collaboration + $collaboration = null; + foreach ($engine->getExecutionInstances() as $instance) { + if ($instance->collaboration) { + $collaboration = $instance->collaboration; + break; + } } - } - // If not exists a collaboration, create a new one - if (!$collaboration) { - $collaboration = new ProcessCollaboration(); - $collaboration->process_id = $request->process->getKey(); - $collaboration->saveOrFail(); - } + // 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(); + // Update collaboration id + $request->process_collaboration_id = $collaboration->id; + $request->saveOrFail(); + } elseif ($request->collaboration_uuid) { + // find by uuid or create + $collaboration = ProcessCollaboration::firstOrCreate([ + 'uuid' => $request->collaboration_uuid, + 'process_id' => $request->process_id, + ]); + if ($collaboration) { + $request->process_collaboration_id = $collaboration->id; + $request->saveOrFail(); + } + } } } diff --git a/config/rabbitmq.php b/config/rabbitmq.php index c1d178c458..5ead5f127a 100644 --- a/config/rabbitmq.php +++ b/config/rabbitmq.php @@ -1,5 +1,10 @@ Enqueue\AmqpLib\AmqpConnectionFactory::class, 'host' => env('RABBITMQ_HOST', '127.0.0.1'), - 'port' => env('RABBITMQ_PORT', 5672), + 'port' => $_port, 'vhost' => env('RABBITMQ_VHOST', '/'), 'login' => env('RABBITMQ_LOGIN', 'guest'), diff --git a/database/migrations/2023_06_29_110207_add_collaboration_uuid_to_process_requests_table.php b/database/migrations/2023_06_29_110207_add_collaboration_uuid_to_process_requests_table.php new file mode 100644 index 0000000000..fb8d5152f7 --- /dev/null +++ b/database/migrations/2023_06_29_110207_add_collaboration_uuid_to_process_requests_table.php @@ -0,0 +1,35 @@ +uuid('collaboration_uuid')->nullable()->after('process_collaboration_id'); + $table->index('collaboration_uuid', 'idx_collaboration_uuid'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('process_requests', function (Blueprint $table) { + // drop collaboration_uuid column + $table->dropColumn('collaboration_uuid'); + }); + } +} diff --git a/database/migrations/2023_06_29_110207_add_uuid_to_process_collaborations_table.php b/database/migrations/2023_06_29_110207_add_uuid_to_process_collaborations_table.php new file mode 100644 index 0000000000..3a34452b65 --- /dev/null +++ b/database/migrations/2023_06_29_110207_add_uuid_to_process_collaborations_table.php @@ -0,0 +1,35 @@ +uuid('uuid')->nullable()->after('id'); + $table->index('uuid', 'idx_uuid'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('process_collaborations', function (Blueprint $table) { + // drop collaboration_uuid column + $table->dropColumn('uuid'); + }); + } +} diff --git a/phpunit.xml b/phpunit.xml index 6628c79e14..3e5a0f996d 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -44,6 +44,7 @@ + diff --git a/routes/api.php b/routes/api.php index 7d8a24780a..40bda4153a 100644 --- a/routes/api.php +++ b/routes/api.php @@ -122,7 +122,7 @@ Route::get('processes', [ProcessController::class, 'index'])->name('processes.index')->middleware('can:view-processes'); Route::get('processes/{process}', [ProcessController::class, 'show'])->name('processes.show')->middleware('can:view-processes'); Route::post('processes/{process}/export', [ProcessController::class, 'export'])->name('processes.export')->middleware('can:export-processes'); - Route::get('processes/{process}/bpmn', [ProcessController::class, 'downloadBpmn'])->name('processes.export')->middleware('can:view-processes'); + Route::get('processes/{process}/bpmn', [ProcessController::class, 'downloadBpmn'])->name('processes.export.bpmn')->middleware('can:view-processes'); Route::post('processes/import', [ProcessController::class, 'import'])->name('processes.import')->middleware('can:import-processes'); Route::post('processes/import/validation', [ProcessController::class, 'preimportValidation'])->name('processes.preimportValidation')->middleware('can:import-processes'); Route::get('processes/import/{code}/is_ready', [ProcessController::class, 'import_ready'])->name('processes.import_is_ready')->middleware('can:import-processes'); diff --git a/upgrades/2023_06_29_110455_populate_process_requests_collaboration_uuid.php b/upgrades/2023_06_29_110455_populate_process_requests_collaboration_uuid.php new file mode 100644 index 0000000000..355ba01782 --- /dev/null +++ b/upgrades/2023_06_29_110455_populate_process_requests_collaboration_uuid.php @@ -0,0 +1,37 @@ +chunkById($batchSize, function ($collaborations) { + foreach ($collaborations as $collaboration) { + $collaboration->uuid = ProcessCollaboration::generateUuid(); + $collaboration->save(); + } + }); + + ProcessRequest::select(['id', 'process_collaboration_id', 'collaboration_uuid'])->whereNull('collaboration_uuid') + ->chunkById($batchSize, function ($requests) { + foreach ($requests as $request) { + if ($request->process_collaboration_id) { + $request->collaboration_uuid = $request->collaboration->uuid; + } else { + $request->collaboration_uuid = ProcessCollaboration::generateUuid(); + } + $request->save(); + } + }); + } +}