Completed
Push — master ( 00b73c...e0922d )
by Maxence
02:24
created

ElasticSearchPlatform::getName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
/**
3
 * FullTextSearch_ElasticSearch - Use Elasticsearch to index the content of your nextcloud
4
 *
5
 * This file is licensed under the Affero General Public License version 3 or
6
 * later. See the COPYING file.
7
 *
8
 * @author Maxence Lange <[email protected]>
9
 * @copyright 2018
10
 * @license GNU AGPL version 3 or any later version
11
 *
12
 * This program is free software: you can redistribute it and/or modify
13
 * it under the terms of the GNU Affero General Public License as
14
 * published by the Free Software Foundation, either version 3 of the
15
 * License, or (at your option) any later version.
16
 *
17
 * This program is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
 * GNU Affero General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU Affero General Public License
23
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
24
 *
25
 */
26
27
namespace OCA\FullTextSearch_ElasticSearch\Platform;
28
29
use Elasticsearch\Client;
30
use Elasticsearch\ClientBuilder;
31
use Elasticsearch\Common\Exceptions\BadRequest400Exception;
32
use Exception;
33
use OCA\FullTextSearch\Exceptions\InterruptException;
34
use OCA\FullTextSearch\Exceptions\TickDoesNotExistException;
35
use OCA\FullTextSearch\IFullTextSearchPlatform;
36
use OCA\FullTextSearch\IFullTextSearchProvider;
37
use OCA\FullTextSearch\Model\DocumentAccess;
38
use OCA\FullTextSearch\Model\Index;
39
use OCA\FullTextSearch\Model\IndexDocument;
40
use OCA\FullTextSearch\Model\Runner;
41
use OCA\FullTextSearch\Model\SearchRequest;
42
use OCA\FullTextSearch_ElasticSearch\AppInfo\Application;
43
use OCA\FullTextSearch_ElasticSearch\Exceptions\AccessIsEmptyException;
44
use OCA\FullTextSearch_ElasticSearch\Exceptions\ConfigurationException;
45
use OCA\FullTextSearch_ElasticSearch\Service\ConfigService;
46
use OCA\FullTextSearch_ElasticSearch\Service\IndexService;
47
use OCA\FullTextSearch_ElasticSearch\Service\MiscService;
48
use OCA\FullTextSearch_ElasticSearch\Service\SearchService;
49
use OCP\AppFramework\QueryException;
50
51
52
class ElasticSearchPlatform implements IFullTextSearchPlatform {
53
54
	/** @var ConfigService */
55
	private $configService;
56
57
	/** @var IndexService */
58
	private $indexService;
59
60
	/** @var SearchService */
61
	private $searchService;
62
63
	/** @var MiscService */
64
	private $miscService;
65
66
	/** @var Client */
67
	private $client;
68
69
	/** @var Runner */
70
	private $runner;
71
72
73
	/**
74
	 * return a unique Id of the platform.
75
	 */
76
	public function getId() {
77
		return 'elastic_search';
78
	}
79
80
	/**
81
	 * return a unique Id of the platform.
82
	 */
83
	public function getName() {
84
		return 'Elasticsearch';
85
	}
86
87
88
	/**
89
	 * @return string
90
	 */
91
	public function getVersion() {
92
		return $this->configService->getAppValue('installed_version');
93
	}
94
95
96
	/**
97
	 * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,array|string>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
98
	 * @throws ConfigurationException
99
	 */
100
	public function getConfiguration() {
101
102
		$result = [];
103
		$hosts = $this->configService->getElasticHost();
104
105
		foreach ($hosts as $host) {
106
			$parsedHost = parse_url($host);
107
			$safeHost = $parsedHost['scheme'] . '://';
108
			if (array_key_exists('user', $parsedHost)) {
109
				$safeHost .= $parsedHost['user'] . ':' . '********' . '@';
110
			}
111
			$safeHost .= $parsedHost['host'];
112
			$safeHost .= ':' . $parsedHost['port'];
113
114
			$result[] = $safeHost;
115
		}
116
117
		return [
118
			'elastic_host'  => $result,
119
			'elastic_index' => $this->configService->getElasticIndex()
120
		];
121
	}
122
123
124
	/**
125
	 * @param Runner $runner
126
	 */
127
	public function setRunner(Runner $runner) {
128
		$this->runner = $runner;
129
	}
130
131
	/**
132
	 * @param $action
133
	 * @param bool $force
134
	 *
135
	 * @throws InterruptException
136
	 * @throws TickDoesNotExistException
137
	 */
138
	private function updateRunnerAction($action, $force = false) {
139
		if ($this->runner === null) {
140
			return;
141
		}
142
143
		$this->runner->updateAction($action, $force);
144
	}
145
146
	/**
147
	 * @param string $info
148
	 * @param string $value
149
	 * @param string $color
150
	 */
151
	private function updateRunnerInfo($info, $value, $color = '') {
152
		if ($this->runner === null) {
153
			return;
154
		}
155
156
		$this->runner->setInfo($info, $value, $color);
157
	}
158
159
	/**
160
	 * @param array $data
161
	 */
162
	private function updateRunnerInfoArray($data) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
163
		if ($this->runner === null) {
164
			return;
165
		}
166
167
		$this->runner->setInfoArray($data);
168
	}
