Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
63.08% covered (warning)
63.08%
41 / 65
72.73% covered (warning)
72.73%
8 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
TestSessionStateRestorationService
63.08% covered (warning)
63.08%
41 / 65
72.73% covered (warning)
72.73%
8 / 11
34.31
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 restore
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 restoreTestSessionState
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 restoreExtendedState
27.27% covered (danger)
27.27%
3 / 11
0.00% covered (danger)
0.00%
0 / 1
3.54
 restoreTimeLineState
27.27% covered (danger)
27.27%
3 / 11
0.00% covered (danger)
0.00%
0 / 1
3.54
 restoreItemsState
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 walkTestParts
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 walkAssessmentSections
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 walkAssessmentSection
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 restoreItemState
20.00% covered (danger)
20.00%
2 / 10
0.00% covered (danger)
0.00%
0 / 1
4.05
 dispatchBackupRemoval
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3/**
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; under version 2
7 * of the License (non-upgradable).
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 *
18 * Copyright (c) 2022 (original work) Open Assessment Technologies SA
19 *
20 */
21
22declare(strict_types=1);
23
24namespace oat\taoQtiTest\models\TestSessionState;
25
26use common_exception_NoContent;
27use common_exception_NotFound;
28use oat\oatbox\filesystem\FilesystemException;
29use oat\tao\model\state\StateMigration;
30use oat\tao\model\taskQueue\QueueDispatcherInterface;
31use oat\taoDelivery\model\execution\DeliveryExecution;
32use oat\taoQtiTest\models\classes\tasks\QtiStateOffload\AbstractQtiStateManipulationTask;
33use oat\taoQtiTest\models\classes\tasks\QtiStateOffload\StateBackupRemovalTask;
34use oat\taoQtiTest\models\QtiTestExtractionFailedException;
35use oat\taoQtiTest\models\QtiTestUtils;
36use oat\taoQtiTest\models\runner\ExtendedState;
37use oat\taoQtiTest\models\runner\time\QtiTimeStorage;
38use oat\taoQtiTest\models\TestSessionService;
39use oat\taoQtiTest\models\TestSessionState\Api\TestSessionStateRestorationInterface;
40use oat\taoQtiTest\models\TestSessionState\Exception\RestorationImpossibleException;
41use Psr\Log\LoggerInterface;
42use qtism\data\AssessmentSection;
43use qtism\data\SectionPartCollection;
44use qtism\data\TestPartCollection;
45
46class TestSessionStateRestorationService implements TestSessionStateRestorationInterface
47{
48    /** @var TestSessionService */
49    private $testSessionService;
50    /** @var QtiTestUtils */
51    private $qtiTestUtils;
52    /** @var StateMigration */
53    private $stateMigration;
54    /** @var LoggerInterface */
55    private $logger;
56    /** @var QueueDispatcherInterface */
57    private $queueDispatcher;
58
59    public function __construct(
60        TestSessionService $testSessionService,
61        QtiTestUtils $qtiTestUtils,
62        StateMigration $stateMigration,
63        LoggerInterface $logger,
64        QueueDispatcherInterface $queueDispatcher
65    ) {
66        $this->testSessionService = $testSessionService;
67        $this->qtiTestUtils = $qtiTestUtils;
68        $this->stateMigration = $stateMigration;
69        $this->logger = $logger;
70        $this->queueDispatcher = $queueDispatcher;
71    }
72
73    /**
74     * @inheritDoc
75     */
76    public function restore(DeliveryExecution $deliveryExecution): void
77    {
78        $deliveryExecutionId = $deliveryExecution->getIdentifier();
79        $userId = $deliveryExecution->getUserIdentifier();
80
81        $this->restoreTestSessionState($userId, $deliveryExecutionId);
82        $this->restoreExtendedState($userId, $deliveryExecutionId);
83        $this->restoreTimeLineState($userId, $deliveryExecutionId);
84        $this->restoreItemsState($deliveryExecution);
85    }
86
87    /**
88     * @throws RestorationImpossibleException
89     */
90    private function restoreTestSessionState(string $userId, string $sessionId)
91    {
92        try {
93            $this->stateMigration->restore($userId, $sessionId);
94            $this->dispatchBackupRemoval($userId, $sessionId, 'Test Session');
95        } catch (FilesystemException $e) {
96            throw new RestorationImpossibleException(
97                sprintf('[%s] impossible to reach archived test session', $sessionId)
98            );
99        }
100    }
101
102    private function restoreExtendedState(string $userId, string $sessionId): void
103    {
104        $extendedStorageId = ExtendedState::getStorageKeyFromTestSessionId($sessionId);
105        try {
106            $this->stateMigration->restore($userId, $extendedStorageId);
107            $this->dispatchBackupRemoval($userId, $extendedStorageId, 'Extended');
108        } catch (FilesystemException $e) {
109            $this->logger->debug(
110                sprintf(
111                    '[%s] Extended state restoration impossible for user %s',
112                    $extendedStorageId,
113                    $userId
114                )
115            );
116        }
117    }
118
119    private function restoreTimeLineState(string $userId, string $sessionId): void
120    {
121        $qtiItemStorageId = QtiTimeStorage::getStorageKeyFromTestSessionId($sessionId);
122        try {
123            $this->stateMigration->restore($userId, $qtiItemStorageId);
124            $this->dispatchBackupRemoval($userId, $qtiItemStorageId, 'TimeLine');
125        } catch (FilesystemException $e) {
126            $this->logger->debug(
127                sprintf(
128                    '[%s] TimeLine state restoration impossible for user %s',
129                    $qtiItemStorageId,
130                    $userId
131                )
132            );
133        }
134    }
135
136    /**
137     * @throws common_exception_NoContent
138     * @throws QtiTestExtractionFailedException
139     * @throws common_exception_NotFound
140     */
141    private function restoreItemsState(DeliveryExecution $deliveryExecution): void
142    {
143        $runtimeInputParameters = $this->testSessionService->getRuntimeInputParameters($deliveryExecution);
144        $testDefinition = $this->qtiTestUtils->getTestDefinition($runtimeInputParameters['QtiTestCompilation']);
145        $this->walkTestParts($testDefinition->getTestParts(), $deliveryExecution);
146    }
147
148    /**
149     * @throws common_exception_NotFound
150     */
151    private function walkTestParts(TestPartCollection $testParts, DeliveryExecution $deliveryExecution)
152    {
153        foreach ($testParts as $testPart) {
154            $this->walkAssessmentSections($testPart->getAssessmentSections(), $deliveryExecution);
155        }
156    }
157
158    /**
159     * @throws common_exception_NotFound
160     */
161    private function walkAssessmentSections(
162        SectionPartCollection $assessmentSections,
163        DeliveryExecution $deliveryExecution
164    ) {
165        foreach ($assessmentSections as $assessmentSection) {
166            $this->walkAssessmentSection($assessmentSection, $deliveryExecution);
167        }
168    }
169
170    /**
171     * @throws common_exception_NotFound
172     */
173    private function walkAssessmentSection(AssessmentSection $assessmentSection, DeliveryExecution $deliveryExecution)
174    {
175        $userId = $deliveryExecution->getUserIdentifier();
176        $deliveryExecutionId = $deliveryExecution->getIdentifier();
177        foreach ($assessmentSection->getSectionParts() as $sectionPart) {
178            $this->restoreItemState($userId, $deliveryExecutionId . $sectionPart->getIdentifier());
179        }
180    }
181
182    private function restoreItemState(string $userId, string $callId)
183    {
184        try {
185            $this->stateMigration->restore($userId, $callId);
186            $this->dispatchBackupRemoval($userId, $callId, 'Test Item');
187        } catch (FilesystemException $e) {
188            $this->logger->debug(
189                sprintf(
190                    '[%s] Test Item state restoration impossible for user %s',
191                    $callId,
192                    $userId
193                )
194            );
195        }
196    }
197
198    private function dispatchBackupRemoval(string $userId, string $callId, string $stateLabel): void
199    {
200        $this->queueDispatcher->createTask(new StateBackupRemovalTask(), [
201            AbstractQtiStateManipulationTask::PARAM_USER_ID_KEY => $userId,
202            AbstractQtiStateManipulationTask::PARAM_CALL_ID_KEY => $callId,
203            AbstractQtiStateManipulationTask::PARAM_STATE_LABEL_KEY => $stateLabel
204        ]);
205    }
206}