Open Source Repository

Home /spring/spring-beans-3.0.5 | Repository Home



org/springframework/beans/factory/support/BeanDefinitionValueResolver.java
/*
 * 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 = (RuntimeBeanReferencevalue;
      return resolveReference(argName, ref);
    }
    else if (value instanceof RuntimeBeanNameReference) {
      String refName = ((RuntimeBeanNameReferencevalue).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 = (BeanDefinitionHoldervalue;
      return resolveInnerBean(argName, bdHolder.getBeanName(), bdHolder.getBeanDefinition());
    }
    else if (value instanceof BeanDefinition) {
      // Resolve plain BeanDefinition, without contained name: use dummy name.
      BeanDefinition bd = (BeanDefinitionvalue;
      return resolveInnerBean(argName, "(inner bean)", bd);
    }
    else if (value instanceof ManagedArray) {
      // May need to resolve contained runtime references.
      ManagedArray array = (ManagedArrayvalue;
      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 = (Propertiesvalue;
      Properties copy = new Properties();
      for (Map.Entry propEntry : original.entrySet()) {
        Object propKey = propEntry.getKey();
        Object propValue = propEntry.getValue();
        if (propKey instanceof TypedStringValue) {
          propKey = evaluate((TypedStringValuepropKey);
        }
        if (propValue instanceof TypedStringValue) {
          propValue = evaluate((TypedStringValuepropValue);
        }
        copy.put(propKey, propValue);
      }
      return copy;
    }
    else if (value instanceof TypedStringValue) {
      // Convert value to target type here.
      TypedStringValue typedStringValue = (TypedStringValuevalue;
      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((Stringvalue, 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 valuethrows 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((FactoryBeaninnerBean, 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;
    }
  }

}