/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.solr.core;
import org.apache.solr.common.util.DOMUtil;
import org.apache.solr.common.util.RegexFileFilter;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.handler.PingRequestHandler;
import org.apache.solr.handler.component.SearchComponent;
import org.apache.solr.request.LocalSolrQueryRequest;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.request.SolrRequestHandler;
import org.apache.solr.response.QueryResponseWriter;
import org.apache.solr.search.CacheConfig;
import org.apache.solr.search.FastLRUCache;
import org.apache.solr.search.QParserPlugin;
import org.apache.solr.search.ValueSourceParser;
import org.apache.solr.update.SolrIndexConfig;
import org.apache.solr.update.processor.UpdateRequestProcessorChain;
import org.apache.solr.spelling.QueryConverter;
import org.apache.solr.highlight.SolrHighlighter;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.index.IndexDeletionPolicy;
import org.apache.lucene.util.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathConstants;
import java.util.*;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
/**
* Provides a static reference to a Config object modeling the main
* configuration data for a a Solr instance -- typically found in
* "solrconfig.xml".
*
* @version $Id: SolrConfig.java 945886 2010-05-18 21:07:28Z hossman $
*/
public class SolrConfig extends Config {
public static final Logger log = LoggerFactory.getLogger(SolrConfig.class);
public static final String DEFAULT_CONF_FILE = "solrconfig.xml";
/**
* Compatibility feature for single-core (pre-solr{215,350} patch); should go away at solr-2.0
* @deprecated Use {@link SolrCore#getSolrConfig()} instead.
*/
@Deprecated
public static SolrConfig config = null;
/**
* Singleton keeping track of configuration errors
*
* @deprecated All exceptions encountered during config parsing are now thrown by the respective constructors, preventing initialization.
*/
@Deprecated
public static final Collection<Throwable> severeErrors = new HashSet<Throwable>();
/** Creates a default instance from the solrconfig.xml. */
public SolrConfig()
throws ParserConfigurationException, IOException, SAXException {
this( (SolrResourceLoader) null, DEFAULT_CONF_FILE, null );
}
/** Creates a configuration instance from a configuration name.
* A default resource loader will be created (@see SolrResourceLoader)
*@param name the configuration name used by the loader
*/
public SolrConfig(String name)
throws ParserConfigurationException, IOException, SAXException {
this( (SolrResourceLoader) null, name, null);
}
/** Creates a configuration instance from a configuration name and stream.
* A default resource loader will be created (@see SolrResourceLoader).
* If the stream is null, the resource loader will open the configuration stream.
* If the stream is not null, no attempt to load the resource will occur (the name is not used).
*@param name the configuration name
*@param is the configuration stream
*/
public SolrConfig(String name, InputStream is)
throws ParserConfigurationException, IOException, SAXException {
this( (SolrResourceLoader) null, name, is );
}
/** Creates a configuration instance from an instance directory, configuration name and stream.
*@param instanceDir the directory used to create the resource loader
*@param name the configuration name used by the loader if the stream is null
*@param is the configuration stream
*/
public SolrConfig(String instanceDir, String name, InputStream is)
throws ParserConfigurationException, IOException, SAXException {
this(new SolrResourceLoader(instanceDir), name, is);
}
/** Creates a configuration instance from a resource loader, a configuration name and a stream.
* If the stream is null, the resource loader will open the configuration stream.
* If the stream is not null, no attempt to load the resource will occur (the name is not used).
*@param loader the resource loader
*@param name the configuration name
*@param is the configuration stream
*/
SolrConfig(SolrResourceLoader loader, String name, InputStream is)
throws ParserConfigurationException, IOException, SAXException {
super(loader, name, is, "/config/");
initLibs();
defaultIndexConfig = new SolrIndexConfig(this, null, null);
mainIndexConfig = new SolrIndexConfig(this, "mainIndex", defaultIndexConfig);
reopenReaders = getBool("mainIndex/reopenReaders", true);
booleanQueryMaxClauseCount = getInt("query/maxBooleanClauses", BooleanQuery.getMaxClauseCount());
luceneMatchVersion = getLuceneVersion("luceneMatchVersion", Version.LUCENE_24);
log.info("Using Lucene MatchVersion: " + luceneMatchVersion);
filtOptEnabled = getBool("query/boolTofilterOptimizer/@enabled", false);
filtOptCacheSize = getInt("query/boolTofilterOptimizer/@cacheSize",32);
filtOptThreshold = getFloat("query/boolTofilterOptimizer/@threshold",.05f);
useFilterForSortedQuery = getBool("query/useFilterForSortedQuery", false);
queryResultWindowSize = getInt("query/queryResultWindowSize", 1);
queryResultMaxDocsCached = getInt("query/queryResultMaxDocsCached", Integer.MAX_VALUE);
enableLazyFieldLoading = getBool("query/enableLazyFieldLoading", false);
filterCacheConfig = CacheConfig.getConfig(this, "query/filterCache");
queryResultCacheConfig = CacheConfig.getConfig(this, "query/queryResultCache");
documentCacheConfig = CacheConfig.getConfig(this, "query/documentCache");
CacheConfig conf = CacheConfig.getConfig(this, "query/fieldValueCache");
if (conf == null) {
Map<String,String> args = new HashMap<String,String>();
args.put("name","fieldValueCache");
args.put("size","10000");
args.put("initialSize","10");
args.put("showItems","-1");
conf = new CacheConfig(FastLRUCache.class, args, null);
}
fieldValueCacheConfig = conf;
unlockOnStartup = getBool("mainIndex/unlockOnStartup", false);
useColdSearcher = getBool("query/useColdSearcher",false);
dataDir = get("dataDir", null);
if (dataDir != null && dataDir.length()==0) dataDir=null;
userCacheConfigs = CacheConfig.getMultipleConfigs(this, "query/cache");
org.apache.solr.search.SolrIndexSearcher.initRegenerators(this);
hashSetInverseLoadFactor = 1.0f / getFloat("//HashDocSet/@loadFactor",0.75f);
hashDocSetMaxSize= getInt("//HashDocSet/@maxSize",3000);
pingQueryParams = readPingQueryParams(this);
httpCachingConfig = new HttpCachingConfig(this);
Node jmx = (Node) getNode("jmx", false);
if (jmx != null) {
jmxConfig = new JmxConfiguration(true,
get("jmx/@agentId", null),
get("jmx/@serviceUrl", null),
get("jmx/@rootName", null));
} else {
jmxConfig = new JmxConfiguration(false, null, null, null);
}
maxWarmingSearchers = getInt("query/maxWarmingSearchers",Integer.MAX_VALUE);
loadPluginInfo(SolrRequestHandler.class,"requestHandler",true, true);
loadPluginInfo(QParserPlugin.class,"queryParser",true, true);
loadPluginInfo(QueryResponseWriter.class,"queryResponseWriter",true, true);
loadPluginInfo(ValueSourceParser.class,"valueSourceParser",true, true);
loadPluginInfo(SearchComponent.class,"searchComponent",true, true);
loadPluginInfo(QueryConverter.class,"queryConverter",true, true);
loadPluginInfo(SolrEventListener.class, "//listener",false, true);
loadPluginInfo(DirectoryFactory.class,"directoryFactory",false, true);
loadPluginInfo(IndexDeletionPolicy.class,"mainIndex/deletionPolicy",false, true);
loadPluginInfo(IndexReaderFactory.class,"indexReaderFactory",false, true);
loadPluginInfo(UpdateRequestProcessorChain.class,"updateRequestProcessorChain",false, false);
//TODO deprecated remove it later
loadPluginInfo(SolrHighlighter.class,"highlighting",false, false);
if( pluginStore.containsKey( SolrHighlighter.class.getName() ) )
log.warn( "Deprecated syntax found. <highlighting/> should move to <searchComponent/>" );
updateHandlerInfo = loadUpdatehandlerInfo();
Config.log.info("Loaded SolrConfig: " + name);
// TODO -- at solr 2.0. this should go away
config = this;
}
protected UpdateHandlerInfo loadUpdatehandlerInfo() {
return new UpdateHandlerInfo(get("updateHandler/@class",null),
getInt("updateHandler/autoCommit/maxDocs",-1),
getInt("updateHandler/autoCommit/maxTime",-1),
getInt("updateHandler/commitIntervalLowerBound",-1));
}
private void loadPluginInfo(Class clazz, String tag, boolean requireName, boolean requireClass) {
List<PluginInfo> result = readPluginInfos(tag, requireName, requireClass);
if(!result.isEmpty()) pluginStore.put(clazz.getName(),result);
}
public List<PluginInfo> readPluginInfos(String tag, boolean requireName, boolean requireClass) {
ArrayList<PluginInfo> result = new ArrayList<PluginInfo>();
NodeList nodes = (NodeList) evaluate(tag, XPathConstants.NODESET);
for (int i=0; i<nodes.getLength(); i++) {
PluginInfo pluginInfo = new PluginInfo(nodes.item(i), "[solrconfig.xml] " + tag, requireName, requireClass);
if(pluginInfo.isEnabled()) result.add(pluginInfo);
}
return result;
}
/* The set of materialized parameters: */
public final int booleanQueryMaxClauseCount;
// SolrIndexSearcher - nutch optimizer
public final boolean filtOptEnabled;
public final int filtOptCacheSize;
public final float filtOptThreshold;
// SolrIndexSearcher - caches configurations
public final CacheConfig filterCacheConfig ;
public final CacheConfig queryResultCacheConfig;
public final CacheConfig documentCacheConfig;
public final CacheConfig fieldValueCacheConfig;
public final CacheConfig[] userCacheConfigs;
// SolrIndexSearcher - more...
public final boolean useFilterForSortedQuery;
public final int queryResultWindowSize;
public final int queryResultMaxDocsCached;
public final boolean enableLazyFieldLoading;
public final boolean reopenReaders;
// DocSet
public final float hashSetInverseLoadFactor;
public final int hashDocSetMaxSize;
// default & main index configurations
public final SolrIndexConfig defaultIndexConfig;
public final SolrIndexConfig mainIndexConfig;
protected UpdateHandlerInfo updateHandlerInfo ;
private Map<String, List<PluginInfo>> pluginStore = new LinkedHashMap<String, List<PluginInfo>>();
public final int maxWarmingSearchers;
public final boolean unlockOnStartup;
public final boolean useColdSearcher;
public final Version luceneMatchVersion;
protected String dataDir;
//JMX configuration
public final JmxConfiguration jmxConfig;
private final HttpCachingConfig httpCachingConfig;
public HttpCachingConfig getHttpCachingConfig() {
return httpCachingConfig;
}
/**
* ping query request parameters
* @deprecated Use {@link PingRequestHandler} instead.
*/
@Deprecated
private final NamedList pingQueryParams;
static private NamedList readPingQueryParams(SolrConfig config) {
String urlSnippet = config.get("admin/pingQuery", "").trim();
StringTokenizer qtokens = new StringTokenizer(urlSnippet,"&");
String tok;
NamedList params = new NamedList();
while (qtokens.hasMoreTokens()) {
tok = qtokens.nextToken();
String[] split = tok.split("=", 2);
params.add(split[0], split[1]);
}
if (0 < params.size()) {
log.warn("The <pingQuery> syntax is deprecated, " +
"please use PingRequestHandler instead");
}
return params;
}
/**
* Returns a Request object based on the admin/pingQuery section
* of the Solr config file.
*
* @deprecated use {@link PingRequestHandler} instead
*/
@Deprecated
public SolrQueryRequest getPingQueryRequest(SolrCore core) {
if(pingQueryParams.size() == 0) {
throw new IllegalStateException
("<pingQuery> not configured (consider registering " +
"PingRequestHandler with the name '/admin/ping' instead)");
}
return new LocalSolrQueryRequest(core, pingQueryParams);
}
public static class JmxConfiguration {
public boolean enabled = false;
public String agentId;
public String serviceUrl;
public String rootName;
@Deprecated
public JmxConfiguration(boolean enabled,
String agentId,
String serviceUrl) {
this(enabled,agentId,serviceUrl,null);
}
public JmxConfiguration(boolean enabled,
String agentId,
String serviceUrl,
String rootName) {
this.enabled = enabled;
this.agentId = agentId;
this.serviceUrl = serviceUrl;
this.rootName = rootName;
if (agentId != null && serviceUrl != null) {
throw new SolrException
(SolrException.ErrorCode.SERVER_ERROR,
"Incorrect JMX Configuration in solrconfig.xml, "+
"both agentId and serviceUrl cannot be specified at the same time");
}
}
}
public static class HttpCachingConfig {
/** config xpath prefix for getting HTTP Caching options */
private final static String CACHE_PRE
= "requestDispatcher/httpCaching/";
/** For extracting Expires "ttl" from <cacheControl> config */
private final static Pattern MAX_AGE
= Pattern.compile("\\bmax-age=(\\d+)");
public static enum LastModFrom {
OPENTIME, DIRLASTMOD, BOGUS;
/** Input must not be null */
public static LastModFrom parse(final String s) {
try {
return valueOf(s.toUpperCase(Locale.ENGLISH));
} catch (Exception e) {
log.warn( "Unrecognized value for lastModFrom: " + s, e);
return BOGUS;
}
}
}
private final boolean never304;
private final String etagSeed;
private final String cacheControlHeader;
private final Long maxAge;
private final LastModFrom lastModFrom;
private HttpCachingConfig(SolrConfig conf) {
never304 = conf.getBool(CACHE_PRE+"@never304", false);
etagSeed = conf.get(CACHE_PRE+"@etagSeed", "Solr");
lastModFrom = LastModFrom.parse(conf.get(CACHE_PRE+"@lastModFrom",
"openTime"));
cacheControlHeader = conf.get(CACHE_PRE+"cacheControl",null);
Long tmp = null; // maxAge
if (null != cacheControlHeader) {
try {
final Matcher ttlMatcher = MAX_AGE.matcher(cacheControlHeader);
final String ttlStr = ttlMatcher.find() ? ttlMatcher.group(1) : null;
tmp = (null != ttlStr && !"".equals(ttlStr))
? Long.valueOf(ttlStr)
: null;
} catch (Exception e) {
log.warn( "Ignoring exception while attempting to " +
"extract max-age from cacheControl config: " +
cacheControlHeader, e);
}
}
maxAge = tmp;
}
public boolean isNever304() { return never304; }
public String getEtagSeed() { return etagSeed; }
/** null if no Cache-Control header */
public String getCacheControlHeader() { return cacheControlHeader; }
/** null if no max age limitation */
public Long getMaxAge() { return maxAge; }
public LastModFrom getLastModFrom() { return lastModFrom; }
}
public static class UpdateHandlerInfo{
public final String className;
public final int autoCommmitMaxDocs,autoCommmitMaxTime,commitIntervalLowerBound;
/**
* @param className
* @param autoCommmitMaxDocs set -1 as default
* @param autoCommmitMaxTime set -1 as default
* @param commitIntervalLowerBound set -1 as default
*/
public UpdateHandlerInfo(String className, int autoCommmitMaxDocs, int autoCommmitMaxTime, int commitIntervalLowerBound) {
this.className = className;
this.autoCommmitMaxDocs = autoCommmitMaxDocs;
this.autoCommmitMaxTime = autoCommmitMaxTime;
this.commitIntervalLowerBound = commitIntervalLowerBound;
}
}
// public Map<String, List<PluginInfo>> getUpdateProcessorChainInfo() { return updateProcessorChainInfo; }
public UpdateHandlerInfo getUpdateHandlerInfo() { return updateHandlerInfo; }
public String getDataDir() { return dataDir; }
/**SolrConfig keeps a repository of plugins by the type. The known interfaces are the types.
* @param type The key is FQN of the plugin class there are a few known types : SolrFormatter, SolrFragmenter
* SolrRequestHandler,QParserPlugin, QueryResponseWriter,ValueSourceParser,
* SearchComponent, QueryConverter, SolrEventListener, DirectoryFactory,
* IndexDeletionPolicy, IndexReaderFactory
*/
public List<PluginInfo> getPluginInfos(String type){
List<PluginInfo> result = pluginStore.get(type);
return result == null ?
(List<PluginInfo>) Collections.EMPTY_LIST:
result;
}
public PluginInfo getPluginInfo(String type){
List<PluginInfo> result = pluginStore.get(type);
return result == null || result.isEmpty() ? null: result.get(0);
}
private void initLibs() {
NodeList nodes = (NodeList) evaluate("lib", XPathConstants.NODESET);
if (nodes==null || nodes.getLength()==0)
return;
log.info("Adding specified lib dirs to ClassLoader");
for (int i=0; i<nodes.getLength(); i++) {
Node node = nodes.item(i);
String baseDir = DOMUtil.getAttr(node, "dir");
String path = DOMUtil.getAttr(node, "path");
if (null != baseDir) {
// :TODO: add support for a simpler 'glob' mutually eclusive of regex
String regex = DOMUtil.getAttr(node, "regex");
FileFilter filter = (null == regex) ? null : new RegexFileFilter(regex);
getResourceLoader().addToClassLoader(baseDir, filter);
} else if (null != path) {
getResourceLoader().addToClassLoader(path);
} else {
throw new RuntimeException
("lib: missing mandatory attributes: 'dir' or 'path'");
}
}
}
}