tao-test/app/taoQtiTest/models/classes/runner/communicator/QtiCommunicationService.php

201 lines
7.1 KiB
PHP

<?php
/**
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; under version 2
* of the License (non-upgradable).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright (c) 2016 (original work) Open Assessment Technologies SA ;
*/
/**
* @author Jean-Sébastien Conan <jean-sebastien.conan@vesperiagroup.com>
*/
namespace oat\taoQtiTest\models\runner\communicator;
use common_exception_InconsistentData;
use oat\oatbox\service\ConfigurableService;
use oat\taoQtiTest\models\runner\QtiRunnerServiceContext;
/**
* Class QtiCommunicationService
*
* Implements a bidirectional communication between client and server using polling
*
* @package oat\taoQtiTest\models\runner\communicator
*/
class QtiCommunicationService extends ConfigurableService implements CommunicationService
{
public const SERVICE_ID = 'taoQtiTest/QtiCommunicationService';
/**
* @deprecated use SERVICE_ID
*/
public const CONFIG_ID = 'taoQtiTest/QtiCommunicationService';
public const OPTION_CHANNELS = 'channels';
public const OPTION_LOG_INPUT = 'log_input';
/**
* Processes the input messages
* @param QtiRunnerServiceContext $context - Needs the current runner context
* @param array $input - Accept a list of input, each one is represented by a channel's name that is a string and a message that can be any type
* @return array - Returns a list of responses in the same order as the input list
* @throws common_exception_InconsistentData
*/
public function processInput(QtiRunnerServiceContext $context, array $input): array
{
$responses = [];
if ($this->islogInputEnabled()) {
$this->logInfo('Test runner message: ' . json_encode($input));
}
foreach ($input as $data) {
if (!is_array($data) || !isset($data['channel'], $data['message'])) {
throw new common_exception_InconsistentData('Wrong message chunk received by the bidirectional communication service: either channel or message content is missing!');
}
if ($this->hasChannel($data['channel'], self::CHANNEL_TYPE_INPUT)) {
$channel = $this->getChannel($data['channel'], self::CHANNEL_TYPE_INPUT);
// known channel, forward...
$responses[] = $this->processChannel($channel, $context, $data['message']);
} else {
// unknown channel, fallback!
$responses[] = $this->fallback($data['channel'], $context, $data['message']);
}
}
return $responses;
}
/**
* Builds the list of output messages
* @param QtiRunnerServiceContext $context - Needs the current runner context
* @return array - Returns a list of output, each one is represented by a channel's name that is a string and a message that can be any type
*/
public function processOutput(QtiRunnerServiceContext $context): array
{
$messages = [];
$channels = $this->getOption(self::OPTION_CHANNELS);
if (is_array($channels[self::CHANNEL_TYPE_OUTPUT])) {
foreach ($channels[self::CHANNEL_TYPE_OUTPUT] as $outputChannelName => $outputChannelClass) {
$channel = $this->getChannel($outputChannelName, self::CHANNEL_TYPE_OUTPUT);
$message = $this->processChannel($channel, $context);
if ($message !== null) {
$messages[] = [
'channel' => $channel->getName(),
'message' => $message,
];
}
}
}
return $messages;
}
/**
* @param CommunicationChannel $channel
* @param integer $channelType
* @throws common_exception_InconsistentData
*/
public function attachChannel(CommunicationChannel $channel, $channelType): void
{
if ($this->hasChannel($channel->getName(), $channelType)) {
throw new common_exception_InconsistentData('Channel ' . $channel->getName() . ' already registered in ' . __CLASS__);
}
$channels = $this->getOption(self::OPTION_CHANNELS);
$channels[$channelType][$channel->getName()] = get_class($channel);
$this->setOption(self::OPTION_CHANNELS, $channels);
}
/**
* @param CommunicationChannel $channel
* @param integer $channelType
* @throws common_exception_InconsistentData
*/
public function detachChannel(CommunicationChannel $channel, $channelType): void
{
if (!$this->hasChannel($channel->getName(), $channelType)) {
throw new common_exception_InconsistentData('Channel ' . $channel->getName() . 'is not registered in ' . __CLASS__);
}
$channels = $this->getOption(self::OPTION_CHANNELS);
unset($channels[$channelType][$channel->getName()]);
$this->setOption(self::OPTION_CHANNELS, $channels);
}
/**
* Check whether channel exists
* @param string $channelName
* @param integer $channelType
* @return bool
*/
protected function hasChannel($channelName, $channelType): bool
{
$channels = $this->getOption(self::OPTION_CHANNELS);
return isset($channels[$channelType][$channelName]);
}
/**
* @param string $channelName
* @param integer $channelType
* @return CommunicationChannel
*/
protected function getChannel($channelName, $channelType): CommunicationChannel
{
$channels = $this->getOption(self::OPTION_CHANNELS);
$channel = new $channels[$channelType][$channelName]();
$this->propagate($channel);
return $channel;
}
/**
* @return bool
*/
private function islogInputEnabled(): bool
{
if (!$this->hasOption(self::OPTION_LOG_INPUT)) {
return false;
}
return filter_var($this->getOption(self::OPTION_LOG_INPUT), FILTER_VALIDATE_BOOLEAN);
}
/**
* @param QtiRunnerServiceContext $context
* @param CommunicationChannel $channel
* @param array $data
* @return mixed channel response
*/
protected function processChannel(CommunicationChannel $channel, QtiRunnerServiceContext $context, array $data = [])
{
return $channel->process($context, $data);
}
/**
* Fallback for unknown channels
* @param QtiRunnerServiceContext $context
* @param string $channel
* @param mixed $message
* @return mixed
*/
protected function fallback($channel, QtiRunnerServiceContext $context, $message)
{
// do nothing by default, need to be overwritten
return null;
}
}