You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by no...@apache.org on 2021/04/19 00:20:06 UTC

[lucene-solr] branch jira/solr15337 created (now 1ee3629)

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

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


      at 1ee3629  solr15337 untested

This branch includes the following new commits:

     new 1ee3629  solr15337 untested

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


[lucene-solr] 01/01: solr15337 untested

Posted by no...@apache.org.
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/lucene-solr.git

commit 1ee3629621345df206363c1ce2615445f000d508
Author: Noble Paul <no...@gmail.com>
AuthorDate: Mon Apr 19 10:19:34 2021 +1000

    solr15337 untested
---
 .../java/org/apache/solr/core/ConfigOverlay.java   |  14 +-
 .../src/java/org/apache/solr/core/PluginInfo.java  |  24 ++
 .../src/java/org/apache/solr/core/SolrConfig.java  | 262 +++++++++++++--------
 .../java/org/apache/solr/schema/IndexSchema.java   |   4 +-
 .../org/apache/solr/schema/IndexSchemaFactory.java |   4 +-
 .../java/org/apache/solr/search/CacheConfig.java   |  46 ++--
 .../org/apache/solr/update/SolrIndexConfig.java    |  83 +++----
 .../java/org/apache/solr/update/VersionInfo.java   |   4 +-
 .../java/org/apache/solr/util/DataConfigNode.java  |   4 +-
 .../org/apache/solr/core/TestCodecSupport.java     |   6 +-
 .../src/test/org/apache/solr/core/TestConfig.java  |  25 +-
 .../org/apache/solr/core/TestSimpleTextCodec.java  |   2 +-
 .../handler/component/SuggestComponentTest.java    | 127 +++++++---
 .../apache/solr/update/SolrIndexConfigTest.java    |  12 +-
 .../org/apache/solr/cluster/api/SimpleMap.java     |   8 +
 .../java/org/apache/solr/common/ConfigNode.java    | 120 ++++++++++
 .../apache/solr/common/util/WrappedSimpleMap.java  |   5 +
 17 files changed, 512 insertions(+), 238 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 1bc8824..62cab00 100644
--- a/solr/core/src/java/org/apache/solr/core/ConfigOverlay.java
+++ b/solr/core/src/java/org/apache/solr/core/ConfigOverlay.java
@@ -16,12 +16,14 @@
  */
 package org.apache.solr.core;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
 import org.apache.solr.common.MapSerializable;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.CoreAdminParams;
@@ -62,6 +64,12 @@ public class ConfigOverlay implements MapSerializable {
     return Utils.getObjectByPath(props, onlyPrimitive, hierarchy);
   }
 
