Completed
Push — master ( 94b632...a799ec )
by Maxence
02:11
created

ElasticSearchPlatform::getDocument()   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 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\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
	 *
134
	 * @throws InterruptException
135
	 * @throws TickDoesNotExistException
136
	 */
137
	private function updateRunner($action) {
138
		if ($this->runner === null) {
139
			return;
140
		}
141
142
		$this->runner->update($action);
143
	}
144
145
146
	/**
147
	 * @param $line
148
	 */
149
	private function outputRunner($line) {
150
		if ($this->runner === null) {
151
			return;
152
		}
153
154
		$this->runner->output($line);
155
	}
156
157
158
	/**
159
	 * Called when loading the platform.
160
	 *
161
	 * Loading some container and connect to ElasticSearch.
162
	 *
163
	 * @throws ConfigurationException
164
	 * @throws QueryException
165
	 * @throws Exception
166
	 */
167
	public function loadPlatform() {
168
		$app = new Application();
169
170
		$container = $app->getContainer();
171
		$this->configService = $container->query(ConfigService::class);
172
		$this->indexService = $container->query(IndexService::class);
173
		$this->searchService = $container->query(SearchService::class);
174
		$this->miscService = $container->query(MiscService::class);
175
176
		try {
177
			$this->connectToElastic($this->configService->getElasticHost());
178
		} catch (ConfigurationException $e) {
179
			throw $e;
180
		}
181
	}
182
183
184
	/**
185
	 * not used yet.
186
	 *
187
	 * @return bool
188
	 * @throws ConfigurationException
189
	 */
190
	public function testPlatform() {
191
		return $this->indexService->testIndex($this->client);
192
	}
193
194
195
	/**
196
	 * called before any index
197
	 *
198
	 * We create a general index.
199
	 *
200
	 * @throws ConfigurationException
201
	 * @throws BadRequest400Exception
202
	 */
203
	public function initializeIndex() {
204
		$this->indexService->initializeIndex($this->client);
205
	}
206
207
208
	/**
209
	 * resetIndex();
210
	 *
211
	 * Called when admin wants to remove an index specific to a $provider.
212
	 * $provider can be null, meaning a reset of the whole index.
213
	 *
214
	 * @param string $providerId
215
	 *
216
	 * @throws ConfigurationException
217
	 */
218
	public function resetIndex($providerId) {
219
		if ($providerId === 'all') {
220
			$this->indexService->resetIndexAll($this->client);
221
		} else {
222
			$this->indexService->resetIndex($this->client, $providerId);
223
		}
224
	}
225
226
227
	/**
228
	 * @param IFullTextSearchProvider $provider
229
	 * @param IndexDocument[] $documents
230
	 *
231
	 * @return Index[]
232
	 * @throws Exception
233
	 */
234
	public function indexDocuments(IFullTextSearchProvider $provider, $documents) {
235
		$indexes = [];
236
		foreach ($documents as $document) {
237
238
			try {
239
				$index = $this->indexDocument($provider, $document);
240
				$indexes[] = $index;
241
			} catch (Exception $e) {
242
				if ($this->runner->isStrict()) {
243
					throw $e;
244
				}
245
				/** we do nohtin' */
246
			}
247
248
		}
249
250
		return $indexes;
251
	}
252
253
254
	/**
255
	 * @param IFullTextSearchProvider $provider
256
	 * @param IndexDocument $document
257
	 *
258
	 * @return Index
259
	 * @throws AccessIsEmptyException
260
	 * @throws ConfigurationException
261
	 * @throws InterruptException
262
	 * @throws TickDoesNotExistException
263
	 */
264
	public function indexDocument(IFullTextSearchProvider $provider, IndexDocument $document) {
265
266
		$this->updateRunner('indexDocument');
267
		$this->outputRunner(' . Indexing: ' . $document->getTitle());
268
269
		$document->initHash();
270
271
		try {
272
			$result = $this->indexService->indexDocument($this->client, $provider, $document);
273
			$this->outputRunner('  result: ' . json_encode($result));
274
275
			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...
276
		} catch (Exception $e) {
277
			return $this->indexDocumentError($provider, $document, $e);
278
		}
279
280
	}
281
282
283
	/**
284
	 * @param IFullTextSearchProvider $provider
285
	 * @param IndexDocument $document
286
	 * @param Exception $e
287
	 *
288
	 * @return Index
289
	 * @throws ConfigurationException
290
	 * @throws AccessIsEmptyException
291
	 */
292
	private function indexDocumentError(
293
		IFullTextSearchProvider $provider, IndexDocument $document, Exception $e
294
	) {
295
		$message = [
296
			'exception' => get_class($e),
297
			'message'   => $e->getMessage()
298
		];
299
300
		$document->setContent(null);
301
		$index = $document->getIndex();
302
		$index->unsetStatus(Index::INDEX_CONTENT);
303
		$index->setMessage(json_encode($message));
304
305
		$result = $this->indexService->indexDocument($this->client, $provider, $document);
306
		$this->outputRunner('  result with no content: ' . json_encode($result));
307
308
		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...
309
	}
310
311
312
	/**
313
	 * {@inheritdoc}
314
	 * @throws ConfigurationException
315
	 */
316
	public function deleteIndexes($indexes) {
317
		try {
318
			$this->indexService->deleteIndexes($this->client, $indexes);
319
		} catch (ConfigurationException $e) {
320
			throw $e;
321
		}
322
	}
323
324
325
	/**
326
	 * {@inheritdoc}
327
	 * @throws ConfigurationException
328
	 * @throws Exception
329
	 */
330
	public function searchDocuments(
331
		IFullTextSearchProvider $provider, DocumentAccess $access, SearchRequest $request
332
	) {
333
		return $this->searchService->searchDocuments($this->client, $provider, $access, $request);
334
	}
335
336
337
	/**
338
	 * @param string $providerId
339
	 * @param string $documentId
340
	 *
341
	 * @return IndexDocument
342
	 */
343
	public function getDocument($providerId, $documentId) {
344
		return $this->searchService->getDocument($this->client, $providerId, $documentId);
345
	}
346
347
348
	/**
349
	 * @param array $hosts
350
	 *
351
	 * @throws Exception
352
	 */
353
	private function connectToElastic($hosts) {
354
355
		try {
356
			$hosts = array_map([MiscService::class, 'noEndSlash'], $hosts);
357
			$this->client = ClientBuilder::create()
358
										 ->setHosts($hosts)
359
										 ->setRetries(3)
360
										 ->build();
361
362
//		}
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...
363
//		catch (CouldNotConnectToHost $e) {
364
//			$this 'CouldNotConnectToHost';
365
//			$previous = $e->getPrevious();
366
//			if ($previous instanceof MaxRetriesException) {
367
//				echo "Max retries!";
368
//			}
369
		} catch (Exception $e) {
370
			throw $e;
371
//			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...
372
		}
373
	}
374
375
376
}