chg: [connectorTools] Tracking of connection request state and improved integration with outbox

pull/59/head
mokaddem 2021-06-21 14:30:32 +02:00
parent 19d837a91c
commit 2b8daf7960
16 changed files with 128 additions and 48 deletions

View File

@ -22,7 +22,7 @@ class BroodInboxProcessor extends GenericInboxProcessor
}
}
class ToolInterconnectionProcessor extends BroodInboxProcessor implements GenericProcessorActionI {
class ToolInterconnectionProcessor extends BroodInboxProcessor implements GenericInboxProcessorActionI {
public $action = 'ToolInterconnection';
protected $description;

View File

@ -5,7 +5,7 @@ use Cake\Utility\Inflector;
use Cake\Validation\Validator;
use Cake\View\ViewBuilder;
interface GenericProcessorActionI
interface GenericInboxProcessorActionI
{
public function create($requestData);
public function process($requestID, $serverRequest, $inboxRequest);

View File

@ -145,11 +145,13 @@ class LocalToolInboxProcessor extends GenericInboxProcessor
'remote_tool' => [
'id' => $remote_tool_id,
'connector' => $connector->connectorName,
'name' => $requestData['tool_name'],
],
'remote_org' => $remote_org,
'remote_tool_data' => $requestData,
'remote_cerebrate' => $remoteCerebrate,
'connection' => $connection,
'connector' => [$connector->connectorName => $connector],
];
}
@ -171,7 +173,7 @@ class LocalToolInboxProcessor extends GenericInboxProcessor
}
}
class IncomingConnectionRequestProcessor extends LocalToolInboxProcessor implements GenericProcessorActionI {
class IncomingConnectionRequestProcessor extends LocalToolInboxProcessor implements GenericInboxProcessorActionI {
public $action = 'IncomingConnectionRequest';
protected $description;
@ -212,7 +214,7 @@ class IncomingConnectionRequestProcessor extends LocalToolInboxProcessor impleme
$connector = $this->getConnector($inboxRequest);
if (!empty($requestData['is_discard'])) { // -> declined
$connectorResult = $this->declineConnection($connector, $remoteCerebrate, $inboxRequest['data']); // Fire-and-forget?
$connectionSuccessfull = true;
$connectionSuccessfull = !empty($connectorResult['success']);
$resultTitle = __('Could not sent declined message to `{0}`\'s for {1}', $inboxRequest['origin'], $inboxRequest['local_tool_name']);
$errors = [];
if ($connectionSuccessfull) {
@ -222,16 +224,20 @@ class IncomingConnectionRequestProcessor extends LocalToolInboxProcessor impleme
} else {
$errors = [];
$connectorResult = [];
$thrownErrorMessage = '';
try {
$connectorResult = $this->acceptConnection($connector, $remoteCerebrate, $inboxRequest['data']);
$connectionSuccessfull = true;
$connectionSuccessfull = !empty($connectorResult['success']);
} catch (\Throwable $th) {
$connectionSuccessfull = false;
$errors = $th->getMessage();
$thrownErrorMessage = $th->getMessage();
}
$resultTitle = __('Could not inter-connect `{0}`\'s {1}', $inboxRequest['origin'], $inboxRequest['local_tool_name']);
$resultTitle = $connectorResult['message'] ?? __('Could not inter-connect `{0}`\'s {1}', $inboxRequest['origin'], $inboxRequest['local_tool_name']);
$errors = $connectorResult['errors'] ?? $thrownErrorMessage;
if ($connectionSuccessfull) {
$resultTitle = __('Interconnection for `{0}`\'s {1} created', $inboxRequest['origin'], $inboxRequest['local_tool_name']);
}
if ($connectionSuccessfull || !empty($connectorResult['placed_in_outbox'])) {
$this->discard($id, $inboxRequest);
}
}
@ -252,7 +258,7 @@ class IncomingConnectionRequestProcessor extends LocalToolInboxProcessor impleme
{
$connection = $this->getConnection($requestData);
$params = $this->genBroodParam($remoteCerebrate, $connection, $connector, $requestData);
$connectorResult = $connector->acceptConnection($params);
$connectorResult = $connector->acceptConnectionWrapper($params);
$response = $this->sendAcceptedRequestToRemote($params, $connectorResult);
return $response;
}
@ -261,7 +267,7 @@ class IncomingConnectionRequestProcessor extends LocalToolInboxProcessor impleme
{
$connection = $this->getConnection($requestData);
$params = $this->genBroodParam($remoteCerebrate, $connection, $connector, $requestData);
$connectorResult = $connector->declineConnection($params);
$connectorResult = $connector->declineConnectionWrapper($params);
$response = $this->sendDeclinedRequestToRemote($params, $connectorResult);
return $response;
}
@ -269,21 +275,17 @@ class IncomingConnectionRequestProcessor extends LocalToolInboxProcessor impleme
protected function sendAcceptedRequestToRemote($params, $connectorResult)
{
$response = $this->Broods->sendLocalToolAcceptedRequest($params, $connectorResult);
// change state if sending fails
// add the entry to the outbox if sending fails.
return $response->getJson();
return $response;
}
protected function sendDeclinedRequestToRemote($remoteCerebrate, $connectorResult)
{
$response = $this->Broods->sendLocalToolDeclinedRequest($params, $connectorResult);
// change state if sending fails
// add the entry to the outbox if sending fails.
return $response->getJson();
return $response;
}
}
class AcceptedRequestProcessor extends LocalToolInboxProcessor implements GenericProcessorActionI {
class AcceptedRequestProcessor extends LocalToolInboxProcessor implements GenericInboxProcessorActionI {
public $action = 'AcceptedRequest';
protected $description;
@ -321,21 +323,22 @@ class AcceptedRequestProcessor extends LocalToolInboxProcessor implements Generi
$errors = [];
$connectorResult = [];
$thrownErrorMessage = '';
try {
$connectorResult = $this->finalizeConnection($connector, $remoteCerebrate, $inboxRequest['data']);
$connectionSuccessfull = true;
$connectorResult = $this->finaliseConnection($connector, $remoteCerebrate, $inboxRequest['data']);
$connectionSuccessfull = !empty($connectorResult['success']);
} catch (\Throwable $th) {
$connectionSuccessfull = false;
$errors = $th->getMessage();
}
$connectionData = [];
$resultTitle = __('Could not finalize inter-connection for `{0}`\'s {1}', $inboxRequest['origin'], $inboxRequest['local_tool_name']);
$errors = $connectorResult['errors'] ?? $thrownErrorMessage;
if ($connectionSuccessfull) {
$resultTitle = __('Interconnection for `{0}`\'s {1} finalized', $inboxRequest['origin'], $inboxRequest['local_tool_name']);
$this->discard($id, $requestData);
}
return $this->genActionResult(
$connectionData,
$connectorResult,
$connectionSuccessfull,
$resultTitle,
$errors
@ -347,16 +350,18 @@ class AcceptedRequestProcessor extends LocalToolInboxProcessor implements Generi
return parent::discard($id, $requestData);
}
protected function finalizeConnection($connector, $remoteCerebrate, $requestData)
protected function finaliseConnection($connector, $remoteCerebrate, $requestData)
{
$connection = $this->getConnection($requestData);
$params = $this->genBroodParam($remoteCerebrate, $connection, $connector, $requestData);
$connectorResult = $connector->finaliseConnection($params);
return $connectorResult;
$connectorResult = $connector->finaliseConnectionWrapper($params);
return [
'success' => true
];
}
}
class DeclinedRequestProcessor extends LocalToolInboxProcessor implements GenericProcessorActionI {
class DeclinedRequestProcessor extends LocalToolInboxProcessor implements GenericInboxProcessorActionI {
public $action = 'DeclinedRequest';
protected $description;

View File

@ -22,7 +22,7 @@ class ProposalInboxProcessor extends GenericInboxProcessor
}
}
class ProposalEditProcessor extends ProposalInboxProcessor implements GenericProcessorActionI {
class ProposalEditProcessor extends ProposalInboxProcessor implements GenericInboxProcessorActionI {
public $action = 'ProposalEdit';
protected $description;

View File

@ -22,7 +22,7 @@ class SynchronisationInboxProcessor extends GenericInboxProcessor
}
}
class DataExchangeProcessor extends SynchronisationInboxProcessor implements GenericProcessorActionI {
class DataExchangeProcessor extends SynchronisationInboxProcessor implements GenericInboxProcessorActionI {
public $action = 'DataExchange';
protected $description;

View File

@ -22,7 +22,7 @@ class SCOPE_InboxProcessor extends GenericInboxProcessor
}
}
class SCOPE_ACTION_Processor extends ProposalInboxProcessor implements GenericProcessorActionI {
class SCOPE_ACTION_Processor extends ProposalInboxProcessor implements GenericInboxProcessorActionI {
public $action = 'ACTION';
protected $description;

View File

@ -24,7 +24,7 @@ class UserInboxProcessor extends GenericInboxProcessor
}
}
class RegistrationProcessor extends UserInboxProcessor implements GenericProcessorActionI {
class RegistrationProcessor extends UserInboxProcessor implements GenericInboxProcessorActionI {
public $action = 'Registration';
protected $description;

View File

@ -97,7 +97,7 @@ $form = $this->element('genericElements/Form/genericForm', [
]
]
]);
$localToolHTML = $this->fetch('content', sprintf('<div class="d-none">%s</div>', $form));
$localToolHTML = $this->fetch('content', sprintf('<div class="d-none">%s</div><div class="form-error-container"></div>', $form));;
$requestData = $this->Bootstrap->collapse(
[
@ -129,7 +129,12 @@ echo $this->Bootstrap->modal([
<script>
function accept(modalObject, tmpApi) {
const $form = modalObject.$modal.find('form')
return tmpApi.postForm($form[0])
return tmpApi.postForm($form[0]).catch((errors) => {
const formHelper = new FormValidationHelper($form[0])
const errorHTMLNode = formHelper.buildValidationMessageNode(errors, true)
modalObject.$modal.find('div.form-error-container').append(errorHTMLNode)
return errors
})
}
function discard(modalObject, tmpApi) {
const $form = modalObject.$modal.find('form')

View File

@ -18,4 +18,4 @@ $form = $this->element('genericElements/Form/genericForm', [
]
]
]);
echo sprintf('<div class="d-none">%s</div>', $form);
echo sprintf('<div class="d-none">%s</div><div class="form-error-container"></div>', $form);

View File

@ -36,9 +36,34 @@ class BroodsOutboxProcessor extends GenericOutboxProcessor
->first();
return $tool;
}
protected function getConnector($className)
{
try {
$connectorClasses = $this->LocalTools->getConnectors($className);
if (!empty($connectorClasses)) {
$connector = array_values($connectorClasses)[0];
}
} catch (NotFoundException $e) {
$connector = null;
}
return $connector;
}
protected function setRemoteToolConnectionStatus(Object $brood, Object $outboxRequest, String $status): void
{
$connector = $this->getConnector($outboxRequest->data['remote_tool']['connector']);
$connection = $this->getLocalTool($outboxRequest->data['local_tool_id']);
$connectorParams = [
'connection' => $connection,
'remote_tool' => $outboxRequest->data['remote_tool'],
'remote_cerebrate' => $brood,
];
$connector->remoteToolConnectionStatus($connectorParams, constant(get_class($connector) . '::' . $status));
}
}
class ResendFailedMessageProcessor extends BroodsOutboxProcessor implements GenericProcessorActionI {
class ResendFailedMessageProcessor extends BroodsOutboxProcessor implements GenericOutboxProcessorActionI {
public $action = 'ResendFailedMessage';
protected $description;
@ -74,12 +99,13 @@ class ResendFailedMessageProcessor extends BroodsOutboxProcessor implements Gene
public function process($id, $requestData, $outboxRequest)
{
$brood = $this->getIssuerBrood((int) $outboxRequest->data['brood_id']);
if (!empty($requestData['is_delete'])) { // -> declined
$success = true;
$messageSucess = __('Message successfully deleted');
$messageFail = '';
$this->setRemoteToolConnectionStatus($brood, $outboxRequest, 'STATE_CANCELLED');
} else {
$brood = $this->getIssuerBrood((int) $outboxRequest->data['brood_id']);
$url = $outboxRequest->data['url'];
$dataSent = $outboxRequest->data['sent'];
$dataSent['connectorName'] = 'MispConnector';
@ -88,6 +114,11 @@ class ResendFailedMessageProcessor extends BroodsOutboxProcessor implements Gene
$success = !empty($jsonReply['success']);
$messageSuccess = __('Message successfully sent to `{0}`', $brood->name);
$messageFail = __('Could not send message to `{0}`.', $brood->name);
if ($success) {
$this->setRemoteToolConnectionStatus($brood, $outboxRequest, $outboxRequest->data['next_connector_state']);
} else {
$this->setRemoteToolConnectionStatus($brood, $outboxRequest, 'STATE_SENDING_ERROR');
}
}
if ($success) {
$this->discard($id, $requestData);

View File

@ -6,7 +6,7 @@ use Cake\Validation\Validator;
use Cake\View\ViewBuilder;
use Cake\Routing\Router;
interface GenericProcessorActionI
interface GenericOutboxProcessorActionI
{
public function create($requestData);
public function process($requestID, $serverRequest, $outboxRequest);

View File

@ -22,7 +22,7 @@ class SCOPE_OutboxProcessor extends GenericOutboxProcessor
}
}
class SCOPE_ACTION_Processor extends ProposalOutboxProcessor implements GenericProcessorActionI {
class SCOPE_ACTION_Processor extends ProposalOutboxProcessor implements GenericOutboxProcessorActionI {
public $action = 'ACTION';
protected $description;

View File

@ -31,7 +31,7 @@ $tools = sprintf(
sprintf('<i class="%s fa-lg"></i>', $this->FontAwesome->getClass('long-arrow-alt-right')),
sprintf('<span class="ml-2 d-inline-flex flex-column"><a href="%s" target="_blank" title="%s">%s</a><i style="font-size: medium;" class="text-center">%s</i></span>',
sprintf('/localTools/broodTools/%s', h($request['data']['remote_tool']['id'])),
h($request['data']['remote_tool']['description']),
h($request['data']['remote_tool']['description'] ?? ''),
h($request['data']['remote_tool']['name']),
__('(remote tool)')
)

View File

@ -16,6 +16,9 @@ class CommonConnectorTools
const STATE_INITIAL = 'Request issued';
const STATE_ACCEPT = 'Request accepted';
const STATE_CONNECTED = 'Connected';
const STATE_SENDING_ERROR = 'Error while sending request';
const STATE_CANCELLED = 'Request cancelled';
const STATE_DECLINED = 'Request declined by remote';
public function addExposedFunction(string $functionName): void
{

View File

@ -221,6 +221,7 @@ class BroodsTable extends AppTable
$data['cerebrateURL'] = Configure::read('App.fullBaseUrl');
$data['local_tool_id'] = $params['connection']['id'];
$data['remote_tool_id'] = $params['remote_tool']['id'];
$data['tool_name'] = $params['connection']['name'];
return $data;
}
@ -232,26 +233,46 @@ class BroodsTable extends AppTable
$response = $this->sendRequest($params['remote_cerebrate'], $url, true, $data);
$jsonReply = $response->getJson();
if (empty($jsonReply['success'])) {
$jsonReply = $this->handleMessageNotCreated($params['remote_cerebrate'], $url, $data, 'LocalTool', 'IncomingConnectionRequest', $response, $params);
$jsonReply = $this->handleMessageNotCreated($params['remote_cerebrate'], $url, $data, 'LocalTool', 'IncomingConnectionRequest', $response, $params, 'STATE_INITIAL');
}
} catch (NotFoundException $e) {
$jsonReply = $this->handleSendingFailed($params['remote_cerebrate'], $url, $data, 'LocalTool', 'IncomingConnectionRequest', $e, $params);
$jsonReply = $this->handleSendingFailed($params['remote_cerebrate'], $url, $data, 'LocalTool', 'IncomingConnectionRequest', $e, $params, 'STATE_INITIAL');
}
return $jsonReply;
}
public function sendLocalToolAcceptedRequest($params, $data): Response
public function sendLocalToolAcceptedRequest($params, $data): array
{
$url = '/inbox/createEntry/LocalTool/AcceptedRequest';
$data = $this->injectRequiredData($params, $data);
return $this->sendRequest($params['remote_cerebrate'], $url, true, $data);
try {
$response = $this->sendRequest($params['remote_cerebrate'], $url, true, $data);
$jsonReply = $response->getJson();
if (empty($jsonReply['success'])) {
$jsonReply = $this->handleMessageNotCreated($params['remote_cerebrate'], $url, $data, 'LocalTool', 'AcceptedRequest', $response, $params, 'STATE_CONNECTED');
} else {
$this->setRemoteToolConnectionStatus($params, 'STATE_CONNECTED');
}
} catch (NotFoundException $e) {
$jsonReply = $this->handleSendingFailed($params['remote_cerebrate'], $url, $data, 'LocalTool', 'AcceptedRequest', $e, $params, 'STATE_CONNECTED');
}
return $jsonReply;
}
public function sendLocalToolDeclinedRequest($params, $data): Response
public function sendLocalToolDeclinedRequest($params, $data): array
{
$url = '/inbox/createEntry/LocalTool/DeclinedRequest';
$data = $this->injectRequiredData($params, $data);
return $this->sendRequest($params['remote_cerebrate'], $url, true, $data);
try {
$response = $this->sendRequest($params['remote_cerebrate'], $url, true, $data);
$jsonReply = $response->getJson();
if (empty($jsonReply['success'])) {
$jsonReply = $this->handleMessageNotCreated($params['remote_cerebrate'], $url, $data, 'LocalTool', 'AcceptedRequest', $response, $params, 'STATE_DECLINED');
}
} catch (NotFoundException $e) {
$jsonReply = $this->handleSendingFailed($params['remote_cerebrate'], $url, $data, 'LocalTool', 'AcceptedRequest', $e, $params, 'STATE_DECLINED');
}
return $jsonReply;
}
/**
@ -260,17 +281,20 @@ class BroodsTable extends AppTable
* @param Object $response
* @return array
*/
private function handleSendingFailed($brood, $url, $data, $model, $action, $e, $params): array
private function handleSendingFailed($brood, $url, $data, $model, $action, $e, $params, $next_connector_state): array
{
$connector = $params['connector'][$params['remote_tool']['connector']];
$reason = [
'message' => __('Failed to send message to remote cerebrate. It has been placed in the outbox.'),
'errors' => [$e->getMessage()],
];
$this->saveErrorInOutbox($brood, $url, $data, $reasonMessage, $params);
$outboxSaveResult = $this->saveErrorInOutbox($brood, $url, $data, $reasonMessage, $params, $next_connector_state);
$connector->remoteToolConnectionStatus($params, $connector::STATE_SENDING_ERROR);
$creationResult = [
'success' => false,
'message' => $reason['message'],
'errors' => $reason['errors'],
'placed_in_outbox' => !empty($outboxSaveResult['success']),
];
return $creationResult;
}
@ -281,8 +305,9 @@ class BroodsTable extends AppTable
* @param Object $response
* @return array
*/
private function handleMessageNotCreated($brood, $url, $data, $model, $action, $response, $params): array
private function handleMessageNotCreated($brood, $url, $data, $model, $action, $response, $params, $next_connector_state): array
{
$connector = $params['connector'][$params['remote_tool']['connector']];
$responseErrors = $response->getStringBody();
if (!is_null($response->getJson())) {
$responseErrors = $response->getJson()['errors'] ?? $response->getJson()['message'];
@ -291,16 +316,18 @@ class BroodsTable extends AppTable
'message' => __('Message rejected by the remote cerebrate. It has been placed in the outbox.'),
'errors' => [$responseErrors],
];
$this->saveErrorInOutbox($brood, $url, $data, $reason, $model, $action, $params);
$outboxSaveResult = $this->saveErrorInOutbox($brood, $url, $data, $reason, $model, $action, $params, $next_connector_state);
$connector->remoteToolConnectionStatus($params, $connector::STATE_SENDING_ERROR);
$creationResult = [
'success' => false,
'message' => $reason['message'],
'errors' => $reason['errors'],
'placed_in_outbox' => !empty($outboxSaveResult['success']),
];
return $creationResult;
}
private function saveErrorInOutbox($brood, $url, $data, $reason, $model, $action, $params): array
private function saveErrorInOutbox($brood, $url, $data, $reason, $model, $action, $params, $next_connector_state): array
{
$this->OutboxProcessors = TableRegistry::getTableLocator()->get('OutboxProcessors');
$processor = $this->OutboxProcessors->getProcessor('Broods', 'ResendFailedMessage');
@ -312,6 +339,7 @@ class BroodsTable extends AppTable
'reason' => $reason,
'local_tool_id' => $params['connection']['id'],
'remote_tool' => $params['remote_tool'],
'next_connector_state' => $next_connector_state,
],
'brood' => $brood,
'model' => $model,
@ -320,4 +348,10 @@ class BroodsTable extends AppTable
$creationResult = $processor->create($entryData);
return $creationResult;
}
private function setRemoteToolConnectionStatus($params, String $status): void
{
$connector = $params['connector'][$params['remote_tool']['connector']];
$connector->remoteToolConnectionStatus($params, constant(get_class($connector) . '::' . $status));
}
}

View File

@ -25,7 +25,9 @@ class OutboxProcessorsTable extends AppTable
public function initialize(array $config): void
{
parent::initialize($config);
$this->loadProcessors();
if (empty($this->outboxProcessors)) {
$this->loadProcessors();
}
}
public function getProcessor($scope, $action=null)