You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by md...@apache.org on 2021/04/30 14:48:21 UTC

[solr] branch main updated: Revert "SOLR-15337: Avoid XPath in solrconfig.xml parsing (#104)"

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

mdrob pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/main by this push:
     new cf7278c  Revert "SOLR-15337: Avoid XPath in solrconfig.xml parsing (#104)"
cf7278c is described below

commit cf7278cf57ebeaa07c79d4933269750e6f6ce883
Author: Mike Drob <md...@apache.org>
AuthorDate: Fri Apr 30 09:46:03 2021 -0500

    Revert "SOLR-15337: Avoid XPath in solrconfig.xml parsing (#104)"
    
    This reverts commit ca031d23b55b3624df859bc5ce19ba5753358eec.
---
 solr/CHANGES.txt                                   |   2 -
 .../java/org/apache/solr/core/ConfigOverlay.java   |  25 +-
 .../org/apache/solr/core/ConfigSetService.java     |   7 -
 .../org/apache/solr/core/OverlaidConfigNode.java   | 108 -----
 .../src/java/org/apache/solr/core/PluginInfo.java  |  24 -
 .../src/java/org/apache/solr/core/SolrConfig.java  | 486 +++++++++------------
 .../java/org/apache/solr/core/XmlConfigFile.java   |  15 +-
 .../apache/solr/handler/DumpRequestHandler.java    |   1 -
 .../apache/solr/schema/FieldTypePluginLoader.java  |   6 +-
 .../java/org/apache/solr/schema/IndexSchema.java   |  18 +-
 .../org/apache/solr/schema/IndexSchemaFactory.java |   4 +-
 .../java/org/apache/solr/search/CacheConfig.java   |  42 +-
 .../org/apache/solr/update/SolrIndexConfig.java    |  82 ++--
 .../java/org/apache/solr/update/VersionInfo.java   |   4 +-
 .../java/org/apache/solr/util/DOMConfigNode.java   |  10 +-
 .../java/org/apache/solr/util/DataConfigNode.java  |  71 ++-
 .../resources/EditableSolrConfigAttributes.json    |   4 +
 .../test/org/apache/solr/core/TestBadConfig.java   |   5 -
 .../org/apache/solr/core/TestCodecSupport.java     |   6 +-
 .../org/apache/solr/core/TestConfLoadPerf.java     |  93 ----
 .../src/test/org/apache/solr/core/TestConfig.java  |  25 +-
 .../org/apache/solr/core/TestConfigOverlay.java    |   3 +
 .../org/apache/solr/core/TestSimpleTextCodec.java  |   2 +-
 .../handler/component/SuggestComponentTest.java    | 122 ++----
 .../apache/solr/update/SolrIndexConfigTest.java    |  12 +-
 .../org/apache/solr/cluster/api/SimpleMap.java     |  11 -
 .../java/org/apache/solr/common/ConfigNode.java    | 133 +-----
 .../java/org/apache/solr/common/util/DOMUtil.java  |  12 +-
 .../solr/common/util/LinkedSimpleHashMap.java      |   6 -
 .../apache/solr/common/util/WrappedSimpleMap.java  |  11 -
 30 files changed, 405 insertions(+), 945 deletions(-)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index dc38762..d73b82c 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -340,8 +340,6 @@ Improvements
 
 * SOLR-15155: Let CloudHttp2SolrClient accept an external Http2SolrClient Builder (Tomás Fernández Löbbe)
 
-* SOLR-15337: Avoid XPath in solrconfig.xml parsing (noble)
-
 Optimizations
 ---------------------
 * SOLR-15079: Block Collapse - Faster collapse code when groups are co-located via Block Join style nested doc indexing.
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 5ddbe5e..75793a2 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,12 @@
  */
 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 org.apache.solr.common.MapSerializable;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.CoreAdminParams;
