/**
* 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 io.hawtjms.util;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
/**
* A Factory finding helper class used to locate objects that serve as Factories for
* other Object types. The search an instantiate mechanism is configurable so that
* in a non-stand-alone environment such as OSGI the finder and be configured to work.
*/
public class FactoryFinder<T extends Object> {
/**
* The strategy that the FactoryFinder uses to find load and instantiate Objects can be
* changed out by calling the
* {@link io.hawtjms.util.FactoryFinder#setObjectFactory(io.hawtjms.util.FactoryFinder.ObjectFactory)}
* method with a custom implementation of ObjectFactory.
*
* The default ObjectFactory is typically changed out when running in a specialized
* container environment where service discovery needs to be done via the container system.
* For example, in an OSGi scenario.
*/
public interface ObjectFactory {
/**
* Creates the requested factory instance.
*
* @param path
* the full service path
*
* @return instance of the factory object being searched for.
*
* @throws IllegalAccessException if an error occurs while accessing the search path.
* @throws InstantiationException if the factory object fails on create.
* @throws IOException if the search encounter an IO error.
* @throws ClassNotFoundException if the class that is to be loaded cannot be found.
*/
public Object create(String path) throws IllegalAccessException, InstantiationException, IOException, ClassNotFoundException;
}
private static ObjectFactory objectFactory = new StandaloneObjectFactory();
private final ConcurrentHashMap<String, T> cachedFactories = new ConcurrentHashMap<String, T>();
private final String path;
private final Class<T> factoryType;
/**
* Creates a new instance of the FactoryFinder using the given search path.
*
* @param path
* The path to use when searching for the factory definitions.
*/
public FactoryFinder(Class<T> factoryType, String path) {
this.path = path;
this.factoryType = factoryType;
}
/**
* @return the currently configured ObjectFactory instance used to locate the Factory objects.
*/
public static ObjectFactory getObjectFactory() {
return objectFactory;
}
/**
* Sets the ObjectFactory instance to use when searching for the Factory class. This allows
* the default instance to be overridden in an environment where the basic version will not
* work.
*
* @param objectFactory
* the new object factory to use when searching for a Factory instance.
*/
public static void setObjectFactory(ObjectFactory objectFactory) {
FactoryFinder.objectFactory = objectFactory;
}
/**
* Creates a new instance of the given key. The method first checks the cache of previously
* found factory instances for one that matches the key. If no cached version exists then
* the factory will be searched for using the configured ObjectFactory instance.
*
* @param key
* is the key to add to the path to find a text file containing the factory name
*
* @return a newly created instance
*
* @throws IllegalAccessException if an error occurs while accessing the search path.
* @throws InstantiationException if the factory object fails on create.
* @throws IOException if the search encounter an IO error.
* @throws ClassNotFoundException if the class that is to be loaded cannot be found.
* @throws ClassCastException if the found object is not assignable to the request factory type.
*/
public T newInstance(String key) throws IllegalAccessException, InstantiationException, IOException, ClassNotFoundException, ClassCastException {
T factory = cachedFactories.get(key);
if (factory == null) {
Object found = objectFactory.create(path + key);
if (found != null && factoryType.isInstance(found)) {
factory = factoryType.cast(found);
cachedFactories.put(key, factory);
} else {
throw new ClassCastException("Cannot cast " + found.getClass().getName() +
" to " + factoryType.getName());
}
}
return factory;
}
/**
* Allow registration of a Provider factory without wiring via META-INF classes
*
* @param scheme
* The URI scheme value that names the target Provider instance.
* @param factory
* The factory to register in this finder.
*/
public void registerProviderFactory(String scheme, T factory) {
cachedFactories.put(scheme, factory);
}
/**
* The default implementation of Object factory which works well in stand-alone applications.
*/
@SuppressWarnings("rawtypes")
protected static class StandaloneObjectFactory implements ObjectFactory {
final ConcurrentHashMap<String, Class> classMap = new ConcurrentHashMap<String, Class>();
@Override
public Object create(final String path) throws InstantiationException, IllegalAccessException, ClassNotFoundException, IOException {
Class clazz = classMap.get(path);
if (clazz == null) {
clazz = loadClass(loadProperties(path));
classMap.put(path, clazz);
}
return clazz.newInstance();
}
static public Class loadClass(Properties properties) throws ClassNotFoundException, IOException {
String className = properties.getProperty("class");
if (className == null) {
throw new IOException("Expected property is missing: class");
}
Class clazz = null;
ClassLoader loader = Thread.currentThread().getContextClassLoader();
if (loader != null) {
try {
clazz = loader.loadClass(className);
} catch (ClassNotFoundException e) {
// ignore
}
}
if (clazz == null) {
clazz = FactoryFinder.class.getClassLoader().loadClass(className);
}
return clazz;
}
static public Properties loadProperties(String uri) throws IOException {
// lets try the thread context class loader first
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if (classLoader == null) {
classLoader = StandaloneObjectFactory.class.getClassLoader();
}
InputStream in = classLoader.getResourceAsStream(uri);
if (in == null) {
in = FactoryFinder.class.getClassLoader().getResourceAsStream(uri);
if (in == null) {
throw new IOException("Could not find factory class for resource: " + uri);
}
}
// lets load the file
BufferedInputStream reader = null;
try {
reader = new BufferedInputStream(in);
Properties properties = new Properties();
properties.load(reader);
return properties;
} finally {
try {
reader.close();
} catch (Exception e) {
}
}
}
}
}