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();
+ }
+ });
+ }
+}