org.apache.commons.discovery.tools.DiscoverSingleton Maven / Gradle / Ivy
/*
* 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.commons.discovery.tools;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.discovery.DiscoveryException;
import org.apache.commons.discovery.jdk.JDKHooks;
import org.apache.commons.discovery.resource.ClassLoaders;
/**
* Discover singleton service providers.
* This
*
*
* DiscoverSingleton instances are cached by the Discovery service,
* keyed by a combination of
*
* - thread context class loader,
* - groupContext, and
* - SPI.
*
* This DOES allow multiple instances of a given singleton class
* to exist for different class loaders and different group contexts.
*
*
* In the context of this package, a service interface is defined by a
* Service Provider Interface (SPI). The SPI is expressed as a Java interface,
* abstract class, or (base) class that defines an expected programming
* interface.
*
*
* DiscoverSingleton provides the find
methods for locating and
* instantiating a singleton instance of an implementation of a service (SPI).
* Each form of find
varies slightly, but they all perform the
* same basic function.
*
* The simplest find
methods are intended for direct use by
* components looking for a service. If you are not sure which finder(s)
* to use, you can narrow your search to one of these:
*
* - static <T> T find(Class<T> spi);
* - static <T> T find(Class<T> spi, Properties properties);
* - static <T> T find(Class<T> spi, String defaultImpl);
* - static <T> T find(Class<T> spi,
* Properties properties, String defaultImpl);
* - static <T> T find(Class<T> spi,
* String propertiesFileName, String defaultImpl);
* - static <T> T find(ClassLoaders loaders, SPInterface<T> spi,
* PropertiesHolder holder, DefaultClassHolder<T> holder);
*
*
* The DiscoverSingleton.find
methods proceed as follows:
*
*
* -
* Examine an internal cache to determine if the desired service was
* previously identified and instantiated. If found in cache, return it.
*
* -
* Get the name of an implementation class. The name is the first
* non-null value obtained from the following resources:
*
* -
* The value of the (scoped) system property whose name is the same as
* the SPI's fully qualified class name (as given by SPI.class.getName()).
* The
ScopedProperties
class provides a way to bind
* properties by classloader, in a secure hierarchy similar in concept
* to the way classloader find class and resource files.
* See ScopedProperties
for more details.
* If the ScopedProperties are not set by users, then behaviour
* is equivalent to System.getProperty()
.
*
*
* -
* The value of a
Properties properties
property, if provided
* as a parameter, whose name is the same as the SPI's fully qualifed class
* name (as given by SPI.class.getName()).
*
* -
* The value obtained using the JDK1.3+ 'Service Provider' specification
* (http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html) to locate a
* service named
SPI.class.getName()
. This is implemented
* internally, so there is not a dependency on JDK 1.3+.
*
*
*
* -
* If the name of the implementation class is non-null, load that class.
* The class loaded is the first class loaded by the following sequence
* of class loaders:
*
* - Thread Context Class Loader
* - DiscoverSingleton's Caller's Class Loader
* - SPI's Class Loader
* - DiscoverSingleton's (this class or wrapper) Class Loader
* - System Class Loader
*
* An exception is thrown if the class cannot be loaded.
*
* -
* If the name of the implementation class is null, AND the default
* implementation class (
defaultImpl
) is null,
* then an exception is thrown.
*
* -
* If the name of the implementation class is null, AND the default
* implementation class (
defaultImpl
) is non-null,
* then load the default implementation class. The class loaded is the
* first class loaded by the following sequence of class loaders:
*
* - SPI's Class Loader
* - DiscoverSingleton's (this class or wrapper) Class Loader
* - System Class Loader
*
*
* This limits the scope in which the default class loader can be found
* to the SPI, DiscoverSingleton, and System class loaders. The assumption
* here is that the default implementation is closely associated with the SPI
* or system, and is not defined in the user's application space.
*
*
* An exception is thrown if the class cannot be loaded.
*
*
* -
* Verify that the loaded class implements the SPI: an exception is thrown
* if the loaded class does not implement the SPI.
*
* -
* Create an instance of the class.
*
*
*
*
* Variances for various forms of the find
* methods are discussed with each such method.
* Variances include the following concepts:
*
* - rootFinderClass - a wrapper encapsulating a finder method
* (factory or other helper class). The root finder class is used to
* determine the 'real' caller, and hence the caller's class loader -
* thereby preserving knowledge that is relevant to finding the
* correct/expected implementation class.
*
* - propertiesFileName -
Properties
may be specified
* directly, or by property file name. A property file is loaded using the
* same sequence of class loaders used to load the SPI implementation:
*
* - Thread Context Class Loader
* - DiscoverSingleton's Caller's Class Loader
* - SPI's Class Loader
* - DiscoverSingleton's (this class) Class Loader
* - System Class Loader
*
*
* - groupContext - differentiates service providers for different
* logical groups of service users, that might otherwise be forced to share
* a common service and, more importantly, a common configuration of that
* service.
*
The groupContext is used to qualify the name of the property file
* name: groupContext + '.' + propertiesFileName
. If that
* file is not found, then the unqualified propertyFileName is used.
*
* In addition, groupContext is used to qualify the name of the system
* property used to find the service implementation by prepending the value
* of groupContext
to the property name:
* groupContext> + '.' + SPI.class.getName()
.
* Again, if a system property cannot be found by that name, then the
* unqualified property name is used.
*
*
*
*
*
* IMPLEMENTATION NOTE - This implementation is modelled
* after the SAXParserFactory and DocumentBuilderFactory implementations
* (corresponding to the JAXP pluggability APIs) found in Apache Xerces.
*
*
* @version $Revision: 1089242 $ $Date: 2011-04-05 23:33:21 +0200 (Tue, 05 Apr 2011) $
*/
public class DiscoverSingleton {
/********************** (RELATIVELY) SIMPLE FINDERS **********************
*
* These finders are suitable for direct use in components looking for a
* service. If you are not sure which finder(s) to use, you can narrow
* your search to one of these.
*/
/**
* Find implementation of SPI.
*
* @param Service Provider Interface type.
* @param spiClass Service Provider Interface Class.
*
* @return Instance of a class implementing the SPI.
*
* @exception DiscoveryException Thrown if the name of a class implementing
* the SPI cannot be found, if the class cannot be loaded and
* instantiated, or if the resulting class does not implement
* (or extend) the SPI.
*/
public static T find(Class spiClass) throws DiscoveryException {
return find(null,
new SPInterface(spiClass),
DiscoverClass.nullProperties,
(DefaultClassHolder) null);
}
/**
* Find implementation of SPI.
*
* @param Service Provider Interface type
*
* @param spiClass Service Provider Interface Class.
*
* @param properties Used to determine name of SPI implementation,
* and passed to implementation.init() method if
* implementation implements Service interface.
*
* @return Instance of a class implementing the SPI.
*
* @exception DiscoveryException Thrown if the name of a class implementing
* the SPI cannot be found, if the class cannot be loaded and
* instantiated, or if the resulting class does not implement
* (or extend) the SPI.
*/
public static T find(Class spiClass, Properties properties) throws DiscoveryException {
return find(null,
new SPInterface(spiClass),
new PropertiesHolder(properties),
(DefaultClassHolder) null);
}
/**
* Find implementation of SPI.
*
* @param Service Provider Interface type
*
* @param spiClass Service Provider Interface Class.
*
* @param defaultImpl Default implementation.
*
* @return Instance of a class implementing the SPI.
*
* @exception DiscoveryException Thrown if the name of a class implementing
* the SPI cannot be found, if the class cannot be loaded and
* instantiated, or if the resulting class does not implement
* (or extend) the SPI.
*/
public static T find(Class spiClass, String defaultImpl) throws DiscoveryException {
return find(null,
new SPInterface(spiClass),
DiscoverClass.nullProperties,
new DefaultClassHolder(defaultImpl));
}
/**
* Find implementation of SPI.
*
* @param Service Provider Interface type
*
* @param spiClass Service Provider Interface Class.
*
* @param properties Used to determine name of SPI implementation,
* and passed to implementation.init() method if
* implementation implements Service interface.
*
* @param defaultImpl Default implementation.
*
* @return Instance of a class implementing the SPI.
*
* @exception DiscoveryException Thrown if the name of a class implementing
* the SPI cannot be found, if the class cannot be loaded and
* instantiated, or if the resulting class does not implement
* (or extend) the SPI.
*/
public static T find(Class spiClass,
Properties properties,
String defaultImpl) throws DiscoveryException {
return find(null,
new SPInterface(spiClass),
new PropertiesHolder(properties),
new DefaultClassHolder(defaultImpl));
}
/**
* Find implementation of SPI.
*
* @param Service Provider Interface type
*
* @param spiClass Service Provider Interface Class.
*
* @param propertiesFileName Used to determine name of SPI implementation,
* and passed to implementation.init() method if
* implementation implements Service interface.
*
* @param defaultImpl Default implementation.
*
* @return Instance of a class implementing the SPI.
*
* @exception DiscoveryException Thrown if the name of a class implementing
* the SPI cannot be found, if the class cannot be loaded and
* instantiated, or if the resulting class does not implement
* (or extend) the SPI.
*/
public static T find(Class spiClass,
String propertiesFileName,
String defaultImpl) throws DiscoveryException {
return find(null,
new SPInterface(spiClass),
new PropertiesHolder(propertiesFileName),
new DefaultClassHolder(defaultImpl));
}
/*************** FINDERS FOR USE IN FACTORY/HELPER METHODS ***************
*/
/**
* Find implementation of SPI.
*
* @param Service Provider Interface type
*
* @param loaders The {@code ClassLoader} holder
*
* @param spi Service Provider Interface Class.
*
* @param properties Used to determine name of SPI implementation,
* and passed to implementation.init() method if
* implementation implements Service interface.
*
* @param defaultImpl Default implementation.
*
* @return Instance of a class implementing the SPI.
*
* @exception DiscoveryException Thrown if the name of a class implementing
* the SPI cannot be found, if the class cannot be loaded and
* instantiated, or if the resulting class does not implement
* (or extend) the SPI.
*/
public static T find(ClassLoaders loaders,
SPInterface spi,
PropertiesHolder properties,
DefaultClassHolder defaultImpl) throws DiscoveryException {
ClassLoader contextLoader = JDKHooks.getJDKHooks().getThreadContextClassLoader();
@SuppressWarnings("unchecked") // spiName is assignable from stored object class
T obj = (T) get(contextLoader, spi.getSPName());
if (obj == null) {
try {
obj = DiscoverClass.newInstance(loaders, spi, properties, defaultImpl);
if (obj != null) {
put(contextLoader, spi.getSPName(), obj);
}
} catch (DiscoveryException de) {
throw de;
} catch (Exception e) {
throw new DiscoveryException("Unable to instantiate implementation class for " + spi.getSPName(), e);
}
}
return obj;
}
/********************** CACHE-MANAGEMENT SUPPORT **********************/
/**
* Release all internal references to previously created service
* instances associated with the current thread context class loader.
* The release()
method is called for service instances that
* implement the Service
interface.
*
* This is useful in environments like servlet containers,
* which implement application reloading by throwing away a ClassLoader.
* Dangling references to objects in that class loader would prevent
* garbage collection.
*/
public static synchronized void release() {
EnvironmentCache.release();
}
/**
* Release any internal references to a previously created service
* instance associated with the current thread context class loader.
* If the SPI instance implements Service
, then call
* release()
.
*
* @param spiClass The previously created service
*/
public static synchronized void release(Class> spiClass) {
Map spis = EnvironmentCache.get(JDKHooks.getJDKHooks().getThreadContextClassLoader());
if (spis != null) {
spis.remove(spiClass.getName());
}
}
/************************* SPI CACHE SUPPORT *************************
*
* Cache services by a 'key' unique to the requesting class/environment:
*
* When we 'release', it is expected that the caller of the 'release'
* have the same thread context class loader... as that will be used
* to identify all cached entries to be released.
*
* We will manage synchronization directly, so all caches are implemented
* as HashMap (unsynchronized).
*
* - ClassLoader::groupContext::SPI::Instance Cache
* Cache : HashMap
* Key : Thread Context Class Loader (ClassLoader
).
* Value : groupContext::SPI Cache (HashMap
).
*
* - groupContext::SPI::Instance Cache
* Cache : HashMap
* Key : groupContext (String
).
* Value : SPI Cache (HashMap
).
*
* - SPI::Instance Cache
* Cache : HashMap
* Key : SPI Class Name (String
).
* Value : SPI Instance/Implementation (Object
.
*/
/**
* Implements first two levels of the cache (loader & groupContext).
* Allows null keys, important as default groupContext is null.
*/
/**
* Get service keyed by spi & classLoader.
*
* @param classLoader The class loader as key to retrieve the related cache
* @param spiName The SPI class name
* @return The object instance associated to the given class loader/SPI name
*/
private static synchronized Object get(ClassLoader classLoader,
String spiName) {
Map spis = EnvironmentCache.get(classLoader);
if (spis != null) {
return spis.get(spiName);
}
return null;
}
/**
* Put service keyed by spi & classLoader.
*
* @param classLoader The {@link EnvironmentCache} key
* @param spiName The SPI class name
* @param service The SPI object reference
*/
private static synchronized void put(ClassLoader classLoader,
String spiName,
Object service) {
if (service != null) {
Map spis = EnvironmentCache.get(classLoader);
if (spis == null) {
spis = new HashMap(EnvironmentCache.smallHashSize);
EnvironmentCache.put(classLoader, spis);
}
spis.put(spiName, service);
}
}
}