You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by no...@apache.org on 2021/04/16 02:03:34 UTC

[solr] branch jira/solr15337 updated: untested

This is an automated email from the ASF dual-hosted git repository.

noble pushed a commit to branch jira/solr15337
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/jira/solr15337 by this push:
     new dea2494  untested
dea2494 is described below

commit dea2494200e9bc294c3cc559bbdce8771d5c17e8
Author: Noble Paul <no...@gmail.com>
AuthorDate: Fri Apr 16 11:58:23 2021 +1000

    untested
---
 .../java/org/apache/solr/core/ConfigOverlay.java   |   1 +
 .../src/java/org/apache/solr/core/SolrConfig.java  | 155 ++++++++++++---------
 .../org/apache/solr/schema/IndexSchemaFactory.java |   4 +-
 .../java/org/apache/solr/search/CacheConfig.java   |   2 +-
 .../org/apache/solr/update/SolrIndexConfig.java    |  79 +++++------
 .../java/org/apache/solr/update/VersionInfo.java   |   4 +-
 .../java/org/apache/solr/util/DataConfigNode.java  |   4 +-
 .../src/test/org/apache/solr/core/TestConfig.java  |   9 +-
 .../java/org/apache/solr/common/ConfigNode.java    |  53 +++++--
 9 files changed, 175 insertions(+), 136 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/core/ConfigOverlay.java b/solr/core/src/java/org/apache/solr/core/ConfigOverlay.java
index 069de5d..e1a44d5 100644
--- a/solr/core/src/java/org/apache/solr/core/ConfigOverlay.java
+++ b/solr/core/src/java/org/apache/solr/core/ConfigOverlay.java
@@ -191,6 +191,7 @@ public class ConfigOverlay implements MapSerializable {
     return isEditable(isXpath, hierarchy, StrUtils.splitSmart(path, isXpath ? '/' : '.'));
   }
 
