You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by dw...@apache.org on 2021/03/10 09:45:43 UTC

[lucene] 02/21: SOLR-14151: Support for packages in schema.xml

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

dweiss pushed a commit to branch jira/solr14155
in repository https://gitbox.apache.org/repos/asf/lucene.git

commit 2bec9e348d78032e739614c806bc58aac47c635b
Author: noble <no...@apache.org>
AuthorDate: Thu Dec 26 12:37:48 2019 +1100

    SOLR-14151: Support for packages in schema.xml
---
 .../src/java/org/apache/solr/core/ConfigSet.java   |  15 ++-
 .../org/apache/solr/core/ConfigSetService.java     |   3 +-
 .../src/java/org/apache/solr/core/PluginInfo.java  |  47 ++++-----
 .../java/org/apache/solr/core/PluginLoader.java    |  30 ++++++
 .../src/java/org/apache/solr/core/SolrCore.java    |  19 +++-
 .../org/apache/solr/core/SolrResourceLoader.java   |  15 ++-
 .../org/apache/solr/schema/CurrencyFieldType.java  |   2 +-
 .../apache/solr/schema/FieldTypePluginLoader.java  |  12 ++-
 .../java/org/apache/solr/schema/IndexSchema.java   | 105 ++++++++++++++++++++-
 .../org/apache/solr/schema/ManagedIndexSchema.java |   2 +-
 .../org/apache/solr/schema/PreAnalyzedField.java   |   5 +-
 .../solr/util/plugin/AbstractPluginLoader.java     |  11 ++-
 .../org/apache/solr/core/TestCodecSupport.java     |   4 +-
 13 files changed, 217 insertions(+), 53 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/core/ConfigSet.java b/solr/core/src/java/org/apache/solr/core/ConfigSet.java
index e0c9fe4..bfd5867 100644
--- a/solr/core/src/java/org/apache/solr/core/ConfigSet.java
+++ b/solr/core/src/java/org/apache/solr/core/ConfigSet.java
@@ -16,6 +16,8 @@
  */
 package org.apache.solr.core;
 
+import java.util.function.Supplier;
+
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.schema.IndexSchema;
 
