/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed 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.springframework.beans.factory.support;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeansException;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.RuntimeBeanNameReference;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* Helper class for use in bean factory implementations,
* resolving values contained in bean definition objects
* into the actual values applied to the target bean instance.
*
* <p>Operates on an {@link AbstractBeanFactory} and a plain
* {@link org.springframework.beans.factory.config.BeanDefinition} object.
* Used by {@link AbstractAutowireCapableBeanFactory}.
*
* @author Juergen Hoeller
* @since 1.2
* @see AbstractAutowireCapableBeanFactory
*/
class BeanDefinitionValueResolver {
private final AbstractBeanFactory beanFactory;
private final String beanName;
private final BeanDefinition beanDefinition;
private final TypeConverter typeConverter;
/**
* Create a BeanDefinitionValueResolver for the given BeanFactory and BeanDefinition.
* @param beanFactory the BeanFactory to resolve against
* @param beanName the name of the bean that we work on
* @param beanDefinition the BeanDefinition of the bean that we work on
* @param typeConverter the TypeConverter to use for resolving TypedStringValues
*/
public BeanDefinitionValueResolver(
AbstractBeanFactory beanFactory, String beanName, BeanDefinition beanDefinition, TypeConverter typeConverter) {
this.beanFactory = beanFactory;
this.beanName = beanName;
this.beanDefinition = beanDefinition;
this.typeConverter = typeConverter;
}
/**
* Given a PropertyValue, return a value, resolving any references to other
* beans in the factory if necessary. The value could be:
* <li>A BeanDefinition, which leads to the creation of a corresponding
* new bean instance. Singleton flags and names of such "inner beans"
* are always ignored: Inner beans are anonymous prototypes.
* <li>A RuntimeBeanReference, which must be resolved.
* <li>A ManagedList. This is a special collection that may contain
* RuntimeBeanReferences or Collections that will need to be resolved.
* <li>A ManagedSet. May also contain RuntimeBeanReferences or
* Collections that will need to be resolved.
* <li>A ManagedMap. In this case the value may be a RuntimeBeanReference
* or Collection that will need to be resolved.
* <li>An ordinary object or <code>null</code>, in which case it's left alone.
* @param argName the name of the argument that the value is defined for
* @param value the value object to resolve
* @return the resolved object
*/
public Object resolveValueIfNecessary(Object argName, Object value) {
// We must check each value to see whether it requires a runtime reference
// to another bean to be resolved.
if (value instanceof RuntimeBeanReference) {
RuntimeBeanReference ref = (RuntimeBeanReference) value;
return resolveReference(argName, ref);
}
else if (value instanceof RuntimeBeanNameReference) {
String refName = ((RuntimeBeanNameReference) value).getBeanName();
refName = String.valueOf(evaluate(refName));
if (!this.beanFactory.containsBean(refName)) {
throw new BeanDefinitionStoreException(
"Invalid bean name '" + refName + "' in bean reference for " + argName);
}
return refName;
}
else if (value instanceof BeanDefinitionHolder) {
// Resolve BeanDefinitionHolder: contains BeanDefinition with name and aliases.
BeanDefinitionHolder bdHolder = (BeanDefinitionHolder) value;
return resolveInnerBean(argName, bdHolder.getBeanName(), bdHolder.getBeanDefinition());
}
else if (value instanceof BeanDefinition) {
// Resolve plain BeanDefinition, without contained name: use dummy name.
BeanDefinition bd = (BeanDefinition) value;
return resolveInnerBean(argName, "(inner bean)", bd);
}
else if (value instanceof ManagedArray) {
// May need to resolve contained runtime references.
ManagedArray array = (ManagedArray) value;
Class elementType = array.resolvedElementType;
if (elementType == null) {
String elementTypeName = array.getElementTypeName();
if (StringUtils.hasText(elementTypeName)) {
try {
elementType = ClassUtils.forName(elementTypeName, this.beanFactory.getBeanClassLoader());
array.resolvedElementType = elementType;
}
catch (Throwable ex) {
// Improve the message by showing the context.
throw new BeanCreationException(
this.beanDefinition.getResourceDescription(), this.beanName,
"Error resolving array type for " + argName, ex);
}
}
else {
elementType = Object.class;
}
}
return resolveManagedArray(argName, (List<?>) value, elementType);
}
else if (value instanceof ManagedList) {
// May need to resolve contained runtime references.
return resolveManagedList(argName, (List<?>) value);
}
else if (value instanceof ManagedSet) {
// May need to resolve contained runtime references.
return resolveManagedSet(argName, (Set<?>) value);
}
else if (value instanceof ManagedMap) {
// May need to resolve contained runtime references.
return resolveManagedMap(argName, (Map<?, ?>) value);
}
else if (value instanceof ManagedProperties) {
Properties original = (Properties) value;
Properties copy = new Properties();
for (Map.Entry propEntry : original.entrySet()) {
Object propKey = propEntry.getKey();
Object propValue = propEntry.getValue();
if (propKey instanceof TypedStringValue) {
propKey = evaluate((TypedStringValue) propKey);
}
if (propValue instanceof TypedStringValue) {
propValue = evaluate((TypedStringValue) propValue);
}
copy.put(propKey, propValue);
}
return copy;
}
else if (value instanceof TypedStringValue) {
// Convert value to target type here.
TypedStringValue typedStringValue = (TypedStringValue) value;
Object valueObject = evaluate(typedStringValue);
try {
Class<?> resolvedTargetType = resolveTargetType(typedStringValue);
if (resolvedTargetType != null) {
return this.typeConverter.convertIfNecessary(valueObject, resolvedTargetType);
}
else {
return valueObject;
}
}
catch (Throwable ex) {
// Improve the message by showing the context.
throw new BeanCreationException(
this.beanDefinition.getResourceDescription(), this.beanName,
"Error converting typed String value for " + argName, ex);
}
}
else {
return evaluate(value);
}
}
/**
* Evaluate the given value as an expression, if necessary.
* @param value the candidate value (may be an expression)
* @return the resolved value
*/
protected Object evaluate(TypedStringValue value) {
Object result = this.beanFactory.evaluateBeanDefinitionString(value.getValue(), this.beanDefinition);
if (result != value.getValue()) {
value.setDynamic();
}
return result;
}
/**
* Evaluate the given value as an expression, if necessary.
* @param value the candidate value (may be an expression)
* @return the resolved value
*/
protected Object evaluate(Object value) {
if (value instanceof String) {
return this.beanFactory.evaluateBeanDefinitionString((String) value, this.beanDefinition);
}
else {
return value;
}
}
/**
* Resolve the target type in the given TypedStringValue.
* @param value the TypedStringValue to resolve
* @return the resolved target type (or <code>null</code> if none specified)
* @throws ClassNotFoundException if the specified type cannot be resolved
* @see TypedStringValue#resolveTargetType
*/
protected Class<?> resolveTargetType(TypedStringValue value) throws ClassNotFoundException {
if (value.hasTargetType()) {
return value.getTargetType();
}
return value.resolveTargetType(this.beanFactory.getBeanClassLoader());
}
/**
* Resolve an inner bean definition.
* @param argName the name of the argument that the inner bean is defined for
* @param innerBeanName the name of the inner bean
* @param innerBd the bean definition for the inner bean
* @return the resolved inner bean instance
*/
private Object resolveInnerBean(Object argName, String innerBeanName, BeanDefinition innerBd) {
RootBeanDefinition mbd = null;
try {
mbd = this.beanFactory.getMergedBeanDefinition(innerBeanName, innerBd, this.beanDefinition);
// Check given bean name whether it is unique. If not already unique,
// add counter - increasing the counter until the name is unique.
String actualInnerBeanName = innerBeanName;
if (mbd.isSingleton()) {
actualInnerBeanName = adaptInnerBeanName(innerBeanName);
}
// Guarantee initialization of beans that the inner bean depends on.
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dependsOnBean : dependsOn) {
this.beanFactory.getBean(dependsOnBean);
this.beanFactory.registerDependentBean(dependsOnBean, actualInnerBeanName);
}
}
Object innerBean = this.beanFactory.createBean(actualInnerBeanName, mbd, null);
this.beanFactory.registerContainedBean(actualInnerBeanName, this.beanName);
if (innerBean instanceof FactoryBean) {
boolean synthetic = (mbd != null && mbd.isSynthetic());
return this.beanFactory.getObjectFromFactoryBean((FactoryBean) innerBean, actualInnerBeanName, !synthetic);
}
else {
return innerBean;
}
}
catch (BeansException ex) {
throw new BeanCreationException(
this.beanDefinition.getResourceDescription(), this.beanName,
"Cannot create inner bean '" + innerBeanName + "' " +
(mbd != null && mbd.getBeanClassName() != null ? "of type [" + mbd.getBeanClassName() + "] " : "") +
"while setting " + argName, ex);
}
}
/**
* Checks the given bean name whether it is unique. If not already unique,
* a counter is added, increasing the counter until the name is unique.
* @param innerBeanName the original name for the inner bean
* @return the adapted name for the inner bean
*/
private String adaptInnerBeanName(String innerBeanName) {
String actualInnerBeanName = innerBeanName;
int counter = 0;
while (this.beanFactory.isBeanNameInUse(actualInnerBeanName)) {
counter++;
actualInnerBeanName = innerBeanName + BeanFactoryUtils.GENERATED_BEAN_NAME_SEPARATOR + counter;
}
return actualInnerBeanName;
}
/**
* Resolve a reference to another bean in the factory.
*/
private Object resolveReference(Object argName, RuntimeBeanReference ref) {
try {
String refName = ref.getBeanName();
refName = String.valueOf(evaluate(refName));
if (ref.isToParent()) {
if (this.beanFactory.getParentBeanFactory() == null) {
throw new BeanCreationException(
this.beanDefinition.getResourceDescription(), this.beanName,
"Can't resolve reference to bean '" + refName +
"' in parent factory: no parent factory available");
}
return this.beanFactory.getParentBeanFactory().getBean(refName);
}
else {
Object bean = this.beanFactory.getBean(refName);
this.beanFactory.registerDependentBean(refName, this.beanName);
return bean;
}
}
catch (BeansException ex) {
throw new BeanCreationException(
this.beanDefinition.getResourceDescription(), this.beanName,
"Cannot resolve reference to bean '" + ref.getBeanName() + "' while setting " + argName, ex);
}
}
/**
* For each element in the managed array, resolve reference if necessary.
*/
private Object resolveManagedArray(Object argName, List<?> ml, Class elementType) {
Object resolved = Array.newInstance(elementType, ml.size());
for (int i = 0; i < ml.size(); i++) {
Array.set(resolved, i,
resolveValueIfNecessary(new KeyedArgName(argName, i), ml.get(i)));
}
return resolved;
}
/**
* For each element in the managed list, resolve reference if necessary.
*/
private List resolveManagedList(Object argName, List<?> ml) {
List<Object> resolved = new ArrayList<Object>(ml.size());
for (int i = 0; i < ml.size(); i++) {
resolved.add(
resolveValueIfNecessary(new KeyedArgName(argName, i), ml.get(i)));
}
return resolved;
}
/**
* For each element in the managed set, resolve reference if necessary.
*/
private Set resolveManagedSet(Object argName, Set<?> ms) {
Set<Object> resolved = new LinkedHashSet<Object>(ms.size());
int i = 0;
for (Object m : ms) {
resolved.add(resolveValueIfNecessary(new KeyedArgName(argName, i), m));
i++;
}
return resolved;
}
/**
* For each element in the managed map, resolve reference if necessary.
*/
private Map resolveManagedMap(Object argName, Map<?, ?> mm) {
Map<Object, Object> resolved = new LinkedHashMap<Object, Object>(mm.size());
for (Map.Entry entry : mm.entrySet()) {
Object resolvedKey = resolveValueIfNecessary(argName, entry.getKey());
Object resolvedValue = resolveValueIfNecessary(
new KeyedArgName(argName, entry.getKey()), entry.getValue());
resolved.put(resolvedKey, resolvedValue);
}
return resolved;
}
/**
* Holder class used for delayed toString building.
*/
private static class KeyedArgName {
private final Object argName;
private final Object key;
public KeyedArgName(Object argName, Object key) {
this.argName = argName;
this.key = key;
}
@Override
public String toString() {
return this.argName + " with key " + BeanWrapper.PROPERTY_KEY_PREFIX +
this.key + BeanWrapper.PROPERTY_KEY_SUFFIX;
}
}
}
|