Index: lucene/analysis/common/src/java/org/apache/lucene/analysis/util/AnalysisSPILoader.java =================================================================== --- lucene/analysis/common/src/java/org/apache/lucene/analysis/util/AnalysisSPILoader.java (revision 1366029) +++ lucene/analysis/common/src/java/org/apache/lucene/analysis/util/AnalysisSPILoader.java (working copy) @@ -33,8 +33,9 @@ */ public final class AnalysisSPILoader { - private final Map> services; + private volatile Map> services = Collections.emptyMap(); private final Class clazz; + private final String[] suffixes; public AnalysisSPILoader(Class clazz) { this(clazz, new String[] { clazz.getSimpleName() }); @@ -50,6 +51,22 @@ public AnalysisSPILoader(Class clazz, String[] suffixes, ClassLoader classloader) { this.clazz = clazz; + this.suffixes = suffixes; + reload(classloader); + } + + /** + * Reloads the internal SPI list from the given {@link ClassLoader}. + * Changes to the service list are visible after the method ends, all + * iterators ({@link #iterator()},...) stay consistent. + * + *

NOTE: Only new service providers are added, existing ones are + * never removed or replaced. + * + *

This method is expensive and should only be called for discovery + * of new service providers on the given classpath/classloader! + */ + public void reload(ClassLoader classloader) { final SPIClassIterator loader = SPIClassIterator.get(clazz, classloader); final LinkedHashMap> services = new LinkedHashMap>(); while (loader.hasNext()) { @@ -69,6 +86,11 @@ // only add the first one for each name, later services will be ignored // this allows to place services before others in classpath to make // them used instead of others + // + // TODO: Should we disallow duplicate names here? + // Allowing it may get confusing on collisions, as different packages + // could contain same factory class, which is a naming bug! + // When changing this be careful to allow reload()! if (!services.containsKey(name)) { services.put(name, service); } Index: lucene/analysis/common/src/java/org/apache/lucene/analysis/util/CharFilterFactory.java =================================================================== --- lucene/analysis/common/src/java/org/apache/lucene/analysis/util/CharFilterFactory.java (revision 1366029) +++ lucene/analysis/common/src/java/org/apache/lucene/analysis/util/CharFilterFactory.java (working copy) @@ -55,5 +55,21 @@ return loader.availableServices(); } + /** + * Reloads the factory list from the given {@link ClassLoader}. + * Changes to the factories are visible after the method ends, all + * iterators ({@link #availableCharFilters()},...) stay consistent. + * + *

NOTE: Only new factories are added, existing ones are + * never removed or replaced. + * + *

This method is expensive and should only be called for discovery + * of new factories on the given classpath/classloader! + */ + public static void reloadCharFilters(ClassLoader classloader) { + loader.reload(classloader); + } + + /** Wraps the given Reader with a CharFilter. */ public abstract Reader create(Reader input); } Index: lucene/analysis/common/src/java/org/apache/lucene/analysis/util/TokenFilterFactory.java =================================================================== --- lucene/analysis/common/src/java/org/apache/lucene/analysis/util/TokenFilterFactory.java (revision 1366029) +++ lucene/analysis/common/src/java/org/apache/lucene/analysis/util/TokenFilterFactory.java (working copy) @@ -55,6 +55,21 @@ return loader.availableServices(); } + /** + * Reloads the factory list from the given {@link ClassLoader}. + * Changes to the factories are visible after the method ends, all + * iterators ({@link #availableTokenFilters()},...) stay consistent. + * + *

NOTE: Only new factories are added, existing ones are + * never removed or replaced. + * + *

This method is expensive and should only be called for discovery + * of new factories on the given classpath/classloader! + */ + public static void reloadTokenFilters(ClassLoader classloader) { + loader.reload(classloader); + } + /** Transform the specified input TokenStream */ public abstract TokenStream create(TokenStream input); } Index: lucene/analysis/common/src/java/org/apache/lucene/analysis/util/TokenizerFactory.java =================================================================== --- lucene/analysis/common/src/java/org/apache/lucene/analysis/util/TokenizerFactory.java (revision 1366029) +++ lucene/analysis/common/src/java/org/apache/lucene/analysis/util/TokenizerFactory.java (working copy) @@ -55,6 +55,21 @@ return loader.availableServices(); } + /** + * Reloads the factory list from the given {@link ClassLoader}. + * Changes to the factories are visible after the method ends, all + * iterators ({@link #availableTokenizers()},...) stay consistent. + * + *

NOTE: Only new factories are added, existing ones are + * never removed or replaced. + * + *

This method is expensive and should only be called for discovery + * of new factories on the given classpath/classloader! + */ + public static void reloadTokenizers(ClassLoader classloader) { + loader.reload(classloader); + } + /** Creates a TokenStream of the specified input */ public abstract Tokenizer create(Reader input); } Index: lucene/core/src/java/org/apache/lucene/codecs/Codec.java =================================================================== --- lucene/core/src/java/org/apache/lucene/codecs/Codec.java (revision 1366029) +++ lucene/core/src/java/org/apache/lucene/codecs/Codec.java (working copy) @@ -86,6 +86,21 @@ return loader.availableServices(); } + /** + * Reloads the codec list from the given {@link ClassLoader}. + * Changes to the codecs are visible after the method ends, all + * iterators ({@link #availableCodecs()},...) stay consistent. + * + *

NOTE: Only new codecs are added, existing ones are + * never removed or replaced. + * + *

This method is expensive and should only be called for discovery + * of new codecs on the given classpath/classloader! + */ + public static void reloadCodecs(ClassLoader classloader) { + loader.reload(classloader); + } + private static Codec defaultCodec = Codec.forName("Lucene40"); /** expert: returns the default codec used for newly created Index: lucene/core/src/java/org/apache/lucene/codecs/PostingsFormat.java =================================================================== --- lucene/core/src/java/org/apache/lucene/codecs/PostingsFormat.java (revision 1366029) +++ lucene/core/src/java/org/apache/lucene/codecs/PostingsFormat.java (working copy) @@ -70,4 +70,19 @@ public static Set availablePostingsFormats() { return loader.availableServices(); } + + /** + * Reloads the postings format list from the given {@link ClassLoader}. + * Changes to the postings formats are visible after the method ends, all + * iterators ({@link #availablePostingsFormats()},...) stay consistent. + * + *

NOTE: Only new postings formats are added, existing ones are + * never removed or replaced. + * + *

This method is expensive and should only be called for discovery + * of new postings formats on the given classpath/classloader! + */ + public static void reloadPostingsFormats(ClassLoader classloader) { + loader.reload(classloader); + } } Index: lucene/core/src/java/org/apache/lucene/util/NamedSPILoader.java =================================================================== --- lucene/core/src/java/org/apache/lucene/util/NamedSPILoader.java (revision 1366029) +++ lucene/core/src/java/org/apache/lucene/util/NamedSPILoader.java (working copy) @@ -28,16 +28,34 @@ * Helper class for loading named SPIs from classpath (e.g. Codec, PostingsFormat). * @lucene.internal */ -// TODO: would be nice to have case insensitive lookups. public final class NamedSPILoader implements Iterable { - private final Map services; + private volatile Map services = Collections.emptyMap(); private final Class clazz; public NamedSPILoader(Class clazz) { + this(clazz, Thread.currentThread().getContextClassLoader()); + } + + public NamedSPILoader(Class clazz, ClassLoader classloader) { this.clazz = clazz; - final SPIClassIterator loader = SPIClassIterator.get(clazz); - final LinkedHashMap services = new LinkedHashMap(); + reload(classloader); + } + + /** + * Reloads the internal SPI list from the given {@link ClassLoader}. + * Changes to the service list are visible after the method ends, all + * iterators ({@link #iterator()},...) stay consistent. + * + *