@@ -62,12 +62,6 @@ 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"})
@@ -186,20 +180,11 @@ public class ConfigOverlay implements MapSerializable {
 
   @SuppressWarnings({"rawtypes"})
   public static Class checkEditable(String path, boolean isXpath, List<String> hierarchy) {
-    return isEditable(isXpath, hierarchy, StrUtils.splitSmart(path, isXpath ? '/' : '.'));
-  }
-
-  @SuppressWarnings("rawtypes")
-  private static Class isEditable(boolean isXpath, List<String> hierarchy, List<String> parts) {
+    List<String> parts = StrUtils.splitSmart(path, isXpath ? '/' : '.');
     Object obj = editable_prop_map;
     for (int i = 0; i < parts.size(); i++) {
       String part = parts.get(i);
-      boolean isAttr = false;
-      try {
-        isAttr = isXpath && part.startsWith("@");
-      } catch (RuntimeException e) {
-        throw e;
-      }
+      boolean isAttr = isXpath && part.startsWith("@");
       if (isAttr) {
         part = part.substring(1);
       }
@@ -262,10 +247,6 @@ public class ConfigOverlay implements MapSerializable {
     return Collections.unmodifiableMap(reqHandlers);
   }
 
-  boolean hasKey(String key) {
-    return props.containsKey(key);
-  }
-
 
   @SuppressWarnings({"unchecked", "rawtypes"})
   public ConfigOverlay addNamedPlugin(Map<String, Object> info, String typ) {
diff --git a/solr/core/src/java/org/apache/solr/core/ConfigSetService.java b/solr/core/src/java/org/apache/solr/core/ConfigSetService.java
index 0e6548d..f5d5c60 100644
--- a/solr/core/src/java/org/apache/solr/core/ConfigSetService.java
+++ b/solr/core/src/java/org/apache/solr/core/ConfigSetService.java
@@ -33,7 +33,6 @@ import com.github.benmanes.caffeine.cache.Caffeine;
 import org.apache.solr.cloud.ZkConfigSetService;
 import org.apache.solr.cloud.ZkController;
 import org.apache.solr.cloud.ZkSolrResourceLoader;
-import org.apache.solr.common.ConfigNode;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.StringUtils;
 import org.apache.solr.common.util.NamedList;
@@ -436,10 +435,4 @@ public abstract class ConfigSetService {
    */
   public abstract List<String> getAllConfigFiles(String configName) throws IOException;
 
-  public interface ConfigResource {
-
-    ConfigNode get() throws Exception;
-
-  }
-
 }
diff --git a/solr/core/src/java/org/apache/solr/core/OverlaidConfigNode.java b/solr/core/src/java/org/apache/solr/core/OverlaidConfigNode.java
deleted file mode 100644
index 39d8abc..0000000
--- a/solr/core/src/java/org/apache/solr/core/OverlaidConfigNode.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.core;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Function;
-import java.util.function.Predicate;
-
-import org.apache.solr.cluster.api.SimpleMap;
-import org.apache.solr.common.ConfigNode;
-
-/**A config node impl which has an overlay
- *
- */
-class OverlaidConfigNode implements ConfigNode {
-
-  private final ConfigOverlay overlay;
-  private final String _name;
-  private final ConfigNode delegate;
-  private final OverlaidConfigNode parent;
-
-  OverlaidConfigNode(ConfigOverlay overlay, String name, OverlaidConfigNode parent, ConfigNode delegate) {
-    this.overlay = overlay;
-    this._name = name;
-    this.delegate = delegate;
-    this.parent = parent;
-  }
-
-  private List<String> path(List<String> path) {
-    if(path== null) path = new ArrayList<>(5);
-    try {
-      if (parent != null) return parent.path(path);
-    } finally {
-      path.add(_name);
-    }
-    return path;
-  }
-
-  @Override
-  public ConfigNode get(String name) {
-    return wrap(delegate.get(name), name);
-  }
-
-  private ConfigNode wrap(ConfigNode n, String name) {
-    return new OverlaidConfigNode(overlay, name,this, n);
-  }
-
-  @Override
-  public ConfigNode get(String name, Predicate<ConfigNode> test) {
-    return wrap(delegate.get(name, test), name);
-  }
-
-  @Override
-  public String txt() {
-    return overlayText(delegate.txt(), null);
-  }
-
-  @Override
-  public ConfigNode get(String name, int idx) {
-    return wrap(delegate.get(name, idx), name);
-  }
-
-  @Override
-  public String name() {
-    return delegate.name();
-  }
-  @Override
-  public SimpleMap<String> attributes() {
-    return delegate.attributes();
-  }
-
-  @Override
-  public boolean exists() {
-    return delegate.exists();
-  }
-
-  @Override
-  public String attr(String name) {
-    return overlayText(delegate.attr(name),name);
-  }
-
-  private String overlayText(String def, String appendToPath) {
-    List<String> path = path(null);
-    if(appendToPath !=null) path.add(appendToPath);
-    Object val = overlay.getXPathProperty(path);
-    return val ==null? def: val.toString();
-  }
-
-  @Override
-  public void forEachChild(Function<ConfigNode, Boolean> fun) {
-    delegate.forEachChild(fun);
-  }
-}
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 b32bac7..9dff7d2 100644
--- a/solr/core/src/java/org/apache/solr/core/PluginInfo.java
+++ b/solr/core/src/java/org/apache/solr/core/PluginInfo.java
@@ -23,7 +23,6 @@ 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.DOMUtil;
 import org.apache.solr.common.util.NamedList;
@@ -100,18 +99,6 @@ 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);
@@ -156,17 +143,6 @@ 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 9231286..804ceab 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrConfig.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrConfig.java
@@ -17,6 +17,8 @@
 package org.apache.solr.core;
 
 
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPathConstants;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -34,30 +36,25 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
-import java.util.Objects;
 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.Function;
-import java.util.function.Predicate;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import com.google.common.collect.ImmutableList;
 import org.apache.commons.io.FileUtils;
 import org.apache.lucene.index.IndexDeletionPolicy;
-import org.apache.lucene.search.BooleanQuery;
 import org.apache.lucene.search.IndexSearcher;
 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;
+import org.apache.solr.common.util.DOMUtil;
 import org.apache.solr.common.util.IOUtils;
 import org.apache.solr.handler.component.SearchComponent;
 import org.apache.solr.pkg.PackageListeners;
@@ -80,11 +77,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.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;
 import static org.apache.solr.common.params.CommonParams.PATH;
@@ -97,7 +95,6 @@ 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;
 
 
 /**
@@ -105,17 +102,12 @@ import static org.apache.solr.core.XmlConfigFile.assertWarnOrFail;
  * configuration data for a Solr instance -- typically found in
  * "solrconfig.xml".
  */
-public class SolrConfig implements MapSerializable {
+public class SolrConfig extends XmlConfigFile 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 int znodeVersion;
-  ConfigNode root;
-  private final SolrResourceLoader resourceLoader;
-  private Properties substituteProperties;
 
   private RequestParams requestParams;
 
@@ -149,7 +141,7 @@ public class SolrConfig implements MapSerializable {
    * @param name        the configuration name used by the loader if the stream is null
    */
   public SolrConfig(Path instanceDir, String name)
-      throws IOException {
+      throws ParserConfigurationException, IOException, SAXException {
     this(new SolrResourceLoader(instanceDir), name, true, null);
   }
 
@@ -166,27 +158,6 @@ public class SolrConfig implements MapSerializable {
       throw new SolrException(ErrorCode.SERVER_ERROR, "Error loading solr config from " + resource, e);
     }
   }
-  private class ResourceProvider implements Function<String, InputStream> {
-    int zkVersion;
-    int hash = -1;
-    InputStream in;
-    String fileName;
-
-    ResourceProvider(InputStream in) {
-      this.in = in;
-      if (in instanceof ZkSolrResourceLoader.ZkByteArrayInputStream) {
-        ZkSolrResourceLoader.ZkByteArrayInputStream zkin = (ZkSolrResourceLoader.ZkByteArrayInputStream) in;
-        zkVersion = zkin.getStat().getVersion();
-        hash = Objects.hash(zkVersion, overlay.getZnodeVersion());
-        this.fileName = zkin.fileName;
-      }
-    }
-
-    @Override
-    public InputStream apply(String s) {
-      return in;
-    }
-  }
 
   /**
    * Creates a configuration instance from a resource loader, a configuration name and a stream.
@@ -197,180 +168,140 @@ public class SolrConfig implements MapSerializable {
    * @param isConfigsetTrusted  false if configset was uploaded using unsecured configset upload API, true otherwise
    * @param substitutableProperties optional properties to substitute into the XML
    */
-  @SuppressWarnings("unchecked")
   private SolrConfig(SolrResourceLoader loader, String name, boolean isConfigsetTrusted, Properties substitutableProperties)
-      throws IOException {
-    this.resourceLoader = loader;
-    this.resourceName = name;
-    this.substituteProperties = 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);
     getOverlay();//just in case it is not initialized
-    // insist we have non-null substituteProperties; it might get overlaid
-    Map<String, IndexSchemaFactory.VersionedConfig> configCache =null;
-    if (loader.getCoreContainer() != null && loader.getCoreContainer().getObjectCache() != null) {
-      configCache = (Map<String, IndexSchemaFactory.VersionedConfig>) loader.getCoreContainer().getObjectCache()
-          .computeIfAbsent(ConfigSetService.ConfigResource.class.getName(), s -> new ConcurrentHashMap<>());
-      ResourceProvider rp = new ResourceProvider(loader.openResource(name));
-      IndexSchemaFactory.VersionedConfig cfg = rp.fileName == null ? null : configCache.get(rp.fileName);
-      if (cfg != null) {
-        if (rp.hash != -1) {
-          if (rp.hash == cfg.version) {
-            log.debug("LOADED_FROM_CACHE");
-            root = cfg.data;
-          } else {
-            readXml(loader, name, configCache, rp);
-          }
-        }
-      }
-    }
-    if(root == null) {
-      readXml(loader, name, configCache,new ResourceProvider(loader.openResource(name)) );
-    }
-    ConfigNode.SUBSTITUTES.set(key -> {
-      if (substitutableProperties != null && substitutableProperties.containsKey(key)) {
-        return substitutableProperties.getProperty(key);
-      } else {
-        Object o = overlay.getUserProps().get(key);
-        return o == null ? null : o.toString();
-      }
-    });
-    try {
-      getRequestParams();
-      initLibs(loader, isConfigsetTrusted);
-      String val = root.child(IndexSchema.LUCENE_MATCH_VERSION_PARAM,
-          () -> new RuntimeException("Missing: " + IndexSchema.LUCENE_MATCH_VERSION_PARAM)).txt();
-
-      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 = get("indexDefaults").exists() || get("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.", get(indexConfigPrefix).get("nrtMode").isNull(),
-          true
-      );
-      assertWarnOrFail("Solr no longer supports forceful unlocking via the 'unlockOnStartup' option.  " +
-              "This is no longer necessary for the default lockType except in situations where " +
-              "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.",
-          !get(indexConfigPrefix).get("unlockOnStartup").exists(),
-          true // 'fail' in trunk
-      );
-
-      // Parse indexConfig section, using mainIndex as backup in case old config is used
-      indexConfig = new SolrIndexConfig(get("indexConfig"), null);
-
-      booleanQueryMaxClauseCount = get("query").get("maxBooleanClauses").intVal(BooleanQuery.getMaxClauseCount());
-      if (IndexSearcher.getMaxClauseCount() < booleanQueryMaxClauseCount) {
-        log.warn("solrconfig.xml: <maxBooleanClauses> of {} is greater than global limit of {} and will have no effect {}"
-            , booleanQueryMaxClauseCount, BooleanQuery.getMaxClauseCount()
-            , "set 'maxBooleanClauses' in solr.xml to increase global limit");
-      }
-
-      // Warn about deprecated / discontinued parameters
-      // boolToFilterOptimizer has had no effect since 3.1
-      if (get("query").get("boolTofilterOptimizer").exists())
-        log.warn("solrconfig.xml: <boolTofilterOptimizer> is currently not implemented and has no effect.");
-      if (get("query").get("HashDocSet").exists())
-        log.warn("solrconfig.xml: <HashDocSet> is deprecated and no longer used.");
+    getRequestParams();
+    initLibs(loader, isConfigsetTrusted);
+    luceneMatchVersion = SolrConfig.parseLuceneVersionString(getVal(IndexSchema.LUCENE_MATCH_VERSION_PARAM, true));
+    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);
+    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,
+        true
+    );
+    assertWarnOrFail("Solr no longer supports forceful unlocking via the 'unlockOnStartup' option.  "+
+                     "This is no longer necessary for the default lockType except in situations where "+
+                     "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),
+                     true // 'fail' in trunk
+                     );
+                     
+    // Parse indexConfig section, using mainIndex as backup in case old config is used
+    indexConfig = new SolrIndexConfig(this, "indexConfig", null);
+
+    booleanQueryMaxClauseCount = getInt("query/maxBooleanClauses", IndexSearcher.getMaxClauseCount());
+    if (IndexSearcher.getMaxClauseCount() < booleanQueryMaxClauseCount) {
+      log.warn("solrconfig.xml: <maxBooleanClauses> of {} is greater than global limit of {} {}"
+          , booleanQueryMaxClauseCount, IndexSearcher.getMaxClauseCount()
+          , "and will have no effect set 'maxBooleanClauses' in solr.xml to increase global limit");
+    }
+    
+    // Warn about deprecated / discontinued parameters
+    // boolToFilterOptimizer has had no effect since 3.1
+    if (get("query/boolTofilterOptimizer", null) != null)
+      log.warn("solrconfig.xml: <boolTofilterOptimizer> is currently not implemented and has no effect.");
+    if (get("query/HashDocSet", null) != null)
+      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()
 //    filtOptEnabled = getBool("query/boolTofilterOptimizer/@enabled", false);
 //    filtOptCacheSize = getInt("query/boolTofilterOptimizer/@cacheSize",32);
 //    filtOptThreshold = getFloat("query/boolTofilterOptimizer/@threshold",.05f);
 
-      useFilterForSortedQuery = get("query").get("useFilterForSortedQuery").boolVal(false);
-      queryResultWindowSize = Math.max(1, get("query").get("queryResultWindowSize").intVal(1));
-      queryResultMaxDocsCached = get("query").get("queryResultMaxDocsCached").intVal(Integer.MAX_VALUE);
-      enableLazyFieldLoading = get("query").get("enableLazyFieldLoading").boolVal(false);
-
-      filterCacheConfig = CacheConfig.getConfig(this, get("query").get("filterCache"), "query/filterCache");
-      queryResultCacheConfig = CacheConfig.getConfig(this, get("query").get("queryResultCache"), "query/queryResultCache");
-      documentCacheConfig = CacheConfig.getConfig(this, get("query").get("documentCache"), "query/documentCache");
-      CacheConfig conf = CacheConfig.getConfig(this, get("query").get("fieldValueCache"), "query/fieldValueCache");
-      if (conf == null) {
-        Map<String, String> args = new HashMap<>();
-        args.put(NAME, "fieldValueCache");
-        args.put("size", "10000");
-        args.put("initialSize", "10");
-        args.put("showItems", "-1");
-        conf = new CacheConfig(CaffeineCache.class, args, null);
-      }
-      fieldValueCacheConfig = conf;
-      useColdSearcher = get("query").get("useColdSearcher").boolVal(false);
-      dataDir = get("dataDir").txt();
-      if (dataDir != null && dataDir.length() == 0) dataDir = null;
-
-
-      org.apache.solr.search.SolrIndexSearcher.initRegenerators(this);
-
-      if (get("jmx").exists()) {
-        log.warn("solrconfig.xml: <jmx> is no longer supported, use solr.xml:/metrics/reporter section instead");
+    useFilterForSortedQuery = getBool("query/useFilterForSortedQuery", false);
+    queryResultWindowSize = Math.max(1, getInt("query/queryResultWindowSize", 1));
+    queryResultMaxDocsCached = getInt("query/queryResultMaxDocsCached", Integer.MAX_VALUE);
+    enableLazyFieldLoading = getBool("query/enableLazyFieldLoading", false);
+    
+    filterCacheConfig = CacheConfig.getConfig(this, "query/filterCache");
+    queryResultCacheConfig = CacheConfig.getConfig(this, "query/queryResultCache");
+    documentCacheConfig = CacheConfig.getConfig(this, "query/documentCache");
+    CacheConfig conf = CacheConfig.getConfig(this, "query/fieldValueCache");
+    if (conf == null) {
+      Map<String, String> args = new HashMap<>();
+      args.put(NAME, "fieldValueCache");
+      args.put("size", "10000");
+      args.put("initialSize", "10");
+      args.put("showItems", "-1");
+      conf = new CacheConfig(CaffeineCache.class, args, null);
+    }
+    fieldValueCacheConfig = conf;
+    useColdSearcher = getBool("query/useColdSearcher", false);
+    dataDir = get("dataDir", null);
+    if (dataDir != null && dataDir.length() == 0) dataDir = null;
+
+
+    org.apache.solr.search.SolrIndexSearcher.initRegenerators(this);
+
+    if (get("jmx", null) != null) {
+      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);
+    for (SolrPluginInfo plugin : plugins) loadPluginInfo(plugin);
+
+    Map<String, CacheConfig> userCacheConfigs = CacheConfig.getMultipleConfigs(this, "query/cache");
+    List<PluginInfo> caches = getPluginInfos(SolrCache.class.getName());
+    if (!caches.isEmpty()) {
+      for (PluginInfo c : caches) {
+        userCacheConfigs.put(c.name, CacheConfig.getConfig(this, "cache", c.attributes, null));
       }
+    }
+    this.userCacheConfigs = Collections.unmodifiableMap(userCacheConfigs);
 
-      httpCachingConfig = new HttpCachingConfig(this);
+    updateHandlerInfo = loadUpdatehandlerInfo();
 
-      maxWarmingSearchers = get("query").get("maxWarmingSearchers").intVal(1);
-      slowQueryThresholdMillis = get("query").get("slowQueryThresholdMillis").intVal(-1);
-      for (SolrPluginInfo plugin : plugins) loadPluginInfo(plugin);
+    multipartUploadLimitKB = getInt(
+        "requestDispatcher/requestParsers/@multipartUploadLimitInKB", Integer.MAX_VALUE);
+    if (multipartUploadLimitKB == -1) multipartUploadLimitKB = Integer.MAX_VALUE;
 
-      Map<String, CacheConfig> userCacheConfigs = CacheConfig.getMultipleConfigs(this, "query/cache",
-          get("query").getAll("cache"));
-      List<PluginInfo> caches = getPluginInfos(SolrCache.class.getName());
-      if (!caches.isEmpty()) {
-        for (PluginInfo c : caches) {
-          userCacheConfigs.put(c.name, CacheConfig.getConfig(this, "cache", c.attributes, null));
-        }
-      }
-      this.userCacheConfigs = Collections.unmodifiableMap(userCacheConfigs);
+    formUploadLimitKB = getInt(
+        "requestDispatcher/requestParsers/@formdataUploadLimitInKB", Integer.MAX_VALUE);
+    if (formUploadLimitKB == -1) formUploadLimitKB = Integer.MAX_VALUE;
 
-      updateHandlerInfo = loadUpdatehandlerInfo();
+    enableRemoteStreams = getBool(
+        "requestDispatcher/requestParsers/@enableRemoteStreaming", false);
 
-      multipartUploadLimitKB = get("requestDispatcher").get("requestParsers").intAttr("multipartUploadLimitInKB", Integer.MAX_VALUE);
-      if (multipartUploadLimitKB == -1) multipartUploadLimitKB = Integer.MAX_VALUE;
+    enableStreamBody = getBool(
+        "requestDispatcher/requestParsers/@enableStreamBody", false);
 
-      formUploadLimitKB = get("requestDispatcher").get("requestParsers").intAttr("formdataUploadLimitInKB", Integer.MAX_VALUE);
-      if (formUploadLimitKB == -1) formUploadLimitKB = Integer.MAX_VALUE;
+    handleSelect = getBool(
+        "requestDispatcher/@handleSelect", false);
 
-      enableRemoteStreams = get("requestDispatcher").get("requestParsers").boolAttr("enableRemoteStreaming", false);
+    addHttpRequestToContext = getBool(
+        "requestDispatcher/requestParsers/@addHttpRequestToContext", false);
 
-      enableStreamBody = get("requestDispatcher").get("requestParsers").boolAttr("enableStreamBody", false);
-
-      handleSelect = get("requestDispatcher").boolAttr("handleSelect", false);
-      addHttpRequestToContext = get("requestDispatcher").get("requestParsers").boolAttr("addHttpRequestToContext", false);
-
-      List<PluginInfo> argsInfos = getPluginInfos(InitParams.class.getName());
-      if (argsInfos != null) {
-        Map<String, InitParams> argsMap = new HashMap<>();
-        for (PluginInfo p : argsInfos) {
-          InitParams args = new InitParams(p);
-          argsMap.put(args.name == null ? String.valueOf(args.hashCode()) : args.name, args);
-        }
-        this.initParams = Collections.unmodifiableMap(argsMap);
+    List<PluginInfo> argsInfos = getPluginInfos(InitParams.class.getName());
+    if (argsInfos != null) {
+      Map<String, InitParams> argsMap = new HashMap<>();
+      for (PluginInfo p : argsInfos) {
+        InitParams args = new InitParams(p);
+        argsMap.put(args.name == null ? String.valueOf(args.hashCode()) : args.name, args);
       }
+      this.initParams = Collections.unmodifiableMap(argsMap);
 
-      solrRequestParsers = new SolrRequestParsers(this);
-      log.debug("Loaded SolrConfig: {}", name);
-    } finally {
-      ConfigNode.SUBSTITUTES.remove();
     }
-  }
 
-  private void readXml(SolrResourceLoader loader, String name, Map<String, IndexSchemaFactory.VersionedConfig> configCache, ResourceProvider rp) throws IOException {
-    XmlConfigFile xml = new XmlConfigFile(loader,rp, name, null, "/config/", null);
-    root = new DataConfigNode(new DOMConfigNode(xml.getDocument().getDocumentElement()));
-    this.znodeVersion = rp.zkVersion;
-    if(configCache != null && rp.fileName !=null) {
-      configCache.put(rp.fileName, new IndexSchemaFactory.VersionedConfig(rp.hash, root));
-    }
+    solrRequestParsers = new SolrRequestParsers(this);
+    log.debug("Loaded SolrConfig: {}", name);
   }
 
   private static final AtomicBoolean versionWarningAlreadyLogged = new AtomicBoolean(false);
@@ -409,25 +340,20 @@ public class SolrConfig implements MapSerializable {
           // and even then -- only if there is a single SpellCheckComponent
           // because of queryConverter.setIndexAnalyzer
       .add(new SolrPluginInfo(QueryConverter.class, "queryConverter", REQUIRE_NAME, REQUIRE_CLASS))
-      // this is hackish, since it picks up all SolrEventListeners,
-      // regardless of when/how/why they are used (or even if they are
-      // declared outside of the appropriate context) but there's no nice
-      // way around that in the PluginInfo framework
+          // this is hackish, since it picks up all SolrEventListeners,
+          // regardless of when/how/why they are used (or even if they are
+          // declared outside of the appropriate context) but there's no nice
+          // way around that in the PluginInfo framework
       .add(new SolrPluginInfo(InitParams.class, InitParams.TYPE, MULTI_OK, REQUIRE_NAME_IN_OVERLAY))
-      .add(new SolrPluginInfo(it -> {
-        List<ConfigNode> result = new ArrayList<>();
-        result.addAll(it.get("query").getAll("listener"));
-        result.addAll( it.get("updateHandler").getAll("listener"));
-        return result;
-      }, SolrEventListener.class, "//listener", REQUIRE_CLASS, MULTI_OK, REQUIRE_NAME_IN_OVERLAY))
+      .add(new SolrPluginInfo(SolrEventListener.class, "//listener", REQUIRE_CLASS, MULTI_OK, REQUIRE_NAME_IN_OVERLAY))
 
       .add(new SolrPluginInfo(DirectoryFactory.class, "directoryFactory", REQUIRE_CLASS))
       .add(new SolrPluginInfo(RecoveryStrategy.Builder.class, "recoveryStrategy"))
-      .add(new SolrPluginInfo(it -> it.get("indexConfig").getAll("deletionPolicy"), IndexDeletionPolicy.class, "indexConfig/deletionPolicy", REQUIRE_CLASS))
+      .add(new SolrPluginInfo(IndexDeletionPolicy.class, "indexConfig/deletionPolicy", REQUIRE_CLASS))
       .add(new SolrPluginInfo(CodecFactory.class, "codecFactory", REQUIRE_CLASS))
       .add(new SolrPluginInfo(IndexReaderFactory.class, "indexReaderFactory", REQUIRE_CLASS))
       .add(new SolrPluginInfo(UpdateRequestProcessorChain.class, "updateRequestProcessorChain", MULTI_OK))
-      .add(new SolrPluginInfo(it -> it.get("updateHandler").getAll("updateLog"), UpdateLog.class, "updateHandler/updateLog"))
+      .add(new SolrPluginInfo(UpdateLog.class, "updateHandler/updateLog"))
       .add(new SolrPluginInfo(IndexSchemaFactory.class, "schemaFactory", REQUIRE_CLASS))
       .add(new SolrPluginInfo(RestManager.class, "restManager"))
       .add(new SolrPluginInfo(StatsCache.class, "statsCache", REQUIRE_CLASS))
@@ -447,18 +373,10 @@ public class SolrConfig implements MapSerializable {
     public final Class clazz;
     public final String tag;
     public final Set<PluginOpts> options;
-    final Function<SolrConfig, List<ConfigNode>> configReader;
 
 
     @SuppressWarnings({"unchecked", "rawtypes"})
     private SolrPluginInfo(Class clz, String tag, PluginOpts... opts) {
-      this(solrConfig -> solrConfig.root.getAll(null, tag), clz, tag, opts);
-
-    }
-
-    @SuppressWarnings({"unchecked", "rawtypes"})
-    private SolrPluginInfo(Function<SolrConfig, List<ConfigNode>> configReader, Class clz, String tag, PluginOpts... opts) {
-      this.configReader = configReader;
       this.clazz = clz;
       this.tag = tag;
       this.options = opts == null ? Collections.EMPTY_SET : EnumSet.of(NOOP, opts);
@@ -487,7 +405,7 @@ public class SolrConfig implements MapSerializable {
         // hopefully no problem, assume no overlay.json file
         return new ConfigOverlay(Collections.EMPTY_MAP, -1);
       }
-
+      
       int version = 0; // will be always 0 for file based resourceLoader
       if (in instanceof ZkSolrResourceLoader.ZkByteArrayInputStream) {
         version = ((ZkSolrResourceLoader.ZkByteArrayInputStream) in).getStat().getVersion();
@@ -510,15 +428,15 @@ public class SolrConfig implements MapSerializable {
   }
 
   protected UpdateHandlerInfo loadUpdatehandlerInfo() {
-    return new UpdateHandlerInfo( get("updateHandler").attr("class"),
-        get("updateHandler").get("autoCommit").get("maxDocs").intVal( -1),
-        get("updateHandler").get("autoCommit").get("maxTime").intVal( -1),
-        convertHeapOptionStyleConfigStringToBytes(get("updateHandler").get("autoCommit").get("maxSize").txt()),
-        get("updateHandler").get("indexWriter").get("closeWaitsForMerges").boolVal(true),
-        get("updateHandler").get("autoCommit").get("openSearcher").boolVal(true),
-        get("updateHandler").get("autoSoftCommit").get("maxDocs").intVal(-1),
-        get("updateHandler").get("autoSoftCommit").get("maxTime").intVal(-1),
-        get("updateHandler").get("commitWithin").get("softCommit").boolVal(true));
+    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));
   }
 
   /**
@@ -529,7 +447,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== null || configStr.isEmpty()) {
+    if (configStr.isEmpty()) {
       return -1;
     }
     long multiplier = 1;
@@ -563,7 +481,7 @@ public class SolrConfig implements MapSerializable {
     boolean requireName = pluginInfo.options.contains(REQUIRE_NAME);
     boolean requireClass = pluginInfo.options.contains(REQUIRE_CLASS);
 
-    List<PluginInfo> result = readPluginInfos(pluginInfo, requireName, requireClass);
+    List<PluginInfo> result = readPluginInfos(pluginInfo.tag, requireName, requireClass);
 
     if (1 < result.size() && !pluginInfo.options.contains(MULTI_OK)) {
       throw new SolrException
@@ -574,10 +492,11 @@ public class SolrConfig implements MapSerializable {
     if (!result.isEmpty()) pluginStore.put(pluginInfo.clazz.getName(), result);
   }
 
-  public List<PluginInfo> readPluginInfos(SolrPluginInfo info, boolean requireName, boolean requireClass) {
+  public List<PluginInfo> readPluginInfos(String tag, boolean requireName, boolean requireClass) {
     ArrayList<PluginInfo> result = new ArrayList<>();
-    for (ConfigNode node : info.configReader.apply(this)) {
-      PluginInfo pluginInfo = new PluginInfo(node, "[solrconfig.xml] " + info.tag, requireName, requireClass);
+    NodeList nodes = (NodeList) evaluate(tag, XPathConstants.NODESET);
+    for (int i = 0; i < nodes.getLength(); i++) {
+      PluginInfo pluginInfo = new PluginInfo(nodes.item(i), "[solrconfig.xml] " + tag, requireName, requireClass);
       if (pluginInfo.isEnabled()) result.add(pluginInfo);
     }
     return result;
@@ -627,6 +546,12 @@ public class SolrConfig implements MapSerializable {
   public static class HttpCachingConfig implements MapSerializable {
 
     /**
+     * config xpath prefix for getting HTTP Caching options
+     */
+    private final static String CACHE_PRE
+        = "requestDispatcher/httpCaching/";
+
+    /**
      * For extracting Expires "ttl" from <cacheControl> config
      */
     private final static Pattern MAX_AGE
@@ -640,7 +565,7 @@ public class SolrConfig implements MapSerializable {
           "cacheControl", cacheControlHeader);
     }
 
-    public enum LastModFrom {
+    public static enum LastModFrom {
       OPENTIME, DIRLASTMOD, BOGUS;
 
       /**
@@ -661,20 +586,18 @@ 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 = get("requestDispatcher").get("httpCaching").boolAttr("never304", false);
+      never304 = conf.getBool(CACHE_PRE + "@never304", false);
 
-      etagSeed = get("requestDispatcher").get("httpCaching").attr("etagSeed", "Solr");
+      etagSeed = conf.get(CACHE_PRE + "@etagSeed", "Solr");
 
 
-      lastModFrom = LastModFrom.parse(get("requestDispatcher").get("httpCaching").attr("lastModFrom","openTime"));
+      lastModFrom = LastModFrom.parse(conf.get(CACHE_PRE + "@lastModFrom",
+          "openTime"));
 
-      cacheControlHeader = get("requestDispatcher").get("httpCaching").get("cacheControl").txt();
+      cacheControlHeader = conf.get(CACHE_PRE + "cacheControl", null);
 
       Long tmp = null; // maxAge
       if (null != cacheControlHeader) {
@@ -692,9 +615,6 @@ public class SolrConfig implements MapSerializable {
       maxAge = tmp;
 
     }
-    private ConfigNode get(String name){
-      return configNode.get(name);
-    }
 
     public boolean isNever304() {
       return never304;
@@ -843,23 +763,23 @@ public class SolrConfig implements MapSerializable {
       }
     }
 
-    List<ConfigNode> nodes = root.getAll("lib");
-    if (nodes != null && nodes.size() > 0) {
+    NodeList nodes = (NodeList) evaluate("lib", XPathConstants.NODESET);
+    if (nodes != null && nodes.getLength() > 0) {
       if (!isConfigsetTrusted) {
         throw new SolrException(ErrorCode.UNAUTHORIZED,
-            "The configset for this collection was uploaded without any authentication in place,"
-                + " and use of <lib> is not available for collections with untrusted configsets. To use this component, re-upload the configset"
-                + " after enabling authentication and authorization.");
+          "The configset for this collection was uploaded without any authentication in place,"
+            + " and use of <lib> is not available for collections with untrusted configsets. To use this component, re-upload the configset"
+            + " after enabling authentication and authorization.");
       }
 
-      for (int i = 0; i < nodes.size(); i++) {
-        ConfigNode node = nodes.get(i);
-        String baseDir = node.attr("dir");
-        String path = node.attr(PATH);
+      for (int i = 0; i < nodes.getLength(); i++) {
+        Node node = nodes.item(i);
+        String baseDir = DOMUtil.getAttr(node, "dir");
+        String path = DOMUtil.getAttr(node, PATH);
         if (null != baseDir) {
           // :TODO: add support for a simpler 'glob' mutually exclusive of regex
           Path dir = instancePath.resolve(baseDir);
-          String regex = node.attr("regex");
+          String regex = DOMUtil.getAttr(node, "regex");
           try {
             if (regex == null)
               urls.addAll(SolrResourceLoader.getURLs(dir));
@@ -911,11 +831,42 @@ public class SolrConfig 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);
+  }
+
+  @Override
+  public String get(String path) {
+    Object val = overlay.getXPathProperty(path, true);
+    return val != null ? val.toString() : super.get(path);
+  }
+
+  @Override
+  public String get(String path, String def) {
+    Object val = overlay.getXPathProperty(path, true);
+    return val != null ? val.toString() : super.get(path, def);
+
+  }
 
   @Override
   @SuppressWarnings({"unchecked", "rawtypes"})
   public Map<String, Object> toMap(Map<String, Object> result) {
-    if (znodeVersion > -1) result.put(ZNODEVER, znodeVersion);
+    if (getZnodeVersion() > -1) result.put(ZNODEVER, getZnodeVersion());
     if(luceneMatchVersion != null) result.put(IndexSchema.LUCENE_MATCH_VERSION_PARAM, luceneMatchVersion.toString());
     result.put("updateHandler", getUpdateHandlerInfo());
     Map m = new LinkedHashMap();
@@ -976,10 +927,11 @@ public class SolrConfig implements MapSerializable {
 
   }
 
+  @Override
   public Properties getSubstituteProperties() {
     Map<String, Object> p = getOverlay().getUserProps();
-    if (p == null || p.isEmpty()) return substituteProperties;
-    Properties result = new Properties(substituteProperties);
+    if (p == null || p.isEmpty()) return super.getSubstituteProperties();
+    Properties result = new Properties(super.getSubstituteProperties());
     result.putAll(p);
     return result;
   }
@@ -988,7 +940,7 @@ public class SolrConfig implements MapSerializable {
 
   public ConfigOverlay getOverlay() {
     if (overlay == null) {
-      overlay = getConfigOverlay(resourceLoader);
+      overlay = getConfigOverlay(getResourceLoader());
     }
     return overlay;
   }
@@ -1017,43 +969,11 @@ public class SolrConfig implements MapSerializable {
   }
 
   public RequestParams refreshRequestParams() {
-    requestParams = RequestParams.getFreshRequestParams(resourceLoader, requestParams);
+    requestParams = RequestParams.getFreshRequestParams(getResourceLoader(), 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 get(String name) {
-    if (!overlay.hasKey(name)) {
-      //there is no overlay
-      return root.get(name);
-    }
-    return new OverlaidConfigNode(overlay, name, null,root.get(name));
-  }
-
-  public ConfigNode get(String name, Predicate<ConfigNode> test) {
-    return root.get(name, test);
-  }
 }
diff --git a/solr/core/src/java/org/apache/solr/core/XmlConfigFile.java b/solr/core/src/java/org/apache/solr/core/XmlConfigFile.java
index 6a545d4..5971eb5 100644
--- a/solr/core/src/java/org/apache/solr/core/XmlConfigFile.java
+++ b/solr/core/src/java/org/apache/solr/core/XmlConfigFile.java
@@ -36,7 +36,6 @@ import java.util.SortedMap;
 import java.util.SortedSet;
 import java.util.TreeMap;
 import java.util.TreeSet;
-import java.util.function.Function;
 
 import org.apache.commons.io.IOUtils;
 import org.apache.solr.cloud.ZkSolrResourceLoader;
@@ -86,15 +85,6 @@ public class XmlConfigFile { // formerly simply "Config"
   {
     this(loader, name, is, prefix, null);
   }
-  public XmlConfigFile(SolrResourceLoader loader, String name, InputSource is, String prefix, Properties substituteProps) throws ParserConfigurationException, IOException, SAXException{
-    this(loader, s -> {
-      try {
-        return loader.openResource(s);
-      } catch (IOException e) {
-        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
-      }
-    },name, is, prefix, substituteProps);
-  }
 
   /**
    * Builds a config:
@@ -113,7 +103,8 @@ public class XmlConfigFile { // formerly simply "Config"
    * @param prefix an optional prefix that will be prepended to all non-absolute xpath expressions
    * @param substituteProps optional property substitution
    */
-  public XmlConfigFile(SolrResourceLoader loader, Function<String, InputStream> fileSupplier, String name, InputSource is, String prefix, Properties substituteProps) throws IOException {
+  public XmlConfigFile(SolrResourceLoader loader, String name, InputSource is, String prefix, Properties substituteProps) throws ParserConfigurationException, IOException, SAXException
+  {
     if (null == loader) throw new NullPointerException("loader");
     this.loader = loader;
     
@@ -124,7 +115,7 @@ public class XmlConfigFile { // formerly simply "Config"
       javax.xml.parsers.DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
 
       if (is == null) {
-        InputStream in = fileSupplier.apply(name);
+        InputStream in = loader.openResource(name);
         if (in instanceof ZkSolrResourceLoader.ZkByteArrayInputStream) {
           zkVersion = ((ZkSolrResourceLoader.ZkByteArrayInputStream) in).getStat().getVersion();
           log.debug("loaded config {} with version {} ",name,zkVersion);
diff --git a/solr/core/src/java/org/apache/solr/handler/DumpRequestHandler.java b/solr/core/src/java/org/apache/solr/handler/DumpRequestHandler.java
index c99ca79..4ce5fa5 100644
--- a/solr/core/src/java/org/apache/solr/handler/DumpRequestHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/DumpRequestHandler.java
@@ -36,7 +36,6 @@ import static org.apache.solr.common.params.CommonParams.NAME;
 
 public class DumpRequestHandler extends RequestHandlerBase
 {
-
   @Override
   @SuppressWarnings({"unchecked"})
   public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws IOException
diff --git a/solr/core/src/java/org/apache/solr/schema/FieldTypePluginLoader.java b/solr/core/src/java/org/apache/solr/schema/FieldTypePluginLoader.java
index b1c72e2..345a2cf 100644
--- a/solr/core/src/java/org/apache/solr/schema/FieldTypePluginLoader.java
+++ b/solr/core/src/java/org/apache/solr/schema/FieldTypePluginLoader.java
@@ -189,9 +189,9 @@ public final class FieldTypePluginLoader
 
     // check for all of these up front, so we can error if used in 
     // conjunction with an explicit analyzer class.
-    List<ConfigNode> charFilterNodes = node.getAll("charFilter");
-    List<ConfigNode> tokenizerNodes = node.getAll("tokenizer");
-    List<ConfigNode> tokenFilterNodes = node.getAll("filter");
+    List<ConfigNode> charFilterNodes = node.children("charFilter");
+    List<ConfigNode> tokenizerNodes = node.children("tokenizer");
+    List<ConfigNode> tokenFilterNodes = node.children("filter");
 
     if (analyzerName != null) {
 
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 d65159e..19c1de2 100644
--- a/solr/core/src/java/org/apache/solr/schema/IndexSchema.java
+++ b/solr/core/src/java/org/apache/solr/schema/IndexSchema.java
@@ -172,9 +172,7 @@ public class IndexSchema {
     this(luceneVersion, resourceLoader, substitutableProperties);
 
     this.resourceName = Objects.requireNonNull(name);
-    ConfigNode.SUBSTITUTES.set(key -> substitutableProperties == null ?
-        null :
-        substitutableProperties.getProperty(key));
+    ConfigNode.SUBSTITUTES.set(substitutableProperties::getProperty);
     try {
       readSchema(schemaResource);
       loader.inform(loader);
@@ -515,9 +513,9 @@ public class IndexSchema {
       // load the Field Types
       final FieldTypePluginLoader typeLoader = new FieldTypePluginLoader(this, fieldTypes, schemaAware);
 
-      List<ConfigNode> fTypes = rootNode.getAll(null, FIELDTYPE_KEYS);
+      List<ConfigNode> fTypes = rootNode.children(null, FIELDTYPE_KEYS);
       ConfigNode types = rootNode.child(TYPES);
-      if(types != null) fTypes.addAll(types.getAll(null, FIELDTYPE_KEYS));
+      if(types != null) fTypes.addAll(types.children(null, FIELDTYPE_KEYS));
       typeLoader.load(solrClassLoader, fTypes);
 
       // load the fields
@@ -562,7 +560,7 @@ public class IndexSchema {
       if (node==null) {
         log.warn("no {} specified in schema.", UNIQUE_KEY);
       } else {
-        uniqueKeyField=getIndexedField(node.txt().trim());
+        uniqueKeyField=getIndexedField(node.textValue().trim());
         uniqueKeyFieldName=uniqueKeyField.getName();
         uniqueKeyFieldType=uniqueKeyField.getType();
         
@@ -649,10 +647,10 @@ public class IndexSchema {
     
     ArrayList<DynamicField> dFields = new ArrayList<>();
 
-    List<ConfigNode> nodes = n.getAll(null,  FIELD_KEYS);
+    List<ConfigNode> nodes = n.children(null,  FIELD_KEYS);
     ConfigNode child = n.child(FIELDS);
     if(child != null) {
-      nodes.addAll(child.getAll(null, FIELD_KEYS));
+      nodes.addAll(child.children(null, FIELD_KEYS));
     }
 
     for (ConfigNode node : nodes) {
@@ -732,10 +730,10 @@ public class IndexSchema {
    * Loads the copy fields
    */
   protected synchronized void loadCopyFields(ConfigNode n) {
-    List<ConfigNode> nodes = n.getAll(COPY_FIELD);
+    List<ConfigNode> nodes = n.children(COPY_FIELD);
     ConfigNode f = n.child(FIELDS);
     if (f != null) {
-      List<ConfigNode> c = f.getAll(COPY_FIELD);
+      List<ConfigNode> c = f.children(COPY_FIELD);
       if (nodes.isEmpty()) nodes = c;
       else nodes.addAll(c);
     }
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 0131943..c9d3187 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 {
-    public final int version;
-    public final ConfigNode data;
+    final int version;
+    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 45ec01a..af1b87f 100644
--- a/solr/core/src/java/org/apache/solr/search/CacheConfig.java
+++ b/solr/core/src/java/org/apache/solr/search/CacheConfig.java
@@ -16,6 +16,7 @@
  */
 package org.apache.solr.search;
 
+import javax.xml.xpath.XPathConstants;
 import java.lang.invoke.MethodHandles;
 import java.util.Collections;
 import java.util.HashMap;
@@ -24,15 +25,19 @@ 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.DOMUtil;
 import org.apache.solr.common.util.StrUtils;
+
 import org.apache.solr.core.PluginInfo;
+
 import org.apache.solr.core.SolrConfig;
 import org.apache.solr.core.SolrResourceLoader;
 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;
 
@@ -65,8 +70,7 @@ 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) {
@@ -84,13 +88,15 @@ public class CacheConfig implements MapSerializable{
     this.regenerator = regenerator;
   }
 
-  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);
+  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);
         result.put(config.args.get(NAME), config);
       }
     }
@@ -98,20 +104,20 @@ public class CacheConfig implements MapSerializable{
   }
 
 
-  @SuppressWarnings({"unchecked"})
-  public static CacheConfig getConfig(SolrConfig solrConfig, ConfigNode node, String xpath) {
-    if (!node.exists() || !"true".equals(node.attributes().get("enabled", "true"))) {
-      Map<String, Object> m = solrConfig.getOverlay().getEditableSubProperties(xpath);
-      if (m == null) return null;
+  public static CacheConfig getConfig(SolrConfig solrConfig, String xpath) {
+    Node node = solrConfig.getNode(xpath, false);
+    if(node == null || !"true".equals(DOMUtil.getAttrOrDefault(node, "enabled", "true"))) {
+      Map<?, ?> m = solrConfig.getOverlay().getEditableSubProperties(xpath);
+      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.emptyMap(), xpath);
     }
-    return getConfig(solrConfig, node.name(), node.attributes().asMap(), xpath);
+    return getConfig(solrConfig, node.getNodeName(),DOMUtil.toMap(node.getAttributes()), 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 00ecefb..a364757 100644
--- a/solr/core/src/java/org/apache/solr/update/SolrIndexConfig.java
+++ b/solr/core/src/java/org/apache/solr/update/SolrIndexConfig.java
@@ -19,6 +19,7 @@ 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;
@@ -30,7 +31,6 @@ 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,12 +91,11 @@ 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() {
+  private SolrIndexConfig(SolrConfig solrConfig) {
     useCompoundFile = false;
     maxBufferedDocs = -1;
     ramBufferSizeMB = 100;
@@ -110,78 +109,88 @@ public class SolrIndexConfig implements MapSerializable {
     // enable coarse-grained metrics by default
     metricsInfo = new PluginInfo("metrics", Collections.emptyMap(), null, null);
   }
-  private ConfigNode get(String s) { return node.get(s); }
-  public SolrIndexConfig(SolrConfig cfg, SolrIndexConfig def)  {
-    this(cfg.get("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(ConfigNode cfg, SolrIndexConfig def)  {
-    this.node = cfg;
+  public SolrIndexConfig(SolrConfig solrConfig, String prefix, SolrIndexConfig def)  {
+    if (prefix == null) {
+      prefix = "indexConfig";
+      log.debug("Defaulting to prefix '{}' for index configuration", prefix);
+    }
+    
     if (def == null) {
-      def = new SolrIndexConfig();
+      def = new SolrIndexConfig(solrConfig);
     }
 
-
     // 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.",
-        get("mergeScheduler").isNull() || get("mergeScheduler").attr("class") != null,
+        !((solrConfig.getNode(prefix + "/mergeScheduler", false) != null) && (solrConfig.get(prefix + "/mergeScheduler/@class", null) == null)),
         true);
     assertWarnOrFail("Beginning with Solr 7.0, <mergePolicy>myclass</mergePolicy> is no longer supported, use <mergePolicyFactory> instead.",
-        get("mergePolicy").isNull() || get("mergePolicy").attr("class") != null,
+        !((solrConfig.getNode(prefix + "/mergePolicy", false) != null) && (solrConfig.get(prefix + "/mergePolicy/@class", null) == null)),
         true);
     assertWarnOrFail("The <luceneAutoCommit>true|false</luceneAutoCommit> parameter is no longer valid in solrconfig.xml.",
-        get("luceneAutoCommit").isNull(),
+        solrConfig.get(prefix + "/luceneAutoCommit", null) == null,
         true);
 
-    useCompoundFile = get("useCompoundFile").boolVal(def.useCompoundFile);
-    maxBufferedDocs = get("maxBufferedDocs").intVal(def.maxBufferedDocs);
-    ramBufferSizeMB = get("ramBufferSizeMB").doubleVal(def.ramBufferSizeMB);
-    maxCommitMergeWaitMillis = get("maxCommitMergeWaitTime").intVal(def.maxCommitMergeWaitMillis);
+    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);
 
     // how do we validate the value??
-    ramPerThreadHardLimitMB = get("ramPerThreadHardLimitMB").intVal(def.ramPerThreadHardLimitMB);
+    ramPerThreadHardLimitMB = solrConfig.getInt(prefix+"/ramPerThreadHardLimitMB", def.ramPerThreadHardLimitMB);
 
-    writeLockTimeout= get("writeLockTimeout").intVal(def.writeLockTimeout);
-    lockType = get("lockType").txt(def.lockType);
+    writeLockTimeout=solrConfig.getInt(prefix+"/writeLockTimeout", def.writeLockTimeout);
+    lockType=solrConfig.get(prefix+"/lockType", def.lockType);
 
-    metricsInfo = getPluginInfo(get("metrics"), def.metricsInfo);
-    mergeSchedulerInfo = getPluginInfo(get("mergeScheduler"), def.mergeSchedulerInfo);
-    mergePolicyFactoryInfo = getPluginInfo(get("mergePolicyFactory"), def.mergePolicyFactoryInfo);
+    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);
 
     assertWarnOrFail("Beginning with Solr 7.0, <mergePolicy> is no longer supported, use <mergePolicyFactory> instead.",
-        get("mergePolicy").isNull(),
+        getPluginInfo(prefix + "/mergePolicy", solrConfig, null) == null,
         true);
     assertWarnOrFail("Beginning with Solr 7.0, <maxMergeDocs> is no longer supported, configure it on the relevant <mergePolicyFactory> instead.",
-        get("maxMergeDocs").isNull(),
+        solrConfig.getInt(prefix+"/maxMergeDocs", 0) == 0,
         true);
     assertWarnOrFail("Beginning with Solr 7.0, <mergeFactor> is no longer supported, configure it on the relevant <mergePolicyFactory> instead.",
-        get("maxMergeFactor").isNull(),
+        solrConfig.getInt(prefix+"/mergeFactor", 0) == 0,
         true);
 
-    if (get("termIndexInterval").exists()) {
+    String val = solrConfig.get(prefix + "/termIndexInterval", null);
+    if (val != null) {
       throw new IllegalArgumentException("Illegal parameter 'termIndexInterval'");
     }
 
-    if(get("infoStream").boolVal(false)) {
-      if (get("infoStream").attr("file") == null) {
+    boolean infoStreamEnabled = solrConfig.getBool(prefix + "/infoStream", false);
+    if(infoStreamEnabled) {
+      String infoStreamFile = solrConfig.get(prefix + "/infoStream/@file", null);
+      if (infoStreamFile == 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(get("mergedSegmentWarmer"), def.mergedSegmentWarmerInfo);
+    mergedSegmentWarmerInfo = getPluginInfo(prefix + "/mergedSegmentWarmer", solrConfig, 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)",
-        get( "checkIntegrityAtMerge").isNull(),
+        (null == solrConfig.getNode(prefix + "/checkIntegrityAtMerge", false)),
         true);
   }
 
@@ -206,10 +215,9 @@ public class SolrIndexConfig implements MapSerializable {
     return m;
   }
 
-  private PluginInfo getPluginInfo(ConfigNode node , PluginInfo def)  {
-    return node != null && node.exists() ?
-        new PluginInfo(node, "[solrconfig.xml] " + node.name(), false, false) :
-        def;
+  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 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 bf21ff4..b97f812 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().get("updateHandler").get("versionBucketLockTimeoutMs")
-        .intVal(Integer.parseInt(System.getProperty(SYS_PROP_BUCKET_VERSION_LOCK_TIMEOUT_MS, "0")));
+    versionBucketLockTimeoutMs = ulog.uhandler.core.getSolrConfig().getInt("updateHandler/versionBucketLockTimeoutMs",
+        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/DOMConfigNode.java b/solr/core/src/java/org/apache/solr/util/DOMConfigNode.java
index fb54af1..6afc5a5 100644
--- a/solr/core/src/java/org/apache/solr/util/DOMConfigNode.java
+++ b/solr/core/src/java/org/apache/solr/util/DOMConfigNode.java
@@ -18,9 +18,7 @@
 package org.apache.solr.util;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 import java.util.function.Function;
 
 import org.apache.solr.cluster.api.SimpleMap;
@@ -45,7 +43,7 @@ public class DOMConfigNode implements ConfigNode {
   }
 
   @Override
-  public String txt() {
+  public String textValue() {
     return DOMUtil.getText(node);
   }
 
@@ -56,8 +54,7 @@ public class DOMConfigNode implements ConfigNode {
   @Override
   public SimpleMap<String> attributes() {
     if (attrs != null) return attrs;
-    Map<String, String> attrs = DOMUtil.toMap(node.getAttributes());
-    return this.attrs = attrs.size() == 0 ? EMPTY : new WrappedSimpleMap<>(attrs);
+    return attrs = new WrappedSimpleMap<>(DOMUtil.toMap(node.getAttributes()));
   }
 
   @Override
@@ -67,7 +64,7 @@ public class DOMConfigNode implements ConfigNode {
   }
 
   @Override
-  public List<ConfigNode> getAll(String name) {
+  public List<ConfigNode> children(String name) {
     List<ConfigNode> result = new ArrayList<>();
     forEachChild(it -> {
       if (name.equals(it.name())) {
@@ -88,6 +85,5 @@ public class DOMConfigNode implements ConfigNode {
       if (Boolean.FALSE == toContinue) break;
     }
   }
-  private static final SimpleMap<String> EMPTY = new WrappedSimpleMap<>(Collections.emptyMap());
 
 }
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 78676a2..38940ee 100644
--- a/solr/core/src/java/org/apache/solr/util/DataConfigNode.java
+++ b/solr/core/src/java/org/apache/solr/util/DataConfigNode.java
@@ -19,7 +19,7 @@ package org.apache.solr.util;
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.LinkedHashMap;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -27,64 +27,56 @@ import java.util.function.BiConsumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
 
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
 import org.apache.solr.cluster.api.SimpleMap;
 import org.apache.solr.common.ConfigNode;
 import org.apache.solr.common.util.PropertiesUtil;
-import org.apache.solr.common.util.WrappedSimpleMap;
 
 /**
  * ConfigNode impl that copies and maintains data internally from DOM
  */
 public class DataConfigNode implements ConfigNode {
-  public final String name;
-  public final SimpleMap<String> attributes;
-  public final SimpleMap<List<ConfigNode>> kids ;
-  public final String textData;
-
+  final String name;
+  final SimpleMap<String> attributes;
+  private final Map<String, List<ConfigNode>> kids = new HashMap<>();
+  private final String textData;
 
   public DataConfigNode(ConfigNode root) {
-    Map<String, List<ConfigNode>> kids = new LinkedHashMap<>();
     name = root.name();
     attributes = wrap(root.attributes());
-    textData = root.txt();
+    textData = root.textValue();
     root.forEachChild(it -> {
       List<ConfigNode> nodes = kids.computeIfAbsent(it.name(),
           k -> new ArrayList<>());
-      nodes.add(new DataConfigNode(it));
+
+     nodes.add(new DataConfigNode(it));
       return Boolean.TRUE;
     });
-    for (Map.Entry<String, List<ConfigNode>> e : kids.entrySet()) {
-      if(e.getValue()  != null) {
-        e.setValue(ImmutableList.copyOf(e.getValue()));
-      }
-    }
-    this.kids = kids.isEmpty()? EMPTY:  new WrappedSimpleMap<>(ImmutableMap.copyOf(kids));
+
   }
 
   public String subtituteVal(String s) {
-    return PropertiesUtil.substitute(s, SUBSTITUTES.get());
+    Function<String, String> props = SUBSTITUTES.get();
+    if (props == null) return s;
+    return PropertiesUtil.substitute(s, props);
   }
 
   private SimpleMap<String> wrap(SimpleMap<String> delegate) {
-    if(delegate.size() == 0) return delegate;//avoid unnecessary object creation
     return new SimpleMap<>() {
-      @Override
-      public String get(String key) {
-        return subtituteVal(delegate.get(key));
-      }
+          @Override
+          public String get(String key) {
+            return subtituteVal(delegate.get(key));
+          }
 
-      @Override
-      public void forEachEntry(BiConsumer<String, ? super String> fun) {
-        delegate.forEachEntry((k, v) -> fun.accept(k, subtituteVal(v)));
-      }
+          @Override
+          public void forEachEntry(BiConsumer<String, ? super String> fun) {
+            delegate.forEachEntry((k, v) -> fun.accept(k, subtituteVal(v)));
+          }
 
-      @Override
-      public int size() {
-        return delegate.size();
-      }
-    };
+          @Override
+          public int size() {
+            return delegate.size();
+          }
+        };
   }
 
   @Override
@@ -93,8 +85,8 @@ public class DataConfigNode implements ConfigNode {
   }
 
   @Override
-  public String txt() {
-    return subtituteVal(textData);
+  public String textValue() {
+    return  subtituteVal(textData);
   }
 
   @Override
@@ -109,12 +101,12 @@ public class DataConfigNode implements ConfigNode {
   }
 
   @Override
-  public List<ConfigNode> getAll(String name) {
-    return kids.get(name, Collections.emptyList());
+  public List<ConfigNode> children(String name) {
+    return kids.getOrDefault(name, Collections.emptyList());
   }
 
   @Override
-  public List<ConfigNode> getAll(Predicate<ConfigNode> test, Set<String> matchNames) {
+  public List<ConfigNode> children(Predicate<ConfigNode> test, Set<String> matchNames) {
     List<ConfigNode> result = new ArrayList<>();
     for (String s : matchNames) {
       List<ConfigNode> vals = kids.get(s);
@@ -131,11 +123,10 @@ public class DataConfigNode implements ConfigNode {
 
   @Override
   public void forEachChild(Function<ConfigNode, Boolean> fun) {
-    kids.forEachEntry((s, configNodes) -> {
+    kids.forEach((s, configNodes) -> {
       if (configNodes != null) {
         configNodes.forEach(fun::apply);
       }
     });
   }
-  public static final SimpleMap<List<ConfigNode>> EMPTY = new WrappedSimpleMap<>(Collections.emptyMap());
 }
diff --git a/solr/core/src/resources/EditableSolrConfigAttributes.json b/solr/core/src/resources/EditableSolrConfigAttributes.json
index fe06fa0..03bb1b6 100644
--- a/solr/core/src/resources/EditableSolrConfigAttributes.json
+++ b/solr/core/src/resources/EditableSolrConfigAttributes.json
@@ -56,6 +56,10 @@
     "enableLazyFieldLoading":1,
     "boolTofilterOptimizer":1,
     "maxBooleanClauses":1},
+  "jmx":{
+    "agentId":0,
+    "serviceUrl":0,
+    "rootName":0},
   "requestDispatcher":{
     "handleSelect":0,
     "requestParsers":{
diff --git a/solr/core/src/test/org/apache/solr/core/TestBadConfig.java b/solr/core/src/test/org/apache/solr/core/TestBadConfig.java
index 677ab39..91fd9ae 100644
--- a/solr/core/src/test/org/apache/solr/core/TestBadConfig.java
+++ b/solr/core/src/test/org/apache/solr/core/TestBadConfig.java
@@ -16,8 +16,6 @@
  */
 package org.apache.solr.core;
 
-import org.junit.Ignore;
-
 public class TestBadConfig extends AbstractBadConfigTestBase {
 
   public void testUnsetSysProperty() throws Exception {
@@ -28,17 +26,14 @@ public class TestBadConfig extends AbstractBadConfigTestBase {
     assertConfigs("bad-solrconfig-nrtmode.xml","schema.xml", "nrtMode");
   }
 
-  @Ignore
   public void testMultipleDirectoryFactories() throws Exception {
       assertConfigs("bad-solrconfig-multiple-dirfactory.xml", "schema12.xml",
                     "directoryFactory");
   }
-  @Ignore
   public void testMultipleIndexConfigs() throws Exception {
       assertConfigs("bad-solrconfig-multiple-indexconfigs.xml", "schema12.xml",
                     "indexConfig");
   }
-  @Ignore
   public void testMultipleCFS() throws Exception {
       assertConfigs("bad-solrconfig-multiple-cfs.xml", "schema12.xml",
                     "useCompoundFile");
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 f497d14..cd86d4f 100644
--- a/solr/core/src/test/org/apache/solr/core/TestCodecSupport.java
+++ b/solr/core/src/test/org/apache/solr/core/TestCodecSupport.java
@@ -200,9 +200,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").attr("class"));
-    assertTrue("Unexpected configuration of codec factory for this test. Expecting empty element",
-        config.get("codecFactory").getAll(null, (String)null).isEmpty());
+    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());
     IndexSchema schema = IndexSchemaFactory.buildIndexSchema("schema_codec.xml", config);
 
     CoreContainer coreContainer = h.getCoreContainer();
diff --git a/solr/core/src/test/org/apache/solr/core/TestConfLoadPerf.java b/solr/core/src/test/org/apache/solr/core/TestConfLoadPerf.java
deleted file mode 100644
index ab85f39..0000000
--- a/solr/core/src/test/org/apache/solr/core/TestConfLoadPerf.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.solr.core;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import org.apache.solr.SolrTestCaseJ4;
-import org.apache.solr.cloud.ZkSolrResourceLoader;
-import org.apache.solr.common.util.SuppressForbidden;
-import org.apache.solr.util.ExternalPaths;
-import org.apache.zookeeper.data.Stat;
-import org.junit.Ignore;
-
-import static org.apache.solr.core.TestConfigSets.solrxml;
-
-public class TestConfLoadPerf extends SolrTestCaseJ4 {
-
-  @Ignore
-  @SuppressForbidden(reason = "Needed to provide time for tests.")
-  public void testPerf() throws Exception{
-    String sourceHome = ExternalPaths.SOURCE_HOME;
-    File configSetDir = new File(sourceHome, "server/solr/configsets/sample_techproducts_configs/conf");
-
-    String configSetsBaseDir = TEST_PATH().resolve("configsets").toString();
-
-
-    File file = new File(configSetDir, "solrconfig.xml");
-    byte[]  b  = new byte[(int) file.length()];
-    new FileInputStream(file).read(b);
-
-    Path testDirectory = createTempDir();
-
-    System.setProperty("configsets", configSetsBaseDir);
-
-    CoreContainer container = new CoreContainer(SolrXmlConfig.fromString(testDirectory, solrxml));
-    container.load();
-    container.shutdown();
-
-    SolrResourceLoader srl = new SolrResourceLoader("temp", Collections.emptyList(), container.solrHome, container.getResourceLoader().classLoader){
-
-      @Override
-      public CoreContainer getCoreContainer() {
-        return container;
-      }
-
-      @Override
-      public InputStream openResource(String resource) throws IOException {
-        if(resource.equals("solrconfig.xml")) {
-          Stat stat = new Stat();
-          stat.setVersion(1);
-          return new ZkSolrResourceLoader.ZkByteArrayInputStream(b, file.getAbsolutePath(), stat);
-        } else {
-          throw new FileNotFoundException(resource);
-        }
-
-      }
-    };
-    System.gc();
-    long heapSize = Runtime.getRuntime().totalMemory();
-    List<SolrConfig> allConfigs = new ArrayList<>();
-    long startTime =  System.currentTimeMillis();
-    for(int i=0;i<100;i++) {
-      allConfigs.add(SolrConfig.readFromResourceLoader(srl, "solrconfig.xml", true, null));
-
-    }
-    System.gc();
-    System.out.println("TIME_TAKEN : "+(System.currentTimeMillis()-startTime));
-    System.out.println("HEAP_SIZE : "+((Runtime.getRuntime().totalMemory()-heapSize)/(1024)));
-  }
-}
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 567262a..ccf3114 100644
--- a/solr/core/src/test/org/apache/solr/core/TestConfig.java
+++ b/solr/core/src/test/org/apache/solr/core/TestConfig.java
@@ -16,18 +16,17 @@
  */
 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;
@@ -36,6 +35,8 @@ 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 {
 
@@ -79,24 +80,24 @@ public class TestConfig extends SolrTestCaseJ4 {
   public void testJavaProperty() {
     // property values defined in build.xml
 
-    String s = solrConfig.get("propTest").txt();
+    String s = solrConfig.get("propTest");
     assertEquals("prefix-proptwo-suffix", s);
 
-    s = solrConfig.get("propTest").attr("attr1", "default");
+    s = solrConfig.get("propTest/@attr1", "default");
     assertEquals("propone-${literal}", s);
 
-    s = solrConfig.get("propTest").attr("attr2", "default");
+    s = solrConfig.get("propTest/@attr2", "default");
     assertEquals("default-from-config", s);
 
+    s = solrConfig.get("propTest[@attr2='default-from-config']", "default");
+    assertEquals("prefix-proptwo-suffix", s);
 
-    assertEquals("prefix-proptwo-suffix", solrConfig.get("propTest",
-        it -> "default-from-config".equals(it.attr("attr2"))).txt());
-
-    List<ConfigNode> nl = solrConfig.root.getAll("propTest");
-    assertEquals(1, nl.size());
-    assertEquals("prefix-proptwo-suffix", nl.get(0).txt());
+    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.get("propTest").txt());
+    Node node = solrConfig.getNode("propTest", true);
+    assertEquals("prefix-proptwo-suffix", node.getTextContent());
   }
 
   // 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/TestConfigOverlay.java b/solr/core/src/test/org/apache/solr/core/TestConfigOverlay.java
index c782be3..07708c6 100644
--- a/solr/core/src/test/org/apache/solr/core/TestConfigOverlay.java
+++ b/solr/core/src/test/org/apache/solr/core/TestConfigOverlay.java
@@ -46,6 +46,9 @@ public class TestConfigOverlay extends SolrTestCase {
     assertTrue(isEditableProp("query.queryResultMaxDocsCached", false, null));
     assertTrue(isEditableProp("query.enableLazyFieldLoading", false, null));
     assertTrue(isEditableProp("query.boolTofilterOptimizer", false, null));
+    assertTrue(isEditableProp("jmx.agentId", false, null));
+    assertTrue(isEditableProp("jmx.serviceUrl", false, null));
+    assertTrue(isEditableProp("jmx.rootName", false, null));
 
     assertTrue(isEditableProp("requestDispatcher.requestParsers.multipartUploadLimitInKB", false, null));
     assertTrue(isEditableProp("requestDispatcher.requestParsers.formdataUploadLimitInKB", false, null));
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 abc7723..9f6776b 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").attr("class");
+    String codecFactory =  config.get("codecFactory/@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 a363190..b15e167 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
@@ -204,25 +204,14 @@ 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().get("searchComponent", n -> "suggest".equals(n.attr("name")))
-            .get("lst",7).
-            get("str", n -> "name".equals(n.attr("name"))).txt());
-
-    assertNull( h.getCore().getSolrConfig().get("searchComponent", n -> "suggest".equals(n.attr("name")))
-        .get("lst",7).
-            get("str", n -> "storeDir".equals(n.attr("name"))).txt());
+    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));
     
     // validate that this suggester only builds manually and has not buildOnStartup parameter
-
-    assertEquals("false",
-        h.getCore().getSolrConfig().get("searchComponent", n -> "suggest".equals(n.attr("name")))
-            .get("lst",7).
-            get("str", n -> "buildOnCommit".equals(n.attr("name"))).txt());
-
-    assertNull(h.getCore().getSolrConfig().get("searchComponent", n -> "suggest".equals(n.attr("name")))
-        .get("lst",7).
-            get("str", n -> "buildOnStartup".equals(n.attr("name"))).txt());
+    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));
     
     reloadCore(random().nextBoolean());
     
@@ -265,27 +254,15 @@ 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().get("searchComponent", n -> "suggest".equals(n.attr("name")))
-            .get("lst",6)
-            .get("str", n -> "name".equals(n.attr("name"))).txt());
-
-    assertEquals(suggester,
-        h.getCore().getSolrConfig().get("searchComponent", n -> "suggest".equals(n.attr("name")))
-            .get("lst",6)
-            .get("str", n -> "storeDir".equals(n.attr("name"))).txt());
+    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));
     
     // validate that this suggester only builds manually and has not buildOnStartup parameter
-
-    assertEquals("false",
-        h.getCore().getSolrConfig().get("searchComponent", n -> "suggest".equals(n.attr("name")))
-            .get("lst",6)
-            .get("str", n -> "buildOnCommit".equals(n.attr("name"))).txt());
-
-    assertNull( h.getCore().getSolrConfig().get("searchComponent", n -> "suggest".equals(n.attr("name")))
-        .get("lst",6)
-        .get("str", n -> "buildOnStartup".equals(n.attr("name"))).txt());
+    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));
     
     assertQ(req("qt", rh, 
         SuggesterParams.SUGGEST_DICT, suggester,
@@ -362,27 +339,16 @@ 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().get("searchComponent", n -> "suggest".equals(n.attr("name")))
-            .get("lst",5).
-            get("str", n -> "name".equals(n.attr("name"))).txt());
-
-    assertEquals(suggester,
-        h.getCore().getSolrConfig().get("searchComponent", n -> "suggest".equals(n.attr("name")))
-            .get("lst",5).
-            get("str", n -> "storeDir".equals(n.attr("name"))).txt());
+    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));
     
     // validate that this suggester only builds manually
-
-    assertEquals("false",
-        h.getCore().getSolrConfig().get("searchComponent", n -> "suggest".equals(n.attr("name")))
-            .get("lst",5).
-            get("str", n -> "buildOnCommit".equals(n.attr("name"))).txt());
-    assertEquals("false",
-        h.getCore().getSolrConfig().get("searchComponent", n -> "suggest".equals(n.attr("name")))
-            .get("lst",5).
-            get("str", n -> "buildOnStartup".equals(n.attr("name"))).txt());
+    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));
     
     // build the suggester manually
     assertQ(req("qt", rh, 
@@ -436,25 +402,15 @@ 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().get("searchComponent", n -> "suggest".equals(n.attr("name")))
-            .get("lst",2).
-            get("str", n -> "name".equals(n.attr("name"))).txt());
-
-    assertNull(h.getCore().getSolrConfig().get("searchComponent", n -> "suggest".equals(n.attr("name")))
-        .get("lst",2)
-        .get("str", n -> "storeDir".equals(n.attr("name"))).txt());
-
+    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));
     
     // assert that buildOnStartup=false
-    assertEquals("false",
-        h.getCore().getSolrConfig().get("searchComponent", n -> "suggest".equals(n.attr("name")))
-            .get("lst",2)
-            .get("str", n -> "buildOnStartup".equals(n.attr("name"))).txt());
-    assertEquals("true",
-        h.getCore().getSolrConfig().get("searchComponent", n -> "suggest".equals(n.attr("name")))
-            .get("lst",2).
-            get("str", n -> "buildOnCommit".equals(n.attr("name"))).txt());
+    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));
     
     // verify that this suggester is built (there was a commit in setUp)
     assertQ(req("qt", rh, 
@@ -497,21 +453,13 @@ 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().get("searchComponent", n -> "suggest".equals(n.attr("name")))
-            .get("lst",4)
-            .get("str", n -> "name".equals(n.attr("name"))).txt());
-    assertNull( h.getCore().getSolrConfig().get("searchComponent", n -> "suggest".equals(n.attr("name")))
-        .get("lst",4)
-        .get("str", n -> "storeDir".equals(n.attr("name"))).txt());
-    assertEquals("true",
-        h.getCore().getSolrConfig().get("searchComponent", n -> "suggest".equals(n.attr("name")))
-            .get("lst",4)
-            .get("str", n -> "buildOnStartup".equals(n.attr("name"))).txt());
-    assertEquals("false",
-        h.getCore().getSolrConfig().get("searchComponent", n -> "suggest".equals(n.attr("name")))
-            .get("lst",4)
-            .get("str", n -> "buildOnCommit".equals(n.attr("name"))).txt());
+    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));
     
     // 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 221dd07..4f43b10 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);
+    SolrIndexConfig solrIndexConfig = new SolrIndexConfig(solrConfig, null, 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);
+    SolrIndexConfig solrIndexConfig = new SolrIndexConfig(solrConfig, null, 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);
+    SolrIndexConfig solrIndexConfig = new SolrIndexConfig(solrConfig, null, 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);
+    SolrIndexConfig solrIndexConfig = new SolrIndexConfig(solrConfig, null, 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);
+    SolrIndexConfig solrIndexConfig = new SolrIndexConfig(solrConfig, null, 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);
+    SolrIndexConfig solrIndexConfig = new SolrIndexConfig(solrConfig, null, 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 885b11d..81da171 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,8 +20,6 @@ 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;
@@ -84,13 +82,4 @@ public interface SimpleMap<T> extends MapWriter {
   default void writeMap(EntryWriter ew) throws IOException {
     forEachEntry(ew::putNoEx);
   }
-
-  default Map<String, T> asMap( Map<String, T> sink) {
-    forEachEntry(sink::put);
-    return sink;
-  }
-
-  default Map<String, T> asMap() {
-    return asMap(new LinkedHashMap<String, T>());
-  }
 }
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 03dba32..1a67b52 100644
--- a/solr/solrj/src/java/org/apache/solr/common/ConfigNode.java
+++ b/solr/solrj/src/java/org/apache/solr/common/ConfigNode.java
@@ -17,23 +17,16 @@
 package org.apache.solr.common;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashSet;
 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
- * Please note that this is an immutable, read-only object.
  */
 public interface ConfigNode {
   ThreadLocal<Function<String,String>> SUBSTITUTES = new ThreadLocal<>();
@@ -44,6 +37,11 @@ public interface ConfigNode {
   String name();
 
   /**
+   * Text value of the node
+   */
+  String textValue();
+
+  /**
    * Attributes
    */
   SimpleMap<String> attributes();
@@ -55,56 +53,6 @@ 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 get(String name) {
-    ConfigNode child = child(null, name);
-    return child == null? EMPTY: child;
-  }
-  default ConfigNode get(String name, Predicate<ConfigNode> test) {
-    List<ConfigNode> children = getAll(test, name);
-    if(children.isEmpty()) return EMPTY;
-    return children.get(0);
-  }
-  default ConfigNode get(String name, int idx) {
-    List<ConfigNode> children = getAll(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 boolVal(boolean def) { return _bool(txt(),def); }
-  default int intVal(int def) { return _int(txt(), 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) {
-    String attr = attr(name);
-    if(attr == null && err != null) throw err.get();
-    return attr;
-  }
-  default int intAttr(String name, int def) { return _int(attr(name), def); }
-  default boolean boolAttr(String name, boolean def){ return _bool(attr(name), def); }
-  default String txt(String def) { return txt() == null ? def : txt();}
-  String txt() ;
-  default double doubleVal(double def){ return _double(txt(), def); }
   /**Iterate through child nodes with the name and return the first child that matches
    */
   default ConfigNode child(Predicate<ConfigNode> test, String name) {
@@ -124,15 +72,15 @@ public interface ConfigNode {
    * @param nodeNames names of tags to be returned
    * @param  test check for the nodes to be returned
    */
-  default List<ConfigNode> getAll(Predicate<ConfigNode> test, String... nodeNames) {
-    return getAll(test, nodeNames == null ? Collections.emptySet() : new HashSet<>(Arrays.asList(nodeNames)));
+  default List<ConfigNode> children(Predicate<ConfigNode> test, String... nodeNames) {
+    return children(test, nodeNames == null ? Collections.emptySet() : Set.of(nodeNames));
   }
 
   /**Iterate through child nodes with the names and return all the matching children
    * @param matchNames names of tags to be returned
    * @param  test check for the nodes to be returned
    */
-  default List<ConfigNode> getAll(Predicate<ConfigNode> test, Set<String> matchNames) {
+  default List<ConfigNode> children(Predicate<ConfigNode> test, Set<String> matchNames) {
     List<ConfigNode> result = new ArrayList<>();
     forEachChild(it -> {
       if (matchNames != null && !matchNames.isEmpty() && !matchNames.contains(it.name())) return Boolean.TRUE;
@@ -142,76 +90,15 @@ public interface ConfigNode {
     return result;
   }
 
-  default List<ConfigNode> getAll(String name) {
-    return getAll(null, Collections.singleton(name));
+  default List<ConfigNode> children(String name) {
+    return children(null, Collections.singleton(name));
   }
 
-  default boolean exists() { return true; }
-  default boolean isNull() { return false; }
-
   /** abortable iterate through children
    *
    * @param fun consume the node and return true to continue or false to abort
    */
   void forEachChild(Function<ConfigNode, Boolean> fun);
 
-  /**An empty node object.
-   * usually returned when the node is absent
-   *
-   */
-  ConfigNode EMPTY = new ConfigNode() {
-    @Override
-    public String name() {
-      return null;
-    }
-
-    @Override
-    public String txt() { 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 get(String name) {
-      return EMPTY;
-    }
-
-    public boolean exists() { return false; }
-
-    @Override
-    public boolean isNull() { return true; }
-
-    @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()); }
-    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/DOMUtil.java b/solr/solrj/src/java/org/apache/solr/common/util/DOMUtil.java
index 1fe7df2..d0b1f7b 100644
--- a/solr/solrj/src/java/org/apache/solr/common/util/DOMUtil.java
+++ b/solr/solrj/src/java/org/apache/solr/common/util/DOMUtil.java
@@ -17,9 +17,7 @@
 package org.apache.solr.common.util;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -41,7 +39,7 @@ public class DOMUtil {
 
   public static final String XML_RESERVED_PREFIX = "xml";
 
-  public static final Set<String>  NL_TAGS = new HashSet<>(Arrays.asList("str", "int", "long", "float", "double", "bool", "null"));
+  public static final Set<String>  NL_TAGS = Set.of("str", "int","long","float","double","bool");
 
 
   public static Map<String,String> toMap(NamedNodeMap attrs) {
@@ -214,8 +212,6 @@ public class DOMUtil {
         val = Double.valueOf(textValue);
       } else if ("bool".equals(type)) {
         val = StrUtils.parseBool(textValue);
-      } else if("null".equals(type)) {
-        val = null;
       }
       // :NOTE: Unexpected Node names are ignored
       // :TODO: should we generate an error here?
@@ -235,7 +231,7 @@ public class DOMUtil {
       String tag = it.name();
       String varName = it.attributes().get("name");
       if (NL_TAGS.contains(tag)) {
-        result.add(varName, parseVal(tag, varName, it.txt()));
+        result.add(varName, parseVal(tag, varName, it.textValue()));
       }
       if ("lst".equals(tag)) {
         result.add(varName, readNamedListChildren(it));
@@ -244,9 +240,7 @@ public class DOMUtil {
         result.add(varName, l);
         it.forEachChild(n -> {
           if (NL_TAGS.contains(n.name())) {
-            l.add(parseVal(n.name(), null, n.txt()));
-          } else if("lst".equals(n.name())){
-            l.add(readNamedListChildren(n));
+            l.add(parseVal(n.name(), null, n.textValue()));
           }
           return Boolean.TRUE;
         });
diff --git a/solr/solrj/src/java/org/apache/solr/common/util/LinkedSimpleHashMap.java b/solr/solrj/src/java/org/apache/solr/common/util/LinkedSimpleHashMap.java
index 6581c83..1fc6afc 100644
--- a/solr/solrj/src/java/org/apache/solr/common/util/LinkedSimpleHashMap.java
+++ b/solr/solrj/src/java/org/apache/solr/common/util/LinkedSimpleHashMap.java
@@ -19,7 +19,6 @@ package org.apache.solr.common.util;
 import org.apache.solr.cluster.api.SimpleMap;
 
 import java.util.LinkedHashMap;
-import java.util.Map;
 import java.util.function.BiConsumer;
 
 public class LinkedSimpleHashMap<T> extends LinkedHashMap<String, T>  implements SimpleMap<T> {
@@ -32,9 +31,4 @@ public class LinkedSimpleHashMap<T> extends LinkedHashMap<String, T>  implements
     public void forEachEntry(BiConsumer<String, ? super T> fun) {
 
     }
-
-    @Override
-    public Map<String, T> asMap() {
-        return this;
-    }
 }
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 ccf000b..e8f58a5 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,7 +19,6 @@ 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;
 
@@ -47,14 +46,4 @@ public class WrappedSimpleMap<T>  implements SimpleMap<T> {
         this.delegate = delegate;
     }
 
-  @Override
-  public Map<String, T> asMap(Map<String, T> sink) {
-    sink.putAll(delegate);
-    return sink;
-  }
-
-    @Override
-    public Map<String, T> asMap() {
-        return Collections.unmodifiableMap(delegate);
-    }
 }