Completed
Push — master ( 721944...2f4a96 )
by Maxence
05:36
created

ElasticSearchPlatform   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 288
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 4
Bugs 0 Features 0
Metric Value
wmc 30
c 4
b 0
f 0
lcom 1
cbo 8
dl 0
loc 288
rs 10

17 Methods

Rating   Name   Duplication   Size   Complexity  
A getId() 0 3 1
A getName() 0 3 1
A getVersion() 0 3 1
A getConfiguration() 0 15 2
A setRunner() 0 3 1
A updateRunner() 0 7 2
A outputRunner() 0 7 2
A loadPlatform() 0 15 2
A testPlatform() 0 2 1
A initializeIndex() 0 5 1
A resetIndex() 0 10 2
A indexDocuments() 0 11 3
A indexDocument() 0 16 2
A indexDocumentError() 0 18 1
A deleteIndexes() 0 7 2
A searchDocuments() 0 11 2
A connectToElastic() 0 19 4
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\Curl\CouldNotConnectToHost;
32
use Elasticsearch\Common\Exceptions\MaxRetriesException;
33
use Exception;
34
use OCA\FullTextSearch\Exceptions\InterruptException;
35
use OCA\FullTextSearch\Exceptions\TickDoesNotExistException;
36
use OCA\FullTextSearch\IFullTextSearchPlatform;
37
use OCA\FullTextSearch\IFullTextSearchProvider;
38
use OCA\FullTextSearch\Model\DocumentAccess;
39
use OCA\FullTextSearch\Model\Index;
40
use OCA\FullTextSearch\Model\IndexDocument;
41
use OCA\FullTextSearch\Model\Runner;
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,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
		$parsedHost = parse_url($this->configService->getElasticHost());
103
		$safeHost = $parsedHost['scheme'] . '://';
104
		if (array_key_exists('user', $parsedHost)) {
105
			$safeHost .= $parsedHost['user'] . ':' . '********' . '@';
106
		}
107
		$safeHost .= $parsedHost['host'];
108
		$safeHost .= ':' . $parsedHost['port'];
109
110
		return [
111
			'host'  => $safeHost,
112
			'index' => $this->configService->getElasticIndex()
113
		];
114
	}
115
116
117
	/**
118
	 * @param Runner $runner
119
	 */
120
	public function setRunner(Runner $runner) {
121
		$this->runner = $runner;
122
	}
123
124
	/**
125
	 * @param $action
126
	 *
127
	 * @throws InterruptException
128
	 * @throws TickDoesNotExistException
129
	 */
130
	private function updateRunner($action) {
131
		if ($this->runner === null) {
132
			return;
133
		}
134
135
		$this->runner->update($action);
136
	}
137
138
139
	/**
140
	 * @param $line
141
	 */
142
	private function outputRunner($line) {
143
		if ($this->runner === null) {
144
			return;
145
		}
146
147
		$this->runner->output($line);
148
	}
149
150
151
	/**
152
	 * Called when loading the platform.
153
	 *
154
	 * Loading some container and connect to ElasticSearch.
155
	 *
156
	 * @throws ConfigurationException
157
	 * @throws QueryException
158
	 */
159
	public function loadPlatform() {
160
		$app = new Application();
161
162
		$container = $app->getContainer();
163
		$this->configService = $container->query(ConfigService::class);
164
		$this->indexService = $container->query(IndexService::class);
165
		$this->searchService = $container->query(SearchService::class);
166
		$this->miscService = $container->query(MiscService::class);
167
168
		try {
169
			$this->connectToElastic($this->configService->getElasticHost());
170
		} catch (ConfigurationException $e) {
171
			throw $e;
172
		}
173
	}
174
175
176
	/**
177
	 * not used yet.
178
	 */
179
	public function testPlatform() {
180
	}
181
182
183
	/**
184
	 * called before any index
185
	 *
186
	 * We create a general index.
187
	 *
188
	 * @param IFullTextSearchProvider $provider
189
	 *
190
	 * @throws ConfigurationException
191
	 */
192
	public function initializeIndex(IFullTextSearchProvider $provider) {
193
		$this->indexService->initializeIndex($this->client);
194
195
		$provider->onInitializingIndex($this);
196
	}
