Completed
Push — master ( 780e5d...cd5778 )
by Maxence
01:52
created

ElasticSearchPlatform::updateRunnerAction()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 2
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
	public function __construct(
73
		ConfigService $configService, IndexService $indexService, SearchService $searchService,
74
		MiscService $miscService
75
	) {
76
		$this->configService = $configService;
77
		$this->indexService = $indexService;
78
		$this->searchService = $searchService;
79
		$this->miscService = $miscService;
80
	}
81
82
	/**
83
	 * return a unique Id of the platform.
84
	 */
85
	public function getId() {
86
		return 'elastic_search';
87
	}
88
89
	/**
90
	 * return a unique Id of the platform.
91
	 */
92
	public function getName() {
93
		return 'Elasticsearch';
94
	}
95
96
97
	/**
98
	 * @return string
99
	 */
100
	public function getVersion() {
101
		return '';
102
	}
103
104
105
	/**
106
	 * @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...
107
	 * @throws ConfigurationException
108
	 */
109
	public function getConfiguration() {
110
111
		$result = [];
112
		$hosts = $this->configService->getElasticHost();
113
114
		foreach ($hosts as $host) {
115
			$parsedHost = parse_url($host);
116
			$safeHost = $parsedHost['scheme'] . '://';
117
			if (array_key_exists('user', $parsedHost)) {
118
				$safeHost .= $parsedHost['user'] . ':' . '********' . '@';
119
			}
120
			$safeHost .= $parsedHost['host'];
121
			$safeHost .= ':' . $parsedHost['port'];
122
123
			$result[] = $safeHost;
124
		}
125
126
		return [
127
			'elastic_host'  => $result,
128
			'elastic_index' => $this->configService->getElasticIndex()
129
		];
130
	}
131
132
133
	/**
134
	 * @param Runner $runner
135
	 */
136
	public function setRunner(Runner $runner) {
137
		$this->runner = $runner;
138
	}
139
140
	/**
141
	 * @param $action
142
	 * @param bool $force
143
	 *
144
	 * @throws Exception
145
	 */
146
	private function updateRunnerAction($action, $force = false) {
147
		if ($this->runner === null) {
148
			return;
149
		}
150
151
		$this->runner->updateAction($action, $force);
152
	}
153
154
155
	/**
156
	 * @param Index $index
157
	 * @param string $message
158
	 * @param string $exception
159
	 * @param int $sev
160
	 */
161
	private function updateNewIndexError($index, $message, $exception, $sev) {
162
		if ($this->runner === null) {
163
			return;
164
		}
165
166
		$this->runner->newIndexError($index, $message, $exception, $sev);
167
	}
168
169
170
	/**
171
	 * @param Index $index
172
	 * @param string $message
173
	 * @param string $status
174
	 * @param int $type
175
	 */
176
	private function updateNewIndexResult($index, $message, $status, $type) {
177
		if ($this->runner === null) {
178
			return;
179
		}
180
181
		$this->runner->newIndexResult($index, $message, $status, $type);
182
	}
183
184
185
	/**
186
	 * Called when loading the platform.
187
	 *
188
	 * Loading some container and connect to ElasticSearch.
189
	 *
190
	 * @throws ConfigurationException
191
	 * @throws QueryException
192
	 * @throws Exception
193
	 */
194
	public function loadPlatform() {
195
		$app = new Application();
0 ignored issues
show
Unused Code introduced by
$app is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
196
197
		try {
198
			$this->connectToElastic($this->configService->getElasticHost());
199
		} catch (ConfigurationException $e) {
200
			throw $e;
201
		}
202
	}
203
204
205
	/**
206
	 * not used yet.
207
	 *
208
	 * @return bool
209
	 */
210
	public function testPlatform() {
211
		return $this->client->ping();
212
	}
213
214
215
	/**
216
	 * called before any index
217
	 *
218
	 * We create a general index.
219
	 *
220
	 * @throws ConfigurationException
221
	 * @throws BadRequest400Exception
222
	 */
223
	public function initializeIndex() {
224
		$this->indexService->initializeIndex($this->client);
225
	}
226
227
228
	/**
229
	 * resetIndex();
230
	 *
231
	 * Called when admin wants to remove an index specific to a $provider.
232
	 * $provider can be null, meaning a reset of the whole index.
233
	 *
234
	 * @param string $providerId
235
	 *
236
	 * @throws ConfigurationException
237
	 */
238
	public function resetIndex($providerId) {
239
		if ($providerId === 'all') {
240
			$this->indexService->resetIndexAll($this->client);
241
		} else {
242
			$this->indexService->resetIndex($this->client, $providerId);
243
		}
244
	}
245
246
247
	/**
248
	 * @deprecated
249
	 *
250
	 * @param IFullTextSearchProvider $provider
251
	 * @param $documents
252
	 */
253
	public function indexDocuments(IFullTextSearchProvider $provider, $documents) {
254
255
	}
256
257
258
	/**
259
	 * @param IFullTextSearchProvider $provider
260
	 * @param IndexDocument $document
261
	 *
262
	 * @return Index
263
	 */