+  public Object getXPathProperty(List<String> path) {
+    List<String> hierarchy = new ArrayList<>();
+    if(isEditable(true, hierarchy, path) == null) return null;
+    return Utils.getObjectByPath(props, true, hierarchy);
+  }
+
   @SuppressWarnings({"unchecked"})
   public ConfigOverlay setUserProperty(String key, Object val) {
     @SuppressWarnings({"rawtypes"})
@@ -180,7 +188,11 @@ public class ConfigOverlay implements MapSerializable {
 
   @SuppressWarnings({"rawtypes"})
   public static Class checkEditable(String path, boolean isXpath, List<String> hierarchy) {
-    List<String> parts = StrUtils.splitSmart(path, isXpath ? '/' : '.');
+    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++) {
       String part = parts.get(i);
diff --git a/solr/core/src/java/org/apache/solr/core/PluginInfo.java b/solr/core/src/java/org/apache/solr/core/PluginInfo.java
index 011d933..2dcab89 100644
--- a/solr/core/src/java/org/apache/solr/core/PluginInfo.java
+++ b/solr/core/src/java/org/apache/solr/core/PluginInfo.java
@@ -23,6 +23,7 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
+import org.apache.solr.common.ConfigNode;
 import org.apache.solr.common.MapSerializable;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.DOMUtil;
@@ -99,6 +100,18 @@ public class PluginInfo implements MapSerializable {
   }
 
 
+  public PluginInfo(ConfigNode node,  String err,boolean requireName, boolean requireClass) {
+    type = node.name();
+    name = node.requiredStrAttr(NAME,requireName? () -> new RuntimeException(err + ": missing mandatory attribute 'name'"):null);
+    cName = parseClassName(node.requiredStrAttr(CLASS_NAME, requireClass? () -> new RuntimeException(err + ": missing mandatory attribute 'class'"):null ));
+    className = cName.className;
+    pkgName = cName.pkg;
+    initArgs = DOMUtil.childNodesToNamedList(node);
+    attributes = node.attributes().asMap();
+    children = loadSubPlugins(node);
+    isFromSolrConfig = true;
+
+  }
   public PluginInfo(Node node, String err, boolean requireName, boolean requireClass) {
     type = node.getNodeName();
     name = DOMUtil.getAttr(node, NAME, requireName ? err : null);
@@ -143,6 +156,17 @@ public class PluginInfo implements MapSerializable {
     isFromSolrConfig = true;
   }
 
+  private List<PluginInfo> loadSubPlugins(ConfigNode node) {
+    List<PluginInfo> children = new ArrayList<>();
+    //if there is another sub tag with a non namedlist tag that has to be another plugin
+    node.forEachChild(nd -> {
+      if (NL_TAGS.contains(nd.name())) return null;
+      PluginInfo pluginInfo = new PluginInfo(nd, null, false, false);
+      if (pluginInfo.isEnabled()) children.add(pluginInfo);
+      return null;
+    });
+    return children.isEmpty() ? Collections.<PluginInfo>emptyList() : unmodifiableList(children);
+  }
   private List<PluginInfo> loadSubPlugins(Node node) {
     List<PluginInfo> children = new ArrayList<>();
     //if there is another sub tag with a non namedlist tag that has to be another plugin
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 eb4909f..6f1d84e 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrConfig.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrConfig.java
@@ -40,7 +40,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;
 
@@ -52,6 +54,7 @@ import org.apache.lucene.util.Version;
 import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.cloud.RecoveryStrategy;
 import org.apache.solr.cloud.ZkSolrResourceLoader;
+import org.apache.solr.common.ConfigNode;
 import org.apache.solr.common.MapSerializable;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
@@ -77,12 +80,12 @@ import org.apache.solr.update.SolrIndexConfig;
 import org.apache.solr.update.UpdateLog;
 import org.apache.solr.update.processor.UpdateRequestProcessorChain;
 import org.apache.solr.update.processor.UpdateRequestProcessorFactory;
+import org.apache.solr.util.DOMConfigNode;
+import org.apache.solr.util.DataConfigNode;
 import org.apache.solr.common.util.DOMUtil;
 import org.apache.solr.util.circuitbreaker.CircuitBreakerManager;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
 import org.xml.sax.SAXException;
 
 import static org.apache.solr.common.params.CommonParams.NAME;
@@ -96,6 +99,7 @@ import static org.apache.solr.core.SolrConfig.PluginOpts.NOOP;
 import static org.apache.solr.core.SolrConfig.PluginOpts.REQUIRE_CLASS;
 import static org.apache.solr.core.SolrConfig.PluginOpts.REQUIRE_NAME;
 import static org.apache.solr.core.SolrConfig.PluginOpts.REQUIRE_NAME_IN_OVERLAY;
+import static org.apache.solr.core.XmlConfigFile.assertWarnOrFail;
 
 
 /**
@@ -103,12 +107,18 @@ import static org.apache.solr.core.SolrConfig.PluginOpts.REQUIRE_NAME_IN_OVERLAY
  * configuration data for a a Solr instance -- typically found in
  * "solrconfig.xml".
  */
-public class SolrConfig extends XmlConfigFile implements MapSerializable {
+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 int znodeVersion;
+  ConfigNode root;
+  private final SolrResourceLoader resourceLoader;
+  private Properties substituteProperties;
 
   private RequestParams requestParams;
 
@@ -171,26 +181,61 @@ public class SolrConfig extends XmlConfigFile implements MapSerializable {
    */
   private SolrConfig(SolrResourceLoader loader, String name, boolean isConfigsetTrusted, Properties substitutableProperties)
       throws ParserConfigurationException, IOException, SAXException {
-    // insist we have non-null substituteProperties; it might get overlayed
-    super(loader, name, null, "/config/", substitutableProperties == null ? new Properties() : substitutableProperties);
+    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
+    if(loader.getCoreContainer()!= null && loader.getCoreContainer().getZkController() != null ){
+      @SuppressWarnings("unchecked")
+      Map<String, IndexSchemaFactory.VersionedConfig> configCache = (Map<String, IndexSchemaFactory.VersionedConfig>) loader.getCoreContainer().getZkController().getSolrCloudManager().getObjectCache()
+          .computeIfAbsent(ConfigSetService.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);
     getRequestParams();
     initLibs(loader, isConfigsetTrusted);
-    luceneMatchVersion = SolrConfig.parseLuceneVersionString(getVal(IndexSchema.LUCENE_MATCH_VERSION_PARAM, true));
+    String val =  root.child(IndexSchema.LUCENE_MATCH_VERSION_PARAM,
+        () -> new RuntimeException("Missing: "+ IndexSchema.LUCENE_MATCH_VERSION_PARAM)) .textValue() ;
+
+    luceneMatchVersion = SolrConfig.parseLuceneVersionString(val);
     log.info("Using Lucene MatchVersion: {}", luceneMatchVersion);
 
     String indexConfigPrefix;
 
     // 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 = (getNode("indexDefaults", false) != null) || (getNode("mainIndex", false) != 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.", getNode(indexConfigPrefix + "/nrtMode", false) == null,
+            " This config will be removed in future versions.", !__("indexDefaults").__("nrtMode").exists(),
         true
     );
     assertWarnOrFail("Solr no longer supports forceful unlocking via the 'unlockOnStartup' option.  "+
@@ -198,14 +243,14 @@ public class SolrConfig extends XmlConfigFile 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 == getNode(indexConfigPrefix + "/unlockOnStartup", false),
+                     !__(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 = getInt("query/maxBooleanClauses", BooleanQuery.getMaxClauseCount());
+    booleanQueryMaxClauseCount = __("query").__("maxBooleanClauses")._int(BooleanQuery.getMaxClauseCount());
     if (BooleanQuery.getMaxClauseCount() < booleanQueryMaxClauseCount) {
       log.warn("solrconfig.xml: <maxBooleanClauses> of {} is greater than global limit of {} and will have no effect {}"
           , booleanQueryMaxClauseCount, BooleanQuery.getMaxClauseCount()
@@ -214,9 +259,9 @@ public class SolrConfig extends XmlConfigFile implements MapSerializable {
     
     // Warn about deprecated / discontinued parameters
     // boolToFilterOptimizer has had no effect since 3.1
-    if (get("query/boolTofilterOptimizer", null) != null)
+    if (__("query").__("boolTofilterOptimizer").exists())
       log.warn("solrconfig.xml: <boolTofilterOptimizer> is currently not implemented and has no effect.");
-    if (get("query/HashDocSet", null) != 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()
@@ -224,15 +269,15 @@ public class SolrConfig extends XmlConfigFile implements MapSerializable {
 //    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);
+    useFilterForSortedQuery = __("query").__("useFilterForSortedQuery")._bool(false);
+    queryResultWindowSize = Math.max(1, __("query").__("queryResultWindowSize")._int(1));
+    queryResultMaxDocsCached = __("query").__("queryResultMaxDocsCached")._int(Integer.MAX_VALUE);
+    enableLazyFieldLoading = __("query").__("enableLazyFieldLoading")._bool(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");
+    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");
@@ -242,24 +287,25 @@ public class SolrConfig extends XmlConfigFile implements MapSerializable {
       conf = new CacheConfig(FastLRUCache.class, args, null);
     }
     fieldValueCacheConfig = conf;
-    useColdSearcher = getBool("query/useColdSearcher", false);
-    dataDir = get("dataDir", null);
+    useColdSearcher = __("query").__("useColdSearcher")._bool(false);
+    dataDir = __("dataDir").txt();
     if (dataDir != null && dataDir.length() == 0) dataDir = null;
 
 
     org.apache.solr.search.SolrIndexSearcher.initRegenerators(this);
 
-    if (get("jmx", null) != null) {
+    if (__("jmx").exists()) {
       log.warn("solrconfig.xml: <jmx> is no longer supported, use solr.xml:/metrics/reporter section instead");
     }
 
     httpCachingConfig = new HttpCachingConfig(this);
 
-    maxWarmingSearchers = getInt("query/maxWarmingSearchers", 1);
-    slowQueryThresholdMillis = getInt("query/slowQueryThresholdMillis", -1);
+    maxWarmingSearchers = __("query").__("maxWarmingSearchers")._int(1);
+    slowQueryThresholdMillis = __("query").__("slowQueryThresholdMillis")._int(-1);
     for (SolrPluginInfo plugin : plugins) loadPluginInfo(plugin);
 
-    Map<String, CacheConfig> userCacheConfigs = CacheConfig.getMultipleConfigs(this, "query/cache");
+    Map<String, CacheConfig> userCacheConfigs = CacheConfig.getMultipleConfigs(this, "query/cache",
+        __("query").children("cache"));
     List<PluginInfo> caches = getPluginInfos(SolrCache.class.getName());
     if (!caches.isEmpty()) {
       for (PluginInfo c : caches) {
@@ -270,25 +316,18 @@ public class SolrConfig extends XmlConfigFile implements MapSerializable {
 
     updateHandlerInfo = loadUpdatehandlerInfo();
 
-    multipartUploadLimitKB = getInt(
-        "requestDispatcher/requestParsers/@multipartUploadLimitInKB", Integer.MAX_VALUE);
+    multipartUploadLimitKB = __("requestDispatcher").__("requestParsers").intAttr("multipartUploadLimitInKB", Integer.MAX_VALUE);
     if (multipartUploadLimitKB == -1) multipartUploadLimitKB = Integer.MAX_VALUE;
 
-    formUploadLimitKB = getInt(
-        "requestDispatcher/requestParsers/@formdataUploadLimitInKB", Integer.MAX_VALUE);
+    formUploadLimitKB = __("requestDispatcher").__("requestParsers").intAttr("formdataUploadLimitInKB", Integer.MAX_VALUE);
     if (formUploadLimitKB == -1) formUploadLimitKB = Integer.MAX_VALUE;
 
-    enableRemoteStreams = getBool(
-        "requestDispatcher/requestParsers/@enableRemoteStreaming", false);
-
-    enableStreamBody = getBool(
-        "requestDispatcher/requestParsers/@enableStreamBody", false);
+    enableRemoteStreams = __("requestDispatcher").__("requestParsers").boolAttr("enableRemoteStreaming", false);
 
-    handleSelect = getBool(
-        "requestDispatcher/@handleSelect", !luceneMatchVersion.onOrAfter(Version.LUCENE_7_0_0));
+    enableStreamBody = __("requestDispatcher").__("requestParsers").boolAttr("enableStreamBody", false);
 
-    addHttpRequestToContext = getBool(
-        "requestDispatcher/requestParsers/@addHttpRequestToContext", false);
+    handleSelect = __("requestDispatcher").boolAttr("handleSelect", !luceneMatchVersion.onOrAfter(Version.LUCENE_7_0_0)) ;
+    addHttpRequestToContext = __("requestDispatcher").__("requestParsers").boolAttr("addHttpRequestToContext", false);
 
     List<PluginInfo> argsInfos = getPluginInfos(InitParams.class.getName());
     if (argsInfos != null) {
@@ -305,6 +344,12 @@ public class SolrConfig extends XmlConfigFile 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) {
@@ -430,15 +475,15 @@ public class SolrConfig extends XmlConfigFile implements MapSerializable {
   }
 
   protected UpdateHandlerInfo loadUpdatehandlerInfo() {
-    return new UpdateHandlerInfo(get("updateHandler/@class", null),
-        getInt("updateHandler/autoCommit/maxDocs", -1),
-        getInt("updateHandler/autoCommit/maxTime", -1),
-        convertHeapOptionStyleConfigStringToBytes(get("updateHandler/autoCommit/maxSize", "")),
-        getBool("updateHandler/indexWriter/closeWaitsForMerges", true),
-        getBool("updateHandler/autoCommit/openSearcher", true),
-        getInt("updateHandler/autoSoftCommit/maxDocs", -1),
-        getInt("updateHandler/autoSoftCommit/maxTime", -1),
-        getBool("updateHandler/commitWithin/softCommit", true));
+    return new UpdateHandlerInfo( __("updateHandler").attr("class"),
+        __("updateHandler").__("autoCommit").__("maxDocs")._int( -1),
+        __("updateHandler").__("autoCommit").__("maxTime")._int( -1),
+        convertHeapOptionStyleConfigStringToBytes(__("updateHandler").__("autoCommit").__("maxSize").txt()),
+       __("updateHandler").__("indexWriter").__("closeWaitsForMerges")._bool(true),
+        __("updateHandler").__("autoCommit").__("openSearcher")._bool(true),
+        __("updateHandler").__("autoSoftCommit").__("maxDocs")._int(-1),
+        __("updateHandler").__("autoSoftCommit").__("maxTime")._int(-1),
+        __("updateHandler").__("commitWithin").__("softCommit")._bool(true));
   }
 
   /**
@@ -449,7 +494,7 @@ public class SolrConfig extends XmlConfigFile 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;
@@ -496,9 +541,9 @@ public class SolrConfig extends XmlConfigFile implements MapSerializable {
 
   public List<PluginInfo> readPluginInfos(String tag, boolean requireName, boolean requireClass) {
     ArrayList<PluginInfo> result = new ArrayList<>();
-    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);
+    List<ConfigNode> nodes = root.children(tag);
+    for (int i = 0; i < nodes.size(); i++) {
+      PluginInfo pluginInfo = new PluginInfo(nodes.get(i), "[solrconfig.xml] " + tag, requireName, requireClass);
       if (pluginInfo.isEnabled()) result.add(pluginInfo);
     }
     return result;
@@ -550,9 +595,9 @@ public class SolrConfig extends XmlConfigFile implements MapSerializable {
     /**
      * config xpath prefix for getting HTTP Caching options
      */
-    private final static String CACHE_PRE
+   /* private final static String CACHE_PRE
         = "requestDispatcher/httpCaching/";
-
+*/
     /**
      * For extracting Expires "ttl" from <cacheControl> config
      */
@@ -567,7 +612,7 @@ public class SolrConfig extends XmlConfigFile implements MapSerializable {
           "cacheControl", cacheControlHeader);
     }
 
-    public static enum LastModFrom {
+    public enum LastModFrom {
       OPENTIME, DIRLASTMOD, BOGUS;
 
       /**
@@ -588,18 +633,20 @@ public class SolrConfig extends XmlConfigFile implements MapSerializable {
     private final String cacheControlHeader;
     private final Long maxAge;
     private final LastModFrom lastModFrom;
+    private ConfigNode configNode;
 
     private HttpCachingConfig(SolrConfig conf) {
+      configNode = conf.root;
 
-      never304 = conf.getBool(CACHE_PRE + "@never304", false);
+      //"requestDispatcher/httpCaching/";
+      never304 = __("requestDispatcher").__("httpCaching").boolAttr("never304", false);
 
-      etagSeed = conf.get(CACHE_PRE + "@etagSeed", "Solr");
+      etagSeed = __("requestDispatcher").__("httpCaching").attr("etagSeed", "Solr");
 
 
-      lastModFrom = LastModFrom.parse(conf.get(CACHE_PRE + "@lastModFrom",
-          "openTime"));
+      lastModFrom = LastModFrom.parse(__("requestDispatcher").__("httpCaching").attr("lastModFrom","openTime"));
 
-      cacheControlHeader = conf.get(CACHE_PRE + "cacheControl", null);
+      cacheControlHeader = __("requestDispatcher").__("httpCaching").__("cacheControl").txt();
 
       Long tmp = null; // maxAge
       if (null != cacheControlHeader) {
@@ -617,6 +664,9 @@ public class SolrConfig extends XmlConfigFile implements MapSerializable {
       maxAge = tmp;
 
     }
+    private ConfigNode __(String name){
+      return configNode.__(name);
+    }
 
     public boolean isNever304() {
       return never304;
@@ -765,8 +815,8 @@ public class SolrConfig extends XmlConfigFile implements MapSerializable {
       }
     }
 
-    NodeList nodes = (NodeList) evaluate("lib", XPathConstants.NODESET);
-    if (nodes != null && nodes.getLength() > 0) {
+    List<ConfigNode> nodes = root.children("lib");
+    if (nodes != null && nodes.size() > 0) {
       if (!isConfigsetTrusted) {
         throw new SolrException(ErrorCode.UNAUTHORIZED,
           "The configset for this collection was uploaded without any authentication in place,"
@@ -774,14 +824,14 @@ public class SolrConfig extends XmlConfigFile implements MapSerializable {
             + " after enabling authentication and authorization.");
       }
 
-      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);
+      for (int i = 0; i < nodes.size(); i++) {
+        ConfigNode node = nodes.get(i);
+        String baseDir = node.attr("dir");
+        String path = node.attr(PATH);
         if (null != baseDir) {
           // :TODO: add support for a simpler 'glob' mutually exclusive of regex
           Path dir = instancePath.resolve(baseDir);
-          String regex = DOMUtil.getAttr(node, "regex");
+          String regex = node.attr("regex");
           try {
             if (regex == null)
               urls.addAll(SolrResourceLoader.getURLs(dir));
@@ -833,42 +883,28 @@ public class SolrConfig extends XmlConfigFile implements MapSerializable {
     return enableStreamBody;
   }
 
-  @Override
-  public int getInt(String path) {
-    return getInt(path, 0);
-  }
-
-  @Override
-  public int getInt(String path, int def) {
-    Object val = overlay.getXPathProperty(path);
-    if (val != null) return Integer.parseInt(val.toString());
-    return super.getInt(path, def);
-  }
-
-  @Override
-  public boolean getBool(String path, boolean def) {
-    Object val = overlay.getXPathProperty(path);
-    if (val != null) return Boolean.parseBoolean(val.toString());
-    return super.getBool(path, def);
+  /*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);
   }
 
-  @Override
   public String get(String path) {
-    Object val = overlay.getXPathProperty(path, true);
-    return val != null ? val.toString() : super.get(path);
+    Object val = _getVal(path);
+    return val != null ? val.toString() :null;
   }
 
-  @Override
   public String get(String path, String def) {
-    Object val = overlay.getXPathProperty(path, true);
-    return val != null ? val.toString() : super.get(path, def);
+    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 (getZnodeVersion() > -1) result.put(ZNODEVER, 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();
@@ -929,11 +965,10 @@ public class SolrConfig extends XmlConfigFile implements MapSerializable {
 
   }
 
-  @Override
   public Properties getSubstituteProperties() {
     Map<String, Object> p = getOverlay().getUserProps();
-    if (p == null || p.isEmpty()) return super.getSubstituteProperties();
-    Properties result = new Properties(super.getSubstituteProperties());
+    if (p == null || p.isEmpty()) return substituteProperties;
+    Properties result = new Properties(substituteProperties);
     result.putAll(p);
     return result;
   }
@@ -942,7 +977,7 @@ public class SolrConfig extends XmlConfigFile implements MapSerializable {
 
   public ConfigOverlay getOverlay() {
     if (overlay == null) {
-      overlay = getConfigOverlay(getResourceLoader());
+      overlay = getConfigOverlay(resourceLoader);
     }
     return overlay;
   }
@@ -969,13 +1004,42 @@ public class SolrConfig extends XmlConfigFile implements MapSerializable {
     if (o == null || PackageLoader.LATEST.equals(o)) return null;
     return o.toString();
   }
-
   public RequestParams refreshRequestParams() {
-    requestParams = RequestParams.getFreshRequestParams(getResourceLoader(), requestParams);
+    requestParams = RequestParams.getFreshRequestParams(resourceLoader, requestParams);
     if (log.isDebugEnabled()) {
       log.debug("current version of requestparams : {}", requestParams.getZnodeVersion());
     }
     return requestParams;
   }
 
+  public SolrResourceLoader getResourceLoader() {
+    return resourceLoader;
+  }
+
+  public int getZnodeVersion() {
+    return znodeVersion;
+  }
+
+  public String getName() {
+    return resourceName;
+  }
+
+  public String 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);
+  }
+
+  public ConfigNode __(String name, Predicate<ConfigNode> test) {
+    return root.__(name, test);
+  }
+
+
 }
diff --git a/solr/core/src/java/org/apache/solr/schema/IndexSchema.java b/solr/core/src/java/org/apache/solr/schema/IndexSchema.java
index 030aa9e..918b878 100644
--- a/solr/core/src/java/org/apache/solr/schema/IndexSchema.java
+++ b/solr/core/src/java/org/apache/solr/schema/IndexSchema.java
@@ -175,7 +175,9 @@ public class IndexSchema {
     this(luceneVersion, resourceLoader, substitutableProperties);
 
     this.resourceName = Objects.requireNonNull(name);
-    ConfigNode.SUBSTITUTES.set(substitutableProperties::getProperty);
+    ConfigNode.SUBSTITUTES.set(key -> substitutableProperties == null ?
+        null :
+        substitutableProperties.getProperty(key));
     try {
       readSchema(schemaResource);
       loader.inform(loader);
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 c49cb12..da7e046 100644
--- a/solr/core/src/java/org/apache/solr/schema/IndexSchemaFactory.java
+++ b/solr/core/src/java/org/apache/solr/schema/IndexSchemaFactory.java
@@ -117,8 +117,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 9b1c297..b2843d4 100644
--- a/solr/core/src/java/org/apache/solr/search/CacheConfig.java
+++ b/solr/core/src/java/org/apache/solr/search/CacheConfig.java
@@ -16,7 +16,6 @@
  */
 package org.apache.solr.search;
 
-import javax.xml.xpath.XPathConstants;
 import java.lang.invoke.MethodHandles;
 import java.util.Collections;
 import java.util.HashMap;
@@ -25,19 +24,15 @@ import java.util.List;
 import java.util.Map;
 import java.util.function.Supplier;
 
+import org.apache.solr.common.ConfigNode;
+import org.apache.solr.common.MapSerializable;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.util.StrUtils;
-import org.apache.solr.common.MapSerializable;
-
 import org.apache.solr.core.PluginInfo;
-
 import org.apache.solr.core.SolrConfig;
 import org.apache.solr.core.SolrResourceLoader;
-import org.apache.solr.common.util.DOMUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
 
 import static org.apache.solr.common.params.CommonParams.NAME;
 
@@ -45,10 +40,8 @@ import static org.apache.solr.common.params.CommonParams.NAME;
  * Contains the knowledge of how cache config is
  * stored in the solrconfig.xml file, and implements a
  * factory to create caches.
- *
- *
  */
-public class CacheConfig implements MapSerializable{
+public class CacheConfig implements MapSerializable {
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
   private String nodeName;
@@ -70,7 +63,8 @@ public class CacheConfig implements MapSerializable{
 
   private String regenImpl;
 
-  public CacheConfig() {}
+  public CacheConfig() {
+  }
 
   @SuppressWarnings({"rawtypes"})
   public CacheConfig(Class<? extends SolrCache> clazz, Map<String,String> args, CacheRegenerator regenerator) {
@@ -88,15 +82,13 @@ public class CacheConfig implements MapSerializable{
     this.regenerator = regenerator;
   }
 
-  public static Map<String, CacheConfig> getMultipleConfigs(SolrConfig solrConfig, String configPath) {
-    NodeList nodes = (NodeList) solrConfig.evaluate(configPath, XPathConstants.NODESET);
-    if (nodes == null || nodes.getLength() == 0) return new LinkedHashMap<>();
-    Map<String, CacheConfig> result = new HashMap<>(nodes.getLength());
-    for (int i = 0; i < nodes.getLength(); i++) {
-      Node node = nodes.item(i);
-      if ("true".equals(DOMUtil.getAttrOrDefault(node, "enabled", "true"))) {
-        CacheConfig config = getConfig(solrConfig, node.getNodeName(),
-            DOMUtil.toMap(node.getAttributes()), configPath);
+  public static Map<String, CacheConfig> getMultipleConfigs(SolrConfig solrConfig, String configPath, List<ConfigNode> nodes) {
+    if (nodes == null || nodes.size() == 0) return new LinkedHashMap<>();
+    Map<String, CacheConfig> result = new HashMap<>(nodes.size());
+    for (int i = 0; i < nodes.size(); i++) {
+      ConfigNode node = nodes.get(i);
+      if (node.boolAttr("enabled", true)) {
+        CacheConfig config = getConfig(solrConfig, node.name(), node.attributes().asMap(), configPath);
         result.put(config.args.get(NAME), config);
       }
     }
@@ -105,20 +97,20 @@ public class CacheConfig implements MapSerializable{
 
 
   @SuppressWarnings({"unchecked"})
-  public static CacheConfig getConfig(SolrConfig solrConfig, String xpath) {
-    Node node = solrConfig.getNode(xpath, false);
-    if(node == null || !"true".equals(DOMUtil.getAttrOrDefault(node, "enabled", "true"))) {
+  public static CacheConfig getConfig(SolrConfig solrConfig, ConfigNode node, String xpath) {
+//    Node node = solrConfig.getNode(xpath, false);
+    if (!node.exists() || !"true".equals(node.attributes().get("enabled", "true"))) {
       Map<String, String> m = solrConfig.getOverlay().getEditableSubProperties(xpath);
-      if(m==null) return null;
+      if (m == null) return null;
       List<String> parts = StrUtils.splitSmart(xpath, '/');
-      return getConfig(solrConfig,parts.get(parts.size()-1) , Collections.EMPTY_MAP,xpath);
+      return getConfig(solrConfig, parts.get(parts.size() - 1), Collections.EMPTY_MAP, xpath);
     }
-    return getConfig(solrConfig, node.getNodeName(),DOMUtil.toMap(node.getAttributes()), xpath);
+    return getConfig(solrConfig, node.name(), node.attributes().asMap(), xpath);
   }
 
 
   @SuppressWarnings({"unchecked"})
-  public static CacheConfig getConfig(SolrConfig solrConfig, String nodeName, Map<String,String> attrs, String xpath) {
+  public static CacheConfig getConfig(SolrConfig solrConfig, String nodeName, Map<String, String> attrs, String xpath) {
     CacheConfig config = new CacheConfig();
     config.nodeName = nodeName;
     @SuppressWarnings({"rawtypes"})
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 a364757..e70fb9b 100644
--- a/solr/core/src/java/org/apache/solr/update/SolrIndexConfig.java
+++ b/solr/core/src/java/org/apache/solr/update/SolrIndexConfig.java
@@ -19,8 +19,8 @@ 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 java.util.function.Function;
 
 import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.analysis.DelegatingAnalyzerWrapper;
@@ -31,6 +31,7 @@ import org.apache.lucene.index.MergePolicy;
 import org.apache.lucene.index.MergeScheduler;
 import org.apache.lucene.search.Sort;
 import org.apache.lucene.util.InfoStream;
+import org.apache.solr.common.ConfigNode;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.Utils;
 import org.apache.solr.core.DirectoryFactory;
@@ -91,11 +92,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;
@@ -109,88 +111,78 @@ 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); }
+  public SolrIndexConfig(SolrConfig cfg, SolrIndexConfig def)  {
+    this(cfg.__("indexConfig"), def);
+  }
   /**
    * 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);
+//    Object unused =  solrConfig.getNode(prefix, false);
 
     // 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.getNode(prefix + "/mergeScheduler", false) != null) && (solrConfig.get(prefix + "/mergeScheduler/@class", null) == null)),
+        __("mergeScheduler").isNull() || __("mergeScheduler").attr("class") != null,
         true);
     assertWarnOrFail("Beginning with Solr 7.0, <mergePolicy>myclass</mergePolicy> is no longer supported, use <mergePolicyFactory> instead.",
-        !((solrConfig.getNode(prefix + "/mergePolicy", false) != null) && (solrConfig.get(prefix + "/mergePolicy/@class", null) == null)),
+        __("mergePolicy").isNull() || __("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").isNull(),
         true);
 
-    useCompoundFile = solrConfig.getBool(prefix+"/useCompoundFile", def.useCompoundFile);
-    maxBufferedDocs = solrConfig.getInt(prefix+"/maxBufferedDocs", def.maxBufferedDocs);
-    ramBufferSizeMB = solrConfig.getDouble(prefix+"/ramBufferSizeMB", def.ramBufferSizeMB);
-    maxCommitMergeWaitMillis = solrConfig.getInt(prefix+"/maxCommitMergeWaitTime", 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.getInt(prefix+"/ramPerThreadHardLimitMB", def.ramPerThreadHardLimitMB);
+    ramPerThreadHardLimitMB = __("ramPerThreadHardLimitMB")._int(def.ramPerThreadHardLimitMB);
 
-    writeLockTimeout=solrConfig.getInt(prefix+"/writeLockTimeout", 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").isNull(),
         true);
     assertWarnOrFail("Beginning with Solr 7.0, <maxMergeDocs> is no longer supported, configure it on the relevant <mergePolicyFactory> instead.",
-        solrConfig.getInt(prefix+"/maxMergeDocs", 0) == 0,
+        __("maxMergeDocs").isNull(),
         true);
     assertWarnOrFail("Beginning with Solr 7.0, <mergeFactor> is no longer supported, configure it on the relevant <mergePolicyFactory> instead.",
-        solrConfig.getInt(prefix+"/mergeFactor", 0) == 0,
+        __("maxMergeFactor").isNull(),
         true);
 
-    String val = solrConfig.get(prefix + "/termIndexInterval", null);
-    if (val != null) {
+    if (__("termIndexInterval").exists()) {
       throw new IllegalArgumentException("Illegal parameter 'termIndexInterval'");
     }
 
-    boolean infoStreamEnabled = solrConfig.getBool(prefix + "/infoStream", false);
-    if(infoStreamEnabled) {
-      String infoStreamFile = solrConfig.get(prefix + "/infoStream/@file", null);
-      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");
       }
     }
-    mergedSegmentWarmerInfo = getPluginInfo(prefix + "/mergedSegmentWarmer", solrConfig, def.mergedSegmentWarmerInfo);
+    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.getNode(prefix + "/checkIntegrityAtMerge", false)),
+        __( "checkIntegrityAtMerge").isNull(),
         true);
   }
 
@@ -215,9 +207,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 d704e4b..f059c2d 100644
--- a/solr/core/src/java/org/apache/solr/util/DataConfigNode.java
+++ b/solr/core/src/java/org/apache/solr/util/DataConfigNode.java
@@ -60,9 +60,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/TestCodecSupport.java b/solr/core/src/test/org/apache/solr/core/TestCodecSupport.java
index c021615..74227c7 100644
--- a/solr/core/src/test/org/apache/solr/core/TestCodecSupport.java
+++ b/solr/core/src/test/org/apache/solr/core/TestCodecSupport.java
@@ -196,9 +196,9 @@ public class TestCodecSupport extends SolrTestCaseJ4 {
     SolrCore c = null;
     
     SolrConfig config = TestHarness.createConfig(testSolrHome, previousCoreName, "solrconfig_codec2.xml");
-    assertEquals("Unexpected codec factory for this test.", "solr.SchemaCodecFactory", config.get("codecFactory/@class"));
-    assertNull("Unexpected configuration of codec factory for this test. Expecting empty element", 
-        config.getNode("codecFactory", false).getFirstChild());
+    assertEquals("Unexpected codec factory for this test.", "solr.SchemaCodecFactory", config.__("codecFactory").attr("class"));
+    assertTrue("Unexpected configuration of codec factory for this test. Expecting empty element",
+        config.__("codecFactory").children(null, (String)null).isEmpty());
     IndexSchema schema = IndexSchemaFactory.buildIndexSchema("schema_codec.xml", config);
 
     CoreContainer coreContainer = h.getCoreContainer();
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 ccf3114..00c01a1 100644
--- a/solr/core/src/test/org/apache/solr/core/TestConfig.java
+++ b/solr/core/src/test/org/apache/solr/core/TestConfig.java
@@ -16,17 +16,18 @@
  */
 package org.apache.solr.core;
 
-import javax.xml.xpath.XPathConstants;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.LinkedHashMap;
 import java.util.Collections;
+import java.util.List;
 
 import org.apache.lucene.index.ConcurrentMergeScheduler;
 import org.apache.lucene.index.IndexWriterConfig;
 import org.apache.lucene.index.TieredMergePolicy;
 import org.apache.lucene.util.InfoStream;
 import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.common.ConfigNode;
 import org.apache.solr.handler.admin.ShowFileRequestHandler;
 import org.apache.solr.schema.IndexSchema;
 import org.apache.solr.schema.IndexSchemaFactory;
@@ -35,8 +36,6 @@ import org.apache.solr.update.SolrIndexConfig;
 import org.junit.Assert;
 import org.junit.BeforeClass;
 import org.junit.Test;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
 
 public class TestConfig extends SolrTestCaseJ4 {
 
@@ -80,24 +79,24 @@ public class TestConfig extends SolrTestCaseJ4 {
   public void testJavaProperty() {
     // property values defined in build.xml
 
-    String s = solrConfig.get("propTest");
+    String s = solrConfig.__("propTest").textValue();
     assertEquals("prefix-proptwo-suffix", s);
 
-    s = solrConfig.get("propTest/@attr1", "default");
+    s = solrConfig.__("propTest").attr("attr1", "default");
     assertEquals("propone-${literal}", s);
 
-    s = solrConfig.get("propTest/@attr2", "default");
+    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);
 
-    NodeList nl = (NodeList) solrConfig.evaluate("propTest", XPathConstants.NODESET);
-    assertEquals(1, nl.getLength());
-    assertEquals("prefix-proptwo-suffix", nl.item(0).getTextContent());
+    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());
 
-    Node node = solrConfig.getNode("propTest", true);
-    assertEquals("prefix-proptwo-suffix", node.getTextContent());
+    assertEquals("prefix-proptwo-suffix", solrConfig.__("propTest"));
   }
 
   // sometime if the config referes to old things, it must be replaced with new stuff
diff --git a/solr/core/src/test/org/apache/solr/core/TestSimpleTextCodec.java b/solr/core/src/test/org/apache/solr/core/TestSimpleTextCodec.java
index 9f6776b..1607ceb 100644
--- a/solr/core/src/test/org/apache/solr/core/TestSimpleTextCodec.java
+++ b/solr/core/src/test/org/apache/solr/core/TestSimpleTextCodec.java
@@ -33,7 +33,7 @@ public class TestSimpleTextCodec extends SolrTestCaseJ4 {
 
   public void test() throws Exception {
     SolrConfig config = h.getCore().getSolrConfig();
-    String codecFactory =  config.get("codecFactory/@class");
+    String codecFactory =  config.__("codecFactory").attr("class");
     assertEquals("Unexpected solrconfig codec factory", "solr.SimpleTextCodecFactory", codecFactory);
 
     assertEquals("Unexpected core codec", "SimpleText", h.getCore().getCodec().getName());
diff --git a/solr/core/src/test/org/apache/solr/handler/component/SuggestComponentTest.java b/solr/core/src/test/org/apache/solr/handler/component/SuggestComponentTest.java
index b15e167..f144bb1 100644
--- a/solr/core/src/test/org/apache/solr/handler/component/SuggestComponentTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/component/SuggestComponentTest.java
@@ -16,7 +16,10 @@
  */
 package org.apache.solr.handler.component;
 
+import java.util.function.Predicate;
+
 import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.common.ConfigNode;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.core.CoreDescriptor;
@@ -25,6 +28,8 @@ import org.apache.solr.spelling.suggest.SuggesterParams;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
+import static org.apache.solr.common.ConfigNode.Helpers.at;
+
 
 public class SuggestComponentTest extends SolrTestCaseJ4 {
 
@@ -204,14 +209,25 @@ public class SuggestComponentTest extends SolrTestCaseJ4 {
     final String suggester = "suggest_doc_default_startup_no_store";
     
     // validate that this suggester is not storing the lookup
-    assertEquals(suggester, 
-        h.getCore().getSolrConfig().getVal("//searchComponent[@name='suggest']/lst[8]/str[@name='name']", false));
-    assertNull(h.getCore().getSolrConfig().getVal("//searchComponent[@name='suggest']/lst[8]/str[@name='storeDir']", false));
+    assertEquals(suggester,
+        h.getCore().getSolrConfig().__("searchComponent", n -> "suggest".equals(n.attr("name")))
+            .__("lst",8).
+            __("str", n -> "name".equals(n.attr("name"))).txt());
+
+    assertNull( h.getCore().getSolrConfig().__("searchComponent", n -> "suggest".equals(n.attr("name")))
+        .__("lst",8).
+            __("str", n -> "storeDir".equals(n.attr("name"))).txt());
     
     // validate that this suggester only builds manually and has not buildOnStartup parameter
-    assertEquals("false", 
-        h.getCore().getSolrConfig().getVal("//searchComponent[@name='suggest']/lst[8]/str[@name='buildOnCommit']", true));
-    assertNull(h.getCore().getSolrConfig().getVal("//searchComponent[@name='suggest']/lst[8]/str[@name='buildOnStartup']", false));
+
+    assertEquals("false",
+        h.getCore().getSolrConfig().__("searchComponent", n -> "suggest".equals(n.attr("name")))
+            .__("lst",8).
+            __("str", n -> "buildOnCommit".equals(n.attr("name"))).txt());
+
+    assertNull(h.getCore().getSolrConfig().__("searchComponent", n -> "suggest".equals(n.attr("name")))
+        .__("lst",8).
+            __("str", n -> "buildOnStartup".equals(n.attr("name"))).txt());
     
     reloadCore(random().nextBoolean());
     
@@ -254,15 +270,27 @@ public class SuggestComponentTest extends SolrTestCaseJ4 {
     final String suggester = "suggest_doc_default_startup";
     
     // validate that this suggester is storing the lookup
-    assertEquals(suggester, 
-        h.getCore().getSolrConfig().getVal("//searchComponent[@name='suggest']/lst[7]/str[@name='name']", false));
-    assertEquals(suggester, 
-        h.getCore().getSolrConfig().getVal("//searchComponent[@name='suggest']/lst[7]/str[@name='storeDir']", false));
+
+    assertEquals(suggester,
+        h.getCore().getSolrConfig().__("searchComponent", n -> "suggest".equals(n.attr("name")))
+            .__("lst",7).
+            __("str", n -> "name".equals(n.attr("name"))).txt());
+
+    assertEquals(suggester,
+        h.getCore().getSolrConfig().__("searchComponent", n -> "suggest".equals(n.attr("name")))
+            .__("lst",7).
+            __("str", n -> "storeDir".equals(n.attr("name"))).txt());
     
     // validate that this suggester only builds manually and has not buildOnStartup parameter
-    assertEquals("false", 
-        h.getCore().getSolrConfig().getVal("//searchComponent[@name='suggest']/lst[7]/str[@name='buildOnCommit']", true));
-    assertNull(h.getCore().getSolrConfig().getVal("//searchComponent[@name='suggest']/lst[7]/str[@name='buildOnStartup']", false));
+
+    assertEquals("false",
+        h.getCore().getSolrConfig().__("searchComponent", n -> "suggest".equals(n.attr("name")))
+            .__("lst",7).
+            __("str", n -> "buildOnCommit".equals(n.attr("name"))).txt());
+
+    assertNull( h.getCore().getSolrConfig().__("searchComponent", n -> "suggest".equals(n.attr("name")))
+        .__("lst",7).
+            __("str", n -> "buildOnStartup".equals(n.attr("name"))).txt());
     
     assertQ(req("qt", rh, 
         SuggesterParams.SUGGEST_DICT, suggester,
@@ -339,16 +367,27 @@ public class SuggestComponentTest extends SolrTestCaseJ4 {
     final String suggester = "suggest_fuzzy_doc_manal_build";
     
     // validate that this suggester is storing the lookup
-    assertEquals(suggester, 
-        h.getCore().getSolrConfig().getVal("//searchComponent[@name='suggest']/lst[6]/str[@name='name']", false));
-    assertEquals(suggester, 
-        h.getCore().getSolrConfig().getVal("//searchComponent[@name='suggest']/lst[6]/str[@name='storeDir']", false));
+
+    assertEquals(suggester,
+        h.getCore().getSolrConfig().__("searchComponent", n -> "suggest".equals(n.attr("name")))
+            .__("lst",6).
+            __("str", n -> "name".equals(n.attr("name"))).txt());
+
+    assertEquals(suggester,
+        h.getCore().getSolrConfig().__("searchComponent", n -> "suggest".equals(n.attr("name")))
+            .__("lst",6).
+            __("str", n -> "storeDir".equals(n.attr("name"))).txt());
     
     // validate that this suggester only builds manually
-    assertEquals("false", 
-        h.getCore().getSolrConfig().getVal("//searchComponent[@name='suggest']/lst[6]/str[@name='buildOnCommit']", true));
-    assertEquals("false", 
-        h.getCore().getSolrConfig().getVal("//searchComponent[@name='suggest']/lst[6]/str[@name='buildOnStartup']", true));
+
+    assertEquals("false",
+        h.getCore().getSolrConfig().__("searchComponent", n -> "suggest".equals(n.attr("name")))
+            .__("lst",6).
+            __("str", n -> "buildOnCommit".equals(n.attr("name"))).txt());
+    assertEquals("false",
+        h.getCore().getSolrConfig().__("searchComponent", n -> "suggest".equals(n.attr("name")))
+            .__("lst",6).
+            __("str", n -> "buildOnStartup".equals(n.attr("name"))).txt());
     
     // build the suggester manually
     assertQ(req("qt", rh, 
@@ -402,15 +441,25 @@ public class SuggestComponentTest extends SolrTestCaseJ4 {
         h.getCore().getSolrConfig().useColdSearcher);
     
     // validate that this suggester is not storing the lookup and buildOnStartup is not set
-    assertEquals(suggesterFuzzy, 
-        h.getCore().getSolrConfig().getVal("//searchComponent[@name='suggest']/lst[3]/str[@name='name']", false));
-    assertNull(h.getCore().getSolrConfig().getVal("//searchComponent[@name='suggest']/lst[3]/str[@name='storeDir']", false));
+    assertEquals(suggesterFuzzy,
+        h.getCore().getSolrConfig().__("searchComponent", n -> "suggest".equals(n.attr("name")))
+            .__("lst",3).
+            __("str", n -> "buildOnStartup".equals(n.attr("name"))).txt());
+
+    assertNull(  h.getCore().getSolrConfig().__("searchComponent", n -> "suggest".equals(n.attr("name")))
+        .__("lst",3).
+            __("str", n -> "storeDir".equals(n.attr("name"))).txt());
+
     
     // assert that buildOnStartup=false
-    assertEquals("false", 
-        h.getCore().getSolrConfig().getVal("//searchComponent[@name='suggest']/lst[3]/str[@name='buildOnStartup']", false));
-    assertEquals("true", 
-        h.getCore().getSolrConfig().getVal("//searchComponent[@name='suggest']/lst[3]/str[@name='buildOnCommit']", false));
+    assertEquals("false",
+        h.getCore().getSolrConfig().__("searchComponent", n -> "suggest".equals(n.attr("name")))
+            .__("lst",3).
+            __("str", n -> "buildOnStartup".equals(n.attr("name"))).txt());
+    assertEquals("true",
+        h.getCore().getSolrConfig().__("searchComponent", n -> "suggest".equals(n.attr("name")))
+            .__("lst",3).
+            __("str", n -> "buildOnCommit".equals(n.attr("name"))).txt());
     
     // verify that this suggester is built (there was a commit in setUp)
     assertQ(req("qt", rh, 
@@ -453,13 +502,21 @@ public class SuggestComponentTest extends SolrTestCaseJ4 {
     final String suggestStartup = "suggest_fuzzy_doc_dict_build_startup";
     
     // repeat the test with "suggest_fuzzy_doc_dict_build_startup", it is exactly the same but with buildOnStartup=true
-    assertEquals(suggestStartup, 
-        h.getCore().getSolrConfig().getVal("//searchComponent[@name='suggest']/lst[5]/str[@name='name']", false));
-    assertNull(h.getCore().getSolrConfig().getVal("//searchComponent[@name='suggest']/lst[5]/str[@name='storeDir']", false));
-    assertEquals("true", 
-        h.getCore().getSolrConfig().getVal("//searchComponent[@name='suggest']/lst[5]/str[@name='buildOnStartup']", false));
-    assertEquals("false", 
-        h.getCore().getSolrConfig().getVal("//searchComponent[@name='suggest']/lst[5]/str[@name='buildOnCommit']", false));
+    assertEquals(suggestStartup,
+        h.getCore().getSolrConfig().__("searchComponent", n -> "suggest".equals(n.attr("name")))
+            .__("lst",5).
+            __("str", n -> "name".equals(n.attr("name"))).txt());
+    assertNull( h.getCore().getSolrConfig().__("searchComponent", n -> "suggest".equals(n.attr("name")))
+        .__("lst",5).
+            __("str", n -> "storeDir".equals(n.attr("name"))).txt());
+    assertEquals("true",
+        h.getCore().getSolrConfig().__("searchComponent", n -> "suggest".equals(n.attr("name")))
+            .__("lst",5).
+            __("str", n -> "buildOnStartup".equals(n.attr("name"))).txt());
+    assertEquals("false",
+        h.getCore().getSolrConfig().__("searchComponent", n -> "suggest".equals(n.attr("name")))
+            .__("lst",5).
+            __("str", n -> "buildOnCommit".equals(n.attr("name"))).txt());
     
     // reload the core
     reloadCore(createNewCores);
diff --git a/solr/core/src/test/org/apache/solr/update/SolrIndexConfigTest.java b/solr/core/src/test/org/apache/solr/update/SolrIndexConfigTest.java
index 4f43b10..221dd07 100644
--- a/solr/core/src/test/org/apache/solr/update/SolrIndexConfigTest.java
+++ b/solr/core/src/test/org/apache/solr/update/SolrIndexConfigTest.java
@@ -70,7 +70,7 @@ public class SolrIndexConfigTest extends SolrTestCaseJ4 {
   @Test
   public void testFailingSolrIndexConfigCreation() throws Exception {
     SolrConfig solrConfig = new SolrConfig(instanceDir,"bad-mpf-solrconfig.xml");
-    SolrIndexConfig solrIndexConfig = new SolrIndexConfig(solrConfig, null, null);
+    SolrIndexConfig solrIndexConfig = new SolrIndexConfig(solrConfig, null);
     IndexSchema indexSchema = IndexSchemaFactory.buildIndexSchema(schemaFileName, solrConfig);
     h.getCore().setLatestSchema(indexSchema);
 
@@ -83,7 +83,7 @@ public class SolrIndexConfigTest extends SolrTestCaseJ4 {
   public void testTieredMPSolrIndexConfigCreation() throws Exception {
     String solrConfigFileName = solrConfigFileNameTieredMergePolicyFactory;
     SolrConfig solrConfig = new SolrConfig(instanceDir, solrConfigFileName);
-    SolrIndexConfig solrIndexConfig = new SolrIndexConfig(solrConfig, null, null);
+    SolrIndexConfig solrIndexConfig = new SolrIndexConfig(solrConfig, null);
     IndexSchema indexSchema = IndexSchemaFactory.buildIndexSchema(schemaFileName, solrConfig);
     
     h.getCore().setLatestSchema(indexSchema);
@@ -108,7 +108,7 @@ public class SolrIndexConfigTest extends SolrTestCaseJ4 {
   public void testConcurrentMergeSchedularSolrIndexConfigCreation() throws Exception {
     String solrConfigFileName = solrConfigFileNameConnMSPolicyFactory;
     SolrConfig solrConfig = new SolrConfig(instanceDir, solrConfigFileName);
-    SolrIndexConfig solrIndexConfig = new SolrIndexConfig(solrConfig, null, null);
+    SolrIndexConfig solrIndexConfig = new SolrIndexConfig(solrConfig, null);
     IndexSchema indexSchema = IndexSchemaFactory.buildIndexSchema(schemaFileName, solrConfig);
 
     h.getCore().setLatestSchema(indexSchema);
@@ -132,7 +132,7 @@ public class SolrIndexConfigTest extends SolrTestCaseJ4 {
     final boolean expectedFieldSortDescending = true;
 
     SolrConfig solrConfig = new SolrConfig(instanceDir, solrConfigFileNameSortingMergePolicyFactory);
-    SolrIndexConfig solrIndexConfig = new SolrIndexConfig(solrConfig, null, null);
+    SolrIndexConfig solrIndexConfig = new SolrIndexConfig(solrConfig, null);
     assertNotNull(solrIndexConfig);
     IndexSchema indexSchema = IndexSchemaFactory.buildIndexSchema(schemaFileName, solrConfig);
 
@@ -150,7 +150,7 @@ public class SolrIndexConfigTest extends SolrTestCaseJ4 {
 
   public void testMergedSegmentWarmerIndexConfigCreation() throws Exception {
     SolrConfig solrConfig = new SolrConfig(instanceDir, solrConfigFileNameWarmerRandomMergePolicyFactory);
-    SolrIndexConfig solrIndexConfig = new SolrIndexConfig(solrConfig, null, null);
+    SolrIndexConfig solrIndexConfig = new SolrIndexConfig(solrConfig, null);
     assertNotNull(solrIndexConfig);
     assertNotNull(solrIndexConfig.mergedSegmentWarmerInfo);
     assertEquals(SimpleMergedSegmentWarmer.class.getName(),
@@ -166,7 +166,7 @@ public class SolrIndexConfigTest extends SolrTestCaseJ4 {
     final String solrConfigFileNameTMP = solrConfigFileNameTieredMergePolicyFactory;
     final String solrConfigFileName = (random().nextBoolean() ? solrConfigFileNameWarmer : solrConfigFileNameTMP);
     SolrConfig solrConfig = new SolrConfig(instanceDir, solrConfigFileName);
-    SolrIndexConfig solrIndexConfig = new SolrIndexConfig(solrConfig, null, null);
+    SolrIndexConfig solrIndexConfig = new SolrIndexConfig(solrConfig, null);
     assertNotNull(solrIndexConfig);
     assertNotNull(solrIndexConfig.mergePolicyFactoryInfo);
     if (solrConfigFileName.equals(solrConfigFileNameWarmerRandomMergePolicyFactory)) {
diff --git a/solr/solrj/src/java/org/apache/solr/cluster/api/SimpleMap.java b/solr/solrj/src/java/org/apache/solr/cluster/api/SimpleMap.java
index 81da171..cd9a372 100644
--- a/solr/solrj/src/java/org/apache/solr/cluster/api/SimpleMap.java
+++ b/solr/solrj/src/java/org/apache/solr/cluster/api/SimpleMap.java
@@ -20,6 +20,8 @@ package org.apache.solr.cluster.api;
 import org.apache.solr.common.MapWriter;
 
 import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.Map;
 import java.util.function.BiConsumer;
 import java.util.function.BiFunction;
 import java.util.function.Consumer;
@@ -82,4 +84,10 @@ public interface SimpleMap<T> extends MapWriter {
   default void writeMap(EntryWriter ew) throws IOException {
     forEachEntry(ew::putNoEx);
   }
+
+  default Map<String, T> asMap() {
+    Map<String, T> result = new LinkedHashMap<>();
+    forEachEntry((k, v) -> result.put(k, v));
+    return result;
+  }
 }
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 91e289d..adbe914 100644
--- a/solr/solrj/src/java/org/apache/solr/common/ConfigNode.java
+++ b/solr/solrj/src/java/org/apache/solr/common/ConfigNode.java
@@ -24,8 +24,12 @@ import java.util.List;
 import java.util.Set;
 import java.util.function.Function;
 import java.util.function.Predicate;
+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
@@ -55,6 +59,55 @@ public interface ConfigNode {
     return child(null, name);
   }
 
+  /**
+   * 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 __(String name, int idx) {
+    List<ConfigNode> children = children(null, name);
+    if(idx < children.size()) return children.get(idx);
+    return EMPTY;
+
+  }
+
+  default ConfigNode child(List<String> path) {
+    ConfigNode node = this;
+    for (String s : path) {
+      node = node.child(s);
+      if (node == null) break;
+    }
+    return node;
+  }
+
+  default ConfigNode child(String name, Supplier<RuntimeException> err) {
+    ConfigNode n = child(name);
+    if(n == null) throw err.get();
+    return n;
+  }
+
+  default boolean _bool(boolean def) { return __bool(textValue(),def); }
+  default int _int(int def) { return __int(textValue(), def); }
+  default String attr(String name, String def) { return __txt(attributes().get(name), def);}
+  default String attr(String name) { return attributes().get(name);}
+  default String requiredStrAttr(String name, Supplier<RuntimeException> err) {
+    if(attributes().get(name) == null && err != null) throw err.get();
+    return attributes().get(name);
+  }
+  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); }
   /**Iterate through child nodes with the name and return the first child that matches
    */
   default ConfigNode child(Predicate<ConfigNode> test, String name) {
@@ -96,11 +149,78 @@ public interface ConfigNode {
     return children(null, Collections.singleton(name));
   }
 
+  default boolean exists() { return true; }
+  default boolean isNull() { return false; }
+
+  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
    */
   void forEachChild(Function<ConfigNode, Boolean> fun);
 
+  ConfigNode EMPTY = new ConfigNode() {
+    @Override
+    public String name() {
+      return null;
+    }
+
+    @Override
+    public String textValue() { return null; }
+
+    @Override
+    public SimpleMap<String> attributes() {
+      return empty_attrs;
+    }
+
+    @Override
+    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 boolean isNull() { return true; }
+
+    @Override
+    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());
+
+  public 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()); }
+    public static Predicate<ConfigNode> at(int i) {
+      return new Predicate<ConfigNode>() {
+        int index =0;
+        @Override
+        public boolean test(ConfigNode node) {
+          if(index == i) return true;
+          index++;
+          return false;
+        }
+      };
+    }
+
+
+  }
 
 }
diff --git a/solr/solrj/src/java/org/apache/solr/common/util/WrappedSimpleMap.java b/solr/solrj/src/java/org/apache/solr/common/util/WrappedSimpleMap.java
index e8f58a5..e8689d2 100644
--- a/solr/solrj/src/java/org/apache/solr/common/util/WrappedSimpleMap.java
+++ b/solr/solrj/src/java/org/apache/solr/common/util/WrappedSimpleMap.java
@@ -19,6 +19,7 @@ package org.apache.solr.common.util;
 
 import org.apache.solr.cluster.api.SimpleMap;
 
+import java.util.Collections;
 import java.util.Map;
 import java.util.function.BiConsumer;
 
@@ -46,4 +47,8 @@ public class WrappedSimpleMap<T>  implements SimpleMap<T> {
         this.delegate = delegate;
     }
 
+    @Override
+    public Map<String, T> asMap() {
+        return Collections.unmodifiableMap(delegate);
+    }
 }