+  @SuppressWarnings("rawtypes")
   private static Class isEditable(boolean isXpath, List<String> hierarchy, List<String> parts) {
     Object obj = editable_prop_map;
     for (int i = 0; i < parts.size(); i++) {
diff --git a/solr/core/src/java/org/apache/solr/core/SolrConfig.java b/solr/core/src/java/org/apache/solr/core/SolrConfig.java
index 65f5043..58a2515 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrConfig.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrConfig.java
@@ -38,7 +38,9 @@ import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
 import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Predicate;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -55,7 +57,6 @@ import org.apache.solr.common.MapSerializable;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
 import org.apache.solr.common.util.IOUtils;
-import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.handler.component.SearchComponent;
 import org.apache.solr.pkg.PackageListeners;
 import org.apache.solr.pkg.PackageLoader;
@@ -82,7 +83,6 @@ import org.apache.solr.util.DataConfigNode;
 import org.apache.solr.util.circuitbreaker.CircuitBreakerManager;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.w3c.dom.Node;
 import org.xml.sax.SAXException;
 
 import static org.apache.solr.common.params.CommonParams.NAME;
@@ -109,9 +109,13 @@ public class SolrConfig implements MapSerializable {
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
   public static final String DEFAULT_CONF_FILE = "solrconfig.xml";
+  private final String resourceName;
 
-  private XmlConfigFile xml;
+//  private XmlConfigFile xml;
+  private int znodeVersion;
   ConfigNode root;
+  private final SolrResourceLoader resourceLoader;
+  private Properties substituteProperties;
 
   private RequestParams requestParams;
 
@@ -174,11 +178,41 @@ public class SolrConfig implements MapSerializable {
    */
   private SolrConfig(SolrResourceLoader loader, String name, boolean isConfigsetTrusted, Properties substitutableProperties)
       throws ParserConfigurationException, IOException, SAXException {
+    this.resourceLoader = loader;
+    this.resourceName = name;
+    this.substituteProperties = substitutableProperties;
+    getOverlay();//just in case it is not initialized
     // insist we have non-null substituteProperties; it might get overlayed
-    xml = new XmlConfigFile(loader, name, null, "/config/", substitutableProperties == null ? new Properties() : substitutableProperties );
-    root = new DataConfigNode(new DOMConfigNode(xml.getDocument().getDocumentElement()));
+    if(loader.getCoreContainer().getZkController() != null ){
+      @SuppressWarnings("unchecked")
+      Map<String, IndexSchemaFactory.VersionedConfig> configCache = (Map<String, IndexSchemaFactory.VersionedConfig>) loader.getCoreContainer().getZkController().getSolrCloudManager().getObjectCache()
+          .computeIfAbsent(IndexSchemaFactory.ConfigResource.class.getName(), s -> new ConcurrentHashMap<>());
+      if(overlay.getZnodeVersion()  == -1) {
+        //currently not optimized for this
+        IndexSchemaFactory.VersionedConfig cfg = configCache.get(name);
+        if(cfg != null){
+          InputStream in = loader.openResource(name);
+          if (in instanceof ZkSolrResourceLoader.ZkByteArrayInputStream) {
+            int zkVersion = ((ZkSolrResourceLoader.ZkByteArrayInputStream) in).getStat().getVersion();
+            if(cfg.version == zkVersion) {
+              root = cfg.data;
+            } else {
+              configCache.remove(name);
+              readXml(loader, name);
+              configCache.put(name, new IndexSchemaFactory.VersionedConfig(this.znodeVersion, root));
+            }
+          }
+        }
+
+      }
+    }
+    if(root == null) {
+      readXml(loader, name);
+    }
+
+//    xml = new XmlConfigFile(loader, name, null, "/config/", substitutableProperties == null ? new Properties() : substitutableProperties );
+//    root = new DataConfigNode(new DOMConfigNode(xml.getDocument().getDocumentElement()));
 //    super(loader, name, null, "/config/", substitutableProperties == null ? new Properties() : substitutableProperties);
-    getOverlay();//just in case it is not initialized
     getRequestParams();
     initLibs(loader, isConfigsetTrusted);
     String val =  root.child(IndexSchema.LUCENE_MATCH_VERSION_PARAM,
@@ -191,14 +225,14 @@ public class SolrConfig implements MapSerializable {
 
     // Old indexDefaults and mainIndex sections are deprecated and fails fast for luceneMatchVersion=>LUCENE_4_0_0.
     // For older solrconfig.xml's we allow the old sections, but never mixed with the new <indexConfig>
-    boolean hasDeprecatedIndexConfig = (root.child("indexDefaults") != null) || (root.child("mainIndex") != null);
+    boolean hasDeprecatedIndexConfig = __("indexDefaults").exists() || __("mainIndex").exists();
     if (hasDeprecatedIndexConfig) {
       throw new SolrException(ErrorCode.FORBIDDEN, "<indexDefaults> and <mainIndex> configuration sections are discontinued. Use <indexConfig> instead.");
     } else {
       indexConfigPrefix = "indexConfig";
     }
     assertWarnOrFail("The <nrtMode> config has been discontinued and NRT mode is always used by Solr." +
-            " This config will be removed in future versions.", root.__("indexDefaults").child("nrtMode") == null,
+            " This config will be removed in future versions.", !__("indexDefaults").__("nrtMode").exists(),
         true
     );
     assertWarnOrFail("Solr no longer supports forceful unlocking via the 'unlockOnStartup' option.  "+
@@ -206,17 +240,14 @@ public class SolrConfig implements MapSerializable {
                      "it would be dangerous and should not be done.  For other lockTypes and/or "+
                      "directoryFactory options it may also be dangerous and users must resolve "+
                      "problematic locks manually.",
-                     null == root.__(indexConfigPrefix).child( "unlockOnStartup"),
+                     __(indexConfigPrefix).__( "unlockOnStartup").exists(),
                      true // 'fail' in trunk
                      );
                      
     // Parse indexConfig section, using mainIndex as backup in case old config is used
-    indexConfig = new SolrIndexConfig(this, "indexConfig", null);
+    indexConfig = new SolrIndexConfig (__("indexConfig"), null);
 
-    booleanQueryMaxClauseCount = root
-        .__("query")
-        .__( "maxBooleanClauses")
-        ._int(IndexSearcher.getMaxClauseCount());
+    booleanQueryMaxClauseCount = __("query").__( "maxBooleanClauses")._int(IndexSearcher.getMaxClauseCount());
     if (IndexSearcher.getMaxClauseCount() < booleanQueryMaxClauseCount) {
       log.warn("solrconfig.xml: <maxBooleanClauses> of {} is greater than global limit of {} {}"
           , booleanQueryMaxClauseCount, IndexSearcher.getMaxClauseCount()
@@ -225,9 +256,9 @@ public class SolrConfig implements MapSerializable {
     
     // Warn about deprecated / discontinued parameters
     // boolToFilterOptimizer has had no effect since 3.1
-    if (__("query").child("boolTofilterOptimizer") != null)
+    if (__("query").__("boolTofilterOptimizer").exists())
       log.warn("solrconfig.xml: <boolTofilterOptimizer> is currently not implemented and has no effect.");
-    if (__("query").child("HashDocSet") != null)
+    if (__("query").__("HashDocSet").exists())
       log.warn("solrconfig.xml: <HashDocSet> is deprecated and no longer used.");
 
 // TODO: Old code - in case somebody wants to re-enable. Also see SolrIndexSearcher#search()
@@ -240,10 +271,10 @@ public class SolrConfig implements MapSerializable {
     queryResultMaxDocsCached = __("query").__("queryResultMaxDocsCached")._int(Integer.MAX_VALUE);
     enableLazyFieldLoading = __("query").__("enableLazyFieldLoading")._bool(false);
     
-    filterCacheConfig = CacheConfig.getConfig(this, __("query").child("filterCache"), "query/filterCache");
-    queryResultCacheConfig = CacheConfig.getConfig(this, __("query").child("queryResultCache"), "query/queryResultCache");
-    documentCacheConfig = CacheConfig.getConfig(this, __("query").child("documentCache"), "query/documentCache");
-    CacheConfig conf = CacheConfig.getConfig(this, __("query").child("fieldValueCache"), "query/fieldValueCache");
+    filterCacheConfig = CacheConfig.getConfig(this, __("query").__("filterCache"), "query/filterCache");
+    queryResultCacheConfig = CacheConfig.getConfig(this, __("query").__("queryResultCache"), "query/queryResultCache");
+    documentCacheConfig = CacheConfig.getConfig(this, __("query").__("documentCache"), "query/documentCache");
+    CacheConfig conf = CacheConfig.getConfig(this, __("query").__("fieldValueCache"), "query/fieldValueCache");
     if (conf == null) {
       Map<String, String> args = new HashMap<>();
       args.put(NAME, "fieldValueCache");
@@ -254,13 +285,13 @@ public class SolrConfig implements MapSerializable {
     }
     fieldValueCacheConfig = conf;
     useColdSearcher = __("query").__("useColdSearcher")._bool(false);
-    dataDir = __("dataDir").textValue();
+    dataDir = __("dataDir").txt();
     if (dataDir != null && dataDir.length() == 0) dataDir = null;
 
 
     org.apache.solr.search.SolrIndexSearcher.initRegenerators(this);
 
-    if (root.child("jmx") != null) {
+    if (__("jmx").exists()) {
       log.warn("solrconfig.xml: <jmx> is no longer supported, use solr.xml:/metrics/reporter section instead");
     }
 
@@ -311,6 +342,12 @@ public class SolrConfig implements MapSerializable {
     log.debug("Loaded SolrConfig: {}", name);
   }
 
+  private void readXml(SolrResourceLoader loader, String name) throws ParserConfigurationException, IOException, SAXException {
+    XmlConfigFile xml = new XmlConfigFile(loader, name, null, "/config/", null);
+    root = new DataConfigNode(new DOMConfigNode(xml.getDocument().getDocumentElement()));
+    this.znodeVersion = xml.getZnodeVersion();
+  }
+
   private static final AtomicBoolean versionWarningAlreadyLogged = new AtomicBoolean(false);
 
   public static final Version parseLuceneVersionString(final String matchVersion) {
@@ -438,12 +475,12 @@ public class SolrConfig implements MapSerializable {
     return new UpdateHandlerInfo( __("updateHandler").attr("class"),
         __("updateHandler").__("autoCommit").__("maxDocs")._int( -1),
         __("updateHandler").__("autoCommit").__("maxTime")._int( -1),
-        convertHeapOptionStyleConfigStringToBytes(__("updateHandler").__("autoCommit").__("maxSize").txt("")),
+        convertHeapOptionStyleConfigStringToBytes(__("updateHandler").__("autoCommit").__("maxSize").txt()),
        __("updateHandler").__("indexWriter").__("closeWaitsForMerges")._bool(true),
         __("updateHandler").__("autoCommit").__("openSearcher")._bool(true),
-        __("updateHandler").__("autoSoftCommit").__("autoSoftCommit")._int(-1),
+        __("updateHandler").__("autoSoftCommit").__("maxDocs")._int(-1),
         __("updateHandler").__("autoSoftCommit").__("maxTime")._int(-1),
-        __("updateHandler").__("commitWithin").__("maxTime")._bool(true));
+        __("updateHandler").__("commitWithin").__("softCommit")._bool(true));
   }
 
   /**
@@ -454,7 +491,7 @@ public class SolrConfig implements MapSerializable {
    * @return the size, in bytes. -1 if the given config string is empty
    */
   protected static long convertHeapOptionStyleConfigStringToBytes(String configStr) {
-    if (configStr.isEmpty()) {
+    if (configStr== null || configStr.isEmpty()) {
       return -1;
     }
     long multiplier = 1;
@@ -572,7 +609,7 @@ public class SolrConfig implements MapSerializable {
           "cacheControl", cacheControlHeader);
     }
 
-    public static enum LastModFrom {
+    public enum LastModFrom {
       OPENTIME, DIRLASTMOD, BOGUS;
 
       /**
@@ -593,18 +630,20 @@ public class SolrConfig implements MapSerializable {
     private final String cacheControlHeader;
     private final Long maxAge;
     private final LastModFrom lastModFrom;
+    private ConfigNode configNode;
 
     private HttpCachingConfig(SolrConfig conf) {
+      configNode = conf.root;
 
       //"requestDispatcher/httpCaching/";
-      never304 = conf.__("requestDispatcher").__("httpCaching").boolAttr("never304", false);
+      never304 = __("requestDispatcher").__("httpCaching").boolAttr("never304", false);
 
-      etagSeed = conf.__("requestDispatcher").__("httpCaching").attr("etagSeed", "Solr");
+      etagSeed = __("requestDispatcher").__("httpCaching").attr("etagSeed", "Solr");
 
 
-      lastModFrom = LastModFrom.parse(conf.__("requestDispatcher").__("httpCaching").attr("lastModFrom","openTime"));
+      lastModFrom = LastModFrom.parse(__("requestDispatcher").__("httpCaching").attr("lastModFrom","openTime"));
 
-      cacheControlHeader = conf.__("requestDispatcher").__("httpCaching").__("cacheControl").textValue();
+      cacheControlHeader = __("requestDispatcher").__("httpCaching").__("cacheControl").txt();
 
       Long tmp = null; // maxAge
       if (null != cacheControlHeader) {
@@ -622,6 +661,9 @@ public class SolrConfig implements MapSerializable {
       maxAge = tmp;
 
     }
+    private ConfigNode __(String name){
+      return configNode.__(name);
+    }
 
     public boolean isNever304() {
       return never304;
@@ -838,29 +880,13 @@ public class SolrConfig implements MapSerializable {
     return enableStreamBody;
   }
 
-  private Object _getVal(String path) {
+  /*private Object _getVal(String path) {
     List<String> parts = StrUtils.split(path,'/');
     Object val = overlay.getXPathProperty(parts);
     if(val !=null) return val;
     return root.child(parts);
   }
 
-
-
-  static int getInt(Object v, int def) {
-    if (v instanceof Number) return ((Number) v).intValue();
-    return v == null ? def : Integer.parseInt(v.toString());
-  }
-
-  static boolean getBool(Object v, boolean def) {
-    if (v instanceof Boolean) return (Boolean) v;
-    return v == null ? def : Boolean.parseBoolean(v.toString());
-  }
-
-  public int _getInt(String path, int def) {
-    Object val = _getVal(path);
-    return val == null ? def : Integer.parseInt(val.toString());
-  }
   public String get(String path) {
     Object val = _getVal(path);
     return val != null ? val.toString() :null;
@@ -870,12 +896,12 @@ public class SolrConfig implements MapSerializable {
     Object val = _getVal(path);
     return val != null ? val.toString() : xml.get(path, def);
 
-  }
+  }*/
 
   @Override
   @SuppressWarnings({"unchecked", "rawtypes"})
   public Map<String, Object> toMap(Map<String, Object> result) {
-    if (xml.getZnodeVersion() > -1) result.put(ZNODEVER, xml.getZnodeVersion());
+    if (znodeVersion > -1) result.put(ZNODEVER, znodeVersion);
     if(luceneMatchVersion != null) result.put(IndexSchema.LUCENE_MATCH_VERSION_PARAM, luceneMatchVersion.toString());
     result.put("updateHandler", getUpdateHandlerInfo());
     Map m = new LinkedHashMap();
@@ -938,8 +964,8 @@ public class SolrConfig implements MapSerializable {
 
   public Properties getSubstituteProperties() {
     Map<String, Object> p = getOverlay().getUserProps();
-    if (p == null || p.isEmpty()) return xml.getSubstituteProperties();
-    Properties result = new Properties(xml.getSubstituteProperties());
+    if (p == null || p.isEmpty()) return substituteProperties;
+    Properties result = new Properties(substituteProperties);
     result.putAll(p);
     return result;
   }
@@ -948,7 +974,7 @@ public class SolrConfig implements MapSerializable {
 
   public ConfigOverlay getOverlay() {
     if (overlay == null) {
-      overlay = getConfigOverlay(xml.getResourceLoader());
+      overlay = getConfigOverlay(resourceLoader);
     }
     return overlay;
   }
@@ -975,12 +1001,8 @@ public class SolrConfig implements MapSerializable {
     if (o == null || PackageLoader.LATEST.equals(o)) return null;
     return o.toString();
   }
-  ConfigNode getRoot() {
-    return root;
-  }
-
   public RequestParams refreshRequestParams() {
-    requestParams = RequestParams.getFreshRequestParams(xml.getResourceLoader(), requestParams);
+    requestParams = RequestParams.getFreshRequestParams(resourceLoader, requestParams);
     if (log.isDebugEnabled()) {
       log.debug("current version of requestparams : {}", requestParams.getZnodeVersion());
     }
@@ -988,24 +1010,33 @@ public class SolrConfig implements MapSerializable {
   }
 
   public SolrResourceLoader getResourceLoader() {
-    return xml.getResourceLoader();
+    return resourceLoader;
   }
 
   public int getZnodeVersion() {
-    return xml.getZnodeVersion();
+    return znodeVersion;
   }
 
   public String getName() {
-    return xml.getName();
+    return resourceName;
   }
 
   public String getResourceName() {
-    return xml.getResourceName();
+    return resourceName;
   }
 
+  /**fetches a child node by name. An "empty node" is returned if the child does not exist
+   * This never returns a null
+   *
+   *
+   */
   public ConfigNode __(String name) {
     return root.__(name);
   }
 
+  ConfigNode __(String name, Predicate<ConfigNode> test) {
+    return root.__(name, test);
+  }
+
 
 }
diff --git a/solr/core/src/java/org/apache/solr/schema/IndexSchemaFactory.java b/solr/core/src/java/org/apache/solr/schema/IndexSchemaFactory.java
index c9d3187..0131943 100644
--- a/solr/core/src/java/org/apache/solr/schema/IndexSchemaFactory.java
+++ b/solr/core/src/java/org/apache/solr/schema/IndexSchemaFactory.java
@@ -141,8 +141,8 @@ public abstract class IndexSchemaFactory implements NamedListInitializedPlugin {
   }
 
   public static class VersionedConfig {
-    final int version;
-    final ConfigNode data;
+    public final int version;
+    public final ConfigNode data;
 
     public VersionedConfig(int version, ConfigNode data) {
       this.version = version;
diff --git a/solr/core/src/java/org/apache/solr/search/CacheConfig.java b/solr/core/src/java/org/apache/solr/search/CacheConfig.java
index 1f83b56..465494b 100644
--- a/solr/core/src/java/org/apache/solr/search/CacheConfig.java
+++ b/solr/core/src/java/org/apache/solr/search/CacheConfig.java
@@ -109,7 +109,7 @@ public class CacheConfig implements MapSerializable{
   @SuppressWarnings({"unchecked"})
   public static CacheConfig getConfig(SolrConfig solrConfig, ConfigNode node, String xpath) {
 //    Node node = solrConfig.getNode(xpath, false);
-    if(node == null || !"true".equals(node.attributes().get( "enabled", "true"))) {
+    if(!node.exists() || !"true".equals(node.attributes().get( "enabled", "true"))) {
       Map<String, String> m = solrConfig.getOverlay().getEditableSubProperties(xpath);
       if(m==null) return null;
       List<String> parts = StrUtils.splitSmart(xpath, '/');
diff --git a/solr/core/src/java/org/apache/solr/update/SolrIndexConfig.java b/solr/core/src/java/org/apache/solr/update/SolrIndexConfig.java
index cf24e33..ebb8395 100644
--- a/solr/core/src/java/org/apache/solr/update/SolrIndexConfig.java
+++ b/solr/core/src/java/org/apache/solr/update/SolrIndexConfig.java
@@ -19,7 +19,6 @@ package org.apache.solr.update;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.util.Collections;
-import java.util.List;
 import java.util.Map;
 
 import org.apache.lucene.analysis.Analyzer;
@@ -37,7 +36,6 @@ import org.apache.solr.common.util.Utils;
 import org.apache.solr.core.DirectoryFactory;
 import org.apache.solr.common.MapSerializable;
 import org.apache.solr.core.PluginInfo;
-import org.apache.solr.core.SolrConfig;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.SolrResourceLoader;
 import org.apache.solr.index.DefaultMergePolicyFactory;
@@ -48,7 +46,6 @@ import org.apache.solr.schema.IndexSchema;
 import org.apache.solr.util.SolrPluginUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.w3c.dom.Node;
 
 import static org.apache.solr.core.XmlConfigFile.assertWarnOrFail;
 
@@ -93,11 +90,12 @@ public class SolrIndexConfig implements MapSerializable {
   public final PluginInfo mergedSegmentWarmerInfo;
   
   public InfoStream infoStream = InfoStream.NO_OUTPUT;
+  private ConfigNode node;
 
   /**
    * Internal constructor for setting defaults based on Lucene Version
    */
-  private SolrIndexConfig(SolrConfig solrConfig) {
+  private SolrIndexConfig() {
     useCompoundFile = false;
     maxBufferedDocs = -1;
     ramBufferSizeMB = 100;
@@ -111,23 +109,18 @@ public class SolrIndexConfig implements MapSerializable {
     // enable coarse-grained metrics by default
     metricsInfo = new PluginInfo("metrics", Collections.emptyMap(), null, null);
   }
-  
+  private ConfigNode __(String s) { return node.__(s); }
   /**
    * Constructs a SolrIndexConfig which parses the Lucene related config params in solrconfig.xml
-   * @param solrConfig the overall SolrConfig object
-   * @param prefix the XPath prefix for which section to parse (mandatory)
    * @param def a SolrIndexConfig instance to pick default values from (optional)
    */
-  public SolrIndexConfig(SolrConfig solrConfig, String prefix, SolrIndexConfig def)  {
-    if (prefix == null) {
-      prefix = "indexConfig";
-      log.debug("Defaulting to prefix '{}' for index configuration", prefix);
-    }
-    
+  public SolrIndexConfig(ConfigNode cfg, SolrIndexConfig def)  {
+    this.node = cfg;
     if (def == null) {
-      def = new SolrIndexConfig(solrConfig);
+      def = new SolrIndexConfig();
     }
 
+
     // sanity check: this will throw an error for us if there is more then one
     // config section
 //    Object unused =  solrConfig.getNode(prefix, false);
@@ -135,65 +128,56 @@ public class SolrIndexConfig implements MapSerializable {
     // Assert that end-of-life parameters or syntax is not in our config.
     // Warn for luceneMatchVersion's before LUCENE_3_6, fail fast above
     assertWarnOrFail("The <mergeScheduler>myclass</mergeScheduler> syntax is no longer supported in solrconfig.xml. Please use syntax <mergeScheduler class=\"myclass\"/> instead.",
-        !(solrConfig.__(prefix).__("mergeScheduler") != null && (solrConfig.__(prefix).attr("class", null) == null)),
+        __("mergeScheduler").exists() && __("mergeScheduler").attr("class", null) == null,
         true);
     assertWarnOrFail("Beginning with Solr 7.0, <mergePolicy>myclass</mergePolicy> is no longer supported, use <mergePolicyFactory> instead.",
-        !((solrConfig.__(prefix).__("mergePolicy") != null) && (solrConfig.__(prefix).__("mergePolicy").attr("class", null) == null)),
+        __("mergePolicy").exists() && __("mergePolicy").attr("class") == null,
         true);
     assertWarnOrFail("The <luceneAutoCommit>true|false</luceneAutoCommit> parameter is no longer valid in solrconfig.xml.",
-        solrConfig.get(prefix + "/luceneAutoCommit", null) == null,
+        __("luceneAutoCommit").textValue() != null,
         true);
 
-    useCompoundFile = solrConfig.__(prefix).__("useCompoundFile")._bool(def.useCompoundFile);
-    maxBufferedDocs = solrConfig.__(prefix).__("maxBufferedDocs")._int(def.maxBufferedDocs);
-    ramBufferSizeMB = solrConfig.__(prefix).__("ramBufferSizeMB").doubleVal(def.ramBufferSizeMB);
-    maxCommitMergeWaitMillis = solrConfig.__(prefix).__("maxCommitMergeWaitTime")._int(def.maxCommitMergeWaitMillis);
+    useCompoundFile = __("useCompoundFile")._bool(def.useCompoundFile);
+    maxBufferedDocs = __("maxBufferedDocs")._int(def.maxBufferedDocs);
+    ramBufferSizeMB = __("ramBufferSizeMB").doubleVal(def.ramBufferSizeMB);
+    maxCommitMergeWaitMillis = __("maxCommitMergeWaitTime")._int(def.maxCommitMergeWaitMillis);
 
     // how do we validate the value??
-    ramPerThreadHardLimitMB = solrConfig.__(prefix).__("ramPerThreadHardLimitMB")._int(def.ramPerThreadHardLimitMB);
+    ramPerThreadHardLimitMB = __("ramPerThreadHardLimitMB")._int(def.ramPerThreadHardLimitMB);
 
-    writeLockTimeout= solrConfig.__(prefix).__("writeLockTimeout")._int(def.writeLockTimeout);
-    lockType=solrConfig.get(prefix+"/lockType", def.lockType);
+    writeLockTimeout= __("writeLockTimeout")._int(def.writeLockTimeout);
+    lockType = __("lockType").txt(def.lockType);
 
-    List<PluginInfo> infos = solrConfig.readPluginInfos(prefix + "/metrics", false, false);
-    if (infos.isEmpty()) {
-      metricsInfo = def.metricsInfo;
-    } else {
-      metricsInfo = infos.get(0);
-    }
-    mergeSchedulerInfo = getPluginInfo(prefix + "/mergeScheduler", solrConfig, def.mergeSchedulerInfo);
-    mergePolicyFactoryInfo = getPluginInfo(prefix + "/mergePolicyFactory", solrConfig, def.mergePolicyFactoryInfo);
+    metricsInfo = getPluginInfo(__("metrics"), def.metricsInfo);
+    mergeSchedulerInfo = getPluginInfo(__("mergeScheduler"), def.mergeSchedulerInfo);
+    mergePolicyFactoryInfo = getPluginInfo(__("mergePolicyFactory"), def.mergePolicyFactoryInfo);
 
     assertWarnOrFail("Beginning with Solr 7.0, <mergePolicy> is no longer supported, use <mergePolicyFactory> instead.",
-        getPluginInfo(prefix + "/mergePolicy", solrConfig, null) == null,
+        __("mergePolicy").exists(),
         true);
     assertWarnOrFail("Beginning with Solr 7.0, <maxMergeDocs> is no longer supported, configure it on the relevant <mergePolicyFactory> instead.",
-        solrConfig.__(prefix).__("maxMergeDocs")._int(0) == 0,
+        __("maxMergeDocs").exists(),
         true);
     assertWarnOrFail("Beginning with Solr 7.0, <mergeFactor> is no longer supported, configure it on the relevant <mergePolicyFactory> instead.",
-        solrConfig.__(prefix).__("mergeFactor")._int(0) == 0,
+        __("maxMergeFactor").exists(),
         true);
 
-    String val = solrConfig.get(prefix + "/termIndexInterval", null);
-    if (val != null) {
+    if (__("termIndexInterval").txt() != null) {
       throw new IllegalArgumentException("Illegal parameter 'termIndexInterval'");
     }
 
-    boolean infoStreamEnabled = solrConfig.__(prefix).__("infoStream")._bool(false);
-    if(infoStreamEnabled) {
-      String infoStreamFile = solrConfig.__(prefix).__("infoStream").attr("file") ;
-      if (infoStreamFile == null) {
+    if(__("infoStream")._bool(false)) {
+      if (__("infoStream").attr("file") == null) {
         log.info("IndexWriter infoStream solr logging is enabled");
         infoStream = new LoggingInfoStream();
       } else {
         throw new IllegalArgumentException("Remove @file from <infoStream> to output messages to solr's logfile");
       }
     }
-    ConfigNode warmerInfo = solrConfig.__(prefix).__("mergedSegmentWarmer");
-    mergedSegmentWarmerInfo = warmerInfo==null? def.mergedSegmentWarmerInfo : new PluginInfo(warmerInfo, "[solrconfig.xml] mergedSegmentWarmer" , false, false);
+    mergedSegmentWarmerInfo = getPluginInfo(__("mergedSegmentWarmer"), def.mergedSegmentWarmerInfo);
 
     assertWarnOrFail("Beginning with Solr 5.0, <checkIntegrityAtMerge> option is no longer supported and should be removed from solrconfig.xml (these integrity checks are now automatic)",
-        (null == solrConfig.__(prefix).__( "checkIntegrityAtMerge")),
+        __( "checkIntegrityAtMerge").exists(),
         true);
   }
 
@@ -218,9 +202,10 @@ public class SolrIndexConfig implements MapSerializable {
     return m;
   }
 
-  private PluginInfo getPluginInfo(String path, SolrConfig solrConfig, PluginInfo def)  {
-    List<PluginInfo> l = solrConfig.readPluginInfos(path, false, true);
-    return l.isEmpty() ? def : l.get(0);
+  private PluginInfo getPluginInfo(ConfigNode node , PluginInfo def)  {
+    return node != null && node.exists() ?
+        new PluginInfo(node, "[solrconfig.xml] " + node.name(), false, true) :
+        def;
   }
 
   private static class DelayedSchemaAnalyzer extends DelegatingAnalyzerWrapper {
diff --git a/solr/core/src/java/org/apache/solr/update/VersionInfo.java b/solr/core/src/java/org/apache/solr/update/VersionInfo.java
index b97f812..cd36854 100644
--- a/solr/core/src/java/org/apache/solr/update/VersionInfo.java
+++ b/solr/core/src/java/org/apache/solr/update/VersionInfo.java
@@ -94,8 +94,8 @@ public class VersionInfo {
     this.ulog = ulog;
     IndexSchema schema = ulog.uhandler.core.getLatestSchema(); 
     versionField = getAndCheckVersionField(schema);
-    versionBucketLockTimeoutMs = ulog.uhandler.core.getSolrConfig().getInt("updateHandler/versionBucketLockTimeoutMs",
-        Integer.parseInt(System.getProperty(SYS_PROP_BUCKET_VERSION_LOCK_TIMEOUT_MS, "0")));
+    versionBucketLockTimeoutMs = ulog.uhandler.core.getSolrConfig().__("updateHandler").__("versionBucketLockTimeoutMs")
+        ._int(Integer.parseInt(System.getProperty(SYS_PROP_BUCKET_VERSION_LOCK_TIMEOUT_MS, "0")));
     buckets = new VersionBucket[ BitUtil.nextHighestPowerOfTwo(nBuckets) ];
     for (int i=0; i<buckets.length; i++) {
       if (versionBucketLockTimeoutMs > 0) {
diff --git a/solr/core/src/java/org/apache/solr/util/DataConfigNode.java b/solr/core/src/java/org/apache/solr/util/DataConfigNode.java
index 38940ee..04d927b 100644
--- a/solr/core/src/java/org/apache/solr/util/DataConfigNode.java
+++ b/solr/core/src/java/org/apache/solr/util/DataConfigNode.java
@@ -55,9 +55,7 @@ public class DataConfigNode implements ConfigNode {
   }
 
   public String subtituteVal(String s) {
-    Function<String, String> props = SUBSTITUTES.get();
-    if (props == null) return s;
-    return PropertiesUtil.substitute(s, props);
+    return PropertiesUtil.substitute(s, SUBSTITUTES.get());
   }
 
   private SimpleMap<String> wrap(SimpleMap<String> delegate) {
diff --git a/solr/core/src/test/org/apache/solr/core/TestConfig.java b/solr/core/src/test/org/apache/solr/core/TestConfig.java
index 7a61fb7..fe39552 100644
--- a/solr/core/src/test/org/apache/solr/core/TestConfig.java
+++ b/solr/core/src/test/org/apache/solr/core/TestConfig.java
@@ -22,6 +22,7 @@ import java.io.InputStream;
 import java.util.LinkedHashMap;
 import java.util.Collections;
 import java.util.List;
+import java.util.function.Predicate;
 
 import org.apache.lucene.index.ConcurrentMergeScheduler;
 import org.apache.lucene.index.IndexWriterConfig;
@@ -91,14 +92,14 @@ public class TestConfig extends SolrTestCaseJ4 {
     s = solrConfig.__("propTest").attr("attr2", "default");
     assertEquals("default-from-config", s);
 
-    s = solrConfig.get("propTest[@attr2='default-from-config']", "default");
-    assertEquals("prefix-proptwo-suffix", s);
 
-    List<ConfigNode> nl = solrConfig.root.children("propTest");
+    assertEquals("prefix-proptwo-suffix", solrConfig.__("propTest",
+        it -> "default-from-config".equals(it.attr("attr2"))).txt());
+
+    List<ConfigNode> nl = solrConfig.root.children ("propTest");
     assertEquals(1, nl.size());
     assertEquals("prefix-proptwo-suffix", nl.get(0).textValue());
 
-
     assertEquals("prefix-proptwo-suffix", solrConfig.__("propTest"));
   }
 
diff --git a/solr/solrj/src/java/org/apache/solr/common/ConfigNode.java b/solr/solrj/src/java/org/apache/solr/common/ConfigNode.java
index 17fd678..e88ca81 100644
--- a/solr/solrj/src/java/org/apache/solr/common/ConfigNode.java
+++ b/solr/solrj/src/java/org/apache/solr/common/ConfigNode.java
@@ -19,9 +19,7 @@ package org.apache.solr.common;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.Optional;
 import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
@@ -29,6 +27,8 @@ import java.util.function.Supplier;
 import org.apache.solr.cluster.api.SimpleMap;
 import org.apache.solr.common.util.WrappedSimpleMap;
 
+import static org.apache.solr.common.ConfigNode.Helpers.*;
+
 /**
  * A generic interface that represents a config file, mostly XML
  */
@@ -59,11 +59,19 @@ public interface ConfigNode {
 
   /**
    * Child by name or return an empty node if null
+   * if there are multiple values , it returns the first elem
+   * This never returns a null
    */
   default ConfigNode __(String name) {
     ConfigNode child = child(null, name);
     return child == null? EMPTY: child;
   }
+  default ConfigNode __(String name, Predicate<ConfigNode> test) {
+    List<ConfigNode> children = children(test, name);
+    if(children.isEmpty()) return EMPTY;
+    return children.get(0);
+
+  }
 
   default ConfigNode child(List<String> path) {
     ConfigNode node = this;
@@ -91,12 +99,8 @@ public interface ConfigNode {
   default int intAttr(String name, int def) { return __int(attributes().get(name), def); }
   default boolean boolAttr(String name, boolean def){ return __bool(attributes().get(name), def); }
   default String txt(String def) { return textValue() == null ? def : textValue();}
+  default String txt() { return textValue();}
   default double doubleVal(double def){ return __double(textValue(), def); }
-  default boolean __bool(Object v, boolean def) { return v == null ? def : Boolean.parseBoolean(v.toString()); }
-  default String __txt(Object v, String def) { return v == null ? def : v.toString(); }
-  default int __int(Object v, int def) { return v==null? def: Integer.parseInt(v.toString()); }
-  default double __double(Object v, double def) { return v == null ? def: Double.parseDouble(v.toString()); }
-
   /**Iterate through child nodes with the name and return the first child that matches
    */
   default ConfigNode child(Predicate<ConfigNode> test, String name) {
@@ -138,6 +142,14 @@ public interface ConfigNode {
     return children(null, Collections.singleton(name));
   }
 
+  default boolean exists() {
+    return true;
+  }
+
+  default <T> T compute(Function<ConfigNode, T> ifNotNull, Supplier<T> ifNull) {
+    return ifNotNull.apply(this);
+  }
+
   /** abortable iterate through children
    *
    * @param fun consume the node and return true to continue or false to abort
@@ -151,9 +163,7 @@ public interface ConfigNode {
     }
 
     @Override
-    public String textValue() {
-      return null;
-    }
+    public String textValue() { return null; }
 
     @Override
     public SimpleMap<String> attributes() {
@@ -161,21 +171,34 @@ public interface ConfigNode {
     }
 
     @Override
-    public ConfigNode child(String name) {
-      return null;
-    }
+    public String attr(String name) { return null; }
+
+    @Override
+    public String attr(String name, String def) { return def; }
+
+    @Override
+    public ConfigNode child(String name) { return null; }
 
     @Override
     public ConfigNode __(String name) {
       return EMPTY;
     }
 
+    public boolean exists() { return false; }
+
     @Override
-    public void forEachChild(Function<ConfigNode, Boolean> fun) {
+    public <T> T compute(Function<ConfigNode, T> ifNotNull, Supplier<T> ifNull) { return ifNull.get(); }
 
-    }
+    @Override
+    public void forEachChild(Function<ConfigNode, Boolean> fun) { }
   } ;
   SimpleMap<String> empty_attrs = new WrappedSimpleMap<>(Collections.emptyMap());
 
+  class Helpers {
+    static boolean __bool(Object v, boolean def) { return v == null ? def : Boolean.parseBoolean(v.toString()); }
+    static String __txt(Object v, String def) { return v == null ? def : v.toString(); }
+    static int __int(Object v, int def) { return v==null? def: Integer.parseInt(v.toString()); }
+    static double __double(Object v, double def) { return v == null ? def: Double.parseDouble(v.toString()); }
+  }
 
 }