| 1 |
<?php
|
| 2 |
require_once 'SolrPhpClient/Apache/Solr/Service.php';
|
| 3 |
|
| 4 |
/**
|
| 5 |
* PHP 5.1 compatability code.
|
| 6 |
*/
|
| 7 |
if (!function_exists('json_decode')) {
|
| 8 |
// Zend files include other files.
|
| 9 |
set_include_path(dirname(__FILE__) . PATH_SEPARATOR . get_include_path());
|
| 10 |
require_once 'Zend/Json/Decoder.php';
|
| 11 |
|
| 12 |
/**
|
| 13 |
* Substitute for missing PHP built-in function.
|
| 14 |
*/
|
| 15 |
function json_decode($string) {
|
| 16 |
return Zend_Json_Decoder::decode($string, 0);
|
| 17 |
}
|
| 18 |
}
|
| 19 |
|
| 20 |
class Drupal_Apache_Solr_Service extends Apache_Solr_Service {
|
| 21 |
|
| 22 |
protected $luke;
|
| 23 |
protected $luke_cid;
|
| 24 |
protected $stats;
|
| 25 |
const LUKE_SERVLET = 'admin/luke';
|
| 26 |
const STATS_SERVLET = 'admin/stats.jsp';
|
| 27 |
|
| 28 |
/**
|
| 29 |
* Call the /admin/ping servlet, to test the connection to the server.
|
| 30 |
*
|
| 31 |
* @param $timeout
|
| 32 |
* maximum time to wait for ping in seconds, -1 for unlimited (default 2).
|
| 33 |
* @return
|
| 34 |
* (float) seconds taken to ping the server, FALSE if timeout occurs.
|
| 35 |
*/
|
| 36 |
public function ping($timeout = 2) {
|
| 37 |
$start = microtime(TRUE);
|
| 38 |
|
| 39 |
if ($timeout <= 0.0) {
|
| 40 |
$timeout = -1;
|
| 41 |
}
|
| 42 |
// Attempt a HEAD request to the solr ping url.
|
| 43 |
list($data, $headers) = $this->_makeHttpRequest($this->_pingUrl, 'HEAD', array(), null, $timeout);
|
| 44 |
$response = new Apache_Solr_Response($data, $headers);
|
| 45 |
|
| 46 |
if ($response->getHttpStatus() == 200) {
|
| 47 |
return microtime(TRUE) - $start;
|
| 48 |
}
|
| 49 |
else {
|
| 50 |
return FALSE;
|
| 51 |
}
|
| 52 |
}
|
| 53 |
|
| 54 |
/**
|
| 55 |
* Sets $this->luke with the meta-data about the index from admin/luke.
|
| 56 |
*/
|
| 57 |
protected function setLuke($num_terms = 0) {
|
| 58 |
if (empty($this->luke[$num_terms])) {
|
| 59 |
$url = $this->_constructUrl(self::LUKE_SERVLET, array('numTerms' => "$num_terms", 'wt' => self::SOLR_WRITER));
|
| 60 |
$this->luke[$num_terms] = $this->_sendRawGet($url);
|
| 61 |
cache_set($this->luke_cid, $this->luke);
|
| 62 |
}
|
| 63 |
}
|
| 64 |
|
| 65 |
/**
|
| 66 |
* Get just the field meta-data about the index.
|
| 67 |
*/
|
| 68 |
public function getFields($num_terms = 0) {
|
| 69 |
return $this->getLuke($num_terms)->fields;
|
| 70 |
}
|
| 71 |
|
| 72 |
/**
|
| 73 |
* Get meta-data about the index.
|
| 74 |
*/
|
| 75 |
public function getLuke($num_terms = 0) {
|
| 76 |
if (!isset($this->luke[$num_terms])) {
|
| 77 |
$this->setLuke($num_terms);
|
| 78 |
}
|
| 79 |
return $this->luke[$num_terms];
|
| 80 |
}
|
| 81 |
|
| 82 |
/**
|
| 83 |
* Sets $this->stats with the information about the Solr Core form /admin/stats.jsp
|
| 84 |
*/
|
| 85 |
protected function setStats() {
|
| 86 |
$data = $this->getLuke();
|
| 87 |
// Only try to get stats if we have connected to the index.
|
| 88 |
if (empty($this->stats) && isset($data->index->numDocs)) {
|
| 89 |
$url = $this->_constructUrl(self::STATS_SERVLET);
|
| 90 |
$this->stats_cid = "apachesolr:stats:" . md5($url);
|
| 91 |
$cache = cache_get($this->stats_cid);
|
| 92 |
if (isset($cache->data)) {
|
| 93 |
$this->stats = simplexml_load_string($cache->data);
|
| 94 |
}
|
| 95 |
else {
|
| 96 |
$response = $this->_sendRawGet($url);
|
| 97 |
$this->stats = simplexml_load_string($response->getRawResponse());
|
| 98 |
cache_set($this->stats_cid, $response->getRawResponse());
|
| 99 |
}
|
| 100 |
}
|
| 101 |
}
|
| 102 |
|
| 103 |
/**
|
| 104 |
* Get information about the Solr Core.
|
| 105 |
*
|
| 106 |
* Returns a Simple XMl document
|
| 107 |
*/
|
| 108 |
public function getStats() {
|
| 109 |
if (!isset($this->stats)) {
|
| 110 |
$this->setStats();
|
| 111 |
}
|
| 112 |
return $this->stats;
|
| 113 |
}
|
| 114 |
|
| 115 |
/**
|
| 116 |
* Get summary information about the Solr Core.
|
| 117 |
*/
|
| 118 |
public function getStatsSummary() {
|
| 119 |
$stats = $this->getStats();
|
| 120 |
$summary = array(
|
| 121 |
'@pending_docs' => '',
|
| 122 |
'@autocommit_time_seconds' => '',
|
| 123 |
'@autocommit_time' => '',
|
| 124 |
'@deletes_by_id' => '',
|
| 125 |
'@deletes_by_query' => '',
|
| 126 |
'@deletes_total' => '',
|
| 127 |
);
|
| 128 |
|
| 129 |
if (!empty($stats)) {
|
| 130 |
$docs_pending_xpath = $stats->xpath('//stat[@name="docsPending"]');
|
| 131 |
$summary['@pending_docs'] = (int) trim($docs_pending_xpath[0]);
|
| 132 |
$max_time_xpath = $stats->xpath('//stat[@name="autocommit maxTime"]');
|
| 133 |
$max_time = (int) trim(current($max_time_xpath));
|
| 134 |
// Convert to seconds.
|
| 135 |
$summary['@autocommit_time_seconds'] = $max_time / 1000;
|
| 136 |
$summary['@autocommit_time'] = format_interval($max_time / 1000);
|
| 137 |
$deletes_id_xpath = $stats->xpath('//stat[@name="deletesById"]');
|
| 138 |
$summary['@deletes_by_id'] = (int) trim($deletes_id_xpath[0]);
|
| 139 |
$deletes_query_xpath = $stats->xpath('//stat[@name="deletesByQuery"]');
|
| 140 |
$summary['@deletes_by_query'] = (int) trim($deletes_query_xpath[0]);
|
| 141 |
$summary['@deletes_total'] = $summary['@deletes_by_id'] + $summary['@deletes_by_query'];
|
| 142 |
}
|
| 143 |
|
| 144 |
return $summary;
|
| 145 |
}
|
| 146 |
|
| 147 |
/**
|
| 148 |
* Clear cached Solr data.
|
| 149 |
*/
|
| 150 |
public function clearCache() {
|
| 151 |
// Don't clear cached data if the server is unavailable.
|
| 152 |
if (@$this->ping()) {
|
| 153 |
$this->_clearCache();
|
| 154 |
}
|
| 155 |
else {
|
| 156 |
throw new Exception('No Solr instance available when trying to clear the cache.');
|
| 157 |
}
|
| 158 |
}
|
| 159 |
|
| 160 |
protected function _clearCache() {
|
| 161 |
cache_clear_all("apachesolr:luke:", 'cache', TRUE);
|
| 162 |
cache_clear_all("apachesolr:stats:", 'cache', TRUE);
|
| 163 |
$this->luke = array();
|
| 164 |
$this->stats = NULL;
|
| 165 |
}
|
| 166 |
|
| 167 |
/**
|
| 168 |
* Clear the cache whenever we commit changes.
|
| 169 |
*
|
| 170 |
* @see Apache_Solr_Service::commit()
|
| 171 |
*/
|
| 172 |
public function commit($optimize = TRUE, $waitFlush = TRUE, $waitSearcher = TRUE, $timeout = 3600) {
|
| 173 |
parent::commit($optimize, $waitFlush, $waitSearcher, $timeout);
|
| 174 |
$this->_clearCache();
|
| 175 |
}
|
| 176 |
|
| 177 |
/**
|
| 178 |
* Construct the Full URLs for the three servlets we reference
|
| 179 |
*
|
| 180 |
* @see Apache_Solr_Service::_initUrls()
|
| 181 |
*/
|
| 182 |
protected function _initUrls() {
|
| 183 |
parent::_initUrls();
|
| 184 |
$this->_lukeUrl = $this->_constructUrl(self::LUKE_SERVLET, array('numTerms' => '0', 'wt' => self::SOLR_WRITER));
|
| 185 |
}
|
| 186 |
|
| 187 |
/**
|
| 188 |
* Put Luke meta-data from the cache into $this->luke when we instantiate.
|
| 189 |
*
|
| 190 |
* @see Apache_Solr_Service::__construct()
|
| 191 |
*/
|
| 192 |
public function __construct($host = 'localhost', $port = 8180, $path = '/solr/') {
|
| 193 |
parent::__construct($host, $port, $path);
|
| 194 |
$this->luke_cid = "apachesolr:luke:" . md5($this->_lukeUrl);
|
| 195 |
$cache = cache_get($this->luke_cid);
|
| 196 |
if (isset($cache->data)) {
|
| 197 |
$this->luke = $cache->data;
|
| 198 |
}
|
| 199 |
}
|
| 200 |
|
| 201 |
/**
|
| 202 |
* Central method for making a get operation against this Solr Server
|
| 203 |
*
|
| 204 |
* @see Apache_Solr_Service::_sendRawGet()
|
| 205 |
*/
|
| 206 |
protected function _sendRawGet($url, $timeout = FALSE) {
|
| 207 |
list ($data, $headers) = $this->_makeHttpRequest($url, 'GET', array(), '', $timeout);
|
| 208 |
$response = new Apache_Solr_Response($data, $headers, $this->_createDocuments, $this->_collapseSingleValueArrays);
|
| 209 |
$code = (int) $response->getHttpStatus();
|
| 210 |
if ($code != 200) {
|
| 211 |
$message = $response->getHttpStatusMessage();
|
| 212 |
if ($code >= 400 && $code != 403 && $code != 404) {
|
| 213 |
// Add details, like Solr's exception message.
|
| 214 |
$message .= $response->getRawResponse();
|
| 215 |
}
|
| 216 |
throw new Exception('"' . $code . '" Status: ' . $message);
|
| 217 |
}
|
| 218 |
return $response;
|
| 219 |
}
|
| 220 |
|
| 221 |
/**
|
| 222 |
* Central method for making a post operation against this Solr Server
|
| 223 |
*
|
| 224 |
* @see Apache_Solr_Service::_sendRawGet()
|
| 225 |
*/
|
| 226 |
protected function _sendRawPost($url, $rawPost, $timeout = FALSE, $contentType = 'text/xml; charset=UTF-8') {
|
| 227 |
if (variable_get('apachesolr_read_only', 0)) {
|
| 228 |
throw new Exception('Operating in read-only mode; updates are disabled.');
|
| 229 |
}
|
| 230 |
$request_headers = array('Content-Type' => $contentType);
|
| 231 |
list ($data, $headers) = $this->_makeHttpRequest($url, 'POST', $request_headers, $rawPost, $timeout);
|
| 232 |
$response = new Apache_Solr_Response($data, $headers, $this->_createDocuments, $this->_collapseSingleValueArrays);
|
| 233 |
$code = (int) $response->getHttpStatus();
|
| 234 |
if ($code != 200) {
|
| 235 |
$message = $response->getHttpStatusMessage();
|
| 236 |
if ($code >= 400 && $code != 403 && $code != 404) {
|
| 237 |
// Add details, like Solr's exception message.
|
| 238 |
$message .= $response->getRawResponse();
|
| 239 |
}
|
| 240 |
throw new Exception('"' . $code . '" Status: ' . $message);
|
| 241 |
}
|
| 242 |
return $response;
|
| 243 |
}
|
| 244 |
|
| 245 |
protected function _makeHttpRequest($url, $method = 'GET', $headers = array(), $content = '', $timeout = FALSE) {
|
| 246 |
// Set a response timeout
|
| 247 |
if ($timeout) {
|
| 248 |
$default_socket_timeout = ini_set('default_socket_timeout', $timeout);
|
| 249 |
}
|
| 250 |
$result = drupal_http_request($url, $headers, $method, $content);
|
| 251 |
// Restore the response timeout
|
| 252 |
if ($timeout) {
|
| 253 |
ini_set('default_socket_timeout', $default_socket_timeout);
|
| 254 |
}
|
| 255 |
|
| 256 |
// This will no longer be needed after http://drupal.org/node/345591 is committed
|
| 257 |
$responses = array(
|
| 258 |
0 => 'Request failed',
|
| 259 |
100 => 'Continue', 101 => 'Switching Protocols',
|
| 260 |
200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content',
|
| 261 |
300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 307 => 'Temporary Redirect',
|
| 262 |
400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Time-out', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', 415 => 'Unsupported Media Type', 416 => 'Requested range not satisfiable', 417 => 'Expectation Failed',
|
| 263 |
500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported'
|
| 264 |
);
|
| 265 |
|
| 266 |
if (!isset($result->code) || $result->code < 0) {
|
| 267 |
$result->code = 0;
|
| 268 |
}
|
| 269 |
|
| 270 |
if (isset($result->error)) {
|
| 271 |
$responses[0] .= ': ' . check_plain($result->error);
|
| 272 |
}
|
| 273 |
|
| 274 |
if (!isset($result->data)) {
|
| 275 |
$result->data = '';
|
| 276 |
}
|
| 277 |
|
| 278 |
if (!isset($responses[$result->code])) {
|
| 279 |
$result->code = floor($result->code / 100) * 100;
|
| 280 |
}
|
| 281 |
|
| 282 |
$protocol = "HTTP/1.1";
|
| 283 |
$headers[] = "{$protocol} {$result->code} {$responses[$result->code]}";
|
| 284 |
if (isset($result->headers)) {
|
| 285 |
foreach ($result->headers as $name => $value) {
|
| 286 |
$headers[] = "$name: $value";
|
| 287 |
}
|
| 288 |
}
|
| 289 |
return array($result->data, $headers);
|
| 290 |
}
|
| 291 |
}
|