blob: 3049c7fc36a4375404b739c7d7ec13f2b0c7b0a3 [file] [log] [blame]
/*
* 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.aries.blueprint.container;
import static org.apache.aries.blueprint.utils.ReflectionUtils.getRealCause;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.aries.blueprint.BeanProcessor;
import org.apache.aries.blueprint.ComponentDefinitionRegistry;
import org.apache.aries.blueprint.ExtendedBlueprintContainer;
import org.apache.aries.blueprint.Interceptor;
import org.apache.aries.blueprint.di.AbstractRecipe;
import org.apache.aries.blueprint.di.Recipe;
import org.apache.aries.blueprint.proxy.AsmInterceptorWrapper;
import org.apache.aries.blueprint.proxy.CgLibInterceptorWrapper;
import org.apache.aries.blueprint.utils.ReflectionUtils;
import org.apache.aries.blueprint.utils.ReflectionUtils.PropertyDescriptor;
import org.osgi.service.blueprint.container.ComponentDefinitionException;
import org.osgi.service.blueprint.container.ReifiedType;
import org.osgi.service.blueprint.reflect.BeanMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A <code>Recipe</code> to create POJOs.
*
* @version $Rev: 978875 $, $Date: 2010-07-24 14:45:05 +0100 (Sat, 24 Jul 2010) $
*/
public class BeanRecipe extends AbstractRecipe {
private static final Logger LOGGER = LoggerFactory.getLogger(BeanRecipe.class);
private final ExtendedBlueprintContainer blueprintContainer;
private final LinkedHashMap<String,Object> properties = new LinkedHashMap<String,Object>();
private final Object type;
private String initMethod;
private String destroyMethod;
private List<Recipe> explicitDependencies;
private Recipe factory;
private String factoryMethod;
private List<Object> arguments;
private List<String> argTypes;
private boolean reorderArguments;
private final boolean allowsFieldInjection;
private BeanMetadata interceptorLookupKey;
public BeanRecipe(String name, ExtendedBlueprintContainer blueprintContainer, Object type, boolean allowsFieldInjection) {
super(name);
this.blueprintContainer = blueprintContainer;
this.type = type;
this.allowsFieldInjection = allowsFieldInjection;
}
public Object getProperty(String name) {
return properties.get(name);
}
public Map<String, Object> getProperties() {
return new LinkedHashMap<String, Object>(properties);
}
public void setProperty(String name, Object value) {
properties.put(name, value);
}
public void setFactoryMethod(String method) {
this.factoryMethod = method;
}
public void setFactoryComponent(Recipe factory) {
this.factory = factory;
}
public void setArgTypes(List<String> argTypes) {
this.argTypes = argTypes;
}
public void setArguments(List<Object> arguments) {
this.arguments = arguments;
}
public void setReorderArguments(boolean reorder) {
this.reorderArguments = reorder;
}
public void setInitMethod(String initMethod) {
this.initMethod = initMethod;
}
public String getInitMethod() {
return initMethod;
}
public void setDestroyMethod(String destroyMethod) {
this.destroyMethod = destroyMethod;
}
public String getDestroyMethod() {
return destroyMethod;
}
public List<Recipe> getExplicitDependencies() {
return explicitDependencies;
}
public void setExplicitDependencies(List<Recipe> explicitDependencies) {
this.explicitDependencies = explicitDependencies;
}
public void setInterceptorLookupKey(BeanMetadata metadata) {
interceptorLookupKey = metadata;
}
@Override
public List<Recipe> getConstructorDependencies() {
List<Recipe> recipes = new ArrayList<Recipe>();
if (explicitDependencies != null) {
recipes.addAll(explicitDependencies);
}
if (arguments != null) {
for (Object argument : arguments) {
if (argument instanceof Recipe) {
recipes.add((Recipe)argument);
}
}
}
return recipes;
}
public List<Recipe> getDependencies() {
List<Recipe> recipes = new ArrayList<Recipe>();
for (Object o : properties.values()) {
if (o instanceof Recipe) {
Recipe recipe = (Recipe) o;
recipes.add(recipe);
}
}
recipes.addAll(getConstructorDependencies());
return recipes;
}
private void instantiateExplicitDependencies() {
if (explicitDependencies != null) {
for (Recipe recipe : explicitDependencies) {
recipe.create();
}
}
}
@Override
protected Class loadClass(String className) {
ClassLoader loader = type instanceof Class ? ((Class) type).getClassLoader() : null;
ReifiedType t = loadType(className, loader);
return t != null ? t.getRawClass() : null;
}
@Override
protected ReifiedType loadType(String className) {
return loadType(className, type instanceof Class ? ((Class) type).getClassLoader() : null);
}
private Object getInstance() throws ComponentDefinitionException {
Object instance;
// Instanciate arguments
List<Object> args = new ArrayList<Object>();
List<ReifiedType> argTypes = new ArrayList<ReifiedType>();
if (arguments != null) {
for (int i = 0; i < arguments.size(); i++) {
Object arg = arguments.get(i);
if (arg instanceof Recipe) {
args.add(((Recipe) arg).create());
} else {
args.add(arg);
}
if (this.argTypes != null) {
argTypes.add(this.argTypes.get(i) != null ? loadType(this.argTypes.get(i)) : null);
}
}
}
if (factory != null) {
// look for instance method on factory object
Object factoryObj = factory.create();
// If the factory is a service reference, we need to get hold of the actual proxy for the service
if (factoryObj instanceof ReferenceRecipe.ServiceProxyWrapper) {
try {
factoryObj = ((ReferenceRecipe.ServiceProxyWrapper) factoryObj).convert(new ReifiedType(Object.class));
} catch (Exception e) {
throw new ComponentDefinitionException("Error when instantiating bean " + getName() + " of class " + getType(), getRealCause(e));
}
}
// Map of matching methods
Map<Method, List<Object>> matches = findMatchingMethods(factoryObj.getClass(), factoryMethod, true, args, argTypes);
if (matches.size() == 1) {
try {
Map.Entry<Method, List<Object>> match = matches.entrySet().iterator().next();
instance = invoke(match.getKey(), factoryObj, match.getValue().toArray());
} catch (Throwable e) {
throw new ComponentDefinitionException("Error when instantiating bean " + getName() + " of class " + getType(), getRealCause(e));
}
} else if (matches.size() == 0) {
throw new ComponentDefinitionException("Unable to find a matching factory method " + factoryMethod + " on class " + factoryObj.getClass().getName() + " for arguments " + args + " when instanciating bean " + getName());
} else {
throw new ComponentDefinitionException("Multiple matching factory methods " + factoryMethod + " found on class " + factoryObj.getClass().getName() + " for arguments " + args + " when instanciating bean " + getName() + ": " + matches.keySet());
}
} else if (factoryMethod != null) {
// Map of matching methods
Map<Method, List<Object>> matches = findMatchingMethods(getType(), factoryMethod, false, args, argTypes);
if (matches.size() == 1) {
try {
Map.Entry<Method, List<Object>> match = matches.entrySet().iterator().next();
instance = invoke(match.getKey(), null, match.getValue().toArray());
} catch (Throwable e) {
throw new ComponentDefinitionException("Error when instanciating bean " + getName() + " of class " + getType(), getRealCause(e));
}
} else if (matches.size() == 0) {
throw new ComponentDefinitionException("Unable to find a matching factory method " + factoryMethod + " on class " + getType().getName() + " for arguments " + args + " when instanciating bean " + getName());
} else {
throw new ComponentDefinitionException("Multiple matching factory methods " + factoryMethod + " found on class " + getType().getName() + " for arguments " + args + " when instanciating bean " + getName() + ": " + matches.keySet());
}
} else {
if (getType() == null) {
throw new ComponentDefinitionException("No factoryMethod nor class is defined for this bean");
}
// Map of matching constructors
Map<Constructor, List<Object>> matches = findMatchingConstructors(getType(), args, argTypes);
if (matches.size() == 1) {
try {
Map.Entry<Constructor, List<Object>> match = matches.entrySet().iterator().next();
instance = newInstance(match.getKey(), match.getValue().toArray());
} catch (Throwable e) {
throw new ComponentDefinitionException("Error when instanciating bean " + getName() + " of class " + getType(), getRealCause(e));
}
} else if (matches.size() == 0) {
throw new ComponentDefinitionException("Unable to find a matching constructor on class " + getType().getName() + " for arguments " + args + " when instanciating bean " + getName());
} else {
throw new ComponentDefinitionException("Multiple matching constructors found on class " + getType().getName() + " for arguments " + args + " when instanciating bean " + getName() + ": " + matches.keySet());
}
}
return instance;
}
private Map<Method, List<Object>> findMatchingMethods(Class type, String name, boolean instance, List<Object> args, List<ReifiedType> types) {
Map<Method, List<Object>> matches = new HashMap<Method, List<Object>>();
// Get constructors
List<Method> methods = new ArrayList<Method>(Arrays.asList(type.getMethods()));
// Discard any signature with wrong cardinality
for (Iterator<Method> it = methods.iterator(); it.hasNext();) {
Method mth = it.next();
if (!mth.getName().equals(name)) {
it.remove();
} else if (mth.getParameterTypes().length != args.size()) {
it.remove();
} else if (instance ^ !Modifier.isStatic(mth.getModifiers())) {
it.remove();
} else if (mth.isBridge()) {
it.remove();
}
}
// on some JVMs (J9) hidden static methods are returned by Class.getMethods so we need to weed them out
// to reduce ambiguity
if (!instance) {
methods = applyStaticHidingRules(methods);
}
// Find a direct match with assignment
if (matches.size() != 1) {
Map<Method, List<Object>> nmatches = new HashMap<Method, List<Object>>();
for (Method mth : methods) {
boolean found = true;
List<Object> match = new ArrayList<Object>();
for (int i = 0; i < args.size(); i++) {
ReifiedType argType = new GenericType(mth.getGenericParameterTypes()[i]);
if (types.get(i) != null && !argType.getRawClass().equals(types.get(i).getRawClass())) {
found = false;
break;
}
if (!AggregateConverter.isAssignable(args.get(i), argType)) {
found = false;
break;
}
try {
match.add(convert(args.get(i), mth.getGenericParameterTypes()[i]));
} catch (Throwable t) {
found = false;
break;
}
}
if (found) {
nmatches.put(mth, match);
}
}
if (nmatches.size() > 0) {
matches = nmatches;
}
}
// Find a direct match with conversion
if (matches.size() != 1) {
Map<Method, List<Object>> nmatches = new HashMap<Method, List<Object>>();
for (Method mth : methods) {
boolean found = true;
List<Object> match = new ArrayList<Object>();
for (int i = 0; i < args.size(); i++) {
ReifiedType argType = new GenericType(mth.getGenericParameterTypes()[i]);
if (types.get(i) != null && !argType.getRawClass().equals(types.get(i).getRawClass())) {
found = false;
break;
}
try {
Object val = convert(args.get(i), argType);
match.add(val);
} catch (Throwable t) {
found = false;
break;
}
}
if (found) {
nmatches.put(mth, match);
}
}
if (nmatches.size() > 0) {
matches = nmatches;
}
}
// Start reordering with assignment
if (matches.size() != 1 && reorderArguments && args.size() > 1) {
Map<Method, List<Object>> nmatches = new HashMap<Method, List<Object>>();
for (Method mth : methods) {
ArgumentMatcher matcher = new ArgumentMatcher(mth.getGenericParameterTypes(), false);
List<Object> match = matcher.match(args, types);
if (match != null) {
nmatches.put(mth, match);
}
}
if (nmatches.size() > 0) {
matches = nmatches;
}
}
// Start reordering with conversion
if (matches.size() != 1 && reorderArguments && args.size() > 1) {
Map<Method, List<Object>> nmatches = new HashMap<Method, List<Object>>();
for (Method mth : methods) {
ArgumentMatcher matcher = new ArgumentMatcher(mth.getGenericParameterTypes(), true);
List<Object> match = matcher.match(args, types);
if (match != null) {
nmatches.put(mth, match);
}
}
if (nmatches.size() > 0) {
matches = nmatches;
}
}
return matches;
}
private static List<Method> applyStaticHidingRules(Collection<Method> methods) {
List<Method> result = new ArrayList<Method>(methods.size());
for (Method m : methods) {
boolean toBeAdded = true;
Iterator<Method> it = result.iterator();
inner: while (it.hasNext()) {
Method other = it.next();
if (hasIdenticalParameters(m, other)) {
Class<?> mClass = m.getDeclaringClass();
Class<?> otherClass = other.getDeclaringClass();
if (mClass.isAssignableFrom(otherClass)) {
toBeAdded = false;
break inner;
} else if (otherClass.isAssignableFrom(mClass)) {
it.remove();
}
}
}
if (toBeAdded) result.add(m);
}
return result;
}
private static boolean hasIdenticalParameters(Method one, Method two) {
Class<?>[] oneTypes = one.getParameterTypes();
Class<?>[] twoTypes = two.getParameterTypes();
if (oneTypes.length != twoTypes.length) return false;
for (int i=0; i<oneTypes.length; i++) {
if (!oneTypes[i].equals(twoTypes[i])) return false;
}
return true;
}
private Map<Constructor, List<Object>> findMatchingConstructors(Class type, List<Object> args, List<ReifiedType> types) {
Map<Constructor, List<Object>> matches = new HashMap<Constructor, List<Object>>();
// Get constructors
List<Constructor> constructors = new ArrayList<Constructor>(Arrays.asList(type.getConstructors()));
// Discard any signature with wrong cardinality
for (Iterator<Constructor> it = constructors.iterator(); it.hasNext();) {
if (it.next().getParameterTypes().length != args.size()) {
it.remove();
}
}
// Find a direct match with assignment
if (matches.size() != 1) {
Map<Constructor, List<Object>> nmatches = new HashMap<Constructor, List<Object>>();
for (Constructor cns : constructors) {
boolean found = true;
List<Object> match = new ArrayList<Object>();
for (int i = 0; i < args.size(); i++) {
ReifiedType argType = new GenericType(cns.getGenericParameterTypes()[i]);
if (types.get(i) != null && !argType.getRawClass().equals(types.get(i).getRawClass())) {
found = false;
break;
}
if (!AggregateConverter.isAssignable(args.get(i), argType)) {
found = false;
break;
}
try {
match.add(convert(args.get(i), cns.getGenericParameterTypes()[i]));
} catch (Throwable t) {
found = false;
break;
}
}
if (found) {
nmatches.put(cns, match);
}
}
if (nmatches.size() > 0) {
matches = nmatches;
}
}
// Find a direct match with conversion
if (matches.size() != 1) {
Map<Constructor, List<Object>> nmatches = new HashMap<Constructor, List<Object>>();
for (Constructor cns : constructors) {
boolean found = true;
List<Object> match = new ArrayList<Object>();
for (int i = 0; i < args.size(); i++) {
ReifiedType argType = new GenericType(cns.getGenericParameterTypes()[i]);
if (types.get(i) != null && !argType.getRawClass().equals(types.get(i).getRawClass())) {
found = false;
break;
}
try {
Object val = convert(args.get(i), argType);
match.add(val);
} catch (Throwable t) {
found = false;
break;
}
}
if (found) {
nmatches.put(cns, match);
}
}
if (nmatches.size() > 0) {
matches = nmatches;
}
}
// Start reordering with assignment
if (matches.size() != 1 && reorderArguments && arguments.size() > 1) {
Map<Constructor, List<Object>> nmatches = new HashMap<Constructor, List<Object>>();
for (Constructor cns : constructors) {
ArgumentMatcher matcher = new ArgumentMatcher(cns.getGenericParameterTypes(), false);
List<Object> match = matcher.match(args, types);
if (match != null) {
nmatches.put(cns, match);
}
}
if (nmatches.size() > 0) {
matches = nmatches;
}
}
// Start reordering with conversion
if (matches.size() != 1 && reorderArguments && arguments.size() > 1) {
Map<Constructor, List<Object>> nmatches = new HashMap<Constructor, List<Object>>();
for (Constructor cns : constructors) {
ArgumentMatcher matcher = new ArgumentMatcher(cns.getGenericParameterTypes(), true);
List<Object> match = matcher.match(args, types);
if (match != null) {
nmatches.put(cns, match);
}
}
if (nmatches.size() > 0) {
matches = nmatches;
}
}
return matches;
}
/**
* Returns init method (if any). Throws exception if the init-method was set explicitly on the bean
* and the method is not found on the instance.
*/
protected Method getInitMethod(Object instance) throws ComponentDefinitionException {
Method method = null;
if (initMethod != null && initMethod.length() > 0) {
method = ReflectionUtils.getLifecycleMethod(instance.getClass(), initMethod);
if (method == null) {
throw new ComponentDefinitionException("Component '" + getName() + "' does not have init-method: " + initMethod);
}
}
return method;
}
/**
* Returns destroy method (if any). Throws exception if the destroy-method was set explicitly on the bean
* and the method is not found on the instance.
*/
public Method getDestroyMethod(Object instance) throws ComponentDefinitionException {
Method method = null;
if (destroyMethod != null && destroyMethod.length() > 0) {
method = ReflectionUtils.getLifecycleMethod(instance.getClass(), destroyMethod);
if (method == null) {
throw new ComponentDefinitionException("Component '" + getName() + "' does not have destroy-method: " + destroyMethod);
}
}
return method;
}
/**
* Small helper class, to construct a chain of BeanCreators.
* <br>
* Each bean creator in the chain will return a bean that has been
* processed by every BeanProcessor in the chain before it.
*/
private static class BeanCreatorChain implements BeanProcessor.BeanCreator {
public enum ChainType{Before,After};
private BeanProcessor.BeanCreator parentBeanCreator;
private BeanProcessor parentBeanProcessor;
private BeanMetadata beanData;
private String beanName;
private ChainType when;
public BeanCreatorChain(BeanProcessor.BeanCreator parentBeanCreator,
BeanProcessor parentBeanProcessor,
BeanMetadata beanData,
String beanName,
ChainType when){
this.parentBeanCreator = parentBeanCreator;
this.parentBeanProcessor = parentBeanProcessor;
this.beanData = beanData;
this.beanName = beanName;
this.when = when;
}
public Object getBean() {
Object previousBean = parentBeanCreator.getBean();
Object processed = null;
switch(when){
case Before :
processed = parentBeanProcessor.beforeInit(previousBean, beanName, parentBeanCreator, beanData);
break;
case After:
processed = parentBeanProcessor.afterInit(previousBean, beanName, parentBeanCreator, beanData);
break;
}
return processed;
}
}
private Object runBeanProcPreInit(Object obj){
String beanName = getName();
BeanMetadata beanData = (BeanMetadata) blueprintContainer
.getComponentDefinitionRegistry().getComponentDefinition(beanName);
List<BeanProcessor> processors = blueprintContainer.getProcessors(BeanProcessor.class);
//The start link of the chain, that provides the
//original, unprocessed bean to the head of the chain.
BeanProcessor.BeanCreator initialBeanCreator = new BeanProcessor.BeanCreator() {
public Object getBean() {
Object obj = getInstance();
//getinit, getdestroy, addpartial object don't need calling again.
//however, property injection does.
setProperties(obj);
return obj;
}
};
BeanProcessor.BeanCreator currentCreator = initialBeanCreator;
for(BeanProcessor processor : processors){
obj = processor.beforeInit(obj, getName(), currentCreator, beanData);
currentCreator = new BeanCreatorChain(currentCreator, processor, beanData, beanName, BeanCreatorChain.ChainType.Before);
}
return obj;
}
private void runBeanProcInit(Method initMethod, Object obj){
// call init method
if (initMethod != null) {
try {
invoke(initMethod, obj, (Object[]) null);
} catch (Throwable t) {
throw new ComponentDefinitionException("Unable to intialize bean " + getName(), getRealCause(t));
}
}
}
private Object runBeanProcPostInit(Object obj){
String beanName = getName();
BeanMetadata beanData = (BeanMetadata) blueprintContainer
.getComponentDefinitionRegistry().getComponentDefinition(beanName);
List<BeanProcessor> processors = blueprintContainer.getProcessors(BeanProcessor.class);
//The start link of the chain, that provides the
//original, unprocessed bean to the head of the chain.
BeanProcessor.BeanCreator initialBeanCreator = new BeanProcessor.BeanCreator() {
public Object getBean() {
Object obj = getInstance();
//getinit, getdestroy, addpartial object don't need calling again.
//however, property injection does.
setProperties(obj);
//as this is the post init chain, new beans need to go thru
//the pre-init chain, and then have init called, before
//being passed along the post-init chain.
obj = runBeanProcPreInit(obj);
runBeanProcInit(getInitMethod(obj), obj);
return obj;
}
};
BeanProcessor.BeanCreator currentCreator = initialBeanCreator;
for(BeanProcessor processor : processors){
obj = processor.afterInit(obj, getName(), currentCreator, beanData);
currentCreator = new BeanCreatorChain(currentCreator, processor, beanData, beanName, BeanCreatorChain.ChainType.After);
}
return obj;
}
private Object addInterceptors(Object original)
throws ComponentDefinitionException {
Object intercepted = null;
ComponentDefinitionRegistry reg = blueprintContainer
.getComponentDefinitionRegistry();
List<Interceptor> interceptors = reg.getInterceptors(interceptorLookupKey);
if (interceptors != null && interceptors.size() > 0) {
boolean asmAvailable = false;
try {
// Try load load an asm class (to make sure it's actually
// available)
getClass().getClassLoader().loadClass(
"org.objectweb.asm.ClassVisitor");
LOGGER.debug("asm available for interceptors");
asmAvailable = true;
} catch (Throwable t) {
try {
// Try load load a cglib class (to make sure it's actually
// available)
getClass().getClassLoader().loadClass(
"net.sf.cglib.proxy.Enhancer");
} catch (Throwable u) {
throw new ComponentDefinitionException(
"Interceptors have been configured but neither asm nor cglib are available",
u);
}
}
if (asmAvailable) {
// if asm is available we can proxy the original object with the
// AsmInterceptorWrapper
intercepted = AsmInterceptorWrapper.createProxyObject(original
.getClass().getClassLoader(), interceptorLookupKey, interceptors,
original, original.getClass());
} else {
LOGGER.debug("cglib available for interceptors");
// otherwise we're using cglib and need to use the interfaces
// with the CgLibInterceptorWrapper
intercepted = CgLibInterceptorWrapper.createProxyObject(
original.getClass().getClassLoader(), interceptorLookupKey,
interceptors, original, original.getClass()
.getInterfaces());
}
} else {
intercepted = original;
}
return intercepted;
}
@Override
protected Object internalCreate() throws ComponentDefinitionException {
instantiateExplicitDependencies();
Object obj = getInstance();
// check for init lifecycle method (if any)
Method initMethod = getInitMethod(obj);
// check for destroy lifecycle method (if any)
getDestroyMethod(obj);
// Add partially created object to the container
// if (initMethod == null) {
addPartialObject(obj);
// }
// inject properties
setProperties(obj);
obj = runBeanProcPreInit(obj);
runBeanProcInit(initMethod, obj);
obj = runBeanProcPostInit(obj);
obj = addInterceptors(obj);
return obj;
}
@Override
public void destroy(Object obj) {
for (BeanProcessor processor : blueprintContainer.getProcessors(BeanProcessor.class)) {
processor.beforeDestroy(obj, getName());
}
try {
Method method = getDestroyMethod(obj);
if (method != null) {
invoke(method, obj, (Object[]) null);
}
} catch (Exception e) {
LOGGER.info("Error invoking destroy method", getRealCause(e));
}
for (BeanProcessor processor : blueprintContainer.getProcessors(BeanProcessor.class)) {
processor.afterDestroy(obj, getName());
}
}
public void setProperties(Object instance) throws ComponentDefinitionException {
// clone the properties so they can be used again
Map<String,Object> propertyValues = new LinkedHashMap<String,Object>(properties);
setProperties(propertyValues, instance, instance.getClass());
}
public Class getType() {
if (type instanceof Class) {
return (Class) type;
} else if (type instanceof String) {
return loadClass((String) type);
} else {
return null;
}
}
private void setProperties(Map<String, Object> propertyValues, Object instance, Class clazz) {
// set remaining properties
for (Map.Entry<String, Object> entry : propertyValues.entrySet()) {
String propertyName = entry.getKey();
Object propertyValue = entry.getValue();
setProperty(instance, clazz, propertyName, propertyValue);
}
}
private void setProperty(Object instance, Class clazz, String propertyName, Object propertyValue) {
String[] names = propertyName.split("\\.");
for (int i = 0; i < names.length - 1; i++) {
PropertyDescriptor pd = getPropertyDescriptor(clazz, names[i]);
if (pd.allowsGet()) {
try {
instance = pd.get(instance, blueprintContainer.getAccessControlContext());
} catch (Exception e) {
throw new ComponentDefinitionException("Error getting property: " + names[i] + " on bean " + getName() + " when setting property " + propertyName + " on class " + clazz.getName(), getRealCause(e));
}
if (instance == null) {
throw new ComponentDefinitionException("Error setting compound property " + propertyName + " on bean " + getName() + ". Property " + names[i] + " is null");
}
clazz = instance.getClass();
} else {
throw new ComponentDefinitionException("No getter for " + names[i] + " property on bean " + getName() + " when setting property " + propertyName + " on class " + clazz.getName());
}
}
// Instantiate value
if (propertyValue instanceof Recipe) {
propertyValue = ((Recipe) propertyValue).create();
}
final PropertyDescriptor pd = getPropertyDescriptor(clazz, names[names.length - 1]);
if (pd.allowsSet()) {
try {
pd.set(instance, propertyValue, blueprintContainer.getAccessControlContext());
} catch (Exception e) {
throw new ComponentDefinitionException("Error setting property: " + pd, getRealCause(e));
}
} else {
throw new ComponentDefinitionException("No setter for " + names[names.length - 1] + " property");
}
}
private ReflectionUtils.PropertyDescriptor getPropertyDescriptor(Class<?> clazz, String name) {
for (ReflectionUtils.PropertyDescriptor pd : ReflectionUtils.getPropertyDescriptors(clazz, allowsFieldInjection)) {
if (pd.getName().equals(name)) {
return pd;
}
}
throw new ComponentDefinitionException("Unable to find property descriptor " + name + " on class " + clazz.getName());
}
private Object invoke(Method method, Object instance, Object... args) throws Exception {
return ReflectionUtils.invoke(blueprintContainer.getAccessControlContext(), method, instance, args);
}
private Object newInstance(Constructor constructor, Object... args) throws Exception {
return ReflectionUtils.newInstance(blueprintContainer.getAccessControlContext(), constructor, args);
}
private static Object UNMATCHED = new Object();
private class ArgumentMatcher {
private List<TypeEntry> entries;
private boolean convert;
public ArgumentMatcher(Type[] types, boolean convert) {
entries = new ArrayList<TypeEntry>();
for (Type type : types) {
entries.add(new TypeEntry(new GenericType(type)));
}
this.convert = convert;
}
public List<Object> match(List<Object> arguments, List<ReifiedType> forcedTypes) {
if (find(arguments, forcedTypes)) {
return getArguments();
}
return null;
}
private List<Object> getArguments() {
List<Object> list = new ArrayList<Object>();
for (TypeEntry entry : entries) {
if (entry.argument == UNMATCHED) {
throw new RuntimeException("There are unmatched types");
} else {
list.add(entry.argument);
}
}
return list;
}
private boolean find(List<Object> arguments, List<ReifiedType> forcedTypes) {
if (entries.size() == arguments.size()) {
boolean matched = true;
for (int i = 0; i < arguments.size() && matched; i++) {
matched = find(arguments.get(i), forcedTypes.get(i));
}
return matched;
}
return false;
}
private boolean find(Object arg, ReifiedType forcedType) {
for (TypeEntry entry : entries) {
Object val = arg;
if (entry.argument != UNMATCHED) {
continue;
}
if (forcedType != null) {
if (!forcedType.equals(entry.type)) {
continue;
}
} else if (arg != null) {
if (convert) {
try {
// TODO: call canConvert instead of convert()
val = convert(arg, entry.type);
} catch (Throwable t) {
continue;
}
} else {
if (!AggregateConverter.isAssignable(arg, entry.type)) {
continue;
}
}
}
entry.argument = val;
return true;
}
return false;
}
}
private static class TypeEntry {
private final ReifiedType type;
private Object argument;
public TypeEntry(ReifiedType type) {
this.type = type;
this.argument = UNMATCHED;
}
}
}