This is an automated email from the ASF dual-hosted git repository. desruisseaux pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
The following commit(s) were added to refs/heads/geoapi-4.0 by this push: new b561bf2d1c Remove the `DefaultFactories` class because it does not work in the context of JPMS. Instead, callers need to invoke `java.util.ServiceLoader.load(…)` themselves, because JPMS uses the caller for checking authorizations. b561bf2d1c is described below commit b561bf2d1c370e09b73b2e28737dbeb13ad184e1 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Sat Jun 10 15:18:40 2023 +0200 Remove the `DefaultFactories` class because it does not work in the context of JPMS. Instead, callers need to invoke `java.util.ServiceLoader.load(…)` themselves, because JPMS uses the caller for checking authorizations. --- .../apache/sis/internal/jaxb/TypeRegistration.java | 4 +- .../sis/internal/metadata/ReferencingServices.java | 14 +- .../apache/sis/internal/metadata/package-info.java | 2 +- .../sis/internal/metadata/sql/Initializer.java | 18 +- .../sis/internal/metadata/sql/LocalDataSource.java | 6 +- .../java/org/apache/sis/xml/MarshallerPool.java | 4 +- .../sis/internal/jaxb/gml/TimePeriodTest.java | 2 +- .../apache/sis/internal/referencing/LazySet.java | 4 +- .../sis/referencing/EPSGFactoryFallback.java | 7 +- .../sis/referencing/factory/sql/EPSGInstaller.java | 5 +- .../sis/internal/converter/SystemRegistry.java | 7 +- .../sis/internal/system/DefaultFactories.java | 213 --------------------- .../org/apache/sis/internal/system/Reflect.java | 106 ++++++++++ .../internal/temporal/DefaultTemporalFactory.java | 9 +- .../apache/sis/internal/temporal/package-info.java | 2 +- .../sis/internal/util/TemporalUtilities.java | 45 +++-- .../apache/sis/setup/InstallationResources.java | 15 +- .../org/apache/sis/storage/DataStoreRegistry.java | 6 +- 18 files changed, 200 insertions(+), 269 deletions(-) diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/TypeRegistration.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/TypeRegistration.java index df04748525..fac23dd33c 100644 --- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/TypeRegistration.java +++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/TypeRegistration.java @@ -29,8 +29,8 @@ import java.lang.ref.WeakReference; import jakarta.xml.bind.JAXBContext; import jakarta.xml.bind.JAXBException; import org.apache.sis.internal.system.Modules; +import org.apache.sis.internal.system.Reflect; import org.apache.sis.internal.system.SystemListener; -import org.apache.sis.internal.system.DefaultFactories; import org.apache.sis.internal.system.DelayedExecutor; import org.apache.sis.internal.system.DelayedRunnable; @@ -163,7 +163,7 @@ public abstract class TypeRegistration { private static ServiceLoader<TypeRegistration> services() { ServiceLoader<TypeRegistration> s = services; if (s == null) { - services = s = DefaultFactories.createServiceLoader(TypeRegistration.class); + services = s = ServiceLoader.load(TypeRegistration.class, Reflect.getContextClassLoader()); DelayedExecutor.schedule(new DelayedRunnable(1, TimeUnit.MINUTES) { @Override public void run() {services = null;} }); diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ReferencingServices.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ReferencingServices.java index 84c7e38341..8e297de80f 100644 --- a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ReferencingServices.java +++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ReferencingServices.java @@ -31,7 +31,6 @@ import org.apache.sis.metadata.iso.extent.DefaultVerticalExtent; import org.apache.sis.metadata.iso.extent.DefaultTemporalExtent; import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox; import org.apache.sis.metadata.iso.extent.DefaultSpatialTemporalExtent; -import org.apache.sis.internal.system.DefaultFactories; import org.apache.sis.internal.system.OptionalDependency; import org.apache.sis.internal.system.Modules; @@ -45,7 +44,7 @@ import org.apache.sis.internal.system.Modules; * <cite>"referencing by coordinates"</cite> but needed by metadata.</p> * * @author Martin Desruisseaux (Geomatys) - * @version 1.3 + * @version 1.4 * @since 0.3 */ public class ReferencingServices extends OptionalDependency { @@ -278,20 +277,11 @@ public class ReferencingServices extends OptionalDependency { /** * Returns the default coordinate operation factory. - * The default implementation is used when the referencing module is not on the classpath, - * in which case this method uses whatever implementation is found by the service loader. - * Otherwise (if the referencing module is present), this method will be overridden with - * the SIS specific implementation. * * @return the coordinate operation factory to use. */ public CoordinateOperationFactory getCoordinateOperationFactory() { - final CoordinateOperationFactory factory = DefaultFactories.forClass(CoordinateOperationFactory.class); - if (factory != null) { - return factory; - } else { - throw moduleNotFound(); - } + throw moduleNotFound(); } /** diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/package-info.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/package-info.java index a825ad016e..3280628ec4 100644 --- a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/package-info.java +++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/package-info.java @@ -24,7 +24,7 @@ * may change in incompatible ways in any future version without notice. * * @author Martin Desruisseaux (Geomatys) - * @version 1.3 + * @version 1.4 * @since 0.3 */ package org.apache.sis.internal.metadata; diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/Initializer.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/Initializer.java index 45d7946804..b2b2218133 100644 --- a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/Initializer.java +++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/Initializer.java @@ -19,13 +19,14 @@ package org.apache.sis.internal.metadata.sql; import java.util.Locale; import java.util.function.Supplier; import java.util.concurrent.Callable; -import java.io.IOException; +import java.util.ServiceLoader; import java.util.logging.Level; import java.util.logging.LogRecord; import javax.sql.DataSource; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.SQLException; +import java.io.IOException; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; @@ -38,10 +39,10 @@ import javax.naming.event.NamingExceptionEvent; import javax.naming.event.ObjectChangeListener; import org.apache.sis.setup.InstallationResources; import org.apache.sis.internal.system.Configuration; -import org.apache.sis.internal.system.DefaultFactories; import org.apache.sis.internal.system.DataDirectory; import org.apache.sis.internal.system.SystemListener; import org.apache.sis.internal.system.Shutdown; +import org.apache.sis.internal.system.Reflect; import org.apache.sis.util.resources.Messages; import org.apache.sis.util.logging.Logging; @@ -111,6 +112,15 @@ public abstract class Initializer { protected Initializer() { } + /** + * Returns initializers found on the class path. + * + * @return initializers found on the class path. + */ + public static ServiceLoader<Initializer> load() { + return ServiceLoader.load(Initializer.class, Reflect.getContextClassLoader()); + } + /** * Invoked for populating an initially empty database. * @@ -191,7 +201,7 @@ public abstract class Initializer { */ Logging.recoverableException(SystemListener.LOGGER, Listener.class, "objectChanged", e); } - for (Initializer init : DefaultFactories.createServiceLoader(Initializer.class)) { + for (Initializer init : load()) { init.dataSourceChanged(); } } @@ -374,7 +384,7 @@ public abstract class Initializer { * @since 0.8 */ private static DataSource embedded() { - for (InstallationResources res : DefaultFactories.createServiceLoader(InstallationResources.class)) { + for (InstallationResources res : InstallationResources.load()) { if (res.getAuthorities().contains(EMBEDDED)) try { final String[] names = res.getResourceNames(EMBEDDED); for (int i=0; i<names.length; i++) { diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/LocalDataSource.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/LocalDataSource.java index b5923c713a..f9a20817ed 100644 --- a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/LocalDataSource.java +++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/LocalDataSource.java @@ -33,7 +33,7 @@ import org.apache.sis.util.ArraysExt; import org.apache.sis.util.logging.Logging; import org.apache.sis.internal.system.Loggers; import org.apache.sis.internal.system.DataDirectory; -import org.apache.sis.internal.system.DefaultFactories; +import org.apache.sis.internal.system.Reflect; import org.apache.sis.internal.util.Strings; @@ -203,7 +203,7 @@ public final class LocalDataSource implements DataSource, Comparable<LocalDataSo case HSQL: classname = "org.hsqldb.jdbc.JDBCDataSource"; break; default: throw new IllegalArgumentException(dialect.toString()); } - final ClassLoader loader = DefaultFactories.getContextClassLoader(); + final ClassLoader loader = Reflect.getContextClassLoader(); final Class<?> c = Class.forName(classname, true, loader); source = (DataSource) c.getConstructor().newInstance(); final Class<?>[] args = {String.class}; @@ -258,7 +258,7 @@ public final class LocalDataSource implements DataSource, Comparable<LocalDataSo default: enabler = null; break; } try (Connection c = source.getConnection()) { - for (Initializer init : DefaultFactories.createServiceLoader(Initializer.class)) { + for (Initializer init : Initializer.load()) { init.createSchema(c); } } finally { diff --git a/core/sis-metadata/src/main/java/org/apache/sis/xml/MarshallerPool.java b/core/sis-metadata/src/main/java/org/apache/sis/xml/MarshallerPool.java index 80b42769a8..a619e14691 100644 --- a/core/sis-metadata/src/main/java/org/apache/sis/xml/MarshallerPool.java +++ b/core/sis-metadata/src/main/java/org/apache/sis/xml/MarshallerPool.java @@ -26,10 +26,10 @@ import jakarta.xml.bind.JAXBException; import jakarta.xml.bind.Marshaller; import jakarta.xml.bind.Unmarshaller; import org.apache.sis.util.logging.Logging; +import org.apache.sis.internal.system.Reflect; import org.apache.sis.internal.system.Configuration; import org.apache.sis.internal.system.DelayedExecutor; import org.apache.sis.internal.system.DelayedRunnable; -import org.apache.sis.internal.system.DefaultFactories; import org.apache.sis.internal.jaxb.AdapterReplacement; import org.apache.sis.internal.jaxb.TypeRegistration; import org.apache.sis.internal.jaxb.Context; @@ -180,7 +180,7 @@ public class MarshallerPool { public MarshallerPool(final JAXBContext context, final Map<String,?> properties) throws JAXBException { ArgumentChecks.ensureNonNull("context", context); this.context = context; - replacements = DefaultFactories.createServiceLoader(AdapterReplacement.class); + replacements = ServiceLoader.load(AdapterReplacement.class, Reflect.getContextClassLoader()); implementation = Implementation.detect(context); /* * Prepares a copy of the property map (if any), then removes the diff --git a/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/gml/TimePeriodTest.java b/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/gml/TimePeriodTest.java index 9fea8fa905..bd35cbeeb1 100644 --- a/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/gml/TimePeriodTest.java +++ b/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/gml/TimePeriodTest.java @@ -68,7 +68,7 @@ public final class TimePeriodTest extends TestCase { * Creates a GeoAPI instant object for the given date. */ private static Instant instant(final String date) { - return DefaultTemporalFactory.INSTANCE.createInstant(date(date)); + return DefaultTemporalFactory.provider().createInstant(date(date)); } /** diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/LazySet.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/LazySet.java index aa77fb140d..e70e9da650 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/LazySet.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/LazySet.java @@ -22,7 +22,7 @@ import java.util.Objects; import java.util.Iterator; import java.util.ServiceLoader; import java.util.NoSuchElementException; -import org.apache.sis.internal.system.DefaultFactories; +import org.apache.sis.internal.system.Reflect; import org.apache.sis.internal.util.SetOfUnknownSize; import org.apache.sis.internal.util.UnmodifiableArrayList; @@ -151,7 +151,7 @@ public class LazySet<E> extends SetOfUnknownSize<E> { */ private boolean canPullMore() { if (sourceIterator == null && cachedElements == null) { - sourceIterator = DefaultFactories.createServiceLoader(service).iterator(); + sourceIterator = ServiceLoader.load(service, Reflect.getContextClassLoader()).iterator(); if (createCache()) { return true; } diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/EPSGFactoryFallback.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/EPSGFactoryFallback.java index 7fbbe71697..e50ac160b2 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/EPSGFactoryFallback.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/EPSGFactoryFallback.java @@ -18,6 +18,7 @@ package org.apache.sis.referencing; import java.util.Set; import java.util.LinkedHashSet; +import java.util.ServiceLoader; import javax.measure.Unit; import org.opengis.referencing.IdentifiedObject; import org.opengis.referencing.NoSuchAuthorityCodeException; @@ -43,7 +44,6 @@ import org.opengis.metadata.citation.Citation; import org.apache.sis.referencing.factory.GeodeticAuthorityFactory; import org.apache.sis.internal.referencing.provider.TransverseMercator; import org.apache.sis.internal.referencing.Resources; -import org.apache.sis.internal.system.DefaultFactories; import org.apache.sis.internal.system.Fallback; import org.apache.sis.internal.util.MetadataServices; import org.apache.sis.internal.util.Constants; @@ -63,7 +63,7 @@ import org.apache.sis.measure.Units; * The EPSG identifiers are provided as references where to find the complete definitions. * * @author Martin Desruisseaux (Geomatys) - * @version 1.3 + * @version 1.4 * @since 0.7 */ @Fallback @@ -364,8 +364,7 @@ final class EPSGFactoryFallback extends GeodeticAuthorityFactory private synchronized String getInstallationURL() { if (installationURL == null) { installationURL = URLs.EPSG_INSTALL; // To be used as fallback. - final Iterable<InstallationResources> services = - DefaultFactories.createServiceLoader(InstallationResources.class); + final ServiceLoader<InstallationResources> services = InstallationResources.load(); /* * Following loop will be executed one or two times. First, we check for resources that are * specifically for EPSG geodetic dataset. If none are found, fallback on embedded database. diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGInstaller.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGInstaller.java index b65c097978..4c93879553 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGInstaller.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGInstaller.java @@ -29,7 +29,6 @@ import java.io.BufferedReader; import org.apache.sis.util.StringBuilders; import org.apache.sis.internal.metadata.sql.ScriptRunner; import org.apache.sis.internal.metadata.sql.SQLUtilities; -import org.apache.sis.internal.system.DefaultFactories; import org.apache.sis.internal.util.StandardDateFormat; import org.apache.sis.internal.system.Fallback; import org.apache.sis.util.Exceptions; @@ -47,7 +46,7 @@ import static org.apache.sis.internal.util.Constants.EPSG; * in the test directory for more information about how the scripts are formatted. * * @author Martin Desruisseaux (Geomatys) - * @version 1.2 + * @version 1.4 * @since 0.7 */ final class EPSGInstaller extends ScriptRunner { @@ -274,7 +273,7 @@ final class EPSGInstaller extends ScriptRunner { */ private static InstallationResources lookupProvider(final Locale locale) throws IOException { InstallationResources fallback = null; - for (final InstallationResources provider : DefaultFactories.createServiceLoader(InstallationResources.class)) { + for (final InstallationResources provider : InstallationResources.load()) { if (provider.getAuthorities().contains(EPSG)) { if (!provider.getClass().isAnnotationPresent(Fallback.class)) { return provider; diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/converter/SystemRegistry.java b/core/sis-utility/src/main/java/org/apache/sis/internal/converter/SystemRegistry.java index 679da2ab24..de13b3569c 100644 --- a/core/sis-utility/src/main/java/org/apache/sis/internal/converter/SystemRegistry.java +++ b/core/sis-utility/src/main/java/org/apache/sis/internal/converter/SystemRegistry.java @@ -17,12 +17,13 @@ package org.apache.sis.internal.converter; import java.util.Date; +import java.util.ServiceLoader; import org.opengis.util.CodeList; import org.apache.sis.util.Numbers; import org.apache.sis.util.ObjectConverter; import org.apache.sis.util.UnconvertibleObjectException; -import org.apache.sis.internal.system.DefaultFactories; import org.apache.sis.internal.system.SystemListener; +import org.apache.sis.internal.system.Reflect; import org.apache.sis.internal.system.Modules; @@ -49,7 +50,7 @@ import org.apache.sis.internal.system.Modules; * The same {@link #INSTANCE} can be safely used by many threads without synchronization on the part of the caller. * * @author Martin Desruisseaux (Geomatys) - * @version 0.8 + * @version 1.4 * @since 0.3 */ public final class SystemRegistry extends ConverterRegistry { @@ -105,7 +106,7 @@ public final class SystemRegistry extends ConverterRegistry { */ @Override protected void initialize() { - for (ObjectConverter<?,?> converter : DefaultFactories.createServiceLoader(ObjectConverter.class)) { + for (ObjectConverter<?,?> converter : ServiceLoader.load(ObjectConverter.class, Reflect.getContextClassLoader())) { if (converter instanceof SystemConverter<?,?>) { converter = ((SystemConverter<?,?>) converter).unique(); } diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/system/DefaultFactories.java b/core/sis-utility/src/main/java/org/apache/sis/internal/system/DefaultFactories.java deleted file mode 100644 index c5e90963cd..0000000000 --- a/core/sis-utility/src/main/java/org/apache/sis/internal/system/DefaultFactories.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * 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.sis.internal.system; - -import java.util.Set; -import java.util.Map; -import java.util.HashSet; -import java.util.IdentityHashMap; -import java.util.ServiceLoader; -import java.util.ServiceConfigurationError; -import java.util.function.Consumer; -import org.apache.sis.util.logging.Logging; - - -/** - * Default factories defined in the {@code sis-utility} module. - * This is a temporary placeholder until we leverage the "dependency injection" pattern. - * A candidate replacement is JSR-330. - * - * @author Martin Desruisseaux (Geomatys) - * @author Guilhem Legal (Geomatys) - * @version 1.4 - * - * @see <a href="https://jcp.org/en/jsr/detail?id=330">JSR-330</a> - * @see <a href="https://issues.apache.org/jira/browse/SIS-102">SIS-102</a> - * - * @since 0.3 - */ -public final class DefaultFactories extends SystemListener { - /** - * Cache of factories found by {@link ServiceLoader} from {@code META-INF/services} files content. - */ - private static final Map<Class<?>, Object> FACTORIES = new IdentityHashMap<>(4); - static { - SystemListener.add(new DefaultFactories()); - } - - /** - * For the singleton system listener only. - */ - private DefaultFactories() { - super(Modules.UTILITIES); - } - - /** - * Discards cached factories when the classpath has changed. - */ - @Override - protected void classpathChanged() { - synchronized (DefaultFactories.class) { - FACTORIES.clear(); - } - } - - /** - * Returns the default factory implementing the given interface. - * This method gives preference to Apache SIS implementation of factories if present. - * This is a temporary mechanism while we are waiting for a real dependency injection mechanism. - * - * @param <T> the interface type. - * @param type the interface type. - * @return a factory implementing the given interface, or {@code null} if none. - */ - public static synchronized <T> T forClass(final Class<T> type) { - T factory = type.cast(FACTORIES.get(type)); - if (factory == null && !FACTORIES.containsKey(type)) { - T fallback = null; - for (final T candidate : createServiceLoader(type)) { - if (candidate.getClass().getName().startsWith(Modules.CLASSNAME_PREFIX)) { - if (factory != null) { - throw new ServiceConfigurationError("Found two implementations of " + type); - } - factory = candidate; - } else if (fallback == null) { - fallback = candidate; - } - } - if (factory == null) { - factory = fallback; - } - /* - * Verifies if the factory that we just selected is the same implementation than an existing instance. - * The main case for this test is org.apache.sis.referencing.factory.GeodeticObjectFactory, where the - * same class implements 3 factory interfaces. - */ - if (factory != null) { - for (final Object existing : FACTORIES.values()) { - if (existing != null && factory.getClass().equals(existing.getClass())) { - factory = type.cast(existing); - break; - } - } - } - FACTORIES.put(type, factory); - } - return factory; - } - - /** - * Returns a service loader for the given type using the default class loader. - * The default is the current thread {@linkplain Thread#getContextClassLoader() context class loader}, - * provided that it can access at least the Apache SIS stores. - * - * @param <T> the compile-time value of {@code service} argument. - * @param service the interface or abstract class representing the service. - * @return a new service loader for the given service type. - * - * @since 0.8 - */ - public static <T> ServiceLoader<T> createServiceLoader(final Class<T> service) { - try { - return ServiceLoader.load(service, getContextClassLoader()); - } catch (SecurityException e) { - /* - * We were not allowed to invoke Thread.currentThread().getContextClassLoader(). - * But ServiceLoader.load(Class) may be allowed to, since it is part of JDK. - */ - Logging.recoverableException(SystemListener.LOGGER, DefaultFactories.class, "createServiceLoader", e); - return ServiceLoader.load(service); - } - } - - /** - * Returns the context class loader, but makes sure that it has Apache SIS on its classpath. - * First, this method invokes {@link Thread#getContextClassLoader()} for the current thread. - * Then this method scans over all Apache SIS classes on the stack trace. For each SIS class, - * its loader is compared to the above-cited context class loader. If the context class loader - * is equal or is a child of the SIS loader, then it is left unchanged. Otherwise the context - * class loader is replaced by the SIS one. - * - * <p>The intent of this method is to ensure that {@link ServiceLoader#load(Class)} will find the - * Apache SIS services even in an environment that defined an unsuitable context class loader.</p> - * - * @return the context class loader if suitable, or another class loader otherwise. - * @throws SecurityException if this method is not allowed to get the current thread - * context class loader or one of its parent. - * - * @since 0.8 - */ - public static ClassLoader getContextClassLoader() throws SecurityException { - final Walker walker = new Walker(); - return StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE).walk((stream) -> { - stream.forEach(walker); - return walker.loader; - }); - } - - /** - * Action to be executed for each stack frame inspected by {@link #getContextClassLoader()}. - * The action is initialized to the context class loader of current thread. - * Then it checks if the class loader should be replaced by another one containing at least - * the Apache SIS class loader. - */ - private static final class Walker implements Consumer<StackWalker.StackFrame> { - /** - * The context class loader to be returned by {@link #getContextClassLoader()}. - */ - ClassLoader loader; - - /** - * All parents of {@link #loader}. - */ - private final Set<ClassLoader> parents; - - /** - * Creates a new walker initialized to the context class loader of current thread. - */ - Walker() { - parents = new HashSet<>(); - setClassLoader(Thread.currentThread().getContextClassLoader()); - } - - /** - * Set the class loader to the given value, which may be null. - */ - private void setClassLoader(ClassLoader c) { - loader = c; - while (c != null) { - parents.add(c); - c = c.getParent(); - } - } - - /** - * If the given stack frame is an Apache SIS method, ensures that {@link #loader} - * is the SIS class loader or has the SIS class loader as a parent. - */ - @Override - public void accept(final StackWalker.StackFrame frame) { - if (frame.getClassName().startsWith(Modules.CLASSNAME_PREFIX)) { - ClassLoader c = frame.getDeclaringClass().getClassLoader(); - if (!parents.contains(c)) { - parents.clear(); - setClassLoader(c); - } - } - } - } -} diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/system/Reflect.java b/core/sis-utility/src/main/java/org/apache/sis/internal/system/Reflect.java new file mode 100644 index 0000000000..35808d7986 --- /dev/null +++ b/core/sis-utility/src/main/java/org/apache/sis/internal/system/Reflect.java @@ -0,0 +1,106 @@ +/* + * 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.sis.internal.system; + +import java.util.Set; +import java.util.HashSet; +import java.util.ServiceLoader; +import java.util.function.Consumer; + + +/** + * Contextual information fetching by reflection. + * + * @author Martin Desruisseaux (Geomatys) + * @author Guilhem Legal (Geomatys) + * @version 1.4 + * @since 0.8 + */ +public final class Reflect implements Consumer<StackWalker.StackFrame> { + /** + * Returns the context class loader, but makes sure that it has Apache SIS on its classpath. + * First, this method invokes {@link Thread#getContextClassLoader()} for the current thread. + * Then this method scans over all Apache SIS classes on the stack trace. For each SIS class, + * its loader is compared to the above-cited context class loader. If the context class loader + * is equal or is a child of the SIS loader, then it is left unchanged. Otherwise the context + * class loader is replaced by the SIS one. + * + * <p>The intent of this method is to ensure that {@link ServiceLoader#load(Class)} will find the + * Apache SIS services even in an environment that defined an unsuitable context class loader. + * Note that the call to {@code ServiceLoader.load(…)} must be done from the caller class. + * We cannot provide this convenience in this class because of JPMS encapsulation.</p> + * + * @return the context class loader if suitable, or another class loader otherwise. + * @throws SecurityException if this method is not allowed to get the current thread + * context class loader or one of its parent. + */ + public static ClassLoader getContextClassLoader() throws SecurityException { + final Reflect walker = new Reflect(); + return StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE).walk((stream) -> { + stream.forEach(walker); + return walker.loader; + }); + } + + /** + * The context class loader to be returned by {@link #getContextClassLoader()}. + */ + private ClassLoader loader; + + /** + * All parents of {@link #loader}. + */ + private final Set<ClassLoader> parents; + + /** + * Creates a new walker initialized to the context class loader of current thread. + */ + private Reflect() { + parents = new HashSet<>(); + setClassLoader(Thread.currentThread().getContextClassLoader()); + } + + /** + * Set the class loader to the given value, which may be null. + */ + private void setClassLoader(ClassLoader c) { + loader = c; + while (c != null) { + parents.add(c); + c = c.getParent(); + } + } + + /** + * Action to be executed for each stack frame inspected by {@link #getContextClassLoader()}. + * The action is initialized to the context class loader of current thread. + * Then it checks if the class loader should be replaced by another one + * containing at least the Apache SIS class loader. + * + * @param frame a stack frame being inspected. + */ + @Override + public void accept(final StackWalker.StackFrame frame) { + if (frame.getClassName().startsWith(Modules.CLASSNAME_PREFIX)) { + ClassLoader c = frame.getDeclaringClass().getClassLoader(); + if (!parents.contains(c)) { + parents.clear(); + setClassLoader(c); + } + } + } +} diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/DefaultTemporalFactory.java b/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/DefaultTemporalFactory.java index 4f1573343a..c69ef42d31 100644 --- a/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/DefaultTemporalFactory.java +++ b/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/DefaultTemporalFactory.java @@ -32,12 +32,17 @@ import org.apache.sis.util.resources.Errors; * GeoAPI temporal interfaces are expected to change a lot in a future revision. * * @author Martin Desruisseaux (Geomatys) - * @version 1.2 + * @version 1.4 * @since 1.2 */ public final class DefaultTemporalFactory implements TemporalFactory { /** The unique instance of this factory. */ - public static final TemporalFactory INSTANCE = new DefaultTemporalFactory(); + private static final TemporalFactory INSTANCE = new DefaultTemporalFactory(); + + /** {@return the unique instance of this factory}. */ + public static TemporalFactory provider() { + return INSTANCE; + } /** Creates the singleton instance. */ private DefaultTemporalFactory() { diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/package-info.java b/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/package-info.java index da91b0c8d7..e616a33332 100644 --- a/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/package-info.java +++ b/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/package-info.java @@ -20,7 +20,7 @@ * the temporal GeoAPI interfaces are expected to change a lot in a future revision. * * @author Martin Desruisseaux (Geomatys) - * @version 1.2 + * @version 1.4 * @since 1.2 */ package org.apache.sis.internal.temporal; diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/TemporalUtilities.java b/core/sis-utility/src/main/java/org/apache/sis/internal/util/TemporalUtilities.java index 4955bc7c17..4d991af9b0 100644 --- a/core/sis-utility/src/main/java/org/apache/sis/internal/util/TemporalUtilities.java +++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/TemporalUtilities.java @@ -17,12 +17,14 @@ package org.apache.sis.internal.util; import java.util.Date; +import java.util.ServiceLoader; import org.opengis.temporal.Instant; import org.opengis.temporal.Period; import org.opengis.temporal.TemporalFactory; import org.opengis.temporal.TemporalPrimitive; -import org.apache.sis.util.Static; -import org.apache.sis.internal.system.DefaultFactories; +import org.apache.sis.internal.system.Modules; +import org.apache.sis.internal.system.Reflect; +import org.apache.sis.internal.system.SystemListener; import org.apache.sis.internal.temporal.DefaultTemporalFactory; @@ -32,28 +34,47 @@ import org.apache.sis.internal.temporal.DefaultTemporalFactory; * * @author Martin Desruisseaux (Geomatys) * @author Guilhem Legal (Geomatys) - * @version 1.2 + * @version 1.4 * @since 0.3 */ -public final class TemporalUtilities extends Static { +public final class TemporalUtilities extends SystemListener { /** - * Do not allow instantiation of this class. + * The default factory to use for implementations. + */ + private static volatile TemporalFactory implementation; + + static { + SystemListener.add(new TemporalUtilities()); + } + + /** + * For the singleton system listener only. */ private TemporalUtilities() { + super(Modules.METADATA); } /** - * Returns a temporal factory if available. + * Discards the cached factory when the classpath has changed. + */ + @Override + protected void classpathChanged() { + implementation = null; + } + + /** + * Returns a temporal factory, or a default implementation if none. * * @return the temporal factory. - * @throws UnsupportedOperationException if the temporal factory is not available on the classpath. */ - public static TemporalFactory getTemporalFactory() throws UnsupportedOperationException { - final TemporalFactory factory = DefaultFactories.forClass(TemporalFactory.class); - if (factory != null) { - return factory; + public static TemporalFactory getTemporalFactory() { + TemporalFactory factory = implementation; + if (factory == null) { + factory = ServiceLoader.load(TemporalFactory.class, Reflect.getContextClassLoader()) + .findFirst().orElseGet(DefaultTemporalFactory::provider); + implementation = factory; } - return DefaultTemporalFactory.INSTANCE; + return factory; } /** diff --git a/core/sis-utility/src/main/java/org/apache/sis/setup/InstallationResources.java b/core/sis-utility/src/main/java/org/apache/sis/setup/InstallationResources.java index 63c86d07c6..8d12b842cd 100644 --- a/core/sis-utility/src/main/java/org/apache/sis/setup/InstallationResources.java +++ b/core/sis-utility/src/main/java/org/apache/sis/setup/InstallationResources.java @@ -18,11 +18,13 @@ package org.apache.sis.setup; import java.util.Set; import java.util.Locale; +import java.util.ServiceLoader; import java.io.IOException; import java.io.BufferedReader; import org.apache.sis.internal.util.URLs; import org.apache.sis.internal.util.Constants; import org.apache.sis.internal.util.MetadataServices; +import org.apache.sis.internal.system.Reflect; /** @@ -66,7 +68,7 @@ import org.apache.sis.internal.util.MetadataServices; * for writing the database; see above-cited web page for more information). * * @author Martin Desruisseaux (Geomatys) - * @version 1.2 + * @version 1.4 * @since 0.7 */ public abstract class InstallationResources { @@ -76,6 +78,17 @@ public abstract class InstallationResources { protected InstallationResources() { } + /** + * Returns installation resources found on the class path. + * + * @return installation resources found on the class path. + * + * @since 1.4 + */ + public static ServiceLoader<InstallationResources> load() { + return ServiceLoader.load(InstallationResources.class, Reflect.getContextClassLoader()); + } + /** * Returns identifiers of the resources provided by this instance. * The values recognized by SIS are listed below diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreRegistry.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreRegistry.java index 9f850b5f31..2290c9ad63 100644 --- a/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreRegistry.java +++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreRegistry.java @@ -21,9 +21,9 @@ import java.util.LinkedList; import java.util.Set; import java.util.Iterator; import java.util.ServiceLoader; +import org.apache.sis.internal.system.Reflect; import org.apache.sis.internal.storage.Resources; import org.apache.sis.internal.storage.StoreMetadata; -import org.apache.sis.internal.system.DefaultFactories; import org.apache.sis.internal.referencing.LazySet; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.ArraysExt; @@ -43,7 +43,7 @@ import org.apache.sis.util.ArraysExt; * on the part of the caller. * * @author Martin Desruisseaux (Geomatys) - * @version 1.1 + * @version 1.4 * @since 0.4 */ final class DataStoreRegistry { @@ -60,7 +60,7 @@ final class DataStoreRegistry { * provided that it can access at least the Apache SIS stores. */ public DataStoreRegistry() { - loader = DefaultFactories.createServiceLoader(DataStoreProvider.class); + this(Reflect.getContextClassLoader()); } /**