Passed
Push — hans/logtests ( 76c086...71695d )
by Simon
06:24 queued 02:27
created

SolrCoreService::coreUnload()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 5
c 1
b 0
f 0
dl 0
loc 9
ccs 6
cts 6
cp 1
rs 10
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
namespace Firesphere\SolrSearch\Services;
4
5
use Exception;
6
use Firesphere\SolrSearch\Factories\DocumentFactory;
7
use Firesphere\SolrSearch\Helpers\FieldResolver;
8
use Firesphere\SolrSearch\Helpers\SolrLogger;
9
use Firesphere\SolrSearch\Indexes\BaseIndex;
10
use Firesphere\SolrSearch\Interfaces\ConfigStore;
11
use GuzzleHttp\Exception\GuzzleException;
12
use GuzzleHttp\HandlerStack;
13
use LogicException;
14
use ReflectionClass;
15
use ReflectionException;
16
use SilverStripe\Control\Director;
17
use SilverStripe\Core\ClassInfo;
18
use SilverStripe\Core\Config\Configurable;
19
use SilverStripe\Core\Injector\Injector;
20
use SilverStripe\ORM\ArrayList;
21
use SilverStripe\ORM\DataObject;
22
use SilverStripe\ORM\SS_List;
23
use Solarium\Client;
24
use Solarium\Core\Client\Adapter\Guzzle;
25
use Solarium\QueryType\Server\CoreAdmin\Query\Query;
26
use Solarium\QueryType\Server\CoreAdmin\Result\StatusResult;
27
use Solarium\QueryType\Update\Result;
28
29
class SolrCoreService
30
{
31
    /**
32
     * Unique ID in Solr
33
     */
34
    public const ID_FIELD = 'id';
35
    /**
36
     * SilverStripe ID of the object
37
     */
38
    public const CLASS_ID_FIELD = 'ObjectID';
39
    /**
40
     * Name of the field that can be used for queries
41
     */
42
    public const CLASSNAME = 'ClassName';
43
    /**
44
     * Solr update types
45
     */
46
    public const DELETE_TYPE_ALL = 'deleteall';
47
    public const DELETE_TYPE = 'delete';
48
    public const UPDATE_TYPE = 'update';
49
    public const CREATE_TYPE = 'create';
50
51
52
    use Configurable;
53
54
    /**
55
     * @var Client
56
     */
57
    protected $client;
58
59
    /**
60
     * @var array
61
     */
62
    protected $validIndexes = [];
63
64
    /**
65
     * @var Query
66
     */
67
    protected $admin;
68
69
    /**
70
     * Add debugging information
71
     * @var bool
72
     */
73
    protected $inDebugMode = false;
74
75
76
    /**
77
     * SolrCoreService constructor.
78
     * @throws ReflectionException
79
     */
80 67
    public function __construct()
81
    {
82 67
        $config = static::config()->get('config');
83 67
        $this->client = new Client($config);
84 67
        $this->client->setAdapter(new Guzzle());
85 67
        $this->admin = $this->client->createCoreAdmin();
86 67
        $this->filterIndexes();
87 67
    }
88
89
    /**
90
     * @throws ReflectionException
91
     */
92 67
    protected function filterIndexes(): void
93
    {
94 67
        $indexes = ClassInfo::subclassesFor(BaseIndex::class);
95 67
        $enabledIndexes = static::config()->get('indexes');
96 67
        $enabledIndexes = is_array($enabledIndexes) ? $enabledIndexes : [$enabledIndexes];
97 67
        foreach ($indexes as $subindex) {
98 67
            $ref = new ReflectionClass($subindex);
99 67
            $notEnabled = !in_array($subindex, $enabledIndexes, true);
100
            // If the config of indexes is set, and the requested index isn't in it, skip addition
101
            // Or, the index simply doesn't exist, also a valid option
102 67
            if ((count($enabledIndexes) && $notEnabled) && $ref->isInstantiable()) {
103 67
                $this->validIndexes[] = $subindex;
104
            }
105
        }
106 67
    }
107
108
    /**
109
     * Create a new core
110
     * @param $core string - The name of the core
111
     * @param ConfigStore $configStore
112
     * @return bool
113
     * @throws Exception
114
     * @throws GuzzleException
115
     */
116 2
    public function coreCreate($core, $configStore): bool
117
    {
118 2
        $action = $this->admin->createCreate();
119
120 2
        $action->setCore($core);
121 2
        $action->setInstanceDir($configStore->instanceDir($core));
122 2
        $this->admin->setAction($action);
123
        try {
124 2
            $response = $this->client->coreAdmin($this->admin);
125
126 2
            return $response->getWasSuccessful();
127
        } catch (Exception $e) {
128
            $solrLogger = new SolrLogger();
129
            $solrLogger->saveSolrLog('Config');
130
131
            throw new Exception($e);
132
        }
133
    }
134
135
    /**
136
     * @param $core
137
     * @return StatusResult|null
138
     */
139 25
    public function coreReload($core): ?StatusResult
140
    {
141 25
        $reload = $this->admin->createReload();
142 25
        $reload->setCore($core);
143
144 25
        $this->admin->setAction($reload);
145
146 25
        $response = $this->client->coreAdmin($this->admin);
147
148 25
        return $response->getStatusResult();
149
    }
150
151
    /**
152
     * @param string $core
153
     * @return StatusResult|null
154
     * @deprecated backward compatibility stub
155
     */
156 1
    public function coreIsActive($core): ?StatusResult
157
    {
158 1
        return $this->coreStatus($core);
159
    }
160
161
    /**
162
     * @param string $core
163
     * @return StatusResult|null
164
     */
165 25
    public function coreStatus($core): ?StatusResult
166
    {
167 25
        $status = $this->admin->createStatus();
168 25
        $status->setCore($core);
169
170 25
        $this->admin->setAction($status);
171 25
        $response = $this->client->coreAdmin($this->admin);
172
173 25
        return $response->getStatusResult();
174
    }
175
176
    /**
177
     * Remove a core from Solr
178
     * @param string $core core name
179
     * @return StatusResult|null A result is successful
180
     */
181 1
    public function coreUnload($core): ?StatusResult
182
    {
183 1
        $unload = $this->admin->createUnload();
184 1
        $unload->setCore($core);
185
186 1
        $this->admin->setAction($unload);
187 1
        $response = $this->client->coreAdmin($this->admin);
188
189 1
        return $response->getStatusResult();
190
    }
191
192
    /**
193
     * @param SS_List|DataObject $items
194
     * @param string $type
195
     * @param null|string $index
196
     * @return bool|Result
197
     * @throws ReflectionException
198
     * @throws Exception
199
     */
200 67
    public function updateItems($items, $type, $index = null)
201
    {
202 67
        $indexes = $this->getValidIndexes($index);
203
204 67
        $result = false;
205 67
        $items = ($items instanceof DataObject) ? ArrayList::create([$items]) : $items;
206 67
        $items = ($items instanceof SS_List) ? $items : ArrayList::create($items);
207
208 67
        $hierarchy = FieldResolver::getHierarchy($items->first()->ClassName);
209
210 67
        foreach ($indexes as $indexString) {
211
            /** @var BaseIndex $index */
212 67
            $index = Injector::inst()->get($indexString);
213 67
            $classes = $index->getClasses();
214 67
            $inArray = array_intersect($classes, $hierarchy);
215
            // No point in sending a delete|update|create for something that's not in the index
216 67
            if (!count($inArray)) {
217 67
                continue;
218
            }
219
220 5
            $result = $this->doManipulate($items, $type, $index);
221
        }
222
223 67
        return $result;
224
    }
225
226
    /**
227
     * Get valid indexes for the project
228
     * @param null|string $index
229
     * @return array
230
     */
231 67
    public function getValidIndexes($index = null): ?array
232
    {
233 67
        if ($index && !in_array($index, $this->validIndexes, true)) {
234 1
            throw new LogicException('Incorrect index ' . $index);
235
        }
236
237 67
        if ($index) {
238 5
            return [$index];
239
        }
240
241
        // return the array values, to reset the keys
242 67
        return array_values($this->validIndexes);
243
    }
244
245
    /**
246
     * @param SS_List $items
247
     * @param $type
248
     * @param BaseIndex $index
249
     * @return Result
250
     * @throws Exception
251
     */
252 7
    public function doManipulate($items, $type, BaseIndex $index): Result
253
    {
254 7
        $client = $index->getClient();
255
256 7
        $update = $this->getUpdate($items, $type, $index, $client);
257
        // commit immediately when in dev mode
258 7
        if (Director::isDev()) {
259 7
            $update->addCommit();
260
        }
261
262 7
        return $client->update($update);
263
    }
264
265
    /**
266
     * @param SS_List $items
267
     * @param string $type
268
     * @param BaseIndex $index
269
     * @param \Solarium\Core\Client\Client $client
270
     * @return mixed
271
     * @throws Exception
272
     */
273 7
    protected function getUpdate($items, $type, BaseIndex $index, \Solarium\Core\Client\Client $client)
274
    {
275
        // get an update query instance
276 7
        $update = $client->createUpdate();
277
278
        switch ($type) {
279 7
            case static::DELETE_TYPE:
280
                // By pushing to a single array, we have less memory usage and no duplicates
281
                // This is faster, and more efficient, because we only do one DB query
282 3
                $delete = $items->map('ID', 'ClassName')->toArray();
283 3
                array_walk($delete, static function (&$item, $key) {
284 3
                    $item = sprintf('%s-%s', $item, $key);
285 3
                });
286 3
                $update->addDeleteByIds(array_values($delete));
287
                // Remove the deletion array from memory
288 3
                break;
289 6
            case static::DELETE_TYPE_ALL:
290 2
                $update->addDeleteQuery('*:*');
291 2
                break;
292 4
            case static::UPDATE_TYPE:
293 1
            case static::CREATE_TYPE:
294 4
                $this->updateIndex($index, $items, $update);
295
        }
296
297 7
        return $update;
298
    }
299
300
    /**
301
     * @param BaseIndex $index
302
     * @param SS_List $items
303
     * @param \Solarium\QueryType\Update\Query\Query $update
304
     * @throws Exception
305
     */
306 4
    public function updateIndex($index, $items, $update): void
307
    {
308 4
        $fields = $index->getFieldsForIndexing();
309 4
        $factory = $this->getFactory($items);
310 4
        $docs = $factory->buildItems($fields, $index, $update);
311 4
        if (count($docs)) {
312 4
            $update->addDocuments($docs);
313
        }
314 4
    }
315
316
    /**
317
     * @param SS_List $items
318
     * @return DocumentFactory
319
     */
320 4
    protected function getFactory($items): DocumentFactory
321
    {
322 4
        $factory = Injector::inst()->get(DocumentFactory::class);
323 4
        $factory->setItems($items);
324 4
        $factory->setClass($items->first()->ClassName);
325 4
        $factory->setDebug($this->isInDebugMode());
326
327 4
        return $factory;
328
    }
329
330
    /**
331
     * @return bool
332
     */
333 4
    public function isInDebugMode(): bool
334
    {
335 4
        return $this->inDebugMode;
336
    }
337
338
    /**
339
     * @param bool $inDebugMode
340
     * @return SolrCoreService
341
     */
342 67
    public function setInDebugMode(bool $inDebugMode): SolrCoreService
343
    {
344 67
        $this->inDebugMode = $inDebugMode;
345
346 67
        return $this;
347
    }
348
349
    /**
350
     * @param HandlerStack|null $handler Used for testing the solr version
351
     * @return int
352
     */
353 25
    public function getSolrVersion($handler = null): int
354
    {
355 25
        $config = self::config()->get('config');
356 25
        $firstEndpoint = array_shift($config['endpoint']);
357
        $clientConfig = [
358 25
            'base_uri' => 'http://' . $firstEndpoint['host'] . ':' . $firstEndpoint['port']
359
        ];
360
361 25
        if ($handler) {
362 1
            $clientConfig['handler'] = $handler;
363
        }
364
365 25
        $client = new \GuzzleHttp\Client($clientConfig);
366
367 25
        $result = $client->get('solr/admin/info/system');
368 25
        $result = json_decode($result->getBody(), 1);
369
370 25
        $solrVersion = 5;
371 25
        $version = version_compare('5.0.0', $result['lucene']['solr-spec-version']);
372 25
        if ($version > 0) {
373 1
            $solrVersion = 4;
374
        }
375
376 25
        return $solrVersion;
377
    }
378
379
    /**
380
     * @return Client
381
     */
382 1
    public function getClient(): Client
383
    {
384 1
        return $this->client;
385
    }
386
387
    /**
388
     * @param Client $client
389
     * @return SolrCoreService
390
     */
391 1
    public function setClient($client): SolrCoreService
392
    {
393 1
        $this->client = $client;
394
395 1
        return $this;
396
    }
397
}
398