You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by no...@apache.org on 2019/07/21 18:21:17 UTC

[lucene-solr] branch jira/solr-13637_1 updated: SOLR-13637: commit woth tests

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

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


The following commit(s) were added to refs/heads/jira/solr-13637_1 by this push:
     new ff89417  SOLR-13637: commit woth tests
ff89417 is described below

commit ff8941728331b8ec35770bc62365cdeb2096c9de
Author: noble <no...@apache.org>
AuthorDate: Sun Jul 21 11:20:56 2019 -0700

    SOLR-13637: commit woth tests
---
 solr/core/src/java/org/apache/solr/api/ApiBag.java |  21 ++--
 .../src/java/org/apache/solr/core/LibListener.java |   8 +-
 .../java/org/apache/solr/core/MemClassLoader.java  |   1 +
 .../src/java/org/apache/solr/core/PluginBag.java   | 117 ++++++++++++++----
 .../src/java/org/apache/solr/core/PluginInfo.java  |  25 +++-
 .../src/java/org/apache/solr/core/SolrCore.java    |  16 ++-
 .../org/apache/solr/handler/SolrConfigHandler.java |  17 ++-
 .../processor/UpdateRequestProcessorChain.java     |  16 +--
 .../src/java/org/apache/solr/util/CryptoKeys.java  |   4 +-
 .../solr/handler/TestContainerReqHandler.java      | 135 ++++++++++++++++++++-
 .../apache/solr/update/processor/RuntimeUrp.java   |   2 +-
 .../org/apache/solr/client/solrj/SolrResponse.java |   9 +-
 .../apache/solr/common/util/CommandOperation.java  |   9 +-
 .../java/org/apache/solr/common/util/Utils.java    |   7 ++
 ...nfig.Commands.addRequestHandler.properties.json |   2 +-
 .../apispec/core.config.Commands.generic.json      |   2 +-
 16 files changed, 327 insertions(+), 64 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/api/ApiBag.java b/solr/core/src/java/org/apache/solr/api/ApiBag.java
index 8a3f972..5692f31 100644
--- a/solr/core/src/java/org/apache/solr/api/ApiBag.java
+++ b/solr/core/src/java/org/apache/solr/api/ApiBag.java
@@ -230,16 +230,21 @@ public class ApiBag {
   }
 
   public static class ReqHandlerToApi extends Api implements PermissionNameProvider {
-    SolrRequestHandler rh;
+     PluginBag.PluginHolder<SolrRequestHandler> rh;
 
     public ReqHandlerToApi(SolrRequestHandler rh, SpecProvider spec) {
       super(spec);
+      this.rh = new PluginBag.PluginHolder(new PluginInfo(SolrRequestHandler.TYPE, Collections.emptyMap()),rh );
+    }
+
+    public ReqHandlerToApi(PluginBag.PluginHolder<SolrRequestHandler> rh, SpecProvider spec) {
+      super(spec);
       this.rh = rh;
     }
 
     @Override
     public void call(SolrQueryRequest req, SolrQueryResponse rsp) {
-      rh.handleRequest(req, rsp);
+      rh.get().handleRequest(req, rsp);
     }
 
     @Override
@@ -339,22 +344,22 @@ public class ApiBag {
   }
 
   public static class LazyLoadedApi extends Api {
-
-    private final PluginBag.PluginHolder<SolrRequestHandler> holder;
     private Api delegate;
 
     protected LazyLoadedApi(SpecProvider specProvider, PluginBag.PluginHolder<SolrRequestHandler> lazyPluginHolder) {
       super(specProvider);
-      this.holder = lazyPluginHolder;
+      delegate =  new ReqHandlerToApi(lazyPluginHolder, spec);
     }
 
     @Override
     public void call(SolrQueryRequest req, SolrQueryResponse rsp) {
-      if (!holder.isLoaded()) {
-        delegate = new ReqHandlerToApi(holder.get(), ApiBag.EMPTY_SPEC);
-      }
       delegate.call(req, rsp);
     }
+
+    @Override
+    public ValidatingJsonMap getSpec() {
+      return super.getSpec();
+    }
   }
 
 }
diff --git a/solr/core/src/java/org/apache/solr/core/LibListener.java b/solr/core/src/java/org/apache/solr/core/LibListener.java
index cc3f4eb..463ad21 100644
--- a/solr/core/src/java/org/apache/solr/core/LibListener.java
+++ b/solr/core/src/java/org/apache/solr/core/LibListener.java
@@ -28,6 +28,7 @@ import java.util.Objects;
 import java.util.stream.Collectors;
 
 import com.google.common.collect.ImmutableMap;
+import org.apache.lucene.analysis.util.ResourceLoader;
 import org.apache.solr.api.Api;
 import org.apache.solr.api.V2HttpCall;
 import org.apache.solr.common.IteratorWriter;
