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