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

[solr] 01/01: refactor QueryResponseWriters

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

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

commit 2d07feac9a48be3690bbbc75f08574a5947e39bb
Author: Noble Paul <no...@gmail.com>
AuthorDate: Wed Nov 16 13:06:50 2022 +1100

    refactor QueryResponseWriters
---
 .../java/org/apache/solr/core/CoreContainer.java   |   4 +-
 .../src/java/org/apache/solr/core/PluginBag.java   | 141 ++++-----------------
 .../org/apache/solr/core/RequestHandlerBag.java    | 125 ++++++++++++++++++
 .../java/org/apache/solr/core/RequestHandlers.java |   6 +-
 .../java/org/apache/solr/core/ResponseWriters.java | 117 +++++++++++++++++
 .../src/java/org/apache/solr/core/SolrCore.java    |  73 +----------
 .../org/apache/solr/filestore/PackageStoreAPI.java |   4 +-
 .../java/org/apache/solr/handler/BlobHandler.java  |  33 +++--
 .../java/org/apache/solr/handler/ClusterAPI.java   |  10 ++
 .../apache/solr/handler/ReplicationHandler.java    |   9 +-
 .../apache/solr/handler/export/ExportWriter.java   |   3 +-
 .../org/apache/solr/jersey/JerseyApplications.java |   9 +-
 .../org/apache/solr/jersey/MetricBeanFactory.java  |  14 +-
 .../apache/solr/jersey/RequestMetricHandling.java  |   7 +-
 .../java/org/apache/solr/servlet/HttpSolrCall.java |   6 +-
 .../processor/UpdateRequestProcessorChain.java     |   2 +-
 .../src/test/org/apache/solr/OutputWriterTest.java |   4 +-
 .../test/org/apache/solr/core/PluginBagTest.java   |  20 +--
 .../org/apache/solr/core/RequestHandlersTest.java  |   2 +-
 19 files changed, 342 insertions(+), 247 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index 00eb7454b8d..e47475103b0 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -187,8 +187,8 @@ public class CoreContainer {
     }
   }
 
-  private volatile PluginBag<SolrRequestHandler> containerHandlers =
-      new PluginBag<>(SolrRequestHandler.class, null);
+  private volatile RequestHandlerBag containerHandlers =
+      new RequestHandlerBag( null);
 
   private volatile ApplicationHandler jerseyAppHandler;
 
diff --git a/solr/core/src/java/org/apache/solr/core/PluginBag.java b/solr/core/src/java/org/apache/solr/core/PluginBag.java
index 5ecf681ad62..2cbc1a13a3f 100644
--- a/solr/core/src/java/org/apache/solr/core/PluginBag.java
+++ b/solr/core/src/java/org/apache/solr/core/PluginBag.java
@@ -16,9 +16,6 @@
  */
 package org.apache.solr.core;
 
-import static java.util.Collections.singletonMap;
-import static org.apache.solr.api.ApiBag.HANDLER_NAME;
-
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.util.Collection;
@@ -32,24 +29,17 @@ import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
-import org.apache.commons.collections4.CollectionUtils;
+
 import org.apache.lucene.util.ResourceLoader;
 import org.apache.lucene.util.ResourceLoaderAware;
-import org.apache.solr.api.Api;
-import org.apache.solr.api.ApiBag;
-import org.apache.solr.api.ApiSupport;
-import org.apache.solr.api.JerseyResource;
 import org.apache.solr.common.SolrException;
-import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.handler.RequestHandlerBase;
 import org.apache.solr.handler.component.SearchComponent;
-import org.apache.solr.jersey.JerseyApplications;
 import org.apache.solr.pkg.PackagePluginHolder;
 import org.apache.solr.request.SolrRequestHandler;
 import org.apache.solr.util.plugin.NamedListInitializedPlugin;
 import org.apache.solr.util.plugin.PluginInfoInitialized;
 import org.apache.solr.util.plugin.SolrCoreAware;
