Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 104
0.00% covered (danger)
0.00%
0 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
DeliveryExecutionsUpdater
0.00% covered (danger)
0.00%
0 / 104
0.00% covered (danger)
0.00%
0 / 13
1056
0.00% covered (danger)
0.00%
0 / 1
 action
n/a
0 / 0
n/a
0 / 0
0
 execute
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
56
 actionBasedOnEndDate
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
20
 actionBasedOnTTL
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
30
 getUpdateableStatuses
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 getDeliveryStateService
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getDeliveryMonitoringService
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getServiceProxy
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getDeliveryLog
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getLastInteractionDateTime
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
20
 getTtlAsActive
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isBasedOnEndDateTime
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDeliveryEndDateTime
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 getDeliveryEndDateProperty
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
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) 2018  (original work) Open Assessment Technologies SA;
19 */
20
21namespace oat\taoProctoring\model\execution;
22
23use common_report_Report as Report;
24use DateInterval;
25use oat\generis\model\OntologyAwareTrait;
26use oat\oatbox\service\ConfigurableService;
27use oat\taoDelivery\model\execution\ServiceProxy;
28use oat\taoDeliveryRdf\model\DeliveryAssemblyService;
29use oat\taoProctoring\model\deliveryLog\DeliveryLog;
30use oat\taoProctoring\model\implementation\DeliveryExecutionStateService;
31use oat\taoProctoring\model\monitorCache\DeliveryMonitoringService;
32use oat\taoDelivery\model\execution\DeliveryExecution;
33use DateTimeImmutable;
34
35/**
36 * Abstract service which allows us to update delivery executions
37 *
38 * Class UpdateDeliveryExecutionsService
39 * @package oat\taoProctoring\model\execution
40 */
41abstract class DeliveryExecutionsUpdater extends ConfigurableService
42{
43    use OntologyAwareTrait;
44
45    /**
46     * Action based on the time of the last delivery execution activity
47     */
48    public const OPTION_TTL_AS_ACTIVE = 'ttlAsActive';
49
50    /**
51     * Action based on the delivery end time
52     */
53    public const OPTION_USE_DELIVERY_END_TIME = 'useDeliveryEndTime';
54
55    /**
56     * Statuses of the DE which will be updated
57     */
58    public const OPTION_UPDATEABLE_STATUSES = 'updateableExecutionStatuses';
59
60    /** @var Report */
61    protected $report;
62
63    /**
64     * @var \core_kernel_classes_Property
65     */
66    private $propertyDeliveryEndDate;
67
68    /**
69     * terminate, delete, finish etc...
70     *
71     * @param $deliveryExecution
72     * @param $executionId
73     * @param bool $isEndDate
74     * @return mixed
75     */
76    abstract protected function action($deliveryExecution, $executionId, $isEndDate = false);
77
78    /**
79     * @return Report
80     * @throws \common_exception_Error
81     */
82    public function execute()
83    {
84        $deliveryMonitoringService = $this->getDeliveryMonitoringService();
85        $executionsMonitoringData  = $deliveryMonitoringService->find([
86            DeliveryMonitoringService::STATUS => $this->getUpdateableStatuses()
87        ]);
88        $this->report = Report::createInfo('Updating executions...');
89        $count  = 0;
90
91        foreach ($executionsMonitoringData as $datum) {
92            try {
93                $data        = $datum->get();
94                $executionId = $data[DeliveryMonitoringService::DELIVERY_EXECUTION_ID];
95                if ($this->isBasedOnEndDateTime()) {
96                    $deliveryID  = $data[DeliveryMonitoringService::DELIVERY_ID];
97                    $endDateTime = $this->getDeliveryEndDateTime($deliveryID);
98                    //if no end date time found, fallback to normal updating execution based on TTL.
99                    if (!is_null($endDateTime)) {
100                        $updated = $this->actionBasedOnEndDate($endDateTime, $executionId);
101                        if ($updated) {
102                            $count++;
103                        }
104                        continue;
105                    }
106                }
107
108                $updated = $this->actionBasedOnTTL($executionId);
109                if ($updated) {
110                    $count++;
111                }
112            } catch (\Exception $exception) {
113                $this->report->add(Report::createFailure($exception->getMessage()));
114            }
115        }
116
117        $this->report->add(Report::createInfo('Number of executions updated: ' . $count));
118        return $this->report;
119    }
120
121    /**
122     * Run an action if the end date matches the condition
123     *
124     * @param DateTimeImmutable $endDateTime
125     * @param $executionId
126     * @return bool
127     * @throws \common_exception_Error
128     * @throws \Exception
129     */
130    protected function actionBasedOnEndDate(DateTimeImmutable $endDateTime, $executionId)
131    {
132        try {
133            $deliveryExecution = $this->getServiceProxy()->getDeliveryExecution($executionId);
134            $lastInteraction   = $this->getLastInteractionDateTime($deliveryExecution);
135
136            if ($lastInteraction === null) {
137                $this->report->add(
138                    Report::createFailure('Execution last interaction cannot be found: ' . $executionId)
139                );
140
141                return false;
142            }
143
144            if (new DateTimeImmutable('now') >= $endDateTime) {
145                $this->action($deliveryExecution, $executionId, true);
146                return true;
147            }
148
149            $this->report->add(Report::createInfo(
150                'Execution not expired yet:' . $executionId .
151                ' Last Interaction:' . $lastInteraction->format('Y-m-d H:i:s') .
152                ' Time when will expire:' . $endDateTime->format('Y-m-d H:i:s')
153            ));
154        } catch (\common_exception_NotFound $e) {
155            $this->report->add(Report::createFailure('Execution cannot be found: ' . $executionId));
156            $this->report->add(Report::createFailure($e->getMessage()));
157        }
158
159        return false;
160    }
161
162    /**
163     * Run an action if TTL matches the condition
164     *
165     * @param $executionId
166     * @return bool
167     * @throws \common_exception_Error
168     * @throws \Exception
169     */
170    protected function actionBasedOnTTL($executionId)
171    {
172        try {
173            $deliveryExecution = $this->getServiceProxy()->getDeliveryExecution($executionId);
174            $lastInteraction   = $this->getLastInteractionDateTime($deliveryExecution);
175
176            if ($lastInteraction === null) {
177                $this->report->add(
178                    Report::createFailure('Execution last interaction cannot be found: ' . $executionId)
179                );
180
181                return false;
182            }
183
184            $ttl = $this->getTtlAsActive();
185            if ($ttl === null) {
186                $this->report->add(Report::createFailure('Execution ttl not set: ' . $executionId));
187                return false;
188            }
189
190            $timeUntilToLive = clone $lastInteraction;
191            $timeUntilToLive = $timeUntilToLive->add(new DateInterval($ttl));
192
193            if ((new DateTimeImmutable('now')) >= $timeUntilToLive) {
194                $this->action($deliveryExecution, $executionId);
195                return true;
196            } else {
197                $this->report->add(Report::createInfo(
198                    'Execution not expired yet:' . $executionId .
199                    ' Last Interaction: ' . $lastInteraction->format('Y-m-d H:i:s') .
200                    ' Time when will expire: ' . $timeUntilToLive->format('Y-m-d H:i:s')
201                ));
202            }
203        } catch (\common_exception_NotFound $e) {
204            $this->report->add(Report::createFailure('Execution cannot be found: ' . $executionId));
205            $this->report->add(Report::createFailure($e->getMessage()));
206        }
207
208        return false;
209    }
210
211    /**
212     * Getting statuses of the delivery executions which needs to be updated
213     * by default used 'active' and 'paused' deliveryExecutions (for backwards compatibility)
214     * @return array|mixed
215     */
216    protected function getUpdateableStatuses()
217    {
218        return $this->hasOption(self::OPTION_UPDATEABLE_STATUSES)
219            ? $this->getOption(self::OPTION_UPDATEABLE_STATUSES)
220            : [
221                DeliveryExecution::STATE_ACTIVE,
222                DeliveryExecution::STATE_PAUSED,
223            ];
224    }
225
226    /**
227     * @return array|DeliveryExecutionStateService|object
228     */
229    protected function getDeliveryStateService()
230    {
231        /** @var DeliveryExecutionStateService $service */
232        $service = $this->getServiceLocator()->get(DeliveryExecutionStateService::SERVICE_ID);
233
234        return $service;
235    }
236
237    /**
238     * @return array|DeliveryMonitoringService|object
239     */
240    protected function getDeliveryMonitoringService()
241    {
242        /** @var DeliveryMonitoringService $service */
243        $service = $this->getServiceLocator()->get(DeliveryMonitoringService::SERVICE_ID);
244
245        return $service;
246    }
247
248    /**
249     * @return array|ServiceProxy|object
250     */
251    protected function getServiceProxy()
252    {
253        /** @var ServiceProxy $service */
254        $service = $this->getServiceLocator()->get(ServiceProxy::SERVICE_ID);
255
256        return $service;
257    }
258
259    /**
260     * @return array|DeliveryLog|object
261     */
262    protected function getDeliveryLog()
263    {
264        /** @var DeliveryLog $deliveryLogService */
265        $deliveryLogService = $this->getServiceLocator()->get(DeliveryLog::SERVICE_ID);
266
267        return $deliveryLogService;
268    }
269
270    /**
271     * @param DeliveryExecution $deliveryExecution
272     * @return bool|DateTimeImmutable|null
273     * @throws \common_exception_NotFound
274     * @throws \Exception
275     */
276    protected function getLastInteractionDateTime(DeliveryExecution $deliveryExecution)
277    {
278        $deliveryLogService  = $this->getDeliveryLog();
279        $testTakerIdentifier = $deliveryExecution->getUserIdentifier();
280
281        $events = array_reverse($deliveryLogService->get($deliveryExecution->getIdentifier()));
282
283        $lastTestTakersEvent = null;
284        foreach ($events as $event) {
285            if ($event[DeliveryLog::CREATED_BY] === $testTakerIdentifier) {
286                $lastTestTakersEvent = $event;
287                break;
288            }
289        }
290
291        if (!is_null($lastTestTakersEvent)) {
292            $lastEventTime = (new DateTimeImmutable())->setTimestamp($lastTestTakersEvent[DeliveryLog::CREATED_AT]);
293        } else {
294            return null;
295        }
296
297        return $lastEventTime;
298    }
299
300    /**
301     * @return string|null
302     */
303    protected function getTtlAsActive()
304    {
305        return $this->getOption(static::OPTION_TTL_AS_ACTIVE);
306    }
307
308    /**
309     * @return string|null
310     */
311    protected function isBasedOnEndDateTime()
312    {
313        return (bool)$this->getOption(static::OPTION_USE_DELIVERY_END_TIME);
314    }
315
316    /**
317     * @param $deliveryId
318     * @return null|DateTimeImmutable
319     */
320    protected function getDeliveryEndDateTime($deliveryId)
321    {
322        $delivery = $this->getResource($deliveryId);
323        try {
324            $endDate = (string)$delivery->getUniquePropertyValue($this->getDeliveryEndDateProperty());
325
326            return (new DateTimeImmutable())->setTimestamp($endDate);
327        } catch (\Exception $e) {
328            return null;
329        }
330    }
331
332    /**
333     * Caching property to avoid multiple generations
334     * @return \core_kernel_classes_Property
335     */
336    private function getDeliveryEndDateProperty()
337    {
338        $this->propertyDeliveryEndDate = $this->propertyDeliveryEndDate
339            ?: $this->getProperty(DeliveryAssemblyService::PROPERTY_END);
340
341        return $this->propertyDeliveryEndDate;
342    }
343}