Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions ProcessMaker/Exception/ConfigurationException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace ProcessMaker\Exception;

use Exception;
use ProcessMaker\Models\ProcessRequestToken;

class ConfigurationException extends Exception
{
public function __construct($message)
{
if (config('app.configuration_debug_mode')) {
throw new Exception($message);
} else {
parent::__construct($message);
}
}

public function getMessageForData(ProcessRequestToken $token)
{
$message = $token->element_name . ' (' . $token->element_id . '): ' . $this->getMessage();

return [
'_configuration_error_' . $token->element_id => $message,
];
}
}
27 changes: 18 additions & 9 deletions ProcessMaker/Jobs/ErrorHandling.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,16 @@ public function __construct(
public function handleRetries($job, $exception)
{
$message = $exception->getMessage();
$finalAttempt = true;

if ($this->retryAttempts() > 0) {
if ($job->attemptNum <= $this->retryAttempts()) {
Log::info('Retry the job process. Attempt ' . $job->attemptNum . ' of ' . $this->retryAttempts() . ', Wait time: ' . $this->retryWaitTime());
$this->requeue($job);

return $message;
$finalAttempt = false;

return [$message, $finalAttempt];
}

$message = __('Job failed after :attempts total attempts', ['attempts' => $job->attemptNum]) . "\n" . $message;
Expand All @@ -59,20 +62,26 @@ public function handleRetries($job, $exception)
$this->sendExecutionErrorNotification($message);
}

return $message;
return [$message, $finalAttempt];
}

private function requeue($job)
{
$class = get_class($job);
$newJob = new $class(
Process::findOrFail($job->definitionsId),
ProcessRequest::findOrFail($job->instanceId),
ProcessRequestToken::findOrFail($job->tokenId),
$job->data,
$job->attemptNum + 1
);
if ($job instanceof RunNayraServiceTask) {
$newJob = new RunNayraServiceTask($this->processRequestToken);
$newJob->attemptNum = $job->attemptNum + 1;
} else {
$newJob = new $class(
Process::findOrFail($job->definitionsId),
ProcessRequest::findOrFail($job->instanceId),
ProcessRequestToken::findOrFail($job->tokenId),
$job->data,
$job->attemptNum + 1
);
}
$newJob->delay($this->retryWaitTime());
$newJob->onQueue('bpmn');
dispatch($newJob);
}

Expand Down
12 changes: 10 additions & 2 deletions ProcessMaker/Jobs/RunNayraScriptTask.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use ProcessMaker\Exception\ConfigurationException;
use ProcessMaker\Exception\ScriptException;
use ProcessMaker\Facades\WorkflowManager;
use ProcessMaker\Managers\DataManager;
Expand Down Expand Up @@ -78,7 +79,7 @@ public function handle()
if (empty($scriptRef)) {
$code = $element->getScript();
if (empty($code)) {
throw new ScriptException(__('No code or script assigned to ":name"', ['name' => $element->getName()]));
throw new ConfigurationException(__('No code or script assigned to ":name"', ['name' => $element->getName()]));
}
$language = Script::scriptFormat2Language($element->getProperty('scriptFormat', 'application/x-php'));
$script = new Script([
Expand All @@ -88,7 +89,11 @@ public function handle()
'script_executor_id' => ScriptExecutor::initialExecutor($language)->id,
]);
} else {
$script = Script::findOrFail($scriptRef)->versionFor($instance);
$script = Script::find($scriptRef);
if (!$script) {
throw new ConfigurationException(__('Script ":id" not found', ['id' => $scriptRef]));
}
$script = $script->versionFor($instance);
}

$dataManager = new DataManager();
Expand All @@ -97,6 +102,9 @@ public function handle()

// Dispatch complete task action
WorkflowManager::completeTask($processModel, $instance, $token, $response['output']);
} catch (ConfigurationException $exception) {
$output = $exception->getMessageForData($token);
WorkflowManager::completeTask($processModel, $instance, $token, $output);
} catch (Throwable $exception) {
Log::error('Script failed: ' . $scriptRef . ' - ' . $exception->getMessage());
Log::error($exception->getTraceAsString());
Expand Down
6 changes: 3 additions & 3 deletions ProcessMaker/Jobs/RunNayraServiceTask.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ class RunNayraServiceTask implements ShouldQueue

public $tokenId;

public $userId;

public $attemptNum = 1;
/**
* Create a new job instance.
*
Expand All @@ -48,6 +48,6 @@ public function handle()
$token->setInstance($instance);

// Run service task
WorkflowManager::handleServiceTask($token);
WorkflowManager::handleServiceTask($token, $this);
}
}
61 changes: 39 additions & 22 deletions ProcessMaker/Jobs/RunScriptTask.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
namespace ProcessMaker\Jobs;

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Facades\Log;
use ProcessMaker\Exception\ConfigurationException;
use ProcessMaker\Exception\ScriptException;
use ProcessMaker\Facades\WorkflowManager;
use ProcessMaker\Managers\DataManager;
Expand Down Expand Up @@ -70,7 +72,7 @@ public function action(ProcessRequestToken $token = null, ScriptTaskInterface $e
if (empty($scriptRef)) {
$code = $element->getScript();
if (empty($code)) {
throw new ScriptException(__('No code or script assigned to ":name"', ['name' => $element->getName()]));
throw new ConfigurationException(__('No code or script assigned to ":name"', ['name' => $element->getName()]));
}
$language = Script::scriptFormat2Language($element->getProperty('scriptFormat', 'application/x-php'));
$script = new Script([
Expand All @@ -80,7 +82,11 @@ public function action(ProcessRequestToken $token = null, ScriptTaskInterface $e
'script_executor_id' => ScriptExecutor::initialExecutor($language)->id,
]);
} else {
$script = Script::findOrFail($scriptRef)->versionFor($instance);
$script = Script::find($scriptRef);
if (!$script) {
throw new ConfigurationException(__('Script ":id" not found', ['id' => $scriptRef]));
}
$script = $script->versionFor($instance);
}

$errorHandling = new ErrorHandling($element, $token);
Expand All @@ -91,29 +97,19 @@ public function action(ProcessRequestToken $token = null, ScriptTaskInterface $e
$data = $dataManager->getData($token);
$response = $script->runScript($data, $configuration, $token->getId(), $errorHandling->timeout());

$this->withUpdatedContext(function ($engine, $instance, $element, $processModel, $token) use ($response) {
// Exit if the task was completed or closed
if (!$token || !$element) {
return;
}
// Update data
if (is_array($response['output'])) {
// Validate data
WorkflowManager::validateData($response['output'], $processModel, $element);
$dataManager = new DataManager();
$dataManager->updateData($token, $response['output']);
$engine->runToNextState();
}
$element->complete($token);
$this->engine = $engine;
$this->instance = $instance;
});
$this->updateData($response);
} catch (ConfigurationException $exception) {
$this->unlock();
$this->updateData(['output' => $exception->getMessageForData($token)]);
} catch (Throwable $exception) {
$token->setStatus(ScriptTaskInterface::TOKEN_STATE_FAILING);

$message = $exception->getMessage();
$finalAttempt = true;
if ($errorHandling) {
$message = $errorHandling->handleRetries($this, $exception);
[$message, $finalAttempt] = $errorHandling->handleRetries($this, $exception);
}

if ($finalAttempt) {
$token->setStatus(ScriptTaskInterface::TOKEN_STATE_FAILING);
}

$error = $element->getRepository()->createError();
Expand All @@ -129,6 +125,27 @@ public function action(ProcessRequestToken $token = null, ScriptTaskInterface $e
}
}

private function updateData($response)
{
$this->withUpdatedContext(function ($engine, $instance, $element, $processModel, $token) use ($response) {
// Exit if the task was completed or closed
if (!$token || !$element) {
return;
}
// Update data
if (is_array($response['output'])) {
// Validate data
WorkflowManager::validateData($response['output'], $processModel, $element);
$dataManager = new DataManager();
$dataManager->updateData($token, $response['output']);
$engine->runToNextState();
}
$element->complete($token);
$this->engine = $engine;
$this->instance = $instance;
});
}

/**
* When Job fails
*/
Expand Down
55 changes: 35 additions & 20 deletions ProcessMaker/Jobs/RunServiceTask.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use ProcessMaker\Exception\ConfigurationException;
use ProcessMaker\Exception\ScriptException;
use ProcessMaker\Facades\WorkflowManager;
use ProcessMaker\Managers\DataManager;
Expand Down Expand Up @@ -73,6 +74,7 @@ public function action(ProcessRequestToken $token = null, ServiceTaskInterface $
if ($configuration === null) {
$configuration = [];
}
$errorHandling = null;
try {
if (empty($implementation)) {
throw new ScriptException('Service task implementation not defined');
Expand Down Expand Up @@ -104,28 +106,20 @@ public function action(ProcessRequestToken $token = null, ServiceTaskInterface $
} else {
$response = $script->runScript($data, $configuration, $token->getId(), $errorHandling->timeout());
}
$this->withUpdatedContext(function ($engine, $instance, $element, $processModel, $token) use ($response) {
// Exit if the task was completed or closed
if (!$token || !$element) {
return;
}
// Update data
if (is_array($response['output'])) {
// Validate data
WorkflowManager::validateData($response['output'], $processModel, $element);
$dataManager = new DataManager();
$dataManager->updateData($token, $response['output']);
$engine->runToNextState();
}
$element->complete($token);
$this->engine = $engine;
$this->instance = $instance;
});
$this->updateData($response);
} catch (ConfigurationException $exception) {
$this->unlock();
$this->updateData(['output' => $exception->getMessageForData($token)]);
} catch (Throwable $exception) {
// Change to error status
$token->setStatus(ServiceTaskInterface::TOKEN_STATE_FAILING);
$finalAttempt = true;
if ($errorHandling) {
[$message, $finalAttempt] = $errorHandling->handleRetries($this, $exception);
}

$message = $errorHandling->handleRetries($this, $exception);
if ($finalAttempt) {
// Change to error status
$token->setStatus(ServiceTaskInterface::TOKEN_STATE_FAILING);
}

$error = $element->getRepository()->createError();
$error->setName($message);
Expand All @@ -140,6 +134,27 @@ public function action(ProcessRequestToken $token = null, ServiceTaskInterface $
}
}

private function updateData($response)
{
$this->withUpdatedContext(function ($engine, $instance, $element, $processModel, $token) use ($response) {
// Exit if the task was completed or closed
if (!$token || !$element) {
return;
}
// Update data
if (is_array($response['output'])) {
// Validate data
WorkflowManager::validateData($response['output'], $processModel, $element);
$dataManager = new DataManager();
$dataManager->updateData($token, $response['output']);
$engine->runToNextState();
}
$element->complete($token);
$this->engine = $engine;
$this->instance = $instance;
});
}

/**
* When Job fails
*/
Expand Down
3 changes: 2 additions & 1 deletion ProcessMaker/Models/Script.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Illuminate\Validation\Rule;
use ProcessMaker\Contracts\ScriptInterface;
use ProcessMaker\Exception\ConfigurationException;
use ProcessMaker\Exception\ScriptLanguageNotSupported;
use ProcessMaker\Models\ScriptCategory;
use ProcessMaker\Models\User;
Expand Down Expand Up @@ -140,7 +141,7 @@ public function runScript(array $data, array $config, $tokenId = '', $timeout =
$runner->setTokenId($tokenId);
$user = User::find($this->run_as_user_id);
if (!$user) {
throw new \RuntimeException('A user is required to run scripts');
throw new ConfigurationException('A user is required to run scripts');
}

return $runner->run($this->code, $data, $config, $timeout, $user);
Expand Down
Loading