tao-test/app/taoQtiTest/helpers/class.TestSessionStorage.php

322 lines
9.8 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) 2013-2016 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);
*
*/
use oat\tao\model\state\StateStorage;
use qtism\common\storage\IStream;
use qtism\runtime\tests\AbstractSessionManager;
use qtism\common\storage\MemoryStream;
use qtism\runtime\storage\binary\BinaryAssessmentTestSeeker;
use qtism\runtime\storage\binary\AbstractQtiBinaryStorage;
use qtism\runtime\storage\common\StorageException;
use qtism\data\AssessmentTest;
use qtism\runtime\tests\AssessmentTestSession;
use qtism\runtime\storage\binary\QtiBinaryStreamAccess;
use oat\taoQtiTest\models\files\QtiFlysystemFileManager;
use oat\oatbox\service\ServiceManager;
use oat\oatbox\service\ServiceManagerAwareInterface;
use oat\oatbox\service\ServiceManagerAwareTrait;
/**
* A QtiSm AssessmentTestSession Storage Service implementation for TAO.
*
* It is able to retrieve test sessions related to a given user and a given
* test definition.
*
* @author Jérôme Bogaerts <jerome@taotesting.com>
*
*/
class taoQtiTest_helpers_TestSessionStorage extends AbstractQtiBinaryStorage implements ServiceManagerAwareInterface
{
use ServiceManagerAwareTrait;
use oat\oatbox\mutex\LockTrait;
/**
* The last recorded error.
*
* @var integer
*/
private $lastError = -1;
/**
* The URI (Uniform Resource Identifier) of the user the Test Session belongs to.
*
* @var string
*/
private $userUri;
/**
* @var AssessmentTestSession
*/
private static $session;
/**
* Create a new TestSessionStorage object.
*
* @param AbstractSessionManager $manager The session manager to be used to create new AssessmentTestSession and AssessmentItemSession objects.
* @param BinaryAssessmentTestSeeker $seeker The seeker making able the storage engine to index AssessmentTest's components.
* @param string $userUri The URI (Uniform Resource Identifier) of the user the Test Session belongs to.
*/
public function __construct(AbstractSessionManager $manager, BinaryAssessmentTestSeeker $seeker, $userUri)
{
parent::__construct($manager, $seeker);
$this->setUserUri($userUri);
}
/**
* Get the last retrieved error. -1 means
* no error.
*
* @return integer
*/
public function getLastError()
{
return $this->lastError;
}
/**
* Set the last retrieved error. -1 means
* no error.
*
* @param integer $lastError
*/
public function setLastError($lastError)
{
$this->lastError = $lastError;
}
/**
* Get the URI (Uniform Resource Identifier) of the user the Test Session belongs to.
*
* @return string
*/
public function getUserUri()
{
return $this->userUri;
}
/**
* Set the URI (Uniform Resource Identifier) of the user the Test Session belongs to.
*
* @param string $userUri
*/
public function setUserUri($userUri)
{
$this->userUri = $userUri;
}
/**
* @param AssessmentTest $test
* @param string $sessionId
* @param bool $forReadingOnly
* @return AssessmentTestSession
* @throws StorageException
*/
public function retrieve(AssessmentTest $test, $sessionId, $forReadingOnly = false)
{
if ($forReadingOnly === false) {
return $this->retrieveSessionInWriteMode($test, $sessionId);
} else {
return $this->retrieveSessionInReadMode($test, $sessionId);
}
}
/**
* @param AssessmentTest $test
* @param string $sessionId
* @return taoQtiTest_helpers_TestSession
* @throws StorageException
*/
private function retrieveSessionInReadMode(AssessmentTest $test, string $sessionId): taoQtiTest_helpers_TestSession
{
if (!$this->sessionExists($sessionId)) {
$this->setLastError(-1);
self::$session = parent::retrieve($test, $sessionId);
self::$session->setReadOnly(true);
}
return self::$session;
}
/**
* @param AssessmentTest $test
* @param string $sessionId
* @return taoQtiTest_helpers_TestSession
* @throws StorageException
*/
private function retrieveSessionInWriteMode(AssessmentTest $test, string $sessionId): taoQtiTest_helpers_TestSession
{
if ($this->sessionExists($sessionId) && self::$session->isLocked()) {
return self::$session;
}
$this->setLastError(-1);
self::$session = parent::retrieve($test, $sessionId);
$this->lockSession(self::$session);
return self::$session;
}
/**
* @param AssessmentTest $test
* @param string $sessionId
* @return AssessmentTestSession
* @throws StorageException
*/
public function instantiate(AssessmentTest $test, $sessionId = '')
{
$session = parent::instantiate($test, $sessionId);
$this->lockSession($session);
return $session;
}
/**
* @param AssessmentTestSession $assessmentTestSession
* @throws StorageException
*/
public function persist(AssessmentTestSession $assessmentTestSession)
{
if ($assessmentTestSession->isReadOnly()) {
throw new StorageException(
'Readonly test session cannot be stored. Test session id: ' . $assessmentTestSession->getSessionId(),
StorageException::PERSITANCE
);
}
parent::persist($assessmentTestSession);
}
/**
* @param AssessmentTestSession $session
*/
private function lockSession(AssessmentTestSession $session)
{
if ($session->isLocked()) {
return;
}
$lock = $this->createLock('AssessmentTestSession_' . $session->getSessionId(), 30);
$lock->acquire(true);
$session->setReadOnly(false);
$session->setLock($lock);
}
protected function getRetrievalStream($sessionId)
{
$storageService = $this->getServiceLocator()->get(tao_models_classes_service_StateStorage::SERVICE_ID);
$userUri = $this->getUserUri();
if (is_null($userUri) === true) {
$msg = "Could not retrieve current user URI.";
throw new StorageException($msg, StorageException::RETRIEVAL);
}
$data = $storageService->get($userUri, $sessionId);
$stateEmpty = (empty($data) === true);
$stream = new MemoryStream(($stateEmpty === true) ? '' : $data);
$stream->open();
if ($stateEmpty === false) {
// Consume additional error (short signed integer).
$this->setLastError($stream->read(2));
}
$stream->close();
return $stream;
}
protected function persistStream(AssessmentTestSession $assessmentTestSession, MemoryStream $stream)
{
/** @var tao_models_classes_service_StateStorage $storageService */
$storageService = $this->getServiceLocator()->get(tao_models_classes_service_StateStorage::SERVICE_ID);;
$userUri = $this->getUserUri();
if (is_null($userUri) === true) {
$msg = "Could not retrieve current user URI.";
throw new StorageException($msg, StorageException::RETRIEVAL);
}
$data = $this->getLastError() . $stream->getBinary();
if (!$storageService->set($userUri, $assessmentTestSession->getSessionId(), $data)) {
throw new StorageException('Can\'t write into storage at ' . static::class);
}
}
public function exists($sessionId)
{
$storageService = $this->getServiceLocator()->get(tao_models_classes_service_StateStorage::SERVICE_ID);
$userUri = $this->getUserUri();
if (is_null($userUri) === true) {
$msg = "Could not retrieve current user URI.";
throw new StorageException($msg, StorageException::RETRIEVAL);
}
return $storageService->has($userUri, $sessionId);
}
/**
* @param string $sessionId
* @return bool
*/
public function delete($sessionId)
{
/** @var StateStorage $storageService */
$storageService = ServiceManager::getServiceManager()->get(StateStorage::SERVICE_ID);
return $storageService->del($this->getUserUri(), $sessionId);
}
protected function createBinaryStreamAccess(IStream $stream)
{
return new QtiBinaryStreamAccess(
$stream,
$this->getServiceLocator()->get(QtiFlysystemFileManager::SERVICE_ID)
);
}
public function getServiceLocator()
{
if ($this->serviceLocator === null) {
return ServiceManager::getServiceManager();
}
return $this->serviceLocator;
}
/**
* @param string $sessionId
* @return bool
*/
private function sessionExists(string $sessionId): bool
{
return self::$session && self::$session->getSessionId() === $sessionId;
}
/**
* @param $forReadingOnly
* @return bool
*/
private function assessModeChangedToWrite($forReadingOnly): bool
{
return !$forReadingOnly && self::$session->isReadOnly();
}
}