Completed
Push — master ( e0922d...801a4e )
by Maxence
01:36
created

ElasticSearchPlatform::parseIndexErrorException()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 13
rs 9.8333
c 0
b 0
f 0
cc 3
nc 3
nop 1
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
	 */
217
	public function testPlatform() {
218
		return $this->client->ping();
219
	}
220
221
222
	/**
223
	 * called before any index
224
	 *
225
	 * We create a general index.
226
	 *
227
	 * @throws ConfigurationException
228
	 * @throws BadRequest400Exception
229
	 */
230
	public function initializeIndex() {
231
		$this->indexService->initializeIndex($this->client);
232
	}
233
234
235
	/**
236
	 * resetIndex();
237
	 *
238
	 * Called when admin wants to remove an index specific to a $provider.
239
	 * $provider can be null, meaning a reset of the whole index.
240
	 *
241
	 * @param string $providerId
242
	 *
243
	 * @throws ConfigurationException
244
	 */
245
	public function resetIndex($providerId) {
246
		if ($providerId === 'all') {
247
			$this->indexService->resetIndexAll($this->client);
248
		} else {
249
			$this->indexService->resetIndex($this->client, $providerId);
250
		}
251
	}
252
253
254
	/**
255
	 * @deprecated
256
	 *
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
			$this->manageIndexErrorException($document, $e);
292
		}
293
294
		try {
295
			$index = $this->indexDocumentError($provider, $document, $e);
296
			$this->updateRunnerInfo('result', 'ok', 'warning');
297
298
			return $index;
299
		} catch (Exception $e) {
300
			$this->updateRunnerInfo('result', 'fail', 'error');
301
			$this->manageIndexErrorException($document, $e);
302
		}
303
304
		return $document->getIndex();
305
	}
306
307
308
	/**
309
	 * @param IFullTextSearchProvider $provider
310
	 * @param IndexDocument $document
311
	 * @param Exception $e
312
	 *
313
	 * @return Index
314
	 * @throws AccessIsEmptyException
315
	 * @throws ConfigurationException
316
	 * @throws InterruptException
317
	 * @throws TickDoesNotExistException
318
	 */
319
	private function indexDocumentError(
320
		IFullTextSearchProvider $provider, IndexDocument $document, Exception $e
321
	) {
322
323
		$this->updateRunnerAction('indexDocumentWithoutContent', true);
324
325
		$document->setContent('');
326
//		$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...
327
//		$index->unsetStatus(Index::INDEX_CONTENT);
328
329
		$result = $this->indexService->indexDocument($this->client, $provider, $document);
330
331
		//$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...
332
333
		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...
334
	}
335
336
337
	/**
338
	 * @param IndexDocument $document
339
	 * @param Exception $e
340
	 */
341
	private function manageIndexErrorException(IndexDocument $document, Exception $e) {
342
343
		$message = $this->parseIndexErrorException($e);
344
		$document->getIndex()
345
				 ->addError($message, get_class($e), Index::ERROR_SEV_3);
346
		$this->updateNewIndexError(
347
			$document->getIndex(), $message, get_class($e), Index::ERROR_SEV_3
348
		);
349
	}
350
351
352
	/**
353
	 * @param Exception $e
354
	 *
355
	 * @return string
356
	 */
357
	private function parseIndexErrorException(Exception $e) {
358
359
		$arr = json_decode($e->getMessage(), true);
360
		if (!is_array($arr)) {
361
			return $e->getMessage();
362
		}
363
364
		if (array_key_exists('reason', $arr['error']['root_cause'][0])) {
365
			return $arr['error']['root_cause'][0]['reason'];
366
		}
367
368
		return $e->getMessage();
369
	}
370
371
372
	/**
373
	 * {@inheritdoc}
374
	 * @throws ConfigurationException
375
	 */
376
	public function deleteIndexes($indexes) {
377
		try {
378
			$this->indexService->deleteIndexes($this->client, $indexes);
379
		} catch (ConfigurationException $e) {
380
			throw $e;
381
		}
382
	}
383
384
385
	/**
386
	 * {@inheritdoc}
387
	 * @throws ConfigurationException
388
	 * @throws Exception
389
	 */
390
	public function searchDocuments(
391
		IFullTextSearchProvider $provider, DocumentAccess $access, SearchRequest $request
392
	) {
393
		return $this->searchService->searchDocuments($this->client, $provider, $access, $request);
394
	}
395
396
397
	/**
398
	 * @param string $providerId
399
	 * @param string $documentId
400
	 *
401
	 * @return IndexDocument
402
	 * @throws ConfigurationException
403
	 */
404
	public function getDocument($providerId, $documentId) {
405
		return $this->searchService->getDocument($this->client, $providerId, $documentId);
406
	}
407
408
409
	/**
410
	 * @param array $hosts
411
	 *
412
	 * @throws Exception
413
	 */
414
	private function connectToElastic($hosts) {
415
416
		try {
417
			$hosts = array_map([MiscService::class, 'noEndSlash'], $hosts);
418
			$this->client = ClientBuilder::create()
419
										 ->setHosts($hosts)
420
										 ->setRetries(3)
421
										 ->build();
422
423
//		}
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...
424
//		catch (CouldNotConnectToHost $e) {
425
//			$this 'CouldNotConnectToHost';
426
//			$previous = $e->getPrevious();
427
//			if ($previous instanceof MaxRetriesException) {
428
//				echo "Max retries!";
429
//			}
430
		} catch (Exception $e) {
431
			throw $e;
432
//			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...
433
		}
434
	}
435
436
437
}