/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2005-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2009-2012, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotoolkit.factory;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.LogRecord;
import javax.imageio.spi.ServiceRegistry;
import org.opengis.metadata.quality.ConformanceResult;
import org.geotoolkit.lang.Debug;
import org.apache.sis.util.CharSequences;
import org.apache.sis.util.logging.Logging;
import org.apache.sis.util.Classes;
import org.geotoolkit.resources.Errors;
import org.geotoolkit.resources.Loggings;
import static org.apache.sis.util.collection.Containers.isNullOrEmpty;
/**
* A registry for factories organized by categories. A category is an interface for a
* <cite>service</cite> as described in the {@linkplain org.geotoolkit.factory package javadoc}.
* {@code FactoryRegistry} extends {@link ServiceRegistry} with the following functionalities:
*
* <ul>
* <li><p>A {@link #scanForPlugins()} method that scans for plugins in
* the {@linkplain Class#getClassLoader registry class loader},
* the {@linkplain Thread#getContextClassLoader thread context class loader} and
* the {@linkplain ClassLoader#getSystemClassLoader system class loader}.</p></li>
*
* <li><p>When more than one implementation is available for the same {@link Factory} subclass,
* an optional set of {@linkplain Hints hints} can specifies the criterion that the desired
* implementation must meets. If a factory implementation depends on other factories, the
* dependencies hints are checked recursively.</p></li>
*
* <li><p><b>Optionally</b>, if no factory matches the given hints, a new instance can be
* {@linkplain DynamicFactoryRegistry#createServiceProvider automatically created}.</p></li>
* </ul>
*
* When more than one {@linkplain Factory factory} implementation is registered for the same category,
* the actual instance to be used is selected according their {@linkplain ServiceRegistry#setOrdering
* ordering} and user-supplied {@linkplain Hints hints}. Hints have precedence. If more than one factory
* matches the hints (including the common case where the user doesn't provide any hint at all), then
* ordering matter.
* <p>
* <strong>NOTE: This class is not thread safe</strong>. Users are responsible
* for synchronization. This is usually done in an utility class wrapping this
* service registry (e.g. {@link org.geotoolkit.factory.FactoryFinder}).
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @author Richard Gould
* @author Jody Garnett (Refractions)
* @version 3.03
*
* @see Factory
* @see Hints
*
* @since 2.1
* @module
*/
public class FactoryRegistry extends ServiceRegistry {
/**
* The logger for all events related to factory registry.
*/
protected static final Logger LOGGER = Logging.getLogger("org.geotoolkit.factory");
/**
* The logger level for debug messages.
*/
@Debug
private static final Level DEBUG_LEVEL = Level.FINEST;
/**
* A copy of the global configuration defined through {@link FactoryIteratorProviders}
* static methods. We keep a copy in every {@code FactoryRegistry} instance in order to
* compare against the master {@link FactoryIteratorProviders#GLOBAL} and detect if the
* configuration changed since the last time this registry was used.
*
* @see #synchronizeIteratorProviders
*/
private final FactoryIteratorProviders globalConfiguration = new FactoryIteratorProviders();
/**
* The set of categories that need to be scanned for plugins, or {@code null} if none.
* On initialization, all categories need to be scanned for plugins. After a category
* has been first used, it is removed from this set so we don't scan for plugins again.
*/
private Set<Class<?>> needScanForPlugins;
/**
* Categories under scanning. This is used by {@link #scanForPlugins(Collection,Class)}
* as a guard against infinite recursivity (i.e. when a factory to be scanned request
* an other dependency of the same category).
*/
private final Set<Class<?>> scanningCategories = new HashSet<>();
/**
* Factories under testing for availability. This is used by
* {@link #isAcceptable} as a guard against infinite recursivity.
*/
private final Set<Class<? extends Factory>> testingAvailability = new HashSet<>();
/**
* Factories under testing for hints compatibility. This is used by
* {@link #usesAcceptableHints} as a guard against infinite recursivity.
*/
private final Map<Factory,Boolean> testingHints = new IdentityHashMap<>();
/**
* If a factory is not available because of some exception, the exception. Otherwise {@code null}.
* This is a temporary field used only during execution of {@code getServiceProvider} methods.
*/
private transient Throwable failureCause;
/**
* Constructs a new registry for the specified category.
*
* @param category The single category.
*
* @since 2.4
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public FactoryRegistry(final Class<?> category) {
this((Collection) Collections.singleton(category));
// Safe because java.lang.Class is final.
}
/**
* Constructs a new registry for the specified categories.
*
* @param categories The categories.
*
* @since 2.4
*/
public FactoryRegistry(final Class<?>[] categories) {
this(Arrays.asList(categories));
}
/**
* Constructs a new registry for the specified categories.
*
* @param categories The categories.
*/
public FactoryRegistry(final Collection<Class<?>> categories) {
super(categories.iterator());
for (final Iterator<Class<?>> it=getCategories(); it.hasNext();) {
if (needScanForPlugins == null) {
needScanForPlugins = new HashSet<>();
}
needScanForPlugins.add(it.next());
}
}
/**
* Returns the providers in the registry for the specified category, filter and hints.
* Providers that are not {@linkplain Factory#availability available} will be ignored.
* This method will {@linkplain #scanForPlugins() scan for plugins} the first time it
* is invoked for the given category.
*
* @param <T> The class represented by the {@code category} argument.
* @param category The category to look for. Usually an interface class
* (not the actual implementation class).
* @param filter The optional filter, or {@code null}.
* @param hints The optional user requirements, or {@code null}.
* @param key The key to use for looking for a user-provided instance
* in the {@code hints} map, or {@code null} if none.
* @return Factories ready to use for the specified category, filter and hints.
*
* @since 3.03 (derived from 2.3)
*/
public <T> Iterator<T> getServiceProviders(final Class<T> category,
final Filter filter, Hints hints, final Hints.ClassKey key)
{
final Class<?>[] requestedType;
if (hints != null && key != null && hints.get(key) != null) {
hints = hints.clone();
final Object value = hints.remove(key);
if (value instanceof Class<?>) {
requestedType = new Class<?>[] {(Class<?>) value};
} else if (value instanceof Class<?>[]) {
requestedType = ((Class<?>[]) value).clone();
} else {
/*
* If the user gaves explicitly a factory, returns only that factory
* provided that it meets other hints. Otherwise returns an empty set.
*/
Collection<T> values = Collections.emptySet();
if (key.getValueClass().isInstance(value)) {
final T provider = category.cast(value);
if (isAcceptable(provider, category, hints, filter, false)) {
values = Collections.singleton(provider);
}
}
return values.iterator();
}
} else {
requestedType = null;
}
/*
* The implementation of this method is very similar to the 'getUnfilteredProviders'
* one except for filter handling. See the comments in 'getUnfilteredProviders' for
* more implementation details.
*/
if (scanningCategories.contains(category)) {
// We reach this point if we accidentally allow more than
// one thread to use the FactoryRegistry at a time.
throw new RecursiveSearchException(category);
}
final Hints userHints = hints;
final Filter hintsFilter = new Filter() {
@Override public boolean filter(final Object provider) {
if (requestedType != null) {
int i=0;
do if (i == requestedType.length) return false;
while (!requestedType[i++].isInstance(provider));
}
return isAcceptable(category.cast(provider), category, userHints, filter, false);
}
};
synchronizeIteratorProviders();
scanForPluginsIfNeeded(category);
return getServiceProviders(category, hintsFilter, true);
}
/**
* Implementation of {@link #getServiceProviders(Class, Filter, Hints)} without the filtering
* applied by the {@link #isAcceptable(Object, Class, Hints, Filter)} method. If this filtering
* is not already presents in the filter given to this method, then it must be applied on the
* elements returned by the iterator. The later is preferable when:
* <p>
* <ul>
* <li>There is some cheaper tests to perform before {@code isAcceptable}.</li>
* <li>We don't want a restrictive filter in order to avoid trigging a classpath
* scan if this method doesn't found any element to iterate.</li>
* </ul>
* <p>
* <b>Note:</b>
* {@link #synchronizeIteratorProviders} should also be invoked once before this method.
*/
final <T> Iterator<T> getUnfilteredProviders(final Class<T> category) {
/*
* If the map is not empty, then this mean that a scanning is under progress, i.e.
* 'scanForPlugins' is currently being executed. This is okay as long as the user
* is not asking for one of the categories under scanning. Otherwise, the answer
* returned by 'getServiceProviders' would be incomplete because not all plugins
* have been found yet. This can lead to some bugs hard to spot because this methoud
* could complete normally but return the wrong plugin. It is safer to thrown an
* exception so the user is advised that something is wrong.
*/
if (scanningCategories.contains(category)) {
throw new RecursiveSearchException(category);
}
scanForPluginsIfNeeded(category);
return getServiceProviders(category, true);
}
/**
* Returns the first provider in the registry for the specified category, using the specified
* map of hints (if any). This method may {@linkplain #scanForPlugins scan for plugins} the
* first time it is invoked. Except as a result of this scan, no new provider instance is
* created by the default implementation of this method. The {@link DynamicFactoryRegistry}
* class change this behavior however.
*
* @param <T> The class represented by the {@code category} argument.
* @param category The category to look for. Must be one of the categories declared to the
* constructor. Usually an interface class (not the actual implementation
* class).
* @param filter An optional filter, or {@code null} if none.
* This is used for example in order to select the first factory for some
* {@linkplain org.opengis.referencing.AuthorityFactory#getAuthority authority}.
* @param hints A {@linkplain Hints map of hints}, or {@code null} if none.
* @param key The key to use for looking for a user-provided instance in the hints, or
* {@code null} if none.
* @return A factory {@linkplain Factory#availability available} for use for the specified
* category and hints. The returns type is {@code Object} instead of {@link Factory}
* because the factory implementation doesn't need to be a Geotk one.
* @throws FactoryNotFoundException if no factory was found for the specified category, filter
* and hints.
* @throws FactoryRegistryException if a factory can't be returned for some other reason.
*
* @see #getServiceProviders
* @see DynamicFactoryRegistry#getServiceProvider
*/
public <T> T getServiceProvider(final Class<T> category, final Filter filter,
Hints hints, final Hints.ClassKey key) throws FactoryRegistryException
{
// The current 'failureCause' should be null,
// except if this method is invoked recursively.
final Throwable old = failureCause;
try {
return getOrCreateServiceProvider(category, filter, hints, key);
} finally {
failureCause = old;
reset();
}
}
/**
* Makes this {@code FactoryRegistry} ready for next use. This method is
* overridden by {@link DynamicFactoryRegistry} with additional cleanup.
*/
void reset() {
}
/**
* Implementation of {@link #getServiceProvider}, in a separated method for making easier to
* encompass in a {@code try ... finally} block. The {@code FactoryRegistry} implementation
* does not create any new provider. However {@link DynamicFactoryRegistry} do override this
* method in a way that may create new objects.
*/
<T> T getOrCreateServiceProvider(final Class<T> category, final Filter filter,
Hints hints, final Hints.ClassKey key) throws FactoryRegistryException
{
synchronizeIteratorProviders();
final boolean debug = LOGGER.isLoggable(DEBUG_LEVEL);
if (debug) {
/*
* We are not required to insert the method name ("GetServiceProvider") in the
* message because it is part of the informations already stored by LogRecord,
* and formatted by the default java.util.logging.SimpleFormatter.
*
* Conventions for the message part according java.util.logging.Logger javadoc:
* - "ENTRY" at the beginning of a method.
* - "RETURN" at the end of a method, if successful.
* - "THROW" in case of failure.
* - "CHECK" ... is our own addition to Sun's convention for this method ...
*/
debug("ENTRY", category, key, null, null);
}
Class<?> implementationType = null;
if (key != null) {
/*
* Sanity check: make sure that the key class is appropriate for the category.
*/
final Class<?> valueClass = key.getValueClass();
if (!category.isAssignableFrom(valueClass)) {
if (debug) {
debug("THROW", category, key, "unexpected type:", valueClass);
}
throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalKey_1, key));
}
if (hints != null) {
final Object hint = hints.get(key);
if (hint != null) {
if (debug) {
debug("CHECK", category, key, "user provided a", hint.getClass());
}
if (category.isInstance(hint)) {
/*
* The factory implementation was given explicitly by the user.
* Nothing to do; we are done.
*/
if (debug) {
debug("RETURN", category, key, "return hint as provided.", null);
}
return category.cast(hint);
}
/*
* Before to pass the hints to the private 'getServiceImplementation' method,
* remove the hint for the user-supplied key. This is because this hint has
* been processed by this public 'getServiceProvider' method, and the policy
* is to remove the processed hints before to pass them to child dependencies
* (see the "Check recursively in factory dependencies" comment elswhere in
* this class).
*
* Use case: DefaultDataSourceTest invokes indirectly 'getServiceProvider'
* with a "CRS_AUTHORITY_FACTORY = ThreadedEpsgFactory.class" hint. However
* ThreadedEpsgFactory (in the org.geotoolkit.referencing.factory.epsg package)
* is a wrapper around DirectEpsgFactory, and defines this dependency through
* a "CRS_AUTHORITY_FACTORY = DirectEpsgFactory.class" hint. There is no way
* to match this hint for both factories in same time. Since we must choose
* one, we assume that the user is interested in the most top level one and
* discart this particular hint for the dependencies.
*/
hints = hints.clone();
if (hints.remove(key) != hint) {
// Should never happen except on concurrent modification in an other thread.
throw new AssertionError(key);
}
/*
* If the user accepts many implementation classes, then try all of them in
* the preference order given by the user. The last class (or the singleton
* if the hint was not an array) will be tried using the "normal" path
* (oustide the loop) in order to get the error message in case of failure.
*/
if (hint instanceof Class<?>[]) {
final Class<?>[] types = (Class<?>[]) hint;
final int length = types.length;
for (int i=0; i<length-1; i++) {
final Class<?> type = types[i];
if (debug) {
debug("CHECK", category, key, "consider hint[" + i + ']', type);
}
final T candidate = getServiceImplementation(category, type, filter, hints);
if (candidate != null) {
if (debug) {
debug("RETURN", category, key, "found implementation", candidate.getClass());
}
return candidate;
}
}
if (length != 0) {
implementationType = types[length-1]; // Last try to be done below.
}
} else {
implementationType = (Class<?>) hint;
}
}
}
}
if (debug && implementationType != null) {
debug("CHECK", category, key, "consider hint[last]", implementationType);
}
final T candidate = getServiceImplementation(category, implementationType, filter, hints);
if (candidate != null) {
if (debug) {
debug("RETURN", category, key, "found implementation", candidate.getClass());
}
return candidate;
}
if (debug) {
debug("THROW", category, key, "could not find implementation.", null);
}
/*
* Before to thrown the exception, initialize its cause to 'failureCause' if the later
* is set. Note that we really need to invoke the constructor without Throwable argument,
* and we really needs to invoke 'Throwable.initCause(failureCause)' only if the failure
* cause is not-null, because the DynamicFactoryRegistry subclass may perform an other
* attempt to set the failure cause.
*/
final String message = Errors.format(Errors.Keys.FactoryNotFound_1,
(implementationType != null) ? implementationType : category);
final FactoryNotFoundException e = new FactoryNotFoundException(message);
initCause(e);
throw e;
}
/**
* Sets the failure cause of the given exception, if it is known. This method is invoked
* by {@code DynamicFactoryRegistry} when it failed to use a factory which was compliant
* with user-specified hints.
*
* @param e The exception for which to set the failure cause.
*/
final void initCause(final FactoryNotFoundException e) {
final Throwable c = failureCause;
if (c != null && e.getClass() == FactoryNotFoundException.class && e.getCause() == null) {
e.initCause(c);
}
}
/**
* Logs a debug message for {@link #getServiceProvider} method. Note: we are not required
* to insert the method name ({@code "GetServiceProvider"}) in the message because it is
* part of the informations already stored by {@link LogRecord}, and formatted by the
* default {@link java.util.logging.SimpleFormatter}.
*
* @param status {@code "ENTRY"}, {@code "RETURN"} or {@code "THROW"},
* according {@link Logger} conventions.
* @param category The category given to the {@link #getServiceProvider} method.
* @param key The key being examined, or {@code null}.
* @param message Optional message, or {@code null} if none.
* @param type Optional class to format after the message, or {@code null}.
*/
@Debug
private static void debug(final String status, final Class<?> category,
final Hints.Key key, final String message, final Class<?> type)
{
final StringBuilder buffer = new StringBuilder(status);
buffer.append(CharSequences.spaces(Math.max(1, 7-status.length())))
.append('(').append(Classes.getShortName(category));
if (key != null) {
buffer.append(", ").append(key);
}
buffer.append(')');
if (message != null) {
buffer.append(": ").append(message);
}
if (type != null) {
buffer.append(' ').append(Classes.getShortName(type)).append('.');
}
final LogRecord record = new LogRecord(DEBUG_LEVEL, buffer.toString());
record.setSourceClassName(FactoryRegistry.class.getName());
record.setSourceMethodName("getServiceProvider");
record.setLoggerName(LOGGER.getName());
LOGGER.log(record);
}
/**
* Searches the first implementation in the registry matching the specified conditions.
* This method is invoked only by the {@link #getServiceProvider(Class, Filter, Hints,
* Hints.Key)} public method above; there is no recursivity there. This method does not
* creates new instance if no matching factory is found.
* <p>
* This method is overridden by {@link DynamicFactoryRegistry} in order to search in its
* cache if no instance was found by this method.
*
* @param category The category to look for. Usually an interface class.
* @param implementationType The desired class for the implementation, or {@code null} if none.
* @param filter An optional filter, or {@code null} if none.
* @param hints A {@linkplain Hints map of hints}, or {@code null} if none.
* @return A factory for the specified category and hints, or {@code null} if none.
*/
<T> T getServiceImplementation(final Class<T> category, final Class<?> implementationType,
final Filter filter, final Hints hints)
{
for (final Iterator<T> it=getUnfilteredProviders(category); it.hasNext();) {
final T candidate = it.next();
// Implementation class must be tested before 'isAcceptable'
// in order to avoid StackOverflowError in some situations.
if (implementationType != null && !implementationType.isInstance(candidate)) {
continue;
}
if (!isAcceptable(candidate, category, hints, filter, true)) {
continue;
}
return candidate;
}
return null;
}
/**
* Returns {@code true} if the specified {@code factory} meets the requirements specified by
* a map of {@code hints} and the filter. This method is the entry point for the following
* public methods:
* <p>
* <ul>
* <li>Singleton {@link #getServiceProvider (Class category, Filter, Hints, Hints.Key)}</li>
* <li>Iterator {@link #getServiceProviders(Class category, Filter, Hints)}</li>
* </ul>
*
* @param candidate The factory to check.
* @param category The factory category. Usually an interface.
* @param hints The optional user requirements, or {@code null}.
* @param filter The optional filter, or {@code null}.
* @param wantCause {@code true} for storing the failure cause in the {@link #failureCause}
* field, or {@code false} for discarting the cause. This argument is {@code true}
* only when failure to find a factory will cause a {@link FactoryNotFoundException}
* to be thrown, in which case we will want to add the cause to the exception to be
* thrown.
* @return {@code true} if the {@code factory} meets the user requirements.
*/
final <T> boolean isAcceptable(final T candidate, final Class<T> category,
final Hints hints, final Filter filter, final boolean wantCause)
{
/*
* Note: availability() must be tested before checking the hints, because in current
* Geotk implementation some hints computation are deferred until a connection to the
* database is established (which availability() does in order to test the connection).
*/
ConformanceResult failure = null;
if (candidate instanceof Factory) {
final Factory factory = (Factory) candidate;
final Class<? extends Factory> type = factory.getClass();
if (!testingAvailability.add(type)) {
throw new RecursiveSearchException(type);
}
try {
failure = factory.availability();
if (Boolean.TRUE.equals(failure.pass())) {
failure = null; // Means "no failure".
} else {
unavailable(factory);
if (!wantCause || failureCause != null) {
/*
* If the caller is not interested about the cause of the failure,
* or if a cause has already been reported for a previous factory,
* we can return immediately. Otherwise we need to remember that we
* failed, but continue nevertheless for making sure that the cause
* is pertinent (we don't want to report failures for factories that
* the user didn't asked for).
*/
return false;
}
}
} finally {
testingAvailability.remove(type);
}
}
/*
* Applies the user-provided filter only after having tested for availability,
* because some implementations may require a connection to a database. Note
* however that the filter should still work; they may just be less accurate.
*/
if (filter!=null && !filter.filter(candidate)) {
return false;
}
/*
* Now checks for implementation hints.
*/
if (candidate instanceof Factory) {
final Factory factory = (Factory) candidate;
if (!isNullOrEmpty(hints)) {
/*
* Ask for implementation hints with special care against infinite recursivity.
* Some implementations use deferred algorithms fetching dependencies only when
* first needed. The call to getImplementationHints() is sometime a trigger for
* fetching dependencies (in order to return accurate hints). For example the
* CachingCoordinateOperationFactory implementation asks for an other instance
* of CoordinateOperationFactory, which can not be itself. Of course the caching
* class will checks that it is not caching itself, but its test happen too late
* for preventing a never-ending loop if we don't put a 'testingHints' guard here.
* It is also a safety against broken factory implementations.
*/
if (testingHints.put(factory, Boolean.TRUE) != null) {
return false;
}
try {
if (!factory.hasCompatibleHints(hints)) {
return false;
}
} finally {
testingHints.remove(factory);
}
}
}
/*
* Checks for optional user conditions supplied in FactoryRegistry subclasses. If
* we pass this final test but the factory is not available (as detected sooner),
* now we can remember the cause.
*/
if (!isAcceptable(candidate, category, hints)) {
return false;
}
if (failure != null) {
if (failure instanceof Factory.Availability) {
failureCause = ((Factory.Availability) failure).getFailureCause();
}
return false;
}
return true;
}
/**
* Returns {@code true} if the specified {@code provider} meets the requirements specified by
* a map of {@code hints}. The default implementation always returns {@code true}. There is no
* need to override this method for {@link Factory} implementations, since their hints are
* automatically checked. Override this method for non-Geotk implementations.
* For example a JTS geometry factory finder may overrides this method in order to check
* if a {@link com.vividsolutions.jts.geom.GeometryFactory} uses the required
* {@link com.vividsolutions.jts.geom.CoordinateSequenceFactory}. Such method should be
* implemented as below, since this method may be invoked for various kind of objects:
*
* {@preformat java
* if (provider instanceof GeometryFactory) {
* // ... Check the GeometryFactory state here.
* }
* }
*
* @param <T> The class represented by the {@code category} argument.
* @param provider The provider to check.
* @param category The factory category. Usually an interface.
* @param hints The user requirements, or {@code null} if none.
* @return {@code true} if the {@code provider} meets the user requirements.
*
* @level advanced
*/
protected <T> boolean isAcceptable(final T provider, final Class<T> category, final Hints hints) {
return true;
}
/**
* Invoked when a factory declares itself as unavailable. The default implementation does
* nothing. Subclasses can override this method if they want to track those unavailable
* factories.
*
* @param factory The factory which declares itself as unavailable.
*
* @see Factory#availability
*
* @since 3.01
*/
void unavailable(final Factory factory) {
}
/**
* Returns all class loaders to be used for scanning plugins. Current implementation
* returns the following class loaders:
* <p>
* <ul>
* <li>{@linkplain Class#getClassLoader This object class loader}</li>
* <li>{@linkplain Thread#getContextClassLoader The thread context class loader}</li>
* <li>{@linkplain ClassLoader#getSystemClassLoader The system class loader}</li>
* </ul>
* <p>
* The actual number of class loaders may be smaller if redundancies was found.
* If some more classloaders should be scanned, they shall be added into the code
* of this method.
*
* @return All classloaders to be used for scanning plugins.
*/
public Set<ClassLoader> getClassLoaders() {
final Set<ClassLoader> loaders = new HashSet<>(6);
for (int i=0; i<4; i++) {
final ClassLoader loader;
try {
switch (i) {
case 0: loader = getClass().getClassLoader(); break;
case 1: loader = FactoryRegistry.class.getClassLoader(); break;
case 2: loader = Thread.currentThread().getContextClassLoader(); break;
case 3: loader = ClassLoader.getSystemClassLoader(); break;
// Add any supplementary class loaders here, if needed.
default: throw new AssertionError(i); // Should never happen.
}
} catch (SecurityException exception) {
// We are not allowed to get a class loader.
// Continue; some other class loader may be available.
continue;
}
loaders.add(loader);
}
loaders.remove(null);
/*
* We now have a set of class loaders with duplicated object already removed
* (e.g. system classloader == context classloader). However, we may still
* have an other form of redundancie. A class loader may be the parent of an
* other one. Try to remove those dependencies.
*/
final ClassLoader[] asArray = loaders.toArray(new ClassLoader[loaders.size()]);
for (ClassLoader loader : asArray) {
try {
while ((loader = loader.getParent()) != null) {
loaders.remove(loader);
}
} catch (SecurityException exception) {
// We are not allowed to fetch the parent class loader.
// Ignore (some redundancies may remains).
}
}
if (loaders.isEmpty()) {
LOGGER.warning("No class loaders available.");
}
return loaders;
}
/**
* Scans for factory plug-ins on the application class path. This method is
* needed because the application class path can theoretically change, or
* additional plug-ins may become available. Rather than re-scanning the
* classpath on every invocation of the API, the class path is scanned
* automatically only on the first invocation. Clients can call this
* method to prompt a re-scan. Thus this method need only be invoked by
* sophisticated applications which dynamically make new plug-ins
* available at runtime.
*
* @level advanced
*/
public void scanForPlugins() {
needScanForPlugins = null;
final Set<ClassLoader> loaders = getClassLoaders();
for (final Iterator<Class<?>> categories=getCategories(); categories.hasNext();) {
final Class<?> category = categories.next();
scanForPlugins(loaders, category);
}
}
/**
* Scans for factory plug-ins of the given category, with guard against recursivities.
* The recursivity check make debugging easier than inspecting a {@link StackOverflowError}.
*
* @param loader The class loader to use.
* @param category The category to scan for plug-ins.
*/
private <T> void scanForPlugins(final Collection<ClassLoader> loaders, final Class<T> category) {
if (!scanningCategories.add(category)) {
throw new RecursiveSearchException(category);
}
try {
final StringBuilder message = getLogHeader(category);
boolean newServices = false;
/*
* First, scan META-INF/services directories (the default mechanism).
*/
for (final ClassLoader loader : loaders) {
newServices |= register(lookupProviders(category, loader), category, message);
}
/*
* Next, query the user-provider iterators (if any).
*/
final FactoryIteratorProvider[] fip = FactoryIteratorProviders.GLOBAL.getIteratorProviders();
for (int i=0; i<fip.length; i++) {
final Iterator<T> it = fip[i].iterator(category);
if (it != null) {
newServices |= register(it, category, message);
}
}
/*
* Finally, log the list of registered factories.
*/
if (newServices) {
log("scanForPlugins", message);
}
} finally {
scanningCategories.remove(category);
}
/*
* After loading all plugins for the current category, gives factories a chance to setup
* their ordering relative to other factories. This operation must be inconditional, even
* if there is no new plugins (newServices == false) because the scan may have registered
* again existing plugins, in which case their previous ordering have been lost and must
* be reset.
*/
final Iterator<T> it = getServiceProviders(category, false);
while (it.hasNext()) {
final T candidate = it.next();
if (candidate instanceof Factory) {
final Factory factory = (Factory) candidate;
factory.setOrdering(factory.new Organizer(this, category));
}
}
pluginScanned(category);
}
/**
* Scans the given category for plugins only if needed. After this method has been
* invoked once for a given category, it will no longer scan for that category.
*/
private <T> void scanForPluginsIfNeeded(final Class<?> category) {
if (needScanForPlugins != null && needScanForPlugins.remove(category)) {
if (needScanForPlugins.isEmpty()) {
needScanForPlugins = null;
}
scanForPlugins(getClassLoaders(), category);
}
}
/**
* Invoked after the factories of the given category have been scanned. The default
* implementation does nothing, which is usually the desired behavior (the public API
* should be used instead). However {@link FactoryFinder} uses this method as an easy
* hook for setting ordering based on the plugin vendor.
*
* @param category The category of the plugins which have been added.
*
* @since 3.02
*/
void pluginScanned(final Class<?> category) {
}
/**
* {@linkplain #registerServiceProvider Registers} all factories given by the
* supplied iterator.
*
* @param factories The factories (or "service providers") to register.
* @param category the category under which to register the providers.
* @param message A buffer where to write the logging message.
* @return {@code true} if at least one factory has been registered.
*/
private <T> boolean register(final Iterator<T> factories, final Class<T> category, final StringBuilder message) {
boolean newServices = false;
final String lineSeparator = System.lineSeparator();
while (factories.hasNext()) {
T factory;
try {
factory = factories.next();
} catch (OutOfMemoryError error) {
// Makes sure that we don't try to handle this error.
throw error;
} catch (NoClassDefFoundError error) {
/*
* A provider can't be registered because of some missing dependencies.
* This occurs for example when trying to register the WarpTransform2D
* math transform on a machine without JAI installation. Since the factory
* may not be essential (this is the case of WarpTransform2D), just skip it.
*/
loadingFailure(category, error, false);
continue;
} catch (ExceptionInInitializerError error) {
/*
* If an exception occurred during class initialization, log the cause.
* The ExceptionInInitializerError alone doesn't help enough.
*/
final Throwable cause = error.getCause();
if (cause != null) {
loadingFailure(category, cause, true);
}
throw error;
} catch (Error error) {
if (!Classes.getShortClassName(error).equals(
Classes.getShortName(ServiceConfigurationError.class)))
{
/*
* In Java 6, ServiceLoader throws java.util.ServiceConfigurationError.
* In Java 4, ServiceRegistry throws sun.misc.ServiceConfigurationError.
* We want to catch those two errors and let all other propagate. However
* the later is not committed API and we don't known if Sun will replace it by
* the exception from java.util package in a future version. Using reflection
* allows us to catch both cases without relying to sun.misc package.
*/
throw error;
}
/*
* Failed to register a factory for a reason probably related to the plugin
* initialisation. It may be some factory-dependent missing resources.
*/
loadingFailure(category, error, true);
continue;
}
final Class<? extends T> factoryClass = factory.getClass().asSubclass(category);
/*
* If the factory implements more than one interface and an instance were
* already registered, reuse the same instance instead of duplicating it.
*/
final T replacement = getServiceProviderByClass(factoryClass);
if (replacement != null) {
factory = replacement;
// Need to register anyway, because the category may not be the same.
}
if (registerServiceProvider(factory, category)) {
/*
* The factory is now registered. Add it to the message to be logged. We will log
* all factories together in a single log event because some registration (e.g.
* MathTransformProviders) would be otherwise quite verbose.
*/
message.append(lineSeparator).append(" \u2022 ").append(factoryClass.getCanonicalName());
if (replacement != null) {
message.append(" (shared)"); // TODO: localize
}
newServices = true;
}
}
return newServices;
}
/**
* Invoked when a factory can't be loaded. Logs a warning, but do not stop the process.
*/
private static void loadingFailure(final Class<?> category, final Throwable error,
final boolean showStackTrace)
{
final String name = Classes.getShortName(category);
final StringBuilder cause = new StringBuilder(Classes.getShortClassName(error));
final String message = error.getLocalizedMessage();
if (message != null) {
cause.append(": ").append(message);
}
final LogRecord record = Loggings.format(Level.WARNING,
Loggings.Keys.CantLoadService_2, name, cause.toString());
if (showStackTrace) {
record.setThrown(error);
}
record.setSourceClassName(FactoryRegistry.class.getName());
record.setSourceMethodName("scanForPlugins");
record.setLoggerName(LOGGER.getName());
LOGGER.log(record);
}
/**
* Prepares a message to be logged if any provider has been registered.
*/
private static StringBuilder getLogHeader(final Class<?> category) {
return new StringBuilder(Loggings.getResources(null).getString(
Loggings.Keys.FactoryImplementations_1, category));
}
/**
* Logs the specified message after the registration of all providers for a given category.
*/
private static void log(final String method, final StringBuilder message) {
final LogRecord record = new LogRecord(Level.CONFIG, message.toString());
record.setSourceClassName(FactoryRegistry.class.getName());
record.setSourceMethodName(method);
record.setLoggerName(LOGGER.getName());
LOGGER.log(record);
}
/**
* Synchronizes the content of the {@link #globalConfiguration} with
* {@link FactoryIteratorProviders#GLOBAL}. New providers are {@linkplain #register registered}
* immediately. Note that this method is typically invoked in a different thread than
* {@link FactoryIteratorProviders} method calls.
*
* @see FactoryIteratorProviders#addFactoryIteratorProvider
*/
private void synchronizeIteratorProviders() {
final FactoryIteratorProvider[] newProviders =
globalConfiguration.synchronizeIteratorProviders();
if (newProviders == null) {
return;
}
for (final Iterator<Class<?>> categories=getCategories(); categories.hasNext();) {
final Class<?> category = categories.next();
if (needScanForPlugins == null || !needScanForPlugins.contains(category)) {
/*
* Register immediately the factories only if some other factories were already
* registered for this category, because in such case scanForPlugin() will not
* be invoked automatically. If no factory are registered for this category, do
* nothing - we will rely on the lazy invocation of scanForPlugins() when first
* needed. We perform this check because getServiceProviders(category).hasNext()
* is the criterion used by FactoryRegistry in order to decide if it should invoke
* automatically scanForPlugins().
*/
for (int i=0; i<newProviders.length; i++) {
register(newProviders[i], category);
}
}
}
}
/**
* Registers every factories from the specified provider for the given category.
*/
private <T> void register(final FactoryIteratorProvider provider, final Class<T> category) {
final Iterator<T> it = provider.iterator(category);
if (it != null) {
final StringBuilder message = getLogHeader(category);
if (register(it, category, message)) {
log("synchronizeIteratorProviders", message);
}
}
}
/**
* Sets pairwise ordering between all factories according a comparator. Calls to
* <code>{@linkplain Comparator#compare compare}(factory1, factory2)</code> should returns:
* <ul>
* <li>{@code -1} if {@code factory1} is preferred to {@code factory2}</li>
* <li>{@code +1} if {@code factory2} is preferred to {@code factory1}</li>
* <li>{@code 0} if there is no preferred order between {@code factory1} and
* {@code factory2}</li>
* </ul>
*
* @param <T> The class represented by the {@code category} argument.
* @param category The category to set ordering.
* @param comparator The comparator to use for ordering.
* @return {@code true} if at least one ordering setting has been modified as a consequence
* of this call.
*
* @level advanced
*/
public <T> boolean setOrdering(final Class<T> category, final Comparator<T> comparator) {
boolean set = false;
final List<T> previous = new ArrayList<>();
for (final Iterator<T> it=getServiceProviders(category, false); it.hasNext();) {
final T f1 = it.next();
for (int i=previous.size(); --i>=0;) {
final T f2 = previous.get(i);
final int c;
try {
c = comparator.compare(f1, f2);
} catch (ClassCastException exception) {
/*
* This exception is expected if the user-supplied comparator follows strictly
* the java.util.Comparator specification and has determined that it can't
* compare the supplied factories. From ServiceRegistry point of view, it just
* means that the ordering between those factories will stay undeterminated.
*/
continue;
}
if (c > 0) {
set |= setOrdering(category, f1, f2);
} else if (c < 0) {
set |= setOrdering(category, f2, f1);
}
}
previous.add(f1);
}
return set;
}
/**
* Sets or unsets a pairwise ordering between all factories meeting a criterion. For example
* in the CRS framework, this is used for setting ordering between all factories provided by
* two vendors, or for two authorities. If one or both factories are not currently registered,
* or if the desired ordering is already set/unset, nothing happens and false is returned.
*
* @param <T> The class represented by the {@code base} argument.
* @param base The base category. Only categories {@linkplain Class#isAssignableFrom
* assignable} to {@code base} will be processed.
* @param service1 Filter for the preferred factory.
* @param service2 Filter for the factory to which {@code service1} is preferred.
* @return {@code true} if the ordering changed as a result of this call.
*
* @level advanced
*/
public <T> boolean setOrdering(final Class<T> base, final Filter service1, final Filter service2) {
return setOrUnsetOrdering(base, service1, service2, true);
}
/**
* Unset an previously set ordering.
*
* @param <T> The class represented by the {@code base} argument.
* @param base The base category. Only categories {@linkplain Class#isAssignableFrom
* assignable} to {@code base} will be processed.
* @param service1 Filter for the preferred factory.
* @param service2 Filter for the factory to which {@code service1} was preferred.
* @return {@code true} if the ordering changed as a result of this call.
*
* @level advanced
*/
public <T> boolean unsetOrdering(final Class<T> base, final Filter service1, final Filter service2) {
return setOrUnsetOrdering(base, service1, service2, false);
}
/**
* Implementation of set/unsetOrdering.
*
* @param set {@code true} for setting the ordering, or {@code false} for unsetting.
*/
final <T> boolean setOrUnsetOrdering(final Class<T> base,
final Filter service1, final Filter service2, final boolean set)
{
boolean done = false;
for (final Iterator<Class<?>> categories=getCategories(); categories.hasNext();) {
final Class<?> candidate = categories.next();
if (base.isAssignableFrom(candidate)) {
final Class<? extends T> category = candidate.asSubclass(base);
done |= setOrUnsetOrdering(category, set, service1, service2);
}
}
return done;
}
/**
* Helper method for the above. Defined as a separated method because of generic type.
*/
private <T> boolean setOrUnsetOrdering(final Class<T> category, final boolean set,
final Filter service1, final Filter service2)
{
boolean done = false;
final List<T> precedences = new ArrayList<>(); // The plugins of the service which have precedence.
for (final Iterator<? extends T> it=getServiceProviders(category, true); it.hasNext();) {
final T factory = it.next();
if (service1.filter(factory)) {
precedences.add(factory);
}
}
for (final Iterator<? extends T> it=getServiceProviders(category, false); it.hasNext();) {
final T factory = it.next();
if (service2.filter(factory)) {
for (final T precedence : precedences) {
if (precedence != factory) {
if (set) done |= setOrdering(category, precedence, factory);
else done |= unsetOrdering(category, precedence, factory);
}
}
}
}
return done;
}
/**
* Returns a string representation of this registry for debugging purpose.
* The default implementation list the providers in a tabular format.
*
* @return A string representation of this registry content.
*/
@Override
public String toString() {
return new FactoryPrinter(this).toString();
}
}