Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
89.72% covered (warning)
89.72%
96 / 107
50.00% covered (danger)
50.00%
5 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
RdsStorage
89.72% covered (warning)
89.72%
96 / 107
50.00% covered (danger)
50.00%
5 / 10
17.31
0.00% covered (danger)
0.00%
0 / 1
 getTableName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 log
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 logMultiple
86.96% covered (warning)
86.96%
20 / 23
0.00% covered (danger)
0.00%
0 / 1
3.02
 searchInstances
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 tableColumns
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 install
96.15% covered (success)
96.15%
50 / 52
0.00% covered (danger)
0.00%
0 / 1
3
 getInsertChunkSize
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 createInsert
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 getUserLogin
60.00% covered (warning)
60.00%
3 / 5
0.00% covered (danger)
0.00%
0 / 1
3.58
 getUserService
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) 2016  (original work) Open Assessment Technologies SA;
19 *
20 * @author Alexander Zagovorichev <zagovorichev@1pt.com>
21 */
22
23namespace oat\taoEventLog\model\eventLog;
24
25use oat\generis\model\user\UserRdf;
26use oat\oatbox\user\User;
27use oat\oatbox\user\UserService;
28use oat\taoEventLog\model\LogEntity;
29use Doctrine\DBAL\Schema\SchemaException;
30use oat\taoEventLog\model\storage\AbstractRdsStorage;
31use Throwable;
32
33/**
34 * Class RdsStorage
35 * @package oat\taoEventLog\model\storage
36 */
37class RdsStorage extends AbstractRdsStorage
38{
39    public const EVENT_LOG_TABLE_NAME = 'event_log';
40
41    public const SERVICE_ID = 'taoEventLog/eventLogStorage';
42
43    public const OPTION_INSERT_CHUNK_SIZE = 'insertChunkSize';
44
45    public const EVENT_LOG_ID = self::ID;
46    public const EVENT_LOG_EVENT_NAME = 'event_name';
47    public const EVENT_LOG_ACTION = 'action';
48    public const EVENT_LOG_USER_ID = 'user_id';
49    public const EVENT_LOG_USER_LOGIN = 'user_login';
50    public const EVENT_LOG_USER_ROLES = 'user_roles';
51    public const EVENT_LOG_OCCURRED = 'occurred';
52    public const EVENT_LOG_PROPERTIES = 'properties';
53
54    private const DEFAULT_INSERT_CHUNK_SIZE = 100;
55
56    private UserService $userService;
57
58    /**
59     * @return string
60     */
61    public function getTableName()
62    {
63        return self::EVENT_LOG_TABLE_NAME;
64    }
65
66    /**
67     * @param LogEntity $logEntity
68     * @return bool
69     */
70    public function log(LogEntity $logEntity)
71    {
72        $result = $this->getPersistence()->insert($this->getTableName(), $this->createInsert($logEntity));
73
74        return $result === 1;
75    }
76
77    public function logMultiple(LogEntity ...$logEntities): bool
78    {
79        $inserts = array_map(
80            fn (LogEntity $logEntity): array => $this->createInsert($logEntity),
81            $logEntities
82        );
83
84        try {
85            $persistence = $this->getPersistence();
86
87            $persistence->transactional(function () use ($inserts, $persistence) {
88                $insertCount = count($inserts);
89                $insertChunkSize = $this->getInsertChunkSize();
90
91                foreach (array_chunk($inserts, $insertChunkSize) as $index => $chunk) {
92                    $this->logDebug(
93                        sprintf(
94                            'Processing chunk %d/%d with %d log entries',
95                            $index + 1,
96                            ceil($insertCount / $insertChunkSize),
97                            count($chunk)
98                        )
99                    );
100
101                    $persistence->insertMultiple($this->getTableName(), $chunk);
102                }
103            });
104
105            return true;
106        } catch (Throwable $exception) {
107            $this->logError('Error when inserting log entries: ' . $exception->getMessage());
108
109            return false;
110        }
111    }
112
113    /**
114     * @param array $params
115     * @deprecated use $this->search() instead
116     * @return array
117     */
118    public function searchInstances(array $params = [])
119    {
120        return $this->search($params);
121    }
122
123    /**
124     * @inheritdoc
125     */
126    public static function tableColumns()
127    {
128        return [
129            self::EVENT_LOG_ID,
130            self::EVENT_LOG_USER_ID,
131            self::EVENT_LOG_USER_LOGIN,
132            self::EVENT_LOG_USER_ROLES,
133            self::EVENT_LOG_EVENT_NAME,
134            self::EVENT_LOG_ACTION,
135            self::EVENT_LOG_OCCURRED,
136            self::EVENT_LOG_PROPERTIES
137        ];
138    }
139
140    /**
141     * @inheritdoc
142     */
143    public static function install($persistence)
144    {
145        /** @var AbstractSchemaManager $schemaManager */
146        $schemaManager = $persistence->getDriver()->getSchemaManager();
147
148        /** @var Schema $schema */
149        $schema = $schemaManager->createSchema();
150        $fromSchema = clone $schema;
151
152        try {
153            $table = $schema->createTable(self::EVENT_LOG_TABLE_NAME);
154            $table->addOption('engine', 'MyISAM');
155
156            $table->addColumn(
157                self::EVENT_LOG_ID,
158                "integer",
159                ["notnull" => true, "autoincrement" => true, 'unsigned' => true]
160            );
161            $table->addColumn(
162                self::EVENT_LOG_EVENT_NAME,
163                "string",
164                ["notnull" => true, "length" => 255, 'comment' => 'Event name']
165            );
166            $table->addColumn(
167                self::EVENT_LOG_ACTION,
168                "string",
169                ["notnull" => false, "length" => 1000, 'comment' => 'Current action']
170            );
171            $table->addColumn(
172                self::EVENT_LOG_USER_ID,
173                "string",
174                ["notnull" => false, "length" => 255, 'default' => '', 'comment' => 'User identifier']
175            );
176            $table->addColumn(
177                self::EVENT_LOG_USER_LOGIN,
178                'string',
179                ['notnull' => false, 'length' => 255, 'default' => null, 'comment' => 'User login']
180            );
181            $table->addColumn(
182                self::EVENT_LOG_USER_ROLES,
183                "text",
184                ["notnull" => true, 'default' => '', 'comment' => 'User roles']
185            );
186            $table->addColumn(self::EVENT_LOG_OCCURRED, "datetime", ["notnull" => true]);
187            $table->addColumn(
188                self::EVENT_LOG_PROPERTIES,
189                "text",
190                ["notnull" => false, 'default' => '', 'comment' => 'Event properties in json']
191            );
192
193            $table->setPrimaryKey([self::EVENT_LOG_ID]);
194            $table->addIndex([self::EVENT_LOG_EVENT_NAME], 'idx_event_name');
195            $table->addIndex([self::EVENT_LOG_ACTION], 'idx_action', [], ['lengths' => [164]]);
196            $table->addIndex([self::EVENT_LOG_USER_ID], 'idx_user_id');
197            $table->addIndex([self::EVENT_LOG_USER_LOGIN], 'idx_user_login');
198            $table->addIndex([self::EVENT_LOG_OCCURRED], 'idx_occurred');
199        } catch (SchemaException $e) {
200            \common_Logger::i('Database Schema for EventLog already up to date.');
201        }
202
203        $queries = $persistence->getPlatForm()->getMigrateSchemaSql($fromSchema, $schema);
204        foreach ($queries as $query) {
205            $persistence->exec($query);
206        }
207    }
208
209    private function getInsertChunkSize(): int
210    {
211        return $this->getOption(self::OPTION_INSERT_CHUNK_SIZE, self::DEFAULT_INSERT_CHUNK_SIZE);
212    }
213
214    private function createInsert(LogEntity $logEntity): array
215    {
216        return [
217            self::EVENT_LOG_EVENT_NAME => $logEntity->getEvent()->getName(),
218            self::EVENT_LOG_ACTION => $logEntity->getAction(),
219            self::EVENT_LOG_USER_ID => $logEntity->getUser()->getIdentifier(),
220            self::EVENT_LOG_USER_LOGIN => $this->getUserLogin($logEntity->getUser()),
221            self::EVENT_LOG_USER_ROLES => implode(',', $logEntity->getUser()->getRoles()),
222            self::EVENT_LOG_OCCURRED => $logEntity->getTime()->format(self::DATE_TIME_FORMAT),
223            self::EVENT_LOG_PROPERTIES => json_encode($logEntity->getData()),
224        ];
225    }
226
227    private function getUserLogin(User $user): ?string
228    {
229        $login = current($user->getPropertyValues(UserRdf::PROPERTY_LOGIN));
230
231        if (!$login) {
232            $generisUser = $this->getUserService()->getUser($user->getIdentifier());
233
234            $login = current($generisUser->getPropertyValues(UserRdf::PROPERTY_LOGIN));
235        }
236
237        return  $login ?: null;
238    }
239
240    private function getUserService(): UserService
241    {
242        if (!isset($this->userService)) {
243            $this->userService = $this->getServiceManager()->get(UserService::SERVICE_ID);
244        }
245
246        return $this->userService;
247    }
248}