tao-test/app/tao/models/classes/actionQueue/implementation/InstantActionQueue.php

269 lines
8.6 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) 2018 (original work) Open Assessment Technologies SA;
*
*
*/
namespace oat\tao\model\actionQueue\implementation;
use oat\oatbox\event\EventManager;
use oat\oatbox\session\SessionService;
use oat\tao\model\actionQueue\ActionQueue;
use oat\oatbox\service\ConfigurableService;
use oat\tao\model\actionQueue\QueuedAction;
use oat\tao\model\actionQueue\ActionQueueException;
use oat\oatbox\user\User;
use oat\tao\model\actionQueue\restriction\BasicRestriction;
use oat\tao\model\actionQueue\event\InstantActionOnQueueEvent;
use oat\tao\model\actionQueue\event\ActionQueueTrendEvent;
/**
*
* Interface InstantActionQueue
* @author Aleh Hutnikau, <hutnikau@1pt.com>
* @package oat\tao\model\actionQueue
*/
class InstantActionQueue extends ConfigurableService implements ActionQueue
{
const QUEUE_TREND = 'queue_trend';
/**
* @return EventManager
*/
protected function getEventManager()
{
return $this->getServiceLocator()->get(EventManager::SERVICE_ID);
}
/**
* @param QueuedAction $action
* @param User $user
* @return boolean
* @throws
*/
public function perform(QueuedAction $action, User $user = null)
{
$this->propagate($action);
if ($user === null) {
$user = $this->getServiceLocator()->get(SessionService::SERVICE_ID)->getCurrentUser();
}
$result = false;
$actionConfig = $this->getActionConfig($action);
$restrictions = $this->getRestrictions($actionConfig);
$allowExecution = $this->checkRestrictions($restrictions);
if ($allowExecution) {
$actionResult = $action([]);
$action->setResult($actionResult);
$result = true;
$this->dequeue($action, $user);
} else {
$this->queue($action, $user);
}
return $result;
}
/**
* Note that this method is not transaction safe so there may be collisions.
* This implementation supposed to provide approximate position in the queue
* @param QueuedAction $action
* @param User $user
* @return integer
* @throws
*/
public function getPosition(QueuedAction $action, User $user = null)
{
$action->setServiceLocator($this->getServiceManager());
$positions = $this->getPositions($action);
return count($positions);
}
/**
* @inheritdoc
*/
public function clearAbandonedPositions(QueuedAction $action)
{
$action->setServiceLocator($this->getServiceManager());
$key = $this->getQueueKey($action);
$positions = $this->getPositions($action);
$edgeTime = time() - $this->getTtl($action);
$newPositions = array_filter($positions, function ($val) use ($edgeTime) {
return $val > $edgeTime;
});
$this->getPersistence()->set($key, json_encode($newPositions));
$this->getPersistence()->set(get_class($action) . self::QUEUE_TREND, 0);
return count($positions) - count($newPositions);
}
/**
* @return int
*/
public function getTrend(QueuedAction $action)
{
$trend = $this->getPersistence()->get(get_class($action) . self::QUEUE_TREND);
return (int)$trend;
}
/**
* @param QueuedAction $action
* @return bool
* @throws ActionQueueException
*/
public function isActionEnabled(QueuedAction $action): bool
{
$actionConfig = $this->getActionConfig($action);
foreach ($this->getRestrictions($actionConfig) as $restriction => $value) {
if ($value !== 0) {
return true;
}
}
return false;
}
/**
* @param QueuedAction $action
* @param User $user
* @throws \common_Exception
*/
protected function queue(QueuedAction $action, User $user)
{
$key = $this->getQueueKey($action);
$positions = $this->getPositions($action);
$positions[$user->getIdentifier()] = time();
$this->getPersistence()->set($key, json_encode($positions));
$this->getEventManager()->trigger(new InstantActionOnQueueEvent($key, $user, $positions, 'queue', $action));
if ($this->getTrend($action) >= 0) {
$this->getEventManager()->trigger(new ActionQueueTrendEvent($action, true));
$this->getPersistence()->set(get_class($action) . self::QUEUE_TREND, -1);
}
}
/**
* @param QueuedAction $action
* @param User $user
* @throws \common_Exception
*/
protected function dequeue(QueuedAction $action, User $user)
{
$key = $this->getQueueKey($action);
$positions = $this->getPositions($action);
if (array_key_exists($user->getIdentifier(), $positions)) {
// now we sure that this user has been queued
unset($positions[$user->getIdentifier()]);
$this->getEventManager()->trigger(new InstantActionOnQueueEvent($key, $user, $positions, 'dequeue', $action));
$this->getPersistence()->set($key, json_encode($positions));
if ($this->getTrend($action) <= 0) {
$this->getEventManager()->trigger(new ActionQueueTrendEvent($action, false));
$this->getPersistence()->set(get_class($action) . self::QUEUE_TREND, 1);
}
}
}
/**
* @return \common_persistence_KeyValuePersistence
* @throws \oat\oatbox\service\exception\InvalidServiceManagerException
*/
protected function getPersistence()
{
$persistenceId = $this->getOption(self::OPTION_PERSISTENCE);
return $this->getServiceManager()->get(\common_persistence_Manager::SERVICE_ID)->getPersistenceById($persistenceId);
}
/**
* @param QueuedAction $action
* @throws
* @return integer
*/
protected function getTtl(QueuedAction $action)
{
$actionConfig = $this->getActionConfig($action);
$ttl = (int) (isset($actionConfig[self::ACTION_PARAM_TTL]) ? $actionConfig[self::ACTION_PARAM_TTL] : 0);
return $ttl;
}
/**
* @param QueuedAction $action
* @return string
*/
protected function getQueueKey(QueuedAction $action)
{
return self::class . '_' . $action->getId();
}
/**
* @param QueuedAction $action
* @throws ActionQueueException in action was not registered in the config
* @return array
*/
protected function getActionConfig(QueuedAction $action)
{
$actions = $this->getOption(self::OPTION_ACTIONS);
if (!isset($actions[$action->getId()])) {
throw new ActionQueueException(__('Action `%s` is not configured in the action queue service', $action->getId()));
}
return $actions[$action->getId()];
}
/**
* @param QueuedAction $action
* @return array
*/
protected function getPositions(QueuedAction $action)
{
$key = $this->getQueueKey($action);
$positions = json_decode($this->getPersistence()->get($key), true);
if (!$positions) {
$positions = [];
}
return $positions;
}
/**
* @param array $actionConfig
* @return array
*/
private function getRestrictions(array $actionConfig): array
{
return $actionConfig['restrictions'] ?? [];
}
/**
* @param array $restrictions
* @return bool
*/
private function checkRestrictions(array $restrictions): bool
{
$allowExecution = true;
foreach ($restrictions as $restrictionClass => $value) {
if (class_exists($restrictionClass) && is_subclass_of($restrictionClass, BasicRestriction::class)) {
/** @var BasicRestriction $restriction */
$restriction = new $restrictionClass();
$this->propagate($restriction);
$allowExecution = $allowExecution && $restriction->doesComply($value);
}
}
return $allowExecution;
}
}