001: /**
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */package org.apache.solr.core;
017:
018: import java.io.File;
019: import java.io.IOException;
020: import java.util.ArrayList;
021: import java.util.HashMap;
022: import java.util.List;
023: import java.util.Map;
024: import java.util.concurrent.Callable;
025: import java.util.concurrent.ExecutorService;
026: import java.util.concurrent.Executors;
027: import java.util.concurrent.Future;
028: import java.util.logging.Logger;
029:
030: import javax.xml.xpath.XPathConstants;
031:
032: import org.apache.lucene.index.IndexReader;
033: import org.apache.lucene.index.IndexWriter;
034: import org.apache.lucene.search.BooleanQuery;
035: import org.apache.lucene.store.Directory;
036: import org.apache.lucene.store.FSDirectory;
037: import org.apache.solr.request.JSONResponseWriter;
038: import org.apache.solr.request.PythonResponseWriter;
039: import org.apache.solr.request.QueryResponseWriter;
040: import org.apache.solr.request.RubyResponseWriter;
041: import org.apache.solr.request.SolrParams;
042: import org.apache.solr.request.SolrQueryRequest;
043: import org.apache.solr.request.SolrQueryResponse;
044: import org.apache.solr.request.SolrRequestHandler;
045: import org.apache.solr.request.XMLResponseWriter;
046: import org.apache.solr.request.SolrParams.EchoParamStyle;
047: import org.apache.solr.schema.IndexSchema;
048: import org.apache.solr.search.SolrIndexSearcher;
049: import org.apache.solr.update.DirectUpdateHandler;
050: import org.apache.solr.update.SolrIndexConfig;
051: import org.apache.solr.update.SolrIndexWriter;
052: import org.apache.solr.update.UpdateHandler;
053: import org.apache.solr.util.DOMUtil;
054: import org.apache.solr.util.NamedList;
055: import org.apache.solr.util.RefCounted;
056: import org.apache.solr.util.SimpleOrderedMap;
057: import org.w3c.dom.Element;
058: import org.w3c.dom.Node;
059: import org.w3c.dom.NodeList;
060:
061: /**
062: * @author yonik
063: * @author <a href='mailto:mbaranczak@epublishing.com'> Mike Baranczak </a>
064: * @version $Id: SolrCore.java 542679 2007-05-29 22:28:21Z ryan $
065: */
066:
067: public final class SolrCore {
068: public static final String version = "1.0";
069:
070: public static Logger log = Logger.getLogger(SolrCore.class
071: .getName());
072:
073: private final IndexSchema schema;
074: private final String dataDir;
075: private final String index_path;
076: private final UpdateHandler updateHandler;
077: private static final long startTime = System.currentTimeMillis();
078: private final RequestHandlers reqHandlers = new RequestHandlers();
079:
080: public long getStartTime() {
081: return startTime;
082: }
083:
084: public static SolrIndexConfig mainIndexConfig = new SolrIndexConfig(
085: "mainIndex");
086:
087: static {
088: BooleanQuery.setMaxClauseCount(SolrConfig.config.getInt(
089: "query/maxBooleanClauses", BooleanQuery
090: .getMaxClauseCount()));
091: if (mainIndexConfig.writeLockTimeout != -1)
092: IndexWriter
093: .setDefaultWriteLockTimeout(mainIndexConfig.writeLockTimeout);
094: }
095:
096: public static List<SolrEventListener> parseListener(String path) {
097: List<SolrEventListener> lst = new ArrayList<SolrEventListener>();
098: log.info("Searching for listeners: " + path);
099: NodeList nodes = (NodeList) SolrConfig.config.evaluate(path,
100: XPathConstants.NODESET);
101: if (nodes != null) {
102: for (int i = 0; i < nodes.getLength(); i++) {
103: Node node = nodes.item(i);
104: String className = DOMUtil.getAttr(node, "class");
105: SolrEventListener listener = (SolrEventListener) Config
106: .newInstance(className);
107: listener.init(DOMUtil.childNodesToNamedList(node));
108: lst.add(listener);
109: log.info("added SolrEventListener: " + listener);
110: }
111: }
112: return lst;
113: }
114:
115: List<SolrEventListener> firstSearcherListeners;
116: List<SolrEventListener> newSearcherListeners;
117:
118: private void parseListeners() {
119: firstSearcherListeners = parseListener("//listener[@event=\"firstSearcher\"]");
120: newSearcherListeners = parseListener("//listener[@event=\"newSearcher\"]");
121: }
122:
123: public IndexSchema getSchema() {
124: return schema;
125: }
126:
127: public String getDataDir() {
128: return dataDir;
129: }
130:
131: public String getIndexDir() {
132: return index_path;
133: }
134:
135: // gets a non-caching searcher
136: public SolrIndexSearcher newSearcher(String name)
137: throws IOException {
138: return new SolrIndexSearcher(schema, name, getIndexDir(), false);
139: }
140:
141: void initIndex() {
142: try {
143: File dirFile = new File(getIndexDir());
144: boolean indexExists = dirFile.canRead();
145:
146: boolean removeLocks = SolrConfig.config.getBool(
147: "mainIndex/unlockOnStartup", false);
148: if (removeLocks) {
149: // to remove locks, the directory must already exist... so we create it
150: // if it didn't exist already...
151: Directory dir = FSDirectory.getDirectory(dirFile,
152: !indexExists);
153: if (IndexReader.isLocked(dir)) {
154: log.warning("WARNING: Solr index directory '"
155: + getIndexDir()
156: + "' is locked. Unlocking...");
157: IndexReader.unlock(dir);
158: }
159: }
160:
161: // Create the index if it doesn't exist. Note that indexExists was tested *before*
162: // lock removal, since that will result in the creation of the directory.
163: if (!indexExists) {
164: log
165: .warning("Solr index directory '" + dirFile
166: + "' doesn't exist."
167: + " Creating new index...");
168:
169: SolrIndexWriter writer = new SolrIndexWriter(
170: "SolrCore.initIndex", getIndexDir(), true,
171: schema, mainIndexConfig);
172: writer.close();
173:
174: }
175:
176: } catch (IOException e) {
177: throw new RuntimeException(e);
178: }
179: }
180:
181: private UpdateHandler createUpdateHandler(String className) {
182: try {
183: Class handlerClass = Config.findClass(className);
184: java.lang.reflect.Constructor cons = handlerClass
185: .getConstructor(new Class[] { SolrCore.class });
186: return (UpdateHandler) cons
187: .newInstance(new Object[] { this });
188: } catch (SolrException e) {
189: throw e;
190: } catch (Exception e) {
191: throw new SolrException(
192: SolrException.ErrorCode.SERVER_ERROR,
193: "Error Instantiating Update Handler " + className,
194: e);
195: }
196: }
197:
198: // Singleton for now...
199: private static SolrCore core;
200:
201: public static SolrCore getSolrCore() {
202: synchronized (SolrCore.class) {
203: if (core == null)
204: core = new SolrCore(null, null);
205: return core;
206: }
207: }
208:
209: public SolrCore(String dataDir, IndexSchema schema) {
210: synchronized (SolrCore.class) {
211: // this is for backward compatibility (and also the reason
212: // the sync block is needed)
213: core = this ; // set singleton
214:
215: if (dataDir == null) {
216: dataDir = SolrConfig.config.get("dataDir", Config
217: .getInstanceDir()
218: + "data");
219: }
220:
221: log.info("Opening new SolrCore at "
222: + Config.getInstanceDir() + ", dataDir=" + dataDir);
223:
224: if (schema == null) {
225: schema = new IndexSchema("schema.xml");
226: }
227:
228: this .schema = schema;
229: this .dataDir = dataDir;
230: this .index_path = dataDir + "/" + "index";
231:
232: this .maxWarmingSearchers = SolrConfig.config.getInt(
233: "query/maxWarmingSearchers", Integer.MAX_VALUE);
234:
235: parseListeners();
236:
237: initIndex();
238:
239: initWriters();
240:
241: reqHandlers.initHandlersFromConfig(SolrConfig.config);
242:
243: try {
244: // Open the searcher *before* the handler so we don't end up opening
245: // one in the middle.
246: getSearcher(false, false, null);
247:
248: updateHandler = createUpdateHandler(SolrConfig.config
249: .get("updateHandler/@class",
250: DirectUpdateHandler.class.getName()));
251:
252: } catch (IOException e) {
253: throw new RuntimeException(e);
254: }
255: }
256: }
257:
258: public void close() {
259: log.info("CLOSING SolrCore!");
260: try {
261: closeSearcher();
262: } catch (Exception e) {
263: SolrException.log(log, e);
264: }
265: try {
266: searcherExecutor.shutdown();
267: } catch (Exception e) {
268: SolrException.log(log, e);
269: }
270: try {
271: updateHandler.close();
272: } catch (Exception e) {
273: SolrException.log(log, e);
274: }
275: }
276:
277: @Override
278: protected void finalize() {
279: close();
280: }
281:
282: ////////////////////////////////////////////////////////////////////////////////
283: // Request Handler
284: ////////////////////////////////////////////////////////////////////////////////
285:
286: /**
287: * Get the request handler registered to a given name.
288: *
289: * This function is thread safe.
290: */
291: public SolrRequestHandler getRequestHandler(String handlerName) {
292: return reqHandlers.get(handlerName);
293: }
294:
295: /**
296: * Returns an unmodifieable Map containing the registered handlers
297: */
298: public Map<String, SolrRequestHandler> getRequestHandlers() {
299: return reqHandlers.getRequestHandlers();
300: }
301:
302: /**
303: * Registers a handler at the specified location. If one exists there, it will be replaced.
304: * To remove a handler, register <code>null</code> at its path
305: *
306: * Once registered the handler can be accessed through:
307: * <pre>
308: * http://${host}:${port}/${context}/${handlerName}
309: * or:
310: * http://${host}:${port}/${context}/select?qt=${handlerName}
311: * </pre>
312: *
313: * Handlers <em>must</em> be initalized before getting registered. Registered
314: * handlers can immediatly accept requests.
315: *
316: * This call is thread safe.
317: *
318: * @return the previous <code>SolrRequestHandler</code> registered to this name <code>null</code> if none.
319: */
320: public SolrRequestHandler registerRequestHandler(
321: String handlerName, SolrRequestHandler handler) {
322: return reqHandlers.register(handlerName, handler);
323: }
324:
325: ////////////////////////////////////////////////////////////////////////////////
326: // Update Handler
327: ////////////////////////////////////////////////////////////////////////////////
328:
329: /**
330: * RequestHandlers need access to the updateHandler so they can all talk to the
331: * same RAM indexer.
332: */
333: public UpdateHandler getUpdateHandler() {
334: return updateHandler;
335: }
336:
337: ////////////////////////////////////////////////////////////////////////////////
338: // Searcher Control
339: ////////////////////////////////////////////////////////////////////////////////
340:
341: // The current searcher used to service queries.
342: // Don't access this directly!!!! use getSearcher() to
343: // get it (and it will increment the ref count at the same time)
344: private RefCounted<SolrIndexSearcher> _searcher;
345:
346: final ExecutorService searcherExecutor = Executors
347: .newSingleThreadExecutor();
348: private int onDeckSearchers; // number of searchers preparing
349: private Object searcherLock = new Object(); // the sync object for the searcher
350: private final int maxWarmingSearchers; // max number of on-deck searchers allowed
351:
352: public RefCounted<SolrIndexSearcher> getSearcher() {
353: try {
354: return getSearcher(false, true, null);
355: } catch (IOException e) {
356: SolrException.log(log, null, e);
357: return null;
358: }
359: }
360:
361: /**
362: * Get a {@link SolrIndexSearcher} or start the process of creating a new one.
363: * <p>
364: * The registered searcher is the default searcher used to service queries.
365: * A searcher will normally be registered after all of the warming
366: * and event handlers (newSearcher or firstSearcher events) have run.
367: * In the case where there is no registered searcher, the newly created searcher will
368: * be registered before running the event handlers (a slow searcher is better than no searcher).
369: *
370: * <p>
371: * If <tt>forceNew==true</tt> then
372: * A new searcher will be opened and registered regardless of whether there is already
373: * a registered searcher or other searchers in the process of being created.
374: * <p>
375: * If <tt>forceNew==false</tt> then:<ul>
376: * <li>If a searcher is already registered, that searcher will be returned</li>
377: * <li>If no searcher is currently registered, but at least one is in the process of being created, then
378: * this call will block until the first searcher is registered</li>
379: * <li>If no searcher is currently registered, and no searchers in the process of being registered, a new
380: * searcher will be created.</li>
381: * </ul>
382: * <p>
383: * If <tt>returnSearcher==true</tt> then a {@link RefCounted}<{@link SolrIndexSearcher}> will be returned with
384: * the reference count incremented. It <b>must</b> be decremented when no longer needed.
385: * <p>
386: * If <tt>waitSearcher!=null</tt> and a new {@link SolrIndexSearcher} was created,
387: * then it is filled in with a Future that will return after the searcher is registered. The Future may be set to
388: * <tt>null</tt> in which case the SolrIndexSearcher created has already been registered at the time
389: * this method returned.
390: * <p>
391: * @param forceNew if true, force the open of a new index searcher regardless if there is already one open.
392: * @param returnSearcher if true, returns a {@link SolrIndexSearcher} holder with the refcount already incremented.
393: * @param waitSearcher if non-null, will be filled in with a {@link Future} that will return after the new searcher is registered.
394: * @throws IOException
395: */
396: public RefCounted<SolrIndexSearcher> getSearcher(boolean forceNew,
397: boolean returnSearcher, final Future[] waitSearcher)
398: throws IOException {
399: // it may take some time to open an index.... we may need to make
400: // sure that two threads aren't trying to open one at the same time
401: // if it isn't necessary.
402:
403: synchronized (searcherLock) {
404: // see if we can return the current searcher
405: if (_searcher != null && !forceNew) {
406: if (returnSearcher) {
407: _searcher.incref();
408: return _searcher;
409: } else {
410: return null;
411: }
412: }
413:
414: // check to see if we can wait for someone else's searcher to be set
415: if (onDeckSearchers > 0 && !forceNew && _searcher == null) {
416: try {
417: searcherLock.wait();
418: } catch (InterruptedException e) {
419: log.info(SolrException.toStr(e));
420: }
421: }
422:
423: // check again: see if we can return right now
424: if (_searcher != null && !forceNew) {
425: if (returnSearcher) {
426: _searcher.incref();
427: return _searcher;
428: } else {
429: return null;
430: }
431: }
432:
433: // At this point, we know we need to open a new searcher...
434: // first: increment count to signal other threads that we are
435: // opening a new searcher.
436: onDeckSearchers++;
437: if (onDeckSearchers < 1) {
438: // should never happen... just a sanity check
439: log.severe("ERROR!!! onDeckSearchers is "
440: + onDeckSearchers);
441: onDeckSearchers = 1; // reset
442: } else if (onDeckSearchers > maxWarmingSearchers) {
443: onDeckSearchers--;
444: String msg = "Error opening new searcher. exceeded limit of maxWarmingSearchers="
445: + maxWarmingSearchers + ", try again later.";
446: log.warning(msg);
447: // HTTP 503==service unavailable, or 409==Conflict
448: throw new SolrException(
449: SolrException.ErrorCode.SERVICE_UNAVAILABLE,
450: msg, true);
451: } else if (onDeckSearchers > 1) {
452: log
453: .info("PERFORMANCE WARNING: Overlapping onDeckSearchers="
454: + onDeckSearchers);
455: }
456: }
457:
458: // open the index synchronously
459: // if this fails, we need to decrement onDeckSearchers again.
460: SolrIndexSearcher tmp;
461: try {
462: tmp = new SolrIndexSearcher(schema, "main", index_path,
463: true);
464: } catch (Throwable th) {
465: synchronized (searcherLock) {
466: onDeckSearchers--;
467: // notify another waiter to continue... it may succeed
468: // and wake any others.
469: searcherLock.notify();
470: }
471: // need to close the searcher here??? we shouldn't have to.
472: throw new RuntimeException(th);
473: }
474:
475: final SolrIndexSearcher newSearcher = tmp;
476:
477: RefCounted<SolrIndexSearcher> currSearcherHolder = null;
478: final RefCounted<SolrIndexSearcher> newSearchHolder = newHolder(newSearcher);
479: if (returnSearcher)
480: newSearchHolder.incref();
481:
482: // a signal to decrement onDeckSearchers if something goes wrong.
483: final boolean[] decrementOnDeckCount = new boolean[1];
484: decrementOnDeckCount[0] = true;
485:
486: try {
487:
488: boolean alreadyRegistered = false;
489: synchronized (searcherLock) {
490: if (_searcher == null) {
491: // if there isn't a current searcher then we may
492: // want to register this one before warming is complete instead of waiting.
493: if (SolrConfig.config.getBool(
494: "query/useColdSearcher", false)) {
495: registerSearcher(newSearchHolder);
496: decrementOnDeckCount[0] = false;
497: alreadyRegistered = true;
498: }
499: } else {
500: // get a reference to the current searcher for purposes of autowarming.
501: currSearcherHolder = _searcher;
502: currSearcherHolder.incref();
503: }
504: }
505:
506: final SolrIndexSearcher currSearcher = currSearcherHolder == null ? null
507: : currSearcherHolder.get();
508:
509: //
510: // Note! if we registered the new searcher (but didn't increment it's
511: // reference count because returnSearcher==false, it's possible for
512: // someone else to register another searcher, and thus cause newSearcher
513: // to close while we are warming.
514: //
515: // Should we protect against that by incrementing the reference count?
516: // Maybe we should just let it fail? After all, if returnSearcher==false
517: // and newSearcher has been de-registered, what's the point of continuing?
518: //
519:
520: Future future = null;
521:
522: // warm the new searcher based on the current searcher.
523: // should this go before the other event handlers or after?
524: if (currSearcher != null) {
525: future = searcherExecutor.submit(new Callable() {
526: public Object call() throws Exception {
527: try {
528: newSearcher.warm(currSearcher);
529: } catch (Throwable e) {
530: SolrException.logOnce(log, null, e);
531: }
532: return null;
533: }
534: });
535: }
536:
537: if (currSearcher == null
538: && firstSearcherListeners.size() > 0) {
539: future = searcherExecutor.submit(new Callable() {
540: public Object call() throws Exception {
541: try {
542: for (SolrEventListener listener : firstSearcherListeners) {
543: listener.newSearcher(newSearcher, null);
544: }
545: } catch (Throwable e) {
546: SolrException.logOnce(log, null, e);
547: }
548: return null;
549: }
550: });
551: }
552:
553: if (currSearcher != null && newSearcherListeners.size() > 0) {
554: future = searcherExecutor.submit(new Callable() {
555: public Object call() throws Exception {
556: try {
557: for (SolrEventListener listener : newSearcherListeners) {
558: listener.newSearcher(newSearcher, null);
559: }
560: } catch (Throwable e) {
561: SolrException.logOnce(log, null, e);
562: }
563: return null;
564: }
565: });
566: }
567:
568: // WARNING: this code assumes a single threaded executor (that all tasks
569: // queued will finish first).
570: final RefCounted<SolrIndexSearcher> currSearcherHolderF = currSearcherHolder;
571: if (!alreadyRegistered) {
572: future = searcherExecutor.submit(new Callable() {
573: public Object call() throws Exception {
574: try {
575: // signal that we no longer need to decrement
576: // the count *before* registering the searcher since
577: // registerSearcher will decrement even if it errors.
578: decrementOnDeckCount[0] = false;
579: registerSearcher(newSearchHolder);
580: } catch (Throwable e) {
581: SolrException.logOnce(log, null, e);
582: } finally {
583: // we are all done with the old searcher we used
584: // for warming...
585: if (currSearcherHolderF != null)
586: currSearcherHolderF.decref();
587: }
588: return null;
589: }
590: });
591: }
592:
593: if (waitSearcher != null) {
594: waitSearcher[0] = future;
595: }
596:
597: // Return the searcher as the warming tasks run in parallel
598: // callers may wait on the waitSearcher future returned.
599: return returnSearcher ? newSearchHolder : null;
600:
601: } catch (Exception e) {
602: SolrException.logOnce(log, null, e);
603: if (currSearcherHolder != null)
604: currSearcherHolder.decref();
605:
606: synchronized (searcherLock) {
607: if (decrementOnDeckCount[0]) {
608: onDeckSearchers--;
609: }
610: if (onDeckSearchers < 0) {
611: // sanity check... should never happen
612: log
613: .severe("ERROR!!! onDeckSearchers after decrement="
614: + onDeckSearchers);
615: onDeckSearchers = 0; // try and recover
616: }
617: // if we failed, we need to wake up at least one waiter to continue the process
618: searcherLock.notify();
619: }
620:
621: // since the indexreader was already opened, assume we can continue on
622: // even though we got an exception.
623: return returnSearcher ? newSearchHolder : null;
624: }
625:
626: }
627:
628: private RefCounted<SolrIndexSearcher> newHolder(
629: SolrIndexSearcher newSearcher) {
630: RefCounted<SolrIndexSearcher> holder = new RefCounted<SolrIndexSearcher>(
631: newSearcher) {
632: public void close() {
633: try {
634: resource.close();
635: } catch (IOException e) {
636: log.severe("Error closing searcher:"
637: + SolrException.toStr(e));
638: }
639: }
640: };
641: holder.incref(); // set ref count to 1 to account for this._searcher
642: return holder;
643: }
644:
645: // Take control of newSearcherHolder (which should have a reference count of at
646: // least 1 already. If the caller wishes to use the newSearcherHolder directly
647: // after registering it, then they should increment the reference count *before*
648: // calling this method.
649: //
650: // onDeckSearchers will also be decremented (it should have been incremented
651: // as a result of opening a new searcher).
652: private void registerSearcher(
653: RefCounted<SolrIndexSearcher> newSearcherHolder)
654: throws IOException {
655: synchronized (searcherLock) {
656: try {
657: if (_searcher != null) {
658: _searcher.decref(); // dec refcount for this._searcher
659: _searcher = null;
660: }
661:
662: _searcher = newSearcherHolder;
663: SolrIndexSearcher newSearcher = newSearcherHolder.get();
664:
665: newSearcher.register(); // register subitems (caches)
666: log.info("Registered new searcher " + newSearcher);
667:
668: } catch (Throwable e) {
669: log(e);
670: } finally {
671: // wake up anyone waiting for a searcher
672: // even in the face of errors.
673: onDeckSearchers--;
674: searcherLock.notifyAll();
675: }
676: }
677: }
678:
679: public void closeSearcher() {
680: log.info("Closing main searcher on request.");
681: synchronized (searcherLock) {
682: if (_searcher != null) {
683: _searcher.decref(); // dec refcount for this._searcher
684: _searcher = null;
685: SolrInfoRegistry.getRegistry()
686: .remove("currentSearcher");
687: }
688: }
689: }
690:
691: public void execute(SolrRequestHandler handler,
692: SolrQueryRequest req, SolrQueryResponse rsp) {
693: // setup response header and handle request
694: final NamedList<Object> responseHeader = new SimpleOrderedMap<Object>();
695: rsp.add("responseHeader", responseHeader);
696: handler.handleRequest(req, rsp);
697: setResponseHeaderValues(handler, responseHeader, req, rsp);
698:
699: log.info(req.getContext().get("path") + " "
700: + req.getParamString() + " 0 "
701: + (int) (rsp.getEndTime() - req.getStartTime()));
702: }
703:
704: @Deprecated
705: public void execute(SolrQueryRequest req, SolrQueryResponse rsp) {
706: SolrRequestHandler handler = getRequestHandler(req
707: .getQueryType());
708: if (handler == null) {
709: log.warning("Unknown Request Handler '"
710: + req.getQueryType() + "' :" + req);
711: throw new SolrException(
712: SolrException.ErrorCode.BAD_REQUEST,
713: "Unknown Request Handler '" + req.getQueryType()
714: + "'", true);
715: }
716: execute(handler, req, rsp);
717: }
718:
719: protected void setResponseHeaderValues(SolrRequestHandler handler,
720: NamedList<Object> responseHeader, SolrQueryRequest req,
721: SolrQueryResponse rsp) {
722: // TODO should check that responseHeader has not been replaced by handler
723:
724: final int qtime = (int) (rsp.getEndTime() - req.getStartTime());
725: responseHeader.add("status", rsp.getException() == null ? 0
726: : 500);
727: responseHeader.add("QTime", qtime);
728:
729: SolrParams params = req.getParams();
730: if (params.getBool(SolrParams.HEADER_ECHO_HANDLER, false)) {
731: responseHeader.add("handler", handler.getName());
732: }
733:
734: // Values for echoParams... false/true/all or false/explicit/all ???
735: String ep = params.get(SolrParams.HEADER_ECHO_PARAMS, null);
736: if (ep != null) {
737: EchoParamStyle echoParams = EchoParamStyle.get(ep);
738: if (echoParams == null) {
739: throw new SolrException(
740: SolrException.ErrorCode.BAD_REQUEST,
741: "Invalid value '" + ep + "' for "
742: + SolrParams.HEADER_ECHO_PARAMS
743: + " parameter, use '"
744: + EchoParamStyle.EXPLICIT + "' or '"
745: + EchoParamStyle.ALL + "'");
746: }
747: if (echoParams == EchoParamStyle.EXPLICIT) {
748: responseHeader.add("params", req.getOriginalParams()
749: .toNamedList());
750: } else if (echoParams == EchoParamStyle.ALL) {
751: responseHeader.add("params", req.getParams()
752: .toNamedList());
753: }
754: }
755: }
756:
757: final public static void log(Throwable e) {
758: SolrException.logOnce(log, null, e);
759: }
760:
761: private QueryResponseWriter defaultResponseWriter;
762: private final Map<String, QueryResponseWriter> responseWriters = new HashMap<String, QueryResponseWriter>();
763:
764: /** Configure the query response writers. There will always be a default writer; additional
765: * writers may also be configured. */
766: private void initWriters() {
767: String xpath = "queryResponseWriter";
768: NodeList nodes = (NodeList) SolrConfig.config.evaluate(xpath,
769: XPathConstants.NODESET);
770: int length = nodes.getLength();
771: for (int i = 0; i < length; i++) {
772: Element elm = (Element) nodes.item(i);
773:
774: try {
775: String name = DOMUtil.getAttr(elm, "name", xpath
776: + " config");
777: String className = DOMUtil.getAttr(elm, "class", xpath
778: + " config");
779: log.info("adding queryResponseWriter " + name + "="
780: + className);
781:
782: QueryResponseWriter writer = (QueryResponseWriter) Config
783: .newInstance(className);
784: writer.init(DOMUtil.childNodesToNamedList(elm));
785: responseWriters.put(name, writer);
786: } catch (Exception ex) {
787: SolrConfig.severeErrors.add(ex);
788: SolrException.logOnce(log, null, ex);
789: // if a writer can't be created, skip it and continue
790: }
791: }
792:
793: // configure the default response writer; this one should never be null
794: if (responseWriters.containsKey("standard")) {
795: defaultResponseWriter = responseWriters.get("standard");
796: }
797: if (defaultResponseWriter == null) {
798: defaultResponseWriter = new XMLResponseWriter();
799: }
800:
801: // make JSON response writers available by default
802: if (responseWriters.get("json") == null) {
803: responseWriters.put("json", new JSONResponseWriter());
804: }
805: if (responseWriters.get("python") == null) {
806: responseWriters.put("python", new PythonResponseWriter());
807: }
808: if (responseWriters.get("ruby") == null) {
809: responseWriters.put("ruby", new RubyResponseWriter());
810: }
811:
812: }
813:
814: /** Finds a writer by name, or returns the default writer if not found. */
815: public final QueryResponseWriter getQueryResponseWriter(
816: String writerName) {
817: if (writerName != null) {
818: QueryResponseWriter writer = responseWriters
819: .get(writerName);
820: if (writer != null) {
821: return writer;
822: }
823: }
824: return defaultResponseWriter;
825: }
826:
827: /** Returns the appropriate writer for a request. If the request specifies a writer via the
828: * 'wt' parameter, attempts to find that one; otherwise return the default writer.
829: */
830: public final QueryResponseWriter getQueryResponseWriter(
831: SolrQueryRequest request) {
832: return getQueryResponseWriter(request.getParam("wt"));
833: }
834: }
|