@@ -102,6 +103,9 @@ public class LibListener implements ClusterPropertiesListener {
     log.info("clusterprops.json changed , version {}", coreContainer.getZkController().getZkStateReader().getClusterPropsVersion());
     boolean forceReload = updateRuntimeLibs(properties);
     extHandler.updateReqHandlers(properties, forceReload);
+    for (SolrCore core : coreContainer.solrCores.getCores()) {
+      core.globalClassLoaderChanged();
+    }
     myversion = coreContainer.getZkController().getZkStateReader().getClusterPropsVersion();
     return false;
   }
@@ -130,7 +134,9 @@ public class LibListener implements ClusterPropertiesListener {
     }
     return needsReload[0];
   }
-
+  public ResourceLoader getResourceLoader() {
+    return memClassLoader == null ? coreContainer.getResourceLoader() : memClassLoader;
+  }
   void createNewClassLoader(Map m) {
     boolean[] loadedAll = new boolean[1];
     loadedAll[0] = true;
diff --git a/solr/core/src/java/org/apache/solr/core/MemClassLoader.java b/solr/core/src/java/org/apache/solr/core/MemClassLoader.java
index 7bba195..5c1b3e1 100644
--- a/solr/core/src/java/org/apache/solr/core/MemClassLoader.java
+++ b/solr/core/src/java/org/apache/solr/core/MemClassLoader.java
@@ -180,6 +180,7 @@ public class MemClassLoader extends ClassLoader implements AutoCloseable, Resour
     try {
       return findClass(cname).asSubclass(expectedType);
     } catch (Exception e) {
+      log.error("Error loading class from runtime libs ", e);
       if (e instanceof SolrException) {
         throw (SolrException) e;
       } else {
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 73ec51c..b5271b0 100644
--- a/solr/core/src/java/org/apache/solr/core/PluginBag.java
+++ b/solr/core/src/java/org/apache/solr/core/PluginBag.java
@@ -115,24 +115,36 @@ public class PluginBag<T> implements AutoCloseable {
     return result;
   }
 
-  public PluginHolder<T> createPlugin(PluginInfo info) {
-    if ("true".equals(String.valueOf(info.attributes.get(RuntimeLib.TYPE)))) {
-      log.debug(" {} : '{}'  created with runtimeLib=true ", meta.getCleanTag(), info.name);
-      LazyPluginHolder<T> holder = new LazyPluginHolder<>(meta, info, core, RuntimeLib.isEnabled() ?
-          core.getMemClassLoader() :
-          core.getResourceLoader(), true);
+  private static <T> T createInitInstance(PluginInfo pluginInfo, SolrConfig.SolrPluginInfo pluginMeta,
+                                          SolrCore core, ResourceLoader resourceLoader,
+                                          boolean isRuntimeLib) {
+    T localInst = null;
+    try {
+      localInst = (T) core.createInstance(pluginInfo.className, pluginMeta.clazz, pluginMeta.getCleanTag(), core, resourceLoader);
+    } catch (SolrException e) {
+      if (isRuntimeLib && !(resourceLoader instanceof MemClassLoader)) {
+        throw new SolrException(SolrException.ErrorCode.getErrorCode(e.code()),
+            e.getMessage() + ". runtime library loading is not enabled, start Solr with -Denable.runtime.lib=true",
+            e.getCause());
+      }
+      throw e;
 
-      return meta.clazz == UpdateRequestProcessorFactory.class ?
-          (PluginHolder<T>) new UpdateRequestProcessorChain.LazyUpdateProcessorFactoryHolder(holder) :
-          holder;
-    } else if ("lazy".equals(info.attributes.get("startup")) && meta.options.contains(SolrConfig.PluginOpts.LAZY)) {
-      log.debug("{} : '{}' created with startup=lazy ", meta.getCleanTag(), info.name);
-      return new LazyPluginHolder<T>(meta, info, core, core.getResourceLoader(), false);
-    } else {
-      T inst = core.createInstance(info.className, (Class<T>) meta.clazz, meta.getCleanTag(), null, core.getResourceLoader());
-      initInstance(inst, info);
-      return new PluginHolder<>(info, inst);
+
+    }
+    initInstance(localInst, pluginInfo);
+    if (localInst instanceof SolrCoreAware) {
+      SolrResourceLoader.assertAwareCompatibility(SolrCoreAware.class, localInst);
+      ((SolrCoreAware) localInst).inform(core);
     }
+    if (localInst instanceof ResourceLoaderAware) {
+      SolrResourceLoader.assertAwareCompatibility(ResourceLoaderAware.class, localInst);
+      try {
+        ((ResourceLoaderAware) localInst).inform(core.getResourceLoader());
+      } catch (IOException e) {
+        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "error initializing component", e);
+      }
+    }
+    return localInst;
   }
 
   /** make a plugin available in an alternate name. This is an internal API and not for public use
@@ -323,13 +335,52 @@ public class PluginBag<T> implements AutoCloseable {
     }
   }
 
+  public PluginHolder<T> createPlugin(PluginInfo info) {
+    String runtimeType = info.getRuntimeLibType();
+    if ("global".equals(runtimeType)) {
+      log.debug(" {} : '{}'  created with runtimeLib=global ", meta.getCleanTag(), info.name);
+      PluginHolder<T> holder = new GlobalLoaderPluginHolder<T>(info, core, meta);
+      return meta.clazz == UpdateRequestProcessorFactory.class ?
+          (PluginHolder<T>) new UpdateRequestProcessorChain.LazyUpdateProcessorFactoryHolder((PluginHolder<UpdateRequestProcessorFactory>) holder) :
+          holder;
+
+    } else if ("core".equals(String.valueOf(runtimeType))) {
+      log.debug(" {} : '{}'  created with runtimeLib=true ", meta.getCleanTag(), info.name);
+      LazyPluginHolder<T> holder = new LazyPluginHolder<>(meta, info, core, RuntimeLib.isEnabled() ?
+          core.getMemClassLoader() :
+          core.getResourceLoader(), true);
+
+      return meta.clazz == UpdateRequestProcessorFactory.class ?
+          (PluginHolder<T>) new UpdateRequestProcessorChain.LazyUpdateProcessorFactoryHolder((PluginHolder<UpdateRequestProcessorFactory>) holder) :
+          holder;
+    } else if ("lazy".equals(info.attributes.get("startup")) && meta.options.contains(SolrConfig.PluginOpts.LAZY)) {
+      log.debug("{} : '{}' created with startup=lazy ", meta.getCleanTag(), info.name);
+      return new LazyPluginHolder<T>(meta, info, core, core.getResourceLoader(), false);
+    } else {
+      T inst = core.createInstance(info.className, (Class<T>) meta.clazz, meta.getCleanTag(), null, core.getResourceLoader());
+      initInstance(inst, info);
+      return new PluginHolder<>(info, inst);
+    }
+  }
+
+  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;
+  }
+
   /**
    * An indirect reference to a plugin. It just wraps a plugin instance.
    * subclasses may choose to lazily load the plugin
    */
   public static class PluginHolder<T> implements AutoCloseable {
-    private T inst;
     protected final PluginInfo pluginInfo;
+    T inst;
     boolean registerAPI = false;
 
     public PluginHolder(PluginInfo info) {
@@ -425,7 +476,8 @@ public class PluginBag<T> implements AutoCloseable {
         MemClassLoader loader = (MemClassLoader) resourceLoader;
         loader.loadJars();
       }
-      Class<T> clazz = (Class<T>) pluginMeta.clazz;
+      lazyInst = createInitInstance(pluginInfo,pluginMeta,core,resourceLoader, isRuntimeLib);
+     /* Class<T> clazz = (Class<T>) pluginMeta.clazz;
       T localInst = null;
       try {
         localInst = core.createInstance(pluginInfo.className, clazz, pluginMeta.getCleanTag(), null, resourceLoader);
@@ -452,22 +504,35 @@ public class PluginBag<T> implements AutoCloseable {
           throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "error initializing component", e);
         }
       }
-      lazyInst = localInst;  // only assign the volatile until after the plugin is completely ready to use
+      lazyInst = localInst;  // only assign the volatile until after the plugin is completely ready to use*/
       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");
+  public class GlobalLoaderPluginHolder<T> extends PluginHolder<T> {
+    private final SolrCore core;
+    private final SolrConfig.SolrPluginInfo pluginMeta;
+    private final LibListener libListener;
+
+    public GlobalLoaderPluginHolder(PluginInfo info, SolrCore core, SolrConfig.SolrPluginInfo pluginMeta) {
+      super(info);
+      this.core = core;
+      this.pluginMeta = pluginMeta;
+      this.core.addGlobalClassLoaderListener(() -> reload());
+      this.libListener = core.getCoreContainer().getClusterPropertiesListener();
+      reload();
     }
-    return apiBag.lookup(path, method, parts);
-  }
 
-  public ApiBag getApiBag() {
-    return apiBag;
+
+    private void reload() {
+      if(inst == null) log.info("reloading plugin {} ", pluginInfo.name);
+      inst = createInitInstance(pluginInfo, pluginMeta, core, core.getCoreContainer().getClusterPropertiesListener().getResourceLoader(), true);
+    }
+
+
   }
 
+
 }
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 1bc85ae..b537105 100644
--- a/solr/core/src/java/org/apache/solr/core/PluginInfo.java
+++ b/solr/core/src/java/org/apache/solr/core/PluginInfo.java
@@ -16,14 +16,22 @@
  */
 package org.apache.solr.core;
 
+import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
 import org.apache.solr.common.MapSerializable;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.util.DOMUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 
-import java.util.*;
-
 import static java.util.Arrays.asList;
 import static java.util.Collections.unmodifiableList;
 import static java.util.Collections.unmodifiableMap;
@@ -35,6 +43,8 @@ import static org.apache.solr.schema.FieldType.CLASS_NAME;
  *
  */
 public class PluginInfo implements MapSerializable {
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
   public final String name, className, type;
   public final NamedList initArgs;
   public final Map<String, String> attributes;
@@ -187,4 +197,15 @@ public class PluginInfo implements MapSerializable {
     result.isFromSolrConfig = isFromSolrConfig;
     return result;
   }
+  public String getRuntimeLibType(){
+    Object runtimeType = attributes.get(RuntimeLib.TYPE);
+    if(runtimeType == null || "false".equals(runtimeType) || Boolean.FALSE.equals(runtimeType)) return null;
+    runtimeType = runtimeType.toString();
+    if ("true".equals(String.valueOf(runtimeType))) {
+      log.warn("runtimeLib = true is deprecated, use runtimeLib=core/global");
+      runtimeType = "core";
+    }
+    return runtimeType.toString();
+  }
+
 }
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 be29e7a..19fcd14 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrCore.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrCore.java
@@ -238,6 +238,7 @@ public final class SolrCore implements SolrInfoBean, SolrMetricProducer, Closeab
   public volatile boolean searchEnabled = true;
   public volatile boolean indexEnabled = true;
   public volatile boolean readOnly = false;
+  private List<Runnable> globalClassLoaderListeners = new ArrayList<>();
 
   public Set<String> getMetricNames() {
     return metricNames;
@@ -352,6 +353,15 @@ public final class SolrCore implements SolrInfoBean, SolrMetricProducer, Closeab
           .getPath();
     }
   }
+  void globalClassLoaderChanged(){
+    for (Runnable r : globalClassLoaderListeners) {
+      r.run();
+
+    }
+  }
+  void addGlobalClassLoaderListener(Runnable r){
+    globalClassLoaderListeners.add(r);
+  }
 
 
   /**
@@ -858,7 +868,11 @@ public final class SolrCore implements SolrInfoBean, SolrMetricProducer, Closeab
 
   public <T extends Object> T createInitInstance(PluginInfo info, Class<T> cast, String msg, String defClassName) {
     if (info == null) return null;
-    T o = createInstance(info.className == null ? defClassName : info.className, cast, msg, this, getResourceLoader());
+    ResourceLoader resourceLoader = "global".equals(info.getRuntimeLibType())?
+        coreContainer.getClusterPropertiesListener().
+        getResourceLoader(): getResourceLoader();
+
+    T o = createInstance(info.className == null ? defClassName : info.className, cast, msg, this, resourceLoader);
     if (o instanceof PluginInfoInitialized) {
       ((PluginInfoInitialized) o).init(info);
     } else if (o instanceof NamedListInitializedPlugin) {
diff --git a/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java b/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java
index ce36017..6e917ea 100644
--- a/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java
@@ -600,6 +600,8 @@ public class SolrConfigHandler extends RequestHandlerBase implements SolrCoreAwa
           }
         }
       } catch (Exception e) {
+
+        log.error( "error executing commands "+ Utils.toJSONString(ops) ,e);
         resp.setException(e);
         resp.add(CommandOperation.ERR_MSGS, singletonList(SchemaManager.getErrorStr(e)));
       }
@@ -788,7 +790,7 @@ public class SolrConfigHandler extends RequestHandlerBase implements SolrCoreAwa
           return overlay;
         }
       }
-      if (!verifyClass(op, clz, info.clazz)) return overlay;
+      if (!verifyClass(op, clz, info)) return overlay;
       if (pluginExists(info, overlay, name)) {
         if (isCeate) {
           op.addError(formatString(" ''{0}'' already exists . Do an ''{1}'' , if you want to change it ", name, "update-" + info.getTagCleanLower()));
@@ -812,12 +814,19 @@ public class SolrConfigHandler extends RequestHandlerBase implements SolrCoreAwa
       return overlay.getNamedPlugins(info.getCleanTag()).containsKey(name);
     }
 
-    private boolean verifyClass(CommandOperation op, String clz, Class expected) {
+    private boolean verifyClass(CommandOperation op, String clz, SolrConfig.SolrPluginInfo pluginMeta) {
       if (clz == null) return true;
-      if (!"true".equals(String.valueOf(op.getStr(RuntimeLib.TYPE, null)))) {
+      PluginInfo info = new PluginInfo(pluginMeta.getCleanTag(), op.getDataMap());
+      if(info.getRuntimeLibType() != null && !RuntimeLib.isEnabled()){
+        op.addError("node not started with enable.runtime.lib=true");
+        return false;
+      }
+
+
+      if ( !"true".equals(String.valueOf(op.getStr(RuntimeLib.TYPE, null)))) {
         //this is not dynamically loaded so we can verify the class right away
         try {
-          req.getCore().createInitInstance(new PluginInfo(SolrRequestHandler.TYPE, op.getDataMap()), expected, clz, "");
+          req.getCore().createInitInstance(new PluginInfo(SolrRequestHandler.TYPE, op.getDataMap()), pluginMeta.clazz, clz, "");
         } catch (Exception e) {
           op.addError(e.getMessage());
           return false;
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 eb3c08b..c45f0c6 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
@@ -328,7 +328,7 @@ public final class UpdateRequestProcessorChain implements PluginInfoInitialized
   public static class LazyUpdateProcessorFactoryHolder extends PluginBag.PluginHolder<UpdateRequestProcessorFactory> {
     private volatile UpdateRequestProcessorFactory lazyFactory;
 
-    public LazyUpdateProcessorFactoryHolder(final PluginBag.LazyPluginHolder holder) {
+    public LazyUpdateProcessorFactoryHolder(final PluginBag.PluginHolder<UpdateRequestProcessorFactory> holder) {
       super(holder.getPluginInfo());
       lazyFactory = new LazyUpdateRequestProcessorFactory(holder);
     }
@@ -340,26 +340,20 @@ public final class UpdateRequestProcessorChain implements PluginInfoInitialized
     }
 
     public class LazyUpdateRequestProcessorFactory extends UpdateRequestProcessorFactory {
-      private final PluginBag.LazyPluginHolder holder;
-      UpdateRequestProcessorFactory delegate;
+      private final PluginBag.PluginHolder<UpdateRequestProcessorFactory> holder;
 
-      public LazyUpdateRequestProcessorFactory(PluginBag.LazyPluginHolder holder) {
+      public LazyUpdateRequestProcessorFactory(PluginBag.PluginHolder holder) {
         this.holder = holder;
       }
 
       public UpdateRequestProcessorFactory getDelegate() {
-        return delegate;
+        return holder.get();
       }
 
       @Override
       public UpdateRequestProcessor getInstance(SolrQueryRequest req, SolrQueryResponse rsp, UpdateRequestProcessor next) {
-        if (delegate != null) return delegate.getInstance(req, rsp, next);
+        return holder.get().getInstance(req, rsp, next);
 
-        synchronized (this) {
-          if (delegate == null)
-            delegate = (UpdateRequestProcessorFactory) holder.get();
-        }
-        return delegate.getInstance(req, rsp, next);
       }
     }
   }
diff --git a/solr/core/src/java/org/apache/solr/util/CryptoKeys.java b/solr/core/src/java/org/apache/solr/util/CryptoKeys.java
index 7d9e32b..ede009a 100644
--- a/solr/core/src/java/org/apache/solr/util/CryptoKeys.java
+++ b/solr/core/src/java/org/apache/solr/util/CryptoKeys.java
@@ -72,11 +72,11 @@ public final class CryptoKeys implements CLIO {
       boolean verified;
       try {
         verified = CryptoKeys.verify(entry.getValue(), Base64.base64ToByteArray(sig), data);
-        log.info("verified {} ", verified);
+        log.debug("verified {} ", verified);
         if (verified) return entry.getKey();
       } catch (Exception e) {
         exception = e;
-        log.info("NOT verified  ");
+        log.debug("NOT verified  ");
       }
 
     }
diff --git a/solr/core/src/test/org/apache/solr/handler/TestContainerReqHandler.java b/solr/core/src/test/org/apache/solr/handler/TestContainerReqHandler.java
index fac0d0f..34656d8 100644
--- a/solr/core/src/test/org/apache/solr/handler/TestContainerReqHandler.java
+++ b/solr/core/src/test/org/apache/solr/handler/TestContainerReqHandler.java
@@ -18,6 +18,8 @@
 package org.apache.solr.handler;
 
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
 import java.lang.invoke.MethodHandles;
 import java.util.Arrays;
 import java.util.List;
@@ -27,18 +29,24 @@ import java.util.function.Predicate;
 
 import com.carrotsearch.ant.tasks.junit4.dependencies.com.google.common.collect.ImmutableMap;
 import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.client.solrj.ResponseParser;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.impl.BaseHttpSolrClient;
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.client.solrj.request.GenericSolrRequest;
 import org.apache.solr.client.solrj.request.V2Request;
 import org.apache.solr.client.solrj.response.V2Response;
+import org.apache.solr.cloud.ConfigRequest;
 import org.apache.solr.cloud.MiniSolrCloudCluster;
 import org.apache.solr.cloud.SolrCloudTestCase;
 import org.apache.solr.common.cloud.ClusterProperties;
 import org.apache.solr.common.cloud.SolrZkClient;
 import org.apache.solr.common.params.MapSolrParams;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.Pair;
 import org.apache.solr.common.util.Utils;
 import org.apache.solr.core.MemClassLoader;
@@ -54,6 +62,8 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import static org.apache.solr.cloud.TestCryptoKeys.readFile;
+import static org.apache.solr.common.params.CommonParams.JAVABIN;
+import static org.apache.solr.common.params.CommonParams.WT;
 import static org.apache.solr.common.util.Utils.getObjectByPath;
 import static org.apache.solr.core.TestDynamicLoading.getFileContent;
 import static org.apache.solr.core.TestDynamicLoadingUrl.runHttpServer;
@@ -98,7 +108,6 @@ public class TestContainerReqHandler extends SolrCloudTestCase {
 
       } catch (Exception e) {
         if (i >= repeats - 1) throw e;
-        log.error("exception in request", e);
         continue;
       }
     }
@@ -223,8 +232,8 @@ public class TestContainerReqHandler extends SolrCloudTestCase {
 
 
     } finally {
-      server.first().stop();
       cluster.shutdown();
+      server.first().stop();
     }
   }
 
@@ -237,7 +246,7 @@ public class TestContainerReqHandler extends SolrCloudTestCase {
 
     Pair<Server, Integer> server = runHttpServer(jars);
     int port = server.second();
-    MiniSolrCloudCluster cluster =  configureCluster(4).configure();
+    MiniSolrCloudCluster cluster = configureCluster(4).configure();
 
     try {
 
@@ -328,7 +337,7 @@ public class TestContainerReqHandler extends SolrCloudTestCase {
 
     Pair<Server, Integer> server = runHttpServer(jars);
     int port = server.second();
-    MiniSolrCloudCluster cluster =  configureCluster(4).configure();
+    MiniSolrCloudCluster cluster = configureCluster(4).configure();
 
     try {
 
@@ -428,4 +437,122 @@ public class TestContainerReqHandler extends SolrCloudTestCase {
     }
 
   }
+
+  public void testPluginGlobalLoader() throws Exception {
+    String COLLECTION_NAME = "globalLoaderColl";
+    Map<String, Object> jars = Utils.makeMap(
+        "/jar1.jar", getFileContent("runtimecode/runtimelibs.jar.bin"),
+        "/jar2.jar", getFileContent("runtimecode/runtimelibs_v2.jar.bin"),
+        "/jar3.jar", getFileContent("runtimecode/runtimelibs_v3.jar.bin"));
+
+    Pair<Server, Integer> server = runHttpServer(jars);
+    int port = server.second();
+    System.setProperty("enable.runtime.lib", "true");
+    MiniSolrCloudCluster cluster = configureCluster(4)
+        .addConfig("conf", configset("cloud-minimal"))
+        .configure();
+    try {
+      CollectionAdminRequest
+          .createCollection(COLLECTION_NAME, "conf", 2, 1)
+          .setMaxShardsPerNode(100)
+          .process(cluster.getSolrClient());
+
+
+      cluster.waitForActiveCollection(COLLECTION_NAME, 2, 2);
+      String payload = "{add-runtimelib:{name : 'foo', url: 'http://localhost:" + port + "/jar1.jar', " +
+          "sha512 : 'd01b51de67ae1680a84a813983b1de3b592fc32f1a22b662fc9057da5953abd1b72476388ba342cad21671cd0b805503c78ab9075ff2f3951fdf75fa16981420'}}";
+      new V2Request.Builder("/cluster")
+          .withPayload(payload)
+          .withMethod(SolrRequest.METHOD.POST)
+          .build().process(cluster.getSolrClient());
+      assertEquals(getObjectByPath(Utils.fromJSONString(payload), true, "add-runtimelib/sha512"),
+          getObjectByPath(new ClusterProperties(cluster.getZkClient()).getClusterProperties(), true, "runtimeLib/foo/sha512"));
+
+
+      payload = "{\n" +
+          "'create-requesthandler' : { 'name' : '/runtime', 'class': 'org.apache.solr.core.RuntimeLibReqHandler' , 'runtimeLib':global }," +
+          "'create-searchcomponent' : { 'name' : 'get', 'class': 'org.apache.solr.core.RuntimeLibSearchComponent' , 'runtimeLib':global }," +
+          "'create-queryResponseWriter' : { 'name' : 'json1', 'class': 'org.apache.solr.core.RuntimeLibResponseWriter' , 'runtimeLib':global }" +
+          "}";
+      cluster.getSolrClient().request(new ConfigRequest(payload) {
+        @Override
+        public String getCollection() {
+          return COLLECTION_NAME;
+        }
+      });
+
+
+      SolrParams params = new MapSolrParams((Map) Utils.makeMap("collection", COLLECTION_NAME, WT, JAVABIN));
+      assertResponseValues(10,
+          cluster.getSolrClient(),
+          new GenericSolrRequest(SolrRequest.METHOD.GET, "/config/overlay", params),
+          Utils.makeMap(
+              "overlay/queryResponseWriter/json1/class", "org.apache.solr.core.RuntimeLibResponseWriter",
+              "overlay/searchComponent/get/class", "org.apache.solr.core.RuntimeLibSearchComponent"
+          ));
+
+      assertResponseValues(10,
+          cluster.getSolrClient(),
+          new GenericSolrRequest(SolrRequest.METHOD.GET, "/runtime", params),
+          Utils.makeMap("class", "org.apache.solr.core.RuntimeLibReqHandler",
+              "loader", MemClassLoader.class.getName()));
+
+      assertResponseValues(10,
+          cluster.getSolrClient(),
+          new GenericSolrRequest(SolrRequest.METHOD.GET, "/get?abc=xyz", params),
+          Utils.makeMap("get", "org.apache.solr.core.RuntimeLibSearchComponent",
+              "loader", MemClassLoader.class.getName()));
+
+      GenericSolrRequest req = new GenericSolrRequest(SolrRequest.METHOD.GET, "/runtime",
+          new MapSolrParams((Map) Utils.makeMap("collection", COLLECTION_NAME, WT, "json1")));
+      req.setResponseParser(new ResponseParser() {
+        @Override
+        public String getWriterType() {
+          return "json1";
+        }
+
+        @Override
+        public NamedList<Object> processResponse(InputStream body, String encoding) {
+          return new NamedList<>((Map) Utils.fromJSON(body));
+        }
+
+        @Override
+        public NamedList<Object> processResponse(Reader reader) {
+          return new NamedList<>((Map) Utils.fromJSON(reader));
+
+        }
+
+      });
+      assertResponseValues(10,
+          cluster.getSolrClient(),
+          req,
+          Utils.makeMap("wt", "org.apache.solr.core.RuntimeLibResponseWriter",
+              "loader", MemClassLoader.class.getName()));
+
+
+
+      payload = "{update-runtimelib:{name : 'foo', url: 'http://localhost:" + port + "/jar2.jar', " +
+          "sha512 : 'bc5ce45ad281b6a08fb7e529b1eb475040076834816570902acb6ebdd809410e31006efdeaa7f78a6c35574f3504963f5f7e4d92247d0eb4db3fc9abdda5d417'}}";
+      new V2Request.Builder("/cluster")
+          .withPayload(payload)
+          .withMethod(SolrRequest.METHOD.POST)
+          .build().process(cluster.getSolrClient());
+      assertEquals(getObjectByPath(Utils.fromJSONString(payload), true, "update-runtimelib/sha512"),
+          getObjectByPath(new ClusterProperties(cluster.getZkClient()).getClusterProperties(), true, "runtimeLib/foo/sha512"));
+
+
+      assertResponseValues(10,
+          cluster.getSolrClient(),
+          new GenericSolrRequest(SolrRequest.METHOD.GET, "/get?abc=xyz", params),
+          Utils.makeMap("get", "org.apache.solr.core.RuntimeLibSearchComponent",
+          "loader", MemClassLoader.class.getName(),
+          "Version","2"));
+    } finally {
+      cluster.deleteAllCollections();
+      cluster.shutdown();
+      server.first().stop();
+    }
+
+  }
+
 }
diff --git a/solr/core/src/test/org/apache/solr/update/processor/RuntimeUrp.java b/solr/core/src/test/org/apache/solr/update/processor/RuntimeUrp.java
index 889b0bf..6cee3d9 100644
--- a/solr/core/src/test/org/apache/solr/update/processor/RuntimeUrp.java
+++ b/solr/core/src/test/org/apache/solr/update/processor/RuntimeUrp.java
@@ -31,7 +31,7 @@ public class RuntimeUrp extends SimpleUpdateProcessorFactory {
     List<String>  names = new ArrayList<>();
     for (UpdateRequestProcessorFactory p : processorChain.getProcessors()) {
       if (p instanceof UpdateRequestProcessorChain.LazyUpdateProcessorFactoryHolder.LazyUpdateRequestProcessorFactory) {
-        p = ((UpdateRequestProcessorChain.LazyUpdateProcessorFactoryHolder.LazyUpdateRequestProcessorFactory) p).delegate;
+        p = ((UpdateRequestProcessorChain.LazyUpdateProcessorFactoryHolder.LazyUpdateRequestProcessorFactory) p).getDelegate();
       }
       names.add(p.getClass().getSimpleName());
     }
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/SolrResponse.java b/solr/solrj/src/java/org/apache/solr/client/solrj/SolrResponse.java
index ef52eb2..73eb863 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/SolrResponse.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/SolrResponse.java
@@ -18,10 +18,12 @@ package org.apache.solr.client.solrj;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.io.Serializable;
 
+import org.apache.solr.common.MapWriter;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
 import org.apache.solr.common.util.NamedList;
@@ -32,7 +34,7 @@ import org.apache.solr.common.util.NamedList;
  * 
  * @since solr 1.3
  */
-public abstract class SolrResponse implements Serializable {
+public abstract class SolrResponse implements Serializable, MapWriter {
 
   /** Elapsed time in milliseconds for the request as seen from the client. */
   public abstract long getElapsedTime();
@@ -43,6 +45,11 @@ public abstract class SolrResponse implements Serializable {
   
   public abstract NamedList<Object> getResponse();
 
+  @Override
+  public void writeMap(EntryWriter ew) throws IOException {
+    getResponse().writeMap(ew);
+  }
+
   public Exception getException() {
     NamedList exp = (NamedList) getResponse().get("exception");
     if (exp == null) {
diff --git a/solr/solrj/src/java/org/apache/solr/common/util/CommandOperation.java b/solr/solrj/src/java/org/apache/solr/common/util/CommandOperation.java
index 277324a..3804f78 100644
--- a/solr/solrj/src/java/org/apache/solr/common/util/CommandOperation.java
+++ b/solr/solrj/src/java/org/apache/solr/common/util/CommandOperation.java
@@ -28,6 +28,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import org.apache.solr.common.MapWriter;
 import org.apache.solr.common.SolrException;
 import org.noggit.JSONParser;
 import org.noggit.ObjectBuilder;
@@ -38,7 +39,7 @@ import static java.util.Collections.singletonMap;
 import static org.apache.solr.common.util.StrUtils.formatString;
 import static org.apache.solr.common.util.Utils.toJSON;
 
-public class CommandOperation {
+public class CommandOperation implements MapWriter {
   public final String name;
   private Object commandData;//this is most often a map
   private List<String> errors = new ArrayList<>();
@@ -386,4 +387,10 @@ public class CommandOperation {
     if (o == null) return null;
     return getInt(name, null);
   }
+
+  @Override
+  public void writeMap(EntryWriter ew) throws IOException {
+    ew.put(name, commandData);
+    ew.putIfNotNull("errors", errors);
+  }
 }
diff --git a/solr/solrj/src/java/org/apache/solr/common/util/Utils.java b/solr/solrj/src/java/org/apache/solr/common/util/Utils.java
index 4eb3a3c..847949f 100644
--- a/solr/solrj/src/java/org/apache/solr/common/util/Utils.java
+++ b/solr/solrj/src/java/org/apache/solr/common/util/Utils.java
@@ -722,4 +722,11 @@ public class Utils {
     return def;
   }
 
+  public static void main(String[] args) {
+    List<String> strings = StrUtils.splitSmart("overlay/requestHandler//runtime/class",
+        '/', true);
+    System.out.println(strings);
+
+  }
+
 }
diff --git a/solr/solrj/src/resources/apispec/core.config.Commands.addRequestHandler.properties.json b/solr/solrj/src/resources/apispec/core.config.Commands.addRequestHandler.properties.json
index 731c3d8..6ee1498 100644
--- a/solr/solrj/src/resources/apispec/core.config.Commands.addRequestHandler.properties.json
+++ b/solr/solrj/src/resources/apispec/core.config.Commands.addRequestHandler.properties.json
@@ -10,7 +10,7 @@
       "description": "The request handler class. Class names do not need to be fully qualified if they are included with Solr, so you can abbreviate the name as 'solr.SearchHandler'. Custom or third-party class names may need to be fully qualified, however."
     },
     "runtimeLib": {
-      "type": "boolean",
+      "type": "string",
       "description": "An optional parameter to use a custom .jar file that has been uploaded to Solr's blobstore. This additionally requires that the .jar has also been registered with the 'add-runtimelib' command, which is one of the available commands for the Config API."
     },
     "startup": {
diff --git a/solr/solrj/src/resources/apispec/core.config.Commands.generic.json b/solr/solrj/src/resources/apispec/core.config.Commands.generic.json
index 9d2b01d..2ebfdf8 100644
--- a/solr/solrj/src/resources/apispec/core.config.Commands.generic.json
+++ b/solr/solrj/src/resources/apispec/core.config.Commands.generic.json
@@ -10,7 +10,7 @@
       "description": "The configuration item class. Class names do not need to be fully qualified if they are included with Solr, so you can abbreviate the name as 'solr.SearchHandler'. Custom or third-party class names may need to be fully qualified, however."
    },
     "runtimeLib": {
-      "type": "boolean",
+      "type": "string",
       "description": "An optional parameter to use a custom .jar file that has been uploaded to Solr's blobstore. This additionally requires that the .jar has also been registered with the 'add-runtimelib' command, which is one of the available commands for the Config API."
    }
   },