1
|
|
|
<?php |
2
|
|
|
namespace ApacheSolrForTypo3\Solr\System\Solr\Service; |
3
|
|
|
|
4
|
|
|
/*************************************************************** |
5
|
|
|
* Copyright notice |
6
|
|
|
* |
7
|
|
|
* (c) 2009-2017 Timo Hund <[email protected]> |
8
|
|
|
* All rights reserved |
9
|
|
|
* |
10
|
|
|
* This script is part of the TYPO3 project. The TYPO3 project is |
11
|
|
|
* free software; you can redistribute it and/or modify |
12
|
|
|
* it under the terms of the GNU General Public License as published by |
13
|
|
|
* the Free Software Foundation; either version 2 of the License, or |
14
|
|
|
* (at your option) any later version. |
15
|
|
|
* |
16
|
|
|
* The GNU General Public License can be found at |
17
|
|
|
* http://www.gnu.org/copyleft/gpl.html. |
18
|
|
|
* |
19
|
|
|
* This script is distributed in the hope that it will be useful, |
20
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
21
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
22
|
|
|
* GNU General Public License for more details. |
23
|
|
|
* |
24
|
|
|
* This copyright notice MUST APPEAR in all copies of the script! |
25
|
|
|
***************************************************************/ |
26
|
|
|
|
27
|
|
|
use ApacheSolrForTypo3\Solr\PingFailedException; |
28
|
|
|
use ApacheSolrForTypo3\Solr\System\Configuration\TypoScriptConfiguration; |
29
|
|
|
use ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager; |
30
|
|
|
use ApacheSolrForTypo3\Solr\Util; |
31
|
|
|
use TYPO3\CMS\Core\Utility\GeneralUtility; |
32
|
|
|
|
33
|
|
|
abstract class AbstractSolrService extends \Apache_Solr_Service { |
34
|
|
|
|
35
|
|
|
const SCHEME_HTTP = 'http'; |
36
|
|
|
const SCHEME_HTTPS = 'https'; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* @var array |
40
|
|
|
*/ |
41
|
|
|
protected static $pingCache = []; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* Server connection scheme. http or https. |
45
|
|
|
* |
46
|
|
|
* @var string |
47
|
|
|
*/ |
48
|
|
|
protected $_scheme = self::SCHEME_HTTP; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* @var TypoScriptConfiguration |
52
|
|
|
*/ |
53
|
|
|
protected $configuration; |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* @var \ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager |
57
|
|
|
*/ |
58
|
|
|
protected $logger = null; |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* SolrAdminService constructor. |
62
|
|
|
* @param string $host |
63
|
|
|
* @param string $port |
64
|
|
|
* @param string $path |
65
|
|
|
* @param string $scheme |
66
|
|
|
* @param TypoScriptConfiguration $typoScriptConfiguration |
67
|
|
|
* @param SolrLogManager $logManager |
68
|
|
|
*/ |
69
|
|
|
public function __construct($host = '', $port = '8983', $path = '/solr/', $scheme = 'http', $typoScriptConfiguration = null, $logManager = null) |
70
|
|
|
{ |
71
|
|
|
$this->setScheme($scheme); |
72
|
|
|
parent::__construct($host, $port, $path); |
73
|
|
|
|
74
|
|
|
$this->configuration = is_null($typoScriptConfiguration) ? Util::getSolrConfiguration() : $typoScriptConfiguration; |
75
|
|
|
$this->logger = is_null($logManager) ? GeneralUtility::makeInstance(SolrLogManager::class, __CLASS__) : $logManager; |
76
|
|
|
$this->initializeTimeoutFromConfiguration(); |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* Initializes the timeout from TypoScript when configuration is present. |
81
|
|
|
* |
82
|
|
|
* @return void |
83
|
|
|
*/ |
84
|
|
|
protected function initializeTimeoutFromConfiguration() |
85
|
|
|
{ |
86
|
|
|
$timeout = $this->configuration->getSolrTimeout(); |
87
|
|
|
if ($timeout > 0) { |
88
|
|
|
$this->getHttpTransport()->setDefaultTimeout($timeout); |
89
|
|
|
} |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* Creates a string representation of the Solr connection. Specifically |
94
|
|
|
* will return the Solr URL. |
95
|
|
|
* |
96
|
|
|
* @return string The Solr URL. |
97
|
|
|
*/ |
98
|
|
|
public function __toString() |
99
|
|
|
{ |
100
|
|
|
return $this->_scheme . '://' . $this->_host . ':' . $this->_port . $this->_path; |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
/** |
104
|
|
|
* Returns the set scheme |
105
|
|
|
* |
106
|
|
|
* @return string |
107
|
|
|
*/ |
108
|
|
|
public function getScheme() |
109
|
|
|
{ |
110
|
|
|
return $this->_scheme; |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* Set the scheme used. If empty will fallback to constants |
115
|
|
|
* |
116
|
|
|
* @param string $scheme Either http or https |
117
|
|
|
* @throws \UnexpectedValueException |
118
|
|
|
*/ |
119
|
|
|
public function setScheme($scheme) |
120
|
|
|
{ |
121
|
|
|
// Use the provided scheme or use the default |
122
|
|
|
if (empty($scheme)) { |
123
|
|
|
throw new \UnexpectedValueException('Scheme parameter is empty', 1380756390); |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
$isHttpOrHttps = in_array($scheme, [self::SCHEME_HTTP, self::SCHEME_HTTPS]); |
127
|
|
|
if (!$isHttpOrHttps) { |
128
|
|
|
throw new \UnexpectedValueException('Unsupported scheme parameter, scheme must be http or https', 1380756442); |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
// we have a valid scheme |
132
|
|
|
$this->_scheme = $scheme; |
133
|
|
|
|
134
|
|
|
if ($this->_urlsInited) { |
135
|
|
|
$this->_initUrls(); |
136
|
|
|
} |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
/** |
140
|
|
|
* Return a valid http URL given this server's scheme, host, port, and path |
141
|
|
|
* and a provided servlet name. |
142
|
|
|
* |
143
|
|
|
* @param string $servlet Servlet name |
144
|
|
|
* @param array $params Additional URL parameters to attach to the end of the URL |
145
|
|
|
* @return string Servlet URL |
146
|
|
|
*/ |
147
|
|
|
protected function _constructUrl($servlet, $params = []) |
148
|
|
|
{ |
149
|
|
|
$url = parent::_constructUrl($servlet, $params); |
150
|
|
|
|
151
|
|
|
if (!GeneralUtility::isFirstPartOfStr($url, $this->_scheme)) { |
152
|
|
|
$parsedUrl = parse_url($url); |
153
|
|
|
|
154
|
|
|
// unfortunately can't use str_replace as it replace all |
155
|
|
|
// occurrences of $needle and can't be limited to replace only once |
156
|
|
|
$url = $this->_scheme . substr($url, strlen($parsedUrl['scheme'])); |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
return $url; |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* Central method for making a get operation against this Solr Server |
164
|
|
|
* |
165
|
|
|
* @param string $url |
166
|
|
|
* @param float|bool $timeout Read timeout in seconds |
167
|
|
|
* @return \Apache_Solr_Response |
168
|
|
|
*/ |
169
|
|
|
protected function _sendRawGet($url, $timeout = false) |
170
|
|
|
{ |
171
|
|
|
$logSeverity = SolrLogManager::INFO; |
172
|
|
|
$exception = null; |
173
|
|
|
|
174
|
|
|
try { |
175
|
|
|
$response = parent::_sendRawGet($url, $timeout); |
|
|
|
|
176
|
|
|
} catch (\Apache_Solr_HttpTransportException $exception) { |
177
|
|
|
$logSeverity = SolrLogManager::ERROR; |
178
|
|
|
$response = $exception->getResponse(); |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
if ($this->configuration->getLoggingQueryRawGet() || $response->getHttpStatus() != 200) { |
182
|
|
|
$this->writeLog($logSeverity, 'Querying Solr using GET', $url, $response, $exception); |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
return $response; |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* Central method for making a HTTP DELETE operation against the Solr server |
190
|
|
|
* |
191
|
|
|
* @param string $url |
192
|
|
|
* @param bool|float $timeout Read timeout in seconds |
193
|
|
|
* @return \Apache_Solr_Response |
194
|
|
|
*/ |
195
|
|
|
protected function _sendRawDelete($url, $timeout = false) |
196
|
|
|
{ |
197
|
|
|
$logSeverity = SolrLogManager::INFO; |
198
|
|
|
$exception = null; |
199
|
|
|
|
200
|
|
|
try { |
201
|
|
|
$httpTransport = $this->getHttpTransport(); |
202
|
|
|
$httpResponse = $httpTransport->performDeleteRequest($url, $timeout); |
203
|
|
|
$solrResponse = new \Apache_Solr_Response($httpResponse, $this->_createDocuments, $this->_collapseSingleValueArrays); |
204
|
|
|
|
205
|
|
|
if ($solrResponse->getHttpStatus() != 200) { |
206
|
|
|
throw new \Apache_Solr_HttpTransportException($solrResponse); |
207
|
|
|
} |
208
|
|
|
} catch (\Apache_Solr_HttpTransportException $exception) { |
209
|
|
|
$logSeverity = SolrLogManager::ERROR; |
210
|
|
|
$solrResponse = $exception->getResponse(); |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
if ($this->configuration->getLoggingQueryRawDelete() || $solrResponse->getHttpStatus() != 200) { |
214
|
|
|
$this->writeLog($logSeverity, 'Querying Solr using DELETE', $url, $solrResponse, $exception); |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
return $solrResponse; |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
/** |
221
|
|
|
* Central method for making a post operation against this Solr Server |
222
|
|
|
* |
223
|
|
|
* @param string $url |
224
|
|
|
* @param string $rawPost |
225
|
|
|
* @param float|bool $timeout Read timeout in seconds |
226
|
|
|
* @param string $contentType |
227
|
|
|
* @return \Apache_Solr_Response |
228
|
|
|
*/ |
229
|
|
|
protected function _sendRawPost($url, $rawPost, $timeout = false, $contentType = 'text/xml; charset=UTF-8') |
230
|
|
|
{ |
231
|
|
|
$logSeverity = SolrLogManager::INFO; |
232
|
|
|
$exception = null; |
233
|
|
|
|
234
|
|
|
try { |
235
|
|
|
$response = parent::_sendRawPost($url, $rawPost, $timeout, $contentType); |
|
|
|
|
236
|
|
|
} catch (\Apache_Solr_HttpTransportException $exception) { |
237
|
|
|
$logSeverity = SolrLogManager::ERROR; |
238
|
|
|
$response = $exception->getResponse(); |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
if ($this->configuration->getLoggingQueryRawPost() || $response->getHttpStatus() != 200) { |
242
|
|
|
$this->writeLog($logSeverity, 'Querying Solr using POST', $url, $response, $exception, $rawPost); |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
return $response; |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
|
249
|
|
|
/** |
250
|
|
|
* Build the log data and writes the message to the log |
251
|
|
|
* |
252
|
|
|
* @param integer $logSeverity |
253
|
|
|
* @param string $message |
254
|
|
|
* @param string $url |
255
|
|
|
* @param \Apache_Solr_Response $solrResponse |
256
|
|
|
* @param \Exception $exception |
257
|
|
|
* @param string $contentSend |
258
|
|
|
*/ |
259
|
|
|
protected function writeLog($logSeverity, $message, $url, $solrResponse, $exception = null, $contentSend = '') |
260
|
|
|
{ |
261
|
|
|
$logData = $this->buildLogDataFromResponse($solrResponse, $exception, $url, $contentSend); |
262
|
|
|
$this->logger->log($logSeverity, $message, $logData); |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
/** |
266
|
|
|
* Parses the solr information to build data for the logger. |
267
|
|
|
* |
268
|
|
|
* @param \Apache_Solr_Response $solrResponse |
269
|
|
|
* @param \Exception $e |
270
|
|
|
* @param string $url |
271
|
|
|
* @param string $contentSend |
272
|
|
|
* @return array |
273
|
|
|
*/ |
274
|
|
|
protected function buildLogDataFromResponse(\Apache_Solr_Response $solrResponse, \Exception $e = null, $url = '', $contentSend = '') |
275
|
|
|
{ |
276
|
|
|
$logData = ['query url' => $url, 'response' => (array)$solrResponse]; |
277
|
|
|
|
278
|
|
|
if ($contentSend !== '') { |
279
|
|
|
$logData['content'] = $contentSend; |
280
|
|
|
} |
281
|
|
|
|
282
|
|
|
if (!empty($e)) { |
283
|
|
|
$logData['exception'] = $e->__toString(); |
284
|
|
|
return $logData; |
285
|
|
|
} else { |
286
|
|
|
// trigger data parsing |
287
|
|
|
$solrResponse->response; |
|
|
|
|
288
|
|
|
$logData['response data'] = print_r($solrResponse, true); |
289
|
|
|
return $logData; |
290
|
|
|
} |
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
|
294
|
|
|
/** |
295
|
|
|
* Returns the core name from the configured path without the core name. |
296
|
|
|
* |
297
|
|
|
* @return string |
298
|
|
|
*/ |
299
|
|
|
public function getCoreBasePath() |
300
|
|
|
{ |
301
|
|
|
$pathWithoutLeadingAndTrailingSlashes = trim(trim($this->_path), "/"); |
302
|
|
|
$pathWithoutLastSegment = substr($pathWithoutLeadingAndTrailingSlashes, 0, strrpos($pathWithoutLeadingAndTrailingSlashes, "/")); |
303
|
|
|
return '/' . $pathWithoutLastSegment . '/'; |
304
|
|
|
} |
305
|
|
|
|
306
|
|
|
/** |
307
|
|
|
* Returns the core name from the configured path. |
308
|
|
|
* |
309
|
|
|
* @return string |
310
|
|
|
*/ |
311
|
|
|
public function getCoreName() |
312
|
|
|
{ |
313
|
|
|
$paths = explode('/', trim($this->_path, '/')); |
314
|
|
|
return (string)array_pop($paths); |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
/** |
318
|
|
|
* Call the /admin/ping servlet, can be used to quickly tell if a connection to the |
319
|
|
|
* server is available. |
320
|
|
|
* |
321
|
|
|
* Simply overrides the SolrPhpClient implementation, changing ping from a |
322
|
|
|
* HEAD to a GET request, see http://forge.typo3.org/issues/44167 |
323
|
|
|
* |
324
|
|
|
* Also does not report the time, see https://forge.typo3.org/issues/64551 |
325
|
|
|
* |
326
|
|
|
* @param int $timeout maximum time to wait for ping in seconds, -1 for unlimited (default is 2) |
327
|
|
|
* @param boolean $useCache indicates if the ping result should be cached in the instance or not |
328
|
|
|
* @return bool TRUE if Solr can be reached, FALSE if not |
329
|
|
|
*/ |
330
|
|
|
public function ping($timeout = 2, $useCache = true) |
331
|
|
|
{ |
332
|
|
|
$httpResponse = $this->performPingRequest($timeout, $useCache); |
333
|
|
|
return ($httpResponse->getStatusCode() === 200); |
|
|
|
|
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
/** |
337
|
|
|
* Call the /admin/ping servlet, can be used to get the runtime of a ping request. |
338
|
|
|
* |
339
|
|
|
* @param int $timeout maximum time to wait for ping in seconds, -1 for unlimited (default is 2) |
340
|
|
|
* @param boolean $useCache indicates if the ping result should be cached in the instance or not |
341
|
|
|
* @return double runtime in milliseconds |
342
|
|
|
* @throws \ApacheSolrForTypo3\Solr\PingFailedException |
343
|
|
|
*/ |
344
|
|
|
public function getPingRoundTripRuntime($timeout = 2, $useCache = true) |
345
|
|
|
{ |
346
|
|
|
$start = $this->getMilliseconds(); |
347
|
|
|
$httpResponse = $this->performPingRequest($timeout, $useCache); |
348
|
|
|
$end = $this->getMilliseconds(); |
349
|
|
|
|
350
|
|
|
if ($httpResponse->getStatusCode() !== 200) { |
351
|
|
|
$message = 'Solr ping failed with unexpected response code: ' . $httpResponse->getStatusCode(); |
352
|
|
|
/** @var $exception \ApacheSolrForTypo3\Solr\PingFailedException */ |
353
|
|
|
$exception = GeneralUtility::makeInstance(PingFailedException::class, $message); |
354
|
|
|
$exception->setHttpResponse($httpResponse); |
355
|
|
|
throw $exception; |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
return $end - $start; |
359
|
|
|
} |
360
|
|
|
|
361
|
|
|
/** |
362
|
|
|
* Make a request to a servlet (a path) that's not a standard path. |
363
|
|
|
* |
364
|
|
|
* @param string $servlet Path to be added to the base Solr path. |
365
|
|
|
* @param array $parameters Optional, additional request parameters when constructing the URL. |
366
|
|
|
* @param string $method HTTP method to use, defaults to GET. |
367
|
|
|
* @param array $requestHeaders Key value pairs of header names and values. Should include 'Content-Type' for POST and PUT. |
368
|
|
|
* @param string $rawPost Must be an empty string unless method is POST or PUT. |
369
|
|
|
* @param float|bool $timeout Read timeout in seconds, defaults to FALSE. |
370
|
|
|
* @return \Apache_Solr_Response Response object |
371
|
|
|
* @throws \Apache_Solr_HttpTransportException if returned HTTP status is other than 200 |
372
|
|
|
*/ |
373
|
|
|
public function requestServlet($servlet, $parameters = [], $method = 'GET', $requestHeaders = [], $rawPost = '', $timeout = false) |
374
|
|
|
{ |
375
|
|
|
// Add default parameters |
376
|
|
|
$parameters['wt'] = self::SOLR_WRITER; |
377
|
|
|
$parameters['json.nl'] = $this->_namedListTreatment; |
378
|
|
|
$url = $this->_constructUrl($servlet, $parameters); |
379
|
|
|
|
380
|
|
|
$httpResponse = $this->getResponseFromTransport($url, $method, $requestHeaders, $rawPost, $timeout); |
381
|
|
|
$solrResponse = new \Apache_Solr_Response($httpResponse, $this->_createDocuments, $this->_collapseSingleValueArrays); |
382
|
|
|
if ($solrResponse->getHttpStatus() != 200) { |
383
|
|
|
throw new \Apache_Solr_HttpTransportException($solrResponse); |
384
|
|
|
} |
385
|
|
|
|
386
|
|
|
return $solrResponse; |
387
|
|
|
} |
388
|
|
|
|
389
|
|
|
/** |
390
|
|
|
* Decides which transport method to used, depending on the request method and retrieves the response. |
391
|
|
|
* |
392
|
|
|
* @param string $url |
393
|
|
|
* @param string $method |
394
|
|
|
* @param array $requestHeaders |
395
|
|
|
* @param string $rawPost |
396
|
|
|
* @param float|bool $timeout |
397
|
|
|
* @return \Apache_Solr_HttpTransport_Response |
398
|
|
|
*/ |
399
|
|
|
protected function getResponseFromTransport($url, $method, $requestHeaders, $rawPost, $timeout) |
400
|
|
|
{ |
401
|
|
|
$httpTransport = $this->getHttpTransport(); |
402
|
|
|
|
403
|
|
|
if ($method == self::METHOD_GET) { |
404
|
|
|
return $httpTransport->performGetRequest($url, $timeout); |
405
|
|
|
} |
406
|
|
|
if ($method == self::METHOD_POST) { |
407
|
|
|
// FIXME should respect all headers, not only Content-Type |
408
|
|
|
return $httpTransport->performPostRequest($url, $rawPost, $requestHeaders['Content-Type'], $timeout); |
409
|
|
|
} |
410
|
|
|
|
411
|
|
|
throw new \InvalidArgumentException('$method should be GET or POST'); |
412
|
|
|
} |
413
|
|
|
|
414
|
|
|
/** |
415
|
|
|
* Performs a ping request and returns the result. |
416
|
|
|
* |
417
|
|
|
* @param int $timeout |
418
|
|
|
* @param boolean $useCache indicates if the ping result should be cached in the instance or not |
419
|
|
|
* @return \Apache_Solr_HttpTransport_Response |
420
|
|
|
*/ |
421
|
|
|
protected function performPingRequest($timeout = 2, $useCache = true) |
422
|
|
|
{ |
423
|
|
|
$cacheKey = (string)($this); |
424
|
|
|
if ($useCache && isset(static::$pingCache[$cacheKey])) { |
425
|
|
|
return static::$pingCache[$cacheKey]; |
426
|
|
|
} |
427
|
|
|
|
428
|
|
|
$pingResult = $this->getHttpTransport()->performGetRequest($this->_pingUrl, $timeout); |
429
|
|
|
|
430
|
|
|
if ($useCache) { |
431
|
|
|
static::$pingCache[$cacheKey] = $pingResult; |
432
|
|
|
} |
433
|
|
|
|
434
|
|
|
return $pingResult; |
435
|
|
|
} |
436
|
|
|
|
437
|
|
|
/** |
438
|
|
|
* Returns the current time in milliseconds. |
439
|
|
|
* |
440
|
|
|
* @return double |
441
|
|
|
*/ |
442
|
|
|
protected function getMilliseconds() |
443
|
|
|
{ |
444
|
|
|
return GeneralUtility::milliseconds(); |
445
|
|
|
} |
446
|
|
|
} |
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.