169
170
171
	/**
172
	 * @param Index $index
173
	 * @param string $message
174
	 * @param string $exception
175
	 * @param int $sev
176
	 */
177
	private function updateNewIndexError($index, $message, $exception, $sev) {
178
		if ($this->runner === null) {
179
			return;
180
		}
181
182
		$this->runner->newIndexError($index, $message, $exception, $sev);
183
	}
184
185
186
	/**
187
	 * Called when loading the platform.
188
	 *
189
	 * Loading some container and connect to ElasticSearch.
190
	 *
191
	 * @throws ConfigurationException
192
	 * @throws QueryException
193
	 * @throws Exception
194
	 */
195
	public function loadPlatform() {
196
		$app = new Application();
197
198
		$container = $app->getContainer();
199
		$this->configService = $container->query(ConfigService::class);
200
		$this->indexService = $container->query(IndexService::class);
201
		$this->searchService = $container->query(SearchService::class);
202
		$this->miscService = $container->query(MiscService::class);
203
204
		try {
205
			$this->connectToElastic($this->configService->getElasticHost());
206
		} catch (ConfigurationException $e) {
207
			throw $e;
208
		}
209
	}
210
211
212
	/**
213
	 * not used yet.
214
	 *
215
	 * @return bool
216
	 * @throws ConfigurationException
217
	 */
218
	public function testPlatform() {
219
		return $this->indexService->testIndex($this->client);
220
	}
221
222
223
	/**
224
	 * called before any index
225
	 *
226
	 * We create a general index.
227
	 *
228
	 * @throws ConfigurationException
229
	 * @throws BadRequest400Exception
230
	 */
231
	public function initializeIndex() {
232
		$this->indexService->initializeIndex($this->client);
233
	}
234
235
236
	/**
237
	 * resetIndex();
238
	 *
239
	 * Called when admin wants to remove an index specific to a $provider.
240
	 * $provider can be null, meaning a reset of the whole index.
241
	 *
242
	 * @param string $providerId
243
	 *
244
	 * @throws ConfigurationException
245
	 */
246
	public function resetIndex($providerId) {
247
		if ($providerId === 'all') {
248
			$this->indexService->resetIndexAll($this->client);
249
		} else {
250
			$this->indexService->resetIndex($this->client, $providerId);
251
		}
252
	}
253
254
255
	/**
256
	 * @deprecated
257
	 * @param IFullTextSearchProvider $provider
258
	 * @param $documents
259
	 */
260
	public function indexDocuments(IFullTextSearchProvider $provider, $documents) {
261
262
	}
263
264
265
	/**
266
	 * @param IFullTextSearchProvider $provider
267
	 * @param IndexDocument $document
268
	 *
269
	 * @return Index
270
	 * @throws AccessIsEmptyException
271
	 * @throws ConfigurationException
272
	 * @throws InterruptException
273
	 * @throws TickDoesNotExistException
274
	 */
