content: /**
   * 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.InputSource;
  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;
  
  
  /**
   * 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 1103086 2011-05-14 12:25:51Z mikemccand $
   */
  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
     */
    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, InputSource 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, InputSource 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, InputSource is)
    throws ParserConfigurationException, IOException, SAXException {
      super(loader, name, is, "/config/");
      initLibs();
      luceneMatchVersion = getLuceneVersion("luceneMatchVersion", Version.LUCENE_24);
      defaultIndexConfig = new SolrIndexConfig(this, null, null);
      mainIndexConfig = new SolrIndexConfig(this, "mainIndex", defaultIndexConfig);
      reopenReaders = getBool("mainIndex/reopenReaders", true);
      
      booleanQueryMaxClauseCount = getInt("query/maxBooleanClauses", BooleanQuery.getMaxClauseCount());
      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 = Math.max(1, 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 = getNode("jmx", false);
      if (jmx != null) {
        jmxConfig = new JmxConfiguration(true, get("jmx/@agentId", null), get(
            "jmx/@serviceUrl", null));
      } else {
        jmxConfig = new JmxConfiguration(false, 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);
  
       // this is hackish, since it picks up all SolrEventListeners,
       // regardless of when/how/why thye are used (or even if they are 
       // declared outside of the appropriate context) but there's no nice 
       // way arround that in the PluginInfo framework
       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 JmxConfiguration(boolean enabled, String agentId, String serviceUrl) {
        this.enabled = enabled;
        this.agentId = agentId;
        this.serviceUrl = serviceUrl;
      }
    }
  
    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'");
         }
       }
    }
  }
  