diff --git a/lucene/analysis/common/src/java/org/apache/lucene/analysis/util/AnalysisSPILoader.java b/lucene/analysis/common/src/java/org/apache/lucene/analysis/util/AnalysisSPILoader.java index af392ebde40..db6c5ac7c0e 100644 --- a/lucene/analysis/common/src/java/org/apache/lucene/analysis/util/AnalysisSPILoader.java +++ b/lucene/analysis/common/src/java/org/apache/lucene/analysis/util/AnalysisSPILoader.java @@ -22,7 +22,7 @@ import java.util.Locale; import java.util.Map; import java.util.LinkedHashMap; import java.util.Set; -import java.util.ServiceLoader; +import org.apache.lucene.util.SPIClassIterator; /** * Helper class for loading named SPIs from classpath (e.g. Tokenizers, TokenStreams). @@ -39,10 +39,11 @@ final class AnalysisSPILoader { public AnalysisSPILoader(Class clazz, String[] suffixes) { this.clazz = clazz; - final ServiceLoader loader = ServiceLoader.load(clazz); + final SPIClassIterator loader = SPIClassIterator.get(clazz); final LinkedHashMap> services = new LinkedHashMap>(); - for (final S service : loader) { - final String clazzName = service.getClass().getSimpleName(); + while (loader.hasNext()) { + final Class service = loader.next(); + final String clazzName = service.getSimpleName(); int suffixIndex = -1; for (String suffix : suffixes) { suffixIndex = clazzName.lastIndexOf(suffix); @@ -56,7 +57,7 @@ final class AnalysisSPILoader { // them used instead of others if (!services.containsKey(name)) { assert checkServiceName(name); - services.put(name, service.getClass().asSubclass(clazz)); + services.put(name, service); } } this.services = Collections.unmodifiableMap(services); diff --git a/lucene/core/src/java/org/apache/lucene/util/NamedSPILoader.java b/lucene/core/src/java/org/apache/lucene/util/NamedSPILoader.java index eb142dc95ac..7eb4796545a 100644 --- a/lucene/core/src/java/org/apache/lucene/util/NamedSPILoader.java +++ b/lucene/core/src/java/org/apache/lucene/util/NamedSPILoader.java @@ -22,7 +22,7 @@ import java.util.Iterator; import java.util.Map; import java.util.LinkedHashMap; import java.util.Set; -import java.util.ServiceLoader; +import java.util.ServiceConfigurationError; /** * Helper class for loading named SPIs from classpath (e.g. Codec, PostingsFormat). @@ -32,21 +32,22 @@ import java.util.ServiceLoader; public final class NamedSPILoader implements Iterable { private final Map services; - - /** This field is a hack for LuceneTestCase to get access - * to the modifiable map (to work around bugs in IBM J9) */ - @SuppressWarnings("unused") - @Deprecated - // Hackidy-Häck-Hack for bugs in IBM J9 ServiceLoader - private final Map modifiableServices; - private final Class clazz; public NamedSPILoader(Class clazz) { this.clazz = clazz; - final ServiceLoader loader = ServiceLoader.load(clazz); + final SPIClassIterator loader = SPIClassIterator.get(clazz); final LinkedHashMap services = new LinkedHashMap(); - for (final S service : loader) { + while (loader.hasNext()) { + final Class c = loader.next(); + final S service; + try { + service = c.newInstance(); + } catch (InstantiationException ie) { + throw new ServiceConfigurationError("Cannot instantiate SPI class: " + c.getName(), ie); + } catch (IllegalAccessException iae) { + throw new ServiceConfigurationError("Cannot instantiate SPI class: " + c.getName(), iae); + } 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 @@ -56,7 +57,6 @@ public final class NamedSPILoader implements services.put(name, service); } } - this.modifiableServices = services; // hack, remove when IBM J9 is fixed! this.services = Collections.unmodifiableMap(services); } diff --git a/lucene/core/src/java/org/apache/lucene/util/SPIClassIterator.java b/lucene/core/src/java/org/apache/lucene/util/SPIClassIterator.java new file mode 100644 index 00000000000..620ed33d814 --- /dev/null +++ b/lucene/core/src/java/org/apache/lucene/util/SPIClassIterator.java @@ -0,0 +1,135 @@ +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.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 classes.", ioe); + } + this.linesIterator = Collections.emptySet().iterator(); + } + + private boolean loadNextProfile() { + ArrayList lines = null; + while (profilesEnum.hasMoreElements()) { + if (lines != null) { + lines.clear(); + } else { + lines = new ArrayList(); + } + try { + final URL url = profilesEnum.nextElement(); + 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 String[] prts = line.trim().split("#"); + if (0 != prts.length) { + final String c = prts[0].trim(); + if (!(0 == c.length() || c.startsWith("#"))) { + lines.add(c); + } + } + } + } catch (IOException ioe) { + priorE = ioe; + } finally { + IOUtils.closeWhileHandlingException(priorE, in); + } + } catch (IOException ioe) { + throw new ServiceConfigurationError("Error loading SPI classes.", 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: + return Class.forName(c, false, loader).asSubclass(clazz); + } catch (ClassNotFoundException cnfe) { + throw new ServiceConfigurationError("SPI class not found: " + c); + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + +} diff --git a/lucene/test-framework/src/java/org/apache/lucene/util/TestRuleSetupAndRestoreClassEnv.java b/lucene/test-framework/src/java/org/apache/lucene/util/TestRuleSetupAndRestoreClassEnv.java index 9c63a909ce0..236d1061a77 100644 --- a/lucene/test-framework/src/java/org/apache/lucene/util/TestRuleSetupAndRestoreClassEnv.java +++ b/lucene/test-framework/src/java/org/apache/lucene/util/TestRuleSetupAndRestoreClassEnv.java @@ -87,33 +87,6 @@ final class TestRuleSetupAndRestoreClassEnv extends AbstractBeforeAfterRule { restoreProperties.put("solr.solr.home", System.getProperty("solr.solr.home")); restoreProperties.put("solr.data.dir", System.getProperty("solr.data.dir")); - // enable the Lucene 3.x PreflexRW codec explicitly, to work around bugs in IBM J9 / Harmony ServiceLoader: - try { - final java.lang.reflect.Field spiLoaderField = Codec.class.getDeclaredField("loader"); - spiLoaderField.setAccessible(true); - final Object spiLoader = spiLoaderField.get(null); - final java.lang.reflect.Field modifiableServicesField = NamedSPILoader.class.getDeclaredField("modifiableServices"); - modifiableServicesField.setAccessible(true); - /* note: re-enable this if we make a Lucene4x impersonator - @SuppressWarnings({"unchecked","rawtypes"}) final Map serviceMap = - (Map) modifiableServicesField.get(spiLoader); - if (!(Codec.forName("Lucene3x") instanceof PreFlexRWCodec)) { - if (Constants.JAVA_VENDOR.startsWith("IBM")) { - // definitely a buggy version - System.err.println("ERROR: Your VM's java.util.ServiceLoader implementation is buggy"+ - " and does not respect classpath order, please report this to the vendor."); - } else { - // could just be a classpath issue - System.err.println("ERROR: fix your classpath to have tests-framework.jar before lucene-core.jar!"+ - " If you have already done this, then your VM's java.util.ServiceLoader implementation is buggy"+ - " and does not respect classpath order, please report this to the vendor."); - } - serviceMap.put("Lucene3x", new PreFlexRWCodec()); - } */ - } catch (Exception e) { - throw new RuntimeException("Cannot access internals of Codec and NamedSPILoader classes", e); - } - // if verbose: print some debugging stuff about which codecs are loaded. if (VERBOSE) { Set codecs = Codec.availableCodecs();