-import org.glassfish.jersey.server.ResourceConfig;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -57,34 +47,16 @@ import org.slf4j.LoggerFactory;
 public class PluginBag<T> implements AutoCloseable {
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
-  private final Map<String, PluginHolder<T>> registry;
-  private final Map<String, PluginHolder<T>> immutableRegistry;
+  protected Map<String, PluginHolder<T>> registry;
   private String def;
   private final Class<T> klass;
-  private SolrCore core;
+  protected SolrCore core;
   private final SolrConfig.SolrPluginInfo meta;
-  private final ApiBag apiBag;
-  private final ResourceConfig jerseyResources;
 
-  public static class JerseyMetricsLookupRegistry
-      extends HashMap<Class<? extends JerseyResource>, RequestHandlerBase> {}
-
-  private final JerseyMetricsLookupRegistry infoBeanByResource;
+  private Map<String, PluginHolder<T>> defaults;
 
   /** Pass needThreadSafety=true if plugins can be added and removed concurrently with lookups. */
   public PluginBag(Class<T> klass, SolrCore core, boolean needThreadSafety) {
-    if (klass == SolrRequestHandler.class) {
-      this.apiBag = new ApiBag(core != null);
-      this.infoBeanByResource = new JerseyMetricsLookupRegistry();
-      this.jerseyResources =
-          (core == null)
-              ? new JerseyApplications.CoreContainerApp(infoBeanByResource)
-              : new JerseyApplications.SolrCoreApp(core, infoBeanByResource);
-    } else {
-      this.apiBag = null;
-      this.jerseyResources = null;
-      this.infoBeanByResource = null;
-    }
     this.core = core;
     this.klass = klass;
     // TODO: since reads will dominate writes, we could also think about creating a new instance of
@@ -93,7 +65,6 @@ public class PluginBag<T> implements AutoCloseable {
     // We could also perhaps make this constructor into a factory method to return different
     // implementations depending on thread safety needs.
     this.registry = needThreadSafety ? new ConcurrentHashMap<>() : new HashMap<>();
-    this.immutableRegistry = Collections.unmodifiableMap(registry);
     meta = SolrConfig.classVsSolrPluginInfo.get(klass.getName());
     if (meta == null) {
       throw new SolrException(
@@ -101,6 +72,15 @@ public class PluginBag<T> implements AutoCloseable {
     }
   }
 
+  public PluginBag(Class<T> klass, SolrCore core, boolean needThreadSafety, Map<String,
+          PluginHolder<T>> defaults,SolrConfig.SolrPluginInfo pluginMetaData) {
+    this.core = core;
+    this.klass = klass;
+    this.registry = needThreadSafety ? new ConcurrentHashMap<>(0) : new HashMap<>(0);
+    this.meta = pluginMetaData;
+    this.defaults = defaults;
+  }
+
   /** Constructs a non-threadsafe plugin registry */
   public PluginBag(Class<T> klass, SolrCore core) {
     this(klass, core, false);
@@ -177,9 +157,14 @@ public class PluginBag<T> implements AutoCloseable {
   /** Get a plugin by name. If the plugin is not already instantiated, it is done here */
   public T get(String name) {
     PluginHolder<T> result = registry.get(name);
+    if(result == null && defaults != null) result = defaults.get(name);
     return result == null ? null : result.get();
   }
 
+  public PluginHolder<T> getHolder(String name) {
+    return registry.get(name);
+  }
+
   /**
    * Fetches a plugin by name , or the default
    *
@@ -193,7 +178,7 @@ public class PluginBag<T> implements AutoCloseable {
   }
 
   public Set<String> keySet() {
-    return immutableRegistry.keySet();
+    return registry.keySet();
   }
 
   /** register a plugin by a name */
@@ -207,61 +192,8 @@ public class PluginBag<T> implements AutoCloseable {
 
   @SuppressWarnings({"unchecked"})
   public PluginHolder<T> put(String name, PluginHolder<T> plugin) {
-    Boolean registerApi = null;
-    Boolean disableHandler = null;
-    if (plugin.pluginInfo != null) {
-      String registerAt = plugin.pluginInfo.attributes.get("registerPath");
-      if (registerAt != null) {
-        List<String> strs = StrUtils.splitSmart(registerAt, ',');
-        disableHandler = !strs.contains("/solr");
-        registerApi = strs.contains("/v2");
-      }
-    }
-
-    if (apiBag != null) {
-      if (plugin.isLoaded()) {
-        T inst = plugin.get();
-        if (inst instanceof ApiSupport) {
-          ApiSupport apiSupport = (ApiSupport) inst;
-          if (registerApi == null) registerApi = apiSupport.registerV2();
-          if (disableHandler == null) disableHandler = !apiSupport.registerV1();
-
-          if (registerApi) {
-            Collection<Api> apis = apiSupport.getApis();
-            if (apis != null) {
-              Map<String, String> nameSubstitutes = singletonMap(HANDLER_NAME, name);
-              for (Api api : apis) {
-                apiBag.register(api, nameSubstitutes);
-              }
-            }
-
-            // TODO Should we use <requestHandler name="/blah"> to override the path that each
-            //  resource registers under?
-            Collection<Class<? extends JerseyResource>> jerseyApis =
-                apiSupport.getJerseyResources();
-            if (!CollectionUtils.isEmpty(jerseyApis)) {
-              for (Class<? extends JerseyResource> jerseyClazz : jerseyApis) {
-                if (log.isDebugEnabled()) {
-                  log.debug("Registering jersey resource class: {}", jerseyClazz.getName());
-                }
-                jerseyResources.register(jerseyClazz);
-                // See MetricsBeanFactory javadocs for a better understanding of this resource->RH
-                // mapping
-                if (inst instanceof RequestHandlerBase) {
-                  infoBeanByResource.put(jerseyClazz, (RequestHandlerBase) inst);
-                }
-              }
-            }
-          }
-        }
-      } else {
-        if (registerApi != null && registerApi)
-          apiBag.registerLazy((PluginHolder<SolrRequestHandler>) plugin, plugin.pluginInfo);
-      }
-    }
-    if (disableHandler == null) disableHandler = Boolean.FALSE;
     PluginHolder<T> old = null;
-    if (!disableHandler) old = registry.put(name, plugin);
+    old = registry.put(name, plugin);
     if (plugin.pluginInfo != null && plugin.pluginInfo.isDefault()) setDefault(name);
     if (plugin.isLoaded()) registerMBean(plugin.get(), core, name);
     // old instance has been replaced - close it to prevent mem leaks
@@ -279,10 +211,6 @@ public class PluginBag<T> implements AutoCloseable {
     this.def = def;
   }
 
-  public Map<String, PluginHolder<T>> getRegistry() {
-    return immutableRegistry;
-  }
-
   public boolean contains(String name) {
     return registry.containsKey(name);
   }
@@ -327,9 +255,11 @@ public class PluginBag<T> implements AutoCloseable {
             infos.stream().map(i -> i.name).collect(Collectors.toList()));
       }
     }
-    for (Map.Entry<String, T> e : defaults.entrySet()) {
-      if (!contains(e.getKey())) {
-        put(e.getKey(), new PluginHolder<>(null, e.getValue()));
+    if(defaults != null) {
+      for (Map.Entry<String, T> e : defaults.entrySet()) {
+        if (!contains(e.getKey())) {
+          put(e.getKey(), new PluginHolder<>(null, e.getValue()));
+        }
       }
     }
   }
@@ -341,7 +271,7 @@ public class PluginBag<T> implements AutoCloseable {
     return result.isLoaded();
   }
 
-  private void registerMBean(Object inst, SolrCore core, String pluginKey) {
+  protected void registerMBean(Object inst, SolrCore core, String pluginKey) {
     if (core == null) return;
     if (inst instanceof SolrInfoBean) {
       SolrInfoBean mBean = (SolrInfoBean) inst;
@@ -398,7 +328,6 @@ public class PluginBag<T> implements AutoCloseable {
       return Optional.ofNullable(inst);
     }
 
-    @Override
     public T get() {
       return inst;
     }
@@ -436,7 +365,6 @@ public class PluginBag<T> implements AutoCloseable {
       return pluginInfo;
     }
 
-    @Override
     public String toString() {
       return String.valueOf(inst);
     }
@@ -518,21 +446,4 @@ public class PluginBag<T> implements AutoCloseable {
       return true;
     }
   }
-
-  public Api v2lookup(String path, String method, Map<String, String> parts) {
-    if (apiBag == null) {
-      throw new SolrException(
-          SolrException.ErrorCode.SERVER_ERROR,
-          "this should not happen, looking up for v2 API at the wrong place");
-    }
-    return apiBag.lookup(path, method, parts);
-  }
-
-  public ApiBag getApiBag() {
-    return apiBag;
-  }
-
-  public ResourceConfig getJerseyEndpoints() {
-    return jerseyResources;
-  }
 }
diff --git a/solr/core/src/java/org/apache/solr/core/RequestHandlerBag.java b/solr/core/src/java/org/apache/solr/core/RequestHandlerBag.java
new file mode 100644
index 00000000000..cfddb08e4a7
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/core/RequestHandlerBag.java
@@ -0,0 +1,125 @@
+package org.apache.solr.core;
+
+
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.solr.api.Api;
+import org.apache.solr.api.ApiBag;
+import org.apache.solr.api.ApiSupport;
+import org.apache.solr.api.JerseyResource;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.util.StrUtils;
+import org.apache.solr.handler.RequestHandlerBase;
+import org.apache.solr.request.SolrRequestHandler;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.invoke.MethodHandles;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static java.util.Collections.singletonMap;
+import static org.apache.solr.api.ApiBag.HANDLER_NAME;
+
+public class RequestHandlerBag extends PluginBag<SolrRequestHandler> {
+    private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+    private  ApiBag apiBag;
+    private  ResourceConfig jerseyResources;
+    private  JerseyMetricsLookupRegistry infoBeanByResource;
+
+    public RequestHandlerBag(SolrCore core) {
+        super(SolrRequestHandler.class, core, true);
+        this.apiBag = new ApiBag(core != null);
+    }
+
+    @Override
+    public PluginHolder<SolrRequestHandler> put(String name, PluginHolder<SolrRequestHandler> plugin) {
+        Boolean registerApi = null;
+        Boolean disableHandler = null;
+        if (plugin.pluginInfo != null) {
+            String registerAt = plugin.pluginInfo.attributes.get("registerPath");
+            if (registerAt != null) {
+                List<String> strs = StrUtils.splitSmart(registerAt, ',');
+                disableHandler = !strs.contains("/solr");
+                registerApi = strs.contains("/v2");
+            }
+        }
+
+        if (apiBag != null) {
+            if (plugin.isLoaded()) {
+                SolrRequestHandler inst = plugin.get();
+                if (inst instanceof ApiSupport) {
+                    ApiSupport apiSupport = (ApiSupport) inst;
+                    if (registerApi == null) registerApi = apiSupport.registerV2();
+                    if (disableHandler == null) disableHandler = !apiSupport.registerV1();
+
+                    if (registerApi) {
+                        Collection<Api> apis = apiSupport.getApis();
+                        if (apis != null) {
+                            Map<String, String> nameSubstitutes = singletonMap(HANDLER_NAME, name);
+                            for (Api api : apis) {
+                                apiBag.register(api, nameSubstitutes);
+                            }
+                        }
+
+                        // TODO Should we use <requestHandler name="/blah"> to override the path that each
+                        //  resource registers under?
+                        Collection<Class<? extends JerseyResource>> jerseyApis =
+                                apiSupport.getJerseyResources();
+                        if (!CollectionUtils.isEmpty(jerseyApis)) {
+                            for (Class<? extends JerseyResource> jerseyClazz : jerseyApis) {
+                                if (log.isDebugEnabled()) {
+                                    log.debug("Registering jersey resource class: {}", jerseyClazz.getName());
+                                }
+                                jerseyResources.register(jerseyClazz);
+                                // See MetricsBeanFactory javadocs for a better understanding of this resource->RH
+                                // mapping
+                                if (inst instanceof RequestHandlerBase) {
+                                    infoBeanByResource.put(jerseyClazz, (RequestHandlerBase) inst);
+                                }
+                            }
+                        }
+                    }
+                }
+            } else {
+                if (registerApi != null && registerApi)
+                    apiBag.registerLazy((PluginHolder<SolrRequestHandler>) plugin, plugin.pluginInfo);
+            }
+        }
+        PluginHolder<SolrRequestHandler> old = null;
+        if (disableHandler == null) disableHandler = Boolean.FALSE;
+        if (!disableHandler) old = registry.put(name, plugin);
+        if (plugin.pluginInfo != null && plugin.pluginInfo.isDefault()) setDefault(name);
+        if (plugin.isLoaded()) registerMBean(plugin.get(), core, name);
+        // old instance has been replaced - close it to prevent mem leaks
+        if (old != null && old != plugin) {
+            closeQuietly(old);
+        }
+        return old;
+    }
+
+    public Api v2lookup(String path, String method, Map<String, String> parts) {
+        if (apiBag == null) {
+            throw new SolrException(
+                    SolrException.ErrorCode.SERVER_ERROR,
+                    "this should not happen, looking up for v2 API at the wrong place");
+        }
+        return apiBag.lookup(path, method, parts);
+    }
+
+
+
+    public ApiBag getApiBag() {
+        return apiBag;
+    }
+
+    public ResourceConfig getJerseyEndpoints() {
+        return jerseyResources;
+    }
+
+    public static class JerseyMetricsLookupRegistry
+        extends HashMap<Class<? extends JerseyResource>, RequestHandlerBase> {}
+}
diff --git a/solr/core/src/java/org/apache/solr/core/RequestHandlers.java b/solr/core/src/java/org/apache/solr/core/RequestHandlers.java
index dca7c832e3f..4c2e7d15041 100644
--- a/solr/core/src/java/org/apache/solr/core/RequestHandlers.java
+++ b/solr/core/src/java/org/apache/solr/core/RequestHandlers.java
@@ -33,7 +33,7 @@ public final class RequestHandlers {
 
   private final SolrCore core;
 
-  final PluginBag<SolrRequestHandler> handlers;
+  final RequestHandlerBag handlers;
 
   /**
    * Trim the trailing '/' if it's there, and convert null to empty string.
@@ -51,7 +51,7 @@ public final class RequestHandlers {
     this.core = core;
     // we need a thread safe registry since methods like register are currently documented to be
     // thread safe.
-    handlers = new PluginBag<>(SolrRequestHandler.class, core, true);
+    handlers = new RequestHandlerBag( core);
   }
 
   /**
@@ -79,7 +79,7 @@ public final class RequestHandlers {
   }
 
   /** Returns an unmodifiable Map containing the registered handlers */
-  public PluginBag<SolrRequestHandler> getRequestHandlers() {
+  public RequestHandlerBag getRequestHandlers() {
     return handlers;
   }
 
diff --git a/solr/core/src/java/org/apache/solr/core/ResponseWriters.java b/solr/core/src/java/org/apache/solr/core/ResponseWriters.java
new file mode 100644
index 00000000000..8a11bb3390f
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/core/ResponseWriters.java
@@ -0,0 +1,117 @@
+/*
+ * 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 com.google.common.collect.ImmutableMap;
+import org.apache.solr.client.solrj.impl.BinaryResponseParser;
+import org.apache.solr.common.params.CommonParams;
+import org.apache.solr.handler.ReplicationHandler;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.*;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+public class ResponseWriters {
+    public static final SolrConfig.SolrPluginInfo info =  SolrConfig.classVsSolrPluginInfo.get(QueryResponseWriter.class.getName());
+    private static QueryResponseWriter standard;
+
+    public static QueryResponseWriter get(String name) {
+        return DEFAULT_RESPONSE_WRITERS.get(name);
+    }
+    public static QueryResponseWriter getOrDefault(String name){
+        QueryResponseWriter result = DEFAULT_RESPONSE_WRITERS.get(name);
+        return result==null? standard: result;
+
+    }
+
+    public static final Map<String, QueryResponseWriter> DEFAULT_RESPONSE_WRITERS;
+    public static final Map<String, PluginBag.PluginHolder<QueryResponseWriter>> DEFAULT_RESPONSE_WRITER_HOLDERS ;
+
+
+    static {
+        HashMap<String, QueryResponseWriter> m = new HashMap<>(15, 1);
+        m.put("xml", new XMLResponseWriter());
+        m.put(CommonParams.JSON, new JSONResponseWriter());
+        m.put("standard", m.get(CommonParams.JSON));
+        m.put("geojson", new GeoJSONResponseWriter());
+        m.put("graphml", new GraphMLResponseWriter());
+        m.put("python", new PythonResponseWriter());
+        m.put("php", new PHPResponseWriter());
+        m.put("phps", new PHPSerializedResponseWriter());
+        m.put("ruby", new RubyResponseWriter());
+        m.put("raw", new RawResponseWriter());
+        m.put(CommonParams.JAVABIN, new BinaryResponseWriter());
+        m.put("csv", new CSVResponseWriter());
+        m.put("schema.xml", new SchemaXmlResponseWriter());
+        m.put("smile", new SmileResponseWriter());
+        standard = m.get("standard");
+        m.put(ReplicationHandler.FILE_STREAM, getFileStreamWriter());
+        DEFAULT_RESPONSE_WRITERS = Collections.unmodifiableMap(m);
+        try {
+            m.put(
+                    "xlsx",
+                    (QueryResponseWriter)
+                            Class.forName("org.apache.solr.handler.extraction.XLSXResponseWriter")
+                                    .getConstructor()
+                                    .newInstance());
+        } catch (Exception e) {
+            // don't worry; extraction module not in class path
+        }
+        ImmutableMap.Builder<String, PluginBag.PluginHolder<QueryResponseWriter>> b = ImmutableMap.builder();
+        DEFAULT_RESPONSE_WRITERS.forEach((k, v) -> b.put(k, new PluginBag.PluginHolder<>(v, info)));
+        DEFAULT_RESPONSE_WRITER_HOLDERS = b.build();
+    }
+
+    private static BinaryResponseWriter getFileStreamWriter() {
+        return new BinaryResponseWriter() {
+            @Override
+            public void write(OutputStream out, SolrQueryRequest req, SolrQueryResponse response)
+                    throws IOException {
+                RawWriter rawWriter = (RawWriter) response.getValues().get(ReplicationHandler.FILE_STREAM);
+                if (rawWriter != null) {
+                    rawWriter.write(out);
+                    if (rawWriter instanceof Closeable) ((Closeable) rawWriter).close();
+                }
+            }
+
+            @Override
+            public String getContentType(SolrQueryRequest request, SolrQueryResponse response) {
+                RawWriter rawWriter = (RawWriter) response.getValues().get(ReplicationHandler.FILE_STREAM);
+                if (rawWriter != null) {
+                    return rawWriter.getContentType();
+                } else {
+                    return BinaryResponseParser.BINARY_CONTENT_TYPE;
+                }
+            }
+        };
+    }
+
+
+
+    public interface RawWriter {
+        default String getContentType() {
+            return BinaryResponseParser.BINARY_CONTENT_TYPE;
+        }
+
+        void write(OutputStream os) throws IOException;
+    }
+}
diff --git a/solr/core/src/java/org/apache/solr/core/SolrCore.java b/solr/core/src/java/org/apache/solr/core/SolrCore.java
index cf6e7d51cc9..8ff9255d221 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrCore.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrCore.java
@@ -17,6 +17,7 @@
 package org.apache.solr.core;
 
 import static org.apache.solr.common.params.CommonParams.PATH;
+import static org.apache.solr.core.ResponseWriters.DEFAULT_RESPONSE_WRITERS;
 
 import com.codahale.metrics.Counter;
 import com.codahale.metrics.Timer;
@@ -1997,7 +1998,7 @@ public class SolrCore implements SolrInfoBean, Closeable {
           && searchComponents.get(name) instanceof HighlightComponent) {
         if (!HighlightComponent.COMPONENT_NAME.equals(name)) {
           searchComponents.put(
-              HighlightComponent.COMPONENT_NAME, searchComponents.getRegistry().get(name));
+              HighlightComponent.COMPONENT_NAME, searchComponents.getHolder(name));
         }
         break;
       }
@@ -2991,84 +2992,20 @@ public class SolrCore implements SolrInfoBean, Closeable {
   public PluginBag<QueryResponseWriter> getResponseWriters() {
     return responseWriters;
   }
-
   private final PluginBag<QueryResponseWriter> responseWriters =
-      new PluginBag<>(QueryResponseWriter.class, this);
-  public static final Map<String, QueryResponseWriter> DEFAULT_RESPONSE_WRITERS;
-
-  static {
-    HashMap<String, QueryResponseWriter> m = new HashMap<>(15, 1);
-    m.put("xml", new XMLResponseWriter());
-    m.put(CommonParams.JSON, new JSONResponseWriter());
-    m.put("standard", m.get(CommonParams.JSON));
-    m.put("geojson", new GeoJSONResponseWriter());
-    m.put("graphml", new GraphMLResponseWriter());
-    m.put("python", new PythonResponseWriter());
-    m.put("php", new PHPResponseWriter());
-    m.put("phps", new PHPSerializedResponseWriter());
-    m.put("ruby", new RubyResponseWriter());
-    m.put("raw", new RawResponseWriter());
-    m.put(CommonParams.JAVABIN, new BinaryResponseWriter());
-    m.put("csv", new CSVResponseWriter());
-    m.put("schema.xml", new SchemaXmlResponseWriter());
-    m.put("smile", new SmileResponseWriter());
-    m.put(ReplicationHandler.FILE_STREAM, getFileStreamWriter());
-    DEFAULT_RESPONSE_WRITERS = Collections.unmodifiableMap(m);
-    try {
-      m.put(
-          "xlsx",
-          (QueryResponseWriter)
-              Class.forName("org.apache.solr.handler.extraction.XLSXResponseWriter")
-                  .getConstructor()
-                  .newInstance());
-    } catch (Exception e) {
-      // don't worry; extraction module not in class path
-    }
-  }
-
-  private static BinaryResponseWriter getFileStreamWriter() {
-    return new BinaryResponseWriter() {
-      @Override
-      public void write(OutputStream out, SolrQueryRequest req, SolrQueryResponse response)
-          throws IOException {
-        RawWriter rawWriter = (RawWriter) response.getValues().get(ReplicationHandler.FILE_STREAM);
-        if (rawWriter != null) {
-          rawWriter.write(out);
-          if (rawWriter instanceof Closeable) ((Closeable) rawWriter).close();
-        }
-      }
-
-      @Override
-      public String getContentType(SolrQueryRequest request, SolrQueryResponse response) {
-        RawWriter rawWriter = (RawWriter) response.getValues().get(ReplicationHandler.FILE_STREAM);
-        if (rawWriter != null) {
-          return rawWriter.getContentType();
-        } else {
-          return BinaryResponseParser.BINARY_CONTENT_TYPE;
-        }
-      }
-    };
-  }
-
+          new PluginBag<>(QueryResponseWriter.class, this, false,
+                  ResponseWriters.DEFAULT_RESPONSE_WRITER_HOLDERS, ResponseWriters.info);
   public void fetchLatestSchema() {
     IndexSchema schema = configSet.getIndexSchema(true);
     setLatestSchema(schema);
   }
 
-  public interface RawWriter {
-    default String getContentType() {
-      return BinaryResponseParser.BINARY_CONTENT_TYPE;
-    }
-
-    void write(OutputStream os) throws IOException;
-  }
-
   /**
    * Configure the query response writers. There will always be a default writer; additional writers
    * may also be configured.
    */
   private void initWriters() {
-    responseWriters.init(DEFAULT_RESPONSE_WRITERS, this);
+    responseWriters.init(null, this);
     // configure the default response writer; this one should never be null
     if (responseWriters.getDefault() == null) responseWriters.setDefault("standard");
   }
diff --git a/solr/core/src/java/org/apache/solr/filestore/PackageStoreAPI.java b/solr/core/src/java/org/apache/solr/filestore/PackageStoreAPI.java
index 0a98bba9a2f..d5985e06f2c 100644
--- a/solr/core/src/java/org/apache/solr/filestore/PackageStoreAPI.java
+++ b/solr/core/src/java/org/apache/solr/filestore/PackageStoreAPI.java
@@ -46,7 +46,7 @@ import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.common.util.Utils;
 import org.apache.solr.core.BlobRepository;
 import org.apache.solr.core.CoreContainer;
-import org.apache.solr.core.SolrCore;
+import org.apache.solr.core.ResponseWriters;
 import org.apache.solr.pkg.PackageAPI;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
@@ -348,7 +348,7 @@ public class PackageStoreAPI {
       req.setParams(SolrParams.wrapDefaults(solrParams, req.getParams()));
       rsp.add(
           FILE_STREAM,
-          (SolrCore.RawWriter)
+          (ResponseWriters.RawWriter)
               os ->
                   packageStore.get(
                       path,
diff --git a/solr/core/src/java/org/apache/solr/handler/BlobHandler.java b/solr/core/src/java/org/apache/solr/handler/BlobHandler.java
index 3bb14b19ed4..789be7117be 100644
--- a/solr/core/src/java/org/apache/solr/handler/BlobHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/BlobHandler.java
@@ -53,6 +53,7 @@ import org.apache.solr.common.util.ContentStream;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.core.PluginInfo;
+import org.apache.solr.core.ResponseWriters;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.handler.admin.api.GetBlobInfoAPI;
 import org.apache.solr.handler.admin.api.UploadBlobAPI;
@@ -210,24 +211,20 @@ public class BlobHandler extends RequestHandlerBase
           if (docs.totalHits.value > 0) {
             rsp.add(
                 ReplicationHandler.FILE_STREAM,
-                new SolrCore.RawWriter() {
-
-                  @Override
-                  public void write(OutputStream os) throws IOException {
-                    Document doc = req.getSearcher().doc(docs.scoreDocs[0].doc);
-                    IndexableField sf = doc.getField("blob");
-                    FieldType fieldType = req.getSchema().getField("blob").getType();
-                    ByteBuffer buf = (ByteBuffer) fieldType.toObject(sf);
-                    if (buf == null) {
-                      // should never happen unless a user wrote this document directly
-                      throw new SolrException(
-                          SolrException.ErrorCode.NOT_FOUND,
-                          "Invalid document . No field called blob");
-                    } else {
-                      os.write(buf.array(), buf.arrayOffset(), buf.limit());
-                    }
-                  }
-                });
+                    (ResponseWriters.RawWriter) os -> {
+                      Document doc = req.getSearcher().doc(docs.scoreDocs[0].doc);
+                      IndexableField sf = doc.getField("blob");
+                      FieldType fieldType = req.getSchema().getField("blob").getType();
+                      ByteBuffer buf = (ByteBuffer) fieldType.toObject(sf);
+                      if (buf == null) {
+                        // should never happen unless a user wrote this document directly
+                        throw new SolrException(
+                            SolrException.ErrorCode.NOT_FOUND,
+                            "Invalid document . No field called blob");
+                      } else {
+                        os.write(buf.array(), buf.arrayOffset(), buf.limit());
+                      }
+                    });
 
           } else {
             throw new SolrException(
diff --git a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
index 1a56f7980d5..236159bbe0a 100644
--- a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
@@ -77,6 +77,16 @@ public class ClusterAPI {
     this.configSetsHandler = configSetsHandler;
   }
 
+  @EndPoint(method = GET, path = "/node/heap", permission = COLL_READ_PERM)
+  public void heap(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
+    Runtime rt = Runtime.getRuntime();
+    if(req.getParams().getBool("gc", false)) {
+      rt.gc();
+    }
+    rsp.add("heap",  rt.totalMemory() - rt.freeMemory());
+  }
+
+
   @EndPoint(method = GET, path = "/cluster/node-roles", permission = COLL_READ_PERM)
   public void roles(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
     rsp.add(
diff --git a/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java b/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java
index 7cae4115598..29b0e47aaa7 100644
--- a/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java
@@ -83,13 +83,8 @@ import org.apache.solr.common.util.SimpleOrderedMap;
 import org.apache.solr.common.util.SolrNamedThreadFactory;
 import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.common.util.SuppressForbidden;
-import org.apache.solr.core.CloseHook;
-import org.apache.solr.core.CoreContainer;
+import org.apache.solr.core.*;
 import org.apache.solr.core.DirectoryFactory.DirContext;
-import org.apache.solr.core.IndexDeletionPolicyWrapper;
-import org.apache.solr.core.SolrCore;
-import org.apache.solr.core.SolrDeletionPolicy;
-import org.apache.solr.core.SolrEventListener;
 import org.apache.solr.core.backup.repository.BackupRepository;
 import org.apache.solr.core.backup.repository.LocalFileSystemRepository;
 import org.apache.solr.handler.IndexFetcher.IndexFetchResult;
@@ -1582,7 +1577,7 @@ public class ReplicationHandler extends RequestHandlerBase implements SolrCoreAw
   }
 
   /** This class is used to read and send files in the lucene index */
-  private class DirectoryFileStream implements SolrCore.RawWriter {
+  private class DirectoryFileStream implements ResponseWriters.RawWriter {
     protected SolrParams params;
 
     protected FastOutputStream fos;
diff --git a/solr/core/src/java/org/apache/solr/handler/export/ExportWriter.java b/solr/core/src/java/org/apache/solr/handler/export/ExportWriter.java
index cc1599562a3..d3764c1088b 100644
--- a/solr/core/src/java/org/apache/solr/handler/export/ExportWriter.java
+++ b/solr/core/src/java/org/apache/solr/handler/export/ExportWriter.java
@@ -55,6 +55,7 @@ import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.params.StreamParams;
 import org.apache.solr.common.util.JavaBinCodec;
+import org.apache.solr.core.ResponseWriters;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.metrics.SolrMetricsContext;
 import org.apache.solr.request.SolrQueryRequest;
@@ -91,7 +92,7 @@ import org.slf4j.LoggerFactory;
  * across the wire) and marked as sent (unset in the bitmap). This process repeats until all
  * matching documents have been sent.
  */
-public class ExportWriter implements SolrCore.RawWriter, Closeable {
+public class ExportWriter implements ResponseWriters.RawWriter, Closeable {
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
   public static final String BATCH_SIZE_PARAM = "batchSize";
diff --git a/solr/core/src/java/org/apache/solr/jersey/JerseyApplications.java b/solr/core/src/java/org/apache/solr/jersey/JerseyApplications.java
index 7a4bb484e4b..dc613dbe958 100644
--- a/solr/core/src/java/org/apache/solr/jersey/JerseyApplications.java
+++ b/solr/core/src/java/org/apache/solr/jersey/JerseyApplications.java
@@ -21,7 +21,8 @@ import io.swagger.v3.oas.annotations.OpenAPIDefinition;
 import io.swagger.v3.oas.annotations.info.Info;
 import io.swagger.v3.oas.annotations.info.License;
 import javax.inject.Singleton;
-import org.apache.solr.core.PluginBag;
+
+import org.apache.solr.core.RequestHandlerBag;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
@@ -44,7 +45,7 @@ import org.glassfish.jersey.server.ResourceConfig;
 public class JerseyApplications {
 
   public static class CoreContainerApp extends ResourceConfig {
-    public CoreContainerApp(PluginBag.JerseyMetricsLookupRegistry beanRegistry) {
+    public CoreContainerApp(RequestHandlerBag.JerseyMetricsLookupRegistry beanRegistry) {
       super();
 
       // Authentication and authorization
@@ -68,7 +69,7 @@ public class JerseyApplications {
             @Override
             protected void configure() {
               bindFactory(new MetricBeanFactory(beanRegistry))
-                  .to(PluginBag.JerseyMetricsLookupRegistry.class)
+                  .to(RequestHandlerBag.JerseyMetricsLookupRegistry.class)
                   .in(Singleton.class);
             }
           });
@@ -102,7 +103,7 @@ public class JerseyApplications {
 
   public static class SolrCoreApp extends CoreContainerApp {
 
-    public SolrCoreApp(SolrCore solrCore, PluginBag.JerseyMetricsLookupRegistry beanRegistry) {
+    public SolrCoreApp(SolrCore solrCore, RequestHandlerBag.JerseyMetricsLookupRegistry beanRegistry) {
       super(beanRegistry);
 
       // Dependency Injection for Jersey resources
diff --git a/solr/core/src/java/org/apache/solr/jersey/MetricBeanFactory.java b/solr/core/src/java/org/apache/solr/jersey/MetricBeanFactory.java
index c23851359d9..9821cd51f87 100644
--- a/solr/core/src/java/org/apache/solr/jersey/MetricBeanFactory.java
+++ b/solr/core/src/java/org/apache/solr/jersey/MetricBeanFactory.java
@@ -17,7 +17,7 @@
 
 package org.apache.solr.jersey;
 
-import org.apache.solr.core.PluginBag;
+import org.apache.solr.core.RequestHandlerBag;
 import org.glassfish.hk2.api.Factory;
 
 /**
@@ -26,7 +26,7 @@ import org.glassfish.hk2.api.Factory;
  * <p>Currently, Jersey resources that have a corresponding v1 API produce the same metrics as their
  * v1 equivalent and rely on the v1 requestHandler instance to do so. Solr facilitates this by
  * building a map of the Jersey resource to requestHandler mapping (a {@link
- * org.apache.solr.core.PluginBag.JerseyMetricsLookupRegistry}), and injecting it into the pre- and
+ * RequestHandlerBag.JerseyMetricsLookupRegistry}), and injecting it into the pre- and
  * post- Jersey filters that handle metrics.
  *
  * <p>This isn't ideal, as requestHandler's don't really "fit" conceptually here. But it's
@@ -35,21 +35,21 @@ import org.glassfish.hk2.api.Factory;
  * @see RequestMetricHandling.PreRequestMetricsFilter
  * @see RequestMetricHandling.PostRequestMetricsFilter
  */
-public class MetricBeanFactory implements Factory<PluginBag.JerseyMetricsLookupRegistry> {
+public class MetricBeanFactory implements Factory<RequestHandlerBag.JerseyMetricsLookupRegistry> {
 
-  private final PluginBag.JerseyMetricsLookupRegistry metricsLookupRegistry;
+  private final RequestHandlerBag.JerseyMetricsLookupRegistry metricsLookupRegistry;
 
-  public MetricBeanFactory(PluginBag.JerseyMetricsLookupRegistry metricsLookupRegistry) {
+  public MetricBeanFactory(RequestHandlerBag.JerseyMetricsLookupRegistry metricsLookupRegistry) {
     this.metricsLookupRegistry = metricsLookupRegistry;
   }
 
   @Override
-  public PluginBag.JerseyMetricsLookupRegistry provide() {
+  public RequestHandlerBag.JerseyMetricsLookupRegistry provide() {
     return metricsLookupRegistry;
   }
 
   @Override
-  public void dispose(PluginBag.JerseyMetricsLookupRegistry instance) {
+  public void dispose(RequestHandlerBag.JerseyMetricsLookupRegistry instance) {
     /* No-op */
   }
 }
diff --git a/solr/core/src/java/org/apache/solr/jersey/RequestMetricHandling.java b/solr/core/src/java/org/apache/solr/jersey/RequestMetricHandling.java
index 595027005ad..b4543a2084b 100644
--- a/solr/core/src/java/org/apache/solr/jersey/RequestMetricHandling.java
+++ b/solr/core/src/java/org/apache/solr/jersey/RequestMetricHandling.java
@@ -31,7 +31,8 @@ import javax.ws.rs.container.ContainerResponseContext;
 import javax.ws.rs.container.ContainerResponseFilter;
 import javax.ws.rs.container.ResourceInfo;
 import javax.ws.rs.core.Context;
-import org.apache.solr.core.PluginBag;
+
+import org.apache.solr.core.RequestHandlerBag;
 import org.apache.solr.handler.RequestHandlerBase;
 import org.apache.solr.request.SolrQueryRequest;
 import org.slf4j.Logger;
@@ -58,10 +59,10 @@ public class RequestMetricHandling {
 
     @Context private ResourceInfo resourceInfo;
 
-    private PluginBag.JerseyMetricsLookupRegistry beanRegistry;
+    private RequestHandlerBag.JerseyMetricsLookupRegistry beanRegistry;
 
     @Inject
-    public PreRequestMetricsFilter(PluginBag.JerseyMetricsLookupRegistry beanRegistry) {
+    public PreRequestMetricsFilter(RequestHandlerBag.JerseyMetricsLookupRegistry beanRegistry) {
       this.beanRegistry = beanRegistry;
     }
 
diff --git a/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java b/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java
index debd481a9a3..692dde2f8d6 100644
--- a/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java
+++ b/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java
@@ -101,6 +101,7 @@ import org.apache.solr.common.util.TimeSource;
 import org.apache.solr.common.util.Utils;
 import org.apache.solr.common.util.ValidatingJsonMap;
 import org.apache.solr.core.CoreContainer;
+import org.apache.solr.core.ResponseWriters;
 import org.apache.solr.core.SolrConfig;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.handler.ContentStreamHandlerBase;
@@ -876,7 +877,7 @@ public class HttpSolrCall {
       }
     }
     QueryResponseWriter respWriter =
-        SolrCore.DEFAULT_RESPONSE_WRITERS.get(solrReq.getParams().get(CommonParams.WT));
+        ResponseWriters.get(solrReq.getParams().get(CommonParams.WT));
     if (respWriter == null) respWriter = getResponseWriter();
     writeResponse(solrResp, respWriter, Method.getMethod(req.getMethod()));
     if (shouldAudit()) {
@@ -905,8 +906,7 @@ public class HttpSolrCall {
     if (core != null) {
       return core.getQueryResponseWriter(wt);
     } else {
-      return SolrCore.DEFAULT_RESPONSE_WRITERS.getOrDefault(
-          wt, SolrCore.DEFAULT_RESPONSE_WRITERS.get("standard"));
+      return ResponseWriters.getOrDefault(wt);
     }
   }
 
diff --git a/solr/core/src/java/org/apache/solr/update/processor/UpdateRequestProcessorChain.java b/solr/core/src/java/org/apache/solr/update/processor/UpdateRequestProcessorChain.java
index 1f5476632f8..e0bfc06c371 100644
--- a/solr/core/src/java/org/apache/solr/update/processor/UpdateRequestProcessorChain.java
+++ b/solr/core/src/java/org/apache/solr/update/processor/UpdateRequestProcessorChain.java
@@ -302,7 +302,7 @@ public final class UpdateRequestProcessorChain implements PluginInfoInitialized
       if (s.isEmpty()) continue;
       UpdateRequestProcessorFactory p = null;
       PluginBag.PluginHolder<UpdateRequestProcessorFactory> holder =
-          core.getUpdateProcessors().getRegistry().get(s);
+          core.getUpdateProcessors().getHolder(s);
       if (holder instanceof PackagePluginHolder) {
         p = new LazyUpdateRequestProcessorFactory(holder);
       } else {
diff --git a/solr/core/src/test/org/apache/solr/OutputWriterTest.java b/solr/core/src/test/org/apache/solr/OutputWriterTest.java
index 13eb69d9014..471471b3e41 100644
--- a/solr/core/src/test/org/apache/solr/OutputWriterTest.java
+++ b/solr/core/src/test/org/apache/solr/OutputWriterTest.java
@@ -67,10 +67,10 @@ public class OutputWriterTest extends SolrTestCaseJ4 {
 
   public void testLazy() {
     PluginBag.PluginHolder<QueryResponseWriter> qrw =
-        h.getCore().getResponseWriters().getRegistry().get("useless");
+        h.getCore().getResponseWriters().getHolder("useless");
     assertTrue("Should be a lazy class", qrw instanceof PluginBag.LazyPluginHolder);
 
-    qrw = h.getCore().getResponseWriters().getRegistry().get("xml");
+    qrw = h.getCore().getResponseWriters().getHolder("xml");
     assertTrue("Should not be a lazy class", qrw.isLoaded());
     assertSame("Should not be a lazy class", qrw.getClass(), PluginBag.PluginHolder.class);
   }
diff --git a/solr/core/src/test/org/apache/solr/core/PluginBagTest.java b/solr/core/src/test/org/apache/solr/core/PluginBagTest.java
index f0281038197..c0a011bc67d 100644
--- a/solr/core/src/test/org/apache/solr/core/PluginBagTest.java
+++ b/solr/core/src/test/org/apache/solr/core/PluginBagTest.java
@@ -49,14 +49,14 @@ public class PluginBagTest extends SolrTestCaseJ4 {
 
   @Test
   public void testOnlyInitsJerseyIfHoldingRequestHandlers() {
-    final PluginBag<SearchComponent> nonRequestHandlerBag =
-        new PluginBag<>(SearchComponent.class, null);
+    final RequestHandlerBag nonRequestHandlerBag =
+        new RequestHandlerBag(null);
     assertNull(
         "Jersey app should not be created for plugin bags that aren't managing RequestHandler's",
         nonRequestHandlerBag.getJerseyEndpoints());
 
-    final PluginBag<SolrRequestHandler> handlerPluginBag =
-        new PluginBag<>(SolrRequestHandler.class, null);
+    final RequestHandlerBag handlerPluginBag =
+        new RequestHandlerBag(null);
     assertNotNull(
         "Jersey app should be created for plugin bags that manage RequestHandlers",
         handlerPluginBag.getJerseyEndpoints());
@@ -64,16 +64,16 @@ public class PluginBagTest extends SolrTestCaseJ4 {
 
   @Test
   public void testCreatesCoreSpecificJerseyAppIfCoreProvided() {
-    final PluginBag<SolrRequestHandler> handlerPluginBag =
-        new PluginBag<>(SolrRequestHandler.class, solrCore);
+    final RequestHandlerBag handlerPluginBag =
+        new RequestHandlerBag(solrCore);
     assertEquals(
         JerseyApplications.SolrCoreApp.class, handlerPluginBag.getJerseyEndpoints().getClass());
   }
 
   @Test
   public void testCreatesContainerSpecificJerseyAppIfNoCoreProvided() {
-    final PluginBag<SolrRequestHandler> handlerPluginBag =
-        new PluginBag<>(SolrRequestHandler.class, null);
+    final RequestHandlerBag handlerPluginBag =
+        new RequestHandlerBag(null);
     assertEquals(
         JerseyApplications.CoreContainerApp.class,
         handlerPluginBag.getJerseyEndpoints().getClass());
@@ -81,8 +81,8 @@ public class PluginBagTest extends SolrTestCaseJ4 {
 
   @Test
   public void testRegistersJerseyResourcesAssociatedWithRequestHandlers() {
-    final PluginBag<SolrRequestHandler> handlerPluginBag =
-        new PluginBag<>(SolrRequestHandler.class, null);
+    final RequestHandlerBag handlerPluginBag =
+        new RequestHandlerBag(null);
     assertFalse(handlerPluginBag.getJerseyEndpoints().isRegistered(ListConfigSetsAPI.class));
 
     handlerPluginBag.put("/foo", new ConfigSetsHandler(coreContainer));
diff --git a/solr/core/src/test/org/apache/solr/core/RequestHandlersTest.java b/solr/core/src/test/org/apache/solr/core/RequestHandlersTest.java
index 7ac9d6a107e..42fd4a466ab 100644
--- a/solr/core/src/test/org/apache/solr/core/RequestHandlersTest.java
+++ b/solr/core/src/test/org/apache/solr/core/RequestHandlersTest.java
@@ -52,7 +52,7 @@ public class RequestHandlersTest extends SolrTestCaseJ4 {
   public void testLazyLoading() {
     SolrCore core = h.getCore();
     PluginBag.PluginHolder<SolrRequestHandler> handler =
-        core.getRequestHandlers().getRegistry().get("/lazy");
+        core.getRequestHandlers().getHolder("/lazy");
     assertFalse(handler.isLoaded());
 
     assertU(