Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.89% covered (success)
94.89%
130 / 137
70.59% covered (warning)
70.59%
12 / 17
CRAP
0.00% covered (danger)
0.00%
0 / 1
RdsStorage
94.89% covered (success)
94.89%
130 / 137
70.59% covered (warning)
70.59%
12 / 17
30.12
0.00% covered (danger)
0.00%
0 / 1
 getPersistence
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 addRevision
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
2
 getRevision
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
2
 getAllRevisions
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 buildRevisionCollection
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 getData
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
2
 saveData
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
2
 getResourcesUriByQuery
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 getResourcesDataByQuery
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
2
 getSelectedResourcesDataByQuery
91.67% covered (success)
91.67%
22 / 24
0.00% covered (danger)
0.00%
0 / 1
6.02
 prepareDataObject
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 getQueryBuilder
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getLocalModel
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getLike
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSchema
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPersistenceId
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 provideSchema
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
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) 2015 (original work) Open Assessment Technologies SA;
19 *
20 */
21
22namespace oat\taoRevision\model\storage;
23
24use common_ext_Namespace;
25use common_ext_NamespaceManager;
26use common_Object;
27use common_persistence_SqlPersistence;
28use core_kernel_classes_ContainerCollection as TriplesCollection;
29use core_kernel_classes_Triple as Triple;
30use Doctrine\DBAL\Driver\Statement;
31use Doctrine\DBAL\Query\QueryBuilder;
32use Doctrine\DBAL\Schema\Schema;
33use oat\generis\model\kernel\persistence\smoothsql\search\driver\TaoSearchDriver;
34use oat\generis\model\OntologyRdfs;
35use oat\generis\persistence\PersistenceManager;
36use oat\generis\persistence\sql\SchemaCollection;
37use oat\generis\persistence\sql\SchemaProviderInterface;
38use oat\oatbox\service\ConfigurableService;
39use oat\taoRevision\model\Revision;
40use oat\taoRevision\model\RevisionNotFoundException;
41use oat\taoRevision\model\RevisionStorageInterface;
42
43/**
44 * Storage class for the revision data
45 *
46 * @author Joel Bout <joel@taotesting.com>
47 */
48class RdsStorage extends ConfigurableService implements RevisionStorageInterface, SchemaProviderInterface
49{
50    public const REVISION_TABLE_NAME = 'revision';
51
52    public const REVISION_RESOURCE = 'resource';
53    public const REVISION_VERSION = 'version';
54    public const REVISION_USER = 'user';
55    public const REVISION_CREATED = 'created';
56    public const REVISION_MESSAGE = 'message';
57
58    public const DATA_TABLE_NAME = 'revision_data';
59
60    public const DATA_RESOURCE = 'resource';
61    public const DATA_VERSION = 'version';
62    public const DATA_SUBJECT = 'subject';
63    public const DATA_PREDICATE = 'predicate';
64    public const DATA_OBJECT = 'object';
65    public const DATA_LANGUAGE = 'language';
66
67    /** @var common_persistence_SqlPersistence */
68    private $persistence;
69
70    /**
71     * @return common_persistence_SqlPersistence
72     */
73    protected function getPersistence()
74    {
75        if ($this->persistence === null) {
76            $this->persistence = $this->getServiceLocator()
77                ->get(PersistenceManager::SERVICE_ID)
78                ->getPersistenceById($this->getPersistenceId());
79        }
80
81        return $this->persistence;
82    }
83
84    /**
85     * @param Revision $revision
86     * @param Triple[] $data
87     *
88     * @return Revision
89     */
90    public function addRevision(Revision $revision, array $data)
91    {
92        $this->getPersistence()->insert(
93            self::REVISION_TABLE_NAME,
94            [
95                self::REVISION_RESOURCE => $revision->getResourceId(),
96                self::REVISION_VERSION => $revision->getVersion(),
97                self::REVISION_USER => $revision->getAuthorId(),
98                self::REVISION_MESSAGE => $revision->getMessage(),
99                self::REVISION_CREATED => $revision->getDateCreated(),
100            ]
101        );
102
103        if (!empty($data)) {
104            $this->saveData($revision, $data);
105        }
106
107        return $revision;
108    }
109
110    /**
111     *
112     * @param string $resourceId
113     * @param int    $version
114     *
115     * @return Revision
116     * @throws RevisionNotFoundException
117     */
118    public function getRevision(string $resourceId, int $version)
119    {
120        $queryBuilder = $this->getQueryBuilder()
121            ->select('*')
122            ->from(self::REVISION_TABLE_NAME)
123            ->where(sprintf('%s = ?', self::REVISION_RESOURCE))
124            ->andWhere(sprintf('%s = ?', self::REVISION_VERSION));
125
126        $variables = $this->getPersistence()
127            ->query($queryBuilder->getSQL(), [$resourceId, $version])
128            ->fetchAll();
129
130        if (count($variables) !== 1) {
131            throw new RevisionNotFoundException($resourceId, $version);
132        }
133
134        $variable = reset($variables);
135
136        return new Revision(
137            $variable[self::REVISION_RESOURCE],
138            $variable[self::REVISION_VERSION],
139            $variable[self::REVISION_CREATED],
140            $variable[self::REVISION_USER],
141            $variable[self::REVISION_MESSAGE]
142        );
143    }
144
145    /**
146     * @param string $resourceId
147     *
148     * @return Revision[]
149     */
150    public function getAllRevisions(string $resourceId)
151    {
152        $queryBuilder = $this->getQueryBuilder()
153            ->select('*')
154            ->from(self::REVISION_TABLE_NAME)
155            ->where(sprintf('%s = ?', self::REVISION_RESOURCE));
156
157        $variables = $this->getPersistence()
158            ->query($queryBuilder->getSQL(), [$resourceId])
159            ->fetchAll();
160
161        return $this->buildRevisionCollection($variables);
162    }
163
164    /**
165     * @param array $variables
166     * @return Revision[]
167     */
168    public function buildRevisionCollection(array $variables)
169    {
170        $revisions = [];
171        foreach ($variables as $variable) {
172            $revisions[] = new Revision(
173                $variable[self::REVISION_RESOURCE],
174                $variable[self::REVISION_VERSION],
175                $variable[self::REVISION_CREATED],
176                $variable[self::REVISION_USER],
177                $variable[self::REVISION_MESSAGE]
178            );
179        }
180
181        return $revisions;
182    }
183
184    /**
185     * @param Revision $revision
186     *
187     * @return TriplesCollection
188     */
189    public function getData(Revision $revision)
190    {
191        $queryBuilder = $this->getQueryBuilder()
192            ->select('*')
193            ->from(self::DATA_TABLE_NAME)
194            ->where(sprintf('%s = ?', self::DATA_RESOURCE))
195            ->andWhere(sprintf('%s = ?', self::DATA_VERSION));
196
197        $result = $this->getPersistence()
198            ->query($queryBuilder->getSQL(), [$revision->getResourceId(), $revision->getVersion()]);
199
200        $triples = new TriplesCollection(new common_Object());
201        while ($statement = $result->fetch()) {
202            $triples->add($this->prepareDataObject($statement, $this->getLocalModel()->getModelId()));
203        }
204
205        return $triples;
206    }
207
208    /**
209     *
210     * @param Revision $revision
211     * @param Triple[] $data
212     *
213     * @return bool
214     */
215    protected function saveData(Revision $revision, array $data)
216    {
217        $dataToSave = [];
218
219        foreach ($data as $triple) {
220            $dataToSave[] = [
221                self::DATA_RESOURCE => $revision->getResourceId(),
222                self::DATA_VERSION => $revision->getVersion(),
223                self::DATA_SUBJECT => $triple->subject,
224                self::DATA_PREDICATE => $triple->predicate,
225                self::DATA_OBJECT => $triple->object,
226                self::DATA_LANGUAGE => $triple->lg,
227            ];
228        }
229
230        return $this->getPersistence()->insertMultiple(self::DATA_TABLE_NAME, $dataToSave);
231    }
232
233    /**
234     * @deprecated
235     * @see getResourcesDataByQuery
236     * @param string $query
237     * @param array $options
238     * @param string $predicate
239     * @return array
240     */
241    public function getResourcesUriByQuery(
242        string $query,
243        array $options = [],
244        string $predicate = OntologyRdfs::RDFS_LABEL
245    ) {
246        $result = $this->getSelectedResourcesDataByQuery(
247            [self::DATA_RESOURCE],
248            $query,
249            $options,
250            $predicate
251        );
252
253        $resourcesUri = [];
254
255        while ($statement = $result->fetch()) {
256            $resourcesUri[] = $statement[self::DATA_RESOURCE];
257        }
258
259        return $resourcesUri;
260    }
261
262    public function getResourcesDataByQuery(
263        string $query,
264        array $options = [],
265        string $predicate = OntologyRdfs::RDFS_LABEL
266    ): array {
267        $result = $this->getSelectedResourcesDataByQuery(
268            [self::DATA_RESOURCE, self::DATA_OBJECT],
269            $query,
270            $options,
271            $predicate
272        );
273
274        $resourcesData = [];
275
276        /** @var Revision $statement */
277        while ($statement = $result->fetch()) {
278            $resourcesData[] = [
279                'id' => $statement[self::DATA_RESOURCE],
280                'label' => $statement[self::DATA_OBJECT],
281            ];
282        }
283
284        return $resourcesData;
285    }
286
287    /**
288     * @param string[] $selectedFields
289     */
290    private function getSelectedResourcesDataByQuery(
291        array $selectedFields,
292        string $query,
293        array $options,
294        string $predicate
295    ): Statement {
296        $queryBuilder = $this->getQueryBuilder();
297
298        foreach ($selectedFields as $selectedField) {
299            $queryBuilder->addSelect('rd.' . $selectedField);
300        }
301
302        $queryBuilder->from(self::DATA_TABLE_NAME, 'rd');
303        $queryBuilder->join(
304            'rd',
305            'statements',
306            'st',
307            'st.subject = rd.' . self::DATA_RESOURCE
308        );
309
310        $fieldName = self::DATA_OBJECT;
311        $condition = "rd.$fieldName {$this->getLike()} '%$query%'";
312        $queryBuilder->where($condition);
313        $queryBuilder->andWhere(sprintf('rd.%s = \'%s\'', self::DATA_PREDICATE, $predicate));
314
315        if (isset($options['limit'])) {
316            $queryBuilder->setMaxResults((int)$options['limit']);
317        }
318
319        if (isset($options['offset'])) {
320            $queryBuilder->setFirstResult((int)$options['offset']);
321        }
322
323        $sort = $options['sort'] ?? self::DATA_RESOURCE;
324        $order = isset($options['order']) ? strtoupper($options['order']) : ' ASC';
325
326        $queryBuilder->addOrderBy($sort, $order);
327        foreach ($selectedFields as $selectedField) {
328            $queryBuilder->addGroupBy('rd.' . $selectedField);
329        }
330
331        return $this->getPersistence()->query($queryBuilder->getSQL());
332    }
333
334    /**
335     * @param array  $statement
336     * @param string $modelId
337     *
338     * @return Triple
339     */
340    private function prepareDataObject(array $statement, string $modelId)
341    {
342        $triple = new Triple();
343        $triple->modelid = $modelId;
344        $triple->subject = $statement[self::DATA_SUBJECT];
345        $triple->predicate = $statement[self::DATA_PREDICATE];
346        $triple->object = $statement[self::DATA_OBJECT];
347        $triple->lg = $statement[self::DATA_LANGUAGE];
348
349        return $triple;
350    }
351
352    /**
353     * @return QueryBuilder
354     */
355    protected function getQueryBuilder()
356    {
357        return $this->getPersistence()->getPlatForm()->getQueryBuilder();
358    }
359
360    /**
361     * @return common_ext_Namespace
362     */
363    protected function getLocalModel()
364    {
365        return common_ext_NamespaceManager::singleton()->getLocalNamespace();
366    }
367
368    /**
369     * @return string
370     */
371    protected function getLike()
372    {
373        return (new TaoSearchDriver())->like();
374    }
375
376    /**
377     * @inheritDoc
378     */
379    public function getSchema(Schema $schema)
380    {
381        return $this->getServiceLocator()->get(RdsSqlSchema::class)->getSchema($schema);
382    }
383
384    public function getPersistenceId()
385    {
386        return $this->getOption(self::OPTION_PERSISTENCE);
387    }
388
389    /**
390     * {@inheritDoc}
391     *
392     * @see SchemaProviderInterface::provideSchema()
393     */
394    public function provideSchema(SchemaCollection $schemaCollection)
395    {
396        $schema = $schemaCollection->getSchema($this->getPersistenceId());
397        $this->getSchema($schema);
398    }
399}