blob: 15df0f59c7ea79567105de1c2e587c97023f7a43 [file] [log] [blame]
package org.apache.fulcrum.factory;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.avalon.framework.activity.Disposable;
import org.apache.avalon.framework.activity.Initializable;
import org.apache.avalon.framework.configuration.Configurable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.logger.AbstractLogEnabled;
import org.apache.fulcrum.factory.utils.ObjectInputStreamForContext;
/**
* The Factory Service instantiates objects using specified
* class loaders. If none is specified, the default one
* will be used.
*
* avalon.component name="factory" lifestyle="singleton"
* avalon.service type="org.apache.fulcrum.factory.FactoryService"
*
* @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>
* @author <a href="mailto:ilkka.priha@simsoft.fi">Ilkka Priha</a>
* @author <a href="mailto:mcconnell@apache.org">Stephen McConnell</a>
* @version $Id$
*
*/
public class DefaultFactoryService
extends AbstractLogEnabled
implements FactoryService, Configurable, Initializable, Disposable
{
protected boolean initialized = false;
/**
* The property specifying a set of additional class loaders.
*/
private static final String CLASS_LOADER = "classloader";
/**
* The property prefix specifying additional object factories.
*/
private static final String OBJECT_FACTORY = "object-factory";
/**
* The name of the default factory.
*/
protected static final String DEFAULT_FACTORY = "default";
/**
* Primitive classes for reflection of constructors.
*/
private static HashMap<String, Class<?>> primitiveClasses = new HashMap<String, Class<?>>(8);
{
primitiveClasses.put(Boolean.TYPE.toString(), Boolean.TYPE);
primitiveClasses.put(Character.TYPE.toString(), Character.TYPE);
primitiveClasses.put(Byte.TYPE.toString(), Byte.TYPE);
primitiveClasses.put(Short.TYPE.toString(), Short.TYPE);
primitiveClasses.put(Integer.TYPE.toString(), Integer.TYPE);
primitiveClasses.put(Long.TYPE.toString(), Long.TYPE);
primitiveClasses.put(Float.TYPE.toString(), Float.TYPE);
primitiveClasses.put(Double.TYPE.toString(), Double.TYPE);
}
/**
* temporary storage of class names between configure and initialize
*/
private String[] loaderNames;
/**
* Additional class loaders.
*/
private ArrayList<ClassLoader> classLoaders = new ArrayList<ClassLoader>();
/**
* Customized object factories.
*/
private ConcurrentHashMap<String, Factory<?>> objectFactories =
new ConcurrentHashMap<String, Factory<?>>();
/**
* Customized object factory classes.
*/
private ConcurrentHashMap<String, String> objectFactoryClasses =
new ConcurrentHashMap<String, String>();
/**
* Gets the class of a primitive type.
*
* @param type a primitive type.
* @return the corresponding class, or null.
*/
protected static Class<?> getPrimitiveClass(String type)
{
return primitiveClasses.get(type);
}
/**
* Gets an instance of a named class.
*
* @param className the name of the class.
* @return the instance.
* @throws FactoryException if instantiation fails.
*/
@Override
public <T> T getInstance(String className) throws FactoryException
{
if (className == null)
{
throw new FactoryException("Missing String className");
}
Factory<T> factory = getFactory(className);
if (factory == null)
{
Class<T> clazz;
try
{
clazz = loadClass(className);
}
catch (ClassNotFoundException x)
{
throw new FactoryException("Instantiation failed for class " + className, x);
}
return getInstance(clazz);
}
else
{
return factory.getInstance();
}
}
/**
* Gets an instance of a named class using a specified class loader.
*
* <p>Class loaders are supported only if the isLoaderSupported
* method returns true. Otherwise the loader parameter is ignored.
*
* @param className the name of the class.
* @param loader the class loader.
* @return the instance.
* @throws FactoryException if instantiation fails.
*/
@Override
public <T> T getInstance(String className, ClassLoader loader) throws FactoryException
{
Factory<T> factory = getFactory(className);
if (factory == null)
{
if (loader != null)
{
Class<T> clazz;
try
{
clazz = loadClass(className, loader);
}
catch (ClassNotFoundException x)
{
throw new FactoryException("Instantiation failed for class " + className, x);
}
return getInstance(clazz);
}
else
{
return getInstance(className);
}
}
else
{
return factory.getInstance(loader);
}
}
/**
* Gets an instance of a named class.
* Parameters for its constructor are given as an array of objects,
* primitive types must be wrapped with a corresponding class.
*
* @param className the name of the class.
* @param params an array containing the parameters of the constructor.
* @param signature an array containing the signature of the constructor.
* @return the instance.
* @throws FactoryException if instantiation fails.
*/
@Override
public <T> T getInstance(String className, Object[] params, String[] signature) throws FactoryException
{
Factory<T> factory = getFactory(className);
if (factory == null)
{
Class<T> clazz;
try
{
clazz = loadClass(className);
}
catch (ClassNotFoundException x)
{
throw new FactoryException("Instantiation failed for class " + className, x);
}
return getInstance(clazz, params, signature);
}
else
{
return factory.getInstance(params, signature);
}
}
/**
* Gets an instance of a named class using a specified class loader.
* Parameters for its constructor are given as an array of objects,
* primitive types must be wrapped with a corresponding class.
*
* <p>Class loaders are supported only if the isLoaderSupported
* method returns true. Otherwise the loader parameter is ignored.
*
* @param className the name of the class.
* @param loader the class loader.
* @param params an array containing the parameters of the constructor.
* @param signature an array containing the signature of the constructor.
* @return the instance.
* @throws FactoryException if instantiation fails.
*/
@Override
public <T> T getInstance(String className, ClassLoader loader, Object[] params, String[] signature)
throws FactoryException
{
Factory<T> factory = getFactory(className);
if (factory == null)
{
if (loader != null)
{
Class<T> clazz;
try
{
clazz = loadClass(className, loader);
}
catch (ClassNotFoundException x)
{
throw new FactoryException("Instantiation failed for class " + className, x);
}
return getInstance(clazz, params, signature);
}
else
{
return getInstance(className, params, signature);
}
}
else
{
return factory.getInstance(loader, params, signature);
}
}
/**
* Tests if specified class loaders are supported for a named class.
*
* @param className the name of the class.
* @return true if class loaders are supported, false otherwise.
* @throws FactoryException if test fails.
*/
@Override
public boolean isLoaderSupported(String className) throws FactoryException
{
Factory<?> factory = getFactory(className);
return factory != null ? factory.isLoaderSupported() : true;
}
/**
* Gets an instance of a specified class.
*
* @param clazz the class.
* @return the instance.
* @throws FactoryException if instantiation fails.
*/
@Override
public <T> T getInstance(Class<T> clazz) throws FactoryException
{
try
{
return clazz.newInstance();
}
catch (Exception x)
{
throw new FactoryException("Instantiation failed for " + clazz.getName(), x);
}
}
/**
* Gets an instance of a specified class.
* Parameters for its constructor are given as an array of objects,
* primitive types must be wrapped with a corresponding class.
*
* @param clazz the class.
* @param params an array containing the parameters of the constructor.
* @param signature an array containing the signature of the constructor.
* @return the instance.
* @throws FactoryException if instantiation fails.
*/
protected <T> T getInstance(Class<T> clazz, Object params[], String signature[]) throws FactoryException
{
/* Try to construct. */
try
{
Class<?>[] sign = getSignature(clazz, params, signature);
return clazz.getConstructor(sign).newInstance(params);
}
catch (Exception x)
{
throw new FactoryException("Instantiation failed for " + clazz.getName(), x);
}
}
/**
* Gets the signature classes for parameters of a method of a class.
*
* @param clazz the class.
* @param params an array containing the parameters of the method.
* @param signature an array containing the signature of the method.
* @return an array of signature classes. Note that in some cases
* objects in the parameter array can be switched to the context
* of a different class loader.
* @throws ClassNotFoundException if any of the classes is not found.
*/
@Override
public Class<?>[] getSignature(Class<?> clazz, Object params[], String signature[]) throws ClassNotFoundException
{
if (signature != null)
{
/* We have parameters. */
ClassLoader tempLoader;
ClassLoader loader = clazz.getClassLoader();
Class<?>[] sign = new Class[signature.length];
for (int i = 0; i < signature.length; i++)
{
/* Check primitive types. */
sign[i] = getPrimitiveClass(signature[i]);
if (sign[i] == null)
{
/* Not a primitive one, continue building. */
if (loader != null)
{
/* Use the class loader of the target object. */
sign[i] = loader.loadClass(signature[i]);
tempLoader = sign[i].getClassLoader();
if ((params[i] != null)
&& (tempLoader != null)
&& !tempLoader.equals(params[i].getClass().getClassLoader()))
{
/*
* The class uses a different class loader,
* switch the parameter.
*/
params[i] = switchObjectContext(params[i], loader);
}
}
else
{
/* Use the default class loader. */
sign[i] = loadClass(signature[i]);
}
}
}
return sign;
}
else
{
return null;
}
}
/**
* Switches an object into the context of a different class loader.
*
* @param object an object to switch.
* @param loader the ClassLoader to use
* @param loader the loader of the new context.
* @return the object
*/
protected Object switchObjectContext(Object object, ClassLoader loader)
{
ByteArrayOutputStream bout = new ByteArrayOutputStream();
try
{
ObjectOutputStream out = new ObjectOutputStream(bout);
out.writeObject(object);
out.flush();
}
catch (IOException x)
{
return object;
}
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
ObjectInputStreamForContext in = null;
try
{
in = new ObjectInputStreamForContext(bin, loader);
return in.readObject();
}
catch (Exception x)
{
return object;
}
finally
{
if (in != null)
{
try
{
in.close();
}
catch (IOException e)
{
// close quietly
}
}
}
}
/**
* Loads the named class using the default class loader.
*
* @param className the name of the class to load.
* @return {@inheritDoc} the loaded class.
* @throws ClassNotFoundException if the class was not found.
*/
@SuppressWarnings("unchecked")
protected <T> Class<T> loadClass(String className) throws ClassNotFoundException
{
ClassLoader loader = this.getClass().getClassLoader();
try
{
Class<T> clazz;
if (loader != null)
{
clazz = (Class<T>) loader.loadClass(className);
}
else
{
clazz = (Class<T>) Class.forName(className);
}
return clazz;
}
catch (ClassNotFoundException x)
{
/* Go through additional loaders. */
for (ClassLoader l : classLoaders)
{
try
{
return (Class<T>) l.loadClass(className);
}
catch (ClassNotFoundException xx)
{
// continue
}
}
/* Give up. */
throw x;
}
}
/**
* Loads the named class using a specified class loader.
*
* @param className the name of the class to load.
* @param loader the loader to use.
* @return {@inheritDoc} the loaded class.
* @throws ClassNotFoundException if the class was not found.
*/
@SuppressWarnings("unchecked")
protected <T> Class<T> loadClass(String className, ClassLoader loader) throws ClassNotFoundException
{
if (loader != null)
{
return (Class<T>) loader.loadClass(className);
}
else
{
return loadClass(className);
}
}
/**
* Gets a customized factory for a named class. If no class-specific
* factory is specified but a default factory is, will use the default
* factory.
*
* @param className the name of the class to load.
* @return {@inheritDoc} the factory, or null if not specified and no default.
* @throws FactoryException if instantiation of the factory fails.
*/
@SuppressWarnings("unchecked")
protected <T> Factory<T> getFactory(String className) throws FactoryException
{
Factory<T> factory = (Factory<T>) objectFactories.get(className);
if (factory == null)
{
//No named factory for this; try the default, if one
//exists.
factory = (Factory<T>) objectFactories.get(DEFAULT_FACTORY);
}
if (factory == null)
{
/* Not yet instantiated... */
String factoryClass = objectFactoryClasses.get(className);
if (factoryClass == null)
{
factoryClass = objectFactoryClasses.get(DEFAULT_FACTORY);
}
if (factoryClass == null)
{
return null;
}
try
{
factory = getInstance(factoryClass);
factory.init(className);
}
catch (ClassCastException x)
{
throw new FactoryException("Incorrect factory " + factoryClass + " for class " + className, x);
}
Factory<T> _factory = (Factory<T>) objectFactories.putIfAbsent(className, factory);
if (_factory != null)
{
// Already created - take first instance
factory = _factory;
}
}
return factory;
}
// ---------------- Avalon Lifecycle Methods ---------------------
/**
* Avalon component lifecycle method
*/
@Override
public void configure(Configuration conf) throws ConfigurationException
{
final Configuration[] loaders = conf.getChildren(CLASS_LOADER);
if (loaders != null)
{
loaderNames = new String[loaders.length];
for (int i = 0; i < loaders.length; i++)
{
loaderNames[i] = loaders[i].getValue();
}
}
final Configuration factories = conf.getChild(OBJECT_FACTORY, false);
if (factories != null)
{
Configuration[] nameVal = factories.getChildren();
for (int i = 0; i < nameVal.length; i++)
{
String key = nameVal[i].getName();
String factory = nameVal[i].getValue();
// Store the factory to the table as a string and
// instantiate it by using the service when needed.
objectFactoryClasses.put(key, factory);
}
}
}
/**
* Avalon component lifecycle method
* Initializes the service by loading default class loaders
* and customized object factories.
*
* @throws Exception if initialization fails.
*/
@Override
public void initialize() throws Exception
{
if (loaderNames != null)
{
for (int i = 0; i < loaderNames.length; i++)
{
try
{
ClassLoader loader = (ClassLoader) loadClass(loaderNames[i]).newInstance();
classLoaders.add(loader);
}
catch (Exception x)
{
throw new Exception(
"No such class loader '" + loaderNames[i] + "' for DefaultFactoryService", x);
}
}
loaderNames = null;
}
}
/**
* Avalon component lifecycle method
* Clear lists and maps
*/
@Override
public void dispose()
{
objectFactories.clear();
objectFactoryClasses.clear();
classLoaders.clear();
}
}