NOTE: Only new service providers are added, existing ones are + * never removed or replaced. + * + *

This method is expensive and should only be called for discovery + * of new service providers on the given classpath/classloader! + */ + public void reload(ClassLoader classloader) { + final LinkedHashMap services = new LinkedHashMap(this.services); + final SPIClassIterator loader = SPIClassIterator.get(clazz, classloader); while (loader.hasNext()) { final Class c = loader.next(); try { Index: solr/core/src/java/org/apache/solr/core/SolrResourceLoader.java =================================================================== --- solr/core/src/java/org/apache/solr/core/SolrResourceLoader.java (revision 1366029) +++ solr/core/src/java/org/apache/solr/core/SolrResourceLoader.java (working copy) @@ -37,6 +37,8 @@ import org.apache.lucene.analysis.util.TokenFilterFactory; import org.apache.lucene.analysis.util.TokenizerFactory; import org.apache.lucene.analysis.util.AnalysisSPILoader; +import org.apache.lucene.codecs.Codec; +import org.apache.lucene.codecs.PostingsFormat; import org.apache.lucene.analysis.util.WordlistLoader; import org.apache.lucene.util.WeakIdentityMap; import org.apache.solr.common.ResourceLoader; @@ -113,7 +115,7 @@ this.classLoader = createClassLoader(null, parent); addToClassLoader("./lib/", null); - + // reloadCodecSPI(); -> already done by the line above this.coreProperties = coreProperties; } @@ -144,6 +146,7 @@ void addToClassLoader(final String baseDir, final FileFilter filter) { File base = FileUtils.resolvePath(new File(getInstanceDir()), baseDir); this.classLoader = replaceClassLoader(classLoader, base, filter); + reloadLuceneSPI(); } /** @@ -164,11 +167,17 @@ return pathname.equals(file); } }); + reloadLuceneSPI(); } else { log.error("Can't find (or read) file to add to classloader: " + file); } } + private void reloadLuceneSPI() { + PostingsFormat.reloadPostingsFormats(this.classLoader); + Codec.reloadCodecs(this.classLoader); + } + private static URLClassLoader replaceClassLoader(final URLClassLoader oldLoader, final File base, final FileFilter filter) {