Index: analysis/common/src/java/org/apache/lucene/analysis/util/ClasspathResourceLoader.java =================================================================== --- analysis/common/src/java/org/apache/lucene/analysis/util/ClasspathResourceLoader.java (revision 1437793) +++ analysis/common/src/java/org/apache/lucene/analysis/util/ClasspathResourceLoader.java (working copy) @@ -20,6 +20,8 @@ import java.io.IOException; import java.io.InputStream; +import org.apache.lucene.util.FallbackClassLoader; + /** * Simple {@link ResourceLoader} that uses {@link ClassLoader#getResourceAsStream(String)} * and {@link Class#forName(String,boolean,ClassLoader)} to open resources and @@ -34,7 +36,8 @@ * Resource paths must be absolute. */ public ClasspathResourceLoader() { - this(Thread.currentThread().getContextClassLoader()); + this(FallbackClassLoader.chain(Thread.currentThread().getContextClassLoader(), + ClasspathResourceLoader.class.getClassLoader())); } /** Index: analysis/common/src/java/org/apache/lucene/analysis/util/AnalysisSPILoader.java =================================================================== --- analysis/common/src/java/org/apache/lucene/analysis/util/AnalysisSPILoader.java (revision 1437793) +++ analysis/common/src/java/org/apache/lucene/analysis/util/AnalysisSPILoader.java (working copy) @@ -1,125 +0,0 @@ -package org.apache.lucene.analysis.util; - -/* - * 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. - */ - -import java.util.Arrays; -import java.util.Collections; -import java.util.Locale; -import java.util.Map; -import java.util.LinkedHashMap; -import java.util.Set; -import java.util.ServiceConfigurationError; - -import org.apache.lucene.util.SPIClassIterator; - -/** - * Helper class for loading named SPIs from classpath (e.g. Tokenizers, TokenStreams). - * @lucene.internal - */ -final class AnalysisSPILoader { - - private volatile Map> services = Collections.emptyMap(); - private final Class clazz; - private final String[] suffixes; - - public AnalysisSPILoader(Class clazz) { - this(clazz, new String[] { clazz.getSimpleName() }); - } - - public AnalysisSPILoader(Class clazz, ClassLoader loader) { - this(clazz, new String[] { clazz.getSimpleName() }, loader); - } - - public AnalysisSPILoader(Class clazz, String[] suffixes) { - this(clazz, suffixes, Thread.currentThread().getContextClassLoader()); - } - - 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 (e.g., from {@link #availableServices()},...) 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()) { - final Class service = loader.next(); - final String clazzName = service.getSimpleName(); - String name = null; - for (String suffix : suffixes) { - if (clazzName.endsWith(suffix)) { - name = clazzName.substring(0, clazzName.length() - suffix.length()).toLowerCase(Locale.ROOT); - break; - } - } - if (name == null) { - throw new ServiceConfigurationError("The class name " + service.getName() + - " has wrong suffix, allowed are: " + Arrays.toString(suffixes)); - } - // 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); - } - } - this.services = Collections.unmodifiableMap(services); - } - - public S newInstance(String name) { - final Class service = lookupClass(name); - try { - return service.newInstance(); - } catch (Exception e) { - throw new IllegalArgumentException("SPI class of type "+clazz.getName()+" with name '"+name+"' cannot be instantiated. " + - "This is likely due to a misconfiguration of the java class '" + service.getName() + "': ", e); - } - } - - public Class lookupClass(String name) { - final Class service = services.get(name.toLowerCase(Locale.ROOT)); - if (service != null) { - return service; - } else { - throw new IllegalArgumentException("A SPI class of type "+clazz.getName()+" with name '"+name+"' does not exist. "+ - "You need to add the corresponding JAR file supporting this SPI to your classpath."+ - "The current classpath supports the following names: "+availableServices()); - } - } - - public Set availableServices() { - return services.keySet(); - } -} Index: core/src/java/org/apache/lucene/util/SPIClassIterator.java =================================================================== --- core/src/java/org/apache/lucene/util/SPIClassIterator.java (revision 1437793) +++ core/src/java/org/apache/lucene/util/SPIClassIterator.java (working copy) @@ -1,138 +0,0 @@ -package org.apache.lucene.util; - -/* - * 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. - */ - -import java.io.IOException; -import java.io.InputStream; -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; -import java.util.Iterator; -import java.util.Locale; -import java.util.NoSuchElementException; -import java.util.ServiceConfigurationError; - -/** - * Helper class for loading SPI classes from classpath (META-INF files). - * This is a light impl of {@link java.util.ServiceLoader} but is guaranteed to - * be bug-free regarding classpath order and does not instantiate or initialize - * the classes found. - * - * @lucene.internal - */ -public final class SPIClassIterator implements Iterator> { - private static final String META_INF_SERVICES = "META-INF/services/"; - - private final Class clazz; - private final ClassLoader loader; - private final Enumeration profilesEnum; - private Iterator linesIterator; - - public static SPIClassIterator get(Class clazz) { - return new SPIClassIterator(clazz, Thread.currentThread().getContextClassLoader()); - } - - public static SPIClassIterator get(Class clazz, ClassLoader loader) { - return new SPIClassIterator(clazz, loader); - } - - private SPIClassIterator(Class clazz, ClassLoader loader) { - if (loader == null) - throw new IllegalArgumentException("You must provide a ClassLoader."); - this.clazz = clazz; - this.loader = loader; - try { - this.profilesEnum = loader.getResources(META_INF_SERVICES + clazz.getName()); - } catch (IOException ioe) { - throw new ServiceConfigurationError("Error loading SPI profiles for type " + clazz.getName() + " from classpath", ioe); - } - this.linesIterator = Collections.emptySet().iterator(); - } - - private boolean loadNextProfile() { - ArrayList lines = null; - while (profilesEnum.hasMoreElements()) { - if (lines != null) { - lines.clear(); - } else { - lines = new ArrayList(); - } - final URL url = profilesEnum.nextElement(); - try { - final InputStream in = url.openStream(); - IOException priorE = null; - try { - final BufferedReader reader = new BufferedReader(new InputStreamReader(in, IOUtils.CHARSET_UTF_8)); - String line; - while ((line = reader.readLine()) != null) { - final int pos = line.indexOf('#'); - if (pos >= 0) { - line = line.substring(0, pos); - } - line = line.trim(); - if (line.length() > 0) { - lines.add(line); - } - } - } catch (IOException ioe) { - priorE = ioe; - } finally { - IOUtils.closeWhileHandlingException(priorE, in); - } - } catch (IOException ioe) { - throw new ServiceConfigurationError("Error loading SPI class list from URL: " + url, ioe); - } - if (!lines.isEmpty()) { - this.linesIterator = lines.iterator(); - return true; - } - } - return false; - } - - @Override - public boolean hasNext() { - return linesIterator.hasNext() || loadNextProfile(); - } - - @Override - public Class next() { - // hasNext() implicitely loads the next profile, so it is essential to call this here! - if (!hasNext()) { - throw new NoSuchElementException(); - } - assert linesIterator.hasNext(); - final String c = linesIterator.next(); - try { - // don't initialize the class (pass false as 2nd parameter): - return Class.forName(c, false, loader).asSubclass(clazz); - } catch (ClassNotFoundException cnfe) { - throw new ServiceConfigurationError(String.format(Locale.ROOT, "A SPI class of type %s with classname %s does not exist, "+ - "please fix the file '%s%1$s' in your classpath.", clazz.getName(), c, META_INF_SERVICES)); - } - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - -} Index: core/src/java/org/apache/lucene/util/NamedSPILoader.java =================================================================== --- core/src/java/org/apache/lucene/util/NamedSPILoader.java (revision 1437793) +++ core/src/java/org/apache/lucene/util/NamedSPILoader.java (working copy) @@ -1,127 +0,0 @@ -package org.apache.lucene.util; - -/* - * 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. - */ - -import java.util.Collections; -import java.util.Iterator; -import java.util.Map; -import java.util.LinkedHashMap; -import java.util.Set; -import java.util.ServiceConfigurationError; - -/** - * Helper class for loading named SPIs from classpath (e.g. Codec, PostingsFormat). - * @lucene.internal - */ -public final class NamedSPILoader implements Iterable { - - 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; - 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 { - final S service = c.newInstance(); - final String name = service.getName(); - // 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 - if (!services.containsKey(name)) { - checkServiceName(name); - services.put(name, service); - } - } catch (Exception e) { - throw new ServiceConfigurationError("Cannot instantiate SPI class: " + c.getName(), e); - } - } - this.services = Collections.unmodifiableMap(services); - } - - /** - * Validates that a service name meets the requirements of {@link NamedSPI} - */ - public static void checkServiceName(String name) { - // based on harmony charset.java - if (name.length() >= 128) { - throw new IllegalArgumentException("Illegal service name: '" + name + "' is too long (must be < 128 chars)."); - } - for (int i = 0, len = name.length(); i < len; i++) { - char c = name.charAt(i); - if (!isLetterOrDigit(c)) { - throw new IllegalArgumentException("Illegal service name: '" + name + "' must be simple ascii alphanumeric."); - } - } - } - - /** - * Checks whether a character is a letter or digit (ascii) which are defined in the spec. - */ - private static boolean isLetterOrDigit(char c) { - return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9'); - } - - public S lookup(String name) { - final S service = services.get(name); - if (service != null) return service; - throw new IllegalArgumentException("A SPI class of type "+clazz.getName()+" with name '"+name+"' does not exist. "+ - "You need to add the corresponding JAR file supporting this SPI to your classpath."+ - "The current classpath supports the following names: "+availableServices()); - } - - public Set availableServices() { - return services.keySet(); - } - - @Override - public Iterator iterator() { - return services.values().iterator(); - } - - /** - * Interface to support {@link NamedSPILoader#lookup(String)} by name. - *

- * Names must be all ascii alphanumeric, and less than 128 characters in length. - */ - public static interface NamedSPI { - String getName(); - } - -} Index: core/src/java/org/apache/lucene/util/FallbackClassLoader.java =================================================================== --- core/src/java/org/apache/lucene/util/FallbackClassLoader.java (revision 0) +++ core/src/java/org/apache/lucene/util/FallbackClassLoader.java (revision 0) @@ -0,0 +1,82 @@ +package org.apache.lucene.util; + +/* + * 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. + */ + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Enumeration; + +/** + * A ClassLoader that falls back to a secondary ClassLoader if a class or resource + * could not be loaded using the primary ClassLoader. + * + * @lucene.internal + */ +public final class FallbackClassLoader extends ClassLoader { + private ClassLoader fallbackClassLoader; + + public FallbackClassLoader(final ClassLoader primaryClassLoader, + ClassLoader fallbackClassLoader) { + super(fallbackClassLoader); + this.fallbackClassLoader = fallbackClassLoader; + } + + public static ClassLoader chain(final ClassLoader primaryClassLoader, + ClassLoader fallbackClassLoader) { + if (primaryClassLoader == fallbackClassLoader) { + return primaryClassLoader; + } else { + return new FallbackClassLoader(primaryClassLoader, fallbackClassLoader); + } + } + + @Override + public Class loadClass(String name) throws ClassNotFoundException { + try { + return super.loadClass(name); + } catch (ClassNotFoundException e) { + return fallbackClassLoader.loadClass(name); + } + } + + @Override + public URL getResource(String name) { + URL u = super.getResource(name); + if (u != null) { + return u; + } + return fallbackClassLoader.getResource(name); + } + + @Override + public Enumeration getResources(String name) throws IOException { + Enumeration en1 = super.getResources(name); + if (en1.hasMoreElements()) { + return en1; + } + return fallbackClassLoader.getResources(name); + } + + @Override + public InputStream getResourceAsStream(String name) { + InputStream in = super.getResourceAsStream(name); + if (in != null) { + return in; + } + return fallbackClassLoader.getResourceAsStream(name); + } +}