UNPKG

17.6 kBJavaScriptView Raw
1/*!
2 * solr client
3 * Copyright(c) 2011-2012 HipSnip Limited
4 * Author Rémy Loubradou <remyloubradou@gmail.com>
5 * MIT Licensed
6 */
7
8/**
9 * Load dependencies
10 */
11
12var http = require('http'),
13 Query = require('./query'),
14 querystring = require('querystring'),
15 format = require('./utils/format'),
16 SolrError = require('./error/solr-error'),
17 JSONStream = require('JSONStream'),
18 duplexer = require('duplexer'),
19 request = require('request');
20
21/**
22 * Expose `createClient()`.
23 */
24
25exports.createClient = createClient;
26
27/**
28 * Create an instance of `Client`
29 *
30 * @param {String|Object} [host='127.0.0.1'] - IP address or host address of the Solr server
31 * @param {Number|String} [port='8983'] - port of the Solr server
32 * @param {String} [core=''] - name of the Solr core requested
33 * @param {String} [path='/solr'] - root path of all requests
34 *
35 * @return {Client}
36 * @api public
37 */
38
39function createClient(host, port, core, path){
40 var options = (typeof host === 'object')? host : {
41 host : host,
42 port : port,
43 core : core,
44 path : path
45 };
46 return new Client(options);
47}
48
49/**
50 * Create a new `Client`
51 * @constructor
52 *
53 * @param {Object} options - set of options used to request the Solr server
54 * @param {String} options.host - IP address or host address of the Solr server
55 * @param {Number|String} options.port - port of the Solr server
56 * @param {String} options.core - name of the Solr core requested
57 * @param {String} options.path - root path of all requests
58 *
59 * @return {Client}
60 * @api private
61 */
62
63var Client = function(options){
64 this.options = {
65 host : options.host || '127.0.0.1',
66 port : options.port || '8983',
67 core : options.core || '',
68 path : options.path || '/solr'
69 };
70 this.data = {};
71 this.autoCommit = false;
72}
73
74/**
75 * Create credential using the basic access authentication method
76 *
77 * @param {String} username
78 * @param {String} password
79 *
80 * @return {Client}
81 * @api public
82 */
83
84Client.prototype.basicAuth = function(username,password){
85 var self = this;
86 this.options.authorization = 'Basic ' + new Buffer(username + ':' + password).toString('base64');
87 return self;
88}
89
90/**
91 * Remove authorization header
92 *
93 * @return {Client}
94 * @api public
95 */
96
97Client.prototype.unauth = function(){
98 var self = this;
99 delete this.options.authorization;
100 return self;
101}
102
103/**
104 * Add a document or a list of documents
105 *
106 * @param {Object|Array} doc - document or list of documents to add into the Solr database
107 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
108 * @param {Error} callback().err
109 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
110 *
111 * @return {Client}
112 * @api public
113 */
114
115Client.prototype.add = function(docs,callback){
116 var self = this;
117 docs = format.dateISOify(docs); // format `Date` object into string understable for Solr as a date.
118 docs = Array.isArray(docs) ? docs : [docs];
119 this.update(docs,callback);
120 return self;
121}
122
123/**
124 * Add the remote resource located at the given path `options.path` into the Solr database.
125 *
126 * @param {Object} options -
127 * @param {String} options.path - path of the file. HTTP URL, the full path or a path relative to the CWD of the running solr server must be used.
128 * @param {String} [options.format='xml'] - format of the resource. XML, CSV or JSON formats must be used.
129 * @param {String} [options.contentType='text/plain;charset=utf-8'] - content type of the resource
130 * @param {Object} [options.parameters] - set of extras parameters pass along in the query.
131 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
132 * @param {Error} callback().err
133 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
134 *
135 * @return {Client}
136 * @api public
137 */
138
139Client.prototype.addRemoteResource = function(options,callback){
140 var self = this;
141 options.parameters = options.parameters || {};
142 options.format = (options.format === 'xml' ? '' : options.format || ''); // reason: the default route of the XmlUpdateRequestHandle is /update and not /update/xml.
143 options.parameters.commit = (options.parameters.commit === undefined ? this.autoCommit : options.parameters.commit);
144 options.parameters['stream.contentType'] = options.contentType || 'text/plain;charset=utf-8';
145 if(options.path.match(/^https?:\/\//)){
146 options.parameters['stream.url'] = options.path;
147 }else{
148 options.parameters['stream.file'] = options.path;
149 }
150 var path = 'update/' + options.format.toLowerCase() + '?' + querystring.stringify(options.parameters) + '&wt=json';
151 this.options.fullPath = [this.options.path, this.options.core, path]
152 .filter(function(element){
153 if(element) return true;
154 return false;
155 })
156 .join('/');
157 queryRequest(this.options,callback);
158 return self;
159}
160
161/**
162 * Create a writable/readable `Stream` to add documents into the Solr database
163 *
164 * return {Stream}
165 * @api public
166 */
167
168Client.prototype.createAddStream = function(){
169 var path = [this.options.path,this.options.core,'update/json?commit='+ this.autoCommit +'&wt=json']
170 .filter(function(element){
171 if(element) return true;
172 return false;
173 })
174 .join('/');
175 var headers = {
176 'content-type' : 'application/json',
177 'charset' : 'utf-8'
178 };
179 if(this.options.authorization){
180 headers['authorization'] = this.options.authorization;
181 }
182 var optionsRequest = {
183 url : 'http://' + this.options.host +':' + this.options.port + path ,
184 method : 'POST',
185 headers : headers
186 };
187 var jsonStreamStringify = JSONStream.stringify();
188 var postRequest = request(optionsRequest);
189 jsonStreamStringify.pipe(postRequest);
190 var duplex = duplexer(jsonStreamStringify,postRequest);
191 return duplex ;
192}
193
194/**
195 * Commit last added and removed documents, that means your documents are now indexed.
196 *
197 * @param {Object} options
198 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
199 * @param {Error} callback().err
200 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
201 *
202 * @return {Client}
203 * @api private
204 */
205
206Client.prototype.commit = function(options,callback){
207 if(callback === undefined){
208 callback = options;
209 options = undefined;
210 }
211 this.data = {};
212 this.data['commit'] = {};
213
214 if( options !== undefined ){
215 var availableAttributes = ['waitFlush','waitSeacher'];
216 for ( var i = 0; i < availableAttributes.length ; i++){
217 if ( options[availableAttributes [i]] !== undefined ){
218 this.data['commit'][availableAttributes[i]] = options[availableAttributes[i]];
219 }
220 }
221 }
222 this.update(this.data,callback);
223}
224
225/**
226 * Delete documents based on the given `field` and `text`.
227 *
228 * @param {String} field
229 * @param {String} text
230 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
231 * @param {Error} callback().err
232 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
233 *
234 * @return {Client}
235 * @api public
236 */
237
238Client.prototype.delete = function(field,text,callback) {
239 var self = this;
240 text = format.dateISOify(text);
241 this.data = {};
242 this.data['delete'] = {query : field + ':' + text};
243 this.update(this.data,callback);
244 return self;
245}
246
247/**
248 * Delete a range of documents based on the given `field`, `start` and `stop` arguments.
249 *
250 * @param {String} field
251 * @param {String|Date} start
252 * @param {String|Date} stop
253 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
254 * @param {Error} callback().err
255 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
256 *
257 * @return {Client}
258 * @api public
259 */
260
261Client.prototype.deleteByRange = function(field,start,stop,callback){
262 var self = this;
263 start = format.dateISOify(start);
264 stop = format.dateISOify(stop);
265 this.data = {};
266 this.data['delete'] = { query : field + ':[' + start + ' TO ' + stop + ']' };
267 this.update(this.data,callback);
268 return self;
269}
270
271/**
272 * Delete the document with the given `id`
273 *
274 * @param {String|Number} id - id of the document you want to delete
275 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
276 * @param {Error} callback().err
277 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
278 *
279 * @return {Client}
280 * @api public
281 */
282
283Client.prototype.deleteByID = function(id,callback){
284 var self = this;
285 this.data = {};
286 this.data['delete'] = {id : id.toString()};
287 this.update(this.data,callback);
288 return self;
289}
290
291/**
292 * Delete documents matching the given `query`
293 *
294 * @param {String} query -
295 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
296 * @param {Error} callback().err
297 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
298 *
299 * @return {Client}
300 * @api public
301 */
302
303Client.prototype.deleteByQuery = function(query,callback){
304 var self = this;
305 this.data = {};
306 this.data['delete'] = {query : query};
307 this.update(this.data,callback);
308 return self;
309}
310
311/**
312 * Optimize the index
313 *
314 * @param {Object} options -
315 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
316 * @param {Error} callback().err
317 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
318 *
319 * @return {Client}
320 * @api public
321 */
322
323Client.prototype.optimize = function(options,callback){
324 if(callback === undefined){
325 callback = options;
326 options = undefined;
327 }
328 this.data = {};
329 this.data['optimize'] = {};
330 if( options !== undefined ){
331 var availableAttributes = ['waitFlush','waitSearcher'];
332 for ( var i = 0; i < availableAttributes.length ; i++){
333 if ( options[availableAttributes [i]] !== undefined ){
334 this.data['optimize'][availableAttributes[i]] = options[availableAttributes[i]];
335 }
336 }
337 }
338 this.update(this.data,callback);
339}
340
341/**
342 * Rollback all add/delete commands made since the last commit.
343 *
344 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
345 * @param {Error} callback().err
346 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
347 *
348 * @return {Client}
349 * @api public
350 */
351
352Client.prototype.rollback = function(callback){
353 this.data = {};
354 this.data['rollback'] = {};
355 this.update(this.data,callback);
356}
357
358/**
359 * Send an update command to the Solr server with the given `data` stringified in the body.
360 *
361 * @param {Object} data - data sent to the Solr server
362 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
363 * @param {Error} callback().err
364 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
365 *
366 * @return {Client}
367 * @api private
368 */
369
370Client.prototype.update = function(data,callback){
371 var self = this;
372 this.options.json = JSON.stringify(data);
373 this.options.fullPath = [this.options.path,this.options.core,'update/json?commit='+ this.autoCommit +'&wt=json']
374 .filter(function(element){
375 if(element) return true;
376 return false;
377 })
378 .join('/');
379 updateRequest(this.options,callback);
380 return self;
381}
382
383/**
384 * Search documents matching the `query`
385 *
386 * @param {Query|String} query
387 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
388 * @param {Error} callback().err
389 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
390 *
391 * @return {Client}
392 * @api public
393 */
394
395Client.prototype.search = function(query,callback){
396 var self = this;
397 // Allow to be more flexible allow query to be a string and not only a Query object
398 var parameters = query.build ? query.build() : query;
399 this.options.fullPath = [this.options.path,this.options.core,'select?' + parameters + '&wt=json']
400 .filter(function(element){
401 if(element) return true;
402 return false;
403 })
404 .join('/'); ;
405 queryRequest(this.options,callback);
406 return self;
407}
408
409/**
410 * Create an instance of `Query`
411 *
412 * @return {Query}
413 * @api public
414 */
415
416Client.prototype.createQuery = function(){
417 return new Query();
418}
419
420
421/**
422 * Ping the Solr server
423 *
424 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
425 * @param {Error} callback().err
426 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
427 *
428 * @return {Client}
429 * @api public
430 */
431
432Client.prototype.ping = function(callback){
433 var self = this;
434 this.options.fullPath = [this.options.path,this.options.core,'admin/ping?wt=json']
435 .filter(function(element){
436 if(element) return true;
437 return false;
438 })
439 .join('/');
440 queryRequest(this.options,callback);
441 return self;
442}
443
444/**
445 * HTTP POST request. Send update commands to the Solr server (commit, add, delete, optimize)
446 *
447 * @param {Object} params
448 * @param {String} params.host - IP address or host address of the Solr server
449 * @param {Number|String} params.port - port of the Solr server
450 * @param {String} params.core - name of the Solr core requested
451 * @param {String} params.authorization - value of the authorization header
452 * @param {String} params.fullPath - full path of the request
453 * @param {String} params.json
454 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
455 * @param {Error} callback().err
456 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
457 *
458 * @api private
459 */
460
461var updateRequest = function(params,callback){
462 var headers = {
463 'content-type' : 'application/json',
464 'charset' : 'utf-8',
465 'content-length': Buffer.byteLength(params.json),
466 };
467 if(params.authorization){
468 headers['authorization'] = params.authorization;
469 }
470 var options = {
471 host : params.host,
472 port : params.port,
473 method : 'POST',
474 headers : headers,
475 path : params.fullPath
476 };
477
478 var callbackResponse = function(res){
479 var buffer = '';
480 var err = null;
481 res.on('data',function(chunk){
482 buffer += chunk;
483 });
484
485 res.on('end',function(){
486 if(res.statusCode !== 200){
487 err = new SolrError(res.statusCode,buffer);
488 if (callback) callback(err,null);
489 }else{
490 try{
491 var data = JSON.parse(buffer);
492 }catch(error){
493 err = error;
494 }finally{
495 if (callback) callback(err,data);
496 }
497 }
498 });
499 }
500
501 var request = http.request(options,callbackResponse);
502
503 request.on('error',function(err){
504 if (callback) callback(err,null);
505 });
506 request.write(params.json);
507 request.end();
508}
509
510/**
511 * HTTP GET request. Send a query command to the Solr server (query)
512 *
513 * @param {Object} params
514 * @param {String} params.host - IP address or host address of the Solr server
515 * @param {Number|String} params.port - port of the Solr server
516 * @param {String} params.core - name of the Solr core requested
517 * @param {String} params.authorization - value of the authorization header
518 * @param {String} params.fullPath - full path of the request, contains query parameters
519 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
520 * @param {Error} callback().err
521 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
522 *
523 * @api private
524 */
525
526var queryRequest = function(params,callback){
527 var options = {
528 host : params.host,
529 port : params.port,
530 path : params.fullPath
531 };
532
533 if(params.authorization){
534 var headers = {
535 'authorization' : params.authorization
536 };
537 options.headers = headers;
538 }
539
540 var callbackResponse = function(res){
541 var buffer = '';
542 var err = null;
543 res.on('data',function(chunk){
544 buffer += chunk;
545 });
546
547 res.on('end',function(){
548 if(res.statusCode !== 200){
549 err = new SolrError(res.statusCode,buffer);
550 if (callback) callback(err,null);
551 }else{
552 try{
553 var data = JSON.parse(buffer);
554 }catch(error){
555 err = error;
556 }finally{
557 if (callback) callback(err,data);
558 }
559 }
560 });
561 }
562
563 var request = http.get(options,callbackResponse);
564
565 request.on('error',function(err){
566 if (callback) callback(err,null);
567 });
568}