197
198
199
	/**
200
	 * resetIndex();
201
	 *
202
	 * Called when admin wants to remove an index specific to a $provider.
203
	 * $provider can be null, meaning a reset of the whole index.
204
	 *
205
	 * @param IFullTextSearchProvider|null $provider
206
	 *
207
	 * @throws ConfigurationException
208
	 */
209
	public function resetIndex($provider) {
210
211
		if ($provider instanceof IFullTextSearchProvider) {
0 ignored issues
show
Bug introduced by
The class OCA\FullTextSearch\IFullTextSearchProvider does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
212
			// TODO: need to specify the map to remove
213
			// TODO: need to remove entries with type=providerId
214
			$provider->onResettingIndex($this);
215
		}
216
217
		$this->indexService->resetIndex($this->client);
218
	}
219
220
221
	/**
222
	 * {@inheritdoc}
223
	 */
224
	public function indexDocuments(IFullTextSearchProvider $provider, $documents) {
225
		$indexes = [];
226
		foreach ($documents as $document) {
227
			$index = $this->indexDocument($provider, $document);
228
			if ($index !== null) {
229
				$indexes[] = $index;
230
			}
231
		}
232
233
		return $indexes;
234
	}
235
236
237
	/**
238
	 * {@inheritdoc}
239
	 */
240
	public function indexDocument(IFullTextSearchProvider $provider, IndexDocument $document) {
241
242
		$this->updateRunner('indexDocument');
243
		$this->outputRunner(' . Indexing: ' . $document->getTitle());
244
245
		try {
246
			$result =
247
				$this->indexService->indexDocument($this, $this->client, $provider, $document);
248
			$this->outputRunner('  result: ' . json_encode($result));
249
250
			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...
251
		} catch (Exception $e) {
252
			return $this->indexDocumentError($provider, $document, $e);
253
		}
254
255
	}
256
257
258
	/**
259
	 * @param IFullTextSearchProvider $provider
260
	 * @param IndexDocument $document
261
	 * @param Exception $e
262
	 *
263
	 * @return Index
264
	 * @throws ConfigurationException
265
	 * @throws AccessIsEmptyException
266
	 */
267
	private function indexDocumentError(
268
		IFullTextSearchProvider $provider, IndexDocument $document, Exception $e
269
	) {
270
		$message = [
271
			'exception' => get_class($e),
272
			'message'   => $e->getMessage()
273
		];
274
275
		$document->setContent(null);
276
		$index = $document->getIndex();
277
		$index->unsetStatus(Index::INDEX_CONTENT);
278
		$index->setMessage(json_encode($message));
279
280
		$result = $this->indexService->indexDocument($this, $this->client, $provider, $document);
281
		$this->outputRunner('  result with no content: ' . json_encode($result));
282
283
		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...
284
	}
285
286
287
	/**
288
	 * {@inheritdoc}
289
	 */
290
	public function deleteIndexes($indexes) {
291
		try {
292
			$this->indexService->deleteIndexes($this->client, $indexes);
293
		} catch (ConfigurationException $e) {
294
			throw $e;
295
		}
296
	}
297
298
299
	/**
300
	 * {@inheritdoc}
301
	 */
302
	public function searchDocuments(
303
		IFullTextSearchProvider $provider, DocumentAccess $access, $request
304
	) {
305
		try {
306
			return $this->searchService->searchDocuments(
307
				$this, $this->client, $provider, $access, $request
308
			);
309
		} catch (ConfigurationException $e) {
310
			throw $e;
311
		}
312
	}
313
314
315
	/**
316
	 * @param string $host
317
	 */
318
	private function connectToElastic($host) {
319
320
		try {
321
			$hosts = [MiscService::noEndSlash($host)];
322
			$this->client = ClientBuilder::create()
323
										 ->setHosts($hosts)
324
										 ->setRetries(2)
325
										 ->build();
326
327
		} catch (CouldNotConnectToHost $e) {
328
			echo 'CouldNotConnectToHost';
329
			$previous = $e->getPrevious();
330
			if ($previous instanceof MaxRetriesException) {
331
				echo "Max retries!";
332
			}
333
		} catch (Exception $e) {
334
			echo ' ElasticSearchPlatform::load() Exception --- ' . $e->getMessage() . "\n";
335
		}
336
	}
337
338
339
}