Completed
Push — master ( 2675cf...85e5b9 )
by Maxence
01:49
created

ElasticSearchPlatform::indexDocumentError()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.7998
c 0
b 0
f 0
cc 1
nc 1
nop 3
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\IFullTextSearchPlatform;
34
use OCA\FullTextSearch\IFullTextSearchProvider;
35
use OCA\FullTextSearch\Model\DocumentAccess;
36
use OCA\FullTextSearch\Model\Index;
37
use OCA\FullTextSearch\Model\IndexDocument;
38
use OCA\FullTextSearch\Model\Runner;
39
use OCA\FullTextSearch\Model\SearchRequest;
40
use OCA\FullTextSearch\Model\SearchResult;
41
use OCA\FullTextSearch_ElasticSearch\AppInfo\Application;
42
use OCA\FullTextSearch_ElasticSearch\Exceptions\AccessIsEmptyException;
43
use OCA\FullTextSearch_ElasticSearch\Exceptions\ConfigurationException;
44
use OCA\FullTextSearch_ElasticSearch\Service\ConfigService;
45
use OCA\FullTextSearch_ElasticSearch\Service\IndexService;
46
use OCA\FullTextSearch_ElasticSearch\Service\MiscService;
47
use OCA\FullTextSearch_ElasticSearch\Service\SearchService;
48
use OCP\AppFramework\QueryException;
49
50
51
class ElasticSearchPlatform implements IFullTextSearchPlatform {
52
53
	/** @var ConfigService */
54
	private $configService;
55
56
	/** @var IndexService */
57
	private $indexService;
58
59
	/** @var SearchService */
60
	private $searchService;
61
62
	/** @var MiscService */
63
	private $miscService;
64
65
	/** @var Client */
66
	private $client;
67
68
	/** @var Runner */
69
	private $runner;
70
71
72
	/**
73
	 * return a unique Id of the platform.
74
	 */
75
	public function getId() {
76
		return 'elastic_search';
77
	}
78
79
	/**
80
	 * return a unique Id of the platform.
81
	 */
82
	public function getName() {
83
		return 'Elasticsearch';
84
	}
85
86
87
	/**
88
	 * @return string
89
	 */
90
	public function getVersion() {
91
		return $this->configService->getAppValue('installed_version');
92
	}
93
94
95
	/**
96
	 * @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...
97
	 * @throws ConfigurationException
98
	 */
99
	public function getConfiguration() {
100
101
		$result = [];
102
		$hosts = $this->configService->getElasticHost();
103
104
		foreach ($hosts as $host) {
105
			$parsedHost = parse_url($host);
106
			$safeHost = $parsedHost['scheme'] . '://';
107
			if (array_key_exists('user', $parsedHost)) {
108
				$safeHost .= $parsedHost['user'] . ':' . '********' . '@';
109
			}
110
			$safeHost .= $parsedHost['host'];
111
			$safeHost .= ':' . $parsedHost['port'];
112
113
			$result[] = $safeHost;
114
		}
115
116
		return [
117
			'elastic_host'  => $result,
118
			'elastic_index' => $this->configService->getElasticIndex()
119
		];
120
	}
121
122
123
	/**
124
	 * @param Runner $runner
125
	 */
126
	public function setRunner(Runner $runner) {
127
		$this->runner = $runner;
128
	}
129
130
	/**
131
	 * @param $action
132
	 * @param bool $force
133
	 *
134
	 * @throws Exception
135
	 */
136
	private function updateRunnerAction($action, $force = false) {
137
		if ($this->runner === null) {
138
			return;
139
		}
140
141
		$this->runner->updateAction($action, $force);
142
	}
143
144
145
	/**
146
	 * @param Index $index
147
	 * @param string $message
148
	 * @param string $exception
149
	 * @param int $sev
150
	 */
151
	private function updateNewIndexError($index, $message, $exception, $sev) {
152
		if ($this->runner === null) {
153
			return;
154
		}
155
156
		$this->runner->newIndexError($index, $message, $exception, $sev);
157
	}
158
159
160
	/**
161
	 * @param Index $index
162
	 * @param string $message
163
	 * @param string $status
164
	 * @param int $type
165
	 */
166
	private function updateNewIndexResult($index, $message, $status, $type) {
167
		if ($this->runner === null) {
168
			return;
169
		}
170
171
		$this->runner->newIndexResult($index, $message, $status, $type);
172
	}
173
174
175
	/**
176
	 * Called when loading the platform.
177
	 *
178
	 * Loading some container and connect to ElasticSearch.
179
	 *
180
	 * @throws ConfigurationException
181
	 * @throws QueryException
182
	 * @throws Exception
183
	 */
184
	public function loadPlatform() {
185
		$app = new Application();
186
187
		$container = $app->getContainer();
188
		$this->configService = $container->query(ConfigService::class);
189
		$this->indexService = $container->query(IndexService::class);
190
		$this->searchService = $container->query(SearchService::class);
191
		$this->miscService = $container->query(MiscService::class);
192
193
		try {
194
			$this->connectToElastic($this->configService->getElasticHost());
195
		} catch (ConfigurationException $e) {
196
			throw $e;
197
		}
198
	}
199
200
201
	/**
202
	 * not used yet.
203
	 *
204
	 * @return bool
205
	 */
206
	public function testPlatform() {
207
		return $this->client->ping();
208
	}
209
210
211
	/**
212
	 * called before any index
213
	 *
214
	 * We create a general index.
215
	 *
216
	 * @throws ConfigurationException
217
	 * @throws BadRequest400Exception
218
	 */
219
	public function initializeIndex() {
220
		$this->indexService->initializeIndex($this->client);
221
	}
222
223
224
	/**
225
	 * resetIndex();
226
	 *
227
	 * Called when admin wants to remove an index specific to a $provider.
228
	 * $provider can be null, meaning a reset of the whole index.
229
	 *
230
	 * @param string $providerId
231
	 *
232
	 * @throws ConfigurationException
233
	 */
234
	public function resetIndex($providerId) {
235
		if ($providerId === 'all') {
236
			$this->indexService->resetIndexAll($this->client);
237
		} else {
238
			$this->indexService->resetIndex($this->client, $providerId);
239
		}
240
	}
241
242
243
	/**
244
	 * @deprecated
245
	 *
246
	 * @param IFullTextSearchProvider $provider
247
	 * @param $documents
248
	 */
249
	public function indexDocuments(IFullTextSearchProvider $provider, $documents) {
250
251
	}
252
253
254
	/**
255
	 * @param IFullTextSearchProvider $provider
256
	 * @param IndexDocument $document
257
	 *
258
	 * @return Index
259
	 */
260
	public function indexDocument(IFullTextSearchProvider $provider, IndexDocument $document) {
261
262
		$document->initHash();
263
264
		try {
265
			$result = $this->indexService->indexDocument($this->client, $provider, $document);
266
267
			$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...
268
269
			$this->updateNewIndexResult(
270
				$document->getIndex(), json_encode($result), 'ok',
271
				Runner::RESULT_TYPE_SUCCESS
272
			);
273
274
			return $index;
275
		} catch (Exception $e) {
276
			$this->updateNewIndexResult(
277
				$document->getIndex(), '', 'issue while indexing, testing with empty content',
278
				Runner::RESULT_TYPE_WARNING
279
			);
280
281
			$this->manageIndexErrorException($document, $e);
282
		}
283
284
		try {
285
			$result = $this->indexDocumentError($provider, $document, $e);
286
			$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...
287
288
			$this->updateNewIndexResult(
289
				$document->getIndex(), json_encode($result), 'ok',
290
				Runner::RESULT_TYPE_WARNING
291
			);
292
293
			return $index;
294
		} catch (Exception $e) {
295
			$this->updateNewIndexResult(
296
				$document->getIndex(), '', 'fail',
297
				Runner::RESULT_TYPE_FAIL
298
			);
299
			$this->manageIndexErrorException($document, $e);
300
		}
301
302
		return $document->getIndex();
303
	}
304
305
306
	/**
307
	 * @param IFullTextSearchProvider $provider
308
	 * @param IndexDocument $document
309
	 * @param Exception $e
310
	 *
311
	 * @return array
0 ignored issues
show
Documentation introduced by
Should the return type not be callable? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
312
	 * @throws AccessIsEmptyException
313
	 * @throws ConfigurationException
314
	 * @throws \Exception
315
	 */
316
	private function indexDocumentError(
317
		IFullTextSearchProvider $provider, IndexDocument $document, Exception $e
318
	) {
319
320
		$this->updateRunnerAction('indexDocumentWithoutContent', true);
321
322
		$document->setContent('');
323
//		$index = $document->getIndex();
324
//		$index->unsetStatus(Index::INDEX_CONTENT);
325
326
		$result = $this->indexService->indexDocument($this->client, $provider, $document);
327
328
		return $result;
329
	}
330
331
332
	/**
333
	 * @param IndexDocument $document
334
	 * @param Exception $e
335
	 */
336
	private function manageIndexErrorException(IndexDocument $document, Exception $e) {
337
338
		$message = $this->parseIndexErrorException($e);
339
		$document->getIndex()
340
				 ->addError($message, get_class($e), Index::ERROR_SEV_3);
341
		$this->updateNewIndexError(
342
			$document->getIndex(), $message, get_class($e), Index::ERROR_SEV_3
343
		);
344
	}
345
346
347
	/**
348
	 * @param Exception $e
349
	 *
350
	 * @return string
351
	 */
352
	private function parseIndexErrorException(Exception $e) {
353
354
		$arr = json_decode($e->getMessage(), true);
355
		if (!is_array($arr)) {
356
			return $e->getMessage();
357
		}
358
359
		if (array_key_exists('reason', $arr['error']['root_cause'][0])) {
360
			return $arr['error']['root_cause'][0]['reason'];
361
		}
362
363
		return $e->getMessage();
364
	}
365
366
367
	/**
368
	 * {@inheritdoc}
369
	 * @throws ConfigurationException
370
	 */
371
	public function deleteIndexes($indexes) {
372
		try {
373
			$this->indexService->deleteIndexes($this->client, $indexes);
374
		} catch (ConfigurationException $e) {
375
			throw $e;
376
		}
377
	}
378
379
380
	/**
381
	 * {@inheritdoc}
382
	 * @throws ConfigurationException
383
	 * @throws Exception
384
	 */
385
	public function searchDocuments(
386
		IFullTextSearchProvider $provider, DocumentAccess $access, SearchRequest $request
387
	) {
388
		return null;
389
//		return $this->searchService->searchDocuments($this->client, $provider, $access, $request);
390
	}
391
392
393
394
	/**
395
	 * {@inheritdoc}
396
	 * @throws Exception
397
	 */
398
	public function searchRequest(SearchResult $result, DocumentAccess $access) {
399
		$this->searchService->searchRequest($this->client, $result, $access);
400
	}
401
402
403
	/**
404
	 * @param string $providerId
405
	 * @param string $documentId
406
	 *
407
	 * @return IndexDocument
408
	 * @throws ConfigurationException
409
	 */
410
	public function getDocument($providerId, $documentId) {
411
		return $this->searchService->getDocument($this->client, $providerId, $documentId);
412
	}
413
414
415
	/**
416
	 * @param array $hosts
417
	 *
418
	 * @throws Exception
419
	 */
420
	private function connectToElastic($hosts) {
421
422
		try {
423
			$hosts = array_map([MiscService::class, 'noEndSlash'], $hosts);
424
			$this->client = ClientBuilder::create()
425
										 ->setHosts($hosts)
426
										 ->setRetries(3)
427
										 ->build();
428
429
//		}
430
//		catch (CouldNotConnectToHost $e) {
431
//			$this 'CouldNotConnectToHost';
432
//			$previous = $e->getPrevious();
433
//			if ($previous instanceof MaxRetriesException) {
434
//				echo "Max retries!";
435
//			}
436
		} catch (Exception $e) {
437
			throw $e;
438
//			echo ' ElasticSearchPlatform::load() Exception --- ' . $e->getMessage() . "\n";
439
		}
440
	}
441
442
443
}