264
	public function indexDocument(IFullTextSearchProvider $provider, IndexDocument $document) {
265
266
		$document->initHash();
267
268
		try {
269
			$result = $this->indexService->indexDocument($this->client, $provider, $document);
270
271
			$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...
272
273
			$this->updateNewIndexResult(
274
				$document->getIndex(), json_encode($result), 'ok',
275
				Runner::RESULT_TYPE_SUCCESS
276
			);
277
278
			return $index;
279
		} catch (Exception $e) {
280
			$this->updateNewIndexResult(
281
				$document->getIndex(), '', 'issue while indexing, testing with empty content',
282
				Runner::RESULT_TYPE_WARNING
283
			);
284
285
			$this->manageIndexErrorException($document, $e);
286
		}
287
288
		try {
289
			$result = $this->indexDocumentError($provider, $document, $e);
290
			$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...
291
292
			$this->updateNewIndexResult(
293
				$document->getIndex(), json_encode($result), 'ok',
294
				Runner::RESULT_TYPE_WARNING
295
			);
296
297
			return $index;
298
		} catch (Exception $e) {
299
			$this->updateNewIndexResult(
300
				$document->getIndex(), '', 'fail',
301
				Runner::RESULT_TYPE_FAIL
302
			);
303
			$this->manageIndexErrorException($document, $e);
304
		}
305
306
		return $document->getIndex();
307
	}
308
309
310
	/**
311
	 * @param IFullTextSearchProvider $provider
312
	 * @param IndexDocument $document
313
	 * @param Exception $e
314
	 *
315
	 * @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...
316
	 * @throws AccessIsEmptyException
317
	 * @throws ConfigurationException
318
	 * @throws \Exception
319
	 */
320
	private function indexDocumentError(
321
		IFullTextSearchProvider $provider, IndexDocument $document, Exception $e
322
	) {
323
324
		$this->updateRunnerAction('indexDocumentWithoutContent', true);
325
326
		$document->setContent('');
327
//		$index = $document->getIndex();
328
//		$index->unsetStatus(Index::INDEX_CONTENT);
329
330
		$result = $this->indexService->indexDocument($this->client, $provider, $document);
331
332
		return $result;
333
	}
334
335
336
	/**
337
	 * @param IndexDocument $document
338
	 * @param Exception $e
339
	 */
340
	private function manageIndexErrorException(IndexDocument $document, Exception $e) {
341
342
		$message = $this->parseIndexErrorException($e);
343
		$document->getIndex()
344
				 ->addError($message, get_class($e), Index::ERROR_SEV_3);
345
		$this->updateNewIndexError(
346
			$document->getIndex(), $message, get_class($e), Index::ERROR_SEV_3
347
		);
348
	}
349
350
351
	/**
352
	 * @param Exception $e
353
	 *
354
	 * @return string
355
	 */
356
	private function parseIndexErrorException(Exception $e) {
357
358
		$arr = json_decode($e->getMessage(), true);
359
		if (!is_array($arr)) {
360
			return $e->getMessage();
361
		}
362
363
		if (array_key_exists('reason', $arr['error']['root_cause'][0])) {
364
			return $arr['error']['root_cause'][0]['reason'];
365
		}
366
367
		return $e->getMessage();
368
	}
369
370
371
	/**
372
	 * {@inheritdoc}
373
	 * @throws ConfigurationException
374
	 */
375
	public function deleteIndexes($indexes) {
376
		try {
377
			$this->indexService->deleteIndexes($this->client, $indexes);
378
		} catch (ConfigurationException $e) {
379
			throw $e;
380
		}
381
	}
382
383
384
	/**
385
	 * {@inheritdoc}
386
	 * @throws ConfigurationException
387
	 * @throws Exception
388
	 */
389
	public function searchDocuments(
390
		IFullTextSearchProvider $provider, DocumentAccess $access, SearchRequest $request
391
	) {
392
		return null;
393
//		return $this->searchService->searchDocuments($this->client, $provider, $access, $request);
394
	}
395
396
397
	/**
398
	 * {@inheritdoc}
399
	 * @throws Exception
400
	 */
401
	public function searchRequest(SearchResult $result, DocumentAccess $access) {
402
		$this->searchService->searchRequest($this->client, $result, $access);
403
	}
404
405
406
	/**
407
	 * @param string $providerId
408
	 * @param string $documentId
409
	 *
410
	 * @return IndexDocument
411
	 * @throws ConfigurationException
412
	 */
413
	public function getDocument($providerId, $documentId) {
414
		return $this->searchService->getDocument($this->client, $providerId, $documentId);
415
	}
416
417
418
	/**
419
	 * @param array $hosts
420
	 *
421
	 * @throws Exception
422
	 */
423
	private function connectToElastic($hosts) {
424
425
		try {
426
			$hosts = array_map([MiscService::class, 'noEndSlash'], $hosts);
427
			$this->client = ClientBuilder::create()
428
										 ->setHosts($hosts)
429
										 ->setRetries(3)
430
										 ->build();
431
432
//		}
433
//		catch (CouldNotConnectToHost $e) {
434
//			$this 'CouldNotConnectToHost';
435
//			$previous = $e->getPrevious();
436
//			if ($previous instanceof MaxRetriesException) {
437
//				echo "Max retries!";
438
//			}
439
		} catch (Exception $e) {
440
			throw $e;
441
//			echo ' ElasticSearchPlatform::load() Exception --- ' . $e->getMessage() . "\n";
442
		}
443
	}
444
445
446
}
447