@@ -28,17 +30,17 @@ public class ConfigSet {
 
   private final SolrConfig solrconfig;
 
-  private final IndexSchema indexSchema;
+  private volatile Supplier<IndexSchema> indexSchemaSupplier;
 
   private final NamedList properties;
 
   private final boolean trusted;
 
-  public ConfigSet(String name, SolrConfig solrConfig, IndexSchema indexSchema,
-      NamedList properties, boolean trusted) {
+  public ConfigSet(String name, SolrConfig solrConfig, Supplier<IndexSchema> indexSchemaSupplier,
+                   NamedList properties, boolean trusted) {
     this.name = name;
     this.solrconfig = solrConfig;
-    this.indexSchema = indexSchema;
+    this.indexSchemaSupplier = indexSchemaSupplier;
     this.properties = properties;
     this.trusted = trusted;
   }
@@ -51,8 +53,11 @@ public class ConfigSet {
     return solrconfig;
   }
 
+  public Supplier<IndexSchema> getIndexSchemaSupplier() {
+    return indexSchemaSupplier;
+  }
   public IndexSchema getIndexSchema() {
-    return indexSchema;
+    return indexSchemaSupplier.get();
   }
 
   public NamedList getProperties() {
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 bc0d4a9..9fa2f12 100644
--- a/solr/core/src/java/org/apache/solr/core/ConfigSetService.java
+++ b/solr/core/src/java/org/apache/solr/core/ConfigSetService.java
@@ -88,8 +88,7 @@ public abstract class ConfigSetService {
               ) ? false: true;
 
       SolrConfig solrConfig = createSolrConfig(dcore, coreLoader, trusted);
-      IndexSchema schema = createIndexSchema(dcore, solrConfig);
-      return new ConfigSet(configName(dcore), solrConfig, schema, properties, trusted);
+      return new ConfigSet(configName(dcore), solrConfig, () -> createIndexSchema(dcore, solrConfig), properties, trusted);
     } catch (Exception e) {
       throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
           "Could not load conf for core " + dcore.getName() +
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 bb290e1..afe4073 100644
--- a/solr/core/src/java/org/apache/solr/core/PluginInfo.java
+++ b/solr/core/src/java/org/apache/solr/core/PluginInfo.java
@@ -25,7 +25,6 @@ import java.util.Map;
 
 import org.apache.solr.common.MapSerializable;
 import org.apache.solr.common.util.NamedList;
-import org.apache.solr.common.util.Pair;
 import org.apache.solr.util.DOMUtil;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
@@ -52,9 +51,9 @@ public class PluginInfo implements MapSerializable {
   public PluginInfo(String type, Map<String, String> attrs, NamedList initArgs, List<PluginInfo> children) {
     this.type = type;
     this.name = attrs.get(NAME);
-    Pair<String, String> parsed = parseClassName(attrs.get(CLASS_NAME));
-    this.className = parsed.second();
-    this.pkgName = parsed.first();
+    ClassName parsed = new ClassName(attrs.get(CLASS_NAME));
+    this.className = parsed.klas;
+    this.pkgName = parsed.pkg;
     this.initArgs = initArgs;
     attributes = unmodifiableMap(attrs);
     this.children = children == null ? Collections.<PluginInfo>emptyList(): unmodifiableList(children);
@@ -63,29 +62,35 @@ public class PluginInfo implements MapSerializable {
 
   /** class names can be prefixed with package name e.g: my_package:my.pkg.Class
    * This checks if it is a package name prefixed classname.
-   * the return value has first = package name & second = class name
    */
-  static Pair<String,String > parseClassName(String name) {
-    String pkgName = null;
-    String className = name;
-    if (name != null) {
-      int colonIdx = name.indexOf(':');
-      if (colonIdx > -1) {
-        pkgName = name.substring(0, colonIdx);
-        className = name.substring(colonIdx + 1);
+
+  public static class ClassName {
+    public final String pkg;
+    public final String klas;
+
+    public ClassName(String name) {
+      String pkgName = null;
+      String className = name;
+      if (name != null) {
+        int colonIdx = name.indexOf(':');
+        if (colonIdx > -1) {
+          pkgName = name.substring(0, colonIdx);
+          className = name.substring(colonIdx + 1);
+        }
       }
-    }
-    return new Pair<>(pkgName, className);
 
+      this.klas = className;
+      this.pkg = pkgName;
+    }
   }
 
 
   public PluginInfo(Node node, String err, boolean requireName, boolean requireClass) {
     type = node.getNodeName();
     name = DOMUtil.getAttr(node, NAME, requireName ? err : null);
-    Pair<String, String> parsed = parseClassName(DOMUtil.getAttr(node, CLASS_NAME, requireClass ? err : null));
-    className = parsed.second();
-    pkgName = parsed.first();
+    ClassName parsed = new ClassName(DOMUtil.getAttr(node, CLASS_NAME, requireClass ? err : null));
+    className = parsed.klas;
+    pkgName = parsed.pkg;
     initArgs = DOMUtil.childNodesToNamedList(node);
     attributes = unmodifiableMap(DOMUtil.toMap(node.getAttributes()));
     children = loadSubPlugins(node);
@@ -115,9 +120,9 @@ public class PluginInfo implements MapSerializable {
     }
     this.type = type;
     this.name = (String) m.get(NAME);
-    Pair<String, String> parsed = parseClassName((String) m.get(CLASS_NAME));
-    this.className = parsed.second();
-    this.pkgName = parsed.first();
+    ClassName parsed = new ClassName((String) m.get(CLASS_NAME));
+    this.className = parsed.klas;
+    this.pkgName = parsed.pkg;
     attributes = unmodifiableMap(m);
     this.children =  Collections.<PluginInfo>emptyList();
     isFromSolrConfig = true;
diff --git a/solr/core/src/java/org/apache/solr/core/PluginLoader.java b/solr/core/src/java/org/apache/solr/core/PluginLoader.java
new file mode 100644
index 0000000..ef0bd94
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/core/PluginLoader.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+/**A generic interface to load classes
+ *
+ */
+public interface PluginLoader {
+
+  <T> T newInstance(String cname, Class<T> expectedType, String... subpackages);
+
+  <T> T newInstance(String cName, Class<T> expectedType, String[] subPackages, Class[] params, Object[] args);
+
+  <T> Class<? extends T> findClass(String cname, Class<T> expectedType);
+}
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 2c6b175..e7c1be5 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrCore.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrCore.java
@@ -56,6 +56,7 @@ import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Supplier;
 
 import com.codahale.metrics.Counter;
 import com.codahale.metrics.Timer;
@@ -173,6 +174,7 @@ import org.apache.zookeeper.data.Stat;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static org.apache.lucene.util.IOUtils.closeWhileHandlingException;
 import static org.apache.solr.common.params.CommonParams.NAME;
 import static org.apache.solr.common.params.CommonParams.PATH;
 
@@ -238,6 +240,7 @@ public final class SolrCore implements SolrInfoBean, Closeable {
   public volatile boolean searchEnabled = true;
   public volatile boolean indexEnabled = true;
   public volatile boolean readOnly = false;
+  private final Supplier<IndexSchema> schemaSupplier;
 
   private PackageListeners packageListeners = new PackageListeners(this);
 
@@ -688,7 +691,7 @@ public final class SolrCore implements SolrInfoBean, Closeable {
         CoreDescriptor cd = new CoreDescriptor(name, getCoreDescriptor());
         cd.loadExtraProperties(); //Reload the extra properties
         core = new SolrCore(coreContainer, getName(), getDataDir(), coreConfig.getSolrConfig(),
-            coreConfig.getIndexSchema(), coreConfig.getProperties(),
+            () -> coreConfig.getIndexSchema(), coreConfig.getProperties(),
             cd, updateHandler, solrDelPolicy, currentCore, true);
 
         // we open a new IndexWriter to pick up the latest config
@@ -897,7 +900,7 @@ public final class SolrCore implements SolrInfoBean, Closeable {
   }
 
   public SolrCore(CoreContainer coreContainer, CoreDescriptor cd, ConfigSet coreConfig) {
-    this(coreContainer, cd.getName(), null, coreConfig.getSolrConfig(), coreConfig.getIndexSchema(), coreConfig.getProperties(),
+    this(coreContainer, cd.getName(), null, coreConfig.getSolrConfig(), coreConfig.getIndexSchemaSupplier(), coreConfig.getProperties(),
         cd, null, null, null, false);
   }
 
@@ -905,6 +908,11 @@ public final class SolrCore implements SolrInfoBean, Closeable {
     return coreContainer;
   }
 
+  public void refreshSchema() {
+    IndexSchema old = schema;
+    schema = schemaSupplier.get();
+    closeWhileHandlingException(old);
+  }
 
   /**
    * Creates a new core and register it in the list of cores. If a core with the
@@ -912,11 +920,11 @@ public final class SolrCore implements SolrInfoBean, Closeable {
    *
    * @param dataDir the index directory
    * @param config  a solr config instance
-   * @param schema  a solr schema instance
+   * @param schemaSupplier  a solr schema {@link Supplier}
    * @since solr 1.3
    */
   public SolrCore(CoreContainer coreContainer, String name, String dataDir, SolrConfig config,
-                  IndexSchema schema, NamedList configSetProperties,
+                  Supplier<IndexSchema> schemaSupplier, NamedList configSetProperties,
                   CoreDescriptor coreDescriptor, UpdateHandler updateHandler,
                   IndexDeletionPolicyWrapper delPolicy, SolrCore prev, boolean reload) {
 
@@ -924,6 +932,7 @@ public final class SolrCore implements SolrInfoBean, Closeable {
 
     this.coreContainer = coreContainer;
 
+    this.schemaSupplier = schemaSupplier;
     final CountDownLatch latch = new CountDownLatch(1);
 
     try {
@@ -935,6 +944,7 @@ public final class SolrCore implements SolrInfoBean, Closeable {
       MDCLoggingContext.setCore(this);
 
       resourceLoader = config.getResourceLoader();
+      resourceLoader.core = this;
       this.solrConfig = config;
       this.configSetProperties = configSetProperties;
       // Initialize the metrics manager
@@ -959,6 +969,7 @@ public final class SolrCore implements SolrInfoBean, Closeable {
       log.info("[{}] Opening new SolrCore at [{}], dataDir=[{}]", logid, resourceLoader.getInstancePath(),
           this.dataDir);
 
+      IndexSchema schema = schemaSupplier.get();
       checkVersionFieldExistsInSchema(schema, coreDescriptor);
 
       // initialize core metrics
diff --git a/solr/core/src/java/org/apache/solr/core/SolrResourceLoader.java b/solr/core/src/java/org/apache/solr/core/SolrResourceLoader.java
index a57660e..46e2ecf 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrResourceLoader.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrResourceLoader.java
@@ -83,7 +83,7 @@ import org.slf4j.LoggerFactory;
 /**
  * @since solr 1.3
  */
-public class SolrResourceLoader implements ResourceLoader, Closeable {
+public class SolrResourceLoader implements ResourceLoader, PluginLoader, Closeable {
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
   static final String project = "solr";
@@ -118,6 +118,9 @@ public class SolrResourceLoader implements ResourceLoader, Closeable {
   // (such as the SolrZkClient) when XML docs are being parsed.    
   private RestManager.Registry managedResourceRegistry;
 
+  //package private , only to be used from SolrCore
+  SolrCore core;
+
   public synchronized RestManager.Registry getManagedResourceRegistry() {
     if (managedResourceRegistry == null) {
       managedResourceRegistry = new RestManager.Registry();
@@ -149,6 +152,15 @@ public class SolrResourceLoader implements ResourceLoader, Closeable {
 
   }
 
+  /**
+   * This could be null if it is not associated with a core yet
+   *
+   */
+  public SolrCore getCore() {
+    return core;
+
+  }
+
 
   public SolrResourceLoader(Path instanceDir) {
     this(instanceDir, null, null);
@@ -615,6 +627,7 @@ public class SolrResourceLoader implements ResourceLoader, Closeable {
   private static final Class[] NO_CLASSES = new Class[0];
   private static final Object[] NO_OBJECTS = new Object[0];
 
+  @Override
   public <T> T newInstance(String cname, Class<T> expectedType, String... subpackages) {
     return newInstance(cname, expectedType, subpackages, NO_CLASSES, NO_OBJECTS);
   }
diff --git a/solr/core/src/java/org/apache/solr/schema/CurrencyFieldType.java b/solr/core/src/java/org/apache/solr/schema/CurrencyFieldType.java
index 4e59212..0511830 100644
--- a/solr/core/src/java/org/apache/solr/schema/CurrencyFieldType.java
+++ b/solr/core/src/java/org/apache/solr/schema/CurrencyFieldType.java
@@ -121,7 +121,7 @@ public class CurrencyFieldType extends FieldType implements SchemaAware, Resourc
     }
     try {
       Class<? extends ExchangeRateProvider> c
-          = schema.getResourceLoader().findClass(exchangeRateProviderClass, ExchangeRateProvider.class);
+          = schema.getPluginLoader().findClass(exchangeRateProviderClass, ExchangeRateProvider.class);
       provider = c.getConstructor().newInstance();
       provider.init(args);
     } catch (Exception e) {
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 781e199..b72b835 100644
--- a/solr/core/src/java/org/apache/solr/schema/FieldTypePluginLoader.java
+++ b/solr/core/src/java/org/apache/solr/schema/FieldTypePluginLoader.java
@@ -34,6 +34,7 @@ import org.apache.lucene.analysis.util.TokenizerFactory;
 import org.apache.lucene.util.Version;
 import org.apache.solr.analysis.TokenizerChain;
 import org.apache.solr.common.SolrException;
+import org.apache.solr.core.PluginLoader;
 import org.apache.solr.core.SolrConfig;
 import org.apache.solr.core.SolrResourceLoader;
 import org.apache.solr.util.DOMUtil;
@@ -78,7 +79,7 @@ public final class FieldTypePluginLoader
 
 
   @Override
-  protected FieldType create( SolrResourceLoader loader, 
+  protected FieldType create( PluginLoader loader,
                               String name, 
                               String className,
                               Node node ) throws Exception {
@@ -192,6 +193,7 @@ public final class FieldTypePluginLoader
   private Analyzer readAnalyzer(Node node) throws XPathExpressionException {
                                 
     final SolrResourceLoader loader = schema.getResourceLoader();
+    final PluginLoader pluginLoader = schema.getPluginLoader();
 
     // parent node used to be passed in as "fieldtype"
     // if (!fieldtype.hasChildNodes()) return null;
@@ -226,7 +228,7 @@ public final class FieldTypePluginLoader
 
       try {
         // No need to be core-aware as Analyzers are not in the core-aware list
-        final Class<? extends Analyzer> clazz = loader.findClass(analyzerName, Analyzer.class);
+        final Class<? extends Analyzer> clazz =  pluginLoader.findClass(analyzerName, Analyzer.class);
         Analyzer analyzer = clazz.getConstructor().newInstance();
 
         final String matchVersionStr = DOMUtil.getAttr(attrs, LUCENE_MATCH_VERSION_PARAM);
@@ -256,7 +258,7 @@ public final class FieldTypePluginLoader
       ("[schema.xml] analyzer/charFilter", CharFilterFactory.class, false, false) {
 
       @Override
-      protected CharFilterFactory create(SolrResourceLoader loader, String name, String className, Node node) throws Exception {
+      protected CharFilterFactory create(PluginLoader loader, String name, String className, Node node) throws Exception {
         final Map<String,String> params = DOMUtil.toMap(node.getAttributes());
         String configuredVersion = params.remove(LUCENE_MATCH_VERSION_PARAM);
         params.put(LUCENE_MATCH_VERSION_PARAM, parseConfiguredVersion(configuredVersion, CharFilterFactory.class.getSimpleName()).toString());
@@ -306,7 +308,7 @@ public final class FieldTypePluginLoader
       ("[schema.xml] analyzer/tokenizer", TokenizerFactory.class, false, false) {
       
       @Override
-      protected TokenizerFactory create(SolrResourceLoader loader, String name, String className, Node node) throws Exception {
+      protected TokenizerFactory create(PluginLoader loader, String name, String className, Node node) throws Exception {
         final Map<String,String> params = DOMUtil.toMap(node.getAttributes());
         String configuredVersion = params.remove(LUCENE_MATCH_VERSION_PARAM);
         params.put(LUCENE_MATCH_VERSION_PARAM, parseConfiguredVersion(configuredVersion, TokenizerFactory.class.getSimpleName()).toString());
@@ -360,7 +362,7 @@ public final class FieldTypePluginLoader
       new AbstractPluginLoader<TokenFilterFactory>("[schema.xml] analyzer/filter", TokenFilterFactory.class, false, false)
     {
       @Override
-      protected TokenFilterFactory create(SolrResourceLoader loader, String name, String className, Node node) throws Exception {
+      protected TokenFilterFactory create(PluginLoader loader, String name, String className, Node node) throws Exception {
         final Map<String,String> params = DOMUtil.toMap(node.getAttributes());
         String configuredVersion = params.remove(LUCENE_MATCH_VERSION_PARAM);
         params.put(LUCENE_MATCH_VERSION_PARAM, parseConfiguredVersion(configuredVersion, TokenFilterFactory.class.getSimpleName()).toString());
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 5f213d3..407c4d1 100644
--- a/solr/core/src/java/org/apache/solr/schema/IndexSchema.java
+++ b/solr/core/src/java/org/apache/solr/schema/IndexSchema.java
@@ -19,6 +19,7 @@ package org.apache.solr.schema;
 import javax.xml.xpath.XPath;
 import javax.xml.xpath.XPathConstants;
 import javax.xml.xpath.XPathExpressionException;
+import java.io.Closeable;
 import java.io.IOException;
 import java.io.Writer;
 import java.lang.invoke.MethodHandles;
@@ -60,9 +61,13 @@ import org.apache.solr.common.util.Cache;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.Pair;
 import org.apache.solr.common.util.SimpleOrderedMap;
+import org.apache.solr.core.PluginInfo;
+import org.apache.solr.core.PluginLoader;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.SolrResourceLoader;
 import org.apache.solr.core.XmlConfigFile;
+import org.apache.solr.pkg.PackageListeners;
+import org.apache.solr.pkg.PackageLoader;
 import org.apache.solr.request.LocalSolrQueryRequest;
 import org.apache.solr.response.SchemaXmlWriter;
 import org.apache.solr.response.SolrQueryResponse;
@@ -91,7 +96,7 @@ import static java.util.Collections.singletonMap;
  *
  *
  */
-public class IndexSchema {
+public class IndexSchema implements Closeable {
   public static final String COPY_FIELD = "copyField";
   public static final String COPY_FIELDS = COPY_FIELD + "s";
   public static final String DEFAULT_SCHEMA_FILE = "schema.xml";
@@ -133,6 +138,7 @@ public class IndexSchema {
   protected final Version luceneVersion;
   protected float version;
   protected final SolrResourceLoader loader;
+  protected final PluginLoader pluginLoader;
 
   protected Map<String,SchemaField> fields = new HashMap<>();
   protected Map<String,FieldType> fieldTypes = new HashMap<>();
@@ -163,6 +169,7 @@ public class IndexSchema {
    */
   protected Map<SchemaField, Integer> copyFieldTargetCounts = new HashMap<>();
 
+
   /**
    * Constructs a schema using the specified resource name and stream.
    * @see SolrResourceLoader#openSchema
@@ -181,9 +188,95 @@ public class IndexSchema {
     }
   }
 
+  public PluginLoader getPluginLoader(){
+    return pluginLoader;
+  }
   protected IndexSchema(Version luceneVersion, SolrResourceLoader loader) {
     this.luceneVersion = Objects.requireNonNull(luceneVersion);
     this.loader = loader;
+    if(loader.getCore() == null) {
+      this.pluginLoader = loader;
+    } else {
+      PackageAwarePluginLoader papl = new PackageAwarePluginLoader(loader.getCore());
+      this.pluginLoader = papl;
+    }
+  }
+
+  class PackageAwarePluginLoader implements PluginLoader, Closeable {
+    final SolrCore core;
+    private final List<PackageListeners.Listener> listeners = new ArrayList<>();
+    Runnable reloadSchemaRunnable = () -> getCore().refreshSchema();
+
+
+    PackageAwarePluginLoader(SolrCore core) {
+      this.core = core;
+    }
+
+    SolrCore getCore() {
+      return core;
+    }
+
+    @Override
+    public <T> T newInstance(String cname, Class<T> expectedType, String... subpackages) {
+      return getIt(cname, expectedType, pkgloader -> pkgloader.newInstance(cname, expectedType, subpackages));
+    }
+
+    private <T> T getIt(String cname, Class expectedType, Function<SolrResourceLoader, T> fun) {
+      PluginInfo.ClassName className = new PluginInfo.ClassName(cname);
+      if (className.pkg == null) {
+        return  fun.apply(loader);
+      } else {
+        SolrResourceLoader pkgloader = core.getResourceLoader(className.pkg);
+        T inst = fun.apply(pkgloader);
+        PackageListeners.Listener listener = new PackageListeners.Listener() {
+          PluginInfo info = new PluginInfo(expectedType.getSimpleName(), singletonMap("class", cname));
+
+          @Override
+          public String packageName() {
+            return className.pkg;
+          }
+
+          @Override
+          public PluginInfo pluginInfo() {
+            return info;
+          }
+
+          @Override
+          public void changed(PackageLoader.Package pkg, PackageListeners.Ctx ctx) {
+            Runnable old = ctx.getPostProcessor(PackageAwarePluginLoader.class.getName());// just want to do one refresh for every package laod
+            if (old == null) ctx.addPostProcessor(PackageAwarePluginLoader.class.getName(), reloadSchemaRunnable);
+          }
+
+          @Override
+          public PackageLoader.Package.Version getPackageVersion() {
+            return null;
+          }
+        };
+        listeners.add(listener);
+        core.getPackageListeners().addListener(listener);
+        return inst;
+      }
+    }
+
+    @Override
+    public <T> Class<? extends T> findClass(String cname, Class<T> expectedType) {
+      return getIt(cname, expectedType, (Function<SolrResourceLoader, Class<? extends T>>) loader -> loader.findClass(cname, expectedType));
+    }
+
+    @Override
+    public <T> T newInstance(String cName, Class<T> expectedType, String[] subPackages, Class[] params, Object[] args) {
+      return getIt(cName, expectedType, loader -> loader.newInstance(cName, expectedType, subPackages, params, args));
+    }
+
+
+
+    @Override
+    public void close() throws IOException {
+      for (PackageListeners.Listener l : listeners) {
+        core.getPackageListeners().removeListener(l);
+      }
+
+    }
   }
 
   /**
@@ -497,7 +590,7 @@ public class IndexSchema {
       if (similarityFactory == null) {
         final Class<?> simClass = SchemaSimilarityFactory.class;
         // use the loader to ensure proper SolrCoreAware handling
-        similarityFactory = loader.newInstance(simClass.getName(), SimilarityFactory.class);
+        similarityFactory = pluginLoader.newInstance(simClass.getName(), SimilarityFactory.class);
         similarityFactory.init(new ModifiableSolrParams());
       } else {
         isExplicitSimilarity = true;
@@ -971,7 +1064,7 @@ public class IndexSchema {
     dynamicCopyFields = temp;
   }
 
-  static SimilarityFactory readSimilarity(SolrResourceLoader loader, Node node) {
+  static SimilarityFactory readSimilarity(PluginLoader loader, Node node) {
     if (node==null) {
       return null;
     } else {
@@ -1981,4 +2074,10 @@ public class IndexSchema {
     return decoders.computeIfAbsent(ft, f -> PayloadUtils.getPayloadDecoder(ft));
   }
 
+  @Override
+  public void close() throws IOException {
+    if (pluginLoader instanceof PackageAwarePluginLoader) {
+      ((PackageAwarePluginLoader) pluginLoader).close();
+    }
+  }
 }
diff --git a/solr/core/src/java/org/apache/solr/schema/ManagedIndexSchema.java b/solr/core/src/java/org/apache/solr/schema/ManagedIndexSchema.java
index 57b0c90..d65ece9 100644
--- a/solr/core/src/java/org/apache/solr/schema/ManagedIndexSchema.java
+++ b/solr/core/src/java/org/apache/solr/schema/ManagedIndexSchema.java
@@ -1272,7 +1272,7 @@ public final class ManagedIndexSchema extends IndexSchema {
     Map<String,FieldType> newFieldTypes = new HashMap<>();
     List<SchemaAware> schemaAwareList = new ArrayList<>();
     FieldTypePluginLoader typeLoader = new FieldTypePluginLoader(this, newFieldTypes, schemaAwareList);
-    typeLoader.loadSingle(loader, FieldTypeXmlAdapter.toNode(options));
+    typeLoader.loadSingle(pluginLoader, FieldTypeXmlAdapter.toNode(options));
     FieldType ft = newFieldTypes.get(typeName);
     if (!schemaAwareList.isEmpty())
       schemaAware.addAll(schemaAwareList);
diff --git a/solr/core/src/java/org/apache/solr/schema/PreAnalyzedField.java b/solr/core/src/java/org/apache/solr/schema/PreAnalyzedField.java
index 00da7ed..9a5683f 100644
--- a/solr/core/src/java/org/apache/solr/schema/PreAnalyzedField.java
+++ b/solr/core/src/java/org/apache/solr/schema/PreAnalyzedField.java
@@ -15,6 +15,7 @@
  * limitations under the License.
  */
 package org.apache.solr.schema;
+
 import java.io.IOException;
 import java.io.Reader;
 import java.io.StringReader;
@@ -36,8 +37,8 @@ import org.apache.lucene.queries.function.valuesource.SortedSetFieldSource;
 import org.apache.lucene.search.SortField;
 import org.apache.lucene.search.SortedSetSelector;
 import org.apache.lucene.util.AttributeFactory;
-import org.apache.lucene.util.AttributeSource.State;
 import org.apache.lucene.util.AttributeSource;
+import org.apache.lucene.util.AttributeSource.State;
 import org.apache.solr.analysis.SolrAnalyzer;
 import org.apache.solr.response.TextResponseWriter;
 import org.apache.solr.search.QParser;
@@ -79,7 +80,7 @@ public class PreAnalyzedField extends TextField implements HasImplicitIndexAnaly
         parser = new SimplePreAnalyzedParser();
       } else {
         try {
-          Class<? extends PreAnalyzedParser> implClazz = schema.getResourceLoader().findClass(implName, PreAnalyzedParser.class);
+          Class<? extends PreAnalyzedParser> implClazz = schema.getPluginLoader().findClass(implName, PreAnalyzedParser.class);
           Constructor<?> c = implClazz.getConstructor(new Class<?>[0]);
           parser = (PreAnalyzedParser) c.newInstance(new Object[0]);
         } catch (Exception e) {
diff --git a/solr/core/src/java/org/apache/solr/util/plugin/AbstractPluginLoader.java b/solr/core/src/java/org/apache/solr/util/plugin/AbstractPluginLoader.java
index 1243017..5be8ab4 100644
--- a/solr/core/src/java/org/apache/solr/util/plugin/AbstractPluginLoader.java
+++ b/solr/core/src/java/org/apache/solr/util/plugin/AbstractPluginLoader.java
@@ -23,7 +23,7 @@ import java.util.Objects;
 
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
-import org.apache.solr.core.SolrResourceLoader;
+import org.apache.solr.core.PluginLoader;
 import org.apache.solr.util.DOMUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -86,11 +86,11 @@ public abstract class AbstractPluginLoader<T>
    * @param node - the XML node defining this plugin
    */
   @SuppressWarnings("unchecked")
-  protected T create( SolrResourceLoader loader, String name, String className, Node node ) throws Exception
+  protected T create(PluginLoader loader, String name, String className, Node node ) throws Exception
   {
     return loader.newInstance(className, pluginClassType, getDefaultPackages());
   }
-  
+
   /**
    * Register a plugin with a given name.
    * @return The plugin previously registered to this name, or null
@@ -135,7 +135,7 @@ public abstract class AbstractPluginLoader<T>
    * If a default element is defined, it will be returned from this function.
    * 
    */
-  public T load( SolrResourceLoader loader, NodeList nodes )
+  public T load(PluginLoader loader, NodeList nodes )
   {
     List<PluginInitInfo> info = new ArrayList<>();
     T defaultPlugin = null;
@@ -223,7 +223,7 @@ public abstract class AbstractPluginLoader<T>
    * The created class for the plugin will be returned from this function.
    * 
    */
-  public T loadSingle(SolrResourceLoader loader, Node node) {
+  public T loadSingle(PluginLoader loader, Node node) {
     List<PluginInitInfo> info = new ArrayList<>();
     T plugin = null;
 
@@ -280,4 +280,5 @@ public abstract class AbstractPluginLoader<T>
       this.node = node;
     }
   }
+
 }
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 a3daeb8..ec9eef4 100644
--- a/solr/core/src/test/org/apache/solr/core/TestCodecSupport.java
+++ b/solr/core/src/test/org/apache/solr/core/TestCodecSupport.java
@@ -30,7 +30,6 @@ import org.apache.lucene.util.TestUtil;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.util.NamedList;
-import org.apache.solr.schema.IndexSchema;
 import org.apache.solr.schema.IndexSchemaFactory;
 import org.apache.solr.schema.SchemaField;
 import org.apache.solr.util.TestHarness;
@@ -203,7 +202,6 @@ public class TestCodecSupport extends SolrTestCaseJ4 {
     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();
     
@@ -211,7 +209,7 @@ public class TestCodecSupport extends SolrTestCaseJ4 {
       CoreDescriptor cd = new CoreDescriptor(newCoreName, testSolrHome.resolve(newCoreName),
           coreContainer.getContainerProperties(), coreContainer.isZooKeeperAware());
       c = new SolrCore(coreContainer, cd,
-          new ConfigSet("fakeConfigset", config, schema, null, true));
+          new ConfigSet("fakeConfigset", config, () -> IndexSchemaFactory.buildIndexSchema("schema_codec.xml", config), null, true));
       assertNull(coreContainer.registerCore(cd, c, false, false));
       h.coreName = newCoreName;
       assertEquals("We are not using the correct core", "solrconfig_codec2.xml", h.getCore().getConfigResource());