275
	public function indexDocument(IFullTextSearchProvider $provider, IndexDocument $document) {
276
277
		$document->initHash();
278
279
		try {
280
			$result = $this->indexService->indexDocument($this->client, $provider, $document);
281
			$this->updateRunnerInfo('info', json_encode($result['_shards']));
282
283
			$index = $this->indexService->parseIndexResult($document->getIndex(), $result);
0 ignored issues
show
Documentation introduced by
$result is of type callable, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
284
			$this->updateRunnerInfo('result', 'ok', 'success');
285
286
			return $index;
287
		} catch (Exception $e) {
288
			$this->updateRunnerInfo(
289
				'result', 'issue while indexing, testing with empty content', 'warning'
290
			);
291
			$document->getIndex()
292
					 ->addError($e->getMessage(), get_class($e), Index::ERROR_SEV_3);
293
			$this->updateNewIndexError(
294
				$document->getIndex(), $e->getMessage(), get_class($e), Index::ERROR_SEV_3
295
			);
296
		}
297
298
		try {
299
			$index = $this->indexDocumentError($provider, $document, $e);
300
			$this->updateRunnerInfo('result', 'ok', 'warning');
301
302
			return $index;
303
		} catch (Exception $e) {
304
			$this->updateRunnerInfo('result', 'fail', 'error');
305
			$document->getIndex()
306
					 ->addError($e->getMessage(), get_class($e), Index::ERROR_SEV_3);
307
			$this->updateNewIndexError(
308
				$document->getIndex(), $e->getMessage(), get_class($e), Index::ERROR_SEV_3
309
			);
310
		}
311
312
		return $document->getIndex();
313
	}
314
315
316
	/**
317
	 * @param IFullTextSearchProvider $provider
318
	 * @param IndexDocument $document
319
	 * @param Exception $e
320
	 *
321
	 * @return Index
322
	 * @throws AccessIsEmptyException
323
	 * @throws ConfigurationException
324
	 * @throws InterruptException
325
	 * @throws TickDoesNotExistException
326
	 */
327
	private function indexDocumentError(
328
		IFullTextSearchProvider $provider, IndexDocument $document, Exception $e
329
	) {
330
331
		$this->updateRunnerAction('indexDocumentWithoutContent', true);
332
333
		$document->setContent('');
334
//		$index = $document->getIndex();
0 ignored issues
show
Unused Code Comprehensibility introduced by
55% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
335
//		$index->unsetStatus(Index::INDEX_CONTENT);
336
337
		$result = $this->indexService->indexDocument($this->client, $provider, $document);
338
339
		$this->miscService->log('____1 ' . json_encode($result));
340
341
		//$this->outputRunner('  result with no content: ' . json_encode($result));
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
342
343
		return $this->indexService->parseIndexResult($document->getIndex(), $result);
0 ignored issues
show
Documentation introduced by
$result is of type callable, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
344
	}
345
346
347
	/**
348
	 * {@inheritdoc}
349
	 * @throws ConfigurationException
350
	 */
351
	public function deleteIndexes($indexes) {
352
		try {
353
			$this->indexService->deleteIndexes($this->client, $indexes);
354
		} catch (ConfigurationException $e) {
355
			throw $e;
356
		}
357
	}
358
359
360
	/**
361
	 * {@inheritdoc}
362
	 * @throws ConfigurationException
363
	 * @throws Exception
364
	 */
365
	public function searchDocuments(
366
		IFullTextSearchProvider $provider, DocumentAccess $access, SearchRequest $request
367
	) {
368
		return $this->searchService->searchDocuments($this->client, $provider, $access, $request);
369
	}
370
371
372
	/**
373
	 * @param string $providerId
374
	 * @param string $documentId
375
	 *
376
	 * @return IndexDocument
377
	 * @throws ConfigurationException
378
	 */
379
	public function getDocument($providerId, $documentId) {
380
		return $this->searchService->getDocument($this->client, $providerId, $documentId);
381
	}
382
383
384
	/**
385
	 * @param array $hosts
386
	 *
387
	 * @throws Exception
388
	 */
389
	private function connectToElastic($hosts) {
390
391
		try {
392
			$hosts = array_map([MiscService::class, 'noEndSlash'], $hosts);
393
			$this->client = ClientBuilder::create()
394
										 ->setHosts($hosts)
395
										 ->setRetries(3)
396
										 ->build();
397
398
//		}
0 ignored issues
show
Unused Code Comprehensibility introduced by
48% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
399
//		catch (CouldNotConnectToHost $e) {
400
//			$this 'CouldNotConnectToHost';
401
//			$previous = $e->getPrevious();
402
//			if ($previous instanceof MaxRetriesException) {
403
//				echo "Max retries!";
404
//			}
405
		} catch (Exception $e) {
406
			throw $e;
407
//			echo ' ElasticSearchPlatform::load() Exception --- ' . $e->getMessage() . "\n";
0 ignored issues
show
Unused Code Comprehensibility introduced by
48% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
408
		}
409
	}
410
411
412
}