diff --git a/org.springframework.beans/ivy.xml b/org.springframework.beans/ivy.xml index d6418d085a..2d06b0be3c 100644 --- a/org.springframework.beans/ivy.xml +++ b/org.springframework.beans/ivy.xml @@ -21,7 +21,10 @@ - + + + + diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/AbstractPropertyAccessor.java b/org.springframework.beans/src/main/java/org/springframework/beans/AbstractPropertyAccessor.java new file mode 100644 index 0000000000..fd2ecfeae2 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/AbstractPropertyAccessor.java @@ -0,0 +1,140 @@ +/* + * Copyright 2002-2007 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; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * Abstract implementation of the {@link PropertyAccessor} interface. + * Provides base implementations of all convenience methods, with the + * implementation of actual property access left to subclasses. + * + * @author Juergen Hoeller + * @since 2.0 + * @see #getPropertyValue + * @see #setPropertyValue + */ +public abstract class AbstractPropertyAccessor extends PropertyEditorRegistrySupport + implements ConfigurablePropertyAccessor { + + private boolean extractOldValueForEditor = false; + + + public void setExtractOldValueForEditor(boolean extractOldValueForEditor) { + this.extractOldValueForEditor = extractOldValueForEditor; + } + + public boolean isExtractOldValueForEditor() { + return this.extractOldValueForEditor; + } + + + public void setPropertyValue(PropertyValue pv) throws BeansException { + setPropertyValue(pv.getName(), pv.getValue()); + } + + public void setPropertyValues(Map map) throws BeansException { + setPropertyValues(new MutablePropertyValues(map)); + } + + public void setPropertyValues(PropertyValues pvs) throws BeansException { + setPropertyValues(pvs, false, false); + } + + public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown) throws BeansException { + setPropertyValues(pvs, ignoreUnknown, false); + } + + public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid) + throws BeansException { + + List propertyAccessExceptions = null; + List propertyValues = (pvs instanceof MutablePropertyValues ? + ((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues())); + for (Iterator it = propertyValues.iterator(); it.hasNext();) { + PropertyValue pv = (PropertyValue) it.next(); + try { + // This method may throw any BeansException, which won't be caught + // here, if there is a critical failure such as no matching field. + // We can attempt to deal only with less serious exceptions. + setPropertyValue(pv); + } + catch (NotWritablePropertyException ex) { + if (!ignoreUnknown) { + throw ex; + } + // Otherwise, just ignore it and continue... + } + catch (NullValueInNestedPathException ex) { + if (!ignoreInvalid) { + throw ex; + } + // Otherwise, just ignore it and continue... + } + catch (PropertyAccessException ex) { + if (propertyAccessExceptions == null) { + propertyAccessExceptions = new LinkedList(); + } + propertyAccessExceptions.add(ex); + } + } + + // If we encountered individual exceptions, throw the composite exception. + if (propertyAccessExceptions != null) { + PropertyAccessException[] paeArray = (PropertyAccessException[]) + propertyAccessExceptions.toArray(new PropertyAccessException[propertyAccessExceptions.size()]); + throw new PropertyBatchUpdateException(paeArray); + } + } + + public Object convertIfNecessary(Object value, Class requiredType) throws TypeMismatchException { + return convertIfNecessary(value, requiredType, null); + } + + + // Redefined with public visibility. + public Class getPropertyType(String propertyPath) { + return null; + } + + /** + * Actually get the value of a property. + * @param propertyName name of the property to get the value of + * @return the value of the property + * @throws InvalidPropertyException if there is no such property or + * if the property isn't readable + * @throws PropertyAccessException if the property was valid but the + * accessor method failed + */ + public abstract Object getPropertyValue(String propertyName) throws BeansException; + + /** + * Actually set a property value. + * @param propertyName name of the property to set value of + * @param value the new value + * @throws InvalidPropertyException if there is no such property or + * if the property isn't writable + * @throws PropertyAccessException if the property was valid but the + * accessor method failed or a type mismatch occured + */ + public abstract void setPropertyValue(String propertyName, Object value) throws BeansException; + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/BeanInstantiationException.java b/org.springframework.beans/src/main/java/org/springframework/beans/BeanInstantiationException.java new file mode 100644 index 0000000000..46e45ba1ef --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/BeanInstantiationException.java @@ -0,0 +1,58 @@ +/* + * Copyright 2002-2006 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; + +/** + * Exception thrown when instantiation of a bean failed. + * Carries the offending bean class. + * + * @author Juergen Hoeller + * @since 1.2.8 + */ +public class BeanInstantiationException extends FatalBeanException { + + private Class beanClass; + + + /** + * Create a new BeanInstantiationException. + * @param beanClass the offending bean class + * @param msg the detail message + */ + public BeanInstantiationException(Class beanClass, String msg) { + this(beanClass, msg, null); + } + + /** + * Create a new BeanInstantiationException. + * @param beanClass the offending bean class + * @param msg the detail message + * @param cause the root cause + */ + public BeanInstantiationException(Class beanClass, String msg, Throwable cause) { + super("Could not instantiate bean class [" + beanClass.getName() + "]: " + msg, cause); + this.beanClass = beanClass; + } + + /** + * Return the offending bean class. + */ + public Class getBeanClass() { + return beanClass; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/BeanMetadataAttribute.java b/org.springframework.beans/src/main/java/org/springframework/beans/BeanMetadataAttribute.java new file mode 100644 index 0000000000..863335a849 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/BeanMetadataAttribute.java @@ -0,0 +1,98 @@ +/* + * Copyright 2002-2007 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; + +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * Holder for a key-value style attribute that is part of a bean definition. + * Keeps track of the definition source in addition to the key-value pair. + * + * @author Juergen Hoeller + * @since 2.5 + */ +public class BeanMetadataAttribute implements BeanMetadataElement { + + private final String name; + + private final Object value; + + private Object source; + + + /** + * Create a new AttributeValue instance. + * @param name the name of the attribute (never null) + * @param value the value of the attribute (possibly before type conversion) + */ + public BeanMetadataAttribute(String name, Object value) { + Assert.notNull(name, "Name must not be null"); + this.name = name; + this.value = value; + } + + + /** + * Return the name of the attribute. + */ + public String getName() { + return this.name; + } + + /** + * Return the value of the attribute. + */ + public Object getValue() { + return this.value; + } + + /** + * Set the configuration source Object for this metadata element. + *

The exact type of the object will depend on the configuration mechanism used. + */ + public void setSource(Object source) { + this.source = source; + } + + public Object getSource() { + return this.source; + } + + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof BeanMetadataAttribute)) { + return false; + } + BeanMetadataAttribute otherMa = (BeanMetadataAttribute) other; + return (this.name.equals(otherMa.name) && + ObjectUtils.nullSafeEquals(this.value, otherMa.value) && + ObjectUtils.nullSafeEquals(this.source, otherMa.source)); + } + + public int hashCode() { + return this.name.hashCode() * 29 + ObjectUtils.nullSafeHashCode(this.value); + } + + public String toString() { + return "metadata attribute '" + this.name + "'"; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/BeanMetadataAttributeAccessor.java b/org.springframework.beans/src/main/java/org/springframework/beans/BeanMetadataAttributeAccessor.java new file mode 100644 index 0000000000..f6f95d6523 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/BeanMetadataAttributeAccessor.java @@ -0,0 +1,79 @@ +/* + * Copyright 2002-2007 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; + +import org.springframework.core.AttributeAccessorSupport; + +/** + * Extension of {@link org.springframework.core.AttributeAccessorSupport}, + * holding attributes as {@link BeanMetadataAttribute} objects in order + * to keep track of the definition source. + * + * @author Juergen Hoeller + * @since 2.5 + */ +public class BeanMetadataAttributeAccessor extends AttributeAccessorSupport implements BeanMetadataElement { + + private Object source; + + + /** + * Set the configuration source Object for this metadata element. + *

The exact type of the object will depend on the configuration mechanism used. + */ + public void setSource(Object source) { + this.source = source; + } + + public Object getSource() { + return this.source; + } + + + /** + * Add the given BeanMetadataAttribute to this accessor's set of attributes. + * @param attribute the BeanMetadataAttribute object to register + */ + public void addMetadataAttribute(BeanMetadataAttribute attribute) { + super.setAttribute(attribute.getName(), attribute); + } + + /** + * Look up the given BeanMetadataAttribute in this accessor's set of attributes. + * @param name the name of the attribute + * @return the corresponding BeanMetadataAttribute object, + * or null if no such attribute defined + */ + public BeanMetadataAttribute getMetadataAttribute(String name) { + return (BeanMetadataAttribute) super.getAttribute(name); + } + + public void setAttribute(String name, Object value) { + super.setAttribute(name, new BeanMetadataAttribute(name, value)); + } + + public Object getAttribute(String name) { + BeanMetadataAttribute attribute = (BeanMetadataAttribute) super.getAttribute(name); + return (attribute != null ? attribute.getValue() : null); + } + + public Object removeAttribute(String name) { + BeanMetadataAttribute attribute = (BeanMetadataAttribute) super.removeAttribute(name); + return (attribute != null ? attribute.getValue() : null); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/BeanMetadataElement.java b/org.springframework.beans/src/main/java/org/springframework/beans/BeanMetadataElement.java new file mode 100644 index 0000000000..5d39c7f4fd --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/BeanMetadataElement.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2006 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; + +/** + * Interface to be implemented by bean metadata elements + * that carry a configuration source object. + * + * @author Juergen Hoeller + * @since 2.0 + */ +public interface BeanMetadataElement { + + /** + * Return the configuration source Object for this metadata element + * (may be null). + */ + Object getSource(); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/BeanUtils.java b/org.springframework.beans/src/main/java/org/springframework/beans/BeanUtils.java new file mode 100644 index 0000000000..6519c7d5f6 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/BeanUtils.java @@ -0,0 +1,598 @@ +/* + * Copyright 2002-2008 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; + +import java.beans.PropertyDescriptor; +import java.beans.PropertyEditor; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.URI; +import java.net.URL; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.WeakHashMap; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.core.MethodParameter; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; + +/** + * Static convenience methods for JavaBeans: for instantiating beans, + * checking bean property types, copying bean properties, etc. + * + *

Mainly for use within the framework, but to some degree also + * useful for application classes. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @author Rob Harrop + */ +public abstract class BeanUtils { + + private static final Log logger = LogFactory.getLog(BeanUtils.class); + + private static final Map unknownEditorTypes = Collections.synchronizedMap(new WeakHashMap()); + + + /** + * Convenience method to instantiate a class using its no-arg constructor. + * As this method doesn't try to load classes by name, it should avoid + * class-loading issues. + *

Note that this method tries to set the constructor accessible + * if given a non-accessible (that is, non-public) constructor. + * @param clazz class to instantiate + * @return the new instance + * @throws BeanInstantiationException if the bean cannot be instantiated + */ + public static Object instantiateClass(Class clazz) throws BeanInstantiationException { + Assert.notNull(clazz, "Class must not be null"); + if (clazz.isInterface()) { + throw new BeanInstantiationException(clazz, "Specified class is an interface"); + } + try { + return instantiateClass(clazz.getDeclaredConstructor((Class[]) null), null); + } + catch (NoSuchMethodException ex) { + throw new BeanInstantiationException(clazz, "No default constructor found", ex); + } + } + + /** + * Convenience method to instantiate a class using the given constructor. + * As this method doesn't try to load classes by name, it should avoid + * class-loading issues. + *

Note that this method tries to set the constructor accessible + * if given a non-accessible (that is, non-public) constructor. + * @param ctor the constructor to instantiate + * @param args the constructor arguments to apply + * @return the new instance + * @throws BeanInstantiationException if the bean cannot be instantiated + */ + public static Object instantiateClass(Constructor ctor, Object[] args) throws BeanInstantiationException { + Assert.notNull(ctor, "Constructor must not be null"); + try { + ReflectionUtils.makeAccessible(ctor); + return ctor.newInstance(args); + } + catch (InstantiationException ex) { + throw new BeanInstantiationException(ctor.getDeclaringClass(), + "Is it an abstract class?", ex); + } + catch (IllegalAccessException ex) { + throw new BeanInstantiationException(ctor.getDeclaringClass(), + "Has the class definition changed? Is the constructor accessible?", ex); + } + catch (IllegalArgumentException ex) { + throw new BeanInstantiationException(ctor.getDeclaringClass(), + "Illegal arguments for constructor", ex); + } + catch (InvocationTargetException ex) { + throw new BeanInstantiationException(ctor.getDeclaringClass(), + "Constructor threw exception", ex.getTargetException()); + } + } + + /** + * Find a method with the given method name and the given parameter types, + * declared on the given class or one of its superclasses. Prefers public methods, + * but will return a protected, package access, or private method too. + *

Checks Class.getMethod first, falling back to + * findDeclaredMethod. This allows to find public methods + * without issues even in environments with restricted Java security settings. + * @param clazz the class to check + * @param methodName the name of the method to find + * @param paramTypes the parameter types of the method to find + * @return the Method object, or null if not found + * @see java.lang.Class#getMethod + * @see #findDeclaredMethod + */ + public static Method findMethod(Class clazz, String methodName, Class[] paramTypes) { + try { + return clazz.getMethod(methodName, paramTypes); + } + catch (NoSuchMethodException ex) { + return findDeclaredMethod(clazz, methodName, paramTypes); + } + } + + /** + * Find a method with the given method name and the given parameter types, + * declared on the given class or one of its superclasses. Will return a public, + * protected, package access, or private method. + *

Checks Class.getDeclaredMethod, cascading upwards to all superclasses. + * @param clazz the class to check + * @param methodName the name of the method to find + * @param paramTypes the parameter types of the method to find + * @return the Method object, or null if not found + * @see java.lang.Class#getDeclaredMethod + */ + public static Method findDeclaredMethod(Class clazz, String methodName, Class[] paramTypes) { + try { + return clazz.getDeclaredMethod(methodName, paramTypes); + } + catch (NoSuchMethodException ex) { + if (clazz.getSuperclass() != null) { + return findDeclaredMethod(clazz.getSuperclass(), methodName, paramTypes); + } + return null; + } + } + + /** + * Find a method with the given method name and minimal parameters (best case: none), + * declared on the given class or one of its superclasses. Prefers public methods, + * but will return a protected, package access, or private method too. + *

Checks Class.getMethods first, falling back to + * findDeclaredMethodWithMinimalParameters. This allows to find public + * methods without issues even in environments with restricted Java security settings. + * @param clazz the class to check + * @param methodName the name of the method to find + * @return the Method object, or null if not found + * @throws IllegalArgumentException if methods of the given name were found but + * could not be resolved to a unique method with minimal parameters + * @see java.lang.Class#getMethods + * @see #findDeclaredMethodWithMinimalParameters + */ + public static Method findMethodWithMinimalParameters(Class clazz, String methodName) + throws IllegalArgumentException { + + Method targetMethod = doFindMethodWithMinimalParameters(clazz.getDeclaredMethods(), methodName); + if (targetMethod == null) { + return findDeclaredMethodWithMinimalParameters(clazz, methodName); + } + return targetMethod; + } + + /** + * Find a method with the given method name and minimal parameters (best case: none), + * declared on the given class or one of its superclasses. Will return a public, + * protected, package access, or private method. + *

Checks Class.getDeclaredMethods, cascading upwards to all superclasses. + * @param clazz the class to check + * @param methodName the name of the method to find + * @return the Method object, or null if not found + * @throws IllegalArgumentException if methods of the given name were found but + * could not be resolved to a unique method with minimal parameters + * @see java.lang.Class#getDeclaredMethods + */ + public static Method findDeclaredMethodWithMinimalParameters(Class clazz, String methodName) + throws IllegalArgumentException { + + Method targetMethod = doFindMethodWithMinimalParameters(clazz.getDeclaredMethods(), methodName); + if (targetMethod == null && clazz.getSuperclass() != null) { + return findDeclaredMethodWithMinimalParameters(clazz.getSuperclass(), methodName); + } + return targetMethod; + } + + /** + * Find a method with the given method name and minimal parameters (best case: none) + * in the given list of methods. + * @param methods the methods to check + * @param methodName the name of the method to find + * @return the Method object, or null if not found + * @throws IllegalArgumentException if methods of the given name were found but + * could not be resolved to a unique method with minimal parameters + */ + private static Method doFindMethodWithMinimalParameters(Method[] methods, String methodName) + throws IllegalArgumentException { + + Method targetMethod = null; + int numMethodsFoundWithCurrentMinimumArgs = 0; + for (int i = 0; i < methods.length; i++) { + if (methods[i].getName().equals(methodName)) { + int numParams = methods[i].getParameterTypes().length; + if (targetMethod == null || + numParams < targetMethod.getParameterTypes().length) { + targetMethod = methods[i]; + numMethodsFoundWithCurrentMinimumArgs = 1; + } + else { + if (targetMethod.getParameterTypes().length == numParams) { + // Additional candidate with same length. + numMethodsFoundWithCurrentMinimumArgs++; + } + } + } + } + if (numMethodsFoundWithCurrentMinimumArgs > 1) { + throw new IllegalArgumentException("Cannot resolve method '" + methodName + + "' to a unique method. Attempted to resolve to overloaded method with " + + "the least number of parameters, but there were " + + numMethodsFoundWithCurrentMinimumArgs + " candidates."); + } + return targetMethod; + } + + /** + * Parse a method signature in the form methodName[([arg_list])], + * where arg_list is an optional, comma-separated list of fully-qualified + * type names, and attempts to resolve that signature against the supplied Class. + *

When not supplying an argument list (methodName) the method whose name + * matches and has the least number of parameters will be returned. When supplying an + * argument type list, only the method whose name and argument types match will be returned. + *

Note then that methodName and methodName() are not + * resolved in the same way. The signature methodName means the method called + * methodName with the least number of arguments, whereas methodName() + * means the method called methodName with exactly 0 arguments. + *

If no method can be found, then null is returned. + * @param signature the method signature as String representation + * @param clazz the class to resolve the method signature against + * @return the resolved Method + * @see #findMethod + * @see #findMethodWithMinimalParameters + */ + public static Method resolveSignature(String signature, Class clazz) { + Assert.hasText(signature, "'signature' must not be empty"); + Assert.notNull(clazz, "Class must not be null"); + + int firstParen = signature.indexOf("("); + int lastParen = signature.indexOf(")"); + + if (firstParen > -1 && lastParen == -1) { + throw new IllegalArgumentException("Invalid method signature '" + signature + + "': expected closing ')' for args list"); + } + else if (lastParen > -1 && firstParen == -1) { + throw new IllegalArgumentException("Invalid method signature '" + signature + + "': expected opening '(' for args list"); + } + else if (firstParen == -1 && lastParen == -1) { + return findMethodWithMinimalParameters(clazz, signature); + } + else { + String methodName = signature.substring(0, firstParen); + String[] parameterTypeNames = + StringUtils.commaDelimitedListToStringArray(signature.substring(firstParen + 1, lastParen)); + Class[] parameterTypes = new Class[parameterTypeNames.length]; + for (int i = 0; i < parameterTypeNames.length; i++) { + String parameterTypeName = parameterTypeNames[i].trim(); + try { + parameterTypes[i] = ClassUtils.forName(parameterTypeName, clazz.getClassLoader()); + } + catch (Throwable ex) { + throw new IllegalArgumentException("Invalid method signature: unable to resolve type [" + + parameterTypeName + "] for argument " + i + ". Root cause: " + ex); + } + } + return findMethod(clazz, methodName, parameterTypes); + } + } + + + /** + * Retrieve the JavaBeans PropertyDescriptors of a given class. + * @param clazz the Class to retrieve the PropertyDescriptors for + * @return an array of PropertyDescriptors for the given class + * @throws BeansException if PropertyDescriptor look fails + */ + public static PropertyDescriptor[] getPropertyDescriptors(Class clazz) throws BeansException { + CachedIntrospectionResults cr = CachedIntrospectionResults.forClass(clazz); + return cr.getBeanInfo().getPropertyDescriptors(); + } + + /** + * Retrieve the JavaBeans PropertyDescriptors for the given property. + * @param clazz the Class to retrieve the PropertyDescriptor for + * @param propertyName the name of the property + * @return the corresponding PropertyDescriptor, or null if none + * @throws BeansException if PropertyDescriptor lookup fails + */ + public static PropertyDescriptor getPropertyDescriptor(Class clazz, String propertyName) + throws BeansException { + + CachedIntrospectionResults cr = CachedIntrospectionResults.forClass(clazz); + return cr.getPropertyDescriptor(propertyName); + } + + /** + * Find a JavaBeans PropertyDescriptor for the given method, + * with the method either being the read method or the write method for + * that bean property. + * @param method the method to find a corresponding PropertyDescriptor for + * @return the corresponding PropertyDescriptor, or null if none + * @throws BeansException if PropertyDescriptor lookup fails + */ + public static PropertyDescriptor findPropertyForMethod(Method method) throws BeansException { + Assert.notNull(method, "Method must not be null"); + PropertyDescriptor[] pds = getPropertyDescriptors(method.getDeclaringClass()); + for (int i = 0; i < pds.length; i++) { + PropertyDescriptor pd = pds[i]; + if (method.equals(pd.getReadMethod()) || method.equals(pd.getWriteMethod())) { + return pd; + } + } + return null; + } + + /** + * Find a JavaBeans PropertyEditor following the 'Editor' suffix convention + * (e.g. "mypackage.MyDomainClass" -> "mypackage.MyDomainClassEditor"). + *

Compatible to the standard JavaBeans convention as implemented by + * {@link java.beans.PropertyEditorManager} but isolated from the latter's + * registered default editors for primitive types. + * @param targetType the type to find an editor for + * @return the corresponding editor, or null if none found + */ + public static PropertyEditor findEditorByConvention(Class targetType) { + if (targetType == null || unknownEditorTypes.containsKey(targetType)) { + return null; + } + ClassLoader cl = targetType.getClassLoader(); + if (cl == null) { + cl = ClassLoader.getSystemClassLoader(); + if (cl == null) { + return null; + } + } + String editorName = targetType.getName() + "Editor"; + try { + Class editorClass = cl.loadClass(editorName); + if (!PropertyEditor.class.isAssignableFrom(editorClass)) { + logger.warn("Editor class [" + editorName + + "] does not implement [java.beans.PropertyEditor] interface"); + unknownEditorTypes.put(targetType, Boolean.TRUE); + return null; + } + return (PropertyEditor) instantiateClass(editorClass); + } + catch (ClassNotFoundException ex) { + if (logger.isDebugEnabled()) { + logger.debug("No property editor [" + editorName + "] found for type " + + targetType.getName() + " according to 'Editor' suffix convention"); + } + unknownEditorTypes.put(targetType, Boolean.TRUE); + return null; + } + } + + /** + * Determine the bean property type for the given property from the + * given classes/interfaces, if possible. + * @param propertyName the name of the bean property + * @param beanClasses the classes to check against + * @return the property type, or Object.class as fallback + */ + public static Class findPropertyType(String propertyName, Class[] beanClasses) { + if (beanClasses != null) { + for (int i = 0; i < beanClasses.length; i++) { + PropertyDescriptor pd = getPropertyDescriptor(beanClasses[i], propertyName); + if (pd != null) { + return pd.getPropertyType(); + } + } + } + return Object.class; + } + + /** + * Obtain a new MethodParameter object for the write method of the + * specified property. + * @param pd the PropertyDescriptor for the property + * @return a corresponding MethodParameter object + */ + public static MethodParameter getWriteMethodParameter(PropertyDescriptor pd) { + if (pd instanceof GenericTypeAwarePropertyDescriptor) { + return new MethodParameter( + ((GenericTypeAwarePropertyDescriptor) pd).getWriteMethodParameter()); + } + else { + return new MethodParameter(pd.getWriteMethod(), 0); + } + } + + /** + * Check if the given type represents a "simple" property: + * a primitive, a String or other CharSequence, a Number, a Date, + * a URI, a URL, a Locale, a Class, or a corresponding array. + *

Used to determine properties to check for a "simple" dependency-check. + * @param clazz the type to check + * @return whether the given type represents a "simple" property + * @see org.springframework.beans.factory.support.RootBeanDefinition#DEPENDENCY_CHECK_SIMPLE + * @see org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#checkDependencies + */ + public static boolean isSimpleProperty(Class clazz) { + Assert.notNull(clazz, "Class must not be null"); + return isSimpleValueType(clazz) || (clazz.isArray() && isSimpleValueType(clazz.getComponentType())); + } + + /** + * Check if the given type represents a "simple" value type: + * a primitive, a String or other CharSequence, a Number, a Date, + * a URI, a URL, a Locale or a Class. + * @param clazz the type to check + * @return whether the given type represents a "simple" value type + */ + public static boolean isSimpleValueType(Class clazz) { + return ClassUtils.isPrimitiveOrWrapper(clazz) || CharSequence.class.isAssignableFrom(clazz) || + Number.class.isAssignableFrom(clazz) || Date.class.isAssignableFrom(clazz) || + clazz.equals(URI.class) || clazz.equals(URL.class) || + clazz.equals(Locale.class) || clazz.equals(Class.class); + } + + /** + * Determine if the given target type is assignable from the given value + * type, assuming setting by reflection. Considers primitive wrapper + * classes as assignable to the corresponding primitive types. + * @param targetType the target type + * @param valueType the value type that should be assigned to the target type + * @return if the target type is assignable from the value type + * @deprecated as of Spring 2.0, in favor of ClassUtils.isAssignable + * @see org.springframework.util.ClassUtils#isAssignable(Class, Class) + */ + public static boolean isAssignable(Class targetType, Class valueType) { + return ClassUtils.isAssignable(targetType, valueType); + } + + /** + * Determine if the given type is assignable from the given value, + * assuming setting by reflection. Considers primitive wrapper classes + * as assignable to the corresponding primitive types. + * @param type the target type + * @param value the value that should be assigned to the type + * @return if the type is assignable from the value + * @deprecated as of Spring 2.0, in favor of ClassUtils.isAssignableValue + * @see org.springframework.util.ClassUtils#isAssignableValue(Class, Object) + */ + public static boolean isAssignable(Class type, Object value) { + return ClassUtils.isAssignableValue(type, value); + } + + + /** + * Copy the property values of the given source bean into the target bean. + *

Note: The source and target classes do not have to match or even be derived + * from each other, as long as the properties match. Any bean properties that the + * source bean exposes but the target bean does not will silently be ignored. + *

This is just a convenience method. For more complex transfer needs, + * consider using a full BeanWrapper. + * @param source the source bean + * @param target the target bean + * @throws BeansException if the copying failed + * @see BeanWrapper + */ + public static void copyProperties(Object source, Object target) throws BeansException { + copyProperties(source, target, null, null); + } + + /** + * Copy the property values of the given source bean into the given target bean, + * only setting properties defined in the given "editable" class (or interface). + *

Note: The source and target classes do not have to match or even be derived + * from each other, as long as the properties match. Any bean properties that the + * source bean exposes but the target bean does not will silently be ignored. + *

This is just a convenience method. For more complex transfer needs, + * consider using a full BeanWrapper. + * @param source the source bean + * @param target the target bean + * @param editable the class (or interface) to restrict property setting to + * @throws BeansException if the copying failed + * @see BeanWrapper + */ + public static void copyProperties(Object source, Object target, Class editable) + throws BeansException { + + copyProperties(source, target, editable, null); + } + + /** + * Copy the property values of the given source bean into the given target bean, + * ignoring the given "ignoreProperties". + *

Note: The source and target classes do not have to match or even be derived + * from each other, as long as the properties match. Any bean properties that the + * source bean exposes but the target bean does not will silently be ignored. + *

This is just a convenience method. For more complex transfer needs, + * consider using a full BeanWrapper. + * @param source the source bean + * @param target the target bean + * @param ignoreProperties array of property names to ignore + * @throws BeansException if the copying failed + * @see BeanWrapper + */ + public static void copyProperties(Object source, Object target, String[] ignoreProperties) + throws BeansException { + + copyProperties(source, target, null, ignoreProperties); + } + + /** + * Copy the property values of the given source bean into the given target bean. + *

Note: The source and target classes do not have to match or even be derived + * from each other, as long as the properties match. Any bean properties that the + * source bean exposes but the target bean does not will silently be ignored. + * @param source the source bean + * @param target the target bean + * @param editable the class (or interface) to restrict property setting to + * @param ignoreProperties array of property names to ignore + * @throws BeansException if the copying failed + * @see BeanWrapper + */ + private static void copyProperties(Object source, Object target, Class editable, String[] ignoreProperties) + throws BeansException { + + Assert.notNull(source, "Source must not be null"); + Assert.notNull(target, "Target must not be null"); + + Class actualEditable = target.getClass(); + if (editable != null) { + if (!editable.isInstance(target)) { + throw new IllegalArgumentException("Target class [" + target.getClass().getName() + + "] not assignable to Editable class [" + editable.getName() + "]"); + } + actualEditable = editable; + } + PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable); + List ignoreList = (ignoreProperties != null) ? Arrays.asList(ignoreProperties) : null; + + for (int i = 0; i < targetPds.length; i++) { + PropertyDescriptor targetPd = targetPds[i]; + if (targetPd.getWriteMethod() != null && + (ignoreProperties == null || (!ignoreList.contains(targetPd.getName())))) { + PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName()); + if (sourcePd != null && sourcePd.getReadMethod() != null) { + try { + Method readMethod = sourcePd.getReadMethod(); + if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) { + readMethod.setAccessible(true); + } + Object value = readMethod.invoke(source, new Object[0]); + Method writeMethod = targetPd.getWriteMethod(); + if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) { + writeMethod.setAccessible(true); + } + writeMethod.invoke(target, new Object[] {value}); + } + catch (Throwable ex) { + throw new FatalBeanException("Could not copy properties from source to target", ex); + } + } + } + } + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapper.java b/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapper.java new file mode 100644 index 0000000000..fcc5155555 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapper.java @@ -0,0 +1,93 @@ +/* + * Copyright 2002-2008 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; + +import java.beans.PropertyDescriptor; + +/** + * The central interface of Spring's low-level JavaBeans infrastructure. + * + *

Typically not used directly but rather implicitly via a + * {@link org.springframework.beans.factory.BeanFactory} or a + * {@link org.springframework.validation.DataBinder}. + * + *

Provides operations to analyze and manipulate standard JavaBeans: + * the ability to get and set property values (individually or in bulk), + * get property descriptors, and query the readability/writability of properties. + * + *

This interface supports nested properties enabling the setting + * of properties on subproperties to an unlimited depth. + * A BeanWrapper instance can be used repeatedly, with its + * {@link #setWrappedInstance(Object) target object} (the wrapped JavaBean + * instance) changing as required. + * + *

A BeanWrapper's default for the "extractOldValueForEditor" setting + * is "false", to avoid side effects caused by getter method invocations. + * Turn this to "true" to expose present property values to custom editors. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 13 April 2001 + * @see #setExtractOldValueForEditor + * @see PropertyAccessor + * @see PropertyEditorRegistry + * @see PropertyAccessorFactory#forBeanPropertyAccess + * @see org.springframework.beans.factory.BeanFactory + * @see org.springframework.validation.BeanPropertyBindingResult + * @see org.springframework.validation.DataBinder#initBeanPropertyAccess() + */ +public interface BeanWrapper extends ConfigurablePropertyAccessor { + + /** + * Change the wrapped JavaBean object. + * @param obj the bean instance to wrap + * @deprecated as of Spring 2.5, + * in favor of recreating a BeanWrapper per target instance + */ + void setWrappedInstance(Object obj); + + /** + * Return the bean instance wrapped by this object, if any. + * @return the bean instance, or null if none set + */ + Object getWrappedInstance(); + + /** + * Return the type of the wrapped JavaBean object. + * @return the type of the wrapped bean instance, + * or null if no wrapped object has been set + */ + Class getWrappedClass(); + + /** + * Obtain the PropertyDescriptors for the wrapped object + * (as determined by standard JavaBeans introspection). + * @return the PropertyDescriptors for the wrapped object + */ + PropertyDescriptor[] getPropertyDescriptors(); + + /** + * Obtain the property descriptor for a specific property + * of the wrapped object. + * @param propertyName the property to obtain the descriptor for + * (may be a nested path, but no indexed/mapped property) + * @return the property descriptor for the specified property + * @throws InvalidPropertyException if there is no such property + */ + PropertyDescriptor getPropertyDescriptor(String propertyName) throws BeansException; + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java b/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java new file mode 100644 index 0000000000..214d145ae4 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java @@ -0,0 +1,891 @@ +/* + * Copyright 2002-2008 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; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.core.GenericCollectionTypeResolver; +import org.springframework.core.JdkVersion; +import org.springframework.core.MethodParameter; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * Default {@link BeanWrapper} implementation that should be sufficient + * for all typical use cases. Caches introspection results for efficiency. + * + *

Note: Auto-registers default property editors from the + * org.springframework.beans.propertyeditors package, which apply + * in addition to the JDK's standard PropertyEditors. Applications can call + * the {@link #registerCustomEditor(Class, java.beans.PropertyEditor)} method + * to register an editor for a particular instance (i.e. they are not shared + * across the application). See the base class + * {@link PropertyEditorRegistrySupport} for details. + * + *

BeanWrapperImpl will convert collection and array values + * to the corresponding target collections or arrays, if necessary. Custom + * property editors that deal with collections or arrays can either be + * written via PropertyEditor's setValue, or against a + * comma-delimited String via setAsText, as String arrays are + * converted in such a format if the array itself is not assignable. + * + *

NOTE: As of Spring 2.5, this is - for almost all purposes - an + * internal class. It is just public in order to allow for access from + * other framework packages. For standard application access purposes, use the + * {@link PropertyAccessorFactory#forBeanPropertyAccess} factory method instead. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @author Rob Harrop + * @since 15 April 2001 + * @see #registerCustomEditor + * @see #setPropertyValues + * @see #setPropertyValue + * @see #getPropertyValue + * @see #getPropertyType + * @see BeanWrapper + * @see PropertyEditorRegistrySupport + */ +public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWrapper { + + /** + * We'll create a lot of these objects, so we don't want a new logger every time. + */ + private static final Log logger = LogFactory.getLog(BeanWrapperImpl.class); + + + /** The wrapped object */ + private Object object; + + private String nestedPath = ""; + + private Object rootObject; + + private TypeConverterDelegate typeConverterDelegate; + + /** + * Cached introspections results for this object, to prevent encountering + * the cost of JavaBeans introspection every time. + */ + private CachedIntrospectionResults cachedIntrospectionResults; + + /** + * Map with cached nested BeanWrappers: nested path -> BeanWrapper instance. + */ + private Map nestedBeanWrappers; + + + /** + * Create new empty BeanWrapperImpl. Wrapped instance needs to be set afterwards. + * Registers default editors. + * @see #setWrappedInstance + */ + public BeanWrapperImpl() { + this(true); + } + + /** + * Create new empty BeanWrapperImpl. Wrapped instance needs to be set afterwards. + * @param registerDefaultEditors whether to register default editors + * (can be suppressed if the BeanWrapper won't need any type conversion) + * @see #setWrappedInstance + */ + public BeanWrapperImpl(boolean registerDefaultEditors) { + if (registerDefaultEditors) { + registerDefaultEditors(); + } + this.typeConverterDelegate = new TypeConverterDelegate(this); + } + + /** + * Create new BeanWrapperImpl for the given object. + * @param object object wrapped by this BeanWrapper + */ + public BeanWrapperImpl(Object object) { + registerDefaultEditors(); + setWrappedInstance(object); + } + + /** + * Create new BeanWrapperImpl, wrapping a new instance of the specified class. + * @param clazz class to instantiate and wrap + */ + public BeanWrapperImpl(Class clazz) { + registerDefaultEditors(); + setWrappedInstance(BeanUtils.instantiateClass(clazz)); + } + + /** + * Create new BeanWrapperImpl for the given object, + * registering a nested path that the object is in. + * @param object object wrapped by this BeanWrapper + * @param nestedPath the nested path of the object + * @param rootObject the root object at the top of the path + */ + public BeanWrapperImpl(Object object, String nestedPath, Object rootObject) { + registerDefaultEditors(); + setWrappedInstance(object, nestedPath, rootObject); + } + + /** + * Create new BeanWrapperImpl for the given object, + * registering a nested path that the object is in. + * @param object object wrapped by this BeanWrapper + * @param nestedPath the nested path of the object + * @param superBw the containing BeanWrapper (must not be null) + */ + private BeanWrapperImpl(Object object, String nestedPath, BeanWrapperImpl superBw) { + setWrappedInstance(object, nestedPath, superBw.getWrappedInstance()); + setExtractOldValueForEditor(superBw.isExtractOldValueForEditor()); + } + + + //--------------------------------------------------------------------- + // Implementation of BeanWrapper interface + //--------------------------------------------------------------------- + + /** + * Switch the target object, replacing the cached introspection results only + * if the class of the new object is different to that of the replaced object. + * @param object the new target object + */ + public void setWrappedInstance(Object object) { + setWrappedInstance(object, "", null); + } + + /** + * Switch the target object, replacing the cached introspection results only + * if the class of the new object is different to that of the replaced object. + * @param object the new target object + * @param nestedPath the nested path of the object + * @param rootObject the root object at the top of the path + */ + public void setWrappedInstance(Object object, String nestedPath, Object rootObject) { + Assert.notNull(object, "Bean object must not be null"); + this.object = object; + this.nestedPath = (nestedPath != null ? nestedPath : ""); + this.rootObject = (!"".equals(this.nestedPath) ? rootObject : object); + this.nestedBeanWrappers = null; + this.typeConverterDelegate = new TypeConverterDelegate(this, object); + setIntrospectionClass(object.getClass()); + } + + public final Object getWrappedInstance() { + return this.object; + } + + public final Class getWrappedClass() { + return (this.object != null ? this.object.getClass() : null); + } + + /** + * Return the nested path of the object wrapped by this BeanWrapper. + */ + public final String getNestedPath() { + return this.nestedPath; + } + + /** + * Return the root object at the top of the path of this BeanWrapper. + * @see #getNestedPath + */ + public final Object getRootInstance() { + return this.rootObject; + } + + /** + * Return the class of the root object at the top of the path of this BeanWrapper. + * @see #getNestedPath + */ + public final Class getRootClass() { + return (this.rootObject != null ? this.rootObject.getClass() : null); + } + + /** + * Set the class to introspect. + * Needs to be called when the target object changes. + * @param clazz the class to introspect + */ + protected void setIntrospectionClass(Class clazz) { + if (this.cachedIntrospectionResults != null && + !clazz.equals(this.cachedIntrospectionResults.getBeanClass())) { + this.cachedIntrospectionResults = null; + } + } + + /** + * Obtain a lazily initializted CachedIntrospectionResults instance + * for the wrapped object. + */ + private CachedIntrospectionResults getCachedIntrospectionResults() { + Assert.state(this.object != null, "BeanWrapper does not hold a bean instance"); + if (this.cachedIntrospectionResults == null) { + this.cachedIntrospectionResults = CachedIntrospectionResults.forClass(getWrappedClass()); + } + return this.cachedIntrospectionResults; + } + + + public PropertyDescriptor[] getPropertyDescriptors() { + return getCachedIntrospectionResults().getBeanInfo().getPropertyDescriptors(); + } + + public PropertyDescriptor getPropertyDescriptor(String propertyName) throws BeansException { + PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName); + if (pd == null) { + throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, + "No property '" + propertyName + "' found"); + } + return pd; + } + + /** + * Internal version of {@link #getPropertyDescriptor}: + * Returns null if not found rather than throwing an exception. + * @param propertyName the property to obtain the descriptor for + * @return the property descriptor for the specified property, + * or null if not found + * @throws BeansException in case of introspection failure + */ + protected PropertyDescriptor getPropertyDescriptorInternal(String propertyName) throws BeansException { + Assert.notNull(propertyName, "Property name must not be null"); + BeanWrapperImpl nestedBw = getBeanWrapperForPropertyPath(propertyName); + return nestedBw.getCachedIntrospectionResults().getPropertyDescriptor(getFinalPath(nestedBw, propertyName)); + } + + public Class getPropertyType(String propertyName) throws BeansException { + try { + PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName); + if (pd != null) { + return pd.getPropertyType(); + } + else { + // Maybe an indexed/mapped property... + Object value = getPropertyValue(propertyName); + if (value != null) { + return value.getClass(); + } + // Check to see if there is a custom editor, + // which might give an indication on the desired target type. + Class editorType = guessPropertyTypeFromEditors(propertyName); + if (editorType != null) { + return editorType; + } + } + } + catch (InvalidPropertyException ex) { + // Consider as not determinable. + } + return null; + } + + public boolean isReadableProperty(String propertyName) { + try { + PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName); + if (pd != null) { + if (pd.getReadMethod() != null) { + return true; + } + } + else { + // Maybe an indexed/mapped property... + getPropertyValue(propertyName); + return true; + } + } + catch (InvalidPropertyException ex) { + // Cannot be evaluated, so can't be readable. + } + return false; + } + + public boolean isWritableProperty(String propertyName) { + try { + PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName); + if (pd != null) { + if (pd.getWriteMethod() != null) { + return true; + } + } + else { + // Maybe an indexed/mapped property... + getPropertyValue(propertyName); + return true; + } + } + catch (InvalidPropertyException ex) { + // Cannot be evaluated, so can't be writable. + } + return false; + } + + /** + * @deprecated in favor of convertIfNecessary + * @see #convertIfNecessary(Object, Class) + */ + public Object doTypeConversionIfNecessary(Object value, Class requiredType) throws TypeMismatchException { + return convertIfNecessary(value, requiredType, null); + } + + public Object convertIfNecessary( + Object value, Class requiredType, MethodParameter methodParam) throws TypeMismatchException { + try { + return this.typeConverterDelegate.convertIfNecessary(value, requiredType, methodParam); + } + catch (IllegalArgumentException ex) { + throw new TypeMismatchException(value, requiredType, ex); + } + } + + /** + * Convert the given value for the specified property to the latter's type. + *

This method is only intended for optimizations in a BeanFactory. + * Use the convertIfNecessary methods for programmatic conversion. + * @param value the value to convert + * @param propertyName the target property + * (note that nested or indexed properties are not supported here) + * @return the new value, possibly the result of type conversion + * @throws TypeMismatchException if type conversion failed + */ + public Object convertForProperty(Object value, String propertyName) throws TypeMismatchException { + PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(propertyName); + if (pd == null) { + throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, + "No property '" + propertyName + "' found"); + } + try { + return this.typeConverterDelegate.convertIfNecessary(null, value, pd); + } + catch (IllegalArgumentException ex) { + PropertyChangeEvent pce = + new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, null, value); + throw new TypeMismatchException(pce, pd.getPropertyType(), ex); + } + } + + + //--------------------------------------------------------------------- + // Implementation methods + //--------------------------------------------------------------------- + + /** + * Get the last component of the path. Also works if not nested. + * @param bw BeanWrapper to work on + * @param nestedPath property path we know is nested + * @return last component of the path (the property on the target bean) + */ + private String getFinalPath(BeanWrapper bw, String nestedPath) { + if (bw == this) { + return nestedPath; + } + return nestedPath.substring(PropertyAccessorUtils.getLastNestedPropertySeparatorIndex(nestedPath) + 1); + } + + /** + * Recursively navigate to return a BeanWrapper for the nested property path. + * @param propertyPath property property path, which may be nested + * @return a BeanWrapper for the target bean + */ + protected BeanWrapperImpl getBeanWrapperForPropertyPath(String propertyPath) { + int pos = PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(propertyPath); + // Handle nested properties recursively. + if (pos > -1) { + String nestedProperty = propertyPath.substring(0, pos); + String nestedPath = propertyPath.substring(pos + 1); + BeanWrapperImpl nestedBw = getNestedBeanWrapper(nestedProperty); + return nestedBw.getBeanWrapperForPropertyPath(nestedPath); + } + else { + return this; + } + } + + /** + * Retrieve a BeanWrapper for the given nested property. + * Create a new one if not found in the cache. + *

Note: Caching nested BeanWrappers is necessary now, + * to keep registered custom editors for nested properties. + * @param nestedProperty property to create the BeanWrapper for + * @return the BeanWrapper instance, either cached or newly created + */ + private BeanWrapperImpl getNestedBeanWrapper(String nestedProperty) { + if (this.nestedBeanWrappers == null) { + this.nestedBeanWrappers = new HashMap(); + } + // Get value of bean property. + PropertyTokenHolder tokens = getPropertyNameTokens(nestedProperty); + String canonicalName = tokens.canonicalName; + Object propertyValue = getPropertyValue(tokens); + if (propertyValue == null) { + throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + canonicalName); + } + + // Lookup cached sub-BeanWrapper, create new one if not found. + BeanWrapperImpl nestedBw = (BeanWrapperImpl) this.nestedBeanWrappers.get(canonicalName); + if (nestedBw == null || nestedBw.getWrappedInstance() != propertyValue) { + if (logger.isTraceEnabled()) { + logger.trace("Creating new nested BeanWrapper for property '" + canonicalName + "'"); + } + nestedBw = newNestedBeanWrapper(propertyValue, this.nestedPath + canonicalName + NESTED_PROPERTY_SEPARATOR); + // Inherit all type-specific PropertyEditors. + copyDefaultEditorsTo(nestedBw); + copyCustomEditorsTo(nestedBw, canonicalName); + this.nestedBeanWrappers.put(canonicalName, nestedBw); + } + else { + if (logger.isTraceEnabled()) { + logger.trace("Using cached nested BeanWrapper for property '" + canonicalName + "'"); + } + } + return nestedBw; + } + + /** + * Create a new nested BeanWrapper instance. + *

Default implementation creates a BeanWrapperImpl instance. + * Can be overridden in subclasses to create a BeanWrapperImpl subclass. + * @param object object wrapped by this BeanWrapper + * @param nestedPath the nested path of the object + * @return the nested BeanWrapper instance + */ + protected BeanWrapperImpl newNestedBeanWrapper(Object object, String nestedPath) { + return new BeanWrapperImpl(object, nestedPath, this); + } + + /** + * Parse the given property name into the corresponding property name tokens. + * @param propertyName the property name to parse + * @return representation of the parsed property tokens + */ + private PropertyTokenHolder getPropertyNameTokens(String propertyName) { + PropertyTokenHolder tokens = new PropertyTokenHolder(); + String actualName = null; + List keys = new ArrayList(2); + int searchIndex = 0; + while (searchIndex != -1) { + int keyStart = propertyName.indexOf(PROPERTY_KEY_PREFIX, searchIndex); + searchIndex = -1; + if (keyStart != -1) { + int keyEnd = propertyName.indexOf(PROPERTY_KEY_SUFFIX, keyStart + PROPERTY_KEY_PREFIX.length()); + if (keyEnd != -1) { + if (actualName == null) { + actualName = propertyName.substring(0, keyStart); + } + String key = propertyName.substring(keyStart + PROPERTY_KEY_PREFIX.length(), keyEnd); + if ((key.startsWith("'") && key.endsWith("'")) || (key.startsWith("\"") && key.endsWith("\""))) { + key = key.substring(1, key.length() - 1); + } + keys.add(key); + searchIndex = keyEnd + PROPERTY_KEY_SUFFIX.length(); + } + } + } + tokens.actualName = (actualName != null ? actualName : propertyName); + tokens.canonicalName = tokens.actualName; + if (!keys.isEmpty()) { + tokens.canonicalName += + PROPERTY_KEY_PREFIX + + StringUtils.collectionToDelimitedString(keys, PROPERTY_KEY_SUFFIX + PROPERTY_KEY_PREFIX) + + PROPERTY_KEY_SUFFIX; + tokens.keys = StringUtils.toStringArray(keys); + } + return tokens; + } + + + //--------------------------------------------------------------------- + // Implementation of PropertyAccessor interface + //--------------------------------------------------------------------- + + public Object getPropertyValue(String propertyName) throws BeansException { + BeanWrapperImpl nestedBw = getBeanWrapperForPropertyPath(propertyName); + PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedBw, propertyName)); + return nestedBw.getPropertyValue(tokens); + } + + private Object getPropertyValue(PropertyTokenHolder tokens) throws BeansException { + String propertyName = tokens.canonicalName; + String actualName = tokens.actualName; + PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName); + if (pd == null || pd.getReadMethod() == null) { + throw new NotReadablePropertyException(getRootClass(), this.nestedPath + propertyName); + } + Method readMethod = pd.getReadMethod(); + try { + if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) { + readMethod.setAccessible(true); + } + Object value = readMethod.invoke(this.object, (Object[]) null); + if (tokens.keys != null) { + // apply indexes and map keys + for (int i = 0; i < tokens.keys.length; i++) { + String key = tokens.keys[i]; + if (value == null) { + throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName, + "Cannot access indexed value of property referenced in indexed " + + "property path '" + propertyName + "': returned null"); + } + else if (value.getClass().isArray()) { + value = Array.get(value, Integer.parseInt(key)); + } + else if (value instanceof List) { + List list = (List) value; + value = list.get(Integer.parseInt(key)); + } + else if (value instanceof Set) { + // Apply index to Iterator in case of a Set. + Set set = (Set) value; + int index = Integer.parseInt(key); + if (index < 0 || index >= set.size()) { + throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, + "Cannot get element with index " + index + " from Set of size " + + set.size() + ", accessed using property path '" + propertyName + "'"); + } + Iterator it = set.iterator(); + for (int j = 0; it.hasNext(); j++) { + Object elem = it.next(); + if (j == index) { + value = elem; + break; + } + } + } + else if (value instanceof Map) { + Map map = (Map) value; + Class mapKeyType = null; + if (JdkVersion.isAtLeastJava15()) { + mapKeyType = GenericCollectionTypeResolver.getMapKeyReturnType(pd.getReadMethod(), i + 1); + } + // IMPORTANT: Do not pass full property name in here - property editors + // must not kick in for map keys but rather only for map values. + Object convertedMapKey = this.typeConverterDelegate.convertIfNecessary(key, mapKeyType); + // Pass full property name and old value in here, since we want full + // conversion ability for map values. + value = map.get(convertedMapKey); + } + else { + throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, + "Property referenced in indexed property path '" + propertyName + + "' is neither an array nor a List nor a Set nor a Map; returned value was [" + value + "]"); + } + } + } + return value; + } + catch (InvocationTargetException ex) { + throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, + "Getter for property '" + actualName + "' threw exception", ex); + } + catch (IllegalAccessException ex) { + throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, + "Illegal attempt to get property '" + actualName + "' threw exception", ex); + } + catch (IndexOutOfBoundsException ex) { + throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, + "Index of out of bounds in property path '" + propertyName + "'", ex); + } + catch (NumberFormatException ex) { + throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, + "Invalid index in property path '" + propertyName + "'", ex); + } + } + + public void setPropertyValue(String propertyName, Object value) throws BeansException { + BeanWrapperImpl nestedBw = null; + try { + nestedBw = getBeanWrapperForPropertyPath(propertyName); + } + catch (NotReadablePropertyException ex) { + throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName, + "Nested property in path '" + propertyName + "' does not exist", ex); + } + PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedBw, propertyName)); + nestedBw.setPropertyValue(tokens, new PropertyValue(propertyName, value)); + } + + public void setPropertyValue(PropertyValue pv) throws BeansException { + PropertyTokenHolder tokens = (PropertyTokenHolder) pv.resolvedTokens; + if (tokens == null) { + String propertyName = pv.getName(); + BeanWrapperImpl nestedBw = null; + try { + nestedBw = getBeanWrapperForPropertyPath(propertyName); + } + catch (NotReadablePropertyException ex) { + throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName, + "Nested property in path '" + propertyName + "' does not exist", ex); + } + tokens = getPropertyNameTokens(getFinalPath(nestedBw, propertyName)); + if (nestedBw == this) { + pv.getOriginalPropertyValue().resolvedTokens = tokens; + } + nestedBw.setPropertyValue(tokens, pv); + } + else { + setPropertyValue(tokens, pv); + } + } + + private void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException { + String propertyName = tokens.canonicalName; + String actualName = tokens.actualName; + + if (tokens.keys != null) { + // Apply indexes and map keys: fetch value for all keys but the last one. + PropertyTokenHolder getterTokens = new PropertyTokenHolder(); + getterTokens.canonicalName = tokens.canonicalName; + getterTokens.actualName = tokens.actualName; + getterTokens.keys = new String[tokens.keys.length - 1]; + System.arraycopy(tokens.keys, 0, getterTokens.keys, 0, tokens.keys.length - 1); + Object propValue = null; + try { + propValue = getPropertyValue(getterTokens); + } + catch (NotReadablePropertyException ex) { + throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName, + "Cannot access indexed value in property referenced " + + "in indexed property path '" + propertyName + "'", ex); + } + // Set value for last key. + String key = tokens.keys[tokens.keys.length - 1]; + if (propValue == null) { + throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName, + "Cannot access indexed value in property referenced " + + "in indexed property path '" + propertyName + "': returned null"); + } + else if (propValue.getClass().isArray()) { + Class requiredType = propValue.getClass().getComponentType(); + int arrayIndex = Integer.parseInt(key); + Object oldValue = null; + try { + if (isExtractOldValueForEditor()) { + oldValue = Array.get(propValue, arrayIndex); + } + Object convertedValue = this.typeConverterDelegate.convertIfNecessary( + propertyName, oldValue, pv.getValue(), requiredType); + Array.set(propValue, Integer.parseInt(key), convertedValue); + } + catch (IllegalArgumentException ex) { + PropertyChangeEvent pce = + new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, pv.getValue()); + throw new TypeMismatchException(pce, requiredType, ex); + } + catch (IndexOutOfBoundsException ex) { + throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, + "Invalid array index in property path '" + propertyName + "'", ex); + } + } + else if (propValue instanceof List) { + PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName); + Class requiredType = null; + if (JdkVersion.isAtLeastJava15()) { + requiredType = GenericCollectionTypeResolver.getCollectionReturnType( + pd.getReadMethod(), tokens.keys.length); + } + List list = (List) propValue; + int index = Integer.parseInt(key); + Object oldValue = null; + if (isExtractOldValueForEditor() && index < list.size()) { + oldValue = list.get(index); + } + try { + Object convertedValue = this.typeConverterDelegate.convertIfNecessary( + propertyName, oldValue, pv.getValue(), requiredType); + if (index < list.size()) { + list.set(index, convertedValue); + } + else if (index >= list.size()) { + for (int i = list.size(); i < index; i++) { + try { + list.add(null); + } + catch (NullPointerException ex) { + throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, + "Cannot set element with index " + index + " in List of size " + + list.size() + ", accessed using property path '" + propertyName + + "': List does not support filling up gaps with null elements"); + } + } + list.add(convertedValue); + } + } + catch (IllegalArgumentException ex) { + PropertyChangeEvent pce = + new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, pv.getValue()); + throw new TypeMismatchException(pce, requiredType, ex); + } + } + else if (propValue instanceof Map) { + PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName); + Class mapKeyType = null; + Class mapValueType = null; + if (JdkVersion.isAtLeastJava15()) { + mapKeyType = GenericCollectionTypeResolver.getMapKeyReturnType( + pd.getReadMethod(), tokens.keys.length); + mapValueType = GenericCollectionTypeResolver.getMapValueReturnType( + pd.getReadMethod(), tokens.keys.length); + } + Map map = (Map) propValue; + Object convertedMapKey = null; + Object convertedMapValue = null; + try { + // IMPORTANT: Do not pass full property name in here - property editors + // must not kick in for map keys but rather only for map values. + convertedMapKey = this.typeConverterDelegate.convertIfNecessary(key, mapKeyType); + } + catch (IllegalArgumentException ex) { + PropertyChangeEvent pce = + new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, null, pv.getValue()); + throw new TypeMismatchException(pce, mapKeyType, ex); + } + Object oldValue = null; + if (isExtractOldValueForEditor()) { + oldValue = map.get(convertedMapKey); + } + try { + // Pass full property name and old value in here, since we want full + // conversion ability for map values. + convertedMapValue = this.typeConverterDelegate.convertIfNecessary( + propertyName, oldValue, pv.getValue(), mapValueType, null, + new MethodParameter(pd.getReadMethod(), -1, tokens.keys.length + 1)); + } + catch (IllegalArgumentException ex) { + PropertyChangeEvent pce = + new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, pv.getValue()); + throw new TypeMismatchException(pce, mapValueType, ex); + } + map.put(convertedMapKey, convertedMapValue); + } + else { + throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, + "Property referenced in indexed property path '" + propertyName + + "' is neither an array nor a List nor a Map; returned value was [" + pv.getValue() + "]"); + } + } + + else { + PropertyDescriptor pd = pv.resolvedDescriptor; + if (pd == null || !pd.getWriteMethod().getDeclaringClass().isInstance(this.object)) { + pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName); + if (pd == null || pd.getWriteMethod() == null) { + PropertyMatches matches = PropertyMatches.forProperty(propertyName, getRootClass()); + throw new NotWritablePropertyException( + getRootClass(), this.nestedPath + propertyName, + matches.buildErrorMessage(), matches.getPossibleMatches()); + } + pv.getOriginalPropertyValue().resolvedDescriptor = pd; + } + + Object oldValue = null; + try { + Object originalValue = pv.getValue(); + Object valueToApply = originalValue; + if (!Boolean.FALSE.equals(pv.conversionNecessary)) { + if (pv.isConverted()) { + valueToApply = pv.getConvertedValue(); + } + else { + if (isExtractOldValueForEditor() && pd.getReadMethod() != null) { + Method readMethod = pd.getReadMethod(); + if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) { + readMethod.setAccessible(true); + } + try { + oldValue = readMethod.invoke(this.object, new Object[0]); + } + catch (Exception ex) { + if (logger.isDebugEnabled()) { + logger.debug("Could not read previous value of property '" + + this.nestedPath + propertyName + "'", ex); + } + } + } + valueToApply = this.typeConverterDelegate.convertIfNecessary(oldValue, originalValue, pd); + } + pv.getOriginalPropertyValue().conversionNecessary = Boolean.valueOf(valueToApply != originalValue); + } + Method writeMethod = pd.getWriteMethod(); + if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) { + writeMethod.setAccessible(true); + } + writeMethod.invoke(this.object, new Object[] {valueToApply}); + } + catch (InvocationTargetException ex) { + PropertyChangeEvent propertyChangeEvent = + new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, pv.getValue()); + if (ex.getTargetException() instanceof ClassCastException) { + throw new TypeMismatchException(propertyChangeEvent, pd.getPropertyType(), ex.getTargetException()); + } + else { + throw new MethodInvocationException(propertyChangeEvent, ex.getTargetException()); + } + } + catch (IllegalArgumentException ex) { + PropertyChangeEvent pce = + new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, pv.getValue()); + throw new TypeMismatchException(pce, pd.getPropertyType(), ex); + } + catch (IllegalAccessException ex) { + PropertyChangeEvent pce = + new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, pv.getValue()); + throw new MethodInvocationException(pce, ex); + } + } + } + + + public String toString() { + StringBuffer sb = new StringBuffer(getClass().getName()); + if (this.object != null) { + sb.append(": wrapping object [").append(ObjectUtils.identityToString(this.object)).append("]"); + } + else { + sb.append(": no wrapped object set"); + } + return sb.toString(); + } + + + //--------------------------------------------------------------------- + // Inner class for internal use + //--------------------------------------------------------------------- + + private static class PropertyTokenHolder { + + public String canonicalName; + + public String actualName; + + public String[] keys; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/BeansException.java b/org.springframework.beans/src/main/java/org/springframework/beans/BeansException.java new file mode 100644 index 0000000000..18789ba015 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/BeansException.java @@ -0,0 +1,69 @@ +/* + * Copyright 2002-2008 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; + +import org.springframework.core.NestedRuntimeException; +import org.springframework.util.ObjectUtils; + +/** + * Abstract superclass for all exceptions thrown in the beans package + * and subpackages. + * + *

Note that this is a runtime (unchecked) exception. Beans exceptions + * are usually fatal; there is no reason for them to be checked. + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +public abstract class BeansException extends NestedRuntimeException { + + /** + * Create a new BeansException with the specified message. + * @param msg the detail message + */ + public BeansException(String msg) { + super(msg); + } + + /** + * Create a new BeansException with the specified message + * and root cause. + * @param msg the detail message + * @param cause the root cause + */ + public BeansException(String msg, Throwable cause) { + super(msg, cause); + } + + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof BeansException)) { + return false; + } + BeansException otherBe = (BeansException) other; + return (getMessage().equals(otherBe.getMessage()) && + ObjectUtils.nullSafeEquals(getCause(), otherBe.getCause())); + } + + public int hashCode() { + return getMessage().hashCode(); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java b/org.springframework.beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java new file mode 100644 index 0000000000..dcbbb3af6a --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java @@ -0,0 +1,273 @@ +/* + * Copyright 2002-2008 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; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.core.JdkVersion; +import org.springframework.util.ClassUtils; + +/** + * Internal class that caches JavaBeans {@link java.beans.PropertyDescriptor} + * information for a Java class. Not intended for direct use by application code. + * + *

Necessary for own caching of descriptors within the application's + * ClassLoader, rather than rely on the JDK's system-wide BeanInfo cache + * (in order to avoid leaks on ClassLoader shutdown). + * + *

Information is cached statically, so we don't need to create new + * objects of this class for every JavaBean we manipulate. Hence, this class + * implements the factory design pattern, using a private constructor and + * a static {@link #forClass(Class)} factory method to obtain instances. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 05 May 2001 + * @see #acceptClassLoader(ClassLoader) + * @see #clearClassLoader(ClassLoader) + * @see #forClass(Class) + */ +public class CachedIntrospectionResults { + + private static final Log logger = LogFactory.getLog(CachedIntrospectionResults.class); + + /** + * Set of ClassLoaders that this CachedIntrospectionResults class will always + * accept classes from, even if the classes do not qualify as cache-safe. + */ + static final Set acceptedClassLoaders = Collections.synchronizedSet(new HashSet()); + + /** + * Map keyed by class containing CachedIntrospectionResults. + * Needs to be a WeakHashMap with WeakReferences as values to allow + * for proper garbage collection in case of multiple class loaders. + */ + static final Map classCache = Collections.synchronizedMap(new WeakHashMap()); + + + /** + * Accept the given ClassLoader as cache-safe, even if its classes would + * not qualify as cache-safe in this CachedIntrospectionResults class. + *

This configuration method is only relevant in scenarios where the Spring + * classes reside in a 'common' ClassLoader (e.g. the system ClassLoader) + * whose lifecycle is not coupled to the application. In such a scenario, + * CachedIntrospectionResults would by default not cache any of the application's + * classes, since they would create a leak in the common ClassLoader. + *

Any acceptClassLoader call at application startup should + * be paired with a {@link #clearClassLoader} call at application shutdown. + * @param classLoader the ClassLoader to accept + */ + public static void acceptClassLoader(ClassLoader classLoader) { + if (classLoader != null) { + acceptedClassLoaders.add(classLoader); + } + } + + /** + * Clear the introspection cache for the given ClassLoader, removing the + * introspection results for all classes underneath that ClassLoader, + * and deregistering the ClassLoader (and any of its children) from the + * acceptance list. + * @param classLoader the ClassLoader to clear the cache for + */ + public static void clearClassLoader(ClassLoader classLoader) { + if (classLoader == null) { + return; + } + synchronized (classCache) { + for (Iterator it = classCache.keySet().iterator(); it.hasNext();) { + Class beanClass = (Class) it.next(); + if (isUnderneathClassLoader(beanClass.getClassLoader(), classLoader)) { + it.remove(); + } + } + } + synchronized (acceptedClassLoaders) { + for (Iterator it = acceptedClassLoaders.iterator(); it.hasNext();) { + ClassLoader registeredLoader = (ClassLoader) it.next(); + if (isUnderneathClassLoader(registeredLoader, classLoader)) { + it.remove(); + } + } + } + } + + /** + * Create CachedIntrospectionResults for the given bean class. + *

We don't want to use synchronization here. Object references are atomic, + * so we can live with doing the occasional unnecessary lookup at startup only. + * @param beanClass the bean class to analyze + * @return the corresponding CachedIntrospectionResults + * @throws BeansException in case of introspection failure + */ + static CachedIntrospectionResults forClass(Class beanClass) throws BeansException { + CachedIntrospectionResults results = null; + Object value = classCache.get(beanClass); + if (value instanceof Reference) { + Reference ref = (Reference) value; + results = (CachedIntrospectionResults) ref.get(); + } + else { + results = (CachedIntrospectionResults) value; + } + if (results == null) { + // can throw BeansException + results = new CachedIntrospectionResults(beanClass); + if (ClassUtils.isCacheSafe(beanClass, CachedIntrospectionResults.class.getClassLoader()) || + isClassLoaderAccepted(beanClass.getClassLoader())) { + classCache.put(beanClass, results); + } + else { + if (logger.isDebugEnabled()) { + logger.debug("Not strongly caching class [" + beanClass.getName() + "] because it is not cache-safe"); + } + classCache.put(beanClass, new WeakReference(results)); + } + } + return results; + } + + /** + * Check whether this CachedIntrospectionResults class is configured + * to accept the given ClassLoader. + * @param classLoader the ClassLoader to check + * @return whether the given ClassLoader is accepted + * @see #acceptClassLoader + */ + private static boolean isClassLoaderAccepted(ClassLoader classLoader) { + // Iterate over array copy in order to avoid synchronization for the entire + // ClassLoader check (avoiding a synchronized acceptedClassLoaders Iterator). + Object[] acceptedLoaderArray = acceptedClassLoaders.toArray(); + for (int i = 0; i < acceptedLoaderArray.length; i++) { + ClassLoader registeredLoader = (ClassLoader) acceptedLoaderArray[i]; + if (isUnderneathClassLoader(classLoader, registeredLoader)) { + return true; + } + } + return false; + } + + /** + * Check whether the given ClassLoader is underneath the given parent, + * that is, whether the parent is within the candidate's hierarchy. + * @param candidate the candidate ClassLoader to check + * @param parent the parent ClassLoader to check for + */ + private static boolean isUnderneathClassLoader(ClassLoader candidate, ClassLoader parent) { + if (candidate == null) { + return false; + } + if (candidate == parent) { + return true; + } + ClassLoader classLoaderToCheck = candidate; + while (classLoaderToCheck != null) { + classLoaderToCheck = classLoaderToCheck.getParent(); + if (classLoaderToCheck == parent) { + return true; + } + } + return false; + } + + + /** The BeanInfo object for the introspected bean class */ + private final BeanInfo beanInfo; + + /** PropertyDescriptor objects keyed by property name String */ + private final Map propertyDescriptorCache; + + + /** + * Create a new CachedIntrospectionResults instance for the given class. + * @param beanClass the bean class to analyze + * @throws BeansException in case of introspection failure + */ + private CachedIntrospectionResults(Class beanClass) throws BeansException { + try { + if (logger.isTraceEnabled()) { + logger.trace("Getting BeanInfo for class [" + beanClass.getName() + "]"); + } + this.beanInfo = Introspector.getBeanInfo(beanClass); + + // Immediately remove class from Introspector cache, to allow for proper + // garbage collection on class loader shutdown - we cache it here anyway, + // in a GC-friendly manner. In contrast to CachedIntrospectionResults, + // Introspector does not use WeakReferences as values of its WeakHashMap! + Class classToFlush = beanClass; + do { + Introspector.flushFromCaches(classToFlush); + classToFlush = classToFlush.getSuperclass(); + } + while (classToFlush != null); + + if (logger.isTraceEnabled()) { + logger.trace("Caching PropertyDescriptors for class [" + beanClass.getName() + "]"); + } + this.propertyDescriptorCache = new HashMap(); + + // This call is slow so we do it once. + PropertyDescriptor[] pds = this.beanInfo.getPropertyDescriptors(); + for (int i = 0; i < pds.length; i++) { + PropertyDescriptor pd = pds[i]; + if (logger.isTraceEnabled()) { + logger.trace("Found bean property '" + pd.getName() + "'" + + (pd.getPropertyType() != null ? + " of type [" + pd.getPropertyType().getName() + "]" : "") + + (pd.getPropertyEditorClass() != null ? + "; editor [" + pd.getPropertyEditorClass().getName() + "]" : "")); + } + if (JdkVersion.isAtLeastJava15()) { + pd = new GenericTypeAwarePropertyDescriptor(beanClass, pd.getName(), + pd.getReadMethod(), pd.getWriteMethod(), pd.getPropertyEditorClass()); + } + this.propertyDescriptorCache.put(pd.getName(), pd); + } + } + catch (IntrospectionException ex) { + throw new FatalBeanException("Cannot get BeanInfo for object of class [" + beanClass.getName() + "]", ex); + } + } + + BeanInfo getBeanInfo() { + return this.beanInfo; + } + + Class getBeanClass() { + return this.beanInfo.getBeanDescriptor().getBeanClass(); + } + + PropertyDescriptor getPropertyDescriptor(String propertyName) { + return (PropertyDescriptor) this.propertyDescriptorCache.get(propertyName); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/ConfigurablePropertyAccessor.java b/org.springframework.beans/src/main/java/org/springframework/beans/ConfigurablePropertyAccessor.java new file mode 100644 index 0000000000..aeb563c271 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/ConfigurablePropertyAccessor.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2007 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; + +/** + * Interface that encapsulates configuration methods for a PropertyAccessor. + * Also extends the PropertyEditorRegistry interface, which defines methods + * for PropertyEditor management. + * + *

Serves as base interface for {@link BeanWrapper}. + * + * @author Juergen Hoeller + * @since 2.0 + * @see BeanWrapper + */ +public interface ConfigurablePropertyAccessor extends PropertyAccessor, PropertyEditorRegistry, TypeConverter { + + /** + * Set whether to extract the old property value when applying a + * property editor to a new value for a property. + */ + void setExtractOldValueForEditor(boolean extractOldValueForEditor); + + /** + * Return whether to extract the old property value when applying a + * property editor to a new value for a property. + */ + boolean isExtractOldValueForEditor(); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java b/org.springframework.beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java new file mode 100644 index 0000000000..44d5fcbb43 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java @@ -0,0 +1,136 @@ +/* + * Copyright 2002-2008 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; + +import java.beans.PropertyChangeEvent; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.core.MethodParameter; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; + +/** + * {@link PropertyAccessor} implementation that directly accesses instance fields. + * Allows for direct binding to fields instead of going through JavaBean setters. + * + *

This implementation just supports fields in the actual target object. + * It is not able to traverse nested fields. + * + *

A DirectFieldAccessor's default for the "extractOldValueForEditor" setting + * is "true", since a field can always be read without side effects. + * + * @author Juergen Hoeller + * @since 2.0 + * @see #setExtractOldValueForEditor + * @see BeanWrapper + * @see org.springframework.validation.DirectFieldBindingResult + * @see org.springframework.validation.DataBinder#initDirectFieldAccess() + */ +public class DirectFieldAccessor extends AbstractPropertyAccessor { + + private final Object target; + + private final Map fieldMap = new HashMap(); + + private final TypeConverterDelegate typeConverterDelegate; + + + /** + * Create a new DirectFieldAccessor for the given target object. + * @param target the target object to access + */ + public DirectFieldAccessor(Object target) { + Assert.notNull(target, "Target object must not be null"); + this.target = target; + ReflectionUtils.doWithFields(this.target.getClass(), new ReflectionUtils.FieldCallback() { + public void doWith(Field field) { + fieldMap.put(field.getName(), field); + } + }); + this.typeConverterDelegate = new TypeConverterDelegate(this, target); + registerDefaultEditors(); + setExtractOldValueForEditor(true); + } + + + public boolean isReadableProperty(String propertyName) throws BeansException { + return this.fieldMap.containsKey(propertyName); + } + + public boolean isWritableProperty(String propertyName) throws BeansException { + return this.fieldMap.containsKey(propertyName); + } + + public Class getPropertyType(String propertyName) throws BeansException { + Field field = (Field) this.fieldMap.get(propertyName); + if (field != null) { + return field.getType(); + } + return null; + } + + public Object getPropertyValue(String propertyName) throws BeansException { + Field field = (Field) this.fieldMap.get(propertyName); + if (field == null) { + throw new NotReadablePropertyException( + this.target.getClass(), propertyName, "Field '" + propertyName + "' does not exist"); + } + try { + ReflectionUtils.makeAccessible(field); + return field.get(this.target); + } + catch (IllegalAccessException ex) { + throw new InvalidPropertyException(this.target.getClass(), propertyName, "Field is not accessible", ex); + } + } + + public void setPropertyValue(String propertyName, Object newValue) throws BeansException { + Field field = (Field) this.fieldMap.get(propertyName); + if (field == null) { + throw new NotWritablePropertyException( + this.target.getClass(), propertyName, "Field '" + propertyName + "' does not exist"); + } + Object oldValue = null; + try { + ReflectionUtils.makeAccessible(field); + oldValue = field.get(this.target); + Object convertedValue = + this.typeConverterDelegate.convertIfNecessary(propertyName, oldValue, newValue, field.getType()); + field.set(this.target, convertedValue); + } + catch (IllegalAccessException ex) { + throw new InvalidPropertyException(this.target.getClass(), propertyName, "Field is not accessible", ex); + } + catch (IllegalArgumentException ex) { + PropertyChangeEvent pce = new PropertyChangeEvent(this.target, propertyName, oldValue, newValue); + throw new TypeMismatchException(pce, field.getType(), ex); + } + } + + public Object convertIfNecessary( + Object value, Class requiredType, MethodParameter methodParam) throws TypeMismatchException { + try { + return this.typeConverterDelegate.convertIfNecessary(value, requiredType, methodParam); + } + catch (IllegalArgumentException ex) { + throw new TypeMismatchException(value, requiredType, ex); + } + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/FatalBeanException.java b/org.springframework.beans/src/main/java/org/springframework/beans/FatalBeanException.java new file mode 100644 index 0000000000..43feaa766c --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/FatalBeanException.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2006 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; + +/** + * Thrown on an unrecoverable problem encountered in the + * beans packages or sub-packages, e.g. bad class or field. + * + * @author Rod Johnson + */ +public class FatalBeanException extends BeansException { + + /** + * Create a new FatalBeanException with the specified message. + * @param msg the detail message + */ + public FatalBeanException(String msg) { + super(msg); + } + + /** + * Create a new FatalBeanException with the specified message + * and root cause. + * @param msg the detail message + * @param cause the root cause + */ + public FatalBeanException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java b/org.springframework.beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java new file mode 100644 index 0000000000..06a6f93f2d --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java @@ -0,0 +1,114 @@ +/* + * Copyright 2002-2008 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; + +import java.beans.IntrospectionException; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Method; + +import org.springframework.core.BridgeMethodResolver; +import org.springframework.core.GenericTypeResolver; +import org.springframework.core.MethodParameter; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; + +/** + * Extension of the standard JavaBeans PropertyDescriptor class, + * overriding getPropertyType() such that a generically + * declared type will be resolved against the containing bean class. + * + * @author Juergen Hoeller + * @since 2.5.2 + */ +class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor { + + private final Class beanClass; + + private final Method readMethod; + + private final Method writeMethod; + + private final Class propertyEditorClass; + + private Class propertyType; + + private MethodParameter writeMethodParameter; + + + public GenericTypeAwarePropertyDescriptor(Class beanClass, String propertyName, + Method readMethod, Method writeMethod, Class propertyEditorClass) + throws IntrospectionException { + + super(propertyName, null, null); + this.beanClass = beanClass; + Method readMethodToUse = BridgeMethodResolver.findBridgedMethod(readMethod); + Method writeMethodToUse = BridgeMethodResolver.findBridgedMethod(writeMethod); + if (writeMethodToUse == null && readMethodToUse != null) { + // Fallback: Original JavaBeans introspection might not have found matching setter + // method due to lack of bridge method resolution, in case of the getter using a + // covariant return type whereas the setter is defined for the concrete property type. + writeMethodToUse = ClassUtils.getMethodIfAvailable(this.beanClass, + "set" + StringUtils.capitalize(getName()), new Class[] {readMethodToUse.getReturnType()}); + } + this.readMethod = readMethodToUse; + this.writeMethod = writeMethodToUse; + this.propertyEditorClass = propertyEditorClass; + } + + + public Method getReadMethod() { + return this.readMethod; + } + + public Method getWriteMethod() { + return this.writeMethod; + } + + public Class getPropertyEditorClass() { + return this.propertyEditorClass; + } + + public synchronized Class getPropertyType() { + if (this.propertyType == null) { + if (this.readMethod != null) { + this.propertyType = GenericTypeResolver.resolveReturnType(this.readMethod, this.beanClass); + } + else { + MethodParameter writeMethodParam = getWriteMethodParameter(); + if (writeMethodParam != null) { + this.propertyType = writeMethodParam.getParameterType(); + } + else { + this.propertyType = super.getPropertyType(); + } + } + } + return this.propertyType; + } + + public synchronized MethodParameter getWriteMethodParameter() { + if (this.writeMethod == null) { + return null; + } + if (this.writeMethodParameter == null) { + this.writeMethodParameter = new MethodParameter(this.writeMethod, 0); + GenericTypeResolver.resolveParameterType(this.writeMethodParameter, this.beanClass); + } + return this.writeMethodParameter; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/InvalidPropertyException.java b/org.springframework.beans/src/main/java/org/springframework/beans/InvalidPropertyException.java new file mode 100644 index 0000000000..d1c976a5a2 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/InvalidPropertyException.java @@ -0,0 +1,70 @@ +/* + * Copyright 2002-2006 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; + +/** + * Exception thrown when referring to an invalid bean property. + * Carries the offending bean class and property name. + * + * @author Juergen Hoeller + * @since 1.0.2 + */ +public class InvalidPropertyException extends FatalBeanException { + + private Class beanClass; + + private String propertyName; + + + /** + * Create a new InvalidPropertyException. + * @param beanClass the offending bean class + * @param propertyName the offending property + * @param msg the detail message + */ + public InvalidPropertyException(Class beanClass, String propertyName, String msg) { + this(beanClass, propertyName, msg, null); + } + + /** + * Create a new InvalidPropertyException. + * @param beanClass the offending bean class + * @param propertyName the offending property + * @param msg the detail message + * @param cause the root cause + */ + public InvalidPropertyException(Class beanClass, String propertyName, String msg, Throwable cause) { + super("Invalid property '" + propertyName + "' of bean class [" + beanClass.getName() + "]: " + msg, cause); + this.beanClass = beanClass; + this.propertyName = propertyName; + } + + /** + * Return the offending bean class. + */ + public Class getBeanClass() { + return beanClass; + } + + /** + * Return the name of the offending property. + */ + public String getPropertyName() { + return propertyName; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/Mergeable.java b/org.springframework.beans/src/main/java/org/springframework/beans/Mergeable.java new file mode 100644 index 0000000000..97f742c442 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/Mergeable.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2006 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; + +/** + * Interface representing an object whose value set can be merged with + * that of a parent object. + * + * @author Rob Harrop + * @since 2.0 + * @see org.springframework.beans.factory.support.ManagedSet + * @see org.springframework.beans.factory.support.ManagedList + * @see org.springframework.beans.factory.support.ManagedMap + * @see org.springframework.beans.factory.support.ManagedProperties + */ +public interface Mergeable { + + /** + * Is merging enabled for this particular instance? + */ + boolean isMergeEnabled(); + + /** + * Merge the current value set with that of the supplied object. + *

The supplied object is considered the parent, and values in + * the callee's value set must override those of the supplied object. + * @param parent the object to merge with + * @return the result of the merge operation + * @throws IllegalArgumentException if the supplied parent is null + * @exception IllegalStateException if merging is not enabled for this instance + * (i.e. mergeEnabled equals false). + */ + Object merge(Object parent); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/MethodInvocationException.java b/org.springframework.beans/src/main/java/org/springframework/beans/MethodInvocationException.java new file mode 100644 index 0000000000..6a9c0eac84 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/MethodInvocationException.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2006 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; + +import java.beans.PropertyChangeEvent; + +/** + * Thrown when a bean property getter or setter method throws an exception, + * analogous to an InvocationTargetException. + * + * @author Rod Johnson + */ +public class MethodInvocationException extends PropertyAccessException { + + /** + * Error code that a method invocation error will be registered with. + */ + public static final String ERROR_CODE = "methodInvocation"; + + + /** + * Create a new MethodInvocationException. + * @param propertyChangeEvent PropertyChangeEvent that resulted in an exception + * @param cause the Throwable raised by the invoked method + */ + public MethodInvocationException(PropertyChangeEvent propertyChangeEvent, Throwable cause) { + super(propertyChangeEvent, "Property '" + propertyChangeEvent.getPropertyName() + "' threw exception", cause); + } + + public String getErrorCode() { + return ERROR_CODE; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/MutablePropertyValues.java b/org.springframework.beans/src/main/java/org/springframework/beans/MutablePropertyValues.java new file mode 100644 index 0000000000..76f36d9d13 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/MutablePropertyValues.java @@ -0,0 +1,350 @@ +/* + * Copyright 2002-2008 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; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.util.StringUtils; + +/** + * Default implementation of the {@link PropertyValues} interface. + * Allows simple manipulation of properties, and provides constructors + * to support deep copy and construction from a Map. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @author Rob Harrop + * @since 13 May 2001 + */ +public class MutablePropertyValues implements PropertyValues, Serializable { + + /** List of PropertyValue objects */ + private final List propertyValueList; + + private Set processedProperties; + + private volatile boolean converted = false; + + + /** + * Creates a new empty MutablePropertyValues object. + * Property values can be added with the addPropertyValue methods. + * @see #addPropertyValue(PropertyValue) + * @see #addPropertyValue(String, Object) + */ + public MutablePropertyValues() { + this.propertyValueList = new ArrayList(); + } + + /** + * Deep copy constructor. Guarantees PropertyValue references + * are independent, although it can't deep copy objects currently + * referenced by individual PropertyValue objects. + * @param original the PropertyValues to copy + * @see #addPropertyValues(PropertyValues) + */ + public MutablePropertyValues(PropertyValues original) { + // We can optimize this because it's all new: + // There is no replacement of existing property values. + if (original != null) { + PropertyValue[] pvs = original.getPropertyValues(); + this.propertyValueList = new ArrayList(pvs.length); + for (int i = 0; i < pvs.length; i++) { + PropertyValue newPv = new PropertyValue(pvs[i]); + this.propertyValueList.add(newPv); + } + } + else { + this.propertyValueList = new ArrayList(0); + } + } + + /** + * Construct a new MutablePropertyValues object from a Map. + * @param original Map with property values keyed by property name Strings + * @see #addPropertyValues(Map) + */ + public MutablePropertyValues(Map original) { + // We can optimize this because it's all new: + // There is no replacement of existing property values. + if (original != null) { + this.propertyValueList = new ArrayList(original.size()); + Iterator it = original.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = (Map.Entry) it.next(); + PropertyValue newPv = new PropertyValue((String) entry.getKey(), entry.getValue()); + this.propertyValueList.add(newPv); + } + } + else { + this.propertyValueList = new ArrayList(0); + } + } + + /** + * Construct a new MutablePropertyValues object using the given List of + * PropertyValue objects as-is. + *

This is a constructor for advanced usage scenarios. + * It is not intended for typical programmatic use. + * @param propertyValueList List of PropertyValue objects + */ + public MutablePropertyValues(List propertyValueList) { + this.propertyValueList = (propertyValueList != null ? propertyValueList : new ArrayList()); + } + + + /** + * Return the underlying List of PropertyValue objects in its raw form. + * The returned List can be modified directly, although this is not recommended. + *

This is an accessor for optimized access to all PropertyValue objects. + * It is not intended for typical programmatic use. + */ + public List getPropertyValueList() { + return this.propertyValueList; + } + + /** + * Copy all given PropertyValues into this object. Guarantees PropertyValue + * references are independent, although it can't deep copy objects currently + * referenced by individual PropertyValue objects. + * @param other the PropertyValues to copy + * @return this object to allow creating objects, adding multiple PropertyValues + * in a single statement + */ + public MutablePropertyValues addPropertyValues(PropertyValues other) { + if (other != null) { + PropertyValue[] pvs = other.getPropertyValues(); + for (int i = 0; i < pvs.length; i++) { + PropertyValue newPv = new PropertyValue(pvs[i]); + addPropertyValue(newPv); + } + } + return this; + } + + /** + * Add all property values from the given Map. + * @param other Map with property values keyed by property name, + * which must be a String + * @return this object to allow creating objects, adding multiple + * PropertyValues in a single statement + */ + public MutablePropertyValues addPropertyValues(Map other) { + if (other != null) { + Iterator it = other.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = (Map.Entry) it.next(); + PropertyValue newPv = new PropertyValue((String) entry.getKey(), entry.getValue()); + addPropertyValue(newPv); + } + } + return this; + } + + /** + * Add a PropertyValue object, replacing any existing one + * for the corresponding property. + * @param pv PropertyValue object to add + * @return this object to allow creating objects, adding multiple + * PropertyValues in a single statement + */ + public MutablePropertyValues addPropertyValue(PropertyValue pv) { + for (int i = 0; i < this.propertyValueList.size(); i++) { + PropertyValue currentPv = (PropertyValue) this.propertyValueList.get(i); + if (currentPv.getName().equals(pv.getName())) { + pv = mergeIfRequired(pv, currentPv); + setPropertyValueAt(pv, i); + return this; + } + } + this.propertyValueList.add(pv); + return this; + } + + /** + * Overloaded version of addPropertyValue that takes + * a property name and a property value. + * @param propertyName name of the property + * @param propertyValue value of the property + * @see #addPropertyValue(PropertyValue) + */ + public void addPropertyValue(String propertyName, Object propertyValue) { + addPropertyValue(new PropertyValue(propertyName, propertyValue)); + } + + /** + * Modify a PropertyValue object held in this object. + * Indexed from 0. + */ + public void setPropertyValueAt(PropertyValue pv, int i) { + this.propertyValueList.set(i, pv); + } + + /** + * Merges the value of the supplied 'new' {@link PropertyValue} with that of + * the current {@link PropertyValue} if merging is supported and enabled. + * @see Mergeable + */ + private PropertyValue mergeIfRequired(PropertyValue newPv, PropertyValue currentPv) { + Object value = newPv.getValue(); + if (value instanceof Mergeable) { + Mergeable mergeable = (Mergeable) value; + if (mergeable.isMergeEnabled()) { + Object merged = mergeable.merge(currentPv.getValue()); + return new PropertyValue(newPv.getName(), merged); + } + } + return newPv; + } + + /** + * Overloaded version of removePropertyValue that takes a property name. + * @param propertyName name of the property + * @see #removePropertyValue(PropertyValue) + */ + public void removePropertyValue(String propertyName) { + removePropertyValue(getPropertyValue(propertyName)); + } + + /** + * Remove the given PropertyValue, if contained. + * @param pv the PropertyValue to remove + */ + public void removePropertyValue(PropertyValue pv) { + this.propertyValueList.remove(pv); + } + + /** + * Clear this holder, removing all PropertyValues. + */ + public void clear() { + this.propertyValueList.clear(); + } + + + public PropertyValue[] getPropertyValues() { + return (PropertyValue[]) + this.propertyValueList.toArray(new PropertyValue[this.propertyValueList.size()]); + } + + public PropertyValue getPropertyValue(String propertyName) { + for (int i = 0; i < this.propertyValueList.size(); i++) { + PropertyValue pv = (PropertyValue) this.propertyValueList.get(i); + if (pv.getName().equals(propertyName)) { + return pv; + } + } + return null; + } + + /** + * Register the specified property as "processed" in the sense + * of some processor calling the corresponding setter method + * outside of the PropertyValue(s) mechanism. + *

This will lead to true being returned from + * a {@link #contains} call for the specified property. + * @param propertyName the name of the property. + */ + public void registerProcessedProperty(String propertyName) { + if (this.processedProperties == null) { + this.processedProperties = new HashSet(); + } + this.processedProperties.add(propertyName); + } + + public boolean contains(String propertyName) { + return (getPropertyValue(propertyName) != null || + (this.processedProperties != null && this.processedProperties.contains(propertyName))); + } + + public boolean isEmpty() { + return this.propertyValueList.isEmpty(); + } + + public int size() { + return this.propertyValueList.size(); + } + + public PropertyValues changesSince(PropertyValues old) { + MutablePropertyValues changes = new MutablePropertyValues(); + if (old == this) { + return changes; + } + + // for each property value in the new set + for (Iterator it = this.propertyValueList.iterator(); it.hasNext();) { + PropertyValue newPv = (PropertyValue) it.next(); + // if there wasn't an old one, add it + PropertyValue pvOld = old.getPropertyValue(newPv.getName()); + if (pvOld == null) { + changes.addPropertyValue(newPv); + } + else if (!pvOld.equals(newPv)) { + // it's changed + changes.addPropertyValue(newPv); + } + } + return changes; + } + + + /** + * Mark this holder as containing converted values only + * (i.e. no runtime resolution needed anymore). + */ + public void setConverted() { + this.converted = true; + } + + /** + * Return whether this holder contains converted values only (true), + * or whether the values still need to be converted (false). + */ + public boolean isConverted() { + return this.converted; + } + + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof MutablePropertyValues)) { + return false; + } + MutablePropertyValues that = (MutablePropertyValues) other; + return this.propertyValueList.equals(that.propertyValueList); + } + + public int hashCode() { + return this.propertyValueList.hashCode(); + } + + public String toString() { + PropertyValue[] pvs = getPropertyValues(); + StringBuffer sb = new StringBuffer("PropertyValues: length=" + pvs.length + "; "); + sb.append(StringUtils.arrayToDelimitedString(pvs, "; ")); + return sb.toString(); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/NotReadablePropertyException.java b/org.springframework.beans/src/main/java/org/springframework/beans/NotReadablePropertyException.java new file mode 100644 index 0000000000..49ba7d795a --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/NotReadablePropertyException.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2006 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; + +/** + * Exception thrown on an attempt to get the value of a property + * that isn't readable, because there's no getter method. + * + * @author Juergen Hoeller + * @since 1.0.2 + */ +public class NotReadablePropertyException extends InvalidPropertyException { + + /** + * Create a new NotReadablePropertyException. + * @param beanClass the offending bean class + * @param propertyName the offending property + */ + public NotReadablePropertyException(Class beanClass, String propertyName) { + super(beanClass, propertyName, + "Bean property '" + propertyName + "' is not readable or has an invalid getter method: " + + "Does the return type of the getter match the parameter type of the setter?"); + } + + /** + * Create a new NotReadablePropertyException. + * @param beanClass the offending bean class + * @param propertyName the offending property + * @param msg the detail message + */ + public NotReadablePropertyException(Class beanClass, String propertyName, String msg) { + super(beanClass, propertyName, msg); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/NotWritablePropertyException.java b/org.springframework.beans/src/main/java/org/springframework/beans/NotWritablePropertyException.java new file mode 100644 index 0000000000..73b607edb1 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/NotWritablePropertyException.java @@ -0,0 +1,86 @@ +/* + * Copyright 2002-2008 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; + +/** + * Exception thrown on an attempt to set the value of a property that + * is not writable (typically because there is no setter method). + * + * @author Rod Johnson + * @author Alef Arendsen + * @author Arjen Poutsma + */ +public class NotWritablePropertyException extends InvalidPropertyException { + + private String[] possibleMatches = null; + + + /** + * Create a new NotWritablePropertyException. + * @param beanClass the offending bean class + * @param propertyName the offending property name + */ + public NotWritablePropertyException(Class beanClass, String propertyName) { + super(beanClass, propertyName, + "Bean property '" + propertyName + "' is not writable or has an invalid setter method: " + + "Does the return type of the getter match the parameter type of the setter?"); + } + + /** + * Create a new NotWritablePropertyException. + * @param beanClass the offending bean class + * @param propertyName the offending property name + * @param msg the detail message + */ + public NotWritablePropertyException(Class beanClass, String propertyName, String msg) { + super(beanClass, propertyName, msg); + } + + /** + * Create a new NotWritablePropertyException. + * @param beanClass the offending bean class + * @param propertyName the offending property name + * @param msg the detail message + * @param cause the root cause + */ + public NotWritablePropertyException(Class beanClass, String propertyName, String msg, Throwable cause) { + super(beanClass, propertyName, msg, cause); + } + + /** + * Create a new NotWritablePropertyException. + * @param beanClass the offending bean class + * @param propertyName the offending property name + * @param msg the detail message + * @param possibleMatches suggestions for actual bean property names + * that closely match the invalid property name + */ + public NotWritablePropertyException(Class beanClass, String propertyName, String msg, String[] possibleMatches) { + super(beanClass, propertyName, msg); + this.possibleMatches = possibleMatches; + } + + + /** + * Return suggestions for actual bean property names that closely match + * the invalid property name, if any. + */ + public String[] getPossibleMatches() { + return this.possibleMatches; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/NullValueInNestedPathException.java b/org.springframework.beans/src/main/java/org/springframework/beans/NullValueInNestedPathException.java new file mode 100644 index 0000000000..40c593c444 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/NullValueInNestedPathException.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2006 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; + +/** + * Exception thrown when navigation of a valid nested property + * path encounters a NullPointerException. + * + *

For example, navigating "spouse.age" could fail because the + * spouse property of the target object has a null value. + * + * @author Rod Johnson + */ +public class NullValueInNestedPathException extends InvalidPropertyException { + + /** + * Create a new NullValueInNestedPathException. + * @param beanClass the offending bean class + * @param propertyName the offending property + */ + public NullValueInNestedPathException(Class beanClass, String propertyName) { + super(beanClass, propertyName, "Value of nested property '" + propertyName + "' is null"); + } + + /** + * Create a new NullValueInNestedPathException. + * @param beanClass the offending bean class + * @param propertyName the offending property + * @param msg the detail message + */ + public NullValueInNestedPathException(Class beanClass, String propertyName, String msg) { + super(beanClass, propertyName, msg); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/PropertyAccessException.java b/org.springframework.beans/src/main/java/org/springframework/beans/PropertyAccessException.java new file mode 100644 index 0000000000..0b42d3dae4 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/PropertyAccessException.java @@ -0,0 +1,65 @@ +/* + * Copyright 2002-2007 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; + +import java.beans.PropertyChangeEvent; + +import org.springframework.core.ErrorCoded; + +/** + * Superclass for exceptions related to a property access, + * such as type mismatch or invocation target exception. + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +public abstract class PropertyAccessException extends BeansException implements ErrorCoded { + + private transient PropertyChangeEvent propertyChangeEvent; + + + /** + * Create a new PropertyAccessException. + * @param propertyChangeEvent the PropertyChangeEvent that resulted in the problem + * @param msg the detail message + * @param cause the root cause + */ + public PropertyAccessException(PropertyChangeEvent propertyChangeEvent, String msg, Throwable cause) { + super(msg, cause); + this.propertyChangeEvent = propertyChangeEvent; + } + + /** + * Create a new PropertyAccessException without PropertyChangeEvent. + * @param msg the detail message + * @param cause the root cause + */ + public PropertyAccessException(String msg, Throwable cause) { + super(msg, cause); + } + + + /** + * Return the PropertyChangeEvent that resulted in the problem. + * May be null; only available if an actual bean property + * was affected. + */ + public PropertyChangeEvent getPropertyChangeEvent() { + return this.propertyChangeEvent; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/PropertyAccessor.java b/org.springframework.beans/src/main/java/org/springframework/beans/PropertyAccessor.java new file mode 100644 index 0000000000..b8c3ad73ac --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/PropertyAccessor.java @@ -0,0 +1,204 @@ +/* + * Copyright 2002-2008 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; + +import java.util.Map; + +/** + * Common interface for classes that can access named properties + * (such as bean properties of an object or fields in an object) + * Serves as base interface for {@link BeanWrapper}. + * + * @author Juergen Hoeller + * @since 1.1 + * @see BeanWrapper + * @see PropertyAccessorFactory#forBeanPropertyAccess + * @see PropertyAccessorFactory#forDirectFieldAccess + */ +public interface PropertyAccessor { + + /** + * Path separator for nested properties. + * Follows normal Java conventions: getFoo().getBar() would be "foo.bar". + */ + String NESTED_PROPERTY_SEPARATOR = "."; + char NESTED_PROPERTY_SEPARATOR_CHAR = '.'; + + /** + * Marker that indicates the start of a property key for an + * indexed or mapped property like "person.addresses[0]". + */ + String PROPERTY_KEY_PREFIX = "["; + char PROPERTY_KEY_PREFIX_CHAR = '['; + + /** + * Marker that indicates the end of a property key for an + * indexed or mapped property like "person.addresses[0]". + */ + String PROPERTY_KEY_SUFFIX = "]"; + char PROPERTY_KEY_SUFFIX_CHAR = ']'; + + + /** + * Determine whether the specified property is readable. + *

Returns false if the property doesn't exist. + * @param propertyName the property to check + * (may be a nested path and/or an indexed/mapped property) + * @return whether the property is readable + */ + boolean isReadableProperty(String propertyName); + + /** + * Determine whether the specified property is writable. + *

Returns false if the property doesn't exist. + * @param propertyName the property to check + * (may be a nested path and/or an indexed/mapped property) + * @return whether the property is writable + */ + boolean isWritableProperty(String propertyName); + + /** + * Determine the property type for the specified property, + * either checking the property descriptor or checking the value + * in case of an indexed or mapped element. + * @param propertyName the property to check + * (may be a nested path and/or an indexed/mapped property) + * @return the property type for the particular property, + * or null if not determinable + * @throws InvalidPropertyException if there is no such property or + * if the property isn't readable + * @throws PropertyAccessException if the property was valid but the + * accessor method failed + */ + Class getPropertyType(String propertyName) throws BeansException; + + /** + * Get the current value of the specified property. + * @param propertyName the name of the property to get the value of + * (may be a nested path and/or an indexed/mapped property) + * @return the value of the property + * @throws InvalidPropertyException if there is no such property or + * if the property isn't readable + * @throws PropertyAccessException if the property was valid but the + * accessor method failed + */ + Object getPropertyValue(String propertyName) throws BeansException; + + /** + * Set the specified value as current property value. + * @param propertyName the name of the property to set the value of + * (may be a nested path and/or an indexed/mapped property) + * @param value the new value + * @throws InvalidPropertyException if there is no such property or + * if the property isn't writable + * @throws PropertyAccessException if the property was valid but the + * accessor method failed or a type mismatch occured + */ + void setPropertyValue(String propertyName, Object value) throws BeansException; + + /** + * Set the specified value as current property value. + * @param pv an object containing the new property value + * @throws InvalidPropertyException if there is no such property or + * if the property isn't writable + * @throws PropertyAccessException if the property was valid but the + * accessor method failed or a type mismatch occured + */ + void setPropertyValue(PropertyValue pv) throws BeansException; + + /** + * Perform a batch update from a Map. + *

Bulk updates from PropertyValues are more powerful: This method is + * provided for convenience. Behavior will be identical to that of + * the {@link #setPropertyValues(PropertyValues)} method. + * @param map Map to take properties from. Contains property value objects, + * keyed by property name + * @throws InvalidPropertyException if there is no such property or + * if the property isn't writable + * @throws PropertyBatchUpdateException if one or more PropertyAccessExceptions + * occured for specific properties during the batch update. This exception bundles + * all individual PropertyAccessExceptions. All other properties will have been + * successfully updated. + */ + void setPropertyValues(Map map) throws BeansException; + + /** + * The preferred way to perform a batch update. + *

Note that performing a batch update differs from performing a single update, + * in that an implementation of this class will continue to update properties + * if a recoverable error (such as a type mismatch, but not an + * invalid field name or the like) is encountered, throwing a + * {@link PropertyBatchUpdateException} containing all the individual errors. + * This exception can be examined later to see all binding errors. + * Properties that were successfully updated remain changed. + *

Does not allow unknown fields or invalid fields. + * @param pvs PropertyValues to set on the target object + * @throws InvalidPropertyException if there is no such property or + * if the property isn't writable + * @throws PropertyBatchUpdateException if one or more PropertyAccessExceptions + * occured for specific properties during the batch update. This exception bundles + * all individual PropertyAccessExceptions. All other properties will have been + * successfully updated. + * @see #setPropertyValues(PropertyValues, boolean, boolean) + */ + void setPropertyValues(PropertyValues pvs) throws BeansException; + + /** + * Perform a batch update with more control over behavior. + *

Note that performing a batch update differs from performing a single update, + * in that an implementation of this class will continue to update properties + * if a recoverable error (such as a type mismatch, but not an + * invalid field name or the like) is encountered, throwing a + * {@link PropertyBatchUpdateException} containing all the individual errors. + * This exception can be examined later to see all binding errors. + * Properties that were successfully updated remain changed. + * @param pvs PropertyValues to set on the target object + * @param ignoreUnknown should we ignore unknown properties (not found in the bean) + * @throws InvalidPropertyException if there is no such property or + * if the property isn't writable + * @throws PropertyBatchUpdateException if one or more PropertyAccessExceptions + * occured for specific properties during the batch update. This exception bundles + * all individual PropertyAccessExceptions. All other properties will have been + * successfully updated. + * @see #setPropertyValues(PropertyValues, boolean, boolean) + */ + void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown) + throws BeansException; + + /** + * Perform a batch update with full control over behavior. + *

Note that performing a batch update differs from performing a single update, + * in that an implementation of this class will continue to update properties + * if a recoverable error (such as a type mismatch, but not an + * invalid field name or the like) is encountered, throwing a + * {@link PropertyBatchUpdateException} containing all the individual errors. + * This exception can be examined later to see all binding errors. + * Properties that were successfully updated remain changed. + * @param pvs PropertyValues to set on the target object + * @param ignoreUnknown should we ignore unknown properties (not found in the bean) + * @param ignoreInvalid should we ignore invalid properties (found but not accessible) + * @throws InvalidPropertyException if there is no such property or + * if the property isn't writable + * @throws PropertyBatchUpdateException if one or more PropertyAccessExceptions + * occured for specific properties during the batch update. This exception bundles + * all individual PropertyAccessExceptions. All other properties will have been + * successfully updated. + */ + void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid) + throws BeansException; + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/PropertyAccessorFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/PropertyAccessorFactory.java new file mode 100644 index 0000000000..f605806983 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/PropertyAccessorFactory.java @@ -0,0 +1,51 @@ +/* + * Copyright 2002-2008 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; + +/** + * Simple factory facade for obtaining {@link PropertyAccessor} instances, + * in particular for {@link BeanWrapper} instances. Conceals the actual + * target implementation classes and their extended public signature. + * + * @author Juergen Hoeller + * @since 2.5.2 + */ +public abstract class PropertyAccessorFactory { + + /** + * Obtain a BeanWrapper for the given target object, + * accessing properties in JavaBeans style. + * @param target the target object to wrap + * @return the property accessor + * @see BeanWrapperImpl + */ + public static BeanWrapper forBeanPropertyAccess(Object target) { + return new BeanWrapperImpl(target); + } + + /** + * Obtain a PropertyAccessor for the given target object, + * accessing properties in direct field style. + * @param target the target object to wrap + * @return the property accessor + * @see DirectFieldAccessor + */ + public static ConfigurablePropertyAccessor forDirectFieldAccess(Object target) { + return new DirectFieldAccessor(target); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/PropertyAccessorUtils.java b/org.springframework.beans/src/main/java/org/springframework/beans/PropertyAccessorUtils.java new file mode 100644 index 0000000000..3e5717e25e --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/PropertyAccessorUtils.java @@ -0,0 +1,184 @@ +/* + * Copyright 2002-2007 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; + +/** + * Utility methods for classes that perform bean property access + * according to the {@link PropertyAccessor} interface. + * + * @author Juergen Hoeller + * @since 1.2.6 + */ +public abstract class PropertyAccessorUtils { + + /** + * Return the actual property name for the given property path. + * @param propertyPath the property path to determine the property name + * for (can include property keys, for example for specifying a map entry) + * @return the actual property name, without any key elements + */ + public static String getPropertyName(String propertyPath) { + int separatorIndex = propertyPath.indexOf(PropertyAccessor.PROPERTY_KEY_PREFIX_CHAR); + return (separatorIndex != -1 ? propertyPath.substring(0, separatorIndex) : propertyPath); + } + + /** + * Check whether the given property path indicates an indexed or nested property. + * @param propertyPath the property path to check + * @return whether the path indicates an indexed or nested property + */ + public static boolean isNestedOrIndexedProperty(String propertyPath) { + if (propertyPath == null) { + return false; + } + for (int i = 0; i < propertyPath.length(); i++) { + char ch = propertyPath.charAt(i); + if (ch == PropertyAccessor.NESTED_PROPERTY_SEPARATOR_CHAR || + ch == PropertyAccessor.PROPERTY_KEY_PREFIX_CHAR) { + return true; + } + } + return false; + } + + /** + * Determine the first nested property separator in the + * given property path, ignoring dots in keys (like "map[my.key]"). + * @param propertyPath the property path to check + * @return the index of the nested property separator, or -1 if none + */ + public static int getFirstNestedPropertySeparatorIndex(String propertyPath) { + return getNestedPropertySeparatorIndex(propertyPath, false); + } + + /** + * Determine the first nested property separator in the + * given property path, ignoring dots in keys (like "map[my.key]"). + * @param propertyPath the property path to check + * @return the index of the nested property separator, or -1 if none + */ + public static int getLastNestedPropertySeparatorIndex(String propertyPath) { + return getNestedPropertySeparatorIndex(propertyPath, true); + } + + /** + * Determine the first (or last) nested property separator in the + * given property path, ignoring dots in keys (like "map[my.key]"). + * @param propertyPath the property path to check + * @param last whether to return the last separator rather than the first + * @return the index of the nested property separator, or -1 if none + */ + private static int getNestedPropertySeparatorIndex(String propertyPath, boolean last) { + boolean inKey = false; + int length = propertyPath.length(); + int i = (last ? length - 1 : 0); + while (last ? i >= 0 : i < length) { + switch (propertyPath.charAt(i)) { + case PropertyAccessor.PROPERTY_KEY_PREFIX_CHAR: + case PropertyAccessor.PROPERTY_KEY_SUFFIX_CHAR: + inKey = !inKey; + break; + case PropertyAccessor.NESTED_PROPERTY_SEPARATOR_CHAR: + if (!inKey) { + return i; + } + } + if (last) { + i--; + } + else { + i++; + } + } + return -1; + } + + /** + * Determine whether the given registered path matches the given property path, + * either indicating the property itself or an indexed element of the property. + * @param propertyPath the property path (typically without index) + * @param registeredPath the registered path (potentially with index) + * @return whether the paths match + */ + public static boolean matchesProperty(String registeredPath, String propertyPath) { + if (!registeredPath.startsWith(propertyPath)) { + return false; + } + if (registeredPath.length() == propertyPath.length()) { + return true; + } + if (registeredPath.charAt(propertyPath.length()) != PropertyAccessor.PROPERTY_KEY_PREFIX_CHAR) { + return false; + } + return (registeredPath.indexOf(PropertyAccessor.PROPERTY_KEY_SUFFIX_CHAR, propertyPath.length() + 1) == + registeredPath.length() - 1); + } + + /** + * Determine the canonical name for the given property path. + * Removes surrounding quotes from map keys:
+ * map['key'] -> map[key]
+ * map["key"] -> map[key] + * @param propertyName the bean property path + * @return the canonical representation of the property path + */ + public static String canonicalPropertyName(String propertyName) { + if (propertyName == null) { + return ""; + } + + StringBuffer buf = new StringBuffer(propertyName); + int searchIndex = 0; + while (searchIndex != -1) { + int keyStart = buf.indexOf(PropertyAccessor.PROPERTY_KEY_PREFIX, searchIndex); + searchIndex = -1; + if (keyStart != -1) { + int keyEnd = buf.indexOf( + PropertyAccessor.PROPERTY_KEY_SUFFIX, keyStart + PropertyAccessor.PROPERTY_KEY_PREFIX.length()); + if (keyEnd != -1) { + String key = buf.substring(keyStart + PropertyAccessor.PROPERTY_KEY_PREFIX.length(), keyEnd); + if ((key.startsWith("'") && key.endsWith("'")) || (key.startsWith("\"") && key.endsWith("\""))) { + buf.delete(keyStart + 1, keyStart + 2); + buf.delete(keyEnd - 2, keyEnd - 1); + keyEnd = keyEnd - 2; + } + searchIndex = keyEnd + PropertyAccessor.PROPERTY_KEY_SUFFIX.length(); + } + } + } + return buf.toString(); + } + + /** + * Determine the canonical names for the given property paths. + * @param propertyNames the bean property paths (as array) + * @return the canonical representation of the property paths + * (as array of the same size) + * @see #canonicalPropertyName(String) + */ + public static String[] canonicalPropertyNames(String[] propertyNames) { + if (propertyNames == null) { + return null; + } + String[] result = new String[propertyNames.length]; + for (int i = 0; i < propertyNames.length; i++) { + result[i] = canonicalPropertyName(propertyNames[i]); + } + return result; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/PropertyBatchUpdateException.java b/org.springframework.beans/src/main/java/org/springframework/beans/PropertyBatchUpdateException.java new file mode 100644 index 0000000000..d95465ed25 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/PropertyBatchUpdateException.java @@ -0,0 +1,143 @@ +/* + * Copyright 2002-2007 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; + +import java.io.PrintStream; +import java.io.PrintWriter; + +import org.springframework.util.Assert; + +/** + * Combined exception, composed of individual PropertyAccessException instances. + * An object of this class is created at the beginning of the binding + * process, and errors added to it as necessary. + * + *

The binding process continues when it encounters application-level + * PropertyAccessExceptions, applying those changes that can be applied + * and storing rejected changes in an object of this class. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 18 April 2001 + */ +public class PropertyBatchUpdateException extends BeansException { + + /** List of PropertyAccessException objects */ + private PropertyAccessException[] propertyAccessExceptions; + + + /** + * Create a new PropertyBatchUpdateException. + * @param propertyAccessExceptions the List of PropertyAccessExceptions + */ + public PropertyBatchUpdateException(PropertyAccessException[] propertyAccessExceptions) { + super(null); + Assert.notEmpty(propertyAccessExceptions, "At least 1 PropertyAccessException required"); + this.propertyAccessExceptions = propertyAccessExceptions; + } + + + /** + * If this returns 0, no errors were encountered during binding. + */ + public final int getExceptionCount() { + return this.propertyAccessExceptions.length; + } + + /** + * Return an array of the propertyAccessExceptions stored in this object. + * Will return the empty array (not null) if there were no errors. + */ + public final PropertyAccessException[] getPropertyAccessExceptions() { + return this.propertyAccessExceptions; + } + + /** + * Return the exception for this field, or null if there isn't one. + */ + public PropertyAccessException getPropertyAccessException(String propertyName) { + for (int i = 0; i < this.propertyAccessExceptions.length; i++) { + PropertyAccessException pae = this.propertyAccessExceptions[i]; + if (propertyName.equals(pae.getPropertyChangeEvent().getPropertyName())) { + return pae; + } + } + return null; + } + + + public String getMessage() { + StringBuffer sb = new StringBuffer("Failed properties: "); + for (int i = 0; i < this.propertyAccessExceptions.length; i++) { + sb.append(this.propertyAccessExceptions[i].getMessage()); + if (i < this.propertyAccessExceptions.length - 1) { + sb.append("; "); + } + } + return sb.toString(); + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append(getClass().getName()).append("; nested PropertyAccessExceptions ("); + sb.append(getExceptionCount()).append(") are:"); + for (int i = 0; i < this.propertyAccessExceptions.length; i++) { + sb.append('\n').append("PropertyAccessException ").append(i + 1).append(": "); + sb.append(this.propertyAccessExceptions[i]); + } + return sb.toString(); + } + + public void printStackTrace(PrintStream ps) { + synchronized (ps) { + ps.println(getClass().getName() + "; nested PropertyAccessException details (" + + getExceptionCount() + ") are:"); + for (int i = 0; i < this.propertyAccessExceptions.length; i++) { + ps.println("PropertyAccessException " + (i + 1) + ":"); + this.propertyAccessExceptions[i].printStackTrace(ps); + } + } + } + + public void printStackTrace(PrintWriter pw) { + synchronized (pw) { + pw.println(getClass().getName() + "; nested PropertyAccessException details (" + + getExceptionCount() + ") are:"); + for (int i = 0; i < this.propertyAccessExceptions.length; i++) { + pw.println("PropertyAccessException " + (i + 1) + ":"); + this.propertyAccessExceptions[i].printStackTrace(pw); + } + } + } + + public boolean contains(Class exType) { + if (exType == null) { + return false; + } + if (exType.isInstance(this)) { + return true; + } + for (int i = 0; i < this.propertyAccessExceptions.length; i++) { + PropertyAccessException pae = this.propertyAccessExceptions[i]; + if (pae.contains(exType)) { + return true; + } + } + return false; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/PropertyEditorRegistrar.java b/org.springframework.beans/src/main/java/org/springframework/beans/PropertyEditorRegistrar.java new file mode 100644 index 0000000000..9e141fde3a --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/PropertyEditorRegistrar.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2007 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; + +/** + * Interface for strategies that register custom + * {@link java.beans.PropertyEditor property editors} with a + * {@link org.springframework.beans.PropertyEditorRegistry property editor registry}. + * + *

This is particularly useful when you need to use the same set of + * property editors in several different situations: write a corresponding + * registrar and reuse that in each case. + * + * @author Juergen Hoeller + * @since 1.2.6 + * @see PropertyEditorRegistry + * @see java.beans.PropertyEditor + */ +public interface PropertyEditorRegistrar { + + /** + * Register custom {@link java.beans.PropertyEditor PropertyEditors} with + * the given PropertyEditorRegistry. + *

The passed-in registry will usually be a {@link BeanWrapper} or a + * {@link org.springframework.validation.DataBinder DataBinder}. + *

It is expected that implementations will create brand new + * PropertyEditors instances for each invocation of this + * method (since PropertyEditors are not threadsafe). + * @param registry the PropertyEditorRegistry to register the + * custom PropertyEditors with + */ + void registerCustomEditors(PropertyEditorRegistry registry); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/PropertyEditorRegistry.java b/org.springframework.beans/src/main/java/org/springframework/beans/PropertyEditorRegistry.java new file mode 100644 index 0000000000..95c6e8a418 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/PropertyEditorRegistry.java @@ -0,0 +1,79 @@ +/* + * Copyright 2002-2007 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; + +import java.beans.PropertyEditor; + +/** + * Encapsulates methods for registering JavaBeans {@link PropertyEditor PropertyEditors}. + * This is the central interface that a {@link PropertyEditorRegistrar} operates on. + * + *

Extended by {@link BeanWrapper}; implemented by {@link BeanWrapperImpl} + * and {@link org.springframework.validation.DataBinder}. + * + * @author Juergen Hoeller + * @since 1.2.6 + * @see java.beans.PropertyEditor + * @see PropertyEditorRegistrar + * @see BeanWrapper + * @see org.springframework.validation.DataBinder + */ +public interface PropertyEditorRegistry { + + /** + * Register the given custom property editor for all properties of the given type. + * @param requiredType the type of the property + * @param propertyEditor the editor to register + */ + void registerCustomEditor(Class requiredType, PropertyEditor propertyEditor); + + /** + * Register the given custom property editor for the given type and + * property, or for all properties of the given type. + *

If the property path denotes an array or Collection property, + * the editor will get applied either to the array/Collection itself + * (the {@link PropertyEditor} has to create an array or Collection value) or + * to each element (the PropertyEditor has to create the element type), + * depending on the specified required type. + *

Note: Only one single registered custom editor per property path + * is supported. In the case of a Collection/array, do not register an editor + * for both the Collection/array and each element on the same property. + *

For example, if you wanted to register an editor for "items[n].quantity" + * (for all values n), you would use "items.quantity" as the value of the + * 'propertyPath' argument to this method. + * @param requiredType the type of the property. This may be null + * if a property is given but should be specified in any case, in particular in + * case of a Collection - making clear whether the editor is supposed to apply + * to the entire Collection itself or to each of its entries. So as a general rule: + * Do not specify null here in case of a Collection/array! + * @param propertyPath the path of the property (name or nested path), or + * null if registering an editor for all properties of the given type + * @param propertyEditor editor to register + */ + void registerCustomEditor(Class requiredType, String propertyPath, PropertyEditor propertyEditor); + + /** + * Find a custom property editor for the given type and property. + * @param requiredType the type of the property (can be null if a property + * is given but should be specified in any case for consistency checking) + * @param propertyPath the path of the property (name or nested path), or + * null if looking for an editor for all properties of the given type + * @return the registered editor, or null if none + */ + PropertyEditor findCustomEditor(Class requiredType, String propertyPath); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java b/org.springframework.beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java new file mode 100644 index 0000000000..b192230b2b --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java @@ -0,0 +1,539 @@ +/* + * Copyright 2002-2008 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; + +import java.beans.PropertyEditor; +import java.io.File; +import java.io.InputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.URI; +import java.net.URL; +import java.nio.charset.Charset; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.regex.Pattern; + +import org.springframework.beans.propertyeditors.ByteArrayPropertyEditor; +import org.springframework.beans.propertyeditors.CharArrayPropertyEditor; +import org.springframework.beans.propertyeditors.CharacterEditor; +import org.springframework.beans.propertyeditors.CharsetEditor; +import org.springframework.beans.propertyeditors.ClassArrayEditor; +import org.springframework.beans.propertyeditors.ClassEditor; +import org.springframework.beans.propertyeditors.CustomBooleanEditor; +import org.springframework.beans.propertyeditors.CustomCollectionEditor; +import org.springframework.beans.propertyeditors.CustomMapEditor; +import org.springframework.beans.propertyeditors.CustomNumberEditor; +import org.springframework.beans.propertyeditors.FileEditor; +import org.springframework.beans.propertyeditors.InputStreamEditor; +import org.springframework.beans.propertyeditors.LocaleEditor; +import org.springframework.beans.propertyeditors.PatternEditor; +import org.springframework.beans.propertyeditors.PropertiesEditor; +import org.springframework.beans.propertyeditors.StringArrayPropertyEditor; +import org.springframework.beans.propertyeditors.URIEditor; +import org.springframework.beans.propertyeditors.URLEditor; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.ResourceArrayPropertyEditor; +import org.springframework.util.ClassUtils; + +/** + * Base implementation of the {@link PropertyEditorRegistry} interface. + * Provides management of default editors and custom editors. + * Mainly serves as base class for {@link BeanWrapperImpl}. + * + * @author Juergen Hoeller + * @author Rob Harrop + * @since 1.2.6 + * @see java.beans.PropertyEditorManager + * @see java.beans.PropertyEditorSupport#setAsText + * @see java.beans.PropertyEditorSupport#setValue + */ +public class PropertyEditorRegistrySupport implements PropertyEditorRegistry { + + private boolean defaultEditorsActive = false; + + private boolean configValueEditorsActive = false; + + private boolean propertySpecificEditorsRegistered = false; + + private Map defaultEditors; + + private Map customEditors; + + private Set sharedEditors; + + private Map customEditorCache; + + + //--------------------------------------------------------------------- + // Management of default editors + //--------------------------------------------------------------------- + + /** + * Activate the default editors for this registry instance, + * allowing for lazily registering default editors when needed. + */ + protected void registerDefaultEditors() { + this.defaultEditorsActive = true; + } + + /** + * Activate config value editors which are only intended for configuration purposes, + * such as {@link org.springframework.beans.propertyeditors.StringArrayPropertyEditor}. + *

Those editors are not registered by default simply because they are in + * general inappropriate for data binding purposes. Of course, you may register + * them individually in any case, through {@link #registerCustomEditor}. + */ + public void useConfigValueEditors() { + this.configValueEditorsActive = true; + } + + /** + * Retrieve the default editor for the given property type, if any. + *

Lazily registers the default editors, if they are active. + * @param requiredType type of the property + * @return the default editor, or null if none found + * @see #registerDefaultEditors + */ + public PropertyEditor getDefaultEditor(Class requiredType) { + if (!this.defaultEditorsActive) { + return null; + } + if (this.defaultEditors == null) { + doRegisterDefaultEditors(); + } + return (PropertyEditor) this.defaultEditors.get(requiredType); + } + + /** + * Actually register the default editors for this registry instance. + * @see org.springframework.beans.propertyeditors.ByteArrayPropertyEditor + * @see org.springframework.beans.propertyeditors.ClassEditor + * @see org.springframework.beans.propertyeditors.CharacterEditor + * @see org.springframework.beans.propertyeditors.CustomBooleanEditor + * @see org.springframework.beans.propertyeditors.CustomNumberEditor + * @see org.springframework.beans.propertyeditors.CustomCollectionEditor + * @see org.springframework.beans.propertyeditors.CustomMapEditor + * @see org.springframework.beans.propertyeditors.FileEditor + * @see org.springframework.beans.propertyeditors.InputStreamEditor + * @see org.springframework.jndi.JndiTemplateEditor + * @see org.springframework.beans.propertyeditors.LocaleEditor + * @see org.springframework.beans.propertyeditors.PropertiesEditor + * @see org.springframework.beans.PropertyValuesEditor + * @see org.springframework.core.io.support.ResourceArrayPropertyEditor + * @see org.springframework.core.io.ResourceEditor + * @see org.springframework.transaction.interceptor.TransactionAttributeEditor + * @see org.springframework.transaction.interceptor.TransactionAttributeSourceEditor + * @see org.springframework.beans.propertyeditors.URLEditor + */ + private void doRegisterDefaultEditors() { + this.defaultEditors = new HashMap(64); + + // Simple editors, without parameterization capabilities. + // The JDK does not contain a default editor for any of these target types. + this.defaultEditors.put(Charset.class, new CharsetEditor()); + this.defaultEditors.put(Class.class, new ClassEditor()); + this.defaultEditors.put(Class[].class, new ClassArrayEditor()); + this.defaultEditors.put(File.class, new FileEditor()); + this.defaultEditors.put(InputStream.class, new InputStreamEditor()); + this.defaultEditors.put(Locale.class, new LocaleEditor()); + this.defaultEditors.put(Pattern.class, new PatternEditor()); + this.defaultEditors.put(Properties.class, new PropertiesEditor()); + this.defaultEditors.put(Resource[].class, new ResourceArrayPropertyEditor()); + this.defaultEditors.put(URI.class, new URIEditor()); + this.defaultEditors.put(URL.class, new URLEditor()); + + // Default instances of collection editors. + // Can be overridden by registering custom instances of those as custom editors. + this.defaultEditors.put(Collection.class, new CustomCollectionEditor(Collection.class)); + this.defaultEditors.put(Set.class, new CustomCollectionEditor(Set.class)); + this.defaultEditors.put(SortedSet.class, new CustomCollectionEditor(SortedSet.class)); + this.defaultEditors.put(List.class, new CustomCollectionEditor(List.class)); + this.defaultEditors.put(SortedMap.class, new CustomMapEditor(SortedMap.class)); + + // Default editors for primitive arrays. + this.defaultEditors.put(byte[].class, new ByteArrayPropertyEditor()); + this.defaultEditors.put(char[].class, new CharArrayPropertyEditor()); + + // The JDK does not contain a default editor for char! + this.defaultEditors.put(char.class, new CharacterEditor(false)); + this.defaultEditors.put(Character.class, new CharacterEditor(true)); + + // Spring's CustomBooleanEditor accepts more flag values than the JDK's default editor. + this.defaultEditors.put(boolean.class, new CustomBooleanEditor(false)); + this.defaultEditors.put(Boolean.class, new CustomBooleanEditor(true)); + + // The JDK does not contain default editors for number wrapper types! + // Override JDK primitive number editors with our own CustomNumberEditor. + this.defaultEditors.put(byte.class, new CustomNumberEditor(Byte.class, false)); + this.defaultEditors.put(Byte.class, new CustomNumberEditor(Byte.class, true)); + this.defaultEditors.put(short.class, new CustomNumberEditor(Short.class, false)); + this.defaultEditors.put(Short.class, new CustomNumberEditor(Short.class, true)); + this.defaultEditors.put(int.class, new CustomNumberEditor(Integer.class, false)); + this.defaultEditors.put(Integer.class, new CustomNumberEditor(Integer.class, true)); + this.defaultEditors.put(long.class, new CustomNumberEditor(Long.class, false)); + this.defaultEditors.put(Long.class, new CustomNumberEditor(Long.class, true)); + this.defaultEditors.put(float.class, new CustomNumberEditor(Float.class, false)); + this.defaultEditors.put(Float.class, new CustomNumberEditor(Float.class, true)); + this.defaultEditors.put(double.class, new CustomNumberEditor(Double.class, false)); + this.defaultEditors.put(Double.class, new CustomNumberEditor(Double.class, true)); + this.defaultEditors.put(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, true)); + this.defaultEditors.put(BigInteger.class, new CustomNumberEditor(BigInteger.class, true)); + + // Only register config value editors if explicitly requested. + if (this.configValueEditorsActive) { + StringArrayPropertyEditor sae = new StringArrayPropertyEditor(); + this.defaultEditors.put(String[].class, sae); + this.defaultEditors.put(short[].class, sae); + this.defaultEditors.put(int[].class, sae); + this.defaultEditors.put(long[].class, sae); + } + } + + /** + * Copy the default editors registered in this instance to the given target registry. + * @param target the target registry to copy to + */ + protected void copyDefaultEditorsTo(PropertyEditorRegistrySupport target) { + target.defaultEditors = this.defaultEditors; + target.defaultEditorsActive = this.defaultEditorsActive; + target.configValueEditorsActive = this.configValueEditorsActive; + } + + + //--------------------------------------------------------------------- + // Management of custom editors + //--------------------------------------------------------------------- + + public void registerCustomEditor(Class requiredType, PropertyEditor propertyEditor) { + registerCustomEditor(requiredType, null, propertyEditor); + } + + public void registerCustomEditor(Class requiredType, String propertyPath, PropertyEditor propertyEditor) { + if (requiredType == null && propertyPath == null) { + throw new IllegalArgumentException("Either requiredType or propertyPath is required"); + } + if (this.customEditors == null) { + this.customEditors = new LinkedHashMap(16); + } + if (propertyPath != null) { + this.customEditors.put(propertyPath, new CustomEditorHolder(propertyEditor, requiredType)); + this.propertySpecificEditorsRegistered = true; + } + else { + this.customEditors.put(requiredType, propertyEditor); + this.customEditorCache = null; + } + } + + /** + * Register the given custom property editor for all properties + * of the given type, indicating that the given instance is a + * shared editor that might be used concurrently. + * @param requiredType the type of the property + * @param propertyEditor the shared editor to register + */ + public void registerSharedEditor(Class requiredType, PropertyEditor propertyEditor) { + registerCustomEditor(requiredType, null, propertyEditor); + if (this.sharedEditors == null) { + this.sharedEditors = new HashSet(); + } + this.sharedEditors.add(propertyEditor); + } + + /** + * Check whether the given editor instance is a shared editor, that is, + * whether the given editor instance might be used concurrently. + * @param propertyEditor the editor instance to check + * @return whether the editor is a shared instance + */ + public boolean isSharedEditor(PropertyEditor propertyEditor) { + return (this.sharedEditors != null && this.sharedEditors.contains(propertyEditor)); + } + + public PropertyEditor findCustomEditor(Class requiredType, String propertyPath) { + if (this.customEditors == null) { + return null; + } + Class requiredTypeToUse = requiredType; + if (propertyPath != null) { + if (this.propertySpecificEditorsRegistered) { + // Check property-specific editor first. + PropertyEditor editor = getCustomEditor(propertyPath, requiredType); + if (editor == null) { + List strippedPaths = new LinkedList(); + addStrippedPropertyPaths(strippedPaths, "", propertyPath); + for (Iterator it = strippedPaths.iterator(); it.hasNext() && editor == null;) { + String strippedPath = (String) it.next(); + editor = getCustomEditor(strippedPath, requiredType); + } + } + if (editor != null) { + return editor; + } + } + if (requiredType == null) { + requiredTypeToUse = getPropertyType(propertyPath); + } + } + // No property-specific editor -> check type-specific editor. + return getCustomEditor(requiredTypeToUse); + } + + /** + * Determine whether this registry contains a custom editor + * for the specified array/collection element. + * @param elementType the target type of the element + * (can be null if not known) + * @param propertyPath the property path (typically of the array/collection; + * can be null if not known) + * @return whether a matching custom editor has been found + */ + public boolean hasCustomEditorForElement(Class elementType, String propertyPath) { + if (this.customEditors == null) { + return false; + } + if (propertyPath != null && this.propertySpecificEditorsRegistered) { + for (Iterator it = this.customEditors.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + if (entry.getKey() instanceof String) { + String regPath = (String) entry.getKey(); + if (PropertyAccessorUtils.matchesProperty(regPath, propertyPath)) { + CustomEditorHolder editorHolder = (CustomEditorHolder) entry.getValue(); + if (editorHolder.getPropertyEditor(elementType) != null) { + return true; + } + } + } + } + } + // No property-specific editor -> check type-specific editor. + return (elementType != null && this.customEditors.containsKey(elementType)); + } + + /** + * Determine the property type for the given property path. + *

Called by {@link #findCustomEditor} if no required type has been specified, + * to be able to find a type-specific editor even if just given a property path. + *

The default implementation always returns null. + * BeanWrapperImpl overrides this with the standard getPropertyType + * method as defined by the BeanWrapper interface. + * @param propertyPath the property path to determine the type for + * @return the type of the property, or null if not determinable + * @see BeanWrapper#getPropertyType(String) + */ + protected Class getPropertyType(String propertyPath) { + return null; + } + + /** + * Get custom editor that has been registered for the given property. + * @param propertyName the property path to look for + * @param requiredType the type to look for + * @return the custom editor, or null if none specific for this property + */ + private PropertyEditor getCustomEditor(String propertyName, Class requiredType) { + CustomEditorHolder holder = (CustomEditorHolder) this.customEditors.get(propertyName); + return (holder != null ? holder.getPropertyEditor(requiredType) : null); + } + + /** + * Get custom editor for the given type. If no direct match found, + * try custom editor for superclass (which will in any case be able + * to render a value as String via getAsText). + * @param requiredType the type to look for + * @return the custom editor, or null if none found for this type + * @see java.beans.PropertyEditor#getAsText() + */ + private PropertyEditor getCustomEditor(Class requiredType) { + if (requiredType == null) { + return null; + } + // Check directly registered editor for type. + PropertyEditor editor = (PropertyEditor) this.customEditors.get(requiredType); + if (editor == null) { + // Check cached editor for type, registered for superclass or interface. + if (this.customEditorCache != null) { + editor = (PropertyEditor) this.customEditorCache.get(requiredType); + } + if (editor == null) { + // Find editor for superclass or interface. + for (Iterator it = this.customEditors.keySet().iterator(); it.hasNext() && editor == null;) { + Object key = it.next(); + if (key instanceof Class && ((Class) key).isAssignableFrom(requiredType)) { + editor = (PropertyEditor) this.customEditors.get(key); + // Cache editor for search type, to avoid the overhead + // of repeated assignable-from checks. + if (this.customEditorCache == null) { + this.customEditorCache = new HashMap(); + } + this.customEditorCache.put(requiredType, editor); + } + } + } + } + return editor; + } + + /** + * Guess the property type of the specified property from the registered + * custom editors (provided that they were registered for a specific type). + * @param propertyName the name of the property + * @return the property type, or null if not determinable + */ + protected Class guessPropertyTypeFromEditors(String propertyName) { + if (this.customEditors != null) { + CustomEditorHolder editorHolder = (CustomEditorHolder) this.customEditors.get(propertyName); + if (editorHolder == null) { + List strippedPaths = new LinkedList(); + addStrippedPropertyPaths(strippedPaths, "", propertyName); + for (Iterator it = strippedPaths.iterator(); it.hasNext() && editorHolder == null;) { + String strippedName = (String) it.next(); + editorHolder = (CustomEditorHolder) this.customEditors.get(strippedName); + } + } + if (editorHolder != null) { + return editorHolder.getRegisteredType(); + } + } + return null; + } + + /** + * Copy the custom editors registered in this instance to the given target registry. + * @param target the target registry to copy to + * @param nestedProperty the nested property path of the target registry, if any. + * If this is non-null, only editors registered for a path below this nested property + * will be copied. If this is null, all editors will be copied. + */ + protected void copyCustomEditorsTo(PropertyEditorRegistry target, String nestedProperty) { + String actualPropertyName = + (nestedProperty != null ? PropertyAccessorUtils.getPropertyName(nestedProperty) : null); + if (this.customEditors != null) { + for (Iterator it = this.customEditors.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + if (entry.getKey() instanceof Class) { + Class requiredType = (Class) entry.getKey(); + PropertyEditor editor = (PropertyEditor) entry.getValue(); + target.registerCustomEditor(requiredType, editor); + } + else if (entry.getKey() instanceof String) { + String editorPath = (String) entry.getKey(); + CustomEditorHolder editorHolder = (CustomEditorHolder) entry.getValue(); + if (nestedProperty != null) { + int pos = PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(editorPath); + if (pos != -1) { + String editorNestedProperty = editorPath.substring(0, pos); + String editorNestedPath = editorPath.substring(pos + 1); + if (editorNestedProperty.equals(nestedProperty) || editorNestedProperty.equals(actualPropertyName)) { + target.registerCustomEditor( + editorHolder.getRegisteredType(), editorNestedPath, editorHolder.getPropertyEditor()); + } + } + } + else { + target.registerCustomEditor( + editorHolder.getRegisteredType(), editorPath, editorHolder.getPropertyEditor()); + } + } + } + } + } + + + /** + * Add property paths with all variations of stripped keys and/or indexes. + * Invokes itself recursively with nested paths. + * @param strippedPaths the result list to add to + * @param nestedPath the current nested path + * @param propertyPath the property path to check for keys/indexes to strip + */ + private void addStrippedPropertyPaths(List strippedPaths, String nestedPath, String propertyPath) { + int startIndex = propertyPath.indexOf(PropertyAccessor.PROPERTY_KEY_PREFIX_CHAR); + if (startIndex != -1) { + int endIndex = propertyPath.indexOf(PropertyAccessor.PROPERTY_KEY_SUFFIX_CHAR); + if (endIndex != -1) { + String prefix = propertyPath.substring(0, startIndex); + String key = propertyPath.substring(startIndex, endIndex + 1); + String suffix = propertyPath.substring(endIndex + 1, propertyPath.length()); + // Strip the first key. + strippedPaths.add(nestedPath + prefix + suffix); + // Search for further keys to strip, with the first key stripped. + addStrippedPropertyPaths(strippedPaths, nestedPath + prefix, suffix); + // Search for further keys to strip, with the first key not stripped. + addStrippedPropertyPaths(strippedPaths, nestedPath + prefix + key, suffix); + } + } + } + + + /** + * Holder for a registered custom editor with property name. + * Keeps the PropertyEditor itself plus the type it was registered for. + */ + private static class CustomEditorHolder { + + private final PropertyEditor propertyEditor; + + private final Class registeredType; + + private CustomEditorHolder(PropertyEditor propertyEditor, Class registeredType) { + this.propertyEditor = propertyEditor; + this.registeredType = registeredType; + } + + private PropertyEditor getPropertyEditor() { + return this.propertyEditor; + } + + private Class getRegisteredType() { + return this.registeredType; + } + + private PropertyEditor getPropertyEditor(Class requiredType) { + // Special case: If no required type specified, which usually only happens for + // Collection elements, or required type is not assignable to registered type, + // which usually only happens for generic properties of type Object - + // then return PropertyEditor if not registered for Collection or array type. + // (If not registered for Collection or array, it is assumed to be intended + // for elements.) + if (this.registeredType == null || + (requiredType != null && + (ClassUtils.isAssignable(this.registeredType, requiredType) || + ClassUtils.isAssignable(requiredType, this.registeredType))) || + (requiredType == null && + (!Collection.class.isAssignableFrom(this.registeredType) && !this.registeredType.isArray()))) { + return this.propertyEditor; + } + else { + return null; + } + } + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/PropertyMatches.java b/org.springframework.beans/src/main/java/org/springframework/beans/PropertyMatches.java new file mode 100644 index 0000000000..1bda68f195 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/PropertyMatches.java @@ -0,0 +1,186 @@ +/* + * Copyright 2002-2006 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; + +import java.beans.PropertyDescriptor; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * Helper class for calculating bean property matches, according to. + * Used by BeanWrapperImpl to suggest alternatives for an invalid property name. + * + * @author Alef Arendsen + * @author Arjen Poutsma + * @author Juergen Hoeller + * @since 2.0 + * @see #forProperty(String, Class) + */ +final class PropertyMatches { + + //--------------------------------------------------------------------- + // Static section + //--------------------------------------------------------------------- + + /** Default maximum property distance: 2 */ + public static final int DEFAULT_MAX_DISTANCE = 2; + + + /** + * Create PropertyMatches for the given bean property. + * @param propertyName the name of the property to find possible matches for + * @param beanClass the bean class to search for matches + */ + public static PropertyMatches forProperty(String propertyName, Class beanClass) { + return forProperty(propertyName, beanClass, DEFAULT_MAX_DISTANCE); + } + + /** + * Create PropertyMatches for the given bean property. + * @param propertyName the name of the property to find possible matches for + * @param beanClass the bean class to search for matches + * @param maxDistance the maximum property distance allowed for matches + */ + public static PropertyMatches forProperty(String propertyName, Class beanClass, int maxDistance) { + return new PropertyMatches(propertyName, beanClass, maxDistance); + } + + + //--------------------------------------------------------------------- + // Instance section + //--------------------------------------------------------------------- + + private final String propertyName; + + private String[] possibleMatches; + + + /** + * Create a new PropertyMatches instance for the given property. + */ + private PropertyMatches(String propertyName, Class beanClass, int maxDistance) { + this.propertyName = propertyName; + this.possibleMatches = calculateMatches(BeanUtils.getPropertyDescriptors(beanClass), maxDistance); + } + + + /** + * Return the calculated possible matches. + */ + public String[] getPossibleMatches() { + return possibleMatches; + } + + /** + * Build an error message for the given invalid property name, + * indicating the possible property matches. + */ + public String buildErrorMessage() { + StringBuffer buf = new StringBuffer(); + buf.append("Bean property '"); + buf.append(this.propertyName); + buf.append("' is not writable or has an invalid setter method. "); + + if (ObjectUtils.isEmpty(this.possibleMatches)) { + buf.append("Does the parameter type of the setter match the return type of the getter?"); + } + else { + buf.append("Did you mean "); + for (int i = 0; i < this.possibleMatches.length; i++) { + buf.append('\''); + buf.append(this.possibleMatches[i]); + if (i < this.possibleMatches.length - 2) { + buf.append("', "); + } + else if (i == this.possibleMatches.length - 2){ + buf.append("', or "); + } + } + buf.append("'?"); + } + return buf.toString(); + } + + + /** + * Generate possible property alternatives for the given property and + * class. Internally uses the getStringDistance method, which + * in turn uses the Levenshtein algorithm to determine the distance between + * two Strings. + * @param propertyDescriptors the JavaBeans property descriptors to search + * @param maxDistance the maximum distance to accept + */ + private String[] calculateMatches(PropertyDescriptor[] propertyDescriptors, int maxDistance) { + List candidates = new ArrayList(); + for (int i = 0; i < propertyDescriptors.length; i++) { + if (propertyDescriptors[i].getWriteMethod() != null) { + String possibleAlternative = propertyDescriptors[i].getName(); + if (calculateStringDistance(this.propertyName, possibleAlternative) <= maxDistance) { + candidates.add(possibleAlternative); + } + } + } + Collections.sort(candidates); + return StringUtils.toStringArray(candidates); + } + + /** + * Calculate the distance between the given two Strings + * according to the Levenshtein algorithm. + * @param s1 the first String + * @param s2 the second String + * @return the distance value + */ + private int calculateStringDistance(String s1, String s2) { + if (s1.length() == 0) { + return s2.length(); + } + if (s2.length() == 0) { + return s1.length(); + } + int d[][] = new int[s1.length() + 1][s2.length() + 1]; + + for (int i = 0; i <= s1.length(); i++) { + d[i][0] = i; + } + for (int j = 0; j <= s2.length(); j++) { + d[0][j] = j; + } + + for (int i = 1; i <= s1.length(); i++) { + char s_i = s1.charAt(i - 1); + for (int j = 1; j <= s2.length(); j++) { + int cost; + char t_j = s2.charAt(j - 1); + if (s_i == t_j) { + cost = 0; + } else { + cost = 1; + } + d[i][j] = Math.min(Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1), + d[i - 1][j - 1] + cost); + } + } + + return d[s1.length()][s2.length()]; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/PropertyValue.java b/org.springframework.beans/src/main/java/org/springframework/beans/PropertyValue.java new file mode 100644 index 0000000000..f421171039 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/PropertyValue.java @@ -0,0 +1,184 @@ +/* + * Copyright 2002-2007 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; + +import java.beans.PropertyDescriptor; +import java.io.Serializable; + +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * Object to hold information and value for an individual bean property. + * Using an object here, rather than just storing all properties in + * a map keyed by property name, allows for more flexibility, and the + * ability to handle indexed properties etc in an optimized way. + * + *

Note that the value doesn't need to be the final required type: + * A {@link BeanWrapper} implementation should handle any necessary conversion, + * as this object doesn't know anything about the objects it will be applied to. + * + * @author Rod Johnson + * @author Rob Harrop + * @author Juergen Hoeller + * @since 13 May 2001 + * @see PropertyValues + * @see BeanWrapper + */ +public class PropertyValue extends BeanMetadataAttributeAccessor implements Serializable { + + private final String name; + + private final Object value; + + private Object source; + + private boolean converted = false; + + private Object convertedValue; + + /** Package-visible field that indicates whether conversion is necessary */ + volatile Boolean conversionNecessary; + + /** Package-visible field for caching the resolved property path tokens */ + volatile Object resolvedTokens; + + /** Package-visible field for caching the resolved PropertyDescriptor */ + volatile PropertyDescriptor resolvedDescriptor; + + + /** + * Create a new PropertyValue instance. + * @param name the name of the property (never null) + * @param value the value of the property (possibly before type conversion) + */ + public PropertyValue(String name, Object value) { + this.name = name; + this.value = value; + } + + /** + * Copy constructor. + * @param original the PropertyValue to copy (never null) + */ + public PropertyValue(PropertyValue original) { + Assert.notNull(original, "Original must not be null"); + this.name = original.getName(); + this.value = original.getValue(); + this.source = original.getSource(); + this.conversionNecessary = original.conversionNecessary; + this.resolvedTokens = original.resolvedTokens; + this.resolvedDescriptor = original.resolvedDescriptor; + copyAttributesFrom(original); + } + + /** + * Constructor that exposes a new value for an original value holder. + * The original holder will be exposed as source of the new holder. + * @param original the PropertyValue to link to (never null) + * @param newValue the new value to apply + */ + public PropertyValue(PropertyValue original, Object newValue) { + Assert.notNull(original, "Original must not be null"); + this.name = original.getName(); + this.value = newValue; + this.source = original; + this.conversionNecessary = original.conversionNecessary; + this.resolvedTokens = original.resolvedTokens; + this.resolvedDescriptor = original.resolvedDescriptor; + copyAttributesFrom(original); + } + + + /** + * Return the name of the property. + */ + public String getName() { + return this.name; + } + + /** + * Return the value of the property. + *

Note that type conversion will not have occurred here. + * It is the responsibility of the BeanWrapper implementation to + * perform type conversion. + */ + public Object getValue() { + return this.value; + } + + /** + * Return the original PropertyValue instance for this value holder. + * @return the original PropertyValue (either a source of this + * value holder or this value holder itself). + */ + public PropertyValue getOriginalPropertyValue() { + PropertyValue original = this; + while (original.source instanceof PropertyValue && original.source != original) { + original = (PropertyValue) original.source; + } + return original; + } + + /** + * Return whether this holder contains a converted value already (true), + * or whether the value still needs to be converted (false). + */ + public synchronized boolean isConverted() { + return this.converted; + } + + /** + * Set the converted value of the constructor argument, + * after processed type conversion. + */ + public synchronized void setConvertedValue(Object value) { + this.converted = true; + this.convertedValue = value; + } + + /** + * Return the converted value of the constructor argument, + * after processed type conversion. + */ + public synchronized Object getConvertedValue() { + return this.convertedValue; + } + + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof PropertyValue)) { + return false; + } + PropertyValue otherPv = (PropertyValue) other; + return (this.name.equals(otherPv.name) && + ObjectUtils.nullSafeEquals(this.value, otherPv.value) && + ObjectUtils.nullSafeEquals(this.source, otherPv.source)); + } + + public int hashCode() { + return this.name.hashCode() * 29 + ObjectUtils.nullSafeHashCode(this.value); + } + + public String toString() { + return "bean property '" + this.name + "'"; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/PropertyValues.java b/org.springframework.beans/src/main/java/org/springframework/beans/PropertyValues.java new file mode 100644 index 0000000000..2a5657a35f --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/PropertyValues.java @@ -0,0 +1,64 @@ +/* + * Copyright 2002-2008 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; + +/** + * Holder containing one or more {@link PropertyValue} objects, + * typically comprising one update for a specific target bean. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 13 May 2001 + * @see PropertyValue + */ +public interface PropertyValues { + + /** + * Return an array of the PropertyValue objects held in this object. + */ + PropertyValue[] getPropertyValues(); + + /** + * Return the property value with the given name, if any. + * @param propertyName the name to search for + * @return the property value, or null + */ + PropertyValue getPropertyValue(String propertyName); + + /** + * Is there a property value (or other processing entry) for this property? + * @param propertyName the name of the property we're interested in + * @return whether there is a property value for this property + */ + boolean contains(String propertyName); + + /** + * Does this holder not contain any PropertyValue objects at all? + */ + boolean isEmpty(); + + /** + * Return the changes since the previous PropertyValues. + * Subclasses should also override equals. + * @param old old property values + * @return PropertyValues updated or new properties. + * Return empty PropertyValues if there are no changes. + * @see java.lang.Object#equals + */ + PropertyValues changesSince(PropertyValues old); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/PropertyValuesEditor.java b/org.springframework.beans/src/main/java/org/springframework/beans/PropertyValuesEditor.java new file mode 100644 index 0000000000..63b79b8cb7 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/PropertyValuesEditor.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2007 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; + +import java.beans.PropertyEditorSupport; +import java.util.Properties; + +import org.springframework.beans.propertyeditors.PropertiesEditor; + +/** + * {@link java.beans.PropertyEditor Editor} for a {@link PropertyValues} object. + * + *

The required format is defined in the {@link java.util.Properties} + * documentation. Each property must be on a new line. + * + *

The present implementation relies on a + * {@link org.springframework.beans.propertyeditors.PropertiesEditor} + * underneath. + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +public class PropertyValuesEditor extends PropertyEditorSupport { + + private final PropertiesEditor propertiesEditor = new PropertiesEditor(); + + public void setAsText(String text) throws IllegalArgumentException { + this.propertiesEditor.setAsText(text); + Properties props = (Properties) this.propertiesEditor.getValue(); + setValue(new MutablePropertyValues(props)); + } + +} + diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/SimpleTypeConverter.java b/org.springframework.beans/src/main/java/org/springframework/beans/SimpleTypeConverter.java new file mode 100644 index 0000000000..b1143e9d2a --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/SimpleTypeConverter.java @@ -0,0 +1,54 @@ +/* + * Copyright 2002-2006 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; + +import org.springframework.core.MethodParameter; + +/** + * Simple implementation of the TypeConverter interface that does not operate + * on any specific target object. This is an alternative to using a full-blown + * BeanWrapperImpl instance for arbitrary type conversion needs. + * + * @author Juergen Hoeller + * @since 2.0 + * @see BeanWrapperImpl + */ +public class SimpleTypeConverter extends PropertyEditorRegistrySupport implements TypeConverter { + + private final TypeConverterDelegate typeConverterDelegate = new TypeConverterDelegate(this); + + + public SimpleTypeConverter() { + registerDefaultEditors(); + } + + + public Object convertIfNecessary(Object value, Class requiredType) throws TypeMismatchException { + return convertIfNecessary(value, requiredType, null); + } + + public Object convertIfNecessary( + Object value, Class requiredType, MethodParameter methodParam) throws TypeMismatchException { + try { + return this.typeConverterDelegate.convertIfNecessary(value, requiredType, methodParam); + } + catch (IllegalArgumentException ex) { + throw new TypeMismatchException(value, requiredType, ex); + } + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/TypeConverter.java b/org.springframework.beans/src/main/java/org/springframework/beans/TypeConverter.java new file mode 100644 index 0000000000..1a38dde532 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/TypeConverter.java @@ -0,0 +1,68 @@ +/* + * Copyright 2002-2006 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; + +import org.springframework.core.MethodParameter; + +/** + * Interface that defines type conversion methods. Typically (but not necessarily) + * implemented in conjunction with the PropertyEditorRegistry interface. + * + * @author Juergen Hoeller + * @since 2.0 + * @see PropertyEditorRegistry + * @see SimpleTypeConverter + * @see BeanWrapperImpl + */ +public interface TypeConverter { + + /** + * Convert the value to the required type (if necessary from a String). + *

Conversions from String to any type will typically use the setAsText + * method of the PropertyEditor class. Note that a PropertyEditor must be registered + * for the given class for this to work; this is a standard JavaBeans API. + * A number of PropertyEditors are automatically registered. + * @param value the value to convert + * @param requiredType the type we must convert to + * (or null if not known, for example in case of a collection element) + * @return the new value, possibly the result of type conversion + * @throws TypeMismatchException if type conversion failed + * @see java.beans.PropertyEditor#setAsText(String) + * @see java.beans.PropertyEditor#getValue() + */ + Object convertIfNecessary(Object value, Class requiredType) throws TypeMismatchException; + + /** + * Convert the value to the required type (if necessary from a String). + *

Conversions from String to any type will typically use the setAsText + * method of the PropertyEditor class. Note that a PropertyEditor must be registered + * for the given class for this to work; this is a standard JavaBeans API. + * A number of PropertyEditors are automatically registered. + * @param value the value to convert + * @param requiredType the type we must convert to + * (or null if not known, for example in case of a collection element) + * @param methodParam the method parameter that is the target of the conversion + * (for analysis of generic types; may be null) + * @return the new value, possibly the result of type conversion + * @throws TypeMismatchException if type conversion failed + * @see java.beans.PropertyEditor#setAsText(String) + * @see java.beans.PropertyEditor#getValue() + */ + Object convertIfNecessary(Object value, Class requiredType, MethodParameter methodParam) + throws TypeMismatchException; + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java b/org.springframework.beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java new file mode 100644 index 0000000000..7d81091cd7 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java @@ -0,0 +1,542 @@ +/* + * Copyright 2002-2008 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; + +import java.beans.PropertyDescriptor; +import java.beans.PropertyEditor; +import java.beans.PropertyEditorManager; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +import java.util.WeakHashMap; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.core.CollectionFactory; +import org.springframework.core.GenericCollectionTypeResolver; +import org.springframework.core.JdkVersion; +import org.springframework.core.MethodParameter; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; + +/** + * Internal helper class for converting property values to target types. + * + *

Works on a given {@link PropertyEditorRegistrySupport} instance. + * Used as a delegate by {@link BeanWrapperImpl} and {@link SimpleTypeConverter}. + * + * @author Juergen Hoeller + * @author Rob Harrop + * @since 2.0 + * @see BeanWrapperImpl + * @see SimpleTypeConverter + */ +class TypeConverterDelegate { + + private static final Log logger = LogFactory.getLog(TypeConverterDelegate.class); + + private static final Map unknownEditorTypes = Collections.synchronizedMap(new WeakHashMap()); + + private final PropertyEditorRegistrySupport propertyEditorRegistry; + + private final Object targetObject; + + + /** + * Create a new TypeConverterDelegate for the given editor registry. + * @param propertyEditorRegistry the editor registry to use + */ + public TypeConverterDelegate(PropertyEditorRegistrySupport propertyEditorRegistry) { + this(propertyEditorRegistry, null); + } + + /** + * Create a new TypeConverterDelegate for the given editor registry and bean instance. + * @param propertyEditorRegistry the editor registry to use + * @param targetObject the target object to work on (as context that can be passed to editors) + */ + public TypeConverterDelegate(PropertyEditorRegistrySupport propertyEditorRegistry, Object targetObject) { + this.propertyEditorRegistry = propertyEditorRegistry; + this.targetObject = targetObject; + } + + + /** + * Convert the value to the specified required type. + * @param newValue the proposed new value + * @param requiredType the type we must convert to + * (or null if not known, for example in case of a collection element) + * @return the new value, possibly the result of type conversion + * @throws IllegalArgumentException if type conversion failed + */ + public Object convertIfNecessary(Object newValue, Class requiredType) throws IllegalArgumentException { + return convertIfNecessary(null, null, newValue, requiredType, null, null); + } + + /** + * Convert the value to the specified required type. + * @param newValue the proposed new value + * @param requiredType the type we must convert to + * (or null if not known, for example in case of a collection element) + * @param methodParam the method parameter that is the target of the conversion + * (may be null) + * @return the new value, possibly the result of type conversion + * @throws IllegalArgumentException if type conversion failed + */ + public Object convertIfNecessary(Object newValue, Class requiredType, MethodParameter methodParam) + throws IllegalArgumentException { + + return convertIfNecessary(null, null, newValue, requiredType, null, methodParam); + } + + /** + * Convert the value to the required type for the specified property. + * @param propertyName name of the property + * @param oldValue the previous value, if available (may be null) + * @param newValue the proposed new value + * @param requiredType the type we must convert to + * (or null if not known, for example in case of a collection element) + * @return the new value, possibly the result of type conversion + * @throws IllegalArgumentException if type conversion failed + */ + public Object convertIfNecessary( + String propertyName, Object oldValue, Object newValue, Class requiredType) + throws IllegalArgumentException { + + return convertIfNecessary(propertyName, oldValue, newValue, requiredType, null, null); + } + + /** + * Convert the value to the required type for the specified property. + * @param oldValue the previous value, if available (may be null) + * @param newValue the proposed new value + * @param descriptor the JavaBeans descriptor for the property + * @return the new value, possibly the result of type conversion + * @throws IllegalArgumentException if type conversion failed + */ + public Object convertIfNecessary(Object oldValue, Object newValue, PropertyDescriptor descriptor) + throws IllegalArgumentException { + + return convertIfNecessary( + descriptor.getName(), oldValue, newValue, descriptor.getPropertyType(), descriptor, + BeanUtils.getWriteMethodParameter(descriptor)); + } + + + /** + * Convert the value to the required type (if necessary from a String), + * for the specified property. + * @param propertyName name of the property + * @param oldValue the previous value, if available (may be null) + * @param newValue the proposed new value + * @param requiredType the type we must convert to + * (or null if not known, for example in case of a collection element) + * @param descriptor the JavaBeans descriptor for the property + * @param methodParam the method parameter that is the target of the conversion + * (may be null) + * @return the new value, possibly the result of type conversion + * @throws IllegalArgumentException if type conversion failed + */ + protected Object convertIfNecessary( + String propertyName, Object oldValue, Object newValue, Class requiredType, + PropertyDescriptor descriptor, MethodParameter methodParam) + throws IllegalArgumentException { + + Object convertedValue = newValue; + + // Custom editor for this type? + PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName); + + // Value not of required type? + if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) { + if (editor == null) { + editor = findDefaultEditor(requiredType, descriptor); + } + convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor); + } + + if (requiredType != null) { + // Try to apply some standard type conversion rules if appropriate. + + if (convertedValue != null) { + if (String.class.equals(requiredType) && ClassUtils.isPrimitiveOrWrapper(convertedValue.getClass())) { + // We can stringify any primitive value... + return convertedValue.toString(); + } + else if (requiredType.isArray()) { + // Array required -> apply appropriate conversion of elements. + return convertToTypedArray(convertedValue, propertyName, requiredType.getComponentType()); + } + else if (convertedValue instanceof Collection && CollectionFactory.isApproximableCollectionType(requiredType)) { + // Convert elements to target type, if determined. + convertedValue = convertToTypedCollection((Collection) convertedValue, propertyName, methodParam); + } + else if (convertedValue instanceof Map && CollectionFactory.isApproximableMapType(requiredType)) { + // Convert keys and values to respective target type, if determined. + convertedValue = convertToTypedMap((Map) convertedValue, propertyName, methodParam); + } + else if (convertedValue instanceof String && !requiredType.isInstance(convertedValue)) { + String strValue = ((String) convertedValue).trim(); + if (JdkVersion.isAtLeastJava15() && requiredType.isEnum() && "".equals(strValue)) { + // It's an empty enum identifier: reset the enum value to null. + return null; + } + // Try field lookup as fallback: for JDK 1.5 enum or custom enum + // with values defined as static fields. Resulting value still needs + // to be checked, hence we don't return it right away. + try { + Field enumField = requiredType.getField(strValue); + convertedValue = enumField.get(null); + } + catch (Throwable ex) { + if (logger.isTraceEnabled()) { + logger.trace("Field [" + convertedValue + "] isn't an enum value", ex); + } + } + } + } + + if (!ClassUtils.isAssignableValue(requiredType, convertedValue)) { + // Definitely doesn't match: throw IllegalArgumentException. + StringBuffer msg = new StringBuffer(); + msg.append("Cannot convert value of type [").append(ClassUtils.getDescriptiveType(newValue)); + msg.append("] to required type [").append(ClassUtils.getQualifiedName(requiredType)).append("]"); + if (propertyName != null) { + msg.append(" for property '" + propertyName + "'"); + } + if (editor != null) { + msg.append(": PropertyEditor [" + editor.getClass().getName() + "] returned inappropriate value"); + } + else { + msg.append(": no matching editors or conversion strategy found"); + } + throw new IllegalArgumentException(msg.toString()); + } + } + + return convertedValue; + } + + /** + * Find a default editor for the given type. + * @param requiredType the type to find an editor for + * @param descriptor the JavaBeans descriptor for the property + * @return the corresponding editor, or null if none + */ + protected PropertyEditor findDefaultEditor(Class requiredType, PropertyDescriptor descriptor) { + PropertyEditor editor = null; + if (descriptor != null) { + if (JdkVersion.isAtLeastJava15()) { + editor = descriptor.createPropertyEditor(this.targetObject); + } + else { + Class editorClass = descriptor.getPropertyEditorClass(); + if (editorClass != null) { + editor = (PropertyEditor) BeanUtils.instantiateClass(editorClass); + } + } + } + if (editor == null && requiredType != null) { + // No custom editor -> check BeanWrapperImpl's default editors. + editor = (PropertyEditor) this.propertyEditorRegistry.getDefaultEditor(requiredType); + if (editor == null && !String.class.equals(requiredType)) { + // No BeanWrapper default editor -> check standard JavaBean editor. + editor = BeanUtils.findEditorByConvention(requiredType); + if (editor == null && !unknownEditorTypes.containsKey(requiredType)) { + // Deprecated global PropertyEditorManager fallback... + editor = PropertyEditorManager.findEditor(requiredType); + if (editor == null) { + // Regular case as of Spring 2.5 + unknownEditorTypes.put(requiredType, Boolean.TRUE); + } + else { + logger.warn("PropertyEditor [" + editor.getClass().getName() + + "] found through deprecated global PropertyEditorManager fallback - " + + "consider using a more isolated form of registration, e.g. on the BeanWrapper/BeanFactory!"); + } + } + } + } + return editor; + } + + /** + * Convert the value to the required type (if necessary from a String), + * using the given property editor. + * @param oldValue the previous value, if available (may be null) + * @param newValue the proposed new value + * @param requiredType the type we must convert to + * (or null if not known, for example in case of a collection element) + * @param editor the PropertyEditor to use + * @return the new value, possibly the result of type conversion + * @throws IllegalArgumentException if type conversion failed + */ + protected Object doConvertValue(Object oldValue, Object newValue, Class requiredType, PropertyEditor editor) { + Object convertedValue = newValue; + boolean sharedEditor = false; + + if (editor != null) { + sharedEditor = this.propertyEditorRegistry.isSharedEditor(editor); + } + + if (editor != null && !(convertedValue instanceof String)) { + // Not a String -> use PropertyEditor's setValue. + // With standard PropertyEditors, this will return the very same object; + // we just want to allow special PropertyEditors to override setValue + // for type conversion from non-String values to the required type. + try { + Object newConvertedValue = null; + if (sharedEditor) { + // Synchronized access to shared editor instance. + synchronized (editor) { + editor.setValue(convertedValue); + newConvertedValue = editor.getValue(); + } + } + else { + // Unsynchronized access to non-shared editor instance. + editor.setValue(convertedValue); + newConvertedValue = editor.getValue(); + } + if (newConvertedValue != convertedValue) { + convertedValue = newConvertedValue; + // Reset PropertyEditor: It already did a proper conversion. + // Don't use it again for a setAsText call. + editor = null; + } + } + catch (Exception ex) { + if (logger.isDebugEnabled()) { + logger.debug("PropertyEditor [" + editor.getClass().getName() + "] does not support setValue call", ex); + } + // Swallow and proceed. + } + } + + if (requiredType != null && !requiredType.isArray() && convertedValue instanceof String[]) { + // Convert String array to a comma-separated String. + // Only applies if no PropertyEditor converted the String array before. + // The CSV String will be passed into a PropertyEditor's setAsText method, if any. + if (logger.isTraceEnabled()) { + logger.trace("Converting String array to comma-delimited String [" + convertedValue + "]"); + } + convertedValue = StringUtils.arrayToCommaDelimitedString((String[]) convertedValue); + } + + if (editor != null && convertedValue instanceof String) { + // Use PropertyEditor's setAsText in case of a String value. + if (logger.isTraceEnabled()) { + logger.trace("Converting String to [" + requiredType + "] using property editor [" + editor + "]"); + } + String newTextValue = (String) convertedValue; + if (sharedEditor) { + // Synchronized access to shared editor instance. + synchronized (editor) { + return doConvertTextValue(oldValue, newTextValue, editor); + } + } + else { + // Unsynchronized access to non-shared editor instance. + return doConvertTextValue(oldValue, newTextValue, editor); + } + } + + return convertedValue; + } + + /** + * Convert the given text value using the given property editor. + * @param oldValue the previous value, if available (may be null) + * @param newTextValue the proposed text value + * @param editor the PropertyEditor to use + * @return the converted value + */ + protected Object doConvertTextValue(Object oldValue, String newTextValue, PropertyEditor editor) { + try { + editor.setValue(oldValue); + } + catch (Exception ex) { + if (logger.isDebugEnabled()) { + logger.debug("PropertyEditor [" + editor.getClass().getName() + "] does not support setValue call", ex); + } + // Swallow and proceed. + } + editor.setAsText(newTextValue); + return editor.getValue(); + } + + protected Object convertToTypedArray(Object input, String propertyName, Class componentType) { + if (input instanceof Collection) { + // Convert Collection elements to array elements. + Collection coll = (Collection) input; + Object result = Array.newInstance(componentType, coll.size()); + int i = 0; + for (Iterator it = coll.iterator(); it.hasNext(); i++) { + Object value = convertIfNecessary( + buildIndexedPropertyName(propertyName, i), null, it.next(), componentType); + Array.set(result, i, value); + } + return result; + } + else if (input.getClass().isArray()) { + // Convert array elements, if necessary. + if (componentType.equals(input.getClass().getComponentType()) && + !this.propertyEditorRegistry.hasCustomEditorForElement(componentType, propertyName)) { + return input; + } + int arrayLength = Array.getLength(input); + Object result = Array.newInstance(componentType, arrayLength); + for (int i = 0; i < arrayLength; i++) { + Object value = convertIfNecessary( + buildIndexedPropertyName(propertyName, i), null, Array.get(input, i), componentType); + Array.set(result, i, value); + } + return result; + } + else { + // A plain value: convert it to an array with a single component. + Object result = Array.newInstance(componentType, 1); + Object value = convertIfNecessary( + buildIndexedPropertyName(propertyName, 0), null, input, componentType); + Array.set(result, 0, value); + return result; + } + } + + protected Collection convertToTypedCollection( + Collection original, String propertyName, MethodParameter methodParam) { + + Class elementType = null; + if (methodParam != null && JdkVersion.isAtLeastJava15()) { + elementType = GenericCollectionTypeResolver.getCollectionParameterType(methodParam); + } + if (elementType == null && + !this.propertyEditorRegistry.hasCustomEditorForElement(null, propertyName)) { + return original; + } + + Collection convertedCopy = null; + Iterator it = null; + try { + it = original.iterator(); + if (it == null) { + if (logger.isDebugEnabled()) { + logger.debug("Collection of type [" + original.getClass().getName() + + "] returned null Iterator - injecting original Collection as-is"); + } + return original; + } + convertedCopy = CollectionFactory.createApproximateCollection(original, original.size()); + } + catch (Throwable ex) { + if (logger.isDebugEnabled()) { + logger.debug("Cannot access Collection of type [" + original.getClass().getName() + + "] - injecting original Collection as-is", ex); + } + return original; + } + boolean actuallyConverted = false; + int i = 0; + for (; it.hasNext(); i++) { + Object element = it.next(); + String indexedPropertyName = buildIndexedPropertyName(propertyName, i); + if (methodParam != null) { + methodParam.increaseNestingLevel(); + } + Object convertedElement = + convertIfNecessary(indexedPropertyName, null, element, elementType, null, methodParam); + if (methodParam != null) { + methodParam.decreaseNestingLevel(); + } + convertedCopy.add(convertedElement); + actuallyConverted = actuallyConverted || (element != convertedElement); + } + return (actuallyConverted ? convertedCopy : original); + } + + protected Map convertToTypedMap(Map original, String propertyName, MethodParameter methodParam) { + Class keyType = null; + Class valueType = null; + if (methodParam != null && JdkVersion.isAtLeastJava15()) { + keyType = GenericCollectionTypeResolver.getMapKeyParameterType(methodParam); + valueType = GenericCollectionTypeResolver.getMapValueParameterType(methodParam); + } + if (keyType == null && valueType == null && + !this.propertyEditorRegistry.hasCustomEditorForElement(null, propertyName)) { + return original; + } + + Map convertedCopy = null; + Iterator it = null; + try { + it = original.entrySet().iterator(); + if (it == null) { + if (logger.isDebugEnabled()) { + logger.debug("Map of type [" + original.getClass().getName() + + "] returned null Iterator - injecting original Map as-is"); + } + } + convertedCopy = CollectionFactory.createApproximateMap(original, original.size()); + } + catch (Throwable ex) { + if (logger.isDebugEnabled()) { + logger.debug("Cannot access Map of type [" + original.getClass().getName() + + "] - injecting original Map as-is", ex); + } + return original; + } + boolean actuallyConverted = false; + while (it.hasNext()) { + Map.Entry entry = (Map.Entry) it.next(); + Object key = entry.getKey(); + Object value = entry.getValue(); + String keyedPropertyName = buildKeyedPropertyName(propertyName, key); + if (methodParam != null) { + methodParam.increaseNestingLevel(); + methodParam.setTypeIndexForCurrentLevel(0); + } + Object convertedKey = convertIfNecessary(keyedPropertyName, null, key, keyType, null, methodParam); + if (methodParam != null) { + methodParam.setTypeIndexForCurrentLevel(1); + } + Object convertedValue = convertIfNecessary(keyedPropertyName, null, value, valueType, null, methodParam); + if (methodParam != null) { + methodParam.decreaseNestingLevel(); + } + convertedCopy.put(convertedKey, convertedValue); + actuallyConverted = actuallyConverted || (key != convertedKey) || (value != convertedValue); + } + return (actuallyConverted ? convertedCopy : original); + } + + private String buildIndexedPropertyName(String propertyName, int index) { + return (propertyName != null ? + propertyName + PropertyAccessor.PROPERTY_KEY_PREFIX + index + PropertyAccessor.PROPERTY_KEY_SUFFIX : + null); + } + + private String buildKeyedPropertyName(String propertyName, Object key) { + return (propertyName != null ? + propertyName + PropertyAccessor.PROPERTY_KEY_PREFIX + key + PropertyAccessor.PROPERTY_KEY_SUFFIX : + null); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/TypeMismatchException.java b/org.springframework.beans/src/main/java/org/springframework/beans/TypeMismatchException.java new file mode 100644 index 0000000000..2ecfaa7bf3 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/TypeMismatchException.java @@ -0,0 +1,112 @@ +/* + * Copyright 2002-2008 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; + +import java.beans.PropertyChangeEvent; + +import org.springframework.util.ClassUtils; + +/** + * Exception thrown on a type mismatch when trying to set a bean property. + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +public class TypeMismatchException extends PropertyAccessException { + + /** + * Error code that a type mismatch error will be registered with. + */ + public static final String ERROR_CODE = "typeMismatch"; + + + private transient Object value; + + private Class requiredType; + + + /** + * Create a new TypeMismatchException. + * @param propertyChangeEvent the PropertyChangeEvent that resulted in the problem + * @param requiredType the required target type + */ + public TypeMismatchException(PropertyChangeEvent propertyChangeEvent, Class requiredType) { + this(propertyChangeEvent, requiredType, null); + } + + /** + * Create a new TypeMismatchException. + * @param propertyChangeEvent the PropertyChangeEvent that resulted in the problem + * @param requiredType the required target type (or null if not known) + * @param cause the root cause (may be null) + */ + public TypeMismatchException(PropertyChangeEvent propertyChangeEvent, Class requiredType, Throwable cause) { + super(propertyChangeEvent, + "Failed to convert property value of type [" + + ClassUtils.getDescriptiveType(propertyChangeEvent.getNewValue()) + "]" + + (requiredType != null ? + " to required type [" + ClassUtils.getQualifiedName(requiredType) + "]" : "") + + (propertyChangeEvent.getPropertyName() != null ? + " for property '" + propertyChangeEvent.getPropertyName() + "'" : ""), + cause); + this.value = propertyChangeEvent.getNewValue(); + this.requiredType = requiredType; + } + + /** + * Create a new TypeMismatchException without PropertyChangeEvent. + * @param value the offending value that couldn't be converted (may be null) + * @param requiredType the required target type (or null if not known) + */ + public TypeMismatchException(Object value, Class requiredType) { + this(value, requiredType, null); + } + + /** + * Create a new TypeMismatchException without PropertyChangeEvent. + * @param value the offending value that couldn't be converted (may be null) + * @param requiredType the required target type (or null if not known) + * @param cause the root cause (may be null) + */ + public TypeMismatchException(Object value, Class requiredType, Throwable cause) { + super("Failed to convert value of type [" + ClassUtils.getDescriptiveType(value) + "]" + + (requiredType != null ? " to required type [" + ClassUtils.getQualifiedName(requiredType) + "]" : ""), + cause); + this.value = value; + this.requiredType = requiredType; + } + + + /** + * Return the offending value (may be null) + */ + public Object getValue() { + return this.value; + } + + /** + * Return the required target type, if any. + */ + public Class getRequiredType() { + return this.requiredType; + } + + public String getErrorCode() { + return ERROR_CODE; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/annotation/AnnotationBeanUtils.java b/org.springframework.beans/src/main/java/org/springframework/beans/annotation/AnnotationBeanUtils.java new file mode 100644 index 0000000000..ac337034c9 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/annotation/AnnotationBeanUtils.java @@ -0,0 +1,56 @@ +/* + * Copyright 2002-2008 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.annotation; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.PropertyAccessorFactory; +import org.springframework.util.ReflectionUtils; + +/** + * General utility methods for working with annotations in JavaBeans style. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + */ +public abstract class AnnotationBeanUtils { + + /** + * Copy the properties of the supplied {@link Annotation} to the supplied target bean. + * Any properties defined in excludedProperties will not be copied. + * @see org.springframework.beans.BeanWrapper + */ + public static void copyPropertiesToBean(Annotation ann, Object bean, String... excludedProperties) { + Set excluded = new HashSet(Arrays.asList(excludedProperties)); + Method[] annotationProperties = ann.annotationType().getDeclaredMethods(); + BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(bean); + for (Method annotationProperty : annotationProperties) { + String propertyName = annotationProperty.getName(); + if ((!excluded.contains(propertyName)) && bw.isWritableProperty(propertyName)) { + Object value = ReflectionUtils.invokeMethod(annotationProperty, ann); + bw.setPropertyValue(propertyName, value); + } + } + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/annotation/package.html b/org.springframework.beans/src/main/java/org/springframework/beans/annotation/package.html new file mode 100644 index 0000000000..78d7b62256 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/annotation/package.html @@ -0,0 +1,7 @@ + + + +Support package for beans-style handling of Java 5 annotations. + + + diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanClassLoaderAware.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanClassLoaderAware.java new file mode 100644 index 0000000000..efa7d9e430 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanClassLoaderAware.java @@ -0,0 +1,54 @@ +/* + * Copyright 2002-2006 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; + +/** + * Callback that allows a bean to be aware of the bean + * {@link ClassLoader class loader}; that is, the class loader used by the + * present bean factory to load bean classes. + * + *

This is mainly intended to be implemented by framework classes which + * have to pick up application classes by name despite themselves potentially + * being loaded from a shared class loader. + * + *

For a list of all bean lifecycle methods, see the + * {@link BeanFactory BeanFactory javadocs}. + * + * @author Juergen Hoeller + * @since 2.0 + * @see BeanNameAware + * @see BeanFactoryAware + * @see InitializingBean + */ +public interface BeanClassLoaderAware { + + /** + * Callback that supplies the bean {@link ClassLoader class loader} to + * a bean instance. + *

Invoked after the population of normal bean properties but + * before an initialization callback such as + * {@link org.springframework.beans.factory.InitializingBean InitializingBean's} + * {@link org.springframework.beans.factory.InitializingBean#afterPropertiesSet()} + * method or a custom init-method. + * @param classLoader the owning class loader; may be null in + * which case a default ClassLoader must be used, for example + * the ClassLoader obtained via + * {@link org.springframework.util.ClassUtils#getDefaultClassLoader()} + */ + void setBeanClassLoader(ClassLoader classLoader); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanCreationException.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanCreationException.java new file mode 100644 index 0000000000..b94c597c09 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanCreationException.java @@ -0,0 +1,203 @@ +/* + * Copyright 2002-2008 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; + +import java.io.PrintStream; +import java.io.PrintWriter; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import org.springframework.beans.FatalBeanException; +import org.springframework.core.NestedRuntimeException; + +/** + * Exception thrown when a BeanFactory encounters an error when + * attempting to create a bean from a bean definition. + * + * @author Juergen Hoeller + */ +public class BeanCreationException extends FatalBeanException { + + private String beanName; + + private String resourceDescription; + + private List relatedCauses; + + + /** + * Create a new BeanCreationException. + * @param msg the detail message + */ + public BeanCreationException(String msg) { + super(msg); + } + + /** + * Create a new BeanCreationException. + * @param msg the detail message + * @param cause the root cause + */ + public BeanCreationException(String msg, Throwable cause) { + super(msg, cause); + } + + /** + * Create a new BeanCreationException. + * @param beanName the name of the bean requested + * @param msg the detail message + */ + public BeanCreationException(String beanName, String msg) { + super("Error creating bean with name '" + beanName + "': " + msg); + this.beanName = beanName; + } + + /** + * Create a new BeanCreationException. + * @param beanName the name of the bean requested + * @param msg the detail message + * @param cause the root cause + */ + public BeanCreationException(String beanName, String msg, Throwable cause) { + this(beanName, msg); + initCause(cause); + } + + /** + * Create a new BeanCreationException. + * @param resourceDescription description of the resource + * that the bean definition came from + * @param beanName the name of the bean requested + * @param msg the detail message + */ + public BeanCreationException(String resourceDescription, String beanName, String msg) { + super("Error creating bean with name '" + beanName + "'" + + (resourceDescription != null ? " defined in " + resourceDescription : "") + ": " + msg); + this.resourceDescription = resourceDescription; + this.beanName = beanName; + } + + /** + * Create a new BeanCreationException. + * @param resourceDescription description of the resource + * that the bean definition came from + * @param beanName the name of the bean requested + * @param msg the detail message + * @param cause the root cause + */ + public BeanCreationException(String resourceDescription, String beanName, String msg, Throwable cause) { + this(resourceDescription, beanName, msg); + initCause(cause); + } + + + /** + * Return the name of the bean requested, if any. + */ + public String getBeanName() { + return this.beanName; + } + + /** + * Return the description of the resource that the bean + * definition came from, if any. + */ + public String getResourceDescription() { + return this.resourceDescription; + } + + /** + * Add a related cause to this bean creation exception, + * not being a direct cause of the failure but having occured + * earlier in the creation of the same bean instance. + * @param ex the related cause to add + */ + public void addRelatedCause(Throwable ex) { + if (this.relatedCauses == null) { + this.relatedCauses = new LinkedList(); + } + this.relatedCauses.add(ex); + } + + /** + * Return the related causes, if any. + * @return the array of related causes, or null if none + */ + public Throwable[] getRelatedCauses() { + if (this.relatedCauses == null) { + return null; + } + return (Throwable[]) this.relatedCauses.toArray(new Throwable[this.relatedCauses.size()]); + } + + + public String toString() { + StringBuffer sb = new StringBuffer(super.toString()); + if (this.relatedCauses != null) { + for (Iterator it = this.relatedCauses.iterator(); it.hasNext();) { + Throwable relatedCause = (Throwable) it.next(); + sb.append("\nRelated cause: "); + sb.append(relatedCause); + } + } + return sb.toString(); + } + + public void printStackTrace(PrintStream ps) { + synchronized (ps) { + super.printStackTrace(ps); + if (this.relatedCauses != null) { + for (Iterator it = this.relatedCauses.iterator(); it.hasNext();) { + Throwable relatedCause = (Throwable) it.next(); + ps.println("Related cause:"); + relatedCause.printStackTrace(ps); + } + } + } + } + + public void printStackTrace(PrintWriter pw) { + synchronized (pw) { + super.printStackTrace(pw); + if (this.relatedCauses != null) { + for (Iterator it = this.relatedCauses.iterator(); it.hasNext();) { + Throwable relatedCause = (Throwable) it.next(); + pw.println("Related cause:"); + relatedCause.printStackTrace(pw); + } + } + } + } + + public boolean contains(Class exClass) { + if (super.contains(exClass)) { + return true; + } + if (this.relatedCauses != null) { + for (Iterator it = this.relatedCauses.iterator(); it.hasNext();) { + Throwable relatedCause = (Throwable) it.next(); + if (relatedCause instanceof NestedRuntimeException && + ((NestedRuntimeException) relatedCause).contains(exClass)) { + return true; + } + } + } + return false; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanCreationNotAllowedException.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanCreationNotAllowedException.java new file mode 100644 index 0000000000..2a078c5fb1 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanCreationNotAllowedException.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2006 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; + +/** + * Exception thrown in case of a bean being requested despite + * bean creation currently not being allowed (for example, during + * the shutdown phase of a bean factory). + * + * @author Juergen Hoeller + * @since 2.0 + */ +public class BeanCreationNotAllowedException extends BeanCreationException { + + /** + * Create a new BeanCreationNotAllowedException. + * @param beanName the name of the bean requested + * @param msg the detail message + */ + public BeanCreationNotAllowedException(String beanName, String msg) { + super(beanName, msg); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanCurrentlyInCreationException.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanCurrentlyInCreationException.java new file mode 100644 index 0000000000..1e014c8563 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanCurrentlyInCreationException.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2006 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; + +/** + * Exception thrown in case of a reference to a bean that's currently in creation. + * Typically happens when constructor autowiring matches the currently constructed bean. + * + * @author Juergen Hoeller + * @since 1.1 + */ +public class BeanCurrentlyInCreationException extends BeanCreationException { + + /** + * Create a new BeanCurrentlyInCreationException, + * with a default error message that indicates a circular reference. + * @param beanName the name of the bean requested + */ + public BeanCurrentlyInCreationException(String beanName) { + super(beanName, + "Requested bean is currently in creation: Is there an unresolvable circular reference?"); + } + + /** + * Create a new BeanCurrentlyInCreationException. + * @param beanName the name of the bean requested + * @param msg the detail message + */ + public BeanCurrentlyInCreationException(String beanName, String msg) { + super(beanName, msg); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanDefinitionStoreException.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanDefinitionStoreException.java new file mode 100644 index 0000000000..be86fbd860 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanDefinitionStoreException.java @@ -0,0 +1,115 @@ +/* + * Copyright 2002-2007 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; + +import org.springframework.beans.FatalBeanException; + +/** + * Exception thrown when a BeanFactory encounters an invalid bean definition: + * e.g. in case of incomplete or contradictory bean metadata. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @author Rob Harrop + */ +public class BeanDefinitionStoreException extends FatalBeanException { + + private String resourceDescription; + + private String beanName; + + + /** + * Create a new BeanDefinitionStoreException. + * @param msg the detail message (used as exception message as-is) + */ + public BeanDefinitionStoreException(String msg) { + super(msg); + } + + /** + * Create a new BeanDefinitionStoreException. + * @param msg the detail message (used as exception message as-is) + * @param cause the root cause (may be null) + */ + public BeanDefinitionStoreException(String msg, Throwable cause) { + super(msg, cause); + } + + /** + * Create a new BeanDefinitionStoreException. + * @param resourceDescription description of the resource that the bean definition came from + * @param msg the detail message (used as exception message as-is) + */ + public BeanDefinitionStoreException(String resourceDescription, String msg) { + super(msg); + this.resourceDescription = resourceDescription; + } + + /** + * Create a new BeanDefinitionStoreException. + * @param resourceDescription description of the resource that the bean definition came from + * @param msg the detail message (used as exception message as-is) + * @param cause the root cause (may be null) + */ + public BeanDefinitionStoreException(String resourceDescription, String msg, Throwable cause) { + super(msg, cause); + this.resourceDescription = resourceDescription; + } + + /** + * Create a new BeanDefinitionStoreException. + * @param resourceDescription description of the resource that the bean definition came from + * @param beanName the name of the bean requested + * @param msg the detail message (appended to an introductory message that indicates + * the resource and the name of the bean) + */ + public BeanDefinitionStoreException(String resourceDescription, String beanName, String msg) { + this(resourceDescription, beanName, msg, null); + } + + /** + * Create a new BeanDefinitionStoreException. + * @param resourceDescription description of the resource that the bean definition came from + * @param beanName the name of the bean requested + * @param msg the detail message (appended to an introductory message that indicates + * the resource and the name of the bean) + * @param cause the root cause (may be null) + */ + public BeanDefinitionStoreException(String resourceDescription, String beanName, String msg, Throwable cause) { + super("Invalid bean definition with name '" + beanName + "' defined in " + resourceDescription + ": " + msg, cause); + this.resourceDescription = resourceDescription; + this.beanName = beanName; + } + + + /** + * Return the description of the resource that the bean + * definition came from, if any. + */ + public String getResourceDescription() { + return this.resourceDescription; + } + + /** + * Return the name of the bean requested, if any. + */ + public String getBeanName() { + return this.beanName; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanFactory.java new file mode 100644 index 0000000000..98e4441121 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanFactory.java @@ -0,0 +1,259 @@ +/* + * Copyright 2002-2007 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; + +import org.springframework.beans.BeansException; + +/** + * The root interface for accessing a Spring bean container. + * This is the basic client view of a bean container; + * further interfaces such as {@link ListableBeanFactory} and + * {@link org.springframework.beans.factory.config.ConfigurableBeanFactory} + * are available for specific purposes. + * + *

This interface is implemented by objects that hold a number of bean definitions, + * each uniquely identified by a String name. Depending on the bean definition, + * the factory will return either an independent instance of a contained object + * (the Prototype design pattern), or a single shared instance (a superior + * alternative to the Singleton design pattern, in which the instance is a + * singleton in the scope of the factory). Which type of instance will be returned + * depends on the bean factory configuration: the API is the same. Since Spring + * 2.0, further scopes are available depending on the concrete application + * context (e.g. "request" and "session" scopes in a web environment). + * + *

The point of this approach is that the BeanFactory is a central registry + * of application components, and centralizes configuration of application + * components (no more do individual objects need to read properties files, + * for example). See chapters 4 and 11 of "Expert One-on-One J2EE Design and + * Development" for a discussion of the benefits of this approach. + * + *

Note that it is generally better to rely on Dependency Injection + * ("push" configuration) to configure application objects through setters + * or constructors, rather than use any form of "pull" configuration like a + * BeanFactory lookup. Spring's Dependency Injection functionality is + * implemented using this BeanFactory interface and its subinterfaces. + * + *

Normally a BeanFactory will load bean definitions stored in a configuration + * source (such as an XML document), and use the org.springframework.beans + * package to configure the beans. However, an implementation could simply return + * Java objects it creates as necessary directly in Java code. There are no + * constraints on how the definitions could be stored: LDAP, RDBMS, XML, + * properties file, etc. Implementations are encouraged to support references + * amongst beans (Dependency Injection). + * + *

In contrast to the methods in {@link ListableBeanFactory}, all of the + * operations in this interface will also check parent factories if this is a + * {@link HierarchicalBeanFactory}. If a bean is not found in this factory instance, + * the immediate parent factory will be asked. Beans in this factory instance + * are supposed to override beans of the same name in any parent factory. + * + *

Bean factory implementations should support the standard bean lifecycle interfaces + * as far as possible. The full set of initialization methods and their standard order is:
+ * 1. BeanNameAware's setBeanName
+ * 2. BeanClassLoaderAware's setBeanClassLoader
+ * 3. BeanFactoryAware's setBeanFactory
+ * 4. ResourceLoaderAware's setResourceLoader + * (only applicable when running in an application context)
+ * 5. ApplicationEventPublisherAware's setApplicationEventPublisher + * (only applicable when running in an application context)
+ * 6. MessageSourceAware's setMessageSource + * (only applicable when running in an application context)
+ * 7. ApplicationContextAware's setApplicationContext + * (only applicable when running in an application context)
+ * 8. ServletContextAware's setServletContext + * (only applicable when running in a web application context)
+ * 9. postProcessBeforeInitialization methods of BeanPostProcessors
+ * 10. InitializingBean's afterPropertiesSet
+ * 11. a custom init-method definition
+ * 12. postProcessAfterInitialization methods of BeanPostProcessors + * + *

On shutdown of a bean factory, the following lifecycle methods apply:
+ * 1. DisposableBean's destroy
+ * 2. a custom destroy-method definition + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 13 April 2001 + * @see BeanNameAware#setBeanName + * @see BeanClassLoaderAware#setBeanClassLoader + * @see BeanFactoryAware#setBeanFactory + * @see org.springframework.context.ResourceLoaderAware#setResourceLoader + * @see org.springframework.context.ApplicationEventPublisherAware#setApplicationEventPublisher + * @see org.springframework.context.MessageSourceAware#setMessageSource + * @see org.springframework.context.ApplicationContextAware#setApplicationContext + * @see org.springframework.web.context.ServletContextAware#setServletContext + * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization + * @see InitializingBean#afterPropertiesSet + * @see org.springframework.beans.factory.support.RootBeanDefinition#getInitMethodName + * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization + * @see DisposableBean#destroy + * @see org.springframework.beans.factory.support.RootBeanDefinition#getDestroyMethodName + */ +public interface BeanFactory { + + /** + * Used to dereference a {@link FactoryBean} instance and distinguish it from + * beans created by the FactoryBean. For example, if the bean named + * myJndiObject is a FactoryBean, getting &myJndiObject + * will return the factory, not the instance returned by the factory. + */ + String FACTORY_BEAN_PREFIX = "&"; + + + /** + * Return an instance, which may be shared or independent, of the specified bean. + *

This method allows a Spring BeanFactory to be used as a replacement for the + * Singleton or Prototype design pattern. Callers may retain references to + * returned objects in the case of Singleton beans. + *

Translates aliases back to the corresponding canonical bean name. + * Will ask the parent factory if the bean cannot be found in this factory instance. + * @param name the name of the bean to retrieve + * @return an instance of the bean + * @throws NoSuchBeanDefinitionException if there is no bean definition + * with the specified name + * @throws BeansException if the bean could not be obtained + */ + Object getBean(String name) throws BeansException; + + /** + * Return an instance, which may be shared or independent, of the specified bean. + *

Behaves the same as {@link #getBean(String)}, but provides a measure of type + * safety by throwing a BeanNotOfRequiredTypeException if the bean is not of the + * required type. This means that ClassCastException can't be thrown on casting + * the result correctly, as can happen with {@link #getBean(String)}. + *

Translates aliases back to the corresponding canonical bean name. + * Will ask the parent factory if the bean cannot be found in this factory instance. + * @param name the name of the bean to retrieve + * @param requiredType type the bean must match. Can be an interface or superclass + * of the actual class, or null for any match. For example, if the value + * is Object.class, this method will succeed whatever the class of the + * returned instance. + * @return an instance of the bean + * @throws NoSuchBeanDefinitionException if there's no such bean definition + * @throws BeanNotOfRequiredTypeException if the bean is not of the required type + * @throws BeansException if the bean could not be created + */ + Object getBean(String name, Class requiredType) throws BeansException; + + /** + * Return an instance, which may be shared or independent, of the specified bean. + *

Allows for specifying explicit constructor arguments / factory method arguments, + * overriding the specified default arguments (if any) in the bean definition. + * @param name the name of the bean to retrieve + * @param args arguments to use if creating a prototype using explicit arguments to a + * static factory method. It is invalid to use a non-null args value in any other case. + * @return an instance of the bean + * @throws NoSuchBeanDefinitionException if there's no such bean definition + * @throws BeanDefinitionStoreException if arguments have been given but + * the affected bean isn't a prototype + * @throws BeansException if the bean could not be created + * @since 2.5 + */ + Object getBean(String name, Object[] args) throws BeansException; + + /** + * Does this bean factory contain a bean with the given name? More specifically, + * is {@link #getBean} able to obtain a bean instance for the given name? + *

Translates aliases back to the corresponding canonical bean name. + * Will ask the parent factory if the bean cannot be found in this factory instance. + * @param name the name of the bean to query + * @return whether a bean with the given name is defined + */ + boolean containsBean(String name); + + /** + * Is this bean a shared singleton? That is, will {@link #getBean} always + * return the same instance? + *

Note: This method returning false does not clearly indicate + * independent instances. It indicates non-singleton instances, which may correspond + * to a scoped bean as well. Use the {@link #isPrototype} operation to explicitly + * check for independent instances. + *

Translates aliases back to the corresponding canonical bean name. + * Will ask the parent factory if the bean cannot be found in this factory instance. + * @param name the name of the bean to query + * @return whether this bean corresponds to a singleton instance + * @throws NoSuchBeanDefinitionException if there is no bean with the given name + * @see #getBean + * @see #isPrototype + */ + boolean isSingleton(String name) throws NoSuchBeanDefinitionException; + + /** + * Is this bean a prototype? That is, will {@link #getBean} always return + * independent instances? + *

Note: This method returning false does not clearly indicate + * a singleton object. It indicates non-independent instances, which may correspond + * to a scoped bean as well. Use the {@link #isSingleton} operation to explicitly + * check for a shared singleton instance. + *

Translates aliases back to the corresponding canonical bean name. + * Will ask the parent factory if the bean cannot be found in this factory instance. + * @param name the name of the bean to query + * @return whether this bean will always deliver independent instances + * @throws NoSuchBeanDefinitionException if there is no bean with the given name + * @since 2.0.3 + * @see #getBean + * @see #isSingleton + */ + boolean isPrototype(String name) throws NoSuchBeanDefinitionException; + + /** + * Check whether the bean with the given name matches the specified type. + * More specifically, check whether a {@link #getBean} call for the given name + * would return an object that is assignable to the specified target type. + *

Translates aliases back to the corresponding canonical bean name. + * Will ask the parent factory if the bean cannot be found in this factory instance. + * @param name the name of the bean to query + * @param targetType the type to match against + * @return true if the bean type matches, + * false if it doesn't match or cannot be determined yet + * @throws NoSuchBeanDefinitionException if there is no bean with the given name + * @since 2.0.1 + * @see #getBean + * @see #getType + */ + boolean isTypeMatch(String name, Class targetType) throws NoSuchBeanDefinitionException; + + /** + * Determine the type of the bean with the given name. More specifically, + * determine the type of object that {@link #getBean} would return for the given name. + *

For a {@link FactoryBean}, return the type of object that the FactoryBean creates, + * as exposed by {@link FactoryBean#getObjectType()}. + *

Translates aliases back to the corresponding canonical bean name. + * Will ask the parent factory if the bean cannot be found in this factory instance. + * @param name the name of the bean to query + * @return the type of the bean, or null if not determinable + * @throws NoSuchBeanDefinitionException if there is no bean with the given name + * @since 1.1.2 + * @see #getBean + * @see #isTypeMatch + */ + Class getType(String name) throws NoSuchBeanDefinitionException; + + /** + * Return the aliases for the given bean name, if any. + * All of those aliases point to the same bean when used in a {@link #getBean} call. + *

If the given name is an alias, the corresponding original bean name + * and other aliases (if any) will be returned, with the original bean name + * being the first element in the array. + *

Will ask the parent factory if the bean cannot be found in this factory instance. + * @param name the bean name to check for aliases + * @return the aliases, or an empty array if none + * @see #getBean + */ + String[] getAliases(String name); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanFactoryAware.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanFactoryAware.java new file mode 100644 index 0000000000..b8a224262c --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanFactoryAware.java @@ -0,0 +1,54 @@ +/* + * Copyright 2002-2007 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; + +import org.springframework.beans.BeansException; + +/** + * Interface to be implemented by beans that wish to be aware of their + * owning {@link BeanFactory}. + * + *

For example, beans can look up collaborating beans via the factory + * (Dependency Lookup). Note that most beans will choose to receive references + * to collaborating beans via corresponding bean properties or constructor + * arguments (Dependency Injection). + * + *

For a list of all bean lifecycle methods, see the + * {@link BeanFactory BeanFactory javadocs}. + * + * @author Rod Johnson + * @since 11.03.2003 + * @see BeanNameAware + * @see BeanClassLoaderAware + * @see InitializingBean + * @see org.springframework.context.ApplicationContextAware + */ +public interface BeanFactoryAware { + + /** + * Callback that supplies the owning factory to a bean instance. + *

Invoked after the population of normal bean properties + * but before an initialization callback such as + * {@link InitializingBean#afterPropertiesSet()} or a custom init-method. + * @param beanFactory owning BeanFactory (never null). + * The bean can immediately call methods on the factory. + * @throws BeansException in case of initialization errors + * @see BeanInitializationException + */ + void setBeanFactory(BeanFactory beanFactory) throws BeansException; + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanFactoryUtils.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanFactoryUtils.java new file mode 100644 index 0000000000..501b52fab4 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanFactoryUtils.java @@ -0,0 +1,421 @@ +/* + * Copyright 2002-2008 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; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.beans.BeansException; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Convenience methods operating on bean factories, in particular + * on the {@link ListableBeanFactory} interface. + * + *

Returns bean counts, bean names or bean instances, + * taking into account the nesting hierarchy of a bean factory + * (which the methods defined on the ListableBeanFactory interface don't, + * in contrast to the methods defined on the BeanFactory interface). + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 04.07.2003 + */ +public abstract class BeanFactoryUtils { + + /** + * Separator for generated bean names. If a class name or parent name is not + * unique, "#1", "#2" etc will be appended, until the name becomes unique. + */ + public static final String GENERATED_BEAN_NAME_SEPARATOR = "#"; + + + /** + * Return whether the given name is a factory dereference + * (beginning with the factory dereference prefix). + * @param name the name of the bean + * @return whether the given name is a factory dereference + * @see BeanFactory#FACTORY_BEAN_PREFIX + */ + public static boolean isFactoryDereference(String name) { + return (name != null && name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)); + } + + /** + * Return the actual bean name, stripping out the factory dereference + * prefix (if any, also stripping repeated factory prefixes if found). + * @param name the name of the bean + * @return the transformed name + * @see BeanFactory#FACTORY_BEAN_PREFIX + */ + public static String transformedBeanName(String name) { + Assert.notNull(name, "'name' must not be null"); + String beanName = name; + while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) { + beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length()); + } + return beanName; + } + + /** + * Return whether the given name is a bean name which has been generated + * by the default naming strategy (containing a "#..." part). + * @param name the name of the bean + * @return whether the given name is a generated bean name + * @see #GENERATED_BEAN_NAME_SEPARATOR + * @see org.springframework.beans.factory.support.BeanDefinitionReaderUtils#generateBeanName + * @see org.springframework.beans.factory.support.DefaultBeanNameGenerator + */ + public static boolean isGeneratedBeanName(String name) { + return (name != null && name.indexOf(GENERATED_BEAN_NAME_SEPARATOR) != -1); + } + + /** + * Extract the "raw" bean name from the given (potentially generated) bean name, + * excluding any "#..." suffixes which might have been added for uniqueness. + * @param name the potentially generated bean name + * @return the raw bean name + * @see #GENERATED_BEAN_NAME_SEPARATOR + */ + public static String originalBeanName(String name) { + Assert.notNull(name, "'name' must not be null"); + int separatorIndex = name.indexOf(GENERATED_BEAN_NAME_SEPARATOR); + return (separatorIndex != -1 ? name.substring(0, separatorIndex) : name); + } + + + /** + * Count all beans in any hierarchy in which this factory participates. + * Includes counts of ancestor bean factories. + *

Beans that are "overridden" (specified in a descendant factory + * with the same name) are only counted once. + * @param lbf the bean factory + * @return count of beans including those defined in ancestor factories + */ + public static int countBeansIncludingAncestors(ListableBeanFactory lbf) { + return beanNamesIncludingAncestors(lbf).length; + } + + /** + * Return all bean names in the factory, including ancestor factories. + * @param lbf the bean factory + * @return the array of matching bean names, or an empty array if none + * @see #beanNamesForTypeIncludingAncestors + */ + public static String[] beanNamesIncludingAncestors(ListableBeanFactory lbf) { + return beanNamesForTypeIncludingAncestors(lbf, Object.class); + } + + + /** + * Get all bean names for the given type, including those defined in ancestor + * factories. Will return unique names in case of overridden bean definitions. + *

Does consider objects created by FactoryBeans, which means that FactoryBeans + * will get initialized. If the object created by the FactoryBean doesn't match, + * the raw FactoryBean itself will be matched against the type. + *

This version of beanNamesForTypeIncludingAncestors automatically + * includes prototypes and FactoryBeans. + * @param lbf the bean factory + * @param type the type that beans must match + * @return the array of matching bean names, or an empty array if none + */ + public static String[] beanNamesForTypeIncludingAncestors(ListableBeanFactory lbf, Class type) { + Assert.notNull(lbf, "ListableBeanFactory must not be null"); + String[] result = lbf.getBeanNamesForType(type); + if (lbf instanceof HierarchicalBeanFactory) { + HierarchicalBeanFactory hbf = (HierarchicalBeanFactory) lbf; + if (hbf.getParentBeanFactory() instanceof ListableBeanFactory) { + String[] parentResult = beanNamesForTypeIncludingAncestors( + (ListableBeanFactory) hbf.getParentBeanFactory(), type); + List resultList = new ArrayList(); + resultList.addAll(Arrays.asList(result)); + for (int i = 0; i < parentResult.length; i++) { + String beanName = parentResult[i]; + if (!resultList.contains(beanName) && !hbf.containsLocalBean(beanName)) { + resultList.add(beanName); + } + } + result = StringUtils.toStringArray(resultList); + } + } + return result; + } + + /** + * Get all bean names for the given type, including those defined in ancestor + * factories. Will return unique names in case of overridden bean definitions. + *

Does consider objects created by FactoryBeans if the "allowEagerInit" + * flag is set, which means that FactoryBeans will get initialized. If the + * object created by the FactoryBean doesn't match, the raw FactoryBean itself + * will be matched against the type. If "allowEagerInit" is not set, + * only raw FactoryBeans will be checked (which doesn't require initialization + * of each FactoryBean). + * @param lbf the bean factory + * @param includeNonSingletons whether to include prototype or scoped beans too + * or just singletons (also applies to FactoryBeans) + * @param allowEagerInit whether to initialize lazy-init singletons and + * objects created by FactoryBeans (or by factory methods with a + * "factory-bean" reference) for the type check. Note that FactoryBeans need to be + * eagerly initialized to determine their type: So be aware that passing in "true" + * for this flag will initialize FactoryBeans and "factory-bean" references. + * @param type the type that beans must match + * @return the array of matching bean names, or an empty array if none + */ + public static String[] beanNamesForTypeIncludingAncestors( + ListableBeanFactory lbf, Class type, boolean includeNonSingletons, boolean allowEagerInit) { + + Assert.notNull(lbf, "ListableBeanFactory must not be null"); + String[] result = lbf.getBeanNamesForType(type, includeNonSingletons, allowEagerInit); + if (lbf instanceof HierarchicalBeanFactory) { + HierarchicalBeanFactory hbf = (HierarchicalBeanFactory) lbf; + if (hbf.getParentBeanFactory() instanceof ListableBeanFactory) { + String[] parentResult = beanNamesForTypeIncludingAncestors( + (ListableBeanFactory) hbf.getParentBeanFactory(), type, includeNonSingletons, allowEagerInit); + List resultList = new ArrayList(); + resultList.addAll(Arrays.asList(result)); + for (int i = 0; i < parentResult.length; i++) { + String beanName = parentResult[i]; + if (!resultList.contains(beanName) && !hbf.containsLocalBean(beanName)) { + resultList.add(beanName); + } + } + result = StringUtils.toStringArray(resultList); + } + } + return result; + } + + /** + * Return all beans of the given type or subtypes, also picking up beans defined in + * ancestor bean factories if the current bean factory is a HierarchicalBeanFactory. + * The returned Map will only contain beans of this type. + *

Does consider objects created by FactoryBeans, which means that FactoryBeans + * will get initialized. If the object created by the FactoryBean doesn't match, + * the raw FactoryBean itself will be matched against the type. + * @param lbf the bean factory + * @param type type of bean to match + * @return the Map of matching bean instances, or an empty Map if none + * @throws BeansException if a bean could not be created + */ + public static Map beansOfTypeIncludingAncestors(ListableBeanFactory lbf, Class type) + throws BeansException { + + Assert.notNull(lbf, "ListableBeanFactory must not be null"); + Map result = new LinkedHashMap(4); + result.putAll(lbf.getBeansOfType(type)); + if (lbf instanceof HierarchicalBeanFactory) { + HierarchicalBeanFactory hbf = (HierarchicalBeanFactory) lbf; + if (hbf.getParentBeanFactory() instanceof ListableBeanFactory) { + Map parentResult = beansOfTypeIncludingAncestors( + (ListableBeanFactory) hbf.getParentBeanFactory(), type); + for (Iterator it = parentResult.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + String beanName = (String) entry.getKey(); + if (!result.containsKey(beanName) && !hbf.containsLocalBean(beanName)) { + result.put(beanName, entry.getValue()); + } + } + } + } + return result; + } + + /** + * Return all beans of the given type or subtypes, also picking up beans defined in + * ancestor bean factories if the current bean factory is a HierarchicalBeanFactory. + * The returned Map will only contain beans of this type. + *

Does consider objects created by FactoryBeans if the "allowEagerInit" + * flag is set, which means that FactoryBeans will get initialized. If the + * object created by the FactoryBean doesn't match, the raw FactoryBean itself + * will be matched against the type. If "allowEagerInit" is not set, + * only raw FactoryBeans will be checked (which doesn't require initialization + * of each FactoryBean). + * @param lbf the bean factory + * @param type type of bean to match + * @param includeNonSingletons whether to include prototype or scoped beans too + * or just singletons (also applies to FactoryBeans) + * @param allowEagerInit whether to initialize lazy-init singletons and + * objects created by FactoryBeans (or by factory methods with a + * "factory-bean" reference) for the type check. Note that FactoryBeans need to be + * eagerly initialized to determine their type: So be aware that passing in "true" + * for this flag will initialize FactoryBeans and "factory-bean" references. + * @return the Map of matching bean instances, or an empty Map if none + * @throws BeansException if a bean could not be created + */ + public static Map beansOfTypeIncludingAncestors( + ListableBeanFactory lbf, Class type, boolean includeNonSingletons, boolean allowEagerInit) + throws BeansException { + + Assert.notNull(lbf, "ListableBeanFactory must not be null"); + Map result = new LinkedHashMap(4); + result.putAll(lbf.getBeansOfType(type, includeNonSingletons, allowEagerInit)); + if (lbf instanceof HierarchicalBeanFactory) { + HierarchicalBeanFactory hbf = (HierarchicalBeanFactory) lbf; + if (hbf.getParentBeanFactory() instanceof ListableBeanFactory) { + Map parentResult = beansOfTypeIncludingAncestors( + (ListableBeanFactory) hbf.getParentBeanFactory(), type, includeNonSingletons, allowEagerInit); + for (Iterator it = parentResult.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + String beanName = (String) entry.getKey(); + if (!result.containsKey(beanName) && !hbf.containsLocalBean(beanName)) { + result.put(beanName, entry.getValue()); + } + } + } + } + return result; + } + + + /** + * Return a single bean of the given type or subtypes, also picking up beans + * defined in ancestor bean factories if the current bean factory is a + * HierarchicalBeanFactory. Useful convenience method when we expect a + * single bean and don't care about the bean name. + *

Does consider objects created by FactoryBeans, which means that FactoryBeans + * will get initialized. If the object created by the FactoryBean doesn't match, + * the raw FactoryBean itself will be matched against the type. + *

This version of beanOfTypeIncludingAncestors automatically includes + * prototypes and FactoryBeans. + * @param lbf the bean factory + * @param type type of bean to match + * @return the matching bean instance + * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException + * if 0 or more than 1 beans of the given type were found + * @throws BeansException if the bean could not be created + */ + public static Object beanOfTypeIncludingAncestors(ListableBeanFactory lbf, Class type) + throws BeansException { + + Map beansOfType = beansOfTypeIncludingAncestors(lbf, type); + if (beansOfType.size() == 1) { + return beansOfType.values().iterator().next(); + } + else { + throw new NoSuchBeanDefinitionException(type, "expected single bean but found " + beansOfType.size()); + } + } + + /** + * Return a single bean of the given type or subtypes, also picking up beans + * defined in ancestor bean factories if the current bean factory is a + * HierarchicalBeanFactory. Useful convenience method when we expect a + * single bean and don't care about the bean name. + *

Does consider objects created by FactoryBeans if the "allowEagerInit" + * flag is set, which means that FactoryBeans will get initialized. If the + * object created by the FactoryBean doesn't match, the raw FactoryBean itself + * will be matched against the type. If "allowEagerInit" is not set, + * only raw FactoryBeans will be checked (which doesn't require initialization + * of each FactoryBean). + * @param lbf the bean factory + * @param type type of bean to match + * @param includeNonSingletons whether to include prototype or scoped beans too + * or just singletons (also applies to FactoryBeans) + * @param allowEagerInit whether to initialize lazy-init singletons and + * objects created by FactoryBeans (or by factory methods with a + * "factory-bean" reference) for the type check. Note that FactoryBeans need to be + * eagerly initialized to determine their type: So be aware that passing in "true" + * for this flag will initialize FactoryBeans and "factory-bean" references. + * @return the matching bean instance + * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException + * if 0 or more than 1 beans of the given type were found + * @throws BeansException if the bean could not be created + */ + public static Object beanOfTypeIncludingAncestors( + ListableBeanFactory lbf, Class type, boolean includeNonSingletons, boolean allowEagerInit) + throws BeansException { + + Map beansOfType = beansOfTypeIncludingAncestors(lbf, type, includeNonSingletons, allowEagerInit); + if (beansOfType.size() == 1) { + return beansOfType.values().iterator().next(); + } + else { + throw new NoSuchBeanDefinitionException(type, "expected single bean but found " + beansOfType.size()); + } + } + + /** + * Return a single bean of the given type or subtypes, not looking in ancestor + * factories. Useful convenience method when we expect a single bean and + * don't care about the bean name. + *

Does consider objects created by FactoryBeans, which means that FactoryBeans + * will get initialized. If the object created by the FactoryBean doesn't match, + * the raw FactoryBean itself will be matched against the type. + *

This version of beanOfType automatically includes + * prototypes and FactoryBeans. + * @param lbf the bean factory + * @param type type of bean to match + * @return the matching bean instance + * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException + * if 0 or more than 1 beans of the given type were found + * @throws BeansException if the bean could not be created + */ + public static Object beanOfType(ListableBeanFactory lbf, Class type) throws BeansException { + Assert.notNull(lbf, "ListableBeanFactory must not be null"); + Map beansOfType = lbf.getBeansOfType(type); + if (beansOfType.size() == 1) { + return beansOfType.values().iterator().next(); + } + else { + throw new NoSuchBeanDefinitionException(type, "expected single bean but found " + beansOfType.size()); + } + } + + /** + * Return a single bean of the given type or subtypes, not looking in ancestor + * factories. Useful convenience method when we expect a single bean and + * don't care about the bean name. + *

Does consider objects created by FactoryBeans if the "allowEagerInit" + * flag is set, which means that FactoryBeans will get initialized. If the + * object created by the FactoryBean doesn't match, the raw FactoryBean itself + * will be matched against the type. If "allowEagerInit" is not set, + * only raw FactoryBeans will be checked (which doesn't require initialization + * of each FactoryBean). + * @param lbf the bean factory + * @param type type of bean to match + * @param includeNonSingletons whether to include prototype or scoped beans too + * or just singletons (also applies to FactoryBeans) + * @param allowEagerInit whether to initialize lazy-init singletons and + * objects created by FactoryBeans (or by factory methods with a + * "factory-bean" reference) for the type check. Note that FactoryBeans need to be + * eagerly initialized to determine their type: So be aware that passing in "true" + * for this flag will initialize FactoryBeans and "factory-bean" references. + * @return the matching bean instance + * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException + * if 0 or more than 1 beans of the given type were found + * @throws BeansException if the bean could not be created + */ + public static Object beanOfType( + ListableBeanFactory lbf, Class type, boolean includeNonSingletons, boolean allowEagerInit) + throws BeansException { + + Assert.notNull(lbf, "ListableBeanFactory must not be null"); + Map beansOfType = lbf.getBeansOfType(type, includeNonSingletons, allowEagerInit); + if (beansOfType.size() == 1) { + return beansOfType.values().iterator().next(); + } + else { + throw new NoSuchBeanDefinitionException(type, "expected single bean but found " + beansOfType.size()); + } + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanInitializationException.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanInitializationException.java new file mode 100644 index 0000000000..297e9be094 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanInitializationException.java @@ -0,0 +1,54 @@ +/* + * Copyright 2002-2006 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; + +import org.springframework.beans.FatalBeanException; + +/** + * Exception that a bean implementation is suggested to throw if its own + * factory-aware initialization code fails. BeansExceptions thrown by + * bean factory methods themselves should simply be propagated as-is. + * + *

Note that non-factory-aware initialization methods like afterPropertiesSet() + * or a custom "init-method" can throw any exception. + * + * @author Juergen Hoeller + * @since 13.11.2003 + * @see BeanFactoryAware#setBeanFactory + * @see InitializingBean#afterPropertiesSet + */ +public class BeanInitializationException extends FatalBeanException { + + /** + * Create a new BeanInitializationException with the specified message. + * @param msg the detail message + */ + public BeanInitializationException(String msg) { + super(msg); + } + + /** + * Create a new BeanInitializationException with the specified message + * and root cause. + * @param msg the detail message + * @param cause the root cause + */ + public BeanInitializationException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanIsAbstractException.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanIsAbstractException.java new file mode 100644 index 0000000000..413f2c7349 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanIsAbstractException.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2005 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; + +/** + * Exception thrown when a bean instance has been requested for a bean + * which has been defined as abstract + * @author Juergen Hoeller + * @since 1.1 + * @see org.springframework.beans.factory.support.AbstractBeanDefinition#setAbstract + */ +public class BeanIsAbstractException extends BeanCreationException { + + /** + * Create a new BeanIsAbstractException. + * @param beanName the name of the bean requested + */ + public BeanIsAbstractException(String beanName) { + super(beanName, "Bean definition is abstract"); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanIsNotAFactoryException.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanIsNotAFactoryException.java new file mode 100644 index 0000000000..7146ec7806 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanIsNotAFactoryException.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2005 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; + +/** + * Exception thrown when a bean is not a factory, but a user tries to get + * at the factory for the given bean name. Whether a bean is a factory is + * determined by whether it implements the FactoryBean interface. + * + * @author Rod Johnson + * @since 10.03.2003 + * @see org.springframework.beans.factory.FactoryBean + */ +public class BeanIsNotAFactoryException extends BeanNotOfRequiredTypeException { + + /** + * Create a new BeanIsNotAFactoryException. + * @param name the name of the bean requested + * @param actualType the actual type returned, which did not match + * the expected type + */ + public BeanIsNotAFactoryException(String name, Class actualType) { + super(name, FactoryBean.class, actualType); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanNameAware.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanNameAware.java new file mode 100644 index 0000000000..580850d3a4 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanNameAware.java @@ -0,0 +1,51 @@ +/* + * Copyright 2002-2007 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; + +/** + * Interface to be implemented by beans that want to be aware of their + * bean name in a bean factory. Note that it is not usually recommended + * that an object depend on its bean name, as this represents a potentially + * brittle dependence on external configuration, as well as a possibly + * unnecessary dependence on a Spring API. + * + *

For a list of all bean lifecycle methods, see the + * {@link BeanFactory BeanFactory javadocs}. + * + * @author Juergen Hoeller + * @since 01.11.2003 + * @see BeanClassLoaderAware + * @see BeanFactoryAware + * @see InitializingBean + */ +public interface BeanNameAware { + + /** + * Set the name of the bean in the bean factory that created this bean. + *

Invoked after population of normal bean properties but before an + * init callback such as {@link InitializingBean#afterPropertiesSet()} + * or a custom init-method. + * @param name the name of the bean in the factory. + * Note that this name is the actual bean name used in the factory, which may + * differ from the originally specified name: in particular for inner bean + * names, the actual bean name might have been made unique through appending + * "#..." suffixes. Use the {@link BeanFactoryUtils#originalBeanName(String)} + * method to extract the original bean name (without suffix), if desired. + */ + void setBeanName(String name); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanNotOfRequiredTypeException.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanNotOfRequiredTypeException.java new file mode 100644 index 0000000000..efdea3ab3d --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanNotOfRequiredTypeException.java @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2007 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; + +import org.springframework.beans.BeansException; + +/** + * Thrown when a bean doesn't match the expected type. + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +public class BeanNotOfRequiredTypeException extends BeansException { + + /** The name of the instance that was of the wrong type */ + private String beanName; + + /** The required type */ + private Class requiredType; + + /** The offending type */ + private Class actualType; + + + /** + * Create a new BeanNotOfRequiredTypeException. + * @param beanName the name of the bean requested + * @param requiredType the required type + * @param actualType the actual type returned, which did not match + * the expected type + */ + public BeanNotOfRequiredTypeException(String beanName, Class requiredType, Class actualType) { + super("Bean named '" + beanName + "' must be of type [" + requiredType.getName() + + "], but was actually of type [" + actualType.getName() + "]"); + this.beanName = beanName; + this.requiredType = requiredType; + this.actualType = actualType; + } + + + /** + * Return the name of the instance that was of the wrong type. + */ + public String getBeanName() { + return this.beanName; + } + + /** + * Return the expected type for the bean. + */ + public Class getRequiredType() { + return this.requiredType; + } + + /** + * Return the actual type of the instance found. + */ + public Class getActualType() { + return this.actualType; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/CannotLoadBeanClassException.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/CannotLoadBeanClassException.java new file mode 100644 index 0000000000..3b15815ea6 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/CannotLoadBeanClassException.java @@ -0,0 +1,96 @@ +/* + * Copyright 2002-2007 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; + +import org.springframework.beans.FatalBeanException; + +/** + * Exception thrown when the BeanFactory cannot load the specified class + * of a given bean. + * + * @author Juergen Hoeller + * @since 2.0 + */ +public class CannotLoadBeanClassException extends FatalBeanException { + + private String resourceDescription; + + private String beanName; + + private String beanClassName; + + + /** + * Create a new CannotLoadBeanClassException. + * @param resourceDescription description of the resource + * that the bean definition came from + * @param beanName the name of the bean requested + * @param beanClassName the name of the bean class + * @param cause the root cause + */ + public CannotLoadBeanClassException( + String resourceDescription, String beanName, String beanClassName, ClassNotFoundException cause) { + + super("Cannot find class [" + beanClassName + "] for bean with name '" + beanName + + "' defined in " + resourceDescription, cause); + this.resourceDescription = resourceDescription; + this.beanName = beanName; + this.beanClassName = beanClassName; + } + + /** + * Create a new CannotLoadBeanClassException. + * @param resourceDescription description of the resource + * that the bean definition came from + * @param beanName the name of the bean requested + * @param beanClassName the name of the bean class + * @param cause the root cause + */ + public CannotLoadBeanClassException( + String resourceDescription, String beanName, String beanClassName, LinkageError cause) { + + super("Error loading class [" + beanClassName + "] for bean with name '" + beanName + + "' defined in " + resourceDescription + ": problem with class file or dependent class", cause); + this.resourceDescription = resourceDescription; + this.beanName = beanName; + this.beanClassName = beanClassName; + } + + + /** + * Return the description of the resource that the bean + * definition came from. + */ + public String getResourceDescription() { + return this.resourceDescription; + } + + /** + * Return the name of the bean requested. + */ + public String getBeanName() { + return this.beanName; + } + + /** + * Return the name of the class we were trying to load. + */ + public String getBeanClassName() { + return this.beanClassName; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/DisposableBean.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/DisposableBean.java new file mode 100644 index 0000000000..12fdeb853b --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/DisposableBean.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2005 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; + +/** + * Interface to be implemented by beans that want to release resources + * on destruction. A BeanFactory is supposed to invoke the destroy + * method if it disposes a cached singleton. An application context + * is supposed to dispose all of its singletons on close. + * + *

An alternative to implementing DisposableBean is specifying a custom + * destroy-method, for example in an XML bean definition. + * For a list of all bean lifecycle methods, see the BeanFactory javadocs. + * + * @author Juergen Hoeller + * @since 12.08.2003 + * @see org.springframework.beans.factory.support.RootBeanDefinition#getDestroyMethodName + * @see org.springframework.context.ConfigurableApplicationContext#close + */ +public interface DisposableBean { + + /** + * Invoked by a BeanFactory on destruction of a singleton. + * @throws Exception in case of shutdown errors. + * Exceptions will get logged but not rethrown to allow + * other beans to release their resources too. + */ + void destroy() throws Exception; + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/FactoryBean.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/FactoryBean.java new file mode 100644 index 0000000000..8a0472f2be --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/FactoryBean.java @@ -0,0 +1,120 @@ +/* + * Copyright 2002-2008 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; + +/** + * Interface to be implemented by objects used within a {@link BeanFactory} + * which are themselves factories. If a bean implements this interface, + * it is used as a factory for an object to expose, not directly as a bean + * instance that will be exposed itself. + * + *

NB: A bean that implements this interface cannot be used as a + * normal bean. A FactoryBean is defined in a bean style, but the + * object exposed for bean references ({@link #getObject()} is always + * the object that it creates. + * + *

FactoryBeans can support singletons and prototypes, and can + * either create objects lazily on demand or eagerly on startup. + * The {@link SmartFactoryBean} interface allows for exposing + * more fine-grained behavioral metadata. + * + *

This interface is heavily used within the framework itself, for + * example for the AOP {@link org.springframework.aop.framework.ProxyFactoryBean} + * or the {@link org.springframework.jndi.JndiObjectFactoryBean}. + * It can be used for application components as well; however, + * this is not common outside of infrastructure code. + * + *

NOTE: FactoryBean objects participate in the containing + * BeanFactory's synchronization of bean creation. There is usually no + * need for internal synchronization other than for purposes of lazy + * initialization within the FactoryBean itself (or the like). + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 08.03.2003 + * @see org.springframework.beans.factory.BeanFactory + * @see org.springframework.aop.framework.ProxyFactoryBean + * @see org.springframework.jndi.JndiObjectFactoryBean + */ +public interface FactoryBean { + + /** + * Return an instance (possibly shared or independent) of the object + * managed by this factory. + *

As with a {@link BeanFactory}, this allows support for both the + * Singleton and Prototype design pattern. + *

If this FactoryBean is not fully initialized yet at the time of + * the call (for example because it is involved in a circular reference), + * throw a corresponding {@link FactoryBeanNotInitializedException}. + *

As of Spring 2.0, FactoryBeans are allowed to return null + * objects. The factory will consider this as normal value to be used; it + * will not throw a FactoryBeanNotInitializedException in this case anymore. + * FactoryBean implementations are encouraged to throw + * FactoryBeanNotInitializedException themselves now, as appropriate. + * @return an instance of the bean (can be null) + * @throws Exception in case of creation errors + * @see FactoryBeanNotInitializedException + */ + Object getObject() throws Exception; + + /** + * Return the type of object that this FactoryBean creates, + * or null if not known in advance. + *

This allows one to check for specific types of beans without + * instantiating objects, for example on autowiring. + *

In the case of implementations that are creating a singleton object, + * this method should try to avoid singleton creation as far as possible; + * it should rather estimate the type in advance. + * For prototypes, returning a meaningful type here is advisable too. + *

This method can be called before this FactoryBean has + * been fully initialized. It must not rely on state created during + * initialization; of course, it can still use such state if available. + *

NOTE: Autowiring will simply ignore FactoryBeans that return + * null here. Therefore it is highly recommended to implement + * this method properly, using the current state of the FactoryBean. + * @return the type of object that this FactoryBean creates, + * or null if not known at the time of the call + * @see ListableBeanFactory#getBeansOfType + */ + Class getObjectType(); + + /** + * Is the object managed by this factory a singleton? That is, + * will {@link #getObject()} always return the same object + * (a reference that can be cached)? + *

NOTE: If a FactoryBean indicates to hold a singleton object, + * the object returned from getObject() might get cached + * by the owning BeanFactory. Hence, do not return true + * unless the FactoryBean always exposes the same reference. + *

The singleton status of the FactoryBean itself will generally + * be provided by the owning BeanFactory; usually, it has to be + * defined as singleton there. + *

NOTE: This method returning false does not + * necessarily indicate that returned objects are independent instances. + * An implementation of the extended {@link SmartFactoryBean} interface + * may explicitly indicate independent instances through its + * {@link SmartFactoryBean#isPrototype()} method. Plain {@link FactoryBean} + * implementations which do not implement this extended interface are + * simply assumed to always return independent instances if the + * isSingleton() implementation returns false. + * @return whether the exposed object is a singleton + * @see #getObject() + * @see SmartFactoryBean#isPrototype() + */ + boolean isSingleton(); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/FactoryBeanNotInitializedException.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/FactoryBeanNotInitializedException.java new file mode 100644 index 0000000000..bffb2c14f9 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/FactoryBeanNotInitializedException.java @@ -0,0 +1,54 @@ +/* + * Copyright 2002-2007 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; + +import org.springframework.beans.FatalBeanException; + +/** + * Exception to be thrown from a FactoryBean's getObject() method + * if the bean is not fully initialized yet, for example because it is involved + * in a circular reference. + * + *

Note: A circular reference with a FactoryBean cannot be solved by eagerly + * caching singleton instances like with normal beans. The reason is that + * every FactoryBean needs to be fully initialized before it can + * return the created bean, while only specific normal beans need + * to be initialized - that is, if a collaborating bean actually invokes + * them on initialization instead of just storing the reference. + * + * @author Juergen Hoeller + * @since 30.10.2003 + * @see FactoryBean#getObject() + */ +public class FactoryBeanNotInitializedException extends FatalBeanException { + + /** + * Create a new FactoryBeanNotInitializedException with the default message. + */ + public FactoryBeanNotInitializedException() { + super("FactoryBean is not fully initialized yet"); + } + + /** + * Create a new FactoryBeanNotInitializedException with the given message. + * @param msg the detail message + */ + public FactoryBeanNotInitializedException(String msg) { + super(msg); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/HierarchicalBeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/HierarchicalBeanFactory.java new file mode 100644 index 0000000000..20eb7845b3 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/HierarchicalBeanFactory.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2006 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; + +/** + * Sub-interface implemented by bean factories that can be part + * of a hierarchy. + * + *

The corresponding setParentBeanFactory method for bean + * factories that allow setting the parent in a configurable + * fashion can be found in the ConfigurableBeanFactory interface. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 07.07.2003 + * @see org.springframework.beans.factory.config.ConfigurableBeanFactory#setParentBeanFactory + */ +public interface HierarchicalBeanFactory extends BeanFactory { + + /** + * Return the parent bean factory, or null if there is none. + */ + BeanFactory getParentBeanFactory(); + + /** + * Return whether the local bean factory contains a bean of the given name, + * ignoring beans defined in ancestor contexts. + *

This is an alternative to containsBean, ignoring a bean + * of the given name from an ancestor bean factory. + * @param name the name of the bean to query + * @return whether a bean with the given name is defined in the local factory + * @see org.springframework.beans.factory.BeanFactory#containsBean + */ + boolean containsLocalBean(String name); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/InitializingBean.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/InitializingBean.java new file mode 100644 index 0000000000..a21912d31c --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/InitializingBean.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2005 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; + +/** + * Interface to be implemented by beans that need to react once all their + * properties have been set by a BeanFactory: for example, to perform custom + * initialization, or merely to check that all mandatory properties have been set. + * + *

An alternative to implementing InitializingBean is specifying a custom + * init-method, for example in an XML bean definition. + * For a list of all bean lifecycle methods, see the BeanFactory javadocs. + * + * @author Rod Johnson + * @see BeanNameAware + * @see BeanFactoryAware + * @see BeanFactory + * @see org.springframework.beans.factory.support.RootBeanDefinition#getInitMethodName + * @see org.springframework.context.ApplicationContextAware + */ +public interface InitializingBean { + + /** + * Invoked by a BeanFactory after it has set all bean properties supplied + * (and satisfied BeanFactoryAware and ApplicationContextAware). + *

This method allows the bean instance to perform initialization only + * possible when all bean properties have been set and to throw an + * exception in the event of misconfiguration. + * @throws Exception in the event of misconfiguration (such + * as failure to set an essential property) or if initialization fails. + */ + void afterPropertiesSet() throws Exception; + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/ListableBeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/ListableBeanFactory.java new file mode 100644 index 0000000000..fe3f909dcd --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/ListableBeanFactory.java @@ -0,0 +1,213 @@ +/* + * Copyright 2002-2008 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; + +import java.util.Map; + +import org.springframework.beans.BeansException; + +/** + * Extension of the {@link BeanFactory} interface to be implemented by bean factories + * that can enumerate all their bean instances, rather than attempting bean lookup + * by name one by one as requested by clients. BeanFactory implementations that + * preload all their bean definitions (such as XML-based factories) may implement + * this interface. + * + *

If this is a {@link HierarchicalBeanFactory}, the return values will not + * take any BeanFactory hierarchy into account, but will relate only to the beans + * defined in the current factory. Use the {@link BeanFactoryUtils} helper class + * to consider beans in ancestor factories too. + * + *

The methods in this interface will just respect bean definitions of this factory. + * They will ignore any singleton beans that have been registered by other means like + * {@link org.springframework.beans.factory.config.ConfigurableBeanFactory}'s + * registerSingleton method, with the exception of + * getBeanNamesOfType and getBeansOfType which will check + * such manually registered singletons too. Of course, BeanFactory's getBean + * does allow transparent access to such special beans as well. However, in typical + * scenarios, all beans will be defined by external bean definitions anyway, so most + * applications don't need to worry about this differentation. + * + *

NOTE: With the exception of getBeanDefinitionCount + * and containsBeanDefinition, the methods in this interface + * are not designed for frequent invocation. Implementations may be slow. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 16 April 2001 + * @see HierarchicalBeanFactory + * @see BeanFactoryUtils + */ +public interface ListableBeanFactory extends BeanFactory { + + /** + * Check if this bean factory contains a bean definition with the given name. + *

Does not consider any hierarchy this factory may participate in, + * and ignores any singleton beans that have been registered by + * other means than bean definitions. + * @param beanName the name of the bean to look for + * @return if this bean factory contains a bean definition with the given name + * @see #containsBean + */ + boolean containsBeanDefinition(String beanName); + + /** + * Return the number of beans defined in the factory. + *

Does not consider any hierarchy this factory may participate in, + * and ignores any singleton beans that have been registered by + * other means than bean definitions. + * @return the number of beans defined in the factory + */ + int getBeanDefinitionCount(); + + /** + * Return the names of all beans defined in this factory. + *

Does not consider any hierarchy this factory may participate in, + * and ignores any singleton beans that have been registered by + * other means than bean definitions. + * @return the names of all beans defined in this factory, + * or an empty array if none defined + */ + String[] getBeanDefinitionNames(); + + /** + * Return the names of beans matching the given type (including subclasses), + * judging from either bean definitions or the value of getObjectType + * in the case of FactoryBeans. + *

NOTE: This method introspects top-level beans only. It does not + * check nested beans which might match the specified type as well. + *

Does consider objects created by FactoryBeans, which means that FactoryBeans + * will get initialized. If the object created by the FactoryBean doesn't match, + * the raw FactoryBean itself will be matched against the type. + *

Does not consider any hierarchy this factory may participate in. + * Use BeanFactoryUtils' beanNamesForTypeIncludingAncestors + * to include beans in ancestor factories too. + *

Note: Does not ignore singleton beans that have been registered + * by other means than bean definitions. + *

This version of getBeanNamesForType matches all kinds of beans, + * be it singletons, prototypes, or FactoryBeans. In most implementations, the + * result will be the same as for getBeanNamesOfType(type, true, true). + *

Bean names returned by this method should always return bean names in the + * order of definition in the backend configuration, as far as possible. + * @param type the class or interface to match, or null for all bean names + * @return the names of beans (or objects created by FactoryBeans) matching + * the given object type (including subclasses), or an empty array if none + * @see FactoryBean#getObjectType + * @see BeanFactoryUtils#beanNamesForTypeIncludingAncestors(ListableBeanFactory, Class) + */ + String[] getBeanNamesForType(Class type); + + /** + * Return the names of beans matching the given type (including subclasses), + * judging from either bean definitions or the value of getObjectType + * in the case of FactoryBeans. + *

NOTE: This method introspects top-level beans only. It does not + * check nested beans which might match the specified type as well. + *

Does consider objects created by FactoryBeans if the "allowEagerInit" flag is set, + * which means that FactoryBeans will get initialized. If the object created by the + * FactoryBean doesn't match, the raw FactoryBean itself will be matched against the + * type. If "allowEagerInit" is not set, only raw FactoryBeans will be checked + * (which doesn't require initialization of each FactoryBean). +$ *

Does not consider any hierarchy this factory may participate in. + * Use BeanFactoryUtils' beanNamesForTypeIncludingAncestors + * to include beans in ancestor factories too. + *

Note: Does not ignore singleton beans that have been registered + * by other means than bean definitions. + *

Bean names returned by this method should always return bean names in the + * order of definition in the backend configuration, as far as possible. + * @param type the class or interface to match, or null for all bean names + * @param includeNonSingletons whether to include prototype or scoped beans too + * or just singletons (also applies to FactoryBeans) + * @param allowEagerInit whether to initialize lazy-init singletons and + * objects created by FactoryBeans (or by factory methods with a + * "factory-bean" reference) for the type check. Note that FactoryBeans need to be + * eagerly initialized to determine their type: So be aware that passing in "true" + * for this flag will initialize FactoryBeans and "factory-bean" references. + * @return the names of beans (or objects created by FactoryBeans) matching + * the given object type (including subclasses), or an empty array if none + * @see FactoryBean#getObjectType + * @see BeanFactoryUtils#beanNamesForTypeIncludingAncestors(ListableBeanFactory, Class, boolean, boolean) + */ + String[] getBeanNamesForType(Class type, boolean includeNonSingletons, boolean allowEagerInit); + + /** + * Return the bean instances that match the given object type (including + * subclasses), judging from either bean definitions or the value of + * getObjectType in the case of FactoryBeans. + *

NOTE: This method introspects top-level beans only. It does not + * check nested beans which might match the specified type as well. + *

Does consider objects created by FactoryBeans, which means that FactoryBeans + * will get initialized. If the object created by the FactoryBean doesn't match, + * the raw FactoryBean itself will be matched against the type. + *

Does not consider any hierarchy this factory may participate in. + * Use BeanFactoryUtils' beansOfTypeIncludingAncestors + * to include beans in ancestor factories too. + *

Note: Does not ignore singleton beans that have been registered + * by other means than bean definitions. + *

This version of getBeansOfType matches all kinds of beans, be it + * singletons, prototypes, or FactoryBeans. In most implementations, the + * result will be the same as for getBeansOfType(type, true, true). + *

The Map returned by this method should always return bean names and + * corresponding bean instances in the order of definition in the + * backend configuration, as far as possible. + * @param type the class or interface to match, or null for all concrete beans + * @return a Map with the matching beans, containing the bean names as + * keys and the corresponding bean instances as values + * @throws BeansException if a bean could not be created + * @since 1.1.2 + * @see FactoryBean#getObjectType + * @see BeanFactoryUtils#beansOfTypeIncludingAncestors(ListableBeanFactory, Class) + */ + Map getBeansOfType(Class type) throws BeansException; + + /** + * Return the bean instances that match the given object type (including + * subclasses), judging from either bean definitions or the value of + * getObjectType in the case of FactoryBeans. + *

NOTE: This method introspects top-level beans only. It does not + * check nested beans which might match the specified type as well. + *

Does consider objects created by FactoryBeans if the "allowEagerInit" flag is set, + * which means that FactoryBeans will get initialized. If the object created by the + * FactoryBean doesn't match, the raw FactoryBean itself will be matched against the + * type. If "allowEagerInit" is not set, only raw FactoryBeans will be checked + * (which doesn't require initialization of each FactoryBean). + *

Does not consider any hierarchy this factory may participate in. + * Use BeanFactoryUtils' beansOfTypeIncludingAncestors + * to include beans in ancestor factories too. + *

Note: Does not ignore singleton beans that have been registered + * by other means than bean definitions. + *

The Map returned by this method should always return bean names and + * corresponding bean instances in the order of definition in the + * backend configuration, as far as possible. + * @param type the class or interface to match, or null for all concrete beans + * @param includeNonSingletons whether to include prototype or scoped beans too + * or just singletons (also applies to FactoryBeans) + * @param allowEagerInit whether to initialize lazy-init singletons and + * objects created by FactoryBeans (or by factory methods with a + * "factory-bean" reference) for the type check. Note that FactoryBeans need to be + * eagerly initialized to determine their type: So be aware that passing in "true" + * for this flag will initialize FactoryBeans and "factory-bean" references. + * @return a Map with the matching beans, containing the bean names as + * keys and the corresponding bean instances as values + * @throws BeansException if a bean could not be created + * @see FactoryBean#getObjectType + * @see BeanFactoryUtils#beansOfTypeIncludingAncestors(ListableBeanFactory, Class, boolean, boolean) + */ + Map getBeansOfType(Class type, boolean includeNonSingletons, boolean allowEagerInit) + throws BeansException; + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/NamedBean.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/NamedBean.java new file mode 100644 index 0000000000..e1e5ed33f5 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/NamedBean.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2006 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; + +/** + * Counterpart of BeanNameAware. Returns the bean name of an object. + * + *

This interface can be introduced to avoid a brittle dependence + * on bean name in objects used with Spring IoC and Spring AOP. + * + * @author Rod Johnson + * @since 2.0 + * @see BeanNameAware + */ +public interface NamedBean { + + /** + * Return the name of this bean in a Spring bean factory. + */ + String getBeanName(); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/NoSuchBeanDefinitionException.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/NoSuchBeanDefinitionException.java new file mode 100644 index 0000000000..ab04476e7b --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/NoSuchBeanDefinitionException.java @@ -0,0 +1,95 @@ +/* + * Copyright 2002-2007 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; + +import org.springframework.beans.BeansException; + +/** + * Exception thrown when a BeanFactory is asked for a bean + * instance name for which it cannot find a definition. + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +public class NoSuchBeanDefinitionException extends BeansException { + + /** Name of the missing bean */ + private String beanName; + + /** Required bean type */ + private Class beanType; + + + /** + * Create a new NoSuchBeanDefinitionException. + * @param name the name of the missing bean + */ + public NoSuchBeanDefinitionException(String name) { + super("No bean named '" + name + "' is defined"); + this.beanName = name; + } + + /** + * Create a new NoSuchBeanDefinitionException. + * @param name the name of the missing bean + * @param message detailed message describing the problem + */ + public NoSuchBeanDefinitionException(String name, String message) { + super("No bean named '" + name + "' is defined: " + message); + this.beanName = name; + } + + /** + * Create a new NoSuchBeanDefinitionException. + * @param type required type of bean + * @param message detailed message describing the problem + */ + public NoSuchBeanDefinitionException(Class type, String message) { + super("No unique bean of type [" + type.getName() + "] is defined: " + message); + this.beanType = type; + } + + /** + * Create a new NoSuchBeanDefinitionException. + * @param type required type of bean + * @param dependencyDescription a description of the originating dependency + * @param message detailed message describing the problem + */ + public NoSuchBeanDefinitionException(Class type, String dependencyDescription, String message) { + super("No matching bean of type [" + type.getName() + "] found for dependency [" + + dependencyDescription + "]: " + message); + this.beanType = type; + } + + + /** + * Return the name of the missing bean, + * if it was a lookup by name that failed. + */ + public String getBeanName() { + return this.beanName; + } + + /** + * Return the required type of bean, + * if it was a lookup by type that failed. + */ + public Class getBeanType() { + return this.beanType; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/ObjectFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/ObjectFactory.java new file mode 100644 index 0000000000..a84d222c0f --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/ObjectFactory.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2007 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; + +import org.springframework.beans.BeansException; + +/** + * Defines a factory which can return an Object instance + * (possibly shared or independent) when invoked. + * + *

This interface is typically used to encapsulate a generic factory which + * returns a new instance (prototype) of some target object on each invocation. + * + *

This interface is similar to {@link FactoryBean}, but implementations + * of the latter are normally meant to be defined as SPI instances in a + * {@link BeanFactory}, while implementations of this class are normally meant + * to be fed as an API to other beans (through injection). As such, the + * getObject() method has different exception handling behavior. + * + * @author Colin Sampaleanu + * @since 1.0.2 + * @see FactoryBean + */ +public interface ObjectFactory { + + /** + * Return an instance (possibly shared or independent) + * of the object managed by this factory. + * @return an instance of the bean (should never be null) + * @throws BeansException in case of creation errors + */ + Object getObject() throws BeansException; + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/SmartFactoryBean.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/SmartFactoryBean.java new file mode 100644 index 0000000000..f429b76acd --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/SmartFactoryBean.java @@ -0,0 +1,75 @@ +/* + * Copyright 2002-2007 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; + +/** + * Extension of the {@link FactoryBean} interface. Implementations may + * indicate whether they always return independent instances, for the + * case where their {@link #isSingleton()} implementation returning + * false does not clearly indicate independent instances. + * + *

Plain {@link FactoryBean} implementations which do not implement + * this extended interface are simply assumed to always return independent + * instances if their {@link #isSingleton()} implementation returns + * false; the exposed object is only accessed on demand. + * + *

NOTE: This interface is a special purpose interface, mainly for + * internal use within the framework and within collaborating frameworks. + * In general, application-provided FactoryBeans should simply implement + * the plain {@link FactoryBean} interface. New methods might be added + * to this extended interface even in point releases. + * + * @author Juergen Hoeller + * @since 2.0.3 + * @see #isPrototype() + * @see #isSingleton() + */ +public interface SmartFactoryBean extends FactoryBean { + + /** + * Is the object managed by this factory a prototype? That is, + * will {@link #getObject()} always return an independent instance? + *

The prototype status of the FactoryBean itself will generally + * be provided by the owning {@link BeanFactory}; usually, it has to be + * defined as singleton there. + *

This method is supposed to strictly check for independent instances; + * it should not return true for scoped objects or other + * kinds of non-singleton, non-independent objects. For this reason, + * this is not simply the inverted form of {@link #isSingleton()}. + * @return whether the exposed object is a prototype + * @see #getObject() + * @see #isSingleton() + */ + boolean isPrototype(); + + /** + * Does this FactoryBean expect eager initialization, that is, + * eagerly initialize itself as well as expect eager initialization + * of its singleton object (if any)? + *

A standard FactoryBean is not expected to initialize eagerly: + * Its {@link #getObject()} will only be called for actual access, even + * in case of a singleton object. Returning true from this + * method suggests that {@link #getObject()} should be called eagerly, + * also applying post-processors eagerly. This may make sense in case + * of a {@link #isSingleton() singleton} object, in particular if + * post-processors expect to be applied on startup. + * @return whether eager initialization applies + * @see org.springframework.beans.factory.config.ConfigurableListableBeanFactory#preInstantiateSingletons() + */ + boolean isEagerInit(); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/UnsatisfiedDependencyException.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/UnsatisfiedDependencyException.java new file mode 100644 index 0000000000..ae2cc0de0a --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/UnsatisfiedDependencyException.java @@ -0,0 +1,94 @@ +/* + * Copyright 2002-2008 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; + +import org.springframework.beans.BeansException; +import org.springframework.util.ClassUtils; + +/** + * Exception thrown when a bean depends on other beans or simple properties + * that were not specified in the bean factory definition, although + * dependency checking was enabled. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 03.09.2003 + */ +public class UnsatisfiedDependencyException extends BeanCreationException { + + /** + * Create a new UnsatisfiedDependencyException. + * @param resourceDescription description of the resource that the bean definition came from + * @param beanName the name of the bean requested + * @param propertyName the name of the bean property that couldn't be satisfied + * @param msg the detail message + */ + public UnsatisfiedDependencyException( + String resourceDescription, String beanName, String propertyName, String msg) { + + super(resourceDescription, beanName, + "Unsatisfied dependency expressed through bean property '" + propertyName + "'" + + (msg != null ? ": " + msg : "")); + } + + /** + * Create a new UnsatisfiedDependencyException. + * @param resourceDescription description of the resource that the bean definition came from + * @param beanName the name of the bean requested + * @param propertyName the name of the bean property that couldn't be satisfied + * @param ex the bean creation exception that indicated the unsatisfied dependency + */ + public UnsatisfiedDependencyException( + String resourceDescription, String beanName, String propertyName, BeansException ex) { + + this(resourceDescription, beanName, propertyName, (ex != null ? ": " + ex.getMessage() : "")); + initCause(ex); + } + + /** + * Create a new UnsatisfiedDependencyException. + * @param resourceDescription description of the resource that the bean definition came from + * @param beanName the name of the bean requested + * @param ctorArgIndex the index of the constructor argument that couldn't be satisfied + * @param ctorArgType the type of the constructor argument that couldn't be satisfied + * @param msg the detail message + */ + public UnsatisfiedDependencyException( + String resourceDescription, String beanName, int ctorArgIndex, Class ctorArgType, String msg) { + + super(resourceDescription, beanName, + "Unsatisfied dependency expressed through constructor argument with index " + + ctorArgIndex + " of type [" + ClassUtils.getQualifiedName(ctorArgType) + "]" + + (msg != null ? ": " + msg : "")); + } + + /** + * Create a new UnsatisfiedDependencyException. + * @param resourceDescription description of the resource that the bean definition came from + * @param beanName the name of the bean requested + * @param ctorArgIndex the index of the constructor argument that couldn't be satisfied + * @param ctorArgType the type of the constructor argument that couldn't be satisfied + * @param ex the bean creation exception that indicated the unsatisfied dependency + */ + public UnsatisfiedDependencyException( + String resourceDescription, String beanName, int ctorArgIndex, Class ctorArgType, BeansException ex) { + + this(resourceDescription, beanName, ctorArgIndex, ctorArgType, (ex != null ? ": " + ex.getMessage() : "")); + initCause(ex); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/access/BeanFactoryLocator.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/access/BeanFactoryLocator.java new file mode 100644 index 0000000000..b4308aa7e9 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/access/BeanFactoryLocator.java @@ -0,0 +1,68 @@ +/* + * Copyright 2002-2007 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.access; + +import org.springframework.beans.BeansException; + +/** + * Defines a contract for the lookup, use, and release of a + * {@link org.springframework.beans.factory.BeanFactory}, + * or a BeanFactory subclass such as an + * {@link org.springframework.context.ApplicationContext}. + * + *

Where this interface is implemented as a singleton class such as + * {@link SingletonBeanFactoryLocator}, the Spring team strongly + * suggests that it be used sparingly and with caution. By far the vast majority + * of the code inside an application is best written in a Dependency Injection + * style, where that code is served out of a + * BeanFactory/ApplicationContext container, and has + * its own dependencies supplied by the container when it is created. However, + * even such a singleton implementation sometimes has its use in the small glue + * layers of code that is sometimes needed to tie other code together. For + * example, third party code may try to construct new objects directly, without + * the ability to force it to get these objects out of a BeanFactory. + * If the object constructed by the third party code is just a small stub or + * proxy, which then uses an implementation of this class to get a + * BeanFactory from which it gets the real object, to which it + * delegates, then proper Dependency Injection has been achieved. + * + *

As another example, in a complex J2EE app with multiple layers, with each + * layer having its own ApplicationContext definition (in a + * hierarchy), a class like SingletonBeanFactoryLocator may be used + * to demand load these contexts. + * + * @author Colin Sampaleanu + * @see org.springframework.beans.factory.BeanFactory + * @see org.springframework.context.access.DefaultLocatorFactory + * @see org.springframework.context.ApplicationContext + */ +public interface BeanFactoryLocator { + + /** + * Use the {@link org.springframework.beans.factory.BeanFactory} (or derived + * interface such as {@link org.springframework.context.ApplicationContext}) + * specified by the factoryKey parameter. + *

The definition is possibly loaded/created as needed. + * @param factoryKey a resource name specifying which BeanFactory the + * BeanFactoryLocator must return for usage. The actual meaning of the + * resource name is specific to the implementation of BeanFactoryLocator. + * @return the BeanFactory instance, wrapped as a {@link BeanFactoryReference} object + * @throws BeansException if there is an error loading or accessing the BeanFactory + */ + BeanFactoryReference useBeanFactory(String factoryKey) throws BeansException; + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/access/BeanFactoryReference.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/access/BeanFactoryReference.java new file mode 100644 index 0000000000..953b0cacf4 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/access/BeanFactoryReference.java @@ -0,0 +1,59 @@ +/* + * Copyright 2002-2007 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.access; + +import org.springframework.beans.FatalBeanException; +import org.springframework.beans.factory.BeanFactory; + +/** + * Used to track a reference to a {@link BeanFactory} obtained through + * a {@link BeanFactoryLocator}. + * + *

It is safe to call {@link #release()} multiple times, but + * {@link #getFactory()} must not be called after calling release. + * + * @author Colin Sampaleanu + * @see BeanFactoryLocator + * @see org.springframework.context.access.ContextBeanFactoryReference + */ +public interface BeanFactoryReference { + + /** + * Return the {@link BeanFactory} instance held by this reference. + * @throws IllegalStateException if invoked after release() has been called + */ + BeanFactory getFactory(); + + /** + * Indicate that the {@link BeanFactory} instance referred to by this object is not + * needed any longer by the client code which obtained the {@link BeanFactoryReference}. + *

Depending on the actual implementation of {@link BeanFactoryLocator}, and + * the actual type of BeanFactory, this may possibly not actually + * do anything; alternately in the case of a 'closeable' BeanFactory + * or derived class (such as {@link org.springframework.context.ApplicationContext}) + * may 'close' it, or may 'close' it once no more references remain. + *

In an EJB usage scenario this would normally be called from + * ejbRemove() and ejbPassivate(). + *

This is safe to call multiple times. + * @throws FatalBeanException if the BeanFactory cannot be released + * @see BeanFactoryLocator + * @see org.springframework.context.access.ContextBeanFactoryReference + * @see org.springframework.context.ConfigurableApplicationContext#close() + */ + void release() throws FatalBeanException; + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/access/BootstrapException.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/access/BootstrapException.java new file mode 100644 index 0000000000..b502384d70 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/access/BootstrapException.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2005 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.access; + +import org.springframework.beans.FatalBeanException; + +/** + * Exception thrown if a bean factory could not be loaded by a bootstrap class. + * + * @author Rod Johnson + * @since 02.12.2002 + */ +public class BootstrapException extends FatalBeanException { + + /** + * Create a new BootstrapException with the specified message. + * @param msg the detail message + */ + public BootstrapException(String msg) { + super(msg); + } + + /** + * Create a new BootstrapException with the specified message + * and root cause. + * @param msg the detail message + * @param cause the root cause + */ + public BootstrapException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/access/SingletonBeanFactoryLocator.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/access/SingletonBeanFactoryLocator.java new file mode 100644 index 0000000000..70cda453a3 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/access/SingletonBeanFactoryLocator.java @@ -0,0 +1,540 @@ +/* + * Copyright 2002-2008 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.access; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.BeansException; +import org.springframework.beans.FatalBeanException; +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternUtils; + +/** + *

Keyed-singleton implementation of {@link BeanFactoryLocator}, + * which accesses shared Spring {@link BeanFactory} instances.

+ * + *

Please see the warning in BeanFactoryLocator's javadoc about appropriate usage + * of singleton style BeanFactoryLocator implementations. It is the opinion of the + * Spring team that the use of this class and similar classes is unnecessary except + * (sometimes) for a small amount of glue code. Excessive usage will lead to code + * that is more tightly coupled, and harder to modify or test.

+ * + *

In this implementation, a BeanFactory is built up from one or more XML + * definition file fragments, accessed as resources. The default resource name + * searched for is 'classpath*:beanRefFactory.xml', with the Spring-standard + * 'classpath*:' prefix ensuring that if the classpath contains multiple copies + * of this file (perhaps one in each component jar) they will be combined. To + * override the default resource name, instead of using the no-arg + * {@link #getInstance()} method, use the {@link #getInstance(String selector)} + * variant, which will treat the 'selector' argument as the resource name to + * search for.

+ * + *

The purpose of this 'outer' BeanFactory is to create and hold a copy of one + * or more 'inner' BeanFactory or ApplicationContext instances, and allow those + * to be obtained either directly or via an alias. As such, this class provides + * both singleton style access to one or more BeanFactories/ApplicationContexts, + * and also a level of indirection, allowing multiple pieces of code, which are + * not able to work in a Dependency Injection fashion, to refer to and use the + * same target BeanFactory/ApplicationContext instance(s), by different names.

+ * + *

Consider an example application scenario: + * + *

    + *
  • com.mycompany.myapp.util.applicationContext.xml - + * ApplicationContext definition file which defines beans for 'util' layer. + *
  • com.mycompany.myapp.dataaccess-applicationContext.xml - + * ApplicationContext definition file which defines beans for 'data access' layer. + * Depends on the above. + *
  • com.mycompany.myapp.services.applicationContext.xml - + * ApplicationContext definition file which defines beans for 'services' layer. + * Depends on the above. + *
+ * + *

In an ideal scenario, these would be combined to create one ApplicationContext, + * or created as three hierarchical ApplicationContexts, by one piece of code + * somewhere at application startup (perhaps a Servlet filter), from which all other + * code in the application would flow, obtained as beans from the context(s). However + * when third party code enters into the picture, things can get problematic. If the + * third party code needs to create user classes, which should normally be obtained + * from a Spring BeanFactory/ApplicationContext, but can handle only newInstance() + * style object creation, then some extra work is required to actually access and + * use object from a BeanFactory/ApplicationContext. One solutions is to make the + * class created by the third party code be just a stub or proxy, which gets the + * real object from a BeanFactory/ApplicationContext, and delegates to it. However, + * it is is not normally workable for the stub to create the BeanFactory on each + * use, as depending on what is inside it, that can be an expensive operation. + * Additionally, there is a fairly tight coupling between the stub and the name of + * the definition resource for the BeanFactory/ApplicationContext. This is where + * SingletonBeanFactoryLocator comes in. The stub can obtain a + * SingletonBeanFactoryLocator instance, which is effectively a singleton, and + * ask it for an appropriate BeanFactory. A subsequent invocation (assuming the + * same class loader is involved) by the stub or another piece of code, will obtain + * the same instance. The simple aliasing mechanism allows the context to be asked + * for by a name which is appropriate for (or describes) the user. The deployer can + * match alias names to actual context names. + * + *

Another use of SingletonBeanFactoryLocator, is to demand-load/use one or more + * BeanFactories/ApplicationContexts. Because the definition can contain one of more + * BeanFactories/ApplicationContexts, which can be independent or in a hierarchy, if + * they are set to lazy-initialize, they will only be created when actually requested + * for use. + * + *

Given the above-mentioned three ApplicationContexts, consider the simplest + * SingletonBeanFactoryLocator usage scenario, where there is only one single + * beanRefFactory.xml definition file: + * + *

<?xml version="1.0" encoding="UTF-8"?>
+ * <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
+ * 
+ * <beans>
+ * 
+ *   <bean id="com.mycompany.myapp"
+ *         class="org.springframework.context.support.ClassPathXmlApplicationContext">
+ *     <constructor-arg>
+ *       <list>
+ *         <value>com/mycompany/myapp/util/applicationContext.xml</value>
+ *         <value>com/mycompany/myapp/dataaccess/applicationContext.xml</value>
+ *         <value>com/mycompany/myapp/dataaccess/services.xml</value>
+ *       </list>
+ *     </constructor-arg>
+ *   </bean>
+ * 
+ * </beans>
+ * 
+ * + * The client code is as simple as: + * + *
+ * BeanFactoryLocator bfl = SingletonBeanFactoryLocator.getInstance();
+ * BeanFactoryReference bf = bfl.useBeanFactory("com.mycompany.myapp");
+ * // now use some bean from factory 
+ * MyClass zed = bf.getFactory().getBean("mybean");
+ * 
+ * + * Another relatively simple variation of the beanRefFactory.xml definition file could be: + * + *
<?xml version="1.0" encoding="UTF-8"?>
+ * <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
+ * 
+ * <beans>
+ * 
+ *   <bean id="com.mycompany.myapp.util" lazy-init="true"
+ *         class="org.springframework.context.support.ClassPathXmlApplicationContext">
+ *     <constructor-arg>
+ *       <value>com/mycompany/myapp/util/applicationContext.xml</value>
+ *     </constructor-arg>
+ *   </bean>
+ * 
+ *   <!-- child of above -->
+ *   <bean id="com.mycompany.myapp.dataaccess" lazy-init="true"
+ *         class="org.springframework.context.support.ClassPathXmlApplicationContext">
+ *     <constructor-arg>
+ *       <list><value>com/mycompany/myapp/dataaccess/applicationContext.xml</value></list>
+ *     </constructor-arg>
+ *     <constructor-arg>
+ *       <ref bean="com.mycompany.myapp.util"/>
+ *     </constructor-arg>
+ *   </bean>
+ * 
+ *   <!-- child of above -->
+ *   <bean id="com.mycompany.myapp.services" lazy-init="true"
+ *         class="org.springframework.context.support.ClassPathXmlApplicationContext">
+ *     <constructor-arg>
+ *       <list><value>com/mycompany/myapp/dataaccess.services.xml</value></value>
+ *     </constructor-arg>
+ *     <constructor-arg>
+ *       <ref bean="com.mycompany.myapp.dataaccess"/>
+ *     </constructor-arg>
+ *   </bean>
+ * 
+ *   <!-- define an alias -->
+ *   <bean id="com.mycompany.myapp.mypackage"
+ *         class="java.lang.String">
+ *     <constructor-arg>
+ *       <value>com.mycompany.myapp.services</value>
+ *     </constructor-arg>
+ *   </bean>
+ * 
+ * </beans>
+ * 
+ * + *

In this example, there is a hierarchy of three contexts created. The (potential) + * advantage is that if the lazy flag is set to true, a context will only be created + * if it's actually used. If there is some code that is only needed some of the time, + * this mechanism can save some resources. Additionally, an alias to the last context + * has been created. Aliases allow usage of the idiom where client code asks for a + * context with an id which represents the package or module the code is in, and the + * actual definition file(s) for the SingletonBeanFactoryLocator maps that id to + * a real context id. + * + *

A final example is more complex, with a beanRefFactory.xml for every module. + * All the files are automatically combined to create the final definition. + * + *

beanRefFactory.xml file inside jar for util module: + * + *

<?xml version="1.0" encoding="UTF-8"?>
+ * <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
+ * 
+ * <beans>
+ *   <bean id="com.mycompany.myapp.util" lazy-init="true"
+ *        class="org.springframework.context.support.ClassPathXmlApplicationContext">
+ *     <constructor-arg>
+ *       <value>com/mycompany/myapp/util/applicationContext.xml</value>
+ *     </constructor-arg>
+ *   </bean>
+ * </beans>
+ * 
+ * + * beanRefFactory.xml file inside jar for data-access module:
+ * + *
<?xml version="1.0" encoding="UTF-8"?>
+ * <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
+ * 
+ * <beans>
+ *   <!-- child of util -->
+ *   <bean id="com.mycompany.myapp.dataaccess" lazy-init="true"
+ *        class="org.springframework.context.support.ClassPathXmlApplicationContext">
+ *     <constructor-arg>
+ *       <list><value>com/mycompany/myapp/dataaccess/applicationContext.xml</value></list>
+ *     </constructor-arg>
+ *     <constructor-arg>
+ *       <ref bean="com.mycompany.myapp.util"/>
+ *     </constructor-arg>
+ *   </bean>
+ * </beans>
+ * 
+ * + * beanRefFactory.xml file inside jar for services module: + * + *
<?xml version="1.0" encoding="UTF-8"?>
+ * <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
+ * 
+ * <beans>
+ *   <!-- child of data-access -->
+ *   <bean id="com.mycompany.myapp.services" lazy-init="true"
+ *        class="org.springframework.context.support.ClassPathXmlApplicationContext">
+ *     <constructor-arg>
+ *       <list><value>com/mycompany/myapp/dataaccess/services.xml</value></list>
+ *     </constructor-arg>
+ *     <constructor-arg>
+ *       <ref bean="com.mycompany.myapp.dataaccess"/>
+ *     </constructor-arg>
+ *   </bean>
+ * </beans>
+ * 
+ * + * beanRefFactory.xml file inside jar for mypackage module. This doesn't + * create any of its own contexts, but allows the other ones to be referred to be + * a name known to this module: + * + *
<?xml version="1.0" encoding="UTF-8"?>
+ * <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
+ * 
+ * <beans>
+ *   <!-- define an alias for "com.mycompany.myapp.services" -->
+ *   <alias name="com.mycompany.myapp.services" alias="com.mycompany.myapp.mypackage"/>
+ * </beans>
+ * 
+ * + * @author Colin Sampaleanu + * @author Juergen Hoeller + * @see org.springframework.context.access.ContextSingletonBeanFactoryLocator + * @see org.springframework.context.access.DefaultLocatorFactory + */ +public class SingletonBeanFactoryLocator implements BeanFactoryLocator { + + private static final String DEFAULT_RESOURCE_LOCATION = "classpath*:beanRefFactory.xml"; + + protected static final Log logger = LogFactory.getLog(SingletonBeanFactoryLocator.class); + + /** The keyed BeanFactory instances */ + private static Map instances = new HashMap(); + + + /** + * Returns an instance which uses the default "classpath*:beanRefFactory.xml", + * as the name of the definition file(s). All resources returned by calling the + * current thread context ClassLoader's getResources method with + * this name will be combined to create a BeanFactory definition set. + * @return the corresponding BeanFactoryLocator instance + * @throws BeansException in case of factory loading failure + */ + public static BeanFactoryLocator getInstance() throws BeansException { + return getInstance(null); + } + + /** + * Returns an instance which uses the the specified selector, as the name of the + * definition file(s). In the case of a name with a Spring 'classpath*:' prefix, + * or with no prefix, which is treated the same, the current thread context + * ClassLoader's getResources method will be called with this value + * to get all resources having that name. These resources will then be combined to + * form a definition. In the case where the name uses a Spring 'classpath:' prefix, + * or a standard URL prefix, then only one resource file will be loaded as the + * definition. + * @param selector the name of the resource(s) which will be read and + * combined to form the definition for the BeanFactoryLocator instance. + * Any such files must form a valid BeanFactory definition. + * @return the corresponding BeanFactoryLocator instance + * @throws BeansException in case of factory loading failure + */ + public static BeanFactoryLocator getInstance(String selector) throws BeansException { + String resourceLocation = selector; + if (resourceLocation == null) { + resourceLocation = DEFAULT_RESOURCE_LOCATION; + } + + // For backwards compatibility, we prepend 'classpath*:' to the selector name if there + // is no other prefix (i.e. classpath*:, classpath:, or some URL prefix. + if (!ResourcePatternUtils.isUrl(resourceLocation)) { + resourceLocation = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resourceLocation; + } + + synchronized (instances) { + if (logger.isTraceEnabled()) { + logger.trace("SingletonBeanFactoryLocator.getInstance(): instances.hashCode=" + + instances.hashCode() + ", instances=" + instances); + } + BeanFactoryLocator bfl = (BeanFactoryLocator) instances.get(resourceLocation); + if (bfl == null) { + bfl = new SingletonBeanFactoryLocator(resourceLocation); + instances.put(resourceLocation, bfl); + } + return bfl; + } + } + + + // We map BeanFactoryGroup objects by String keys, and by the definition object. + private final Map bfgInstancesByKey = new HashMap(); + + private final Map bfgInstancesByObj = new HashMap(); + + private final String resourceLocation; + + + /** + * Constructor which uses the the specified name as the resource name + * of the definition file(s). + * @param resourceLocation the Spring resource location to use + * (either a URL or a "classpath:" / "classpath*:" pseudo URL) + */ + protected SingletonBeanFactoryLocator(String resourceLocation) { + this.resourceLocation = resourceLocation; + } + + public BeanFactoryReference useBeanFactory(String factoryKey) throws BeansException { + synchronized (this.bfgInstancesByKey) { + BeanFactoryGroup bfg = (BeanFactoryGroup) this.bfgInstancesByKey.get(this.resourceLocation); + + if (bfg != null) { + bfg.refCount++; + } + else { + // This group definition doesn't exist, we need to try to load it. + if (logger.isTraceEnabled()) { + logger.trace("Factory group with resource name [" + this.resourceLocation + + "] requested. Creating new instance."); + } + + // Create the BeanFactory but don't initialize it. + BeanFactory groupContext = createDefinition(this.resourceLocation, factoryKey); + + // Record its existence now, before instantiating any singletons. + bfg = new BeanFactoryGroup(); + bfg.definition = groupContext; + bfg.refCount = 1; + this.bfgInstancesByKey.put(this.resourceLocation, bfg); + this.bfgInstancesByObj.put(groupContext, bfg); + + // Now initialize the BeanFactory. This may cause a re-entrant invocation + // of this method, but since we've already added the BeanFactory to our + // mappings, the next time it will be found and simply have its + // reference count incremented. + try { + initializeDefinition(groupContext); + } + catch (BeansException ex) { + this.bfgInstancesByKey.remove(this.resourceLocation); + this.bfgInstancesByObj.remove(groupContext); + throw new BootstrapException("Unable to initialize group definition. " + + "Group resource name [" + this.resourceLocation + "], factory key [" + factoryKey + "]", ex); + } + } + + try { + BeanFactory beanFactory = null; + if (factoryKey != null) { + beanFactory = (BeanFactory) bfg.definition.getBean(factoryKey, BeanFactory.class); + } + else if (bfg.definition instanceof ListableBeanFactory) { + beanFactory = (BeanFactory) + BeanFactoryUtils.beanOfType((ListableBeanFactory) bfg.definition, BeanFactory.class); + } + else { + throw new IllegalStateException( + "Factory key is null, and underlying factory is not a ListableBeanFactory: " + bfg.definition); + } + return new CountingBeanFactoryReference(beanFactory, bfg.definition); + } + catch (BeansException ex) { + throw new BootstrapException("Unable to return specified BeanFactory instance: factory key [" + + factoryKey + "], from group with resource name [" + this.resourceLocation + "]", ex); + } + + } + } + + /** + * Actually creates definition in the form of a BeanFactory, given a resource name + * which supports standard Spring resource prefixes ('classpath:', 'classpath*:', etc.) + * This is split out as a separate method so that subclasses can override the actual + * type used (to be an ApplicationContext, for example). + *

The default implementation simply builds a + * {@link org.springframework.beans.factory.support.DefaultListableBeanFactory} + * and populates it using an + * {@link org.springframework.beans.factory.xml.XmlBeanDefinitionReader}. + *

This method should not instantiate any singletons. That function is performed + * by {@link #initializeDefinition initializeDefinition()}, which should also be + * overridden if this method is. + * @param resourceLocation the resource location for this factory group + * @param factoryKey the bean name of the factory to obtain + * @return the corresponding BeanFactory reference + */ + protected BeanFactory createDefinition(String resourceLocation, String factoryKey) { + DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); + XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); + ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); + + try { + Resource[] configResources = resourcePatternResolver.getResources(resourceLocation); + if (configResources.length == 0) { + throw new FatalBeanException("Unable to find resource for specified definition. " + + "Group resource name [" + this.resourceLocation + "], factory key [" + factoryKey + "]"); + } + reader.loadBeanDefinitions(configResources); + } + catch (IOException ex) { + throw new BeanDefinitionStoreException( + "Error accessing bean definition resource [" + this.resourceLocation + "]", ex); + } + catch (BeanDefinitionStoreException ex) { + throw new FatalBeanException("Unable to load group definition: " + + "group resource name [" + this.resourceLocation + "], factory key [" + factoryKey + "]", ex); + } + + return factory; + } + + /** + * Instantiate singletons and do any other normal initialization of the factory. + * Subclasses that override {@link #createDefinition createDefinition()} should + * also override this method. + * @param groupDef the factory returned by {@link #createDefinition createDefinition()} + */ + protected void initializeDefinition(BeanFactory groupDef) { + if (groupDef instanceof ConfigurableListableBeanFactory) { + ((ConfigurableListableBeanFactory) groupDef).preInstantiateSingletons(); + } + } + + /** + * Destroy definition in separate method so subclass may work with other definition types. + * @param groupDef the factory returned by {@link #createDefinition createDefinition()} + * @param selector the resource location for this factory group + */ + protected void destroyDefinition(BeanFactory groupDef, String selector) { + if (groupDef instanceof ConfigurableBeanFactory) { + if (logger.isTraceEnabled()) { + logger.trace("Factory group with selector '" + selector + + "' being released, as there are no more references to it"); + } + ((ConfigurableBeanFactory) groupDef).destroySingletons(); + } + } + + + /** + * We track BeanFactory instances with this class. + */ + private static class BeanFactoryGroup { + + private BeanFactory definition; + + private int refCount = 0; + } + + + /** + * BeanFactoryReference implementation for this locator. + */ + private class CountingBeanFactoryReference implements BeanFactoryReference { + + private BeanFactory beanFactory; + + private BeanFactory groupContextRef; + + public CountingBeanFactoryReference(BeanFactory beanFactory, BeanFactory groupContext) { + this.beanFactory = beanFactory; + this.groupContextRef = groupContext; + } + + public BeanFactory getFactory() { + return this.beanFactory; + } + + // Note that it's legal to call release more than once! + public void release() throws FatalBeanException { + synchronized (bfgInstancesByKey) { + BeanFactory savedRef = this.groupContextRef; + if (savedRef != null) { + this.groupContextRef = null; + BeanFactoryGroup bfg = (BeanFactoryGroup) bfgInstancesByObj.get(savedRef); + if (bfg != null) { + bfg.refCount--; + if (bfg.refCount == 0) { + destroyDefinition(savedRef, resourceLocation); + bfgInstancesByKey.remove(resourceLocation); + bfgInstancesByObj.remove(savedRef); + } + } + else { + // This should be impossible. + logger.warn("Tried to release a SingletonBeanFactoryLocator group definition " + + "more times than it has actually been used. Resource name [" + resourceLocation + "]"); + } + } + } + } + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/access/el/SimpleSpringBeanELResolver.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/access/el/SimpleSpringBeanELResolver.java new file mode 100644 index 0000000000..91e6de00d4 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/access/el/SimpleSpringBeanELResolver.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2008 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.access.el; + +import javax.el.ELContext; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.util.Assert; + +/** + * Simple concrete variant of {@link SpringBeanELResolver}, delegating + * to a given {@link BeanFactory} that the resolver was constructed with. + * + * @author Juergen Hoeller + * @since 2.5.2 + */ +public class SimpleSpringBeanELResolver extends SpringBeanELResolver { + + private final BeanFactory beanFactory; + + + /** + * Create a new SimpleSpringBeanELResolver for the given BeanFactory. + * @param beanFactory the Spring BeanFactory to delegate to + */ + public SimpleSpringBeanELResolver(BeanFactory beanFactory) { + Assert.notNull(beanFactory, "BeanFactory must not be null"); + this.beanFactory = beanFactory; + } + + protected BeanFactory getBeanFactory(ELContext elContext) { + return this.beanFactory; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/access/el/SpringBeanELResolver.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/access/el/SpringBeanELResolver.java new file mode 100644 index 0000000000..1f69ea7610 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/access/el/SpringBeanELResolver.java @@ -0,0 +1,111 @@ +/* + * Copyright 2002-2008 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.access.el; + +import java.beans.FeatureDescriptor; +import java.util.Iterator; + +import javax.el.ELContext; +import javax.el.ELException; +import javax.el.ELResolver; +import javax.el.PropertyNotWritableException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.BeanFactory; + +/** + * Unified EL ELResolver that delegates to a Spring BeanFactory, + * resolving name references to Spring-defined beans. + * + * @author Juergen Hoeller + * @since 2.5.2 + * @see org.springframework.web.jsf.el.SpringBeanFacesELResolver + */ +public abstract class SpringBeanELResolver extends ELResolver { + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + + public Object getValue(ELContext elContext, Object base, Object property) throws ELException { + if (base == null) { + String beanName = property.toString(); + BeanFactory bf = getBeanFactory(elContext); + if (bf.containsBean(beanName)) { + if (logger.isTraceEnabled()) { + logger.trace("Successfully resolved variable '" + beanName + "' in Spring BeanFactory"); + } + elContext.setPropertyResolved(true); + return bf.getBean(beanName); + } + } + return null; + } + + public Class getType(ELContext elContext, Object base, Object property) throws ELException { + if (base == null) { + String beanName = property.toString(); + BeanFactory bf = getBeanFactory(elContext); + if (bf.containsBean(beanName)) { + elContext.setPropertyResolved(true); + return bf.getType(beanName); + } + } + return null; + } + + public void setValue(ELContext elContext, Object base, Object property, Object value) throws ELException { + if (base == null) { + String beanName = property.toString(); + BeanFactory bf = getBeanFactory(elContext); + if (bf.containsBean(beanName)) { + throw new PropertyNotWritableException( + "Variable '" + beanName + "' refers to a Spring bean which by definition is not writable"); + } + } + } + + public boolean isReadOnly(ELContext elContext, Object base, Object property) throws ELException { + if (base == null) { + String beanName = property.toString(); + BeanFactory bf = getBeanFactory(elContext); + if (bf.containsBean(beanName)) { + return true; + } + } + return false; + } + + public Iterator getFeatureDescriptors(ELContext elContext, Object base) { + return null; + } + + public Class getCommonPropertyType(ELContext elContext, Object base) { + return Object.class; + } + + + /** + * Retrieve the Spring BeanFactory to delegate bean name resolution to. + * @param elContext the current ELContext + * @return the Spring BeanFactory (never null) + */ + protected abstract BeanFactory getBeanFactory(ELContext elContext); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/access/el/package.html b/org.springframework.beans/src/main/java/org/springframework/beans/factory/access/el/package.html new file mode 100644 index 0000000000..b96555ac89 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/access/el/package.html @@ -0,0 +1,7 @@ + + + +Support classes for accessing a Spring BeanFactory from Unified EL. + + + diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/access/package.html b/org.springframework.beans/src/main/java/org/springframework/beans/factory/access/package.html new file mode 100644 index 0000000000..7b1495be75 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/access/package.html @@ -0,0 +1,11 @@ + + + +Helper infrastructure to locate and access bean factories. + +

Note: This package is only relevant for special sharing of bean +factories, for example behind EJB facades. It is not used in a +typical web application or standalone application. + + + diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/AnnotatedBeanDefinition.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/AnnotatedBeanDefinition.java new file mode 100644 index 0000000000..4e53fec1ad --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/AnnotatedBeanDefinition.java @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2007 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.annotation; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.core.type.AnnotationMetadata; + +/** + * Extended {@link org.springframework.beans.factory.config.BeanDefinition} + * interface that exposes {@link org.springframework.core.type.AnnotationMetadata} + * about its bean class - without requiring the class to be loaded yet. + * + * @author Juergen Hoeller + * @since 2.5 + * @see AnnotatedGenericBeanDefinition + * @see org.springframework.core.type.AnnotationMetadata + */ +public interface AnnotatedBeanDefinition extends BeanDefinition { + + /** + * Obtain the annotation metadata (as well as basic class metadata) + * for this bean definition's bean class. + * @return the annotation metadata object (never null) + */ + AnnotationMetadata getMetadata(); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/AnnotatedGenericBeanDefinition.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/AnnotatedGenericBeanDefinition.java new file mode 100644 index 0000000000..3f76c25221 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/AnnotatedGenericBeanDefinition.java @@ -0,0 +1,58 @@ +/* + * Copyright 2002-2008 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.annotation; + +import org.springframework.beans.factory.support.GenericBeanDefinition; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.StandardAnnotationMetadata; + +/** + * Extension of the {@link org.springframework.beans.factory.support.GenericBeanDefinition} + * class, adding support for annotation metadata exposed through the + * {@link AnnotatedBeanDefinition} interface. + * + *

This GenericBeanDefinition variant is mainly useful for testing code that expects + * to operate on an AnnotatedBeanDefinition, for example strategy implementations + * in Spring's component scanning support (where the default definition class is + * {@link org.springframework.context.annotation.ScannedGenericBeanDefinition}, + * which also implements the AnnotatedBeanDefinition interface). + * + * @author Juergen Hoeller + * @since 2.5 + * @see AnnotatedBeanDefinition#getMetadata() + * @see org.springframework.core.type.StandardAnnotationMetadata + */ +public class AnnotatedGenericBeanDefinition extends GenericBeanDefinition implements AnnotatedBeanDefinition { + + private final AnnotationMetadata annotationMetadata; + + + /** + * Create a new AnnotatedGenericBeanDefinition for the given bean class. + * @param beanClass the loaded bean class + */ + public AnnotatedGenericBeanDefinition(Class beanClass) { + setBeanClass(beanClass); + this.annotationMetadata = new StandardAnnotationMetadata(beanClass); + } + + + public final AnnotationMetadata getMetadata() { + return this.annotationMetadata; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/AnnotationBeanWiringInfoResolver.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/AnnotationBeanWiringInfoResolver.java new file mode 100644 index 0000000000..ef1a2e4b63 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/AnnotationBeanWiringInfoResolver.java @@ -0,0 +1,79 @@ +/* + * Copyright 2002-2007 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.annotation; + +import org.springframework.beans.factory.wiring.BeanWiringInfo; +import org.springframework.beans.factory.wiring.BeanWiringInfoResolver; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * {@link org.springframework.beans.factory.wiring.BeanWiringInfoResolver} that + * uses the Configurable annotation to identify which classes need autowiring. + * The bean name to look up will be taken from the {@link Configurable} annotation + * if specified; otherwise the default will be the fully-qualified name of the + * class being configured. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + * @see Configurable + * @see org.springframework.beans.factory.wiring.ClassNameBeanWiringInfoResolver + */ +public class AnnotationBeanWiringInfoResolver implements BeanWiringInfoResolver { + + public BeanWiringInfo resolveWiringInfo(Object beanInstance) { + Assert.notNull(beanInstance, "Bean instance must not be null"); + Configurable annotation = beanInstance.getClass().getAnnotation(Configurable.class); + return (annotation != null ? buildWiringInfo(beanInstance, annotation) : null); + } + + /** + * Build the BeanWiringInfo for the given Configurable annotation. + * @param beanInstance the bean instance + * @param annotation the Configurable annotation found on the bean class + * @return the resolved BeanWiringInfo + */ + protected BeanWiringInfo buildWiringInfo(Object beanInstance, Configurable annotation) { + if (!Autowire.NO.equals(annotation.autowire())) { + return new BeanWiringInfo(annotation.autowire().value(), annotation.dependencyCheck()); + } + else { + if (!"".equals(annotation.value())) { + // explicitly specified bean name + return new BeanWiringInfo(annotation.value(), false); + } + else { + // default bean name + return new BeanWiringInfo(getDefaultBeanName(beanInstance), true); + } + } + } + + /** + * Determine the default bean name for the specified bean instance. + *

The default implementation returns the superclass name for a CGLIB + * proxy and the name of the plain bean class else. + * @param beanInstance the bean instance to build a default name for + * @return the default bean name to use + * @see org.springframework.util.ClassUtils#getUserClass(Class) + */ + protected String getDefaultBeanName(Object beanInstance) { + return ClassUtils.getUserClass(beanInstance).getName(); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/Autowire.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/Autowire.java new file mode 100644 index 0000000000..bb1eba859d --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/Autowire.java @@ -0,0 +1,81 @@ +/* + * Copyright 2002-2007 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.annotation; + +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; + +/** + * Enumeration determining autowiring status: that is, whether a bean should + * have its dependencies automatically injected by the Spring container using + * setter injection. This is a core concept in Spring DI. + * + *

Available for use in annotation-based configurations, such as for the + * AspectJ AnnotationBeanConfigurer aspect. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + * @see org.springframework.beans.factory.annotation.Configurable + * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory + */ +public enum Autowire { + + /** + * Constant that indicates that autowiring information was not specified. + * In some cases it may be necessary to specify autowiring status, + * but merely confirm that this should be inherited from an enclosing + * container definition scope. + */ + INHERITED(-1), + + /** + * Constant that indicates no autowiring at all. + */ + NO(AutowireCapableBeanFactory.AUTOWIRE_NO), + + /** + * Constant that indicates autowiring bean properties by name. + */ + BY_NAME(AutowireCapableBeanFactory.AUTOWIRE_BY_NAME), + + /** + * Constant that indicates autowiring bean properties by type. + */ + BY_TYPE(AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE); + + + private final int value; + + + Autowire(int value) { + this.value = value; + } + + public int value() { + return this.value; + } + + /** + * Return whether this represents an actual autowiring value. + * @return whether actual autowiring was specified + * (either BY_NAME or BY_TYPE) + */ + public boolean isAutowire() { + return (this == BY_NAME || this == BY_TYPE); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/Autowired.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/Autowired.java new file mode 100644 index 0000000000..c326508253 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/Autowired.java @@ -0,0 +1,67 @@ +/* + * Copyright 2002-2008 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.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a constructor, field, setter method or config method as to be + * autowired by Spring's dependency injection facilities. + * + *

Only one constructor (at max) of any given bean class may carry this + * annotation, indicating the constructor to autowire when used as a Spring + * bean. Such a constructor does not have to be public. + * + *

Fields are injected right after construction of a bean, before any + * config methods are invoked. Such a config field does not have to be public. + * + *

Config methods may have an arbitrary name and any number of arguments; + * each of those arguments will be autowired with a matching bean in the + * Spring container. Bean property setter methods are effectively just + * a special case of such a general config method. Such config methods + * do not have to be public. + * + *

In the case of multiple argument methods, the 'required' parameter is + * applicable for all arguments. + * + *

In case of a {@link java.util.Collection} or {@link java.util.Map} + * dependency type, the container will autowire all beans matching the + * declared value type. In case of a Map, the keys must be declared as + * type String and will be resolved to the corresponding bean names. + * + *

Please do consult the javadoc for the {@link AutowiredAnnotationBeanPostProcessor} + * class (which, by default, checks for the presence of this annotation). + * + * @author Juergen Hoeller + * @author Mark Fisher + * @since 2.5 + * @see AutowiredAnnotationBeanPostProcessor + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD}) +public @interface Autowired { + + /** + * Declares whether the annotated dependency is required. + *

Defaults to true. + */ + boolean required() default true; + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java new file mode 100644 index 0000000000..39ccb77617 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java @@ -0,0 +1,548 @@ +/* + * Copyright 2002-2008 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.annotation; + +import java.beans.PropertyDescriptor; +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.PropertyValues; +import org.springframework.beans.TypeConverter; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.DependencyDescriptor; +import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.core.GenericTypeResolver; +import org.springframework.core.MethodParameter; +import org.springframework.core.Ordered; +import org.springframework.core.PriorityOrdered; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; + +/** + * {@link org.springframework.beans.factory.config.BeanPostProcessor} implementation + * that autowires annotated fields, setter methods and arbitrary config methods. + * Such members to be injected are detected through a Java 5 annotation: + * by default, Spring's {@link Autowired} annotation. + * + *

Only one constructor (at max) of any given bean class may carry this + * annotation with the 'required' parameter set to true, + * indicating the constructor to autowire when used as a Spring bean. + * If multiple non-required constructors carry the annotation, they + * will be considered as candidates for autowiring. The constructor with + * the greatest number of dependencies that can be satisfied by matching + * beans in the Spring container will be chosen. If none of the candidates + * can be satisfied, then a default constructor (if present) will be used. + * An annotated constructor does not have to be public. + * + *

Fields are injected right after construction of a bean, before any + * config methods are invoked. Such a config field does not have to be public. + * + *

Config methods may have an arbitrary name and any number of arguments; + * each of those arguments will be autowired with a matching bean in the + * Spring container. Bean property setter methods are effectively just + * a special case of such a general config method. Such config methods + * do not have to be public. + * + *

Note: A default AutowiredAnnotationBeanPostProcessor will be registered + * by the "context:annotation-config" and "context:component-scan" XML tags. + * Remove or turn off the default annotation configuration there if you intend + * to specify a custom AutowiredAnnotationBeanPostProcessor bean definition. + * + * @author Juergen Hoeller + * @author Mark Fisher + * @since 2.5 + * @see #setAutowiredAnnotationType + * @see Autowired + * @see org.springframework.context.annotation.CommonAnnotationBeanPostProcessor + */ +public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter + implements MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware { + + protected final Log logger = LogFactory.getLog(AutowiredAnnotationBeanPostProcessor.class); + + private Class autowiredAnnotationType = Autowired.class; + + private String requiredParameterName = "required"; + + private boolean requiredParameterValue = true; + + private int order = Ordered.LOWEST_PRECEDENCE - 2; + + private ConfigurableListableBeanFactory beanFactory; + + private final Map, Constructor[]> candidateConstructorsCache = + new ConcurrentHashMap, Constructor[]>(); + + private final Map, InjectionMetadata> injectionMetadataCache = + new ConcurrentHashMap, InjectionMetadata>(); + + + /** + * Set the 'autowired' annotation type, to be used on constructors, fields, + * setter methods and arbitrary config methods. + *

The default autowired annotation type is the Spring-provided + * {@link Autowired} annotation. + *

This setter property exists so that developers can provide their own + * (non-Spring-specific) annotation type to indicate that a member is + * supposed to be autowired. + */ + public void setAutowiredAnnotationType(Class autowiredAnnotationType) { + Assert.notNull(autowiredAnnotationType, "'autowiredAnnotationType' must not be null"); + this.autowiredAnnotationType = autowiredAnnotationType; + } + + /** + * Return the 'autowired' annotation type. + */ + protected Class getAutowiredAnnotationType() { + return this.autowiredAnnotationType; + } + + /** + * Set the name of a parameter of the annotation that specifies + * whether it is required. + * @see #setRequiredParameterValue(boolean) + */ + public void setRequiredParameterName(String requiredParameterName) { + this.requiredParameterName = requiredParameterName; + } + + /** + * Set the boolean value that marks a dependency as required + *

For example if using 'required=true' (the default), + * this value should be true; but if using + * 'optional=false', this value should be false. + * @see #setRequiredParameterName(String) + */ + public void setRequiredParameterValue(boolean requiredParameterValue) { + this.requiredParameterValue = requiredParameterValue; + } + + public void setOrder(int order) { + this.order = order; + } + + public int getOrder() { + return this.order; + } + + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + if (!(beanFactory instanceof ConfigurableListableBeanFactory)) { + throw new IllegalArgumentException( + "AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory"); + } + this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; + } + + + public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class beanType, String beanName) { + if (beanType != null) { + InjectionMetadata metadata = findAutowiringMetadata(beanType); + metadata.checkConfigMembers(beanDefinition); + } + } + + public Constructor[] determineCandidateConstructors(Class beanClass, String beanName) throws BeansException { + // Quick check on the concurrent map first, with minimal locking. + Constructor[] candidateConstructors = this.candidateConstructorsCache.get(beanClass); + if (candidateConstructors == null) { + synchronized (this.candidateConstructorsCache) { + candidateConstructors = this.candidateConstructorsCache.get(beanClass); + if (candidateConstructors == null) { + Constructor[] rawCandidates = beanClass.getDeclaredConstructors(); + List candidates = new ArrayList(rawCandidates.length); + Constructor requiredConstructor = null; + Constructor defaultConstructor = null; + for (int i = 0; i < rawCandidates.length; i++) { + Constructor candidate = rawCandidates[i]; + Annotation annotation = candidate.getAnnotation(getAutowiredAnnotationType()); + if (annotation != null) { + if (requiredConstructor != null) { + throw new BeanCreationException("Invalid autowire-marked constructor: " + candidate + + ". Found another constructor with 'required' Autowired annotation: " + requiredConstructor); + } + if (candidate.getParameterTypes().length == 0) { + throw new IllegalStateException("Autowired annotation requires at least one argument: " + candidate); + } + boolean required = determineRequiredStatus(annotation); + if (required) { + if (!candidates.isEmpty()) { + throw new BeanCreationException("Invalid autowire-marked constructors: " + candidates + + ". Found another constructor with 'required' Autowired annotation: " + requiredConstructor); + } + requiredConstructor = candidate; + } + candidates.add(candidate); + } + else if (candidate.getParameterTypes().length == 0) { + defaultConstructor = candidate; + } + } + if (!candidates.isEmpty()) { + // Add default constructor to list of optional constructors, as fallback. + if (requiredConstructor == null && defaultConstructor != null) { + candidates.add(defaultConstructor); + } + candidateConstructors = (Constructor[]) candidates.toArray(new Constructor[candidates.size()]); + } + else { + candidateConstructors = new Constructor[0]; + } + this.candidateConstructorsCache.put(beanClass, candidateConstructors); + } + } + } + return (candidateConstructors.length > 0 ? candidateConstructors : null); + } + + public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException { + InjectionMetadata metadata = findAutowiringMetadata(bean.getClass()); + try { + metadata.injectFields(bean, beanName); + } + catch (Throwable ex) { + throw new BeanCreationException(beanName, "Autowiring of fields failed", ex); + } + return true; + } + + public PropertyValues postProcessPropertyValues( + PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException { + + InjectionMetadata metadata = findAutowiringMetadata(bean.getClass()); + try { + metadata.injectMethods(bean, beanName, pvs); + } + catch (Throwable ex) { + throw new BeanCreationException(beanName, "Autowiring of methods failed", ex); + } + return pvs; + } + + /** + * 'Native' processing method for direct calls with an arbitrary target + * instance, resolving all of its fields and methods which are annotated + * with @Autowired. + * @param bean the target instance to process + */ + public void processInjection(Object bean) throws BeansException { + InjectionMetadata metadata = findAutowiringMetadata(bean.getClass()); + try { + metadata.injectFields(bean, null); + metadata.injectMethods(bean, null, null); + } + catch (Throwable ex) { + throw new BeanCreationException("Autowiring of fields/methods failed", ex); + } + } + + + private InjectionMetadata findAutowiringMetadata(final Class clazz) { + // Quick check on the concurrent map first, with minimal locking. + InjectionMetadata metadata = this.injectionMetadataCache.get(clazz); + if (metadata == null) { + synchronized (this.injectionMetadataCache) { + metadata = this.injectionMetadataCache.get(clazz); + if (metadata == null) { + final InjectionMetadata newMetadata = new InjectionMetadata(clazz); + ReflectionUtils.doWithFields(clazz, new ReflectionUtils.FieldCallback() { + public void doWith(Field field) { + Annotation annotation = field.getAnnotation(getAutowiredAnnotationType()); + if (annotation != null) { + if (Modifier.isStatic(field.getModifiers())) { + throw new IllegalStateException("Autowired annotation is not supported on static fields"); + } + boolean required = determineRequiredStatus(annotation); + newMetadata.addInjectedField(new AutowiredFieldElement(field, required)); + } + } + }); + ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() { + public void doWith(Method method) { + Annotation annotation = method.getAnnotation(getAutowiredAnnotationType()); + if (annotation != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) { + if (Modifier.isStatic(method.getModifiers())) { + throw new IllegalStateException("Autowired annotation is not supported on static methods"); + } + if (method.getParameterTypes().length == 0) { + throw new IllegalStateException("Autowired annotation requires at least one argument: " + method); + } + boolean required = determineRequiredStatus(annotation); + PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method); + newMetadata.addInjectedMethod(new AutowiredMethodElement(method, required, pd)); + } + } + }); + metadata = newMetadata; + this.injectionMetadataCache.put(clazz, metadata); + } + } + } + return metadata; + } + + /** + * Obtain all beans of the given type as autowire candidates. + * @param type the type of the bean + * @return the target beans, or an empty Collection if no bean of this type is found + * @throws BeansException if bean retrieval failed + */ + protected Map findAutowireCandidates(Class type) throws BeansException { + if (this.beanFactory == null) { + throw new IllegalStateException("No BeanFactory configured - " + + "override the getBeanOfType method or specify the 'beanFactory' property"); + } + return BeanFactoryUtils.beansOfTypeIncludingAncestors(this.beanFactory, type); + } + + /** + * Determine if the annotated field or method requires its dependency. + *

A 'required' dependency means that autowiring should fail when no beans + * are found. Otherwise, the autowiring process will simply bypass the field + * or method when no beans are found. + * @param annotation the Autowired annotation + * @return whether the annotation indicates that a dependency is required + */ + protected boolean determineRequiredStatus(Annotation annotation) { + try { + Method method = ReflectionUtils.findMethod(annotation.annotationType(), this.requiredParameterName); + return (this.requiredParameterValue == (Boolean) ReflectionUtils.invokeMethod(method, annotation)); + } + catch (Exception ex) { + // required by default + return true; + } + } + + /** + * Register the specified bean as dependent on the autowired beans. + */ + private void registerDependentBeans(String beanName, Set autowiredBeanNames) { + if (beanName != null) { + for (Iterator it = autowiredBeanNames.iterator(); it.hasNext();) { + String autowiredBeanName = (String) it.next(); + beanFactory.registerDependentBean(autowiredBeanName, beanName); + if (logger.isDebugEnabled()) { + logger.debug("Autowiring by type from bean name '" + beanName + + "' to bean named '" + autowiredBeanName + "'"); + } + } + } + } + + + /** + * Class representing injection information about an annotated field. + */ + private class AutowiredFieldElement extends InjectionMetadata.InjectedElement { + + private final boolean required; + + private volatile boolean cached = false; + + private volatile Object cachedFieldValue; + + public AutowiredFieldElement(Field field, boolean required) { + super(field, null); + this.required = required; + } + + @Override + protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable { + Field field = (Field) this.member; + try { + Object value = null; + if (this.cached) { + if (this.cachedFieldValue instanceof DependencyDescriptor) { + DependencyDescriptor descriptor = (DependencyDescriptor) this.cachedFieldValue; + TypeConverter typeConverter = beanFactory.getTypeConverter(); + value = beanFactory.resolveDependency(descriptor, beanName, null, typeConverter); + } + else if (this.cachedFieldValue instanceof RuntimeBeanReference) { + value = beanFactory.getBean(((RuntimeBeanReference) this.cachedFieldValue).getBeanName()); + } + else { + value = this.cachedFieldValue; + } + } + else { + Set autowiredBeanNames = new LinkedHashSet(1); + TypeConverter typeConverter = beanFactory.getTypeConverter(); + DependencyDescriptor descriptor = new DependencyDescriptor(field, this.required); + this.cachedFieldValue = descriptor; + value = beanFactory.resolveDependency(descriptor, beanName, autowiredBeanNames, typeConverter); + if (value != null) { + registerDependentBeans(beanName, autowiredBeanNames); + if (autowiredBeanNames.size() == 1) { + String autowiredBeanName = autowiredBeanNames.iterator().next(); + if (beanFactory.containsBean(autowiredBeanName)) { + if (beanFactory.isTypeMatch(autowiredBeanName, field.getType())) { + this.cachedFieldValue = new RuntimeBeanReference(autowiredBeanName); + } + } + } + } + else { + this.cachedFieldValue = null; + } + this.cached = true; + } + if (value != null) { + ReflectionUtils.makeAccessible(field); + field.set(bean, value); + } + } + catch (Throwable ex) { + throw new BeanCreationException("Could not autowire field: " + field, ex); + } + } + } + + + /** + * Class representing injection information about an annotated method. + */ + private class AutowiredMethodElement extends InjectionMetadata.InjectedElement { + + private final boolean required; + + private volatile boolean cached = false; + + private volatile Object[] cachedMethodArguments; + + public AutowiredMethodElement(Method method, boolean required, PropertyDescriptor pd) { + super(method, pd); + this.required = required; + } + + @Override + protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable { + if (this.skip == null && this.pd != null && pvs != null && pvs.contains(this.pd.getName())) { + // Explicit value provided as part of the bean definition. + this.skip = Boolean.TRUE; + } + if (this.skip != null && this.skip.booleanValue()) { + return; + } + Method method = (Method) this.member; + try { + Object[] arguments = null; + if (this.cached) { + if (this.cachedMethodArguments != null) { + arguments = new Object[this.cachedMethodArguments.length]; + for (int i = 0; i < arguments.length; i++) { + Object cachedArg = this.cachedMethodArguments[i]; + if (cachedArg instanceof DependencyDescriptor) { + DependencyDescriptor descriptor = (DependencyDescriptor) cachedArg; + TypeConverter typeConverter = beanFactory.getTypeConverter(); + arguments[i] = beanFactory.resolveDependency(descriptor, beanName, null, typeConverter); + } + else if (cachedArg instanceof RuntimeBeanReference) { + arguments[i] = beanFactory.getBean(((RuntimeBeanReference) cachedArg).getBeanName()); + } + else { + arguments[i] = cachedArg; + } + } + } + } + else { + Class[] paramTypes = method.getParameterTypes(); + arguments = new Object[paramTypes.length]; + Set autowiredBeanNames = new LinkedHashSet(arguments.length); + TypeConverter typeConverter = beanFactory.getTypeConverter(); + this.cachedMethodArguments = new Object[arguments.length]; + for (int i = 0; i < arguments.length; i++) { + MethodParameter methodParam = new MethodParameter(method, i); + GenericTypeResolver.resolveParameterType(methodParam, bean.getClass()); + DependencyDescriptor descriptor = new DependencyDescriptor(methodParam, this.required); + this.cachedMethodArguments[i] = descriptor; + arguments[i] = beanFactory.resolveDependency( + descriptor, beanName, autowiredBeanNames, typeConverter); + if (arguments[i] == null) { + arguments = null; + break; + } + } + if (arguments != null) { + registerDependentBeans(beanName, autowiredBeanNames); + if (autowiredBeanNames.size() == paramTypes.length) { + Iterator it = autowiredBeanNames.iterator(); + for (int i = 0; i < paramTypes.length; i++) { + String autowiredBeanName = it.next(); + if (beanFactory.containsBean(autowiredBeanName)) { + if (beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) { + this.cachedMethodArguments[i] = new RuntimeBeanReference(autowiredBeanName); + } + } + else { + this.cachedMethodArguments[i] = arguments[i]; + } + } + } + } + else { + this.cachedMethodArguments = null; + } + this.cached = true; + } + if (this.skip == null) { + if (this.pd != null && pvs instanceof MutablePropertyValues) { + ((MutablePropertyValues) pvs).registerProcessedProperty(this.pd.getName()); + } + this.skip = Boolean.FALSE; + } + if (arguments != null) { + ReflectionUtils.makeAccessible(method); + method.invoke(bean, arguments); + } + } + catch (InvocationTargetException ex) { + throw ex.getTargetException(); + } + catch (Throwable ex) { + throw new BeanCreationException("Could not autowire method: " + method, ex); + } + } + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/Configurable.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/Configurable.java new file mode 100644 index 0000000000..b19a404d03 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/Configurable.java @@ -0,0 +1,63 @@ +/* + * Copyright 2002-2007 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.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a class as being eligible for Spring-driven configuration. + * + *

Typically used with the AspectJ AnnotationBeanConfigurerAspect. + * + * @author Rod Johnson + * @author Rob Harrop + * @author Adrian Colyer + * @author Ramnivas Laddad + * @since 2.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +@Inherited +public @interface Configurable { + + /** + * The name of the bean definition that serves as the configuration template. + */ + String value() default ""; + + /** + * Are dependencies to be injected via autowiring? + */ + Autowire autowire() default Autowire.NO; + + /** + * Is dependency checking to be performed for configured objects? + */ + boolean dependencyCheck() default false; + + /** + * Are dependencies to be injected prior to the construction of an object? + */ + boolean preConstruction() default false; + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/CustomAutowireConfigurer.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/CustomAutowireConfigurer.java new file mode 100644 index 0000000000..7baf644e4e --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/CustomAutowireConfigurer.java @@ -0,0 +1,122 @@ +/* + * Copyright 2002-2007 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.annotation; + +import java.lang.annotation.Annotation; +import java.util.Iterator; +import java.util.Set; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.core.Ordered; +import org.springframework.util.ClassUtils; + +/** + * A {@link org.springframework.beans.factory.config.BeanFactoryPostProcessor} + * implementation that allows for convenient registration of custom autowire + * qualifier types. + * + *

+ * <bean id="customAutowireConfigurer" class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
+ *   <property name="customQualifierTypes">
+ *     <set>
+ *       <value>mypackage.MyQualifier</value>
+ *     </set>
+ *   </property>
+ * </bean>
+ * + * @author Mark Fisher + * @author Juergen Hoeller + * @since 2.5 + * @see org.springframework.beans.factory.annotation.Qualifier + */ +public class CustomAutowireConfigurer implements BeanFactoryPostProcessor, BeanClassLoaderAware, Ordered { + + private int order = Ordered.LOWEST_PRECEDENCE; // default: same as non-Ordered + + private Set customQualifierTypes; + + private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); + + + public void setOrder(int order) { + this.order = order; + } + + public int getOrder() { + return this.order; + } + + public void setBeanClassLoader(ClassLoader beanClassLoader) { + this.beanClassLoader = beanClassLoader; + } + + /** + * Register custom qualifier annotation types to be considered + * when autowiring beans. Each element of the provided set may + * be either a Class instance or a String representation of the + * fully-qualified class name of the custom annotation. + *

Note that any annotation that is itself annotated with Spring's + * {@link org.springframework.beans.factory.annotation.Qualifier} + * does not require explicit registration. + * @param customQualifierTypes the custom types to register + */ + public void setCustomQualifierTypes(Set customQualifierTypes) { + this.customQualifierTypes = customQualifierTypes; + } + + + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + if (this.customQualifierTypes != null) { + if (!(beanFactory instanceof DefaultListableBeanFactory)) { + throw new IllegalStateException( + "CustomAutowireConfigurer needs to operate on a DefaultListableBeanFactory"); + } + DefaultListableBeanFactory dlbf = (DefaultListableBeanFactory) beanFactory; + if (!(dlbf.getAutowireCandidateResolver() instanceof QualifierAnnotationAutowireCandidateResolver)) { + throw new IllegalStateException( + "CustomAutowireConfigurer needs to operate on a QualifierAnnotationAutowireCandidateResolver"); + } + QualifierAnnotationAutowireCandidateResolver resolver = + (QualifierAnnotationAutowireCandidateResolver) dlbf.getAutowireCandidateResolver(); + for (Iterator it = this.customQualifierTypes.iterator(); it.hasNext();) { + Class customType = null; + Object value = it.next(); + if (value instanceof Class) { + customType = (Class) value; + } + else if (value instanceof String) { + String className = (String) value; + customType = ClassUtils.resolveClassName(className, this.beanClassLoader); + } + else { + throw new IllegalArgumentException( + "Invalid value [" + value + "] for custom qualifier type: needs to be Class or String."); + } + if (!Annotation.class.isAssignableFrom(customType)) { + throw new IllegalArgumentException( + "Qualifier type [" + customType.getName() + "] needs to be annotation type"); + } + resolver.addQualifierType(customType); + } + } + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java new file mode 100644 index 0000000000..a264a6b120 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java @@ -0,0 +1,310 @@ +/* + * Copyright 2002-2008 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.annotation; + +import java.io.Serializable; +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor; +import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.core.Ordered; +import org.springframework.core.PriorityOrdered; +import org.springframework.util.ReflectionUtils; + +/** + * {@link org.springframework.beans.factory.config.BeanPostProcessor} implementation + * that invokes annotated init and destroy methods. Allows for an annotation + * alternative to Spring's {@link org.springframework.beans.factory.InitializingBean} + * and {@link org.springframework.beans.factory.DisposableBean} callback interfaces. + * + *

The actual annotation types that this post-processor checks for can be + * configured through the {@link #setInitAnnotationType "initAnnotationType"} + * and {@link #setDestroyAnnotationType "destroyAnnotationType"} properties. + * Any custom annotation can be used, since there are no required annotation + * attributes. + * + *

Init and destroy annotations may be applied to methods of any visibility: + * public, package-protected, protected, or private. Multiple such methods + * may be annotated, but it is recommended to only annotate one single + * init method and destroy method, respectively. + * + *

Spring's {@link org.springframework.context.annotation.CommonAnnotationBeanPostProcessor} + * supports the JSR-250 {@link javax.annotation.PostConstruct} and {@link javax.annotation.PreDestroy} + * annotations out of the box, as init annotation and destroy annotation, respectively. + * Furthermore, it also supports the {@link javax.annotation.Resource} annotation + * for annotation-driven injection of named beans. + * + * @author Juergen Hoeller + * @since 2.5 + * @see #setInitAnnotationType + * @see #setDestroyAnnotationType + * @see org.springframework.context.annotation.CommonAnnotationBeanPostProcessor + */ +public class InitDestroyAnnotationBeanPostProcessor + implements DestructionAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor, PriorityOrdered, Serializable { + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + private Class initAnnotationType; + + private Class destroyAnnotationType; + + private int order = Ordered.LOWEST_PRECEDENCE - 1; + + private transient final Map, LifecycleMetadata> lifecycleMetadataCache = + new ConcurrentHashMap, LifecycleMetadata>(); + + + /** + * Specify the init annotation to check for, indicating initialization + * methods to call after configuration of a bean. + *

Any custom annotation can be used, since there are no required + * annotation attributes. There is no default, although a typical choice + * is the JSR-250 {@link javax.annotation.PostConstruct} annotation. + */ + public void setInitAnnotationType(Class initAnnotationType) { + this.initAnnotationType = initAnnotationType; + } + + /** + * Specify the destroy annotation to check for, indicating destruction + * methods to call when the context is shutting down. + *

Any custom annotation can be used, since there are no required + * annotation attributes. There is no default, although a typical choice + * is the JSR-250 {@link javax.annotation.PreDestroy} annotation. + */ + public void setDestroyAnnotationType(Class destroyAnnotationType) { + this.destroyAnnotationType = destroyAnnotationType; + } + + public void setOrder(int order) { + this.order = order; + } + + public int getOrder() { + return this.order; + } + + + public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class beanType, String beanName) { + if (beanType != null) { + LifecycleMetadata metadata = findLifecycleMetadata(beanType); + for (Iterator it = metadata.getInitMethods().iterator(); it.hasNext();) { + String methodName = it.next().getMethod().getName(); + if (!beanDefinition.isExternallyManagedInitMethod(methodName)) { + beanDefinition.registerExternallyManagedInitMethod(methodName); + } + else { + it.remove(); + } + } + for (Iterator it = metadata.getDestroyMethods().iterator(); it.hasNext();) { + String methodName = it.next().getMethod().getName(); + if (!beanDefinition.isExternallyManagedDestroyMethod(methodName)) { + beanDefinition.registerExternallyManagedDestroyMethod(methodName); + } + else { + it.remove(); + } + } + } + } + + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass()); + try { + metadata.invokeInitMethods(bean, beanName); + } + catch (InvocationTargetException ex) { + throw new BeanCreationException(beanName, "Invocation of init method failed", ex.getTargetException()); + } + catch (Throwable ex) { + throw new BeanCreationException(beanName, "Couldn't invoke init method", ex); + } + return bean; + } + + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException { + LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass()); + try { + metadata.invokeDestroyMethods(bean, beanName); + } + catch (InvocationTargetException ex) { + String msg = "Invocation of destroy method failed on bean with name '" + beanName + "'"; + if (logger.isDebugEnabled()) { + logger.warn(msg, ex.getTargetException()); + } + else { + logger.warn(msg + ": " + ex.getTargetException()); + } + } + catch (Throwable ex) { + logger.error("Couldn't invoke destroy method on bean with name '" + beanName + "'", ex); + } + } + + + private LifecycleMetadata findLifecycleMetadata(Class clazz) { + if (this.lifecycleMetadataCache == null) { + // Happens after deserialization, during destruction... + return buildLifecycleMetadata(clazz); + } + // Quick check on the concurrent map first, with minimal locking. + LifecycleMetadata metadata = this.lifecycleMetadataCache.get(clazz); + if (metadata == null) { + synchronized (this.lifecycleMetadataCache) { + metadata = this.lifecycleMetadataCache.get(clazz); + if (metadata == null) { + metadata = buildLifecycleMetadata(clazz); + this.lifecycleMetadataCache.put(clazz, metadata); + } + return metadata; + } + } + return metadata; + } + + private LifecycleMetadata buildLifecycleMetadata(final Class clazz) { + final LifecycleMetadata newMetadata = new LifecycleMetadata(); + final boolean debug = logger.isDebugEnabled(); + ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() { + public void doWith(Method method) { + if (initAnnotationType != null) { + if (method.getAnnotation(initAnnotationType) != null) { + newMetadata.addInitMethod(method); + if (debug) { + logger.debug("Found init method on class [" + clazz.getName() + "]: " + method); + } + } + } + if (destroyAnnotationType != null) { + if (method.getAnnotation(destroyAnnotationType) != null) { + newMetadata.addDestroyMethod(method); + if (debug) { + logger.debug("Found destroy method on class [" + clazz.getName() + "]: " + method); + } + } + } + } + }); + return newMetadata; + } + + + /** + * Class representing information about annotated init and destroy methods. + */ + private class LifecycleMetadata { + + private final Set initMethods = new LinkedHashSet(); + + private final Set destroyMethods = new LinkedHashSet(); + + public void addInitMethod(Method method) { + this.initMethods.add(new LifecycleElement(method)); + } + + public Set getInitMethods() { + return this.initMethods; + } + + public void invokeInitMethods(Object target, String beanName) throws Throwable { + if (!this.initMethods.isEmpty()) { + boolean debug = logger.isDebugEnabled(); + for (LifecycleElement element : this.initMethods) { + if (debug) { + logger.debug("Invoking init method on bean '" + beanName + "': " + element.getMethod()); + } + element.invoke(target); + } + } + } + + public void addDestroyMethod(Method method) { + this.destroyMethods.add(new LifecycleElement(method)); + } + + public Set getDestroyMethods() { + return this.destroyMethods; + } + + public void invokeDestroyMethods(Object target, String beanName) throws Throwable { + if (!this.destroyMethods.isEmpty()) { + boolean debug = logger.isDebugEnabled(); + for (LifecycleElement element : this.destroyMethods) { + if (debug) { + logger.debug("Invoking destroy method on bean '" + beanName + "': " + element.getMethod()); + } + element.invoke(target); + } + } + } + } + + + /** + * Class representing injection information about an annotated method. + */ + private static class LifecycleElement { + + private final Method method; + + public LifecycleElement(Method method) { + if (method.getParameterTypes().length != 0) { + throw new IllegalStateException("Lifecycle method annotation requires a no-arg method: " + method); + } + this.method = method; + } + + public Method getMethod() { + return this.method; + } + + public void invoke(Object target) throws Throwable { + ReflectionUtils.makeAccessible(this.method); + this.method.invoke(target, (Object[]) null); + } + + public boolean equals(Object other) { + return (this == other || (other instanceof LifecycleElement && + this.method.getName().equals(((LifecycleElement) other).method.getName()))); + } + + public int hashCode() { + return this.method.getName().hashCode(); + } + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/InjectionMetadata.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/InjectionMetadata.java new file mode 100644 index 0000000000..2a4e264d78 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/InjectionMetadata.java @@ -0,0 +1,253 @@ +/* + * Copyright 2002-2008 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.annotation; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.PropertyValues; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.util.ReflectionUtils; + +/** + * Internal class for managing injection metadata. + * Not intended for direct use in applications. + * + *

Used by {@link AutowiredAnnotationBeanPostProcessor}, + * {@link org.springframework.context.annotation.CommonAnnotationBeanPostProcessor} and + * {@link org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor}. + * + * @author Juergen Hoeller + * @since 2.5 + */ +public class InjectionMetadata { + + private final Log logger = LogFactory.getLog(InjectionMetadata.class); + + private String targetClassName; + + private final Set injectedFields = new LinkedHashSet(); + + private final Set injectedMethods = new LinkedHashSet(); + + + public InjectionMetadata() { + } + + public InjectionMetadata(Class targetClass) { + this.targetClassName = targetClass.getName(); + } + + + public void addInjectedField(InjectedElement element) { + if (logger.isDebugEnabled()) { + logger.debug("Found injected field on class [" + this.targetClassName + "]: " + element); + } + this.injectedFields.add(element); + } + + public void addInjectedMethod(InjectedElement element) { + if (logger.isDebugEnabled()) { + logger.debug("Found injected method on class [" + this.targetClassName + "]: " + element); + } + this.injectedMethods.add(element); + } + + public void checkConfigMembers(RootBeanDefinition beanDefinition) { + doRegisterConfigMembers(beanDefinition, this.injectedFields); + doRegisterConfigMembers(beanDefinition, this.injectedMethods); + } + + private void doRegisterConfigMembers(RootBeanDefinition beanDefinition, Set members) { + for (Iterator it = members.iterator(); it.hasNext();) { + Member member = it.next().getMember(); + if (!beanDefinition.isExternallyManagedConfigMember(member)) { + beanDefinition.registerExternallyManagedConfigMember(member); + } + else { + it.remove(); + } + } + } + + public void injectFields(Object target, String beanName) throws Throwable { + if (!this.injectedFields.isEmpty()) { + boolean debug = logger.isDebugEnabled(); + for (InjectedElement element : this.injectedFields) { + if (debug) { + logger.debug("Processing injected field of bean '" + beanName + "': " + element); + } + element.inject(target, beanName, null); + } + } + } + + public void injectMethods(Object target, String beanName, PropertyValues pvs) throws Throwable { + if (!this.injectedMethods.isEmpty()) { + boolean debug = logger.isDebugEnabled(); + for (InjectedElement element : this.injectedMethods) { + if (debug) { + logger.debug("Processing injected method of bean '" + beanName + "': " + element); + } + element.inject(target, beanName, pvs); + } + } + } + + + public static abstract class InjectedElement { + + protected final Member member; + + protected final boolean isField; + + protected final PropertyDescriptor pd; + + protected volatile Boolean skip; + + protected InjectedElement(Member member, PropertyDescriptor pd) { + this.member = member; + this.isField = (member instanceof Field); + this.pd = pd; + } + + public final Member getMember() { + return this.member; + } + + protected final Class getResourceType() { + if (this.isField) { + return ((Field) this.member).getType(); + } + else if (this.pd != null) { + return this.pd.getPropertyType(); + } + else { + return ((Method) this.member).getParameterTypes()[0]; + } + } + + protected final void checkResourceType(Class resourceType) { + if (this.isField) { + Class fieldType = ((Field) this.member).getType(); + if (!(resourceType.isAssignableFrom(fieldType) || fieldType.isAssignableFrom(resourceType))) { + throw new IllegalStateException("Specified field type [" + fieldType + + "] is incompatible with resource type [" + resourceType.getName() + "]"); + } + } + else { + Class paramType = + (this.pd != null ? this.pd.getPropertyType() : ((Method) this.member).getParameterTypes()[0]); + if (!(resourceType.isAssignableFrom(paramType) || paramType.isAssignableFrom(resourceType))) { + throw new IllegalStateException("Specified parameter type [" + paramType + + "] is incompatible with resource type [" + resourceType.getName() + "]"); + } + } + } + + /** + * Either this or {@link #getResourceToInject} needs to be overridden. + */ + protected void inject(Object target, String requestingBeanName, PropertyValues pvs) throws Throwable { + if (this.isField) { + Field field = (Field) this.member; + ReflectionUtils.makeAccessible(field); + field.set(target, getResourceToInject(target, requestingBeanName)); + } + else { + if (this.skip == null) { + this.skip = Boolean.valueOf(checkPropertySkipping(pvs)); + } + if (this.skip.booleanValue()) { + return; + } + try { + Method method = (Method) this.member; + ReflectionUtils.makeAccessible(method); + method.invoke(target, getResourceToInject(target, requestingBeanName)); + } + catch (InvocationTargetException ex) { + throw ex.getTargetException(); + } + } + } + + /** + * Checks whether this injector's property needs to be skipped due to + * an explicit property value having been specified. Also marks the + * affected property as processed for other processors to ignore it. + */ + protected boolean checkPropertySkipping(PropertyValues pvs) { + if (this.pd != null && pvs != null) { + if (pvs.contains(this.pd.getName())) { + // Explicit value provided as part of the bean definition. + return true; + } + else if (pvs instanceof MutablePropertyValues) { + ((MutablePropertyValues) pvs).registerProcessedProperty(this.pd.getName()); + } + } + return false; + } + + /** + * Either this or {@link #inject} needs to be overridden. + */ + protected Object getResourceToInject(Object target, String requestingBeanName) { + return null; + } + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof InjectedElement)) { + return false; + } + InjectedElement otherElement = (InjectedElement) other; + if (this.isField) { + return this.member.equals(otherElement.member); + } + else { + return (otherElement.member instanceof Method && + this.member.getName().equals(otherElement.member.getName()) && + Arrays.equals(((Method) this.member).getParameterTypes(), + ((Method) otherElement.member).getParameterTypes())); + } + } + + public int hashCode() { + return this.member.getClass().hashCode() * 29 + this.member.getName().hashCode(); + } + + public String toString() { + return getClass().getSimpleName() + " for " + this.member; + } + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/Qualifier.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/Qualifier.java new file mode 100644 index 0000000000..ec44c70812 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/Qualifier.java @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2007 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.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation may be used on a field or parameter as a qualifier for + * candidate beans when autowiring. It may also be used to annotate other + * custom annotations that can then in turn be used as qualifiers. + * + * @author Mark Fisher + * @author Juergen Hoeller + * @since 2.5 + */ +@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +public @interface Qualifier { + + String value() default ""; + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java new file mode 100644 index 0000000000..289db5b642 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java @@ -0,0 +1,179 @@ +/* + * Copyright 2002-2007 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.annotation; + +import java.lang.annotation.Annotation; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.springframework.beans.SimpleTypeConverter; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.config.DependencyDescriptor; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.AutowireCandidateQualifier; +import org.springframework.beans.factory.support.AutowireCandidateResolver; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; + +/** + * {@link AutowireCandidateResolver} implementation that matches bean definition + * qualifiers against qualifier annotations on the field or parameter to be autowired. + * + * @author Mark Fisher + * @author Juergen Hoeller + * @since 2.5 + * @see AutowireCandidateQualifier + * @see Qualifier + */ +public class QualifierAnnotationAutowireCandidateResolver implements AutowireCandidateResolver { + + private final Set> qualifierTypes; + + + /** + * Create a new QualifierAnnotationAutowireCandidateResolver + * for Spring's standard {@link Qualifier} annotation. + */ + public QualifierAnnotationAutowireCandidateResolver() { + this.qualifierTypes = new HashSet>(1); + this.qualifierTypes.add(Qualifier.class); + } + + /** + * Create a new QualifierAnnotationAutowireCandidateResolver + * for the given qualifier annotation type. + * @param qualifierType the qualifier annotation to look for + */ + public QualifierAnnotationAutowireCandidateResolver(Class qualifierType) { + Assert.notNull(qualifierType, "'qualifierType' must not be null"); + this.qualifierTypes = new HashSet>(1); + this.qualifierTypes.add(qualifierType); + } + + /** + * Create a new QualifierAnnotationAutowireCandidateResolver + * for the given qualifier annotation types. + * @param qualifierTypes the qualifier annotations to look for + */ + public QualifierAnnotationAutowireCandidateResolver(Set> qualifierTypes) { + Assert.notNull(qualifierTypes, "'qualifierTypes' must not be null"); + this.qualifierTypes = new HashSet>(qualifierTypes); + } + + + /** + * Register the given type to be used as a qualifier when autowiring. + *

This implementation only supports annotations as qualifier types. + * @param qualifierType the annotation type to register + */ + public void addQualifierType(Class qualifierType) { + this.qualifierTypes.add(qualifierType); + } + + /** + * Determine if the provided bean definition is an autowire candidate. + *

To be considered a candidate the bean's autowire-candidate + * attribute must not have been set to 'false'. Also if an annotation on + * the field or parameter to be autowired is recognized by this bean factory + * as a qualifier, the bean must 'match' against the annotation as + * well as any attributes it may contain. The bean definition must contain + * the same qualifier or match by meta attributes. A "value" attribute will + * fallback to match against the bean name or an alias if a qualifier or + * attribute does not match. + */ + public boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) { + if (!bdHolder.getBeanDefinition().isAutowireCandidate()) { + // if explicitly false, do not proceed with qualifier check + return false; + } + if (descriptor == null || ObjectUtils.isEmpty(descriptor.getAnnotations())) { + // no qualification necessary + return true; + } + AbstractBeanDefinition bd = (AbstractBeanDefinition) bdHolder.getBeanDefinition(); + SimpleTypeConverter typeConverter = new SimpleTypeConverter(); + Annotation[] annotations = (Annotation[]) descriptor.getAnnotations(); + for (Annotation annotation : annotations) { + Class type = annotation.annotationType(); + if (isQualifier(type)) { + AutowireCandidateQualifier qualifier = bd.getQualifier(type.getName()); + if (qualifier == null) { + qualifier = bd.getQualifier(ClassUtils.getShortName(type)); + } + if (qualifier == null && bd.hasBeanClass()) { + // look for matching annotation on the target class + Class beanClass = bd.getBeanClass(); + Annotation targetAnnotation = beanClass.getAnnotation(type); + if (targetAnnotation != null && targetAnnotation.equals(annotation)) { + return true; + } + } + Map attributes = AnnotationUtils.getAnnotationAttributes(annotation); + if (attributes.isEmpty() && qualifier == null) { + // if no attributes, the qualifier must be present + return false; + } + for (Map.Entry entry : attributes.entrySet()) { + String attributeName = entry.getKey(); + Object expectedValue = entry.getValue(); + Object actualValue = null; + // check qualifier first + if (qualifier != null) { + actualValue = qualifier.getAttribute(attributeName); + } + if (actualValue == null) { + // fall back on bean definition attribute + actualValue = bd.getAttribute(attributeName); + } + if (actualValue == null && attributeName.equals(AutowireCandidateQualifier.VALUE_KEY) && + (expectedValue.equals(bdHolder.getBeanName()) || + ObjectUtils.containsElement(bdHolder.getAliases(), expectedValue))) { + // fall back on bean name (or alias) match + continue; + } + if (actualValue == null && qualifier != null) { + // fall back on default, but only if the qualifier is present + actualValue = AnnotationUtils.getDefaultValue(annotation, attributeName); + } + if (actualValue != null) { + actualValue = typeConverter.convertIfNecessary(actualValue, expectedValue.getClass()); + } + if (!expectedValue.equals(actualValue)) { + return false; + } + } + } + } + return true; + } + + /** + * Checks whether the given annotation type is a recognized qualifier type. + */ + private boolean isQualifier(Class annotationType) { + for (Class qualifierType : this.qualifierTypes) { + if (annotationType.equals(qualifierType) || annotationType.isAnnotationPresent(qualifierType)) { + return true; + } + } + return false; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/Required.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/Required.java new file mode 100644 index 0000000000..91aff60e6f --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/Required.java @@ -0,0 +1,39 @@ +/* + * Copyright 2002-2007 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.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a method (typically a JavaBean setter method) as being 'required': that is, + * the setter method must be configured to be dependency-injected with a value. + * + *

Please do consult the javadoc for the {@link RequiredAnnotationBeanPostProcessor} + * class (which, by default, checks for the presence of this annotation). + * + * @author Rob Harrop + * @since 2.0 + * @see RequiredAnnotationBeanPostProcessor + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Required { + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/RequiredAnnotationBeanPostProcessor.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/RequiredAnnotationBeanPostProcessor.java new file mode 100644 index 0000000000..7c18de5975 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/RequiredAnnotationBeanPostProcessor.java @@ -0,0 +1,169 @@ +/* + * Copyright 2002-2007 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.annotation; + +import java.beans.PropertyDescriptor; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.springframework.beans.BeansException; +import org.springframework.beans.PropertyValues; +import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter; +import org.springframework.core.Ordered; +import org.springframework.core.PriorityOrdered; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.Assert; + +/** + * {@link org.springframework.beans.factory.config.BeanPostProcessor} implementation + * that enforces required JavaBean properties to have been configured. + * Required bean properties are detected through a Java 5 annotation: + * by default, Spring's {@link Required} annotation. + * + *

The motivation for the existence of this BeanPostProcessor is to allow + * developers to annotate the setter properties of their own classes with an + * arbitrary JDK 1.5 annotation to indicate that the container must check + * for the configuration of a dependency injected value. This neatly pushes + * responsibility for such checking onto the container (where it arguably belongs), + * and obviates the need (in part) for a developer to code a method that + * simply checks that all required properties have actually been set. + * + *

Please note that an 'init' method may still need to implemented (and may + * still be desirable), because all that this class does is enforce that a + * 'required' property has actually been configured with a value. It does + * not check anything else... In particular, it does not check that a + * configured value is not null. + * + *

Note: A default RequiredAnnotationBeanPostProcessor will be registered + * by the "context:annotation-config" and "context:component-scan" XML tags. + * Remove or turn off the default annotation configuration there if you intend + * to specify a custom RequiredAnnotationBeanPostProcessor bean definition. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + * @see #setRequiredAnnotationType + * @see Required + */ +public class RequiredAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter + implements PriorityOrdered { + + private Class requiredAnnotationType = Required.class; + + private int order = Ordered.LOWEST_PRECEDENCE - 1; + + /** Cache for validated bean names, skipping re-validation for the same bean */ + private final Set validatedBeanNames = Collections.synchronizedSet(new HashSet()); + + + /** + * Set the 'required' annotation type, to be used on bean property + * setter methods. + *

The default required annotation type is the Spring-provided + * {@link Required} annotation. + *

This setter property exists so that developers can provide their own + * (non-Spring-specific) annotation type to indicate that a property value + * is required. + */ + public void setRequiredAnnotationType(Class requiredAnnotationType) { + Assert.notNull(requiredAnnotationType, "'requiredAnnotationType' must not be null"); + this.requiredAnnotationType = requiredAnnotationType; + } + + /** + * Return the 'required' annotation type. + */ + protected Class getRequiredAnnotationType() { + return this.requiredAnnotationType; + } + + public void setOrder(int order) { + this.order = order; + } + + public int getOrder() { + return this.order; + } + + + public PropertyValues postProcessPropertyValues( + PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) + throws BeansException { + + if (!this.validatedBeanNames.contains(beanName)) { + List invalidProperties = new ArrayList(); + for (PropertyDescriptor pd : pds) { + if (isRequiredProperty(pd) && !pvs.contains(pd.getName())) { + invalidProperties.add(pd.getName()); + } + } + if (!invalidProperties.isEmpty()) { + throw new BeanInitializationException(buildExceptionMessage(invalidProperties, beanName)); + } + this.validatedBeanNames.add(beanName); + } + return pvs; + } + + /** + * Is the supplied property required to have a value (that is, to be dependency-injected)? + *

This implementation looks for the existence of a + * {@link #setRequiredAnnotationType "required" annotation} + * on the supplied {@link PropertyDescriptor property}. + * @param propertyDescriptor the target PropertyDescriptor (never null) + * @return true if the supplied property has been marked as being required; + * false if not, or if the supplied property does not have a setter method + */ + protected boolean isRequiredProperty(PropertyDescriptor propertyDescriptor) { + Method setter = propertyDescriptor.getWriteMethod(); + return (setter != null && AnnotationUtils.getAnnotation(setter, getRequiredAnnotationType()) != null); + } + + /** + * Build an exception message for the given list of invalid properties. + * @param invalidProperties the list of names of invalid properties + * @param beanName the name of the bean + * @return the exception message + */ + private String buildExceptionMessage(List invalidProperties, String beanName) { + int size = invalidProperties.size(); + StringBuilder sb = new StringBuilder(); + sb.append(size == 1 ? "Property" : "Properties"); + for (int i = 0; i < size; i++) { + String propertyName = invalidProperties.get(i); + if (i > 0) { + if (i == (size - 1)) { + sb.append(" and"); + } + else { + sb.append(","); + } + } + sb.append(" '").append(propertyName).append("'"); + } + sb.append(size == 1 ? " is" : " are"); + sb.append(" required for bean '").append(beanName).append("'"); + return sb.toString(); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/package.html b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/package.html new file mode 100644 index 0000000000..af2eb603c8 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/package.html @@ -0,0 +1,7 @@ + + + +Support package for annotation-driven bean configuration. + + + diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/AbstractFactoryBean.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/AbstractFactoryBean.java new file mode 100644 index 0000000000..52c5fabe12 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/AbstractFactoryBean.java @@ -0,0 +1,262 @@ +/* + * Copyright 2002-2008 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.config; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.SimpleTypeConverter; +import org.springframework.beans.TypeConverter; +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.FactoryBeanNotInitializedException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; +import org.springframework.util.ReflectionUtils; + +/** + * Simple template superclass for {@link FactoryBean} implementations that + * creates a singleton or a prototype object, depending on a flag. + * + *

If the "singleton" flag is true (the default), + * this class will create the object that it creates exactly once + * on initialization and subsequently return said singleton instance + * on all calls to the {@link #getObject()} method. + * + *

Else, this class will create a new instance every time the + * {@link #getObject()} method is invoked. Subclasses are responsible + * for implementing the abstract {@link #createInstance()} template + * method to actually create the object(s) to expose. + * + * @author Juergen Hoeller + * @author Keith Donald + * @since 1.0.2 + * @see #setSingleton + * @see #createInstance() + */ +public abstract class AbstractFactoryBean + implements FactoryBean, BeanClassLoaderAware, BeanFactoryAware, InitializingBean, DisposableBean { + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + private boolean singleton = true; + + private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); + + private BeanFactory beanFactory; + + private boolean initialized = false; + + private Object singletonInstance; + + private Object earlySingletonInstance; + + + /** + * Set if a singleton should be created, or a new object + * on each request else. Default is true (a singleton). + */ + public void setSingleton(boolean singleton) { + this.singleton = singleton; + } + + public boolean isSingleton() { + return this.singleton; + } + + public void setBeanClassLoader(ClassLoader classLoader) { + this.beanClassLoader = classLoader; + } + + public void setBeanFactory(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + /** + * Return the BeanFactory that this bean runs in. + */ + protected BeanFactory getBeanFactory() { + return this.beanFactory; + } + + /** + * Obtain a bean type converter from the BeanFactory that this bean + * runs in. This is typically a fresh instance for each call, + * since TypeConverters are usually not thread-safe. + *

Falls back to a SimpleTypeConverter when not running in a BeanFactory. + * @see ConfigurableBeanFactory#getTypeConverter() + * @see org.springframework.beans.SimpleTypeConverter + */ + protected TypeConverter getBeanTypeConverter() { + BeanFactory beanFactory = getBeanFactory(); + if (beanFactory instanceof ConfigurableBeanFactory) { + return ((ConfigurableBeanFactory) beanFactory).getTypeConverter(); + } + else { + return new SimpleTypeConverter(); + } + } + + /** + * Eagerly create the singleton instance, if necessary. + */ + public void afterPropertiesSet() throws Exception { + if (isSingleton()) { + this.initialized = true; + this.singletonInstance = createInstance(); + this.earlySingletonInstance = null; + } + } + + + /** + * Expose the singleton instance or create a new prototype instance. + * @see #createInstance() + * @see #getEarlySingletonInterfaces() + */ + public final Object getObject() throws Exception { + if (isSingleton()) { + return (this.initialized ? this.singletonInstance : getEarlySingletonInstance()); + } + else { + return createInstance(); + } + } + + /** + * Determine an 'eager singleton' instance, exposed in case of a + * circular reference. Not called in a non-circular scenario. + */ + private Object getEarlySingletonInstance() throws Exception { + Class[] ifcs = getEarlySingletonInterfaces(); + if (ifcs == null) { + throw new FactoryBeanNotInitializedException( + getClass().getName() + " does not support circular references"); + } + if (this.earlySingletonInstance == null) { + this.earlySingletonInstance = Proxy.newProxyInstance( + this.beanClassLoader, ifcs, new EarlySingletonInvocationHandler()); + } + return this.earlySingletonInstance; + } + + /** + * Expose the singleton instance (for access through the 'early singleton' proxy). + * @return the singleton instance that this FactoryBean holds + * @throws IllegalStateException if the singleton instance is not initialized + */ + private Object getSingletonInstance() throws IllegalStateException { + if (!this.initialized) { + throw new IllegalStateException("Singleton instance not initialized yet"); + } + return this.singletonInstance; + } + + /** + * Destroy the singleton instance, if any. + * @see #destroyInstance(Object) + */ + public void destroy() throws Exception { + if (isSingleton()) { + destroyInstance(this.singletonInstance); + } + } + + + /** + * This abstract method declaration mirrors the method in the FactoryBean + * interface, for a consistent offering of abstract template methods. + * @see org.springframework.beans.factory.FactoryBean#getObjectType() + */ + public abstract Class getObjectType(); + + /** + * Template method that subclasses must override to construct + * the object returned by this factory. + *

Invoked on initialization of this FactoryBean in case of + * a singleton; else, on each {@link #getObject()} call. + * @return the object returned by this factory + * @throws Exception if an exception occured during object creation + * @see #getObject() + */ + protected abstract Object createInstance() throws Exception; + + /** + * Return an array of interfaces that a singleton object exposed by this + * FactoryBean is supposed to implement, for use with an 'early singleton + * proxy' that will be exposed in case of a circular reference. + *

The default implementation returns this FactoryBean's object type, + * provided that it is an interface, or null else. The latter + * indicates that early singleton access is not supported by this FactoryBean. + * This will lead to a FactoryBeanNotInitializedException getting thrown. + * @return the interfaces to use for 'early singletons', + * or null to indicate a FactoryBeanNotInitializedException + * @see org.springframework.beans.factory.FactoryBeanNotInitializedException + */ + protected Class[] getEarlySingletonInterfaces() { + Class type = getObjectType(); + return (type != null && type.isInterface() ? new Class[] {type} : null); + } + + /** + * Callback for destroying a singleton instance. Subclasses may + * override this to destroy the previously created instance. + *

The default implementation is empty. + * @param instance the singleton instance, as returned by + * {@link #createInstance()} + * @throws Exception in case of shutdown errors + * @see #createInstance() + */ + protected void destroyInstance(Object instance) throws Exception { + } + + + private class EarlySingletonInvocationHandler implements InvocationHandler { + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (ReflectionUtils.isEqualsMethod(method)) { + // Only consider equal when proxies are identical. + return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE); + } + else if (ReflectionUtils.isHashCodeMethod(method)) { + // Use hashCode of reference proxy. + return new Integer(System.identityHashCode(proxy)); + } + else if (!initialized && ReflectionUtils.isToStringMethod(method)) { + return "Early singleton proxy for interfaces " + + ObjectUtils.nullSafeToString(getEarlySingletonInterfaces()); + } + try { + return method.invoke(getSingletonInstance(), args); + } + catch (InvocationTargetException ex) { + throw ex.getTargetException(); + } + } + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/AutowireCapableBeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/AutowireCapableBeanFactory.java new file mode 100644 index 0000000000..07f26595ec --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/AutowireCapableBeanFactory.java @@ -0,0 +1,317 @@ +/* + * Copyright 2002-2008 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.config; + +import java.util.Set; + +import org.springframework.beans.BeansException; +import org.springframework.beans.TypeConverter; +import org.springframework.beans.factory.BeanFactory; + +/** + * Extension of the {@link org.springframework.beans.factory.BeanFactory} + * interface to be implemented by bean factories that are capable of + * autowiring, provided that they want to expose this functionality for + * existing bean instances. + * + *

This subinterface of BeanFactory is not meant to be used in normal + * application code: stick to {@link org.springframework.beans.factory.BeanFactory} + * or {@link org.springframework.beans.factory.ListableBeanFactory} for + * typical use cases. + * + *

Integration code for other frameworks can leverage this interface to + * wire and populate existing bean instances that Spring does not control + * the lifecycle of. This is particularly useful for WebWork Actions and + * Tapestry Page objects, for example. + * + *

Note that this interface is not implemented by + * {@link org.springframework.context.ApplicationContext} facades, + * as it is hardly ever used by application code. That said, it is available + * from an application context too, accessible through ApplicationContext's + * {@link org.springframework.context.ApplicationContext#getAutowireCapableBeanFactory()} + * method. + * + *

You may also implement the {@link org.springframework.beans.factory.BeanFactoryAware} + * interface, which exposes the internal BeanFactory even when running in an + * ApplicationContext, to get access to an AutowireCapableBeanFactory: + * simply cast the passed-in BeanFactory to AutowireCapableBeanFactory. + * + * @author Juergen Hoeller + * @since 04.12.2003 + * @see org.springframework.beans.factory.BeanFactoryAware + * @see org.springframework.beans.factory.config.ConfigurableListableBeanFactory + * @see org.springframework.context.ApplicationContext#getAutowireCapableBeanFactory() + */ +public interface AutowireCapableBeanFactory extends BeanFactory { + + /** + * Constant that indicates no externally defined autowiring. Note that + * BeanFactoryAware etc and annotation-driven injection will still be applied. + * @see #createBean + * @see #autowire + * @see #autowireBeanProperties + */ + int AUTOWIRE_NO = 0; + + /** + * Constant that indicates autowiring bean properties by name + * (applying to all bean property setters). + * @see #createBean + * @see #autowire + * @see #autowireBeanProperties + */ + int AUTOWIRE_BY_NAME = 1; + + /** + * Constant that indicates autowiring bean properties by type + * (applying to all bean property setters). + * @see #createBean + * @see #autowire + * @see #autowireBeanProperties + */ + int AUTOWIRE_BY_TYPE = 2; + + /** + * Constant that indicates autowiring the greediest constructor that + * can be satisfied (involves resolving the appropriate constructor). + * @see #createBean + * @see #autowire + */ + int AUTOWIRE_CONSTRUCTOR = 3; + + /** + * Constant that indicates determining an appropriate autowire strategy + * through introspection of the bean class. + * @see #createBean + * @see #autowire + */ + int AUTOWIRE_AUTODETECT = 4; + + + //------------------------------------------------------------------------- + // Typical methods for creating and populating external bean instances + //------------------------------------------------------------------------- + + /** + * Fully create a new bean instance of the given class. + *

Performs full initialization of the bean, including all applicable + * {@link BeanPostProcessor BeanPostProcessors}. + *

Note: This is intended for creating a fresh instance, populating annotated + * fields and methods as well as applying all standard bean initialiation callbacks. + * It does not imply traditional by-name or by-type autowiring of properties; + * use {@link #createBean(Class, int, boolean)} for that purposes. + * @param beanClass the class of the bean to create + * @return the new bean instance + * @throws BeansException if instantiation or wiring failed + */ + Object createBean(Class beanClass) throws BeansException; + + /** + * Populate the given bean instance through applying after-instantiation callbacks + * and bean property post-processing (e.g. for annotation-driven injection). + *

Note: This is essentially intended for (re-)populating annotated fields and + * methods, either for new instances or for deserialized instances. It does + * not imply traditional by-name or by-type autowiring of properties; + * use {@link #autowireBeanProperties} for that purposes. + * @param existingBean the existing bean instance + * @throws BeansException if wiring failed + */ + void autowireBean(Object existingBean) throws BeansException; + + /** + * Configure the given raw bean: autowiring bean properties, applying + * bean property values, applying factory callbacks such as setBeanName + * and setBeanFactory, and also applying all bean post processors + * (including ones which might wrap the given raw bean). + *

This is effectively a superset of what {@link #initializeBean} provides, + * fully applying the configuration specified by the corresponding bean definition. + * Note: This method requires a bean definition for the given name! + * @param existingBean the existing bean instance + * @param beanName the name of the bean, to be passed to it if necessary + * (a bean definition of that name has to be available) + * @return the bean instance to use, either the original or a wrapped one + * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException + * if there is no bean definition with the given name + * @throws BeansException if the initialization failed + * @see #initializeBean + */ + Object configureBean(Object existingBean, String beanName) throws BeansException; + + /** + * Resolve the specified dependency against the beans defined in this factory. + * @param descriptor the descriptor for the dependency + * @param beanName the name of the bean which declares the present dependency + * @return the resolved object, or null if none found + * @throws BeansException in dependency resolution failed + */ + Object resolveDependency(DependencyDescriptor descriptor, String beanName) throws BeansException; + + + //------------------------------------------------------------------------- + // Specialized methods for fine-grained control over the bean lifecycle + //------------------------------------------------------------------------- + + /** + * Fully create a new bean instance of the given class with the specified + * autowire strategy. All constants defined in this interface are supported here. + *

Performs full initialization of the bean, including all applicable + * {@link BeanPostProcessor BeanPostProcessors}. This is effectively a superset + * of what {@link #autowire} provides, adding {@link #initializeBean} behavior. + * @param beanClass the class of the bean to create + * @param autowireMode by name or type, using the constants in this interface + * @param dependencyCheck whether to perform a dependency check for objects + * (not applicable to autowiring a constructor, thus ignored there) + * @return the new bean instance + * @throws BeansException if instantiation or wiring failed + * @see #AUTOWIRE_NO + * @see #AUTOWIRE_BY_NAME + * @see #AUTOWIRE_BY_TYPE + * @see #AUTOWIRE_CONSTRUCTOR + * @see #AUTOWIRE_AUTODETECT + */ + Object createBean(Class beanClass, int autowireMode, boolean dependencyCheck) throws BeansException; + + /** + * Instantiate a new bean instance of the given class with the specified autowire + * strategy. All constants defined in this interface are supported here. + * Can also be invoked with AUTOWIRE_NO in order to just apply + * before-instantiation callbacks (e.g. for annotation-driven injection). + *

Does not apply standard {@link BeanPostProcessor BeanPostProcessors} + * callbacks or perform any further initialization of the bean. This interface + * offers distinct, fine-grained operations for those purposes, for example + * {@link #initializeBean}. However, {@link InstantiationAwareBeanPostProcessor} + * callbacks are applied, if applicable to the construction of the instance. + * @param beanClass the class of the bean to instantiate + * @param autowireMode by name or type, using the constants in this interface + * @param dependencyCheck whether to perform a dependency check for object + * references in the bean instance (not applicable to autowiring a constructor, + * thus ignored there) + * @return the new bean instance + * @throws BeansException if instantiation or wiring failed + * @see #AUTOWIRE_NO + * @see #AUTOWIRE_BY_NAME + * @see #AUTOWIRE_BY_TYPE + * @see #AUTOWIRE_CONSTRUCTOR + * @see #AUTOWIRE_AUTODETECT + * @see #initializeBean + * @see #applyBeanPostProcessorsBeforeInitialization + * @see #applyBeanPostProcessorsAfterInitialization + */ + Object autowire(Class beanClass, int autowireMode, boolean dependencyCheck) throws BeansException; + + /** + * Autowire the bean properties of the given bean instance by name or type. + * Can also be invoked with AUTOWIRE_NO in order to just apply + * after-instantiation callbacks (e.g. for annotation-driven injection). + *

Does not apply standard {@link BeanPostProcessor BeanPostProcessors} + * callbacks or perform any further initialization of the bean. This interface + * offers distinct, fine-grained operations for those purposes, for example + * {@link #initializeBean}. However, {@link InstantiationAwareBeanPostProcessor} + * callbacks are applied, if applicable to the configuration of the instance. + * @param existingBean the existing bean instance + * @param autowireMode by name or type, using the constants in this interface + * @param dependencyCheck whether to perform a dependency check for object + * references in the bean instance + * @throws BeansException if wiring failed + * @see #AUTOWIRE_BY_NAME + * @see #AUTOWIRE_BY_TYPE + * @see #AUTOWIRE_NO + */ + void autowireBeanProperties(Object existingBean, int autowireMode, boolean dependencyCheck) + throws BeansException; + + /** + * Apply the property values of the bean definition with the given name to + * the given bean instance. The bean definition can either define a fully + * self-contained bean, reusing its property values, or just property values + * meant to be used for existing bean instances. + *

This method does not autowire bean properties; it just applies + * explicitly defined property values. Use the {@link #autowireBeanProperties} + * method to autowire an existing bean instance. + * Note: This method requires a bean definition for the given name! + *

Does not apply standard {@link BeanPostProcessor BeanPostProcessors} + * callbacks or perform any further initialization of the bean. This interface + * offers distinct, fine-grained operations for those purposes, for example + * {@link #initializeBean}. However, {@link InstantiationAwareBeanPostProcessor} + * callbacks are applied, if applicable to the configuration of the instance. + * @param existingBean the existing bean instance + * @param beanName the name of the bean definition in the bean factory + * (a bean definition of that name has to be available) + * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException + * if there is no bean definition with the given name + * @throws BeansException if applying the property values failed + * @see #autowireBeanProperties + */ + void applyBeanPropertyValues(Object existingBean, String beanName) throws BeansException; + + /** + * Initialize the given raw bean, applying factory callbacks + * such as setBeanName and setBeanFactory, + * also applying all bean post processors (including ones which + * might wrap the given raw bean). + *

Note that no bean definition of the given name has to exist + * in the bean factory. The passed-in bean name will simply be used + * for callbacks but not checked against the registered bean definitions. + * @param existingBean the existing bean instance + * @param beanName the name of the bean, to be passed to it if necessary + * (only passed to {@link BeanPostProcessor BeanPostProcessors}) + * @return the bean instance to use, either the original or a wrapped one + * @throws BeansException if the initialization failed + */ + Object initializeBean(Object existingBean, String beanName) throws BeansException; + + /** + * Apply {@link BeanPostProcessor BeanPostProcessors} to the given existing bean + * instance, invoking their postProcessBeforeInitialization methods. + * The returned bean instance may be a wrapper around the original. + * @param existingBean the new bean instance + * @param beanName the name of the bean + * @return the bean instance to use, either the original or a wrapped one + * @throws BeansException if any post-processing failed + * @see BeanPostProcessor#postProcessBeforeInitialization + */ + Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName) + throws BeansException; + + /** + * Apply {@link BeanPostProcessor BeanPostProcessors} to the given existing bean + * instance, invoking their postProcessAfterInitialization methods. + * The returned bean instance may be a wrapper around the original. + * @param existingBean the new bean instance + * @param beanName the name of the bean + * @return the bean instance to use, either the original or a wrapped one + * @throws BeansException if any post-processing failed + * @see BeanPostProcessor#postProcessAfterInitialization + */ + Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) + throws BeansException; + + /** + * Resolve the specified dependency against the beans defined in this factory. + * @param descriptor the descriptor for the dependency + * @param beanName the name of the bean which declares the present dependency + * @param autowiredBeanNames a Set that all names of autowired beans (used for + * resolving the present dependency) are supposed to be added to + * @param typeConverter the TypeConverter to use for populating arrays and + * collections + * @return the resolved object, or null if none found + * @throws BeansException in dependency resolution failed + */ + Object resolveDependency(DependencyDescriptor descriptor, String beanName, + Set autowiredBeanNames, TypeConverter typeConverter) throws BeansException; + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java new file mode 100644 index 0000000000..7eab8d2fa0 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java @@ -0,0 +1,218 @@ +/* + * Copyright 2002-2008 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.config; + +import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.MutablePropertyValues; +import org.springframework.core.AttributeAccessor; + +/** + * A BeanDefinition describes a bean instance, which has property values, + * constructor argument values, and further information supplied by + * concrete implementations. + * + *

This is just a minimal interface: The main intention is to allow a + * {@link BeanFactoryPostProcessor} such as {@link PropertyPlaceholderConfigurer} + * to introspect and modify property values and other bean metadata. + * + * @author Juergen Hoeller + * @author Rob Harrop + * @since 19.03.2004 + * @see ConfigurableListableBeanFactory#getBeanDefinition + * @see org.springframework.beans.factory.support.RootBeanDefinition + * @see org.springframework.beans.factory.support.ChildBeanDefinition + */ +public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement { + + /** + * Scope identifier for the standard singleton scope: "singleton". + *

Note that extended bean factories might support further scopes. + * @see #setScope + */ + String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON; + + /** + * Scope identifier for the standard prototype scope: "prototype". + *

Note that extended bean factories might support further scopes. + * @see #setScope + */ + String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE; + + + /** + * Role hint indicating that a BeanDefinition is a major part + * of the application. Typically corresponds to a user-defined bean. + */ + int ROLE_APPLICATION = 0; + + /** + * Role hint indicating that a BeanDefinition is a supporting + * part of some larger configuration, typically an outer + * {@link org.springframework.beans.factory.parsing.ComponentDefinition}. + * SUPPORT beans are considered important enough to be aware + * of when looking more closely at a particular + * {@link org.springframework.beans.factory.parsing.ComponentDefinition}, + * but not when looking at the overall configuration of an application. + */ + int ROLE_SUPPORT = 1; + + /** + * Role hint indicating that a BeanDefinition is providing an + * entirely background role and has no relevance to the end-user. This hint is + * used when registering beans that are completely part of the internal workings + * of a {@link org.springframework.beans.factory.parsing.ComponentDefinition}. + */ + int ROLE_INFRASTRUCTURE = 2; + + + /** + * Return the name of the parent definition of this bean definition, if any. + */ + String getParentName(); + + /** + * Set the name of the parent definition of this bean definition, if any. + */ + void setParentName(String parentName); + + /** + * Return the current bean class name of this bean definition. + *

Note that this does not have to be the actual class name used at runtime, in + * case of a child definition overriding/inheriting the class name from its parent. + * Hence, do not consider this to be the definitive bean type at runtime but + * rather only use it for parsing purposes at the individual bean definition level. + */ + String getBeanClassName(); + + /** + * Override the bean class name of this bean definition. + *

The class name can be modified during bean factory post-processing, + * typically replacing the original class name with a parsed variant of it. + */ + void setBeanClassName(String beanClassName); + + /** + * Return the factory bean name, if any. + */ + String getFactoryBeanName(); + + /** + * Specify the factory bean to use, if any. + */ + void setFactoryBeanName(String factoryBeanName); + + /** + * Return a factory method, if any. + */ + String getFactoryMethodName(); + + /** + * Specify a factory method, if any. This method will be invoked with + * constructor arguments, or with no arguments if none are specified. + * The method will be invoked on the specifed factory bean, if any, + * or as static method on the local bean class else. + * @param factoryMethodName static factory method name, + * or null if normal constructor creation should be used + * @see #getBeanClassName() + */ + void setFactoryMethodName(String factoryMethodName); + + /** + * Return the name of the current target scope for this bean. + */ + String getScope(); + + /** + * Override the target scope of this bean, specifying a new scope name. + * @see #SCOPE_SINGLETON + * @see #SCOPE_PROTOTYPE + */ + void setScope(String scope); + + /** + * Return whether this bean is a candidate for getting autowired into some other bean. + */ + boolean isAutowireCandidate(); + + /** + * Set whether this bean is a candidate for getting autowired into some other bean. + */ + void setAutowireCandidate(boolean autowireCandidate); + + + /** + * Return the constructor argument values for this bean. + *

The returned instance can be modified during bean factory post-processing. + * @return the ConstructorArgumentValues object (never null) + */ + ConstructorArgumentValues getConstructorArgumentValues(); + + /** + * Return the property values to be applied to a new instance of the bean. + *

The returned instance can be modified during bean factory post-processing. + * @return the MutablePropertyValues object (never null) + */ + MutablePropertyValues getPropertyValues(); + + + /** + * Return whether this a Singleton, with a single, shared instance + * returned on all calls. + */ + boolean isSingleton(); + + /** + * Return whether this bean is "abstract", that is, not meant to be instantiated. + */ + boolean isAbstract(); + + /** + * Return whether this bean should be lazily initialized, that is, not + * eagerly instantiated on startup. + */ + boolean isLazyInit(); + + /** + * Get the role hint for this BeanDefinition. The role hint + * provides tools with an indication of the importance of a particular + * BeanDefinition. + * @see #ROLE_APPLICATION + * @see #ROLE_INFRASTRUCTURE + * @see #ROLE_SUPPORT + */ + int getRole(); + + /** + * Return a human-readable description of this bean definition. + */ + String getDescription(); + + /** + * Return a description of the resource that this bean definition + * came from (for the purpose of showing context in case of errors). + */ + String getResourceDescription(); + + /** + * Return the originating BeanDefinition, or null if none. + * Allows for retrieving the decorated bean definition, if any. + *

Note that this method returns the immediate originator. Iterate through the + * originator chain to find the original BeanDefinition as defined by the user. + */ + BeanDefinition getOriginatingBeanDefinition(); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/BeanDefinitionHolder.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/BeanDefinitionHolder.java new file mode 100644 index 0000000000..f6816c75d7 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/BeanDefinitionHolder.java @@ -0,0 +1,173 @@ +/* + * Copyright 2002-2006 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.config; + +import org.springframework.beans.BeanMetadataElement; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * Holder for a BeanDefinition with name and aliases. + * Can be registered as a placeholder for an inner bean. + * + *

Can also be used for programmatic registration of inner bean + * definitions. If you don't care about BeanNameAware and the like, + * registering RootBeanDefinition or ChildBeanDefinition is good enough. + * + * @author Juergen Hoeller + * @since 1.0.2 + * @see org.springframework.beans.factory.BeanNameAware + * @see org.springframework.beans.factory.support.RootBeanDefinition + * @see org.springframework.beans.factory.support.ChildBeanDefinition + */ +public class BeanDefinitionHolder implements BeanMetadataElement { + + private final BeanDefinition beanDefinition; + + private final String beanName; + + private final String[] aliases; + + + /** + * Create a new BeanDefinitionHolder. + * @param beanDefinition the BeanDefinition to wrap + * @param beanName the name of the bean, as specified for the bean definition + */ + public BeanDefinitionHolder(BeanDefinition beanDefinition, String beanName) { + this(beanDefinition, beanName, null); + } + + /** + * Create a new BeanDefinitionHolder. + * @param beanDefinition the BeanDefinition to wrap + * @param beanName the name of the bean, as specified for the bean definition + * @param aliases alias names for the bean, or null if none + */ + public BeanDefinitionHolder(BeanDefinition beanDefinition, String beanName, String[] aliases) { + Assert.notNull(beanDefinition, "BeanDefinition must not be null"); + Assert.notNull(beanName, "Bean name must not be null"); + this.beanDefinition = beanDefinition; + this.beanName = beanName; + this.aliases = aliases; + } + + /** + * Copy constructor: Create a new BeanDefinitionHolder with the + * same contents as the given BeanDefinitionHolder instance. + *

Note: The wrapped BeanDefinition reference is taken as-is; + * it is not deeply copied. + * @param beanDefinitionHolder the BeanDefinitionHolder to copy + */ + public BeanDefinitionHolder(BeanDefinitionHolder beanDefinitionHolder) { + Assert.notNull(beanDefinitionHolder, "BeanDefinitionHolder must not be null"); + this.beanDefinition = beanDefinitionHolder.getBeanDefinition(); + this.beanName = beanDefinitionHolder.getBeanName(); + this.aliases = beanDefinitionHolder.getAliases(); + } + + + /** + * Return the wrapped BeanDefinition. + */ + public BeanDefinition getBeanDefinition() { + return this.beanDefinition; + } + + /** + * Return the primary name of the bean, as specified for the bean definition. + */ + public String getBeanName() { + return this.beanName; + } + + /** + * Return the alias names for the bean, as specified directly for the bean definition. + * @return the array of alias names, or null if none + */ + public String[] getAliases() { + return this.aliases; + } + + /** + * Expose the bean definition's source object. + * @see BeanDefinition#getSource() + */ + public Object getSource() { + return this.beanDefinition.getSource(); + } + + + /** + * Return a friendly, short description for the bean, stating name and aliases. + * @see #getBeanName() + * @see #getAliases() + */ + public String getShortDescription() { + StringBuffer sb = new StringBuffer(); + sb.append("Bean definition with name '").append(this.beanName).append("'"); + if (this.aliases != null) { + sb.append(" and aliases [").append(StringUtils.arrayToCommaDelimitedString(this.aliases)).append("]"); + } + return sb.toString(); + } + + /** + * Return a long description for the bean, including name and aliases + * as well as a description of the contained {@link BeanDefinition}. + * @see #getShortDescription() + * @see #getBeanDefinition() + */ + public String getLongDescription() { + StringBuffer sb = new StringBuffer(getShortDescription()); + sb.append(": ").append(this.beanDefinition); + return sb.toString(); + } + + /** + * This implementation returns the long description. Can be overridden + * to return the short description or any kind of custom description instead. + * @see #getLongDescription() + * @see #getShortDescription() + */ + public String toString() { + return getLongDescription(); + } + + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof BeanDefinitionHolder)) { + return false; + } + BeanDefinitionHolder otherHolder = (BeanDefinitionHolder) other; + return this.beanDefinition.equals(otherHolder.beanDefinition) && + this.beanName.equals(otherHolder.beanName) && + ObjectUtils.nullSafeEquals(this.aliases, otherHolder.aliases); + } + + public int hashCode() { + int hashCode = this.beanDefinition.hashCode(); + hashCode = 29 * hashCode + this.beanName.hashCode(); + hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.aliases); + return hashCode; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/BeanDefinitionVisitor.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/BeanDefinitionVisitor.java new file mode 100644 index 0000000000..18e7380ae2 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/BeanDefinitionVisitor.java @@ -0,0 +1,268 @@ +/* + * Copyright 2002-2008 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.config; + +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.PropertyValue; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringValueResolver; + +/** + * Visitor class for traversing {@link BeanDefinition} objects, in particular + * the property values and constructor argument values contained in them, + * resolving bean metadata values. + * + *

Used by {@link PropertyPlaceholderConfigurer} to parse all String values + * contained in a BeanDefinition, resolving any placeholders found. + * + * @author Juergen Hoeller + * @since 1.2 + * @see BeanDefinition + * @see BeanDefinition#getPropertyValues + * @see BeanDefinition#getConstructorArgumentValues + * @see PropertyPlaceholderConfigurer + */ +public class BeanDefinitionVisitor { + + private StringValueResolver valueResolver; + + + /** + * Create a new BeanDefinitionVisitor, applying the specified + * value resolver to all bean metadata values. + * @param valueResolver the StringValueResolver to apply + */ + public BeanDefinitionVisitor(StringValueResolver valueResolver) { + Assert.notNull(valueResolver, "StringValueResolver must not be null"); + this.valueResolver = valueResolver; + } + + /** + * Create a new BeanDefinitionVisitor for subclassing. + * Subclasses need to override the {@link #resolveStringValue} method. + */ + protected BeanDefinitionVisitor() { + } + + + /** + * Traverse the given BeanDefinition object and the MutablePropertyValues + * and ConstructorArgumentValues contained in them. + * @param beanDefinition the BeanDefinition object to traverse + * @see #resolveStringValue(String) + */ + public void visitBeanDefinition(BeanDefinition beanDefinition) { + visitParentName(beanDefinition); + visitBeanClassName(beanDefinition); + visitFactoryBeanName(beanDefinition); + visitFactoryMethodName(beanDefinition); + visitScope(beanDefinition); + visitPropertyValues(beanDefinition.getPropertyValues()); + ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues(); + visitIndexedArgumentValues(cas.getIndexedArgumentValues()); + visitGenericArgumentValues(cas.getGenericArgumentValues()); + } + + protected void visitParentName(BeanDefinition beanDefinition) { + String parentName = beanDefinition.getParentName(); + if (parentName != null) { + String resolvedName = resolveStringValue(parentName); + if (!parentName.equals(resolvedName)) { + beanDefinition.setParentName(resolvedName); + } + } + } + + protected void visitBeanClassName(BeanDefinition beanDefinition) { + String beanClassName = beanDefinition.getBeanClassName(); + if (beanClassName != null) { + String resolvedName = resolveStringValue(beanClassName); + if (!beanClassName.equals(resolvedName)) { + beanDefinition.setBeanClassName(resolvedName); + } + } + } + + protected void visitFactoryBeanName(BeanDefinition beanDefinition) { + String factoryBeanName = beanDefinition.getFactoryBeanName(); + if (factoryBeanName != null) { + String resolvedName = resolveStringValue(factoryBeanName); + if (!factoryBeanName.equals(resolvedName)) { + beanDefinition.setFactoryBeanName(resolvedName); + } + } + } + + protected void visitFactoryMethodName(BeanDefinition beanDefinition) { + String factoryMethodName = beanDefinition.getFactoryBeanName(); + if (factoryMethodName != null) { + String resolvedName = resolveStringValue(factoryMethodName); + if (!factoryMethodName.equals(resolvedName)) { + beanDefinition.setFactoryMethodName(resolvedName); + } + } + } + + protected void visitScope(BeanDefinition beanDefinition) { + String scope = beanDefinition.getScope(); + if (scope != null) { + String resolvedScope = resolveStringValue(scope); + if (!scope.equals(resolvedScope)) { + beanDefinition.setScope(resolvedScope); + } + } + } + + protected void visitPropertyValues(MutablePropertyValues pvs) { + PropertyValue[] pvArray = pvs.getPropertyValues(); + for (int i = 0; i < pvArray.length; i++) { + PropertyValue pv = pvArray[i]; + Object newVal = resolveValue(pv.getValue()); + if (!ObjectUtils.nullSafeEquals(newVal, pv.getValue())) { + pvs.addPropertyValue(pv.getName(), newVal); + } + } + } + + protected void visitIndexedArgumentValues(Map ias) { + for (Iterator it = ias.values().iterator(); it.hasNext();) { + ConstructorArgumentValues.ValueHolder valueHolder = + (ConstructorArgumentValues.ValueHolder) it.next(); + Object newVal = resolveValue(valueHolder.getValue()); + if (!ObjectUtils.nullSafeEquals(newVal, valueHolder.getValue())) { + valueHolder.setValue(newVal); + } + } + } + + protected void visitGenericArgumentValues(List gas) { + for (Iterator it = gas.iterator(); it.hasNext();) { + ConstructorArgumentValues.ValueHolder valueHolder = + (ConstructorArgumentValues.ValueHolder) it.next(); + Object newVal = resolveValue(valueHolder.getValue()); + if (!ObjectUtils.nullSafeEquals(newVal, valueHolder.getValue())) { + valueHolder.setValue(newVal); + } + } + } + + protected Object resolveValue(Object value) { + if (value instanceof BeanDefinition) { + visitBeanDefinition((BeanDefinition) value); + } + else if (value instanceof BeanDefinitionHolder) { + visitBeanDefinition(((BeanDefinitionHolder) value).getBeanDefinition()); + } + else if (value instanceof RuntimeBeanReference) { + RuntimeBeanReference ref = (RuntimeBeanReference) value; + String newBeanName = resolveStringValue(ref.getBeanName()); + if (!newBeanName.equals(ref.getBeanName())) { + return new RuntimeBeanReference(newBeanName); + } + } + else if (value instanceof List) { + visitList((List) value); + } + else if (value instanceof Set) { + visitSet((Set) value); + } + else if (value instanceof Map) { + visitMap((Map) value); + } + else if (value instanceof TypedStringValue) { + TypedStringValue typedStringValue = (TypedStringValue) value; + String stringValue = typedStringValue.getValue(); + if (stringValue != null) { + String visitedString = resolveStringValue(stringValue); + typedStringValue.setValue(visitedString); + } + } + else if (value instanceof String) { + return resolveStringValue((String) value); + } + return value; + } + + protected void visitList(List listVal) { + for (int i = 0; i < listVal.size(); i++) { + Object elem = listVal.get(i); + Object newVal = resolveValue(elem); + if (!ObjectUtils.nullSafeEquals(newVal, elem)) { + listVal.set(i, newVal); + } + } + } + + protected void visitSet(Set setVal) { + Set newContent = new LinkedHashSet(); + boolean entriesModified = false; + for (Iterator it = setVal.iterator(); it.hasNext();) { + Object elem = it.next(); + int elemHash = (elem != null ? elem.hashCode() : 0); + Object newVal = resolveValue(elem); + int newValHash = (newVal != null ? newVal.hashCode() : 0); + newContent.add(newVal); + entriesModified = entriesModified || (newVal != elem || newValHash != elemHash); + } + if (entriesModified) { + setVal.clear(); + setVal.addAll(newContent); + } + } + + protected void visitMap(Map mapVal) { + Map newContent = new LinkedHashMap(); + boolean entriesModified = false; + for (Iterator it = mapVal.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + Object key = entry.getKey(); + int keyHash = (key != null ? key.hashCode() : 0); + Object newKey = resolveValue(key); + int newKeyHash = (newKey != null ? newKey.hashCode() : 0); + Object val = entry.getValue(); + Object newVal = resolveValue(val); + newContent.put(newKey, newVal); + entriesModified = entriesModified || (newVal != val || newKey != key || newKeyHash != keyHash); + } + if (entriesModified) { + mapVal.clear(); + mapVal.putAll(newContent); + } + } + + /** + * Resolve the given String value, for example parsing placeholders. + * @param strVal the original String value + * @return the resolved String value + */ + protected String resolveStringValue(String strVal) { + if (this.valueResolver == null) { + throw new IllegalStateException("No StringValueResolver specified - pass a resolver " + + "object into the constructor or override the 'resolveStringValue' method"); + } + return this.valueResolver.resolveStringValue(strVal); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/BeanFactoryPostProcessor.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/BeanFactoryPostProcessor.java new file mode 100644 index 0000000000..c61ce8272d --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/BeanFactoryPostProcessor.java @@ -0,0 +1,51 @@ +/* + * Copyright 2002-2005 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.config; + +import org.springframework.beans.BeansException; + +/** + * Allows for custom modification of an application context's bean definitions, + * adapting the bean property values of the context's underlying bean factory. + * + *

Application contexts can auto-detect BeanFactoryPostProcessor beans in + * their bean definitions and apply them before any other beans get created. + * + *

Useful for custom config files targeted at system administrators that + * override bean properties configured in the application context. + * + *

See PropertyResourceConfigurer and its concrete implementations + * for out-of-the-box solutions that address such configuration needs. + * + * @author Juergen Hoeller + * @since 06.07.2003 + * @see BeanPostProcessor + * @see PropertyResourceConfigurer + */ +public interface BeanFactoryPostProcessor { + + /** + * Modify the application context's internal bean factory after its standard + * initialization. All bean definitions will have been loaded, but no beans + * will have been instantiated yet. This allows for overriding or adding + * properties even to eager-initializing beans. + * @param beanFactory the bean factory used by the application context + * @throws org.springframework.beans.BeansException in case of errors + */ + void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException; + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/BeanPostProcessor.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/BeanPostProcessor.java new file mode 100644 index 0000000000..180bede10e --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/BeanPostProcessor.java @@ -0,0 +1,78 @@ +/* + * Copyright 2002-2007 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.config; + +import org.springframework.beans.BeansException; + +/** + * Factory hook that allows for custom modification of new bean instances, + * e.g. checking for marker interfaces or wrapping them with proxies. + * + *

ApplicationContexts can autodetect BeanPostProcessor beans in their + * bean definitions and apply them to any beans subsequently created. + * Plain bean factories allow for programmatic registration of post-processors, + * applying to all beans created through this factory. + * + *

Typically, post-processors that populate beans via marker interfaces + * or the like will implement {@link #postProcessBeforeInitialization}, + * while post-processors that wrap beans with proxies will normally + * implement {@link #postProcessAfterInitialization}. + * + * @author Juergen Hoeller + * @since 10.10.2003 + * @see InstantiationAwareBeanPostProcessor + * @see DestructionAwareBeanPostProcessor + * @see ConfigurableBeanFactory#addBeanPostProcessor + * @see BeanFactoryPostProcessor + */ +public interface BeanPostProcessor { + + /** + * Apply this BeanPostProcessor to the given new bean instance before any bean + * initialization callbacks (like InitializingBean's afterPropertiesSet + * or a custom init-method). The bean will already be populated with property values. + * The returned bean instance may be a wrapper around the original. + * @param bean the new bean instance + * @param beanName the name of the bean + * @return the bean instance to use, either the original or a wrapped one + * @throws org.springframework.beans.BeansException in case of errors + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet + */ + Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException; + + /** + * Apply this BeanPostProcessor to the given new bean instance after any bean + * initialization callbacks (like InitializingBean's afterPropertiesSet + * or a custom init-method). The bean will already be populated with property values. + * The returned bean instance may be a wrapper around the original. + *

In case of a FactoryBean, this callback will be invoked for both the FactoryBean + * instance and the objects created by the FactoryBean (as of Spring 2.0). The + * post-processor can decide whether to apply to either the FactoryBean or created + * objects or both through corresponding bean instanceof FactoryBean checks. + *

This callback will also be invoked after a short-circuiting triggered by a + * {@link InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation} method, + * in contrast to all other BeanPostProcessor callbacks. + * @param bean the new bean instance + * @param beanName the name of the bean + * @return the bean instance to use, either the original or a wrapped one + * @throws org.springframework.beans.BeansException in case of errors + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet + * @see org.springframework.beans.factory.FactoryBean + */ + Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException; + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/BeanReference.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/BeanReference.java new file mode 100644 index 0000000000..2dac38ecc7 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/BeanReference.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2006 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.config; + +import org.springframework.beans.BeanMetadataElement; + +/** + * Interface that exposes a reference to a bean name in an abstract fashion. + * This interface does not necessarily imply a reference to an actual bean + * instance; it just expresses a logical reference to the name of a bean. + * + *

Serves as common interface implemented by any kind of bean reference + * holder, such as {@link RuntimeBeanReference RuntimeBeanReference} and + * {@link RuntimeBeanNameReference RuntimeBeanNameReference}. + * + * @author Juergen Hoeller + * @since 2.0 + */ +public interface BeanReference extends BeanMetadataElement { + + /** + * Return the target bean name that this reference points to (never null). + */ + String getBeanName(); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/BeanReferenceFactoryBean.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/BeanReferenceFactoryBean.java new file mode 100644 index 0000000000..d58fa56f8a --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/BeanReferenceFactoryBean.java @@ -0,0 +1,108 @@ +/* + * Copyright 2002-2007 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.config; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.FactoryBeanNotInitializedException; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.SmartFactoryBean; + +/** + * FactoryBean that exposes an arbitrary target bean under a different name. + * + *

Usually, the target bean will reside in a different bean definition file, + * using this FactoryBean to link it in and expose it under a different name. + * Effectively, this corresponds to an alias for the target bean. + * + *

NOTE: For XML bean definition files, an <alias> + * tag is available that effectively achieves the same. + * + *

A special capability of this FactoryBean is enabled through its configuration + * as bean definition: The "targetBeanName" can be substituted through a placeholder, + * in combination with Spring's {@link PropertyPlaceholderConfigurer}. + * Thanks to Marcus Bristav for pointing this out! + * + * @author Juergen Hoeller + * @since 1.2 + * @see #setTargetBeanName + * @see PropertyPlaceholderConfigurer + */ +public class BeanReferenceFactoryBean implements SmartFactoryBean, BeanFactoryAware { + + private String targetBeanName; + + private BeanFactory beanFactory; + + + /** + * Set the name of the target bean. + *

This property is required. The value for this property can be + * substituted through a placeholder, in combination with Spring's + * PropertyPlaceholderConfigurer. + * @param targetBeanName the name of the target bean + * @see PropertyPlaceholderConfigurer + */ + public void setTargetBeanName(String targetBeanName) { + this.targetBeanName = targetBeanName; + } + + public void setBeanFactory(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + if (this.targetBeanName == null) { + throw new IllegalArgumentException("'targetBeanName' is required"); + } + if (!this.beanFactory.containsBean(this.targetBeanName)) { + throw new NoSuchBeanDefinitionException(this.targetBeanName, this.beanFactory.toString()); + } + } + + + public Object getObject() throws BeansException { + if (this.beanFactory == null) { + throw new FactoryBeanNotInitializedException(); + } + return this.beanFactory.getBean(this.targetBeanName); + } + + public Class getObjectType() { + if (this.beanFactory == null) { + return null; + } + return this.beanFactory.getType(this.targetBeanName); + } + + public boolean isSingleton() { + if (this.beanFactory == null) { + throw new FactoryBeanNotInitializedException(); + } + return this.beanFactory.isSingleton(this.targetBeanName); + } + + public boolean isPrototype() { + if (this.beanFactory == null) { + throw new FactoryBeanNotInitializedException(); + } + return this.beanFactory.isPrototype(this.targetBeanName); + } + + public boolean isEagerInit() { + return false; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/CommonsLogFactoryBean.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/CommonsLogFactoryBean.java new file mode 100644 index 0000000000..12e082b388 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/CommonsLogFactoryBean.java @@ -0,0 +1,69 @@ +/* + * Copyright 2002-2005 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.config; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; + +/** + * Factory bean for + * commons-logging + * {@link org.apache.commons.logging.Log} instances. + * + *

Useful for sharing Log instances among multiple beans instead of using + * one Log instance per class name, e.g. for common log topics. + * + * @author Juergen Hoeller + * @see org.apache.commons.logging.Log + * @since 16.11.2003 + */ +public class CommonsLogFactoryBean implements FactoryBean, InitializingBean { + + private Log log; + + + /** + * The name of the log. + *

This property is required. + * @param logName the name of the log + */ + public void setLogName(String logName) { + this.log = LogFactory.getLog(logName); + } + + + public void afterPropertiesSet() { + if (this.log == null) { + throw new IllegalArgumentException("logName is required"); + } + } + + public Object getObject() { + return log; + } + + public Class getObjectType() { + return (this.log != null ? this.log.getClass() : Log.class); + } + + public boolean isSingleton() { + return true; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java new file mode 100644 index 0000000000..c84c9b6a72 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java @@ -0,0 +1,329 @@ +/* + * Copyright 2002-2008 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.config; + +import java.beans.PropertyEditor; + +import org.springframework.beans.PropertyEditorRegistrar; +import org.springframework.beans.PropertyEditorRegistry; +import org.springframework.beans.TypeConverter; +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.HierarchicalBeanFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.util.StringValueResolver; + +/** + * Configuration interface to be implemented by most bean factories. Provides + * facilities to configure a bean factory, in addition to the bean factory + * client methods in the {@link org.springframework.beans.factory.BeanFactory} + * interface. + * + *

This bean factory interface is not meant to be used in normal application + * code: Stick to {@link org.springframework.beans.factory.BeanFactory} or + * {@link org.springframework.beans.factory.ListableBeanFactory} for typical + * needs. This extended interface is just meant to allow for framework-internal + * plug'n'play and for special access to bean factory configuration methods. + * + * @author Juergen Hoeller + * @since 03.11.2003 + * @see org.springframework.beans.factory.BeanFactory + * @see org.springframework.beans.factory.ListableBeanFactory + * @see ConfigurableListableBeanFactory + */ +public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, SingletonBeanRegistry { + + /** + * Scope identifier for the standard singleton scope: "singleton". + * Custom scopes can be added via registerScope. + * @see #registerScope + */ + String SCOPE_SINGLETON = "singleton"; + + /** + * Scope identifier for the standard prototype scope: "prototype". + * Custom scopes can be added via registerScope. + * @see #registerScope + */ + String SCOPE_PROTOTYPE = "prototype"; + + + /** + * Set the parent of this bean factory. + *

Note that the parent cannot be changed: It should only be set outside + * a constructor if it isn't available at the time of factory instantiation. + * @param parentBeanFactory the parent BeanFactory + * @throws IllegalStateException if this factory is already associated with + * a parent BeanFactory + * @see #getParentBeanFactory() + */ + void setParentBeanFactory(BeanFactory parentBeanFactory) throws IllegalStateException; + + /** + * Set the class loader to use for loading bean classes. + * Default is the thread context class loader. + *

Note that this class loader will only apply to bean definitions + * that do not carry a resolved bean class yet. This is the case as of + * Spring 2.0 by default: Bean definitions only carry bean class names, + * to be resolved once the factory processes the bean definition. + * @param beanClassLoader the class loader to use, + * or null to suggest the default class loader + */ + void setBeanClassLoader(ClassLoader beanClassLoader); + + /** + * Return this factory's class loader for loading bean classes. + */ + ClassLoader getBeanClassLoader(); + + /** + * Specify a temporary ClassLoader to use for type matching purposes. + * Default is none, simply using the standard bean ClassLoader. + *

A temporary ClassLoader is usually just specified if + * load-time weaving is involved, to make sure that actual bean + * classes are loaded as lazily as possible. The temporary loader is + * then removed once the BeanFactory completes its bootstrap phase. + */ + void setTempClassLoader(ClassLoader tempClassLoader); + + /** + * Return the temporary ClassLoader to use for type matching purposes, + * if any. + */ + ClassLoader getTempClassLoader(); + + /** + * Set whether to cache bean metadata such as given bean definitions + * (in merged fashion) and resolved bean classes. Default is on. + *

Turn this flag off to enable hot-refreshing of bean definition objects + * and in particular bean classes. If this flag is off, any creation of a bean + * instance will re-query the bean class loader for newly resolved classes. + */ + void setCacheBeanMetadata(boolean cacheBeanMetadata); + + /** + * Return whether to cache bean metadata such as given bean definitions + * (in merged fashion) and resolved bean classes. + */ + boolean isCacheBeanMetadata(); + + /** + * Add a PropertyEditorRegistrar to be applied to all bean creation processes. + *

Such a registrar creates new PropertyEditor instances and registers them + * on the given registry, fresh for each bean creation attempt. This avoids + * the need for synchronization on custom editors; hence, it is generally + * preferable to use this method instead of {@link #registerCustomEditor}. + * @param registrar the PropertyEditorRegistrar to register + */ + void addPropertyEditorRegistrar(PropertyEditorRegistrar registrar); + + /** + * Register the given custom property editor for all properties of the + * given type. To be invoked during factory configuration. + *

Note that this method will register a shared custom editor instance; + * access to that instance will be synchronized for thread-safety. It is + * generally preferable to use {@link #addPropertyEditorRegistrar} instead + * of this method, to avoid for the need for synchronization on custom editors. + * @param requiredType type of the property + * @param propertyEditorClass the {@link PropertyEditor} class to register + */ + void registerCustomEditor(Class requiredType, Class propertyEditorClass); + + /** + * Register the given custom property editor for all properties of the + * given type. To be invoked during factory configuration. + *

Note that this method will register a shared custom editor instance; + * access to that instance will be synchronized for thread-safety. It is + * generally preferable to use {@link #addPropertyEditorRegistrar} instead + * of this method, to avoid for the need for synchronization on custom editors. + * @param requiredType type of the property + * @param propertyEditor editor to register + * @deprecated as of Spring 2.0.7, in favor of {@link #addPropertyEditorRegistrar} + * and {@link #registerCustomEditor(Class, Class)} + */ + void registerCustomEditor(Class requiredType, PropertyEditor propertyEditor); + + /** + * Initialize the given PropertyEditorRegistry with the custom editors + * that have been registered with this BeanFactory. + * @param registry the PropertyEditorRegistry to initialize + */ + void copyRegisteredEditorsTo(PropertyEditorRegistry registry); + + /** + * Set a custom type converter that this BeanFactory should use for converting + * bean property values, constructor argument values, etc. + *

This will override the default PropertyEditor mechanism and hence make + * any custom editors or custom editor registrars irrelevant. + * @see #addPropertyEditorRegistrar + * @see #registerCustomEditor + */ + void setTypeConverter(TypeConverter typeConverter); + + /** + * Obtain a type converter as used by this BeanFactory. This may be a fresh + * instance for each call, since TypeConverters are usually not thread-safe. + *

If the default PropertyEditor mechanism is active, the returned + * TypeConverter will be aware of all custom editors that have been registered. + */ + TypeConverter getTypeConverter(); + + /** + * Add a new BeanPostProcessor that will get applied to beans created + * by this factory. To be invoked during factory configuration. + * @param beanPostProcessor the bean processor to register + */ + void addBeanPostProcessor(BeanPostProcessor beanPostProcessor); + + /** + * Return the current number of registered BeanPostProcessors, if any. + */ + int getBeanPostProcessorCount(); + + /** + * Register the given scope, backed by the given Scope implementation. + * @param scopeName the scope identifier + * @param scope the backing Scope implementation + */ + void registerScope(String scopeName, Scope scope); + + /** + * Return the names of all currently registered scopes. + *

This will only return the names of explicitly registered scopes. + * Built-in scopes such as "singleton" and "prototype" won't be exposed. + * @return the array of scope names, or an empty array if none + * @see #registerScope + */ + String[] getRegisteredScopeNames(); + + /** + * Return the Scope implementation for the given scope name, if any. + *

This will only return explicitly registered scopes. + * Built-in scopes such as "singleton" and "prototype" won't be exposed. + * @param scopeName the name of the scope + * @return the registered Scope implementation, or null if none + * @see #registerScope + */ + Scope getRegisteredScope(String scopeName); + + /** + * Copy all relevant configuration from the given other factory. + *

Should include all standard configuration settings as well as + * BeanPostProcessors, Scopes, and factory-specific internal settings. + * Should not include any metadata of actual bean definitions, + * such as BeanDefinition objects and bean name aliases. + * @param otherFactory the other BeanFactory to copy from + */ + void copyConfigurationFrom(ConfigurableBeanFactory otherFactory); + + /** + * Given a bean name, create an alias. We typically use this method to + * support names that are illegal within XML ids (used for bean names). + *

Typically invoked during factory configuration, but can also be + * used for runtime registration of aliases. Therefore, a factory + * implementation should synchronize alias access. + * @param beanName the canonical name of the target bean + * @param alias the alias to be registered for the bean + * @throws BeanDefinitionStoreException if the alias is already in use + */ + void registerAlias(String beanName, String alias) throws BeanDefinitionStoreException; + + /** + * Resolve all alias target names and aliases registered in this + * factory, applying the given StringValueResolver to them. + *

The value resolver may for example resolve placeholders + * in target bean names and even in alias names. + * @param valueResolver the StringValueResolver to apply + */ + void resolveAliases(StringValueResolver valueResolver); + + /** + * Return a merged BeanDefinition for the given bean name, + * merging a child bean definition with its parent if necessary. + * Considers bean definitions in ancestor factories as well. + * @param beanName the name of the bean to retrieve the merged definition for + * @return a (potentially merged) BeanDefinition for the given bean + * @throws NoSuchBeanDefinitionException if there is no bean definition with the given name + */ + BeanDefinition getMergedBeanDefinition(String beanName) throws NoSuchBeanDefinitionException; + + /** + * Determine whether the bean with the given name is a FactoryBean. + * @param name the name of the bean to check + * @return whether the bean is a FactoryBean + * (false means the bean exists but is not a FactoryBean) + * @throws NoSuchBeanDefinitionException if there is no bean with the given name + */ + boolean isFactoryBean(String name) throws NoSuchBeanDefinitionException; + + /** + * Determine whether the specified bean is currently in creation. + * @param beanName the name of the bean + * @return whether the bean is currently in creation + */ + boolean isCurrentlyInCreation(String beanName); + + /** + * Register a dependent bean for the given bean, + * to be destroyed before the given bean is destroyed. + * @param beanName the name of the bean + * @param dependentBeanName the name of the dependent bean + */ + void registerDependentBean(String beanName, String dependentBeanName); + + /** + * Return the names of all beans which depend on the specified bean, if any. + * @param beanName the name of the bean + * @return the array of dependent bean names, or an empty array if none + */ + String[] getDependentBeans(String beanName); + + /** + * Return the names of all beans that the specified bean depends on, if any. + * @param beanName the name of the bean + * @return the array of names of beans which the bean depends on, + * or an empty array if none + */ + String[] getDependenciesForBean(String beanName); + + /** + * Destroy the given bean instance (usually a prototype instance + * obtained from this factory) according to its bean definition. + *

Any exception that arises during destruction should be caught + * and logged instead of propagated to the caller of this method. + * @param beanName the name of the bean definition + * @param beanInstance the bean instance to destroy + */ + void destroyBean(String beanName, Object beanInstance); + + /** + * Destroy the specified scoped bean in the current target scope, if any. + *

Any exception that arises during destruction should be caught + * and logged instead of propagated to the caller of this method. + * @param beanName the name of the scoped bean + */ + void destroyScopedBean(String beanName); + + /** + * Destroy all singleton beans in this factory, including inner beans that have + * been registered as disposable. To be called on shutdown of a factory. + *

Any exception that arises during destruction should be caught + * and logged instead of propagated to the caller of this method. + */ + void destroySingletons(); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/ConfigurableListableBeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/ConfigurableListableBeanFactory.java new file mode 100644 index 0000000000..ff9d5ee135 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/ConfigurableListableBeanFactory.java @@ -0,0 +1,133 @@ +/* + * Copyright 2002-2008 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.config; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; + +/** + * Configuration interface to be implemented by most listable bean factories. + * In addition to {@link ConfigurableBeanFactory}, it provides facilities to + * analyze and modify bean definitions, and to pre-instantiate singletons. + * + *

This subinterface of {@link org.springframework.beans.factory.BeanFactory} + * is not meant to be used in normal application code: Stick to + * {@link org.springframework.beans.factory.BeanFactory} or + * {@link org.springframework.beans.factory.ListableBeanFactory} for typical + * use cases. This interface is just meant to allow for framework-internal + * plug'n'play even when needing access to bean factory configuration methods. + * + * @author Juergen Hoeller + * @since 03.11.2003 + * @see org.springframework.context.support.AbstractApplicationContext#getBeanFactory() + */ +public interface ConfigurableListableBeanFactory + extends ListableBeanFactory, AutowireCapableBeanFactory, ConfigurableBeanFactory { + + /** + * Ignore the given dependency type for autowiring: + * for example, String. Default is none. + * @param type the dependency type to ignore + */ + void ignoreDependencyType(Class type); + + /** + * Ignore the given dependency interface for autowiring. + *

This will typically be used by application contexts to register + * dependencies that are resolved in other ways, like BeanFactory through + * BeanFactoryAware or ApplicationContext through ApplicationContextAware. + *

By default, only the BeanFactoryAware interface is ignored. + * For further types to ignore, invoke this method for each type. + * @param ifc the dependency interface to ignore + * @see org.springframework.beans.factory.BeanFactoryAware + * @see org.springframework.context.ApplicationContextAware + */ + void ignoreDependencyInterface(Class ifc); + + /** + * Register a special dependency type with corresponding autowired value. + *

This is intended for factory/context references that are supposed + * to be autowirable but are not defined as beans in the factory: + * e.g. a dependency of type ApplicationContext resolved to the + * ApplicationContext instance that the bean is living in. + *

Note: There are no such default types registered in a plain BeanFactory, + * not even for the BeanFactory interface itself. + * @param dependencyType the dependency type to register. This will typically + * be a base interface such as BeanFactory, with extensions of it resolved + * as well if declared as an autowiring dependency (e.g. ListableBeanFactory), + * as long as the given value actually implements the extended interface. + * @param autowiredValue the corresponding autowired value. This may also be an + * implementation of the {@link org.springframework.beans.factory.ObjectFactory} + * interface, which allows for lazy resolution of the actual target value. + */ + void registerResolvableDependency(Class dependencyType, Object autowiredValue); + + /** + * Determine whether the specified bean qualifies as an autowire candidate, + * to be injected into other beans which declare a dependency of matching type. + *

This method checks ancestor factories as well. + * @param beanName the name of the bean to check + * @param descriptor the descriptor of the dependency to resolve + * @return whether the bean should be considered as autowire candidate + * @throws NoSuchBeanDefinitionException if there is no bean with the given name + */ + boolean isAutowireCandidate(String beanName, DependencyDescriptor descriptor) + throws NoSuchBeanDefinitionException; + + /** + * Return the registered BeanDefinition for the specified bean, allowing access + * to its property values and constructor argument value (which can be + * modified during bean factory post-processing). + *

A returned BeanDefinition object should not be a copy but the original + * definition object as registered in the factory. This means that it should + * be castable to a more specific implementation type, if necessary. + *

NOTE: This method does not consider ancestor factories. + * It is only meant for accessing local bean definitions of this factory. + * @param beanName the name of the bean + * @return the registered BeanDefinition + * @throws NoSuchBeanDefinitionException if there is no bean with the given name + * defined in this factory + */ + BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException; + + /** + * Freeze all bean definitions, signalling that the registered bean definitions + * will not be modified or post-processed any further. + *

This allows the factory to aggressively cache bean definition metadata. + */ + void freezeConfiguration(); + + /** + * Return whether this factory's bean definitions are frozen, + * i.e. are not supposed to be modified or post-processed any further. + * @return true if the factory's configuration is considered frozen + */ + boolean isConfigurationFrozen(); + + /** + * Ensure that all non-lazy-init singletons are instantiated, also considering + * {@link org.springframework.beans.factory.FactoryBean FactoryBeans}. + * Typically invoked at the end of factory setup, if desired. + * @throws BeansException if one of the singleton beans could not be created. + * Note: This may have left the factory with some beans already initialized! + * Call {@link #destroySingletons()} for full cleanup in this case. + * @see #destroySingletons() + */ + void preInstantiateSingletons() throws BeansException; + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/ConstructorArgumentValues.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/ConstructorArgumentValues.java new file mode 100644 index 0000000000..8a129485dc --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/ConstructorArgumentValues.java @@ -0,0 +1,502 @@ +/* + * Copyright 2002-2007 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.config; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.Mergeable; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; + +/** + * Holder for constructor argument values, typically as part of a bean definition. + * + *

Supports values for a specific index in the constructor argument list + * as well as for generic argument matches by type. + * + * @author Juergen Hoeller + * @since 09.11.2003 + * @see BeanDefinition#getConstructorArgumentValues + */ +public class ConstructorArgumentValues { + + private final Map indexedArgumentValues = new HashMap(); + + private final List genericArgumentValues = new LinkedList(); + + + /** + * Create a new empty ConstructorArgumentValues object. + */ + public ConstructorArgumentValues() { + } + + /** + * Deep copy constructor. + * @param original the ConstructorArgumentValues to copy + */ + public ConstructorArgumentValues(ConstructorArgumentValues original) { + addArgumentValues(original); + } + + /** + * Copy all given argument values into this object, using separate holder + * instances to keep the values independent from the original object. + *

Note: Identical ValueHolder instances will only be registered once, + * to allow for merging and re-merging of argument value definitions. Distinct + * ValueHolder instances carrying the same content are of course allowed. + */ + public void addArgumentValues(ConstructorArgumentValues other) { + if (other != null) { + for (Iterator it = other.indexedArgumentValues.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + ValueHolder valueHolder = (ValueHolder) entry.getValue(); + addOrMergeIndexedArgumentValue(entry.getKey(), valueHolder.copy()); + } + for (Iterator it = other.genericArgumentValues.iterator(); it.hasNext();) { + ValueHolder valueHolder = (ValueHolder) it.next(); + if (!this.genericArgumentValues.contains(valueHolder)) { + this.genericArgumentValues.add(valueHolder.copy()); + } + } + } + } + + + /** + * Add argument value for the given index in the constructor argument list. + * @param index the index in the constructor argument list + * @param value the argument value + */ + public void addIndexedArgumentValue(int index, Object value) { + addIndexedArgumentValue(index, new ValueHolder(value)); + } + + /** + * Add argument value for the given index in the constructor argument list. + * @param index the index in the constructor argument list + * @param value the argument value + * @param type the type of the constructor argument + */ + public void addIndexedArgumentValue(int index, Object value, String type) { + addIndexedArgumentValue(index, new ValueHolder(value, type)); + } + + /** + * Add argument value for the given index in the constructor argument list. + * @param index the index in the constructor argument list + * @param newValue the argument value in the form of a ValueHolder + */ + public void addIndexedArgumentValue(int index, ValueHolder newValue) { + Assert.isTrue(index >= 0, "Index must not be negative"); + Assert.notNull(newValue, "ValueHolder must not be null"); + addOrMergeIndexedArgumentValue(new Integer(index), newValue); + } + + /** + * Add argument value for the given index in the constructor argument list, + * merging the new value (typically a collection) with the current value + * if demanded: see {@link org.springframework.beans.Mergeable}. + * @param key the index in the constructor argument list + * @param newValue the argument value in the form of a ValueHolder + */ + private void addOrMergeIndexedArgumentValue(Object key, ValueHolder newValue) { + ValueHolder currentValue = (ValueHolder) this.indexedArgumentValues.get(key); + if (currentValue != null && newValue.getValue() instanceof Mergeable) { + Mergeable mergeable = (Mergeable) newValue.getValue(); + if (mergeable.isMergeEnabled()) { + newValue.setValue(mergeable.merge(currentValue.getValue())); + } + } + this.indexedArgumentValues.put(key, newValue); + } + + /** + * Get argument value for the given index in the constructor argument list. + * @param index the index in the constructor argument list + * @param requiredType the type to match (can be null to match + * untyped values only) + * @return the ValueHolder for the argument, or null if none set + */ + public ValueHolder getIndexedArgumentValue(int index, Class requiredType) { + Assert.isTrue(index >= 0, "Index must not be negative"); + ValueHolder valueHolder = (ValueHolder) this.indexedArgumentValues.get(new Integer(index)); + if (valueHolder != null) { + if (valueHolder.getType() == null || + (requiredType != null && requiredType.getName().equals(valueHolder.getType()))) { + return valueHolder; + } + } + return null; + } + + /** + * Return the map of indexed argument values. + * @return unmodifiable Map with Integer index as key and ValueHolder as value + * @see ValueHolder + */ + public Map getIndexedArgumentValues() { + return Collections.unmodifiableMap(this.indexedArgumentValues); + } + + + /** + * Add generic argument value to be matched by type. + *

Note: A single generic argument value will just be used once, + * rather than matched multiple times (as of Spring 1.1). + * @param value the argument value + */ + public void addGenericArgumentValue(Object value) { + this.genericArgumentValues.add(new ValueHolder(value)); + } + + /** + * Add generic argument value to be matched by type. + *

Note: A single generic argument value will just be used once, + * rather than matched multiple times (as of Spring 1.1). + * @param value the argument value + * @param type the type of the constructor argument + */ + public void addGenericArgumentValue(Object value, String type) { + this.genericArgumentValues.add(new ValueHolder(value, type)); + } + + /** + * Add generic argument value to be matched by type. + *

Note: A single generic argument value will just be used once, + * rather than matched multiple times (as of Spring 1.1). + * @param newValue the argument value in the form of a ValueHolder + *

Note: Identical ValueHolder instances will only be registered once, + * to allow for merging and re-merging of argument value definitions. Distinct + * ValueHolder instances carrying the same content are of course allowed. + */ + public void addGenericArgumentValue(ValueHolder newValue) { + Assert.notNull(newValue, "ValueHolder must not be null"); + if (!this.genericArgumentValues.contains(newValue)) { + this.genericArgumentValues.add(newValue); + } + } + + /** + * Look for a generic argument value that matches the given type. + * @param requiredType the type to match (can be null to find + * an arbitrary next generic argument value) + * @return the ValueHolder for the argument, or null if none set + */ + public ValueHolder getGenericArgumentValue(Class requiredType) { + return getGenericArgumentValue(requiredType, null); + } + + /** + * Look for the next generic argument value that matches the given type, + * ignoring argument values that have already been used in the current + * resolution process. + * @param requiredType the type to match (can be null to find + * an arbitrary next generic argument value) + * @param usedValueHolders a Set of ValueHolder objects that have already been used + * in the current resolution process and should therefore not be returned again + * @return the ValueHolder for the argument, or null if none found + */ + public ValueHolder getGenericArgumentValue(Class requiredType, Set usedValueHolders) { + for (Iterator it = this.genericArgumentValues.iterator(); it.hasNext();) { + ValueHolder valueHolder = (ValueHolder) it.next(); + if (usedValueHolders == null || !usedValueHolders.contains(valueHolder)) { + if (requiredType != null) { + // Check matching type. + if (valueHolder.getType() != null) { + if (valueHolder.getType().equals(requiredType.getName())) { + return valueHolder; + } + } + else if (ClassUtils.isAssignableValue(requiredType, valueHolder.getValue())) { + return valueHolder; + } + } + else { + // No required type specified -> consider untyped values only. + if (valueHolder.getType() == null) { + return valueHolder; + } + } + } + } + return null; + } + + /** + * Return the list of generic argument values. + * @return unmodifiable List of ValueHolders + * @see ValueHolder + */ + public List getGenericArgumentValues() { + return Collections.unmodifiableList(this.genericArgumentValues); + } + + + /** + * Look for an argument value that either corresponds to the given index + * in the constructor argument list or generically matches by type. + * @param index the index in the constructor argument list + * @param requiredType the type to match + * @return the ValueHolder for the argument, or null if none set + */ + public ValueHolder getArgumentValue(int index, Class requiredType) { + return getArgumentValue(index, requiredType, null); + } + + /** + * Look for an argument value that either corresponds to the given index + * in the constructor argument list or generically matches by type. + * @param index the index in the constructor argument list + * @param requiredType the type to match (can be null to find + * an untyped argument value) + * @param usedValueHolders a Set of ValueHolder objects that have already + * been used in the current resolution process and should therefore not + * be returned again (allowing to return the next generic argument match + * in case of multiple generic argument values of the same type) + * @return the ValueHolder for the argument, or null if none set + */ + public ValueHolder getArgumentValue(int index, Class requiredType, Set usedValueHolders) { + Assert.isTrue(index >= 0, "Index must not be negative"); + ValueHolder valueHolder = getIndexedArgumentValue(index, requiredType); + if (valueHolder == null) { + valueHolder = getGenericArgumentValue(requiredType, usedValueHolders); + } + return valueHolder; + } + + /** + * Return the number of argument values held in this instance, + * counting both indexed and generic argument values. + */ + public int getArgumentCount() { + return (this.indexedArgumentValues.size() + this.genericArgumentValues.size()); + } + + /** + * Return if this holder does not contain any argument values, + * neither indexed ones nor generic ones. + */ + public boolean isEmpty() { + return (this.indexedArgumentValues.isEmpty() && this.genericArgumentValues.isEmpty()); + } + + /** + * Clear this holder, removing all argument values. + */ + public void clear() { + this.indexedArgumentValues.clear(); + this.genericArgumentValues.clear(); + } + + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof ConstructorArgumentValues)) { + return false; + } + ConstructorArgumentValues that = (ConstructorArgumentValues) other; + if (this.genericArgumentValues.size() != that.genericArgumentValues.size() || + this.indexedArgumentValues.size() != that.indexedArgumentValues.size()) { + return false; + } + Iterator it1 = this.genericArgumentValues.iterator(); + Iterator it2 = that.genericArgumentValues.iterator(); + while (it1.hasNext() && it2.hasNext()) { + ValueHolder vh1 = (ValueHolder) it1.next(); + ValueHolder vh2 = (ValueHolder) it2.next(); + if (!vh1.contentEquals(vh2)) { + return false; + } + } + for (Iterator it = this.indexedArgumentValues.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + ValueHolder vh1 = (ValueHolder) entry.getValue(); + ValueHolder vh2 = (ValueHolder) that.indexedArgumentValues.get(entry.getKey()); + if (!vh1.contentEquals(vh2)) { + return false; + } + } + return true; + } + + public int hashCode() { + int hashCode = 7; + for (Iterator it = this.genericArgumentValues.iterator(); it.hasNext();) { + ValueHolder valueHolder = (ValueHolder) it.next(); + hashCode = 31 * hashCode + valueHolder.contentHashCode(); + } + hashCode = 29 * hashCode; + for (Iterator it = this.indexedArgumentValues.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + Integer key = (Integer) entry.getKey(); + ValueHolder value = (ValueHolder) entry.getValue(); + hashCode = 31 * hashCode + (value.contentHashCode() ^ key.hashCode()); + } + return hashCode; + } + + + /** + * Holder for a constructor argument value, with an optional type + * attribute indicating the target type of the actual constructor argument. + */ + public static class ValueHolder implements BeanMetadataElement { + + private Object value; + + private String type; + + private Object source; + + private boolean converted = false; + + private Object convertedValue; + + /** + * Create a new ValueHolder for the given value. + * @param value the argument value + */ + public ValueHolder(Object value) { + this.value = value; + } + + /** + * Create a new ValueHolder for the given value and type. + * @param value the argument value + * @param type the type of the constructor argument + */ + public ValueHolder(Object value, String type) { + this.value = value; + this.type = type; + } + + /** + * Set the value for the constructor argument. + * Only necessary for manipulating a registered value, + * for example in BeanFactoryPostProcessors. + * @see PropertyPlaceholderConfigurer + */ + public void setValue(Object value) { + this.value = value; + } + + /** + * Return the value for the constructor argument. + */ + public Object getValue() { + return this.value; + } + + /** + * Set the type of the constructor argument. + * Only necessary for manipulating a registered value, + * for example in BeanFactoryPostProcessors. + * @see PropertyPlaceholderConfigurer + */ + public void setType(String type) { + this.type = type; + } + + /** + * Return the type of the constructor argument. + */ + public String getType() { + return this.type; + } + + /** + * Set the configuration source Object for this metadata element. + *

The exact type of the object will depend on the configuration mechanism used. + */ + public void setSource(Object source) { + this.source = source; + } + + public Object getSource() { + return this.source; + } + + /** + * Return whether this holder contains a converted value already (true), + * or whether the value still needs to be converted (false). + */ + public synchronized boolean isConverted() { + return this.converted; + } + + /** + * Set the converted value of the constructor argument, + * after processed type conversion. + */ + public synchronized void setConvertedValue(Object value) { + this.converted = true; + this.convertedValue = value; + } + + /** + * Return the converted value of the constructor argument, + * after processed type conversion. + */ + public synchronized Object getConvertedValue() { + return this.convertedValue; + } + + /** + * Determine whether the content of this ValueHolder is equal + * to the content of the given other ValueHolder. + *

Note that ValueHolder does not implement equals + * directly, to allow for multiple ValueHolder instances with the + * same content to reside in the same Set. + */ + private boolean contentEquals(ValueHolder other) { + return (this == other || + (ObjectUtils.nullSafeEquals(this.value, other.value) && ObjectUtils.nullSafeEquals(this.type, other.type))); + } + + /** + * Determine whether the hash code of the content of this ValueHolder. + *

Note that ValueHolder does not implement hashCode + * directly, to allow for multiple ValueHolder instances with the + * same content to reside in the same Set. + */ + private int contentHashCode() { + return ObjectUtils.nullSafeHashCode(this.value) * 29 + ObjectUtils.nullSafeHashCode(this.type); + } + + /** + * Create a copy of this ValueHolder: that is, an independent + * ValueHolder instance with the same contents. + */ + public ValueHolder copy() { + ValueHolder copy = new ValueHolder(this.value, this.type); + copy.setSource(this.source); + return copy; + } + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/CustomEditorConfigurer.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/CustomEditorConfigurer.java new file mode 100644 index 0000000000..3d82150a72 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/CustomEditorConfigurer.java @@ -0,0 +1,208 @@ +/* + * Copyright 2002-2008 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.config; + +import java.beans.PropertyEditor; +import java.util.Iterator; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.BeansException; +import org.springframework.beans.FatalBeanException; +import org.springframework.beans.PropertyEditorRegistrar; +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.core.Ordered; +import org.springframework.util.ClassUtils; + +/** + * {@link BeanFactoryPostProcessor} implementation that allows for convenient + * registration of custom {@link PropertyEditor property editors}. + * + *

As of Spring 2.0, the recommended usage is to use custom + * {@link PropertyEditorRegistrar} implementations that in turn register + * any desired editors on a given + * {@link org.springframework.beans.PropertyEditorRegistry registry}. + * Each PropertyEditorRegistrar can register any number of custom editors. + * + *

+ * <bean id="customEditorConfigurer" class="org.springframework.beans.factory.config.CustomEditorConfigurer">
+ *   <property name="propertyEditorRegistrars">
+ *     <list>
+ *       <bean class="mypackage.MyCustomDateEditorRegistrar"/>
+ *       <bean class="mypackage.MyObjectEditorRegistrar"/>
+ *     </list>
+ *   </property>
+ * </bean>
+ * + *

Alternative configuration example with custom editor classes: + * + *

+ * <bean id="customEditorConfigurer" class="org.springframework.beans.factory.config.CustomEditorConfigurer">
+ *   <property name="customEditors">
+ *     <map>
+ *       <entry key="java.util.Date" value="mypackage.MyCustomDateEditor"/>
+ *       <entry key="mypackage.MyObject" value="mypackage.MyObjectEditor"/>
+ *     </map>
+ *   </property>
+ * </bean>
+ * + *

Also supports "java.lang.String[]"-style array class names and primitive + * class names (e.g. "boolean"). Delegates to {@link ClassUtils} for actual + * class name resolution. + * + *

NOTE: Custom property editors registered with this configurer do + * not apply to data binding. Custom editors for data binding need to + * be registered on the {@link org.springframework.validation.DataBinder}: + * Use a common base class or delegate to common PropertyEditorRegistrar + * implementations to reuse editor registration there. + * + * @author Juergen Hoeller + * @since 27.02.2004 + * @see java.beans.PropertyEditor + * @see org.springframework.beans.PropertyEditorRegistrar + * @see ConfigurableBeanFactory#addPropertyEditorRegistrar + * @see ConfigurableBeanFactory#registerCustomEditor + * @see org.springframework.validation.DataBinder#registerCustomEditor + * @see org.springframework.web.servlet.mvc.BaseCommandController#setPropertyEditorRegistrars + * @see org.springframework.web.servlet.mvc.BaseCommandController#initBinder + */ +public class CustomEditorConfigurer implements BeanFactoryPostProcessor, BeanClassLoaderAware, Ordered { + + protected final Log logger = LogFactory.getLog(getClass()); + + private int order = Ordered.LOWEST_PRECEDENCE; // default: same as non-Ordered + + private PropertyEditorRegistrar[] propertyEditorRegistrars; + + private Map customEditors; + + private boolean ignoreUnresolvableEditors = false; + + private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); + + + public void setOrder(int order) { + this.order = order; + } + + public int getOrder() { + return this.order; + } + + /** + * Specify the {@link PropertyEditorRegistrar PropertyEditorRegistrars} + * to apply to beans defined within the current application context. + *

This allows for sharing PropertyEditorRegistrars with + * {@link org.springframework.validation.DataBinder DataBinders}, etc. + * Furthermore, it avoids the need for synchronization on custom editors: + * A PropertyEditorRegistrar will always create fresh editor + * instances for each bean creation attempt. + * @see ConfigurableListableBeanFactory#addPropertyEditorRegistrar + */ + public void setPropertyEditorRegistrars(PropertyEditorRegistrar[] propertyEditorRegistrars) { + this.propertyEditorRegistrars = propertyEditorRegistrars; + } + + /** + * Specify the custom editors to register via a {@link Map}, using the + * class name of the required type as the key and the class name of the + * associated {@link PropertyEditor} as value. + *

Also supports {@link PropertyEditor} instances as values; however, + * this is deprecated since Spring 2.0.7 and will be removed in Spring 3.0. + * @param customEditors said Map of editors (can be null) + * @see ConfigurableListableBeanFactory#registerCustomEditor + */ + public void setCustomEditors(Map customEditors) { + this.customEditors = customEditors; + } + + /** + * Set whether unresolvable editors should simply be skipped. + * Default is to raise an exception in such a case. + *

This typically applies to either the editor class or the required type + * class not being found in the classpath. If you expect this to happen in + * some deployments and prefer to simply ignore the affected editors, + * then switch this flag to "true". + */ + public void setIgnoreUnresolvableEditors(boolean ignoreUnresolvableEditors) { + this.ignoreUnresolvableEditors = ignoreUnresolvableEditors; + } + + public void setBeanClassLoader(ClassLoader beanClassLoader) { + this.beanClassLoader = beanClassLoader; + } + + + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + if (this.propertyEditorRegistrars != null) { + for (int i = 0; i < this.propertyEditorRegistrars.length; i++) { + beanFactory.addPropertyEditorRegistrar(this.propertyEditorRegistrars[i]); + } + } + + if (this.customEditors != null) { + for (Iterator it = this.customEditors.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + Object key = entry.getKey(); + Object value = entry.getValue(); + Class requiredType = null; + + try { + if (key instanceof Class) { + requiredType = (Class) key; + } + else if (key instanceof String) { + requiredType = ClassUtils.forName((String) key, this.beanClassLoader); + } + else { + throw new IllegalArgumentException( + "Invalid key [" + key + "] for custom editor: needs to be Class or String."); + } + + if (value instanceof PropertyEditor) { + beanFactory.registerCustomEditor(requiredType, (PropertyEditor) value); + } + else if (value instanceof Class) { + beanFactory.registerCustomEditor(requiredType, (Class) value); + } + else if (value instanceof String) { + Class editorClass = ClassUtils.forName((String) value, this.beanClassLoader); + beanFactory.registerCustomEditor(requiredType, editorClass); + } + else { + throw new IllegalArgumentException("Mapped value [" + value + "] for custom editor key [" + + key + "] is not of required type [" + PropertyEditor.class.getName() + + "] or a corresponding Class or String value indicating a PropertyEditor implementation"); + } + } + catch (ClassNotFoundException ex) { + if (this.ignoreUnresolvableEditors) { + logger.info("Skipping editor [" + value + "] for required type [" + key + "]: " + + (requiredType != null ? "editor" : "required type") + " class not found."); + } + else { + throw new FatalBeanException( + (requiredType != null ? "Editor" : "Required type") + " class not found", ex); + } + } + } + } + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/CustomScopeConfigurer.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/CustomScopeConfigurer.java new file mode 100644 index 0000000000..07a14fbb6a --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/CustomScopeConfigurer.java @@ -0,0 +1,111 @@ +/* + * Copyright 2002-2008 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.config; + +import java.util.Iterator; +import java.util.Map; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.core.Ordered; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * Simple {@link BeanFactoryPostProcessor} implementation that registers + * custom {@link Scope Scope(s)} with the containing {@link ConfigurableBeanFactory}. + * + *

Will register all of the supplied {@link #setScopes(java.util.Map) scopes} + * with the {@link ConfigurableListableBeanFactory} that is passed to the + * {@link #postProcessBeanFactory(ConfigurableListableBeanFactory)} method. + * + *

This class allows for declarative registration of custom scopes. + * Alternatively, consider implementing a custom {@link BeanFactoryPostProcessor} + * that calls {@link ConfigurableBeanFactory#registerScope} programmatically. + * + * @author Juergen Hoeller + * @author Rick Evans + * @since 2.0 + * @see ConfigurableBeanFactory#registerScope + */ +public class CustomScopeConfigurer implements BeanFactoryPostProcessor, BeanClassLoaderAware, Ordered { + + private Map scopes; + + private int order = Ordered.LOWEST_PRECEDENCE; + + private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); + + + /** + * Specify the custom scopes that are to be registered. + *

The keys indicate the scope names (of type String); each value + * is expected to be the corresponding custom {@link Scope} instance + * or class name. + */ + public void setScopes(Map scopes) { + this.scopes = scopes; + } + + public void setOrder(int order) { + this.order = order; + } + + public int getOrder() { + return this.order; + } + + public void setBeanClassLoader(ClassLoader beanClassLoader) { + this.beanClassLoader = beanClassLoader; + } + + + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + if (this.scopes != null) { + for (Iterator it = this.scopes.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + Object key = entry.getKey(); + if (!(key instanceof String)) { + throw new IllegalArgumentException( + "Invalid scope key [" + key + "]: only Strings allowed"); + } + String scopeName = (String) key; + Object value = entry.getValue(); + if (value instanceof Scope) { + beanFactory.registerScope(scopeName, (Scope) value); + } + else if (value instanceof Class) { + Class scopeClass = (Class) value; + Assert.isAssignable(Scope.class, scopeClass); + beanFactory.registerScope(scopeName, (Scope) BeanUtils.instantiateClass(scopeClass)); + } + else if (value instanceof String) { + Class scopeClass = ClassUtils.resolveClassName((String) value, this.beanClassLoader); + Assert.isAssignable(Scope.class, scopeClass); + beanFactory.registerScope(scopeName, (Scope) BeanUtils.instantiateClass(scopeClass)); + } + else { + throw new IllegalArgumentException("Mapped value [" + value + "] for scope key [" + + key + "] is not an instance of required type [" + Scope.class.getName() + + "] or a corresponding Class or String value indicating a Scope implementation"); + } + } + } + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java new file mode 100644 index 0000000000..612d1b08e2 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java @@ -0,0 +1,207 @@ +/* + * Copyright 2002-2008 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.config; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import org.springframework.core.GenericCollectionTypeResolver; +import org.springframework.core.JdkVersion; +import org.springframework.core.MethodParameter; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; + +/** + * Descriptor for a specific dependency that is about to be injected. + * Wraps a constructor parameter, a method parameter or a field, + * allowing unified access to their metadata. + * + * @author Juergen Hoeller + * @since 2.5 + */ +public class DependencyDescriptor { + + private static final Method fieldAnnotationsMethod = + ClassUtils.getMethodIfAvailable(Field.class, "getAnnotations", new Class[0]); + + + private MethodParameter methodParameter; + + private Field field; + + private final boolean required; + + private final boolean eager; + + private Object[] fieldAnnotations; + + + /** + * Create a new descriptor for a method or constructor parameter. + * Considers the dependency as 'eager'. + * @param methodParameter the MethodParameter to wrap + * @param required whether the dependency is required + */ + public DependencyDescriptor(MethodParameter methodParameter, boolean required) { + this(methodParameter, required, true); + } + + /** + * Create a new descriptor for a method or constructor parameter. + * @param methodParameter the MethodParameter to wrap + * @param required whether the dependency is required + * @param eager whether this dependency is 'eager' in the sense of + * eagerly resolving potential target beans for type matching + */ + public DependencyDescriptor(MethodParameter methodParameter, boolean required, boolean eager) { + Assert.notNull(methodParameter, "MethodParameter must not be null"); + this.methodParameter = methodParameter; + this.required = required; + this.eager = eager; + } + + /** + * Create a new descriptor for a field. + * Considers the dependency as 'eager'. + * @param field the field to wrap + * @param required whether the dependency is required + */ + public DependencyDescriptor(Field field, boolean required) { + this(field, required, true); + } + + /** + * Create a new descriptor for a field. + * @param field the field to wrap + * @param required whether the dependency is required + * @param eager whether this dependency is 'eager' in the sense of + * eagerly resolving potential target beans for type matching + */ + public DependencyDescriptor(Field field, boolean required, boolean eager) { + Assert.notNull(field, "Field must not be null"); + this.field = field; + this.required = required; + this.eager = eager; + } + + + /** + * Return the wrapped MethodParameter, if any. + *

Note: Either MethodParameter or Field is available. + * @return the MethodParameter, or null if none + */ + public MethodParameter getMethodParameter() { + return this.methodParameter; + } + + /** + * Return the wrapped Field, if any. + *

Note: Either MethodParameter or Field is available. + * @return the Field, or null if none + */ + public Field getField() { + return this.field; + } + + /** + * Return whether this dependency is required. + */ + public boolean isRequired() { + return this.required; + } + + /** + * Return whether this dependency is 'eager' in the sense of + * eagerly resolving potential target beans for type matching. + */ + public boolean isEager() { + return this.eager; + } + + + /** + * Determine the declared (non-generic) type of the wrapped parameter/field. + * @return the declared type (never null) + */ + public Class getDependencyType() { + return (this.field != null ? this.field.getType() : this.methodParameter.getParameterType()); + } + + /** + * Determine the generic element type of the wrapped Collection parameter/field, if any. + * @return the generic type, or null if none + */ + public Class getCollectionType() { + if (JdkVersion.getMajorJavaVersion() < JdkVersion.JAVA_15) { + return null; + } + return (this.field != null ? + GenericCollectionTypeResolver.getCollectionFieldType(this.field) : + GenericCollectionTypeResolver.getCollectionParameterType(this.methodParameter)); + } + + /** + * Determine the generic key type of the wrapped Map parameter/field, if any. + * @return the generic type, or null if none + */ + public Class getMapKeyType() { + if (JdkVersion.getMajorJavaVersion() < JdkVersion.JAVA_15) { + return null; + } + return (this.field != null ? + GenericCollectionTypeResolver.getMapKeyFieldType(this.field) : + GenericCollectionTypeResolver.getMapKeyParameterType(this.methodParameter)); + } + + /** + * Determine the generic value type of the wrapped Map parameter/field, if any. + * @return the generic type, or null if none + */ + public Class getMapValueType() { + if (JdkVersion.getMajorJavaVersion() < JdkVersion.JAVA_15) { + return null; + } + return (this.field != null ? + GenericCollectionTypeResolver.getMapValueFieldType(this.field) : + GenericCollectionTypeResolver.getMapValueParameterType(this.methodParameter)); + } + + /** + * Obtain the annotations associated with the wrapped parameter/field, if any. + * @return the parameter/field annotations, or null if there is + * no annotation support (on JDK < 1.5). The return value is an Object array + * instead of an Annotation array simply for compatibility with older JDKs; + * feel free to cast it to Annotation[] on JDK 1.5 or higher. + */ + public Object[] getAnnotations() { + if (this.field != null) { + if (this.fieldAnnotations != null) { + return this.fieldAnnotations; + } + if (fieldAnnotationsMethod == null) { + return null; + } + this.fieldAnnotations = (Object[]) ReflectionUtils.invokeMethod(fieldAnnotationsMethod, this.field); + return this.fieldAnnotations; + } + else { + return this.methodParameter.getParameterAnnotations(); + } + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/DestructionAwareBeanPostProcessor.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/DestructionAwareBeanPostProcessor.java new file mode 100644 index 0000000000..a59eedf817 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/DestructionAwareBeanPostProcessor.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2005 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.config; + +import org.springframework.beans.BeansException; + +/** + * Subinterface of BeanPostProcessor that adds a before-destruction callback. + * + *

The typical usage will be to invoke custom destruction callbacks on + * specific bean types, matching corresponding initialization callbacks. + * + * @author Juergen Hoeller + * @since 1.0.1 + * @see org.springframework.web.struts.ActionServletAwareProcessor + */ +public interface DestructionAwareBeanPostProcessor extends BeanPostProcessor { + + /** + * Apply this BeanPostProcessor to the given bean instance before + * its destruction. Can invoke custom destruction callbacks. + *

Like DisposableBean's destroy and a custom destroy method, + * this callback just applies to singleton beans in the factory (including + * inner beans). + * @param bean the bean instance to be destroyed + * @param beanName the name of the bean + * @throws org.springframework.beans.BeansException in case of errors + * @see org.springframework.beans.factory.DisposableBean + * @see org.springframework.beans.factory.support.AbstractBeanDefinition#setDestroyMethodName + */ + void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException; + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/FieldRetrievingFactoryBean.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/FieldRetrievingFactoryBean.java new file mode 100644 index 0000000000..db83b47266 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/FieldRetrievingFactoryBean.java @@ -0,0 +1,216 @@ +/* + * Copyright 2002-2007 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.config; + +import java.lang.reflect.Field; + +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.FactoryBeanNotInitializedException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; + +/** + * FactoryBean which retrieves a static or non-static field value. + * + *

Typically used for retrieving public static final constants. Usage example: + * + *

// standard definition for exposing a static field, specifying the "staticField" property
+ * <bean id="myField" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
+ *   <property name="staticField" value="java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
+ * </bean>
+ *
+ * // convenience version that specifies a static field pattern as bean name
+ * <bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
+ *       class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"/>
+ * + * + *

If you are using Spring 2.0, you can also use the following style of configuration for + * public static fields. + * + *

<util:constant static-field="java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
+ * + * @author Juergen Hoeller + * @since 1.1 + * @see #setStaticField + */ +public class FieldRetrievingFactoryBean implements FactoryBean, BeanNameAware, BeanClassLoaderAware, InitializingBean { + + private Class targetClass; + + private Object targetObject; + + private String targetField; + + private String staticField; + + private String beanName; + + private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); + + // the field we will retrieve + private Field fieldObject; + + + /** + * Set the target class on which the field is defined. + * Only necessary when the target field is static; else, + * a target object needs to be specified anyway. + * @see #setTargetObject + * @see #setTargetField + */ + public void setTargetClass(Class targetClass) { + this.targetClass = targetClass; + } + + /** + * Return the target class on which the field is defined. + */ + public Class getTargetClass() { + return targetClass; + } + + /** + * Set the target object on which the field is defined. + * Only necessary when the target field is not static; + * else, a target class is sufficient. + * @see #setTargetClass + * @see #setTargetField + */ + public void setTargetObject(Object targetObject) { + this.targetObject = targetObject; + } + + /** + * Return the target object on which the field is defined. + */ + public Object getTargetObject() { + return this.targetObject; + } + + /** + * Set the name of the field to be retrieved. + * Refers to either a static field or a non-static field, + * depending on a target object being set. + * @see #setTargetClass + * @see #setTargetObject + */ + public void setTargetField(String targetField) { + this.targetField = StringUtils.trimAllWhitespace(targetField); + } + + /** + * Return the name of the field to be retrieved. + */ + public String getTargetField() { + return this.targetField; + } + + /** + * Set a fully qualified static field name to retrieve, + * e.g. "example.MyExampleClass.MY_EXAMPLE_FIELD". + * Convenient alternative to specifying targetClass and targetField. + * @see #setTargetClass + * @see #setTargetField + */ + public void setStaticField(String staticField) { + this.staticField = StringUtils.trimAllWhitespace(staticField); + } + + /** + * The bean name of this FieldRetrievingFactoryBean will be interpreted + * as "staticField" pattern, if neither "targetClass" nor "targetObject" + * nor "targetField" have been specified. + * This allows for concise bean definitions with just an id/name. + */ + public void setBeanName(String beanName) { + this.beanName = StringUtils.trimAllWhitespace(BeanFactoryUtils.originalBeanName(beanName)); + } + + public void setBeanClassLoader(ClassLoader classLoader) { + this.beanClassLoader = classLoader; + } + + + public void afterPropertiesSet() throws ClassNotFoundException, NoSuchFieldException { + if (this.targetClass != null && this.targetObject != null) { + throw new IllegalArgumentException("Specify either targetClass or targetObject, not both"); + } + + if (this.targetClass == null && this.targetObject == null) { + if (this.targetField != null) { + throw new IllegalArgumentException( + "Specify targetClass or targetObject in combination with targetField"); + } + + // If no other property specified, consider bean name as static field expression. + if (this.staticField == null) { + this.staticField = this.beanName; + } + + // Try to parse static field into class and field. + int lastDotIndex = this.staticField.lastIndexOf('.'); + if (lastDotIndex == -1 || lastDotIndex == this.staticField.length()) { + throw new IllegalArgumentException( + "staticField must be a fully qualified class plus method name: " + + "e.g. 'example.MyExampleClass.MY_EXAMPLE_FIELD'"); + } + String className = this.staticField.substring(0, lastDotIndex); + String fieldName = this.staticField.substring(lastDotIndex + 1); + this.targetClass = ClassUtils.forName(className, this.beanClassLoader); + this.targetField = fieldName; + } + + else if (this.targetField == null) { + // Either targetClass or targetObject specified. + throw new IllegalArgumentException("targetField is required"); + } + + // Try to get the exact method first. + Class targetClass = (this.targetObject != null) ? this.targetObject.getClass() : this.targetClass; + this.fieldObject = targetClass.getField(this.targetField); + } + + + public Object getObject() throws IllegalAccessException { + if (this.fieldObject == null) { + throw new FactoryBeanNotInitializedException(); + } + ReflectionUtils.makeAccessible(this.fieldObject); + if (this.targetObject != null) { + // instance field + return this.fieldObject.get(this.targetObject); + } + else{ + // class field + return this.fieldObject.get(null); + } + } + + public Class getObjectType() { + return (this.fieldObject != null ? this.fieldObject.getType() : null); + } + + public boolean isSingleton() { + return false; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.java new file mode 100644 index 0000000000..1d0438a284 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.java @@ -0,0 +1,109 @@ +/* + * Copyright 2002-2008 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.config; + +import java.beans.PropertyDescriptor; + +import org.springframework.beans.BeansException; +import org.springframework.beans.PropertyValues; + +/** + * Subinterface of {@link BeanPostProcessor} that adds a before-instantiation callback, + * and a callback after instantiation but before explicit properties are set or + * autowiring occurs. + * + *

Typically used to suppress default instantiation for specific target beans, + * for example to create proxies with special TargetSources (pooling targets, + * lazily initializing targets, etc), or to implement additional injection strategies + * such as field injection. + * + *

NOTE: This interface is a special purpose interface, mainly for + * internal use within the framework. It is recommended to implement the plain + * {@link BeanPostProcessor} interface as far as possible, or to derive from + * {@link InstantiationAwareBeanPostProcessorAdapter} in order to be shielded + * from extensions to this interface. + * + * @author Juergen Hoeller + * @author Rod Johnson + * @since 1.2 + * @see org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#setCustomTargetSourceCreators + * @see org.springframework.aop.framework.autoproxy.target.LazyInitTargetSourceCreator + */ +public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor { + + /** + * Apply this BeanPostProcessor before the target bean gets instantiated. + * The returned bean object may be a proxy to use instead of the target bean, + * effectively suppressing default instantiation of the target bean. + *

If a non-null object is returned by this method, the bean creation process + * will be short-circuited. The only further processing applied is the + * {@link #postProcessAfterInitialization} callback from the configured + * {@link BeanPostProcessor BeanPostProcessors}. + *

This callback will only be applied to bean definitions with a bean class. + * In particular, it will not be applied to beans with a "factory-method". + *

Post-processors may implement the extended + * {@link SmartInstantiationAwareBeanPostProcessor} interface in order + * to predict the type of the bean object that they are going to return here. + * @param beanClass the class of the bean to be instantiated + * @param beanName the name of the bean + * @return the bean object to expose instead of a default instance of the target bean, + * or null to proceed with default instantiation + * @throws org.springframework.beans.BeansException in case of errors + * @see org.springframework.beans.factory.support.AbstractBeanDefinition#hasBeanClass + * @see org.springframework.beans.factory.support.AbstractBeanDefinition#getFactoryMethodName + */ + Object postProcessBeforeInstantiation(Class beanClass, String beanName) throws BeansException; + + /** + * Perform operations after the bean has been instantiated, via a constructor or factory method, + * but before Spring property population (from explicit properties or autowiring) occurs. + *

This is the ideal callback for performing field injection on the given bean instance. + * See Spring's own {@link org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor} + * for a typical example. + * @param bean the bean instance created, with properties not having been set yet + * @param beanName the name of the bean + * @return true if properties should be set on the bean; false + * if property population should be skipped. Normal implementations should return true. + * Returning false will also prevent any subsequent InstantiationAwareBeanPostProcessor + * instances being invoked on this bean instance. + * @throws org.springframework.beans.BeansException in case of errors + */ + boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException; + + /** + * Post-process the given property values before the factory applies them + * to the given bean. Allows for checking whether all dependencies have been + * satisfied, for example based on a "Required" annotation on bean property setters. + *

Also allows for replacing the property values to apply, typically through + * creating a new MutablePropertyValues instance based on the original PropertyValues, + * adding or removing specific values. + * @param pvs the property values that the factory is about to apply (never null) + * @param pds the relevant property descriptors for the target bean (with ignored + * dependency types - which the factory handles specifically - already filtered out) + * @param bean the bean instance created, but whose properties have not yet been set + * @param beanName the name of the bean + * @return the actual property values to apply to to the given bean + * (can be the passed-in PropertyValues instance), or null + * to skip property population + * @throws org.springframework.beans.BeansException in case of errors + * @see org.springframework.beans.MutablePropertyValues + */ + PropertyValues postProcessPropertyValues( + PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) + throws BeansException; + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessorAdapter.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessorAdapter.java new file mode 100644 index 0000000000..eea1b94ce7 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessorAdapter.java @@ -0,0 +1,77 @@ +/* + * Copyright 2002-2008 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.config; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Constructor; + +import org.springframework.beans.BeansException; +import org.springframework.beans.PropertyValues; + +/** + * Adapter that implements all methods on {@link SmartInstantiationAwareBeanPostProcessor} + * as no-ops, which will not change normal processing of each bean instantiated + * by the container. Subclasses may override merely those methods that they are + * actually interested in. + * + *

Note that this base class is only recommendable if you actually require + * {@link InstantiationAwareBeanPostProcessor} functionality. If all you need + * is plain {@link BeanPostProcessor} functionality, prefer a straight + * implementation of that (simpler) interface. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + */ +public abstract class InstantiationAwareBeanPostProcessorAdapter implements SmartInstantiationAwareBeanPostProcessor { + + public Class predictBeanType(Class beanClass, String beanName) { + return null; + } + + public Constructor[] determineCandidateConstructors(Class beanClass, String beanName) throws BeansException { + return null; + } + + public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException { + return bean; + } + + public Object postProcessBeforeInstantiation(Class beanClass, String beanName) throws BeansException { + return null; + } + + public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException { + return true; + } + + public PropertyValues postProcessPropertyValues( + PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) + throws BeansException { + + return pvs; + } + + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/ListFactoryBean.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/ListFactoryBean.java new file mode 100644 index 0000000000..1fc998c98d --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/ListFactoryBean.java @@ -0,0 +1,99 @@ +/* + * Copyright 2002-2007 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.config; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.TypeConverter; +import org.springframework.core.GenericCollectionTypeResolver; +import org.springframework.core.JdkVersion; + +/** + * Simple factory for shared List instances. Allows for central setup + * of Lists via the "list" element in XML bean definitions. + * + * @author Juergen Hoeller + * @since 09.12.2003 + * @see SetFactoryBean + * @see MapFactoryBean + */ +public class ListFactoryBean extends AbstractFactoryBean { + + private List sourceList; + + private Class targetListClass; + + + /** + * Set the source List, typically populated via XML "list" elements. + */ + public void setSourceList(List sourceList) { + this.sourceList = sourceList; + } + + /** + * Set the class to use for the target List. Can be populated with a fully + * qualified class name when defined in a Spring application context. + *

Default is a java.util.ArrayList. + * @see java.util.ArrayList + */ + public void setTargetListClass(Class targetListClass) { + if (targetListClass == null) { + throw new IllegalArgumentException("'targetListClass' must not be null"); + } + if (!List.class.isAssignableFrom(targetListClass)) { + throw new IllegalArgumentException("'targetListClass' must implement [java.util.List]"); + } + this.targetListClass = targetListClass; + } + + + public Class getObjectType() { + return List.class; + } + + protected Object createInstance() { + if (this.sourceList == null) { + throw new IllegalArgumentException("'sourceList' is required"); + } + List result = null; + if (this.targetListClass != null) { + result = (List) BeanUtils.instantiateClass(this.targetListClass); + } + else { + result = new ArrayList(this.sourceList.size()); + } + Class valueType = null; + if (this.targetListClass != null && JdkVersion.isAtLeastJava15()) { + valueType = GenericCollectionTypeResolver.getCollectionType(this.targetListClass); + } + if (valueType != null) { + TypeConverter converter = getBeanTypeConverter(); + for (Iterator it = this.sourceList.iterator(); it.hasNext();) { + result.add(converter.convertIfNecessary(it.next(), valueType)); + } + } + else { + result.addAll(this.sourceList); + } + return result; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/MapFactoryBean.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/MapFactoryBean.java new file mode 100644 index 0000000000..4390c4b76f --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/MapFactoryBean.java @@ -0,0 +1,104 @@ +/* + * Copyright 2002-2007 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.config; + +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.TypeConverter; +import org.springframework.core.GenericCollectionTypeResolver; +import org.springframework.core.JdkVersion; + +/** + * Simple factory for shared Map instances. Allows for central setup + * of Maps via the "map" element in XML bean definitions. + * + * @author Juergen Hoeller + * @since 09.12.2003 + * @see SetFactoryBean + * @see ListFactoryBean + */ +public class MapFactoryBean extends AbstractFactoryBean { + + private Map sourceMap; + + private Class targetMapClass; + + + /** + * Set the source Map, typically populated via XML "map" elements. + */ + public void setSourceMap(Map sourceMap) { + this.sourceMap = sourceMap; + } + + /** + * Set the class to use for the target Map. Can be populated with a fully + * qualified class name when defined in a Spring application context. + *

Default is a linked HashMap, keeping the registration order. + * @see java.util.LinkedHashMap + */ + public void setTargetMapClass(Class targetMapClass) { + if (targetMapClass == null) { + throw new IllegalArgumentException("'targetMapClass' must not be null"); + } + if (!Map.class.isAssignableFrom(targetMapClass)) { + throw new IllegalArgumentException("'targetMapClass' must implement [java.util.Map]"); + } + this.targetMapClass = targetMapClass; + } + + + public Class getObjectType() { + return Map.class; + } + + protected Object createInstance() { + if (this.sourceMap == null) { + throw new IllegalArgumentException("'sourceMap' is required"); + } + Map result = null; + if (this.targetMapClass != null) { + result = (Map) BeanUtils.instantiateClass(this.targetMapClass); + } + else { + result = new LinkedHashMap(this.sourceMap.size()); + } + Class keyType = null; + Class valueType = null; + if (this.targetMapClass != null && JdkVersion.isAtLeastJava15()) { + keyType = GenericCollectionTypeResolver.getMapKeyType(this.targetMapClass); + valueType = GenericCollectionTypeResolver.getMapValueType(this.targetMapClass); + } + if (keyType != null || valueType != null) { + TypeConverter converter = getBeanTypeConverter(); + for (Iterator it = this.sourceMap.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + Object convertedKey = converter.convertIfNecessary(entry.getKey(), keyType); + Object convertedValue = converter.convertIfNecessary(entry.getValue(), valueType); + result.put(convertedKey, convertedValue); + } + } + else { + result.putAll(this.sourceMap); + } + return result; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/MethodInvokingFactoryBean.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/MethodInvokingFactoryBean.java new file mode 100644 index 0000000000..efa24eacf8 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/MethodInvokingFactoryBean.java @@ -0,0 +1,205 @@ +/* + * Copyright 2002-2007 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.config; + +import java.lang.reflect.InvocationTargetException; + +import org.springframework.beans.TypeConverter; +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.FactoryBeanNotInitializedException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.support.ArgumentConvertingMethodInvoker; +import org.springframework.util.ClassUtils; + +/** + * FactoryBean which returns a value which is the result of a static or instance + * method invocation. For most use cases it is better to just use the container's + * built-in factory method support for the same purpose, since that is smarter at + * converting arguments. This factory bean is still useful though when you need to + * call a method which doesn't return any value (for example, a static class method + * to force some sort of initialization to happen). This use case is not supported + * by factory methods, since a return value is needed to obtain the bean instance. + * + *

Note that as it is expected to be used mostly for accessing factory methods, + * this factory by default operates in a singleton fashion. The first request + * to {@link #getObject} by the owning bean factory will cause a method invocation, + * whose return value will be cached for subsequent requests. An internal + * {@link #setSingleton singleton} property may be set to "false", to cause this + * factory to invoke the target method each time it is asked for an object. + * + *

A static target method may be specified by setting the + * {@link #setTargetMethod targetMethod} property to a String representing the static + * method name, with {@link #setTargetClass targetClass} specifying the Class that + * the static method is defined on. Alternatively, a target instance method may be + * specified, by setting the {@link #setTargetObject targetObject} property as the target + * object, and the {@link #setTargetMethod targetMethod} property as the name of the + * method to call on that target object. Arguments for the method invocation may be + * specified by setting the {@link #setArguments arguments} property. + * + *

This class depends on {@link #afterPropertiesSet()} being called once + * all properties have been set, as per the InitializingBean contract. + * + *

An example (in an XML based bean factory definition) of a bean definition + * which uses this class to call a static factory method: + * + *

+ * <bean id="myObject" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
+ *   <property name="staticMethod"><value>com.whatever.MyClassFactory.getInstance</value></property>
+ * </bean>
+ * + *

An example of calling a static method then an instance method to get at a + * Java system property. Somewhat verbose, but it works. + * + *

+ * <bean id="sysProps" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
+ *   <property name="targetClass"><value>java.lang.System</value></property>
+ *   <property name="targetMethod"><value>getProperties</value></property>
+ * </bean>
+ *
+ * <bean id="javaVersion" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
+ *   <property name="targetObject"><ref local="sysProps"/></property>
+ *   <property name="targetMethod"><value>getProperty</value></property>
+ *   <property name="arguments">
+ *     <list>
+ *       <value>java.version</value>
+ *     </list>
+ *   </property>
+ * </bean>
+ * + * @author Colin Sampaleanu + * @author Juergen Hoeller + * @since 21.11.2003 + */ +public class MethodInvokingFactoryBean extends ArgumentConvertingMethodInvoker + implements FactoryBean, BeanClassLoaderAware, BeanFactoryAware, InitializingBean { + + private boolean singleton = true; + + private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); + + private ConfigurableBeanFactory beanFactory; + + private boolean initialized = false; + + /** Method call result in the singleton case */ + private Object singletonObject; + + + /** + * Set if a singleton should be created, or a new object on each + * request else. Default is "true". + */ + public void setSingleton(boolean singleton) { + this.singleton = singleton; + } + + public boolean isSingleton() { + return this.singleton; + } + + public void setBeanClassLoader(ClassLoader classLoader) { + this.beanClassLoader = classLoader; + } + + protected Class resolveClassName(String className) throws ClassNotFoundException { + return ClassUtils.forName(className, this.beanClassLoader); + } + + public void setBeanFactory(BeanFactory beanFactory) { + if (beanFactory instanceof ConfigurableBeanFactory) { + this.beanFactory = (ConfigurableBeanFactory) beanFactory; + } + } + + /** + * Obtain the TypeConverter from the BeanFactory that this bean runs in, + * if possible. + * @see ConfigurableBeanFactory#getTypeConverter() + */ + protected TypeConverter getDefaultTypeConverter() { + if (this.beanFactory != null) { + return this.beanFactory.getTypeConverter(); + } + else { + return super.getDefaultTypeConverter(); + } + } + + + public void afterPropertiesSet() throws Exception { + prepare(); + if (this.singleton) { + this.initialized = true; + this.singletonObject = doInvoke(); + } + } + + /** + * Perform the invocation and convert InvocationTargetException + * into the underlying target exception. + */ + private Object doInvoke() throws Exception { + try { + return invoke(); + } + catch (InvocationTargetException ex) { + if (ex.getTargetException() instanceof Exception) { + throw (Exception) ex.getTargetException(); + } + if (ex.getTargetException() instanceof Error) { + throw (Error) ex.getTargetException(); + } + throw ex; + } + } + + + /** + * Returns the same value each time if the singleton property is set + * to "true", otherwise returns the value returned from invoking the + * specified method on the fly. + */ + public Object getObject() throws Exception { + if (this.singleton) { + if (!this.initialized) { + throw new FactoryBeanNotInitializedException(); + } + // Singleton: return shared object. + return this.singletonObject; + } + else { + // Prototype: new object on each call. + return doInvoke(); + } + } + + /** + * Return the type of object that this FactoryBean creates, + * or null if not known in advance. + */ + public Class getObjectType() { + if (!isPrepared()) { + // Not fully initialized yet -> return null to indicate "not known yet". + return null; + } + return getPreparedMethod().getReturnType(); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/ObjectFactoryCreatingFactoryBean.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/ObjectFactoryCreatingFactoryBean.java new file mode 100644 index 0000000000..30626606af --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/ObjectFactoryCreatingFactoryBean.java @@ -0,0 +1,141 @@ +/* + * Copyright 2002-2007 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.config; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.util.Assert; + +/** + * A {@link org.springframework.beans.factory.FactoryBean} implementation that + * returns a value which is an {@link org.springframework.beans.factory.ObjectFactory} + * that in turn returns a bean sourced from a {@link org.springframework.beans.factory.BeanFactory}. + * + *

As such, this may be used to avoid having a client object directly calling + * {@link org.springframework.beans.factory.BeanFactory#getBean(String)} to get + * a (typically prototype) bean from a + * {@link org.springframework.beans.factory.BeanFactory}, which would be a + * violation of the inversion of control principle. Instead, with the use + * of this class, the client object can be fed an + * {@link org.springframework.beans.factory.ObjectFactory} instance as a + * property which directly returns only the one target bean (again, which is + * typically a prototype bean). + * + *

A sample config in an XML-based + * {@link org.springframework.beans.factory.BeanFactory} might look as follows: + * + *

<beans>
+ *
+ *   <!-- Prototype bean since we have state -->
+ *   <bean id="myService" class="a.b.c.MyService" singleton="false"/>
+ *
+ *   <bean id="myServiceFactory"
+ *       class="org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean">
+ *     <property name="targetBeanName"><idref local="myService"/></property>
+ *   </bean>
+ *
+ *   <bean id="clientBean" class="a.b.c.MyClientBean">
+ *     <property name="myServiceFactory" ref="myServiceFactory"/>
+ *   </bean>
+ *
+ *</beans>
+ * + *

The attendant MyClientBean class implementation might look + * something like this: + * + *

package a.b.c;
+ *
+ * import org.springframework.beans.factory.ObjectFactory;
+ *
+ * public class MyClientBean {
+ *
+ *   private ObjectFactory myServiceFactory;
+ *
+ *   public void setMyServiceFactory(ObjectFactory myServiceFactory) {
+ *     this.myServiceFactory = myServiceFactory;
+ *   }
+ *
+ *   public void someBusinessMethod() {
+ *     // get a 'fresh', brand new MyService instance
+ *     MyService service = this.myServiceFactory.getObject();
+ *     // use the service object to effect the business logic...
+ *   }
+ * }
+ * + *

An alternate approach to this application of an object creational pattern + * would be to use the {@link ServiceLocatorFactoryBean} + * to source (prototype) beans. The {@link ServiceLocatorFactoryBean} approach + * has the advantage of the fact that one doesn't have to depend on any + * Spring-specific interface such as {@link org.springframework.beans.factory.ObjectFactory}, + * but has the disadvantage of requiring runtime class generation. Please do + * consult the {@link ServiceLocatorFactoryBean ServiceLocatorFactoryBean JavaDoc} + * for a fuller discussion of this issue. + * + * @author Colin Sampaleanu + * @author Juergen Hoeller + * @since 1.0.2 + * @see org.springframework.beans.factory.ObjectFactory + * @see ServiceLocatorFactoryBean + */ +public class ObjectFactoryCreatingFactoryBean extends AbstractFactoryBean { + + private String targetBeanName; + + + /** + * Set the name of the target bean. + *

The target does not have to be a prototype bean, but realisticially + * always will be (because if the target bean were a singleton, then said + * singleton bean could simply be injected straight into the dependent object, + * thus obviating the need for the extra level of indirection afforded by + * the approach encapsulated by this class). Please note that no exception + * will be thrown if the supplied targetBeanName does not + * reference a prototype bean. + */ + public void setTargetBeanName(String targetBeanName) { + this.targetBeanName = targetBeanName; + } + + public void afterPropertiesSet() throws Exception { + Assert.hasText(this.targetBeanName, "Property 'targetBeanName' is required"); + super.afterPropertiesSet(); + } + + + public Class getObjectType() { + return ObjectFactory.class; + } + + protected Object createInstance() { + return new ObjectFactory() { + public Object getObject() throws BeansException { + return getTargetBean(targetBeanName); + } + }; + } + + /** + * Template method for obtaining a target bean instance. + * Called by the exposed ObjectFactory's getObject() method. + * @param targetBeanName the name of the target bean + * @return the target bean instance + */ + protected Object getTargetBean(String targetBeanName) { + return getBeanFactory().getBean(targetBeanName); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/PreferencesPlaceholderConfigurer.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/PreferencesPlaceholderConfigurer.java new file mode 100644 index 0000000000..b910b07fe0 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/PreferencesPlaceholderConfigurer.java @@ -0,0 +1,134 @@ +/* + * Copyright 2002-2007 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.config; + +import java.util.Properties; +import java.util.prefs.BackingStoreException; +import java.util.prefs.Preferences; + +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.InitializingBean; + +/** + * Subclass of PropertyPlaceholderConfigurer that supports JDK 1.4's + * Preferences API (java.util.prefs). + * + *

Tries to resolve placeholders as keys first in the user preferences, + * then in the system preferences, then in this configurer's properties. + * Thus, behaves like PropertyPlaceholderConfigurer if no corresponding + * preferences defined. + * + *

Supports custom paths for the system and user preferences trees. Also + * supports custom paths specified in placeholders ("myPath/myPlaceholderKey"). + * Uses the respective root node if not specified. + * + * @author Juergen Hoeller + * @since 16.02.2004 + * @see #setSystemTreePath + * @see #setUserTreePath + * @see java.util.prefs.Preferences + */ +public class PreferencesPlaceholderConfigurer extends PropertyPlaceholderConfigurer implements InitializingBean { + + private String systemTreePath; + + private String userTreePath; + + private Preferences systemPrefs; + + private Preferences userPrefs; + + + /** + * Set the path in the system preferences tree to use for resolving + * placeholders. Default is the root node. + */ + public void setSystemTreePath(String systemTreePath) { + this.systemTreePath = systemTreePath; + } + + /** + * Set the path in the system preferences tree to use for resolving + * placeholders. Default is the root node. + */ + public void setUserTreePath(String userTreePath) { + this.userTreePath = userTreePath; + } + + + /** + * This implementation eagerly fetches the Preferences instances + * for the required system and user tree nodes. + */ + public void afterPropertiesSet() { + this.systemPrefs = (this.systemTreePath != null) ? + Preferences.systemRoot().node(this.systemTreePath) : Preferences.systemRoot(); + this.userPrefs = (this.userTreePath != null) ? + Preferences.userRoot().node(this.userTreePath) : Preferences.userRoot(); + } + + /** + * This implementation tries to resolve placeholders as keys first + * in the user preferences, then in the system preferences, then in + * the passed-in properties. + */ + protected String resolvePlaceholder(String placeholder, Properties props) { + String path = null; + String key = placeholder; + int endOfPath = placeholder.lastIndexOf('/'); + if (endOfPath != -1) { + path = placeholder.substring(0, endOfPath); + key = placeholder.substring(endOfPath + 1); + } + String value = resolvePlaceholder(path, key, this.userPrefs); + if (value == null) { + value = resolvePlaceholder(path, key, this.systemPrefs); + if (value == null) { + value = props.getProperty(placeholder); + } + } + return value; + } + + /** + * Resolve the given path and key against the given Preferences. + * @param path the preferences path (placeholder part before '/') + * @param key the preferences key (placeholder part after '/') + * @param preferences the Preferences to resolve against + * @return the value for the placeholder, or null if none found + */ + protected String resolvePlaceholder(String path, String key, Preferences preferences) { + if (path != null) { + // Do not create the node if it does not exist... + try { + if (preferences.nodeExists(path)) { + return preferences.node(path).get(key, null); + } + else { + return null; + } + } + catch (BackingStoreException ex) { + throw new BeanDefinitionStoreException("Cannot access specified node path [" + path + "]", ex); + } + } + else { + return preferences.get(key, null); + } + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/PropertiesFactoryBean.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/PropertiesFactoryBean.java new file mode 100644 index 0000000000..07403ce47f --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/PropertiesFactoryBean.java @@ -0,0 +1,101 @@ +/* + * Copyright 2002-2007 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.config; + +import java.io.IOException; +import java.util.Properties; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.io.support.PropertiesLoaderSupport; + +/** + * Allows for making a properties file from a classpath location available + * as Properties instance in a bean factory. Can be used to populate + * any bean property of type Properties via a bean reference. + * + *

Supports loading from a properties file and/or setting local properties + * on this FactoryBean. The created Properties instance will be merged from + * loaded and local values. If neither a location nor local properties are set, + * an exception will be thrown on initialization. + * + *

Can create a singleton or a new object on each request. + * Default is a singleton. + * + * @author Juergen Hoeller + * @see #setLocation + * @see #setProperties + * @see #setLocalOverride + * @see java.util.Properties + */ +public class PropertiesFactoryBean extends PropertiesLoaderSupport + implements FactoryBean, InitializingBean { + + private boolean singleton = true; + + private Object singletonInstance; + + + /** + * Set whether a shared 'singleton' Properties instance should be + * created, or rather a new Properties instance on each request. + *

Default is "true" (a shared singleton). + */ + public final void setSingleton(boolean singleton) { + this.singleton = singleton; + } + + public final boolean isSingleton() { + return this.singleton; + } + + + public final void afterPropertiesSet() throws IOException { + if (this.singleton) { + this.singletonInstance = createInstance(); + } + } + + public final Object getObject() throws IOException { + if (this.singleton) { + return this.singletonInstance; + } + else { + return createInstance(); + } + } + + public Class getObjectType() { + return Properties.class; + } + + + /** + * Template method that subclasses may override to construct the object + * returned by this factory. The default implementation returns the + * plain merged Properties instance. + *

Invoked on initialization of this FactoryBean in case of a + * shared singleton; else, on each {@link #getObject()} call. + * @return the object returned by this factory + * @throws IOException if an exception occured during properties loading + * @see #mergeProperties() + */ + protected Object createInstance() throws IOException { + return mergeProperties(); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/PropertyOverrideConfigurer.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/PropertyOverrideConfigurer.java new file mode 100644 index 0000000000..e735c601e6 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/PropertyOverrideConfigurer.java @@ -0,0 +1,161 @@ +/* + * Copyright 2002-2008 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.config; + +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Properties; +import java.util.Set; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanInitializationException; + +/** + * Property resource configurer that overrides bean property values in an application + * context definition. It pushes values from a properties file into bean definitions. + * + *

Configuration lines are expected to be of the following form: + * + *

beanName.property=value
+ * + * Example properties file: + * + *
dataSource.driverClassName=com.mysql.jdbc.Driver
+ * dataSource.url=jdbc:mysql:mydb
+ * + * In contrast to PropertyPlaceholderConfigurer, the original definition can have default + * values or no values at all for such bean properties. If an overriding properties file does + * not have an entry for a certain bean property, the default context definition is used. + * + *

Note that the context definition is not aware of being overridden; + * so this is not immediately obvious when looking at the XML definition file. + * Furthermore, note that specified override values are always literal values; + * they are not translated into bean references. This also applies when the original + * value in the XML bean definition specifies a bean reference. + * + *

In case of multiple PropertyOverrideConfigurers that define different values for + * the same bean property, the last one will win (due to the overriding mechanism). + * + *

Property values can be converted after reading them in, through overriding + * the convertPropertyValue method. For example, encrypted values + * can be detected and decrypted accordingly before processing them. + * + * @author Juergen Hoeller + * @author Rod Johnson + * @since 12.03.2003 + * @see #convertPropertyValue + * @see PropertyPlaceholderConfigurer + */ +public class PropertyOverrideConfigurer extends PropertyResourceConfigurer { + + public static final String DEFAULT_BEAN_NAME_SEPARATOR = "."; + + + private String beanNameSeparator = DEFAULT_BEAN_NAME_SEPARATOR; + + private boolean ignoreInvalidKeys = false; + + /** Contains names of beans that have overrides */ + private Set beanNames = Collections.synchronizedSet(new HashSet()); + + + /** + * Set the separator to expect between bean name and property path. + * Default is a dot ("."). + */ + public void setBeanNameSeparator(String beanNameSeparator) { + this.beanNameSeparator = beanNameSeparator; + } + + /** + * Set whether to ignore invalid keys. Default is "false". + *

If you ignore invalid keys, keys that do not follow the + * 'beanName.property' format will just be logged as warning. + * This allows to have arbitrary other keys in a properties file. + */ + public void setIgnoreInvalidKeys(boolean ignoreInvalidKeys) { + this.ignoreInvalidKeys = ignoreInvalidKeys; + } + + + protected void processProperties(ConfigurableListableBeanFactory beanFactory, Properties props) + throws BeansException { + + for (Enumeration names = props.propertyNames(); names.hasMoreElements();) { + String key = (String) names.nextElement(); + try { + processKey(beanFactory, key, props.getProperty(key)); + } + catch (BeansException ex) { + String msg = "Could not process key '" + key + "' in PropertyOverrideConfigurer"; + if (!this.ignoreInvalidKeys) { + throw new BeanInitializationException(msg, ex); + } + if (logger.isDebugEnabled()) { + logger.debug(msg, ex); + } + } + } + } + + /** + * Process the given key as 'beanName.property' entry. + */ + protected void processKey(ConfigurableListableBeanFactory factory, String key, String value) + throws BeansException { + + int separatorIndex = key.indexOf(this.beanNameSeparator); + if (separatorIndex == -1) { + throw new BeanInitializationException("Invalid key '" + key + + "': expected 'beanName" + this.beanNameSeparator + "property'"); + } + String beanName = key.substring(0, separatorIndex); + String beanProperty = key.substring(separatorIndex+1); + this.beanNames.add(beanName); + applyPropertyValue(factory, beanName, beanProperty, value); + if (logger.isDebugEnabled()) { + logger.debug("Property '" + key + "' set to value [" + value + "]"); + } + } + + /** + * Apply the given property value to the corresponding bean. + */ + protected void applyPropertyValue( + ConfigurableListableBeanFactory factory, String beanName, String property, String value) { + + BeanDefinition bd = factory.getBeanDefinition(beanName); + while (bd.getOriginatingBeanDefinition() != null) { + bd = bd.getOriginatingBeanDefinition(); + } + bd.getPropertyValues().addPropertyValue(property, value); + } + + + /** + * Were there overrides for this bean? + * Only valid after processing has occurred at least once. + * @param beanName name of the bean to query status for + * @return whether there were property overrides for + * the named bean + */ + public boolean hasPropertyOverridesFor(String beanName) { + return this.beanNames.contains(beanName); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/PropertyPathFactoryBean.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/PropertyPathFactoryBean.java new file mode 100644 index 0000000000..ab55bc47e3 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/PropertyPathFactoryBean.java @@ -0,0 +1,224 @@ +/* + * Copyright 2002-2008 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.config; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.BeansException; +import org.springframework.beans.PropertyAccessorFactory; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.util.StringUtils; + +/** + * {@link FactoryBean} that evaluates a property path on a given target object. + * + *

The target object can be specified directly or via a bean name. + * + *

Usage examples: + * + *

<!-- target bean to be referenced by name -->
+ * <bean id="tb" class="org.springframework.beans.TestBean" singleton="false">
+ *   <property name="age" value="10"/>
+ *   <property name="spouse">
+ *     <bean class="org.springframework.beans.TestBean">
+ *       <property name="age" value="11"/>
+ *     </bean>
+ *   </property>
+ * </bean>
+ *
+ * <!-- will result in 12, which is the value of property 'age' of the inner bean -->
+ * <bean id="propertyPath1" class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
+ *   <property name="targetObject">
+ *     <bean class="org.springframework.beans.TestBean">
+ *       <property name="age" value="12"/>
+ *     </bean>
+ *   </property>
+ *   <property name="propertyPath" value="age"/>
+ * </bean>
+ *
+ * <!-- will result in 11, which is the value of property 'spouse.age' of bean 'tb' -->
+ * <bean id="propertyPath2" class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
+ *   <property name="targetBeanName" value="tb"/>
+ *   <property name="propertyPath" value="spouse.age"/>
+ * </bean>
+ *
+ * <!-- will result in 10, which is the value of property 'age' of bean 'tb' -->
+ * <bean id="tb.age" class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/>
+ * + *

If you are using Spring 2.0 and XML Schema support in your configuration file(s), + * you can also use the following style of configuration for property path access. + * (See also the appendix entitled 'XML Schema-based configuration' in the Spring + * reference manual for more examples.) + * + *

 <!-- will result in 10, which is the value of property 'age' of bean 'tb' -->
+ * <util:property-path id="name" path="testBean.age"/>
+ * + * Thanks to Matthias Ernst for the suggestion and initial prototype! + * + * @author Juergen Hoeller + * @since 1.1.2 + * @see #setTargetObject + * @see #setTargetBeanName + * @see #setPropertyPath + */ +public class PropertyPathFactoryBean implements FactoryBean, BeanNameAware, BeanFactoryAware { + + private static final Log logger = LogFactory.getLog(PropertyPathFactoryBean.class); + + private BeanWrapper targetBeanWrapper; + + private String targetBeanName; + + private String propertyPath; + + private Class resultType; + + private String beanName; + + private BeanFactory beanFactory; + + + /** + * Specify a target object to apply the property path to. + * Alternatively, specify a target bean name. + * @param targetObject a target object, for example a bean reference + * or an inner bean + * @see #setTargetBeanName + */ + public void setTargetObject(Object targetObject) { + this.targetBeanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(targetObject); + } + + /** + * Specify the name of a target bean to apply the property path to. + * Alternatively, specify a target object directly. + * @param targetBeanName the bean name to be looked up in the + * containing bean factory (e.g. "testBean") + * @see #setTargetObject + */ + public void setTargetBeanName(String targetBeanName) { + this.targetBeanName = StringUtils.trimAllWhitespace(targetBeanName); + } + + /** + * Specify the property path to apply to the target. + * @param propertyPath the property path, potentially nested + * (e.g. "age" or "spouse.age") + */ + public void setPropertyPath(String propertyPath) { + this.propertyPath = StringUtils.trimAllWhitespace(propertyPath); + } + + /** + * Specify the type of the result from evaluating the property path. + *

Note: This is not necessary for directly specified target objects + * or singleton target beans, where the type can be determined through + * introspection. Just specify this in case of a prototype target, + * provided that you need matching by type (for example, for autowiring). + * @param resultType the result type, for example "java.lang.Integer" + */ + public void setResultType(Class resultType) { + this.resultType = resultType; + } + + /** + * The bean name of this PropertyPathFactoryBean will be interpreted + * as "beanName.property" pattern, if neither "targetObject" nor + * "targetBeanName" nor "propertyPath" have been specified. + * This allows for concise bean definitions with just an id/name. + */ + public void setBeanName(String beanName) { + this.beanName = StringUtils.trimAllWhitespace(BeanFactoryUtils.originalBeanName(beanName)); + } + + + public void setBeanFactory(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + + if (this.targetBeanWrapper != null && this.targetBeanName != null) { + throw new IllegalArgumentException("Specify either 'targetObject' or 'targetBeanName', not both"); + } + + if (this.targetBeanWrapper == null && this.targetBeanName == null) { + if (this.propertyPath != null) { + throw new IllegalArgumentException( + "Specify 'targetObject' or 'targetBeanName' in combination with 'propertyPath'"); + } + + // No other properties specified: check bean name. + int dotIndex = this.beanName.indexOf('.'); + if (dotIndex == -1) { + throw new IllegalArgumentException( + "Neither 'targetObject' nor 'targetBeanName' specified, and PropertyPathFactoryBean " + + "bean name '" + this.beanName + "' does not follow 'beanName.property' syntax"); + } + this.targetBeanName = this.beanName.substring(0, dotIndex); + this.propertyPath = this.beanName.substring(dotIndex + 1); + } + + else if (this.propertyPath == null) { + // either targetObject or targetBeanName specified + throw new IllegalArgumentException("'propertyPath' is required"); + } + + if (this.targetBeanWrapper == null && this.beanFactory.isSingleton(this.targetBeanName)) { + // Eagerly fetch singleton target bean, and determine result type. + Object bean = this.beanFactory.getBean(this.targetBeanName); + this.targetBeanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(bean); + this.resultType = this.targetBeanWrapper.getPropertyType(this.propertyPath); + } + } + + + public Object getObject() throws BeansException { + BeanWrapper target = this.targetBeanWrapper; + if (target != null) { + if (logger.isWarnEnabled() && this.beanFactory instanceof ConfigurableBeanFactory && + ((ConfigurableBeanFactory) this.beanFactory).isCurrentlyInCreation(this.targetBeanName)) { + logger.warn("Target bean '" + this.targetBeanName + "' is still in creation due to a circular " + + "reference - obtained value for property '" + this.propertyPath + "' may be outdated!"); + } + } + else { + // Fetch prototype target bean... + Object bean = this.beanFactory.getBean(this.targetBeanName); + target = PropertyAccessorFactory.forBeanPropertyAccess(bean); + } + return target.getPropertyValue(this.propertyPath); + } + + public Class getObjectType() { + return this.resultType; + } + + /** + * While this FactoryBean will often be used for singleton targets, + * the invoked getters for the property path might return a new object + * for each call, so we have to assume that we're not returning the + * same object for each {@link #getObject()} call. + */ + public boolean isSingleton() { + return false; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurer.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurer.java new file mode 100644 index 0000000000..db04b76ab6 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurer.java @@ -0,0 +1,450 @@ +/* + * Copyright 2002-2008 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.config; + +import java.util.HashSet; +import java.util.Properties; +import java.util.Set; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.core.Constants; +import org.springframework.util.StringUtils; +import org.springframework.util.StringValueResolver; + +/** + * A property resource configurer that resolves placeholders in bean property values of + * context definitions. It pulls values from a properties file into bean definitions. + * + *

The default placeholder syntax follows the Ant / Log4J / JSP EL style: + * + *

${...}
+ * + * Example XML context definition: + * + *
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
+ *   <property name="driverClassName"><value>${driver}</value></property>
+ *   <property name="url"><value>jdbc:${dbname}</value></property>
+ * </bean>
+ * + * Example properties file: + * + *
driver=com.mysql.jdbc.Driver
+ * dbname=mysql:mydb
+ * + * PropertyPlaceholderConfigurer checks simple property values, lists, maps, + * props, and bean names in bean references. Furthermore, placeholder values can + * also cross-reference other placeholders, like: + * + *
rootPath=myrootdir
+ * subPath=${rootPath}/subdir
+ * + * In contrast to PropertyOverrideConfigurer, this configurer allows to fill in + * explicit placeholders in context definitions. Therefore, the original definition + * cannot specify any default values for such bean properties, and the placeholder + * properties file is supposed to contain an entry for each defined placeholder. + * + *

If a configurer cannot resolve a placeholder, a BeanDefinitionStoreException + * will be thrown. If you want to check against multiple properties files, specify + * multiple resources via the "locations" setting. You can also define multiple + * PropertyPlaceholderConfigurers, each with its own placeholder syntax. + * + *

Default property values can be defined via "properties", to make overriding + * definitions in properties files optional. A configurer will also check against + * system properties (e.g. "user.dir") if it cannot resolve a placeholder with any + * of the specified properties. This can be customized via "systemPropertiesMode". + * + *

Note that the context definition is aware of being incomplete; + * this is immediately obvious to users when looking at the XML definition file. + * Hence, placeholders have to be resolved; any desired defaults have to be + * defined as placeholder values as well (for example in a default properties file). + * + *

Property values can be converted after reading them in, through overriding + * the {@link #convertPropertyValue} method. For example, encrypted values can + * be detected and decrypted accordingly before processing them. + * + * @author Juergen Hoeller + * @since 02.10.2003 + * @see #setLocations + * @see #setProperties + * @see #setPlaceholderPrefix + * @see #setPlaceholderSuffix + * @see #setSystemPropertiesModeName + * @see System#getProperty(String) + * @see #convertPropertyValue + * @see PropertyOverrideConfigurer + */ +public class PropertyPlaceholderConfigurer extends PropertyResourceConfigurer + implements BeanNameAware, BeanFactoryAware { + + /** Default placeholder prefix: "${" */ + public static final String DEFAULT_PLACEHOLDER_PREFIX = "${"; + + /** Default placeholder suffix: "}" */ + public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}"; + + + /** Never check system properties. */ + public static final int SYSTEM_PROPERTIES_MODE_NEVER = 0; + + /** + * Check system properties if not resolvable in the specified properties. + * This is the default. + */ + public static final int SYSTEM_PROPERTIES_MODE_FALLBACK = 1; + + /** + * Check system properties first, before trying the specified properties. + * This allows system properties to override any other property source. + */ + public static final int SYSTEM_PROPERTIES_MODE_OVERRIDE = 2; + + + private static final Constants constants = new Constants(PropertyPlaceholderConfigurer.class); + + private String placeholderPrefix = DEFAULT_PLACEHOLDER_PREFIX; + + private String placeholderSuffix = DEFAULT_PLACEHOLDER_SUFFIX; + + private int systemPropertiesMode = SYSTEM_PROPERTIES_MODE_FALLBACK; + + private boolean searchSystemEnvironment = true; + + private boolean ignoreUnresolvablePlaceholders = false; + + private String nullValue; + + private String beanName; + + private BeanFactory beanFactory; + + + /** + * Set the prefix that a placeholder string starts with. + * The default is "${". + * @see #DEFAULT_PLACEHOLDER_PREFIX + */ + public void setPlaceholderPrefix(String placeholderPrefix) { + this.placeholderPrefix = placeholderPrefix; + } + + /** + * Set the suffix that a placeholder string ends with. + * The default is "}". + * @see #DEFAULT_PLACEHOLDER_SUFFIX + */ + public void setPlaceholderSuffix(String placeholderSuffix) { + this.placeholderSuffix = placeholderSuffix; + } + + /** + * Set the system property mode by the name of the corresponding constant, + * e.g. "SYSTEM_PROPERTIES_MODE_OVERRIDE". + * @param constantName name of the constant + * @throws java.lang.IllegalArgumentException if an invalid constant was specified + * @see #setSystemPropertiesMode + */ + public void setSystemPropertiesModeName(String constantName) throws IllegalArgumentException { + this.systemPropertiesMode = constants.asNumber(constantName).intValue(); + } + + /** + * Set how to check system properties: as fallback, as override, or never. + * For example, will resolve ${user.dir} to the "user.dir" system property. + *

The default is "fallback": If not being able to resolve a placeholder + * with the specified properties, a system property will be tried. + * "override" will check for a system property first, before trying the + * specified properties. "never" will not check system properties at all. + * @see #SYSTEM_PROPERTIES_MODE_NEVER + * @see #SYSTEM_PROPERTIES_MODE_FALLBACK + * @see #SYSTEM_PROPERTIES_MODE_OVERRIDE + * @see #setSystemPropertiesModeName + */ + public void setSystemPropertiesMode(int systemPropertiesMode) { + this.systemPropertiesMode = systemPropertiesMode; + } + + /** + * Set whether to search for a matching system environment variable + * if no matching system property has been found. Only applied when + * "systemPropertyMode" is active (i.e. "fallback" or "override"), right + * after checking JVM system properties. + *

Default is "true". Switch this setting off to never resolve placeholders + * against system environment variables. Note that it is generally recommended + * to pass external values in as JVM system properties: This can easily be + * achieved in a startup script, even for existing environment variables. + *

NOTE: Access to environment variables does not work on the + * Sun VM 1.4, where the corresponding {@link System#getenv} support was + * disabled - before it eventually got re-enabled for the Sun VM 1.5. + * Please upgrade to 1.5 (or higher) if you intend to rely on the + * environment variable support. + * @see #setSystemPropertiesMode + * @see java.lang.System#getProperty(String) + * @see java.lang.System#getenv(String) + */ + public void setSearchSystemEnvironment(boolean searchSystemEnvironment) { + this.searchSystemEnvironment = searchSystemEnvironment; + } + + /** + * Set whether to ignore unresolvable placeholders. Default is "false": + * An exception will be thrown if a placeholder cannot be resolved. + */ + public void setIgnoreUnresolvablePlaceholders(boolean ignoreUnresolvablePlaceholders) { + this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders; + } + + /** + * Set a value that should be treated as null when + * resolved as a placeholder value: e.g. "" (empty String) or "null". + *

Note that this will only apply to full property values, + * not to parts of concatenated values. + *

By default, no such null value is defined. This means that + * there is no way to express null as a property + * value unless you explictly map a corresponding value here. + */ + public void setNullValue(String nullValue) { + this.nullValue = nullValue; + } + + /** + * Only necessary to check that we're not parsing our own bean definition, + * to avoid failing on unresolvable placeholders in properties file locations. + * The latter case can happen with placeholders for system properties in + * resource locations. + * @see #setLocations + * @see org.springframework.core.io.ResourceEditor + */ + public void setBeanName(String beanName) { + this.beanName = beanName; + } + + /** + * Only necessary to check that we're not parsing our own bean definition, + * to avoid failing on unresolvable placeholders in properties file locations. + * The latter case can happen with placeholders for system properties in + * resource locations. + * @see #setLocations + * @see org.springframework.core.io.ResourceEditor + */ + public void setBeanFactory(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + + protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props) + throws BeansException { + + StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(props); + BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver); + + String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames(); + for (int i = 0; i < beanNames.length; i++) { + // Check that we're not parsing our own bean definition, + // to avoid failing on unresolvable placeholders in properties file locations. + if (!(beanNames[i].equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) { + BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(beanNames[i]); + try { + visitor.visitBeanDefinition(bd); + } + catch (BeanDefinitionStoreException ex) { + throw new BeanDefinitionStoreException(bd.getResourceDescription(), beanNames[i], ex.getMessage()); + } + } + } + + // New in Spring 2.5: resolve placeholders in alias target names and aliases as well. + beanFactoryToProcess.resolveAliases(valueResolver); + } + + /** + * Parse the given String value recursively, to be able to resolve + * nested placeholders (when resolved property values in turn contain + * placeholders again). + * @param strVal the String value to parse + * @param props the Properties to resolve placeholders against + * @param visitedPlaceholders the placeholders that have already been visited + * during the current resolution attempt (used to detect circular references + * between placeholders). Only non-null if we're parsing a nested placeholder. + * @throws BeanDefinitionStoreException if invalid values are encountered + * @see #resolvePlaceholder(String, java.util.Properties, int) + */ + protected String parseStringValue(String strVal, Properties props, Set visitedPlaceholders) + throws BeanDefinitionStoreException { + + StringBuffer buf = new StringBuffer(strVal); + + int startIndex = strVal.indexOf(this.placeholderPrefix); + while (startIndex != -1) { + int endIndex = findPlaceholderEndIndex(buf, startIndex); + if (endIndex != -1) { + String placeholder = buf.substring(startIndex + this.placeholderPrefix.length(), endIndex); + if (!visitedPlaceholders.add(placeholder)) { + throw new BeanDefinitionStoreException( + "Circular placeholder reference '" + placeholder + "' in property definitions"); + } + // Recursive invocation, parsing placeholders contained in the placeholder key. + placeholder = parseStringValue(placeholder, props, visitedPlaceholders); + // Now obtain the value for the fully resolved key... + String propVal = resolvePlaceholder(placeholder, props, this.systemPropertiesMode); + if (propVal != null) { + // Recursive invocation, parsing placeholders contained in the + // previously resolved placeholder value. + propVal = parseStringValue(propVal, props, visitedPlaceholders); + buf.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal); + if (logger.isTraceEnabled()) { + logger.trace("Resolved placeholder '" + placeholder + "'"); + } + startIndex = buf.indexOf(this.placeholderPrefix, startIndex + propVal.length()); + } + else if (this.ignoreUnresolvablePlaceholders) { + // Proceed with unprocessed value. + startIndex = buf.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length()); + } + else { + throw new BeanDefinitionStoreException("Could not resolve placeholder '" + placeholder + "'"); + } + visitedPlaceholders.remove(placeholder); + } + else { + startIndex = -1; + } + } + + return buf.toString(); + } + + private int findPlaceholderEndIndex(CharSequence buf, int startIndex) { + int index = startIndex + this.placeholderPrefix.length(); + int withinNestedPlaceholder = 0; + while (index < buf.length()) { + if (StringUtils.substringMatch(buf, index, this.placeholderSuffix)) { + if (withinNestedPlaceholder > 0) { + withinNestedPlaceholder--; + index = index + this.placeholderSuffix.length(); + } + else { + return index; + } + } + else if (StringUtils.substringMatch(buf, index, this.placeholderPrefix)) { + withinNestedPlaceholder++; + index = index + this.placeholderPrefix.length(); + } + else { + index++; + } + } + return -1; + } + + /** + * Resolve the given placeholder using the given properties, performing + * a system properties check according to the given mode. + *

Default implementation delegates to resolvePlaceholder + * (placeholder, props) before/after the system properties check. + *

Subclasses can override this for custom resolution strategies, + * including customized points for the system properties check. + * @param placeholder the placeholder to resolve + * @param props the merged properties of this configurer + * @param systemPropertiesMode the system properties mode, + * according to the constants in this class + * @return the resolved value, of null if none + * @see #setSystemPropertiesMode + * @see System#getProperty + * @see #resolvePlaceholder(String, java.util.Properties) + */ + protected String resolvePlaceholder(String placeholder, Properties props, int systemPropertiesMode) { + String propVal = null; + if (systemPropertiesMode == SYSTEM_PROPERTIES_MODE_OVERRIDE) { + propVal = resolveSystemProperty(placeholder); + } + if (propVal == null) { + propVal = resolvePlaceholder(placeholder, props); + } + if (propVal == null && systemPropertiesMode == SYSTEM_PROPERTIES_MODE_FALLBACK) { + propVal = resolveSystemProperty(placeholder); + } + return propVal; + } + + /** + * Resolve the given placeholder using the given properties. + * The default implementation simply checks for a corresponding property key. + *

Subclasses can override this for customized placeholder-to-key mappings + * or custom resolution strategies, possibly just using the given properties + * as fallback. + *

Note that system properties will still be checked before respectively + * after this method is invoked, according to the system properties mode. + * @param placeholder the placeholder to resolve + * @param props the merged properties of this configurer + * @return the resolved value, of null if none + * @see #setSystemPropertiesMode + */ + protected String resolvePlaceholder(String placeholder, Properties props) { + return props.getProperty(placeholder); + } + + /** + * Resolve the given key as JVM system property, and optionally also as + * system environment variable if no matching system property has been found. + * @param key the placeholder to resolve as system property key + * @return the system property value, or null if not found + * @see #setSearchSystemEnvironment + * @see java.lang.System#getProperty(String) + * @see java.lang.System#getenv(String) + */ + protected String resolveSystemProperty(String key) { + try { + String value = System.getProperty(key); + if (value == null && this.searchSystemEnvironment) { + value = System.getenv(key); + } + return value; + } + catch (Throwable ex) { + if (logger.isDebugEnabled()) { + logger.debug("Could not access system property '" + key + "': " + ex); + } + return null; + } + } + + + /** + * BeanDefinitionVisitor that resolves placeholders in String values, + * delegating to the parseStringValue method of the + * containing class. + */ + private class PlaceholderResolvingStringValueResolver implements StringValueResolver { + + private final Properties props; + + public PlaceholderResolvingStringValueResolver(Properties props) { + this.props = props; + } + + public String resolveStringValue(String strVal) throws BeansException { + String value = parseStringValue(strVal, this.props, new HashSet()); + return (value.equals(nullValue) ? null : value); + } + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/PropertyResourceConfigurer.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/PropertyResourceConfigurer.java new file mode 100644 index 0000000000..90194227cf --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/PropertyResourceConfigurer.java @@ -0,0 +1,128 @@ +/* + * Copyright 2002-2007 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.config; + +import java.io.IOException; +import java.util.Enumeration; +import java.util.Properties; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.core.Ordered; +import org.springframework.core.PriorityOrdered; +import org.springframework.core.io.support.PropertiesLoaderSupport; +import org.springframework.util.ObjectUtils; + +/** + * Allows for configuration of individual bean property values from a property resource, + * i.e. a properties file. Useful for custom config files targetted at system + * administrators that override bean properties configured in the application context. + * + *

Two concrete implementations are provided in the distribution: + *

    + *
  • {@link PropertyOverrideConfigurer} for "beanName.property=value" style overriding + * (pushing values from a properties file into bean definitions) + *
  • {@link PropertyPlaceholderConfigurer} for replacing "${...}" placeholders + * (pulling values from a properties file into bean definitions) + *
+ * + *

Property values can be converted after reading them in, through overriding + * the {@link #convertPropertyValue} method. For example, encrypted values + * can be detected and decrypted accordingly before processing them. + * + * @author Juergen Hoeller + * @since 02.10.2003 + * @see PropertyOverrideConfigurer + * @see PropertyPlaceholderConfigurer + */ +public abstract class PropertyResourceConfigurer extends PropertiesLoaderSupport + implements BeanFactoryPostProcessor, PriorityOrdered { + + private int order = Ordered.LOWEST_PRECEDENCE; // default: same as non-Ordered + + + public void setOrder(int order) { + this.order = order; + } + + public int getOrder() { + return this.order; + } + + + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + try { + Properties mergedProps = mergeProperties(); + + // Convert the merged properties, if necessary. + convertProperties(mergedProps); + + // Let the subclass process the properties. + processProperties(beanFactory, mergedProps); + } + catch (IOException ex) { + throw new BeanInitializationException("Could not load properties", ex); + } + } + + /** + * Convert the given merged properties, converting property values + * if necessary. The result will then be processed. + *

The default implementation will invoke {@link #convertPropertyValue} + * for each property value, replacing the original with the converted value. + * @param props the Properties to convert + * @see #processProperties + */ + protected void convertProperties(Properties props) { + Enumeration propertyNames = props.propertyNames(); + while (propertyNames.hasMoreElements()) { + String propertyName = (String) propertyNames.nextElement(); + String propertyValue = props.getProperty(propertyName); + String convertedValue = convertPropertyValue(propertyValue); + if (!ObjectUtils.nullSafeEquals(propertyValue, convertedValue)) { + props.setProperty(propertyName, convertedValue); + } + } + } + + /** + * Convert the given property value from the properties source + * to the value that should be applied. + *

The default implementation simply returns the original value. + * Can be overridden in subclasses, for example to detect + * encrypted values and decrypt them accordingly. + * @param originalValue the original value from the properties source + * (properties file or local "properties") + * @return the converted value, to be used for processing + * @see #setProperties + * @see #setLocations + * @see #setLocation + */ + protected String convertPropertyValue(String originalValue) { + return originalValue; + } + + /** + * Apply the given Properties to the given BeanFactory. + * @param beanFactory the BeanFactory used by the application context + * @param props the Properties to apply + * @throws org.springframework.beans.BeansException in case of errors + */ + protected abstract void processProperties(ConfigurableListableBeanFactory beanFactory, Properties props) + throws BeansException; + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/RuntimeBeanNameReference.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/RuntimeBeanNameReference.java new file mode 100644 index 0000000000..ebaea25aec --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/RuntimeBeanNameReference.java @@ -0,0 +1,83 @@ +/* + * Copyright 2002-2007 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.config; + +import org.springframework.util.Assert; + +/** + * Immutable placeholder class used for a property value object when it's a + * reference to another bean name in the factory, to be resolved at runtime. + * + * @author Juergen Hoeller + * @since 2.0 + * @see RuntimeBeanReference + * @see BeanDefinition#getPropertyValues() + * @see org.springframework.beans.factory.BeanFactory#getBean + */ +public class RuntimeBeanNameReference implements BeanReference { + + private final String beanName; + + private Object source; + + + /** + * Create a new RuntimeBeanNameReference to the given bean name. + * @param beanName name of the target bean + */ + public RuntimeBeanNameReference(String beanName) { + Assert.hasText(beanName, "'beanName' must not be empty"); + this.beanName = beanName; + } + + public String getBeanName() { + return this.beanName; + } + + /** + * Set the configuration source Object for this metadata element. + *

The exact type of the object will depend on the configuration mechanism used. + */ + public void setSource(Object source) { + this.source = source; + } + + public Object getSource() { + return this.source; + } + + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof RuntimeBeanNameReference)) { + return false; + } + RuntimeBeanNameReference that = (RuntimeBeanNameReference) other; + return this.beanName.equals(that.beanName); + } + + public int hashCode() { + return this.beanName.hashCode(); + } + + public String toString() { + return '<' + getBeanName() + '>'; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/RuntimeBeanReference.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/RuntimeBeanReference.java new file mode 100644 index 0000000000..3418666efc --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/RuntimeBeanReference.java @@ -0,0 +1,110 @@ +/* + * Copyright 2002-2007 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.config; + +import org.springframework.util.Assert; + +/** + * Immutable placeholder class used for a property value object when it's + * a reference to another bean in the factory, to be resolved at runtime. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see BeanDefinition#getPropertyValues() + * @see org.springframework.beans.factory.BeanFactory#getBean + */ +public class RuntimeBeanReference implements BeanReference { + + private final String beanName; + + private final boolean toParent; + + private Object source; + + + /** + * Create a new RuntimeBeanReference to the given bean name, + * without explicitly marking it as reference to a bean in + * the parent factory. + * @param beanName name of the target bean + */ + public RuntimeBeanReference(String beanName) { + this(beanName, false); + } + + /** + * Create a new RuntimeBeanReference to the given bean name, + * with the option to mark it as reference to a bean in + * the parent factory. + * @param beanName name of the target bean + * @param toParent whether this is an explicit reference to + * a bean in the parent factory + */ + public RuntimeBeanReference(String beanName, boolean toParent) { + Assert.hasText(beanName, "'beanName' must not be empty"); + this.beanName = beanName; + this.toParent = toParent; + } + + + public String getBeanName() { + return this.beanName; + } + + /** + * Return whether this is an explicit reference to a bean + * in the parent factory. + */ + public boolean isToParent() { + return this.toParent; + } + + /** + * Set the configuration source Object for this metadata element. + *

The exact type of the object will depend on the configuration mechanism used. + */ + public void setSource(Object source) { + this.source = source; + } + + public Object getSource() { + return this.source; + } + + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof RuntimeBeanReference)) { + return false; + } + RuntimeBeanReference that = (RuntimeBeanReference) other; + return (this.beanName.equals(that.beanName) && this.toParent == that.toParent); + } + + public int hashCode() { + int result = this.beanName.hashCode(); + result = 29 * result + (this.toParent ? 1 : 0); + return result; + } + + public String toString() { + return '<' + getBeanName() + '>'; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/Scope.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/Scope.java new file mode 100644 index 0000000000..2f52f9d549 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/Scope.java @@ -0,0 +1,137 @@ +/* + * Copyright 2002-2007 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.config; + +import org.springframework.beans.factory.ObjectFactory; + +/** + * Strategy interface used by a {@link ConfigurableBeanFactory}, + * representing a target scope to hold bean instances in. + * This allows for extending the BeanFactory's standard scopes + * {@link ConfigurableBeanFactory#SCOPE_SINGLETON "singleton"} and + * {@link ConfigurableBeanFactory#SCOPE_PROTOTYPE "prototype"} + * with custom further scopes, registered for a + * {@link ConfigurableBeanFactory#registerScope(String, Scope) specific key}. + * + *

{@link org.springframework.context.ApplicationContext} implementations + * such as a {@link org.springframework.web.context.WebApplicationContext} + * may register additional standard scopes specific to their environment, + * e.g. {@link org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST "request"} + * and {@link org.springframework.web.context.WebApplicationContext#SCOPE_SESSION "session"}, + * based on this Scope SPI. + * + *

Even if its primary use is for extended scopes in a web environment, + * this SPI is completely generic: It provides the ability to get and put + * objects from any underlying storage mechanism, such as an HTTP session + * or a custom conversation mechanism. The name passed into this class's + * get and remove methods will identify the + * target object in the current scope. + * + *

Scope implementations are expected to be thread-safe. + * One Scope instance can be used with multiple bean factories + * at the same time, if desired (unless it explicitly wants to be aware of + * the containing BeanFactory), with any number of threads accessing + * the Scope concurrently from any number of factories. + * + * @author Juergen Hoeller + * @author Rob Harrop + * @since 2.0 + * @see ConfigurableBeanFactory#registerScope + * @see CustomScopeConfigurer + * @see org.springframework.aop.scope.ScopedProxyFactoryBean + * @see org.springframework.web.context.request.RequestScope + * @see org.springframework.web.context.request.SessionScope + */ +public interface Scope { + + /** + * Return the object with the given name from the underlying scope, + * {@link org.springframework.beans.factory.ObjectFactory#getObject() creating it} + * if not found in the underlying storage mechanism. + *

This is the central operation of a Scope, and the only operation + * that is absolutely required. + * @param name the name of the object to retrieve + * @param objectFactory the {@link ObjectFactory} to use to create the scoped + * object if it is not present in the underlying storage mechanism + * @return the desired object (never null) + */ + Object get(String name, ObjectFactory objectFactory); + + /** + * Remove the object with the given name from the underlying scope. + *

Returns null if no object was found; otherwise + * returns the removed Object. + *

Note that an implementation should also remove a registered destruction + * callback for the specified object, if any. It does, however, not + * need to execute a registered destruction callback in this case, + * since the object will be destroyed by the caller (if appropriate). + *

Note: This is an optional operation. Implementations may throw + * {@link UnsupportedOperationException} if they do not support explicitly + * removing an object. + * @param name the name of the object to remove + * @return the removed object, or null if no object was present + * @see #registerDestructionCallback + */ + Object remove(String name); + + /** + * Register a callback to be executed on destruction of the specified + * object in the scope (or at destruction of the entire scope, if the + * scope does not destroy individual objects but rather only terminates + * in its entirety). + *

Note: This is an optional operation. This method will only + * be called for scoped beans with actual destruction configuration + * (DisposableBean, destroy-method, DestructionAwareBeanPostProcessor). + * Implementations should do their best to execute a given callback + * at the appropriate time. If such a callback is not supported by the + * underlying runtime environment at all, the callback must be + * ignored and a corresponding warning should be logged. + *

Note that 'destruction' refers to to automatic destruction of + * the object as part of the scope's own lifecycle, not to the individual + * scoped object having been explicitly removed by the application. + * If a scoped object gets removed via this facade's {@link #remove(String)} + * method, any registered destruction callback should be removed as well, + * assuming that the removed object will be reused or manually destroyed. + * @param name the name of the object to execute the destruction callback for + * @param callback the destruction callback to be executed. + * Note that the passed-in Runnable will never throw an exception, + * so it can safely be executed without an enclosing try-catch block. + * Furthermore, the Runnable will usually be serializable, provided + * that its target object is serializable as well. + * @see org.springframework.beans.factory.DisposableBean + * @see org.springframework.beans.factory.support.AbstractBeanDefinition#getDestroyMethodName() + * @see DestructionAwareBeanPostProcessor + */ + void registerDestructionCallback(String name, Runnable callback); + + /** + * Return the conversation ID for the current underlying scope, if any. + *

The exact meaning of the conversation ID depends on the underlying + * storage mechanism. In the case of session-scoped objects, the + * conversation ID would typically be equal to (or derived from) the + * {@link javax.servlet.http.HttpSession#getId() session ID}; in the + * case of a custom conversation that sits within the overall session, + * the specific ID for the current conversation would be appropriate. + *

Note: This is an optional operation. It is perfectly valid to + * return null in an implementation of this method if the + * underlying storage mechanism has no obvious candidate for such an ID. + * @return the conversation ID, or null if there is no + * conversation ID for the current scope + */ + String getConversationId(); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/ServiceLocatorFactoryBean.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/ServiceLocatorFactoryBean.java new file mode 100644 index 0000000000..8dacdfb856 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/ServiceLocatorFactoryBean.java @@ -0,0 +1,416 @@ +/* + * Copyright 2002-2008 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.config; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Properties; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.FatalBeanException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; + +/** + * A {@link org.springframework.beans.factory.FactoryBean} implementation that + * takes an interface which must have one or more methods with + * the signatures MyType xxx() or MyType xxx(MyIdType id) + * (typically, MyService getService() or MyService getService(String id)) + * and creates a dynamic proxy which implements that interface, delegating to an + * underlying {@link org.springframework.beans.factory.BeanFactory}. + * + *

Such service locators permit the decoupling of calling code from + * the {@link org.springframework.beans.factory.BeanFactory} API, by using an + * appropriate custom locator interface. They will typically be used for + * prototype beans, i.e. for factory methods that are supposed to + * return a new instance for each call. The client receives a reference to the + * service locator via setter or constructor injection, to be able to invoke + * the locator's factory methods on demand. For singleton beans, direct + * setter or constructor injection of the target bean is preferable. + * + *

On invocation of the no-arg factory method, or the single-arg factory + * method with a String id of null or empty String, if exactly + * one bean in the factory matches the return type of the factory + * method, that bean is returned, otherwise a + * {@link org.springframework.beans.factory.NoSuchBeanDefinitionException} + * is thrown. + * + *

On invocation of the single-arg factory method with a non-null (and + * non-empty) argument, the proxy returns the result of a + * {@link org.springframework.beans.factory.BeanFactory#getBean(String)} call, + * using a stringified version of the passed-in id as bean name. + * + *

A factory method argument will usually be a String, but can also be an + * int or a custom enumeration type, for example, stringified via + * toString. The resulting String can be used as bean name as-is, + * provided that corresponding beans are defined in the bean factory. + * Alternatively, {@link #setServiceMappings(java.util.Properties) a custom mapping} + * between service ids and bean names can be defined. + * + *

By way of an example, consider the following service locator interface. + * Note that this interface is not dependant on any Spring APIs. + * + *

package a.b.c;
+ *
+ *public interface ServiceFactory {
+ *
+ *    public MyService getService ();
+ *}
+ * + *

A sample config in an XML-based + * {@link org.springframework.beans.factory.BeanFactory} might look as follows: + * + *

<beans>
+ *
+ *   <!-- Prototype bean since we have state -->
+ *   <bean id="myService" class="a.b.c.MyService" singleton="false"/>
+ *
+ *   <!-- will lookup the above 'myService' bean by *TYPE* -->
+ *   <bean id="myServiceFactory"
+ *            class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean">
+ *     <property name="serviceLocatorInterface" value="a.b.c.ServiceFactory"/>
+ *   </bean>
+ *
+ *   <bean id="clientBean" class="a.b.c.MyClientBean">
+ *     <property name="myServiceFactory" ref="myServiceFactory"/>
+ *   </bean>
+ *
+ *</beans>
+ * + *

The attendant MyClientBean class implementation might then + * look something like this: + * + *

package a.b.c;
+ *
+ *public class MyClientBean {
+ *
+ *    private ServiceFactory myServiceFactory;
+ *
+ *    // actual implementation provided by the Spring container
+ *    public void setServiceFactory(ServiceFactory myServiceFactory) {
+ *        this.myServiceFactory = myServiceFactory;
+ *    }
+ *
+ *    public void someBusinessMethod() {
+ *        // get a 'fresh', brand new MyService instance
+ *        MyService service = this.myServiceFactory.getService();
+ *        // use the service object to effect the business logic...
+ *    }
+ *}
+ * + *

By way of an example that looks up a bean by name, consider + * the following service locator interface. Again, note that this + * interface is not dependant on any Spring APIs. + * + *

package a.b.c;
+ *
+ *public interface ServiceFactory {
+ *
+ *    public MyService getService (String serviceName);
+ *}
+ * + *

A sample config in an XML-based + * {@link org.springframework.beans.factory.BeanFactory} might look as follows: + * + *

<beans>
+ *
+ *   <!-- Prototype beans since we have state (both extend MyService) -->
+ *   <bean id="specialService" class="a.b.c.SpecialService" singleton="false"/>
+ *   <bean id="anotherService" class="a.b.c.AnotherService" singleton="false"/>
+ *
+ *   <bean id="myServiceFactory"
+ *            class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean">
+ *     <property name="serviceLocatorInterface" value="a.b.c.ServiceFactory"/>
+ *   </bean>
+ *
+ *   <bean id="clientBean" class="a.b.c.MyClientBean">
+ *     <property name="myServiceFactory" ref="myServiceFactory"/>
+ *   </bean>
+ *
+ *</beans>
+ * + *

The attendant MyClientBean class implementation might then + * look something like this: + * + *

package a.b.c;
+ *
+ *public class MyClientBean {
+ *
+ *    private ServiceFactory myServiceFactory;
+ *
+ *    // actual implementation provided by the Spring container
+ *    public void setServiceFactory(ServiceFactory myServiceFactory) {
+ *        this.myServiceFactory = myServiceFactory;
+ *    }
+ *
+ *    public void someBusinessMethod() {
+ *        // get a 'fresh', brand new MyService instance
+ *        MyService service = this.myServiceFactory.getService("specialService");
+ *        // use the service object to effect the business logic...
+ *    }
+ *
+ *    public void anotherBusinessMethod() {
+ *        // get a 'fresh', brand new MyService instance
+ *        MyService service = this.myServiceFactory.getService("anotherService");
+ *        // use the service object to effect the business logic...
+ *    }
+ *}
+ * + *

See {@link ObjectFactoryCreatingFactoryBean} for an alternate approach. + * + * @author Colin Sampaleanu + * @author Juergen Hoeller + * @since 1.1.4 + * @see #setServiceLocatorInterface + * @see #setServiceMappings + * @see ObjectFactoryCreatingFactoryBean + */ +public class ServiceLocatorFactoryBean implements FactoryBean, BeanFactoryAware, InitializingBean { + + private Class serviceLocatorInterface; + + private Constructor serviceLocatorExceptionConstructor; + + private Properties serviceMappings; + + private ListableBeanFactory beanFactory; + + private Object proxy; + + + /** + * Set the service locator interface to use, which must have one or more methods with + * the signatures MyType xxx() or MyType xxx(MyIdType id) + * (typically, MyService getService() or MyService getService(String id)). + * See the {@link ServiceLocatorFactoryBean class-level Javadoc} for + * information on the semantics of such methods. + */ + public void setServiceLocatorInterface(Class interfaceType) { + this.serviceLocatorInterface = interfaceType; + } + + /** + * Set the exception class that the service locator should throw if service + * lookup failed. The specified exception class must have a constructor + * with one of the following parameter types: (String, Throwable) + * or (Throwable) or (String). + *

If not specified, subclasses of Spring's BeansException will be thrown, + * for example NoSuchBeanDefinitionException. As those are unchecked, the + * caller does not need to handle them, so it might be acceptable that + * Spring exceptions get thrown as long as they are just handled generically. + * @see #determineServiceLocatorExceptionConstructor + * @see #createServiceLocatorException + */ + public void setServiceLocatorExceptionClass(Class serviceLocatorExceptionClass) { + if (serviceLocatorExceptionClass != null && !Exception.class.isAssignableFrom(serviceLocatorExceptionClass)) { + throw new IllegalArgumentException( + "serviceLocatorException [" + serviceLocatorExceptionClass.getName() + "] is not a subclass of Exception"); + } + this.serviceLocatorExceptionConstructor = + determineServiceLocatorExceptionConstructor(serviceLocatorExceptionClass); + } + + /** + * Set mappings between service ids (passed into the service locator) + * and bean names (in the bean factory). Service ids that are not defined + * here will be treated as bean names as-is. + *

The empty string as service id key defines the mapping for null and + * empty string, and for factory methods without parameter. If not defined, + * a single matching bean will be retrieved from the bean factory. + * @param serviceMappings mappings between service ids and bean names, + * with service ids as keys as bean names as values + */ + public void setServiceMappings(Properties serviceMappings) { + this.serviceMappings = serviceMappings; + } + + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + if (!(beanFactory instanceof ListableBeanFactory)) { + throw new FatalBeanException( + "ServiceLocatorFactoryBean needs to run in a BeanFactory that is a ListableBeanFactory"); + } + this.beanFactory = (ListableBeanFactory) beanFactory; + } + + public void afterPropertiesSet() { + if (this.serviceLocatorInterface == null) { + throw new IllegalArgumentException("Property 'serviceLocatorInterface' is required"); + } + + // Create service locator proxy. + this.proxy = Proxy.newProxyInstance( + this.serviceLocatorInterface.getClassLoader(), + new Class[] {this.serviceLocatorInterface}, + new ServiceLocatorInvocationHandler()); + } + + + /** + * Determine the constructor to use for the given service locator exception + * class. Only called in case of a custom service locator exception. + *

The default implementation looks for a constructor with one of the + * following parameter types: (String, Throwable) + * or (Throwable) or (String). + * @param exceptionClass the exception class + * @return the constructor to use + * @see #setServiceLocatorExceptionClass + */ + protected Constructor determineServiceLocatorExceptionConstructor(Class exceptionClass) { + try { + return exceptionClass.getConstructor(new Class[] {String.class, Throwable.class}); + } + catch (NoSuchMethodException ex) { + try { + return exceptionClass.getConstructor(new Class[] {Throwable.class}); + } + catch (NoSuchMethodException ex2) { + try { + return exceptionClass.getConstructor(new Class[] {String.class}); + } + catch (NoSuchMethodException ex3) { + throw new IllegalArgumentException( + "Service locator exception [" + exceptionClass.getName() + + "] neither has a (String, Throwable) constructor nor a (String) constructor"); + } + } + } + } + + /** + * Create a service locator exception for the given cause. + * Only called in case of a custom service locator exception. + *

The default implementation can handle all variations of + * message and exception arguments. + * @param exceptionConstructor the constructor to use + * @param cause the cause of the service lookup failure + * @return the service locator exception to throw + * @see #setServiceLocatorExceptionClass + */ + protected Exception createServiceLocatorException(Constructor exceptionConstructor, BeansException cause) { + Class[] paramTypes = exceptionConstructor.getParameterTypes(); + Object[] args = new Object[paramTypes.length]; + for (int i = 0; i < paramTypes.length; i++) { + if (paramTypes[i].equals(String.class)) { + args[i] = cause.getMessage(); + } + else if (paramTypes[i].isInstance(cause)) { + args[i] = cause; + } + } + return (Exception) BeanUtils.instantiateClass(exceptionConstructor, args); + } + + + public Object getObject() { + return this.proxy; + } + + public Class getObjectType() { + return this.serviceLocatorInterface; + } + + public boolean isSingleton() { + return true; + } + + + /** + * Invocation handler that delegates service locator calls to the bean factory. + */ + private class ServiceLocatorInvocationHandler implements InvocationHandler { + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (ReflectionUtils.isEqualsMethod(method)) { + // Only consider equal when proxies are identical. + return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE); + } + else if (ReflectionUtils.isHashCodeMethod(method)) { + // Use hashCode of service locator proxy. + return new Integer(System.identityHashCode(proxy)); + } + else if (ReflectionUtils.isToStringMethod(method)) { + return "Service locator: " + serviceLocatorInterface.getName(); + } + else { + return invokeServiceLocatorMethod(method, args); + } + } + + private Object invokeServiceLocatorMethod(Method method, Object[] args) throws Exception { + Class serviceLocatorMethodReturnType = getServiceLocatorMethodReturnType(method); + try { + String beanName = tryGetBeanName(args); + if (StringUtils.hasLength(beanName)) { + // Service locator for a specific bean name. + return beanFactory.getBean(beanName, serviceLocatorMethodReturnType); + } + else { + // Service locator for a bean type. + return BeanFactoryUtils.beanOfTypeIncludingAncestors(beanFactory, serviceLocatorMethodReturnType); + } + } + catch (BeansException ex) { + if (serviceLocatorExceptionConstructor != null) { + throw createServiceLocatorException(serviceLocatorExceptionConstructor, ex); + } + throw ex; + } + } + + /** + * Check whether a service id was passed in. + */ + private String tryGetBeanName(Object[] args) { + String beanName = ""; + if (args != null && args.length == 1 && args[0] != null) { + beanName = args[0].toString(); + } + // Look for explicit serviceId-to-beanName mappings. + if (serviceMappings != null) { + String mappedName = serviceMappings.getProperty(beanName); + if (mappedName != null) { + beanName = mappedName; + } + } + return beanName; + } + + private Class getServiceLocatorMethodReturnType(Method method) throws NoSuchMethodException { + Class[] paramTypes = method.getParameterTypes(); + Method interfaceMethod = serviceLocatorInterface.getMethod(method.getName(), paramTypes); + Class serviceLocatorReturnType = interfaceMethod.getReturnType(); + + // Check whether the method is a valid service locator. + if (paramTypes.length > 1 || void.class.equals(serviceLocatorReturnType)) { + throw new UnsupportedOperationException( + "May only call methods with signature ' xxx()' or ' xxx( id)' " + + "on factory interface, but tried to call: " + interfaceMethod); + } + return serviceLocatorReturnType; + } + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/SetFactoryBean.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/SetFactoryBean.java new file mode 100644 index 0000000000..454241b5fe --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/SetFactoryBean.java @@ -0,0 +1,99 @@ +/* + * Copyright 2002-2007 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.config; + +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.TypeConverter; +import org.springframework.core.GenericCollectionTypeResolver; +import org.springframework.core.JdkVersion; + +/** + * Simple factory for shared Set instances. Allows for central setup + * of Sets via the "set" element in XML bean definitions. + * + * @author Juergen Hoeller + * @since 09.12.2003 + * @see ListFactoryBean + * @see MapFactoryBean + */ +public class SetFactoryBean extends AbstractFactoryBean { + + private Set sourceSet; + + private Class targetSetClass; + + + /** + * Set the source Set, typically populated via XML "set" elements. + */ + public void setSourceSet(Set sourceSet) { + this.sourceSet = sourceSet; + } + + /** + * Set the class to use for the target Set. Can be populated with a fully + * qualified class name when defined in a Spring application context. + *

Default is a linked HashSet, keeping the registration order. + * @see java.util.LinkedHashSet + */ + public void setTargetSetClass(Class targetSetClass) { + if (targetSetClass == null) { + throw new IllegalArgumentException("'targetSetClass' must not be null"); + } + if (!Set.class.isAssignableFrom(targetSetClass)) { + throw new IllegalArgumentException("'targetSetClass' must implement [java.util.Set]"); + } + this.targetSetClass = targetSetClass; + } + + + public Class getObjectType() { + return Set.class; + } + + protected Object createInstance() { + if (this.sourceSet == null) { + throw new IllegalArgumentException("'sourceSet' is required"); + } + Set result = null; + if (this.targetSetClass != null) { + result = (Set) BeanUtils.instantiateClass(this.targetSetClass); + } + else { + result = new LinkedHashSet(this.sourceSet.size()); + } + Class valueType = null; + if (this.targetSetClass != null && JdkVersion.isAtLeastJava15()) { + valueType = GenericCollectionTypeResolver.getCollectionType(this.targetSetClass); + } + if (valueType != null) { + TypeConverter converter = getBeanTypeConverter(); + for (Iterator it = this.sourceSet.iterator(); it.hasNext();) { + result.add(converter.convertIfNecessary(it.next(), valueType)); + } + } + else { + result.addAll(this.sourceSet); + } + return result; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/SingletonBeanRegistry.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/SingletonBeanRegistry.java new file mode 100644 index 0000000000..d6d091dae9 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/SingletonBeanRegistry.java @@ -0,0 +1,121 @@ +/* + * Copyright 2002-2007 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.config; + +/** + * Interface that defines a registry for shared bean instances. + * Can be implemented by {@link org.springframework.beans.factory.BeanFactory} + * implementations in order to expose their singleton management facility + * in a uniform manner. + * + *

The {@link ConfigurableBeanFactory} interface extends this interface. + * + * @author Juergen Hoeller + * @since 2.0 + * @see ConfigurableBeanFactory + * @see org.springframework.beans.factory.support.DefaultSingletonBeanRegistry + * @see org.springframework.beans.factory.support.AbstractBeanFactory + */ +public interface SingletonBeanRegistry { + + /** + * Register the given existing object as singleton in the bean registry, + * under the given bean name. + *

The given instance is supposed to be fully initialized; the registry + * will not perform any initialization callbacks (in particular, it won't + * call InitializingBean's afterPropertiesSet method). + * The given instance will not receive any destruction callbacks + * (like DisposableBean's destroy method) either. + *

If running within a full BeanFactory: Register a bean definition + * instead of an existing instance if your bean is supposed to receive + * initialization and/or destruction callbacks. + *

Typically invoked during registry configuration, but can also be used + * for runtime registration of singletons. As a consequence, a registry + * implementation should synchronize singleton access; it will have to do + * this anyway if it supports a BeanFactory's lazy initialization of singletons. + * @param beanName the name of the bean + * @param singletonObject the existing singleton object + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet + * @see org.springframework.beans.factory.DisposableBean#destroy + * @see org.springframework.beans.factory.support.BeanDefinitionRegistry#registerBeanDefinition + */ + void registerSingleton(String beanName, Object singletonObject); + + /** + * Return the (raw) singleton object registered under the given name. + *

Only checks already instantiated singletons; does not return an Object + * for singleton bean definitions which have not been instantiated yet. + *

The main purpose of this method is to access manually registered singletons + * (see {@link #registerSingleton}). Can also be used to access a singleton + * defined by a bean definition that already been created, in a raw fashion. + * @param beanName the name of the bean to look for + * @return the registered singleton object, or null if none found + * @see ConfigurableListableBeanFactory#getBeanDefinition + */ + Object getSingleton(String beanName); + + /** + * Check if this registry contains a singleton instance with the given name. + *

Only checks already instantiated singletons; does not return true + * for singleton bean definitions which have not been instantiated yet. + *

The main purpose of this method is to check manually registered singletons + * (see {@link #registerSingleton}). Can also be used to check whether a + * singleton defined by a bean definition has already been created. + *

To check whether a bean factory contains a bean definition with a given name, + * use ListableBeanFactory's containsBeanDefinition. Calling both + * containsBeanDefinition and containsSingleton answers + * whether a specific bean factory contains an own bean with the given name. + *

Use BeanFactory's containsBean for general checks whether the + * factory knows about a bean with a given name (whether manually registered singleton + * instance or created by bean definition), also checking ancestor factories. + * @param beanName the name of the bean to look for + * @return if this bean factory contains a singleton instance with the given name + * @see #registerSingleton + * @see org.springframework.beans.factory.ListableBeanFactory#containsBeanDefinition + * @see org.springframework.beans.factory.BeanFactory#containsBean + */ + boolean containsSingleton(String beanName); + + /** + * Return the names of singleton beans registered in this registry. + *

Only checks already instantiated singletons; does not return names + * for singleton bean definitions which have not been instantiated yet. + *

The main purpose of this method is to check manually registered singletons + * (see {@link #registerSingleton}). Can also be used to check which + * singletons defined by a bean definition have already been created. + * @return the list of names as String array (never null) + * @see #registerSingleton + * @see org.springframework.beans.factory.support.BeanDefinitionRegistry#getBeanDefinitionNames + * @see org.springframework.beans.factory.ListableBeanFactory#getBeanDefinitionNames + */ + String[] getSingletonNames(); + + /** + * Return the number of singleton beans registered in this registry. + *

Only checks already instantiated singletons; does not count + * singleton bean definitions which have not been instantiated yet. + *

The main purpose of this method is to check manually registered singletons + * (see {@link #registerSingleton}). Can also be used to count the number of + * singletons defined by a bean definition that have already been created. + * @return the number of singleton beans + * @see #registerSingleton + * @see org.springframework.beans.factory.support.BeanDefinitionRegistry#getBeanDefinitionCount + * @see org.springframework.beans.factory.ListableBeanFactory#getBeanDefinitionCount + */ + int getSingletonCount(); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/SmartInstantiationAwareBeanPostProcessor.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/SmartInstantiationAwareBeanPostProcessor.java new file mode 100644 index 0000000000..3b96baacfe --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/SmartInstantiationAwareBeanPostProcessor.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2008 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.config; + +import java.lang.reflect.Constructor; + +import org.springframework.beans.BeansException; + +/** + * Extension of the {@link InstantiationAwareBeanPostProcessor} interface, + * adding a callback for predicting the eventual type of a processed bean. + * + *

NOTE: This interface is a special purpose interface, mainly for + * internal use within the framework. In general, application-provided + * post-processors should simply implement the plain {@link BeanPostProcessor} + * interface or derive from the {@link InstantiationAwareBeanPostProcessorAdapter} + * class. New methods might be added to this interface even in point releases. + * + * @author Juergen Hoeller + * @since 2.0.3 + * @see InstantiationAwareBeanPostProcessorAdapter + */ +public interface SmartInstantiationAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessor { + + /** + * Predict the type of the bean to be eventually returned from this + * processor's {@link #postProcessBeforeInstantiation} callback. + * @param beanClass the raw class of the bean + * @param beanName the name of the bean + * @return the type of the bean, or null if not predictable + * @throws org.springframework.beans.BeansException in case of errors + */ + Class predictBeanType(Class beanClass, String beanName) throws BeansException; + + /** + * Determine the candidate constructors to use for the given bean. + * @param beanClass the raw class of the bean (never null) + * @param beanName the name of the bean + * @return the candidate constructors, or null if none specified + * @throws org.springframework.beans.BeansException in case of errors + */ + Constructor[] determineCandidateConstructors(Class beanClass, String beanName) throws BeansException; + + /** + * Obtain a reference for early access to the specified bean, + * typically for the purpose of resolving a circular reference. + *

This callback gives post-processors a chance to expose a wrapper + * early - that is, before the target bean instance is fully initialized. + * The exposed object should be equivalent to the what + * {@link #postProcessBeforeInitialization} / {@link #postProcessAfterInitialization} + * would expose otherwise. Note that the object returned by this method will + * be used as bean reference unless the post-processor returns a different + * wrapper from said post-process callbacks. In other words: Those post-process + * callbacks may either eventually expose the same reference or alternatively + * return the raw bean instance from those subsequent callbacks (if the wrapper + * for the affected bean has been built for a call to this method already, + * it will be exposes as final bean reference by default). + * @param bean the raw bean instance + * @param beanName the name of the bean + * @return the object to expose as bean reference + * (typically with the passed-in bean instance as default) + * @throws org.springframework.beans.BeansException in case of errors + */ + Object getEarlyBeanReference(Object bean, String beanName) throws BeansException; + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/TypedStringValue.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/TypedStringValue.java new file mode 100644 index 0000000000..358909a73f --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/TypedStringValue.java @@ -0,0 +1,193 @@ +/* + * Copyright 2002-2008 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.config; + +import org.springframework.beans.BeanMetadataElement; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; + +/** + * Holder for a typed String value. Can be added to bean definitions + * in order to explicitly specify a target type for a String value, + * for example for collection elements. + * + *

This holder will just store the String value and the target type. + * The actual conversion will be performed by the bean factory. + * + * @author Juergen Hoeller + * @since 1.2 + * @see BeanDefinition#getPropertyValues + * @see org.springframework.beans.MutablePropertyValues#addPropertyValue + */ +public class TypedStringValue implements BeanMetadataElement { + + private String value; + + private Object targetType; + + private Object source; + + + /** + * Create a new {@link TypedStringValue} for the given String value. + * @param value the String value + */ + public TypedStringValue(String value) { + setValue(value); + } + + /** + * Create a new {@link TypedStringValue} for the given String value + * and target type. + * @param value the String value + * @param targetType the type to convert to + */ + public TypedStringValue(String value, Class targetType) { + setValue(value); + setTargetType(targetType); + } + + /** + * Create a new {@link TypedStringValue} for the given String value + * and target type. + * @param value the String value + * @param targetTypeName the type to convert to + */ + public TypedStringValue(String value, String targetTypeName) { + setValue(value); + setTargetTypeName(targetTypeName); + } + + + /** + * Set the String value. + *

Only necessary for manipulating a registered value, + * for example in BeanFactoryPostProcessors. + * @see PropertyPlaceholderConfigurer + */ + public void setValue(String value) { + this.value = value; + } + + /** + * Return the String value. + */ + public String getValue() { + return this.value; + } + + /** + * Set the type to convert to. + *

Only necessary for manipulating a registered value, + * for example in BeanFactoryPostProcessors. + * @see PropertyPlaceholderConfigurer + */ + public void setTargetType(Class targetType) { + Assert.notNull(targetType, "'targetType' must not be null"); + this.targetType = targetType; + } + + /** + * Return the type to convert to. + */ + public Class getTargetType() { + if (!(this.targetType instanceof Class)) { + throw new IllegalStateException("Typed String value does not carry a resolved target type"); + } + return (Class) this.targetType; + } + + /** + * Specify the type to convert to. + */ + public void setTargetTypeName(String targetTypeName) { + Assert.notNull(targetTypeName, "'targetTypeName' must not be null"); + this.targetType = targetTypeName; + } + + /** + * Return the type to convert to. + */ + public String getTargetTypeName() { + if (this.targetType instanceof Class) { + return ((Class) this.targetType).getName(); + } + else { + return (String) this.targetType; + } + } + + /** + * Return whether this typed String value carries a target type . + */ + public boolean hasTargetType() { + return (this.targetType instanceof Class); + } + + /** + * Determine the type to convert to, resolving it from a specified class name + * if necessary. Will also reload a specified Class from its name when called + * with the target type already resolved. + * @param classLoader the ClassLoader to use for resolving a (potential) class name + * @return the resolved type to convert to + * @throws ClassNotFoundException if the type cannot be resolved + */ + public Class resolveTargetType(ClassLoader classLoader) throws ClassNotFoundException { + if (this.targetType == null) { + return null; + } + Class resolvedClass = ClassUtils.forName(getTargetTypeName(), classLoader); + this.targetType = resolvedClass; + return resolvedClass; + } + + + /** + * Set the configuration source Object for this metadata element. + *

The exact type of the object will depend on the configuration mechanism used. + */ + public void setSource(Object source) { + this.source = source; + } + + public Object getSource() { + return this.source; + } + + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof TypedStringValue)) { + return false; + } + TypedStringValue otherValue = (TypedStringValue) other; + return (ObjectUtils.nullSafeEquals(this.value, otherValue.value) && + ObjectUtils.nullSafeEquals(this.targetType, otherValue.targetType)); + } + + public int hashCode() { + return ObjectUtils.nullSafeHashCode(this.value) * 29 + ObjectUtils.nullSafeHashCode(this.targetType); + } + + public String toString() { + return "TypedStringValue: value [" + this.value + "], target type [" + this.targetType + "]"; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/package.html b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/package.html new file mode 100644 index 0000000000..761bfdefd7 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/package.html @@ -0,0 +1,7 @@ + + + +SPI interfaces and configuration-related convenience classes for bean factories. + + + diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/generic/GenericBeanFactoryAccessor.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/generic/GenericBeanFactoryAccessor.java new file mode 100644 index 0000000000..e172efa57f --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/generic/GenericBeanFactoryAccessor.java @@ -0,0 +1,140 @@ +/* + * Copyright 2002-2008 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.generic; + +import java.lang.annotation.Annotation; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.Assert; + +/** + * Simple wrapper around a {@link ListableBeanFactory} that provides typed, generics-based + * access to key methods. This removes the need for casting in many cases and should + * increase compile-time type safety. + * + *

Provides a simple mechanism for accessing all beans with a particular {@link Annotation}. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + */ +public class GenericBeanFactoryAccessor { + + /** + * The {@link ListableBeanFactory} being wrapped. + */ + private final ListableBeanFactory beanFactory; + + + /** + * Constructs a GenericBeanFactoryAccessor that wraps the supplied {@link ListableBeanFactory}. + */ + public GenericBeanFactoryAccessor(ListableBeanFactory beanFactory) { + Assert.notNull(beanFactory, "Bean factory must not be null"); + this.beanFactory = beanFactory; + } + + /** + * Return the wrapped {@link ListableBeanFactory}. + */ + public final ListableBeanFactory getBeanFactory() { + return this.beanFactory; + } + + + /** + * @see org.springframework.beans.factory.BeanFactory#getBean(String) + */ + public T getBean(String name) throws BeansException { + return (T) this.beanFactory.getBean(name); + } + + /** + * @see org.springframework.beans.factory.BeanFactory#getBean(String, Class) + */ + public T getBean(String name, Class requiredType) throws BeansException { + return (T) this.beanFactory.getBean(name, requiredType); + } + + /** + * @see ListableBeanFactory#getBeansOfType(Class) + */ + public Map getBeansOfType(Class type) throws BeansException { + return this.beanFactory.getBeansOfType(type); + } + + /** + * @see ListableBeanFactory#getBeansOfType(Class, boolean, boolean) + */ + public Map getBeansOfType(Class type, boolean includeNonSingletons, boolean allowEagerInit) + throws BeansException { + + return this.beanFactory.getBeansOfType(type, includeNonSingletons, allowEagerInit); + } + + /** + * Find all beans whose Class has the supplied {@link Annotation} type. + * @param annotationType the type of annotation to look for + * @return a Map with the matching beans, containing the bean names as + * keys and the corresponding bean instances as values + */ + public Map getBeansWithAnnotation(Class annotationType) { + Map results = new LinkedHashMap(); + for (String beanName : this.beanFactory.getBeanNamesForType(Object.class)) { + if (findAnnotationOnBean(beanName, annotationType) != null) { + results.put(beanName, this.beanFactory.getBean(beanName)); + } + } + return results; + } + + /** + * Find a {@link Annotation} of annotationType on the specified + * bean, traversing its interfaces and super classes if no annotation can be + * found on the given class itself, as well as checking its raw bean class + * if not found on the exposed bean reference (e.g. in case of a proxy). + * @param beanName the name of the bean to look for annotations on + * @param annotationType the annotation class to look for + * @return the annotation of the given type found, or null + * @see org.springframework.core.annotation.AnnotationUtils#findAnnotation(Class, Class) + */ + public A findAnnotationOnBean(String beanName, Class annotationType) { + Class handlerType = this.beanFactory.getType(beanName); + A ann = AnnotationUtils.findAnnotation(handlerType, annotationType); + if (ann == null && this.beanFactory instanceof ConfigurableBeanFactory && + this.beanFactory.containsBeanDefinition(beanName)) { + ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) this.beanFactory; + BeanDefinition bd = cbf.getMergedBeanDefinition(beanName); + if (bd instanceof AbstractBeanDefinition) { + AbstractBeanDefinition abd = (AbstractBeanDefinition) bd; + if (abd.hasBeanClass()) { + Class beanClass = abd.getBeanClass(); + ann = AnnotationUtils.findAnnotation(beanClass, annotationType); + } + } + } + return ann; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/generic/package.html b/org.springframework.beans/src/main/java/org/springframework/beans/factory/generic/package.html new file mode 100644 index 0000000000..e7e3dcf234 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/generic/package.html @@ -0,0 +1,8 @@ + + + +Support package for generic BeanFactory access, +leveraging Java 5 generics in the accessor API. + + + diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/package.html b/org.springframework.beans/src/main/java/org/springframework/beans/factory/package.html new file mode 100644 index 0000000000..84846a3ca3 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/package.html @@ -0,0 +1,15 @@ + + + +The core package implementing Spring's lightweight Inversion of Control (IoC) container. +

+Provides an alternative to the Singleton and Prototype design +patterns, including a consistent approach to configuration management. +Builds on the org.springframework.beans package. + +

This package and related packages are discussed in Chapter 11 of +Expert One-On-One J2EE Design and Development +by Rod Johnson (Wrox, 2002). + + + diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/AbstractComponentDefinition.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/AbstractComponentDefinition.java new file mode 100644 index 0000000000..945085d4cb --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/AbstractComponentDefinition.java @@ -0,0 +1,70 @@ +/* + * Copyright 2002-2006 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.parsing; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanReference; + +/** + * Base implementation of {@link ComponentDefinition} that provides a basic implementation of + * {@link #getDescription} which delegates to {@link #getName}. Also provides a base implementation + * of {@link #toString} which delegates to {@link #getDescription} in keeping with the recommended + * implementation strategy. Also provides default implementations of {@link #getInnerBeanDefinitions} + * and {@link #getBeanReferences} that return an empty array. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + */ +public abstract class AbstractComponentDefinition implements ComponentDefinition { + + /** + * Delegates to {@link #getName}. + */ + public String getDescription() { + return getName(); + } + + /** + * Returns an empty array. + */ + public BeanDefinition[] getBeanDefinitions() { + return new BeanDefinition[0]; + } + + /** + * Returns an empty array. + */ + public BeanDefinition[] getInnerBeanDefinitions() { + return new BeanDefinition[0]; + } + + /** + * Returns an empty array. + */ + public BeanReference[] getBeanReferences() { + return new BeanReference[0]; + } + + /** + * Delegates to {@link #getDescription}. + */ + public String toString() { + return getDescription(); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/AliasDefinition.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/AliasDefinition.java new file mode 100644 index 0000000000..dd81c9d9b9 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/AliasDefinition.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2007 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.parsing; + +import org.springframework.beans.BeanMetadataElement; +import org.springframework.util.Assert; + +/** + * Representation of an alias that has been registered during the parsing process. + * + * @author Juergen Hoeller + * @since 2.0 + * @see ReaderEventListener#aliasRegistered(AliasDefinition) + */ +public class AliasDefinition implements BeanMetadataElement { + + private final String beanName; + + private final String alias; + + private final Object source; + + + /** + * Create a new AliasDefinition. + * @param beanName the canonical name of the bean + * @param alias the alias registered for the bean + */ + public AliasDefinition(String beanName, String alias) { + this(beanName, alias, null); + } + + /** + * Create a new AliasDefinition. + * @param beanName the canonical name of the bean + * @param alias the alias registered for the bean + * @param source the source object (may be null) + */ + public AliasDefinition(String beanName, String alias, Object source) { + Assert.notNull(beanName, "Bean name must not be null"); + Assert.notNull(alias, "Alias must not be null"); + this.beanName = beanName; + this.alias = alias; + this.source = source; + } + + + /** + * Return the canonical name of the bean. + */ + public final String getBeanName() { + return this.beanName; + } + + /** + * Return the alias registered for the bean. + */ + public final String getAlias() { + return this.alias; + } + + public final Object getSource() { + return this.source; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/BeanComponentDefinition.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/BeanComponentDefinition.java new file mode 100644 index 0000000000..148a2c8e78 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/BeanComponentDefinition.java @@ -0,0 +1,134 @@ +/* + * Copyright 2002-2008 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.parsing; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.beans.PropertyValue; +import org.springframework.beans.PropertyValues; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.config.BeanReference; + +/** + * ComponentDefinition based on a standard BeanDefinition, exposing the given bean + * definition as well as inner bean definitions and bean references for the given bean. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + */ +public class BeanComponentDefinition extends BeanDefinitionHolder implements ComponentDefinition { + + private BeanDefinition[] innerBeanDefinitions; + + private BeanReference[] beanReferences; + + + /** + * Create a new BeanComponentDefinition for the given bean. + * @param beanDefinition the BeanDefinition + * @param beanName the name of the bean + */ + public BeanComponentDefinition(BeanDefinition beanDefinition, String beanName) { + super(beanDefinition, beanName); + findInnerBeanDefinitionsAndBeanReferences(beanDefinition); + } + + /** + * Create a new BeanComponentDefinition for the given bean. + * @param beanDefinition the BeanDefinition + * @param beanName the name of the bean + * @param aliases alias names for the bean, or null if none + */ + public BeanComponentDefinition(BeanDefinition beanDefinition, String beanName, String[] aliases) { + super(beanDefinition, beanName, aliases); + findInnerBeanDefinitionsAndBeanReferences(beanDefinition); + } + + /** + * Create a new BeanComponentDefinition for the given bean. + * @param holder the BeanDefinitionHolder encapsulating the + * bean definition as well as the name of the bean + */ + public BeanComponentDefinition(BeanDefinitionHolder holder) { + super(holder); + findInnerBeanDefinitionsAndBeanReferences(holder.getBeanDefinition()); + } + + + private void findInnerBeanDefinitionsAndBeanReferences(BeanDefinition beanDefinition) { + List innerBeans = new ArrayList(); + List references = new ArrayList(); + PropertyValues propertyValues = beanDefinition.getPropertyValues(); + for (int i = 0; i < propertyValues.getPropertyValues().length; i++) { + PropertyValue propertyValue = propertyValues.getPropertyValues()[i]; + Object value = propertyValue.getValue(); + if (value instanceof BeanDefinitionHolder) { + innerBeans.add(((BeanDefinitionHolder) value).getBeanDefinition()); + } + else if (value instanceof BeanDefinition) { + innerBeans.add(value); + } + else if (value instanceof BeanReference) { + references.add(value); + } + } + this.innerBeanDefinitions = (BeanDefinition[]) innerBeans.toArray(new BeanDefinition[innerBeans.size()]); + this.beanReferences = (BeanReference[]) references.toArray(new BeanReference[references.size()]); + } + + + public String getName() { + return getBeanName(); + } + + public String getDescription() { + return getShortDescription(); + } + + public BeanDefinition[] getBeanDefinitions() { + return new BeanDefinition[] {getBeanDefinition()}; + } + + public BeanDefinition[] getInnerBeanDefinitions() { + return this.innerBeanDefinitions; + } + + public BeanReference[] getBeanReferences() { + return this.beanReferences; + } + + + /** + * This implementation returns this ComponentDefinition's description. + * @see #getDescription() + */ + public String toString() { + return getDescription(); + } + + /** + * This implementations expects the other object to be of type BeanComponentDefinition + * as well, in addition to the superclass's equality requirements. + */ + public boolean equals(Object other) { + return (this == other || (other instanceof BeanComponentDefinition && super.equals(other))); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/BeanDefinitionParsingException.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/BeanDefinitionParsingException.java new file mode 100644 index 0000000000..43b537dd05 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/BeanDefinitionParsingException.java @@ -0,0 +1,39 @@ +/* + * Copyright 2002-2006 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.parsing; + +import org.springframework.beans.factory.BeanDefinitionStoreException; + +/** + * Exception thrown when a bean definition reader encounters an error + * during the parsing process. + * + * @author Juergen Hoeller + * @author Rob Harrop + * @since 2.0 + */ +public class BeanDefinitionParsingException extends BeanDefinitionStoreException { + + /** + * Create a new BeanDefinitionParsingException. + * @param problem the configuration problem that was detected during the parsing process + */ + public BeanDefinitionParsingException(Problem problem) { + super(problem.getResourceDescription(), problem.toString(), problem.getRootCause()); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/BeanEntry.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/BeanEntry.java new file mode 100644 index 0000000000..4bb1f36a4b --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/BeanEntry.java @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2006 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.parsing; + +/** + * {@link ParseState} entry representing a bean definition. + * + * @author Rob Harrop + * @since 2.0 + */ +public class BeanEntry implements ParseState.Entry { + + private String beanDefinitionName; + + + /** + * Creates a new instance of {@link BeanEntry} class. + * @param beanDefinitionName the name of the associated bean definition + */ + public BeanEntry(String beanDefinitionName) { + this.beanDefinitionName = beanDefinitionName; + } + + + public String toString() { + return "Bean '" + this.beanDefinitionName + "'"; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/ComponentDefinition.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/ComponentDefinition.java new file mode 100644 index 0000000000..b20786a201 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/ComponentDefinition.java @@ -0,0 +1,123 @@ +/* + * Copyright 2002-2006 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.parsing; + +import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanReference; + +/** + * Interface that describes the logical view of a set of {@link BeanDefinition BeanDefinitions} + * and {@link BeanReference BeanReferences} as presented in some configuration context. + * + *

With the introduction of {@link org.springframework.beans.factory.xml.NamespaceHandler pluggable custom XML tags}, + * it is now possible for a single logical configuration entity, in this case an XML tag, to + * create multiple {@link BeanDefinition BeanDefinitions} and {@link BeanReference RuntimeBeanReferences} + * in order to provide more succinct configuration and greater convenience to end users. As such, it can + * no longer be assumed that each configuration entity (e.g. XML tag) maps to one {@link BeanDefinition}. + * For tool vendors and other users who wish to present visualization or support for configuring Spring + * applications it is important that there is some mechanism in place to tie the {@link BeanDefinition BeanDefinitions} + * in the {@link org.springframework.beans.factory.BeanFactory} back to the configuration data in a way + * that has concrete meaning to the end user. As such, {@link org.springframework.beans.factory.xml.NamespaceHandler} + * implementations are able to publish events in the form of a ComponentDefinition for each + * logical entity being configured. Third parties can then {@link org.springframework.beans.factory.parsing.ReaderEventListener subscribe to these events}, + * allowing for a user-centric view of the bean metadata. + * + *

Each ComponentDefinition has a {@link #getSource source object} which is configuration-specific. + * In the case of XML-based configuration this is typically the {@link org.w3c.dom.Node} which contains the user + * supplied configuration information. In addition to this, each {@link BeanDefinition} enclosed in a + * ComponentDefinition has its own {@link BeanDefinition#getSource() source object} which may point + * to a different, more specific, set of configuration data. Beyond this, individual pieces of bean metadata such + * as the {@link org.springframework.beans.PropertyValue PropertyValues} may also have a source object giving an + * even greater level of detail. Source object extraction is handled through the + * {@link org.springframework.beans.factory.parsing.SourceExtractor} which can be customized as required. + * + *

Whilst direct access to important {@link BeanReference BeanReferences} is provided through + * {@link #getBeanReferences}, tools may wish to inspect all {@link BeanDefinition BeanDefinitions} to gather + * the full set of {@link BeanReference BeanReferences}. Implementations are required to provide + * all {@link BeanReference BeanReferences} that are required to validate the configuration of the + * overall logical entity as well as those required to provide full user visualisation of the configuration. + * It is expected that certain {@link BeanReference BeanReferences} will not be important to + * validation or to the user view of the configuration and as such these may be ommitted. A tool may wish to + * display any additional {@link BeanReference BeanReferences} sourced through the supplied + * {@link BeanDefinition BeanDefinitions} but this is not considered to be a typical case. + * + *

Tools can determine the important of contained {@link BeanDefinition BeanDefinitions} by checking the + * {@link BeanDefinition#getRole role identifier}. The role is essentially a hint to the tool as to how + * important the configuration provider believes a {@link BeanDefinition} is to the end user. It is expected + * that tools will not display all {@link BeanDefinition BeanDefinitions} for a given + * ComponentDefinition choosing instead to filter based on the role. Tools may choose to make + * this filtering user configurable. Particular notice should be given to the + * {@link BeanDefinition#ROLE_INFRASTRUCTURE INFRASTRUCTURE role identifier}. {@link BeanDefinition BeanDefinitions} + * classified with this role are completely unimportant to the end user and are required only for + * internal implementation reasons. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + * @see AbstractComponentDefinition + * @see CompositeComponentDefinition + * @see BeanComponentDefinition + * @see ReaderEventListener#componentRegistered(ComponentDefinition) + */ +public interface ComponentDefinition extends BeanMetadataElement { + + /** + * Get the user-visible name of this ComponentDefinition. + *

This should link back directly to the corresponding configuration data + * for this component in a given context. + */ + String getName(); + + /** + * Return a friendly description of the described component. + *

Implementations are encouraged to return the same value from + * toString(). + */ + String getDescription(); + + /** + * Return the {@link BeanDefinition BeanDefinitions} that were registered + * to form this ComponentDefinition. + *

It should be noted that a ComponentDefinition may well be related with + * other {@link BeanDefinition BeanDefinitions} via {@link BeanReference references}, + * however these are not included as they may be not available immediately. + * Important {@link BeanReference BeanReferences} are available from {@link #getBeanReferences()}. + * @return the array of BeanDefinitions, or an empty array if none + */ + BeanDefinition[] getBeanDefinitions(); + + /** + * Return the {@link BeanDefinition BeanDefinitions} that represent all relevant + * inner beans within this component. + *

Other inner beans may exist within the associated {@link BeanDefinition BeanDefinitions}, + * however these are not considered to be needed for validation or for user visualization. + * @return the array of BeanDefinitions, or an empty array if none + */ + BeanDefinition[] getInnerBeanDefinitions(); + + /** + * Return the set of {@link BeanReference BeanReferences} that are considered + * to be important to this ComponentDefinition. + *

Other {@link BeanReference BeanReferences} may exist within the associated + * {@link BeanDefinition BeanDefinitions}, however these are not considered + * to be needed for validation or for user visualization. + * @return the array of BeanReferences, or an empty array if none + */ + BeanReference[] getBeanReferences(); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/CompositeComponentDefinition.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/CompositeComponentDefinition.java new file mode 100644 index 0000000000..3e09f027f2 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/CompositeComponentDefinition.java @@ -0,0 +1,81 @@ +/* + * Copyright 2002-2006 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.parsing; + +import java.util.LinkedList; +import java.util.List; + +import org.springframework.util.Assert; + +/** + * {@link ComponentDefinition} implementation that holds one or more nested + * {@link ComponentDefinition} instances, aggregating them into a named group + * of components. + * + * @author Juergen Hoeller + * @since 2.0.1 + * @see #getNestedComponents() + */ +public class CompositeComponentDefinition extends AbstractComponentDefinition { + + private final String name; + + private final Object source; + + private final List nestedComponents = new LinkedList(); + + + /** + * Create a new CompositeComponentDefinition. + * @param name the name of the composite component + * @param source the source element that defines the root of the composite component + */ + public CompositeComponentDefinition(String name, Object source) { + Assert.notNull(name, "Name must not be null"); + this.name = name; + this.source = source; + } + + + public String getName() { + return this.name; + } + + public Object getSource() { + return this.source; + } + + + /** + * Add the given component as nested element of this composite component. + * @param component the nested component to add + */ + public void addNestedComponent(ComponentDefinition component) { + Assert.notNull(component, "ComponentDefinition must not be null"); + this.nestedComponents.add(component); + } + + /** + * Return the nested components that this composite component holds. + * @return the array of nested components, or an empty array if none + */ + public ComponentDefinition[] getNestedComponents() { + return (ComponentDefinition[]) + this.nestedComponents.toArray(new ComponentDefinition[this.nestedComponents.size()]); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/ConstructorArgumentEntry.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/ConstructorArgumentEntry.java new file mode 100644 index 0000000000..9f54568df1 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/ConstructorArgumentEntry.java @@ -0,0 +1,59 @@ +/* + * Copyright 2002-2007 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.parsing; + +import org.springframework.util.Assert; + +/** + * {@link ParseState} entry representing a (possibly indexed) + * constructor argument. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + */ +public class ConstructorArgumentEntry implements ParseState.Entry { + + private final int index; + + + /** + * Creates a new instance of the {@link ConstructorArgumentEntry} class + * representing a constructor argument with a (currently) unknown index. + */ + public ConstructorArgumentEntry() { + this.index = -1; + } + + /** + * Creates a new instance of the {@link ConstructorArgumentEntry} class + * representing a constructor argument at the supplied index. + * @param index the index of the constructor argument + * @throws IllegalArgumentException if the supplied index + * is less than zero + */ + public ConstructorArgumentEntry(int index) { + Assert.isTrue(index >= 0, "Constructor argument index must be greater than or equal to zero"); + this.index = index; + } + + + public String toString() { + return "Constructor-arg" + (this.index >= 0 ? " #" + this.index : ""); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/DefaultsDefinition.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/DefaultsDefinition.java new file mode 100644 index 0000000000..4a9e10ff95 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/DefaultsDefinition.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2007 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.parsing; + +import org.springframework.beans.BeanMetadataElement; + +/** + * Marker interface for a defaults definition, + * extending BeanMetadataElement to inherit source exposure. + * + *

Concrete implementations are typically based on 'document defaults', + * for example specified at the root tag level within an XML document. + * + * @author Juergen Hoeller + * @since 2.0.2 + * @see org.springframework.beans.factory.xml.DocumentDefaultsDefinition + * @see ReaderEventListener#defaultsRegistered(DefaultsDefinition) + */ +public interface DefaultsDefinition extends BeanMetadataElement { + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/EmptyReaderEventListener.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/EmptyReaderEventListener.java new file mode 100644 index 0000000000..914280a647 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/EmptyReaderEventListener.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2007 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.parsing; + +/** + * Empty implementation of the ReaderEventListener interface, + * providing no-op implementations of all callback methods. + * + * @author Juergen Hoeller + * @since 2.0 + */ +public class EmptyReaderEventListener implements ReaderEventListener { + + public void defaultsRegistered(DefaultsDefinition defaultsDefinition) { + // no-op + } + + public void componentRegistered(ComponentDefinition componentDefinition) { + // no-op + } + + public void aliasRegistered(AliasDefinition aliasDefinition) { + // no-op + } + + public void importProcessed(ImportDefinition importDefinition) { + // no-op + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/FailFastProblemReporter.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/FailFastProblemReporter.java new file mode 100644 index 0000000000..9b3311f512 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/FailFastProblemReporter.java @@ -0,0 +1,79 @@ +/* + * Copyright 2002-2007 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.parsing; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Simple {@link ProblemReporter} implementation that exhibits fail-fast + * behavior when errors are encountered. + * + *

The first error encountered results in a {@link BeanDefinitionParsingException} + * being thrown. + * + *

Warnings are written to + * {@link #setLogger(org.apache.commons.logging.Log) the log} for this class. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @author Rick Evans + * @since 2.0 + */ +public class FailFastProblemReporter implements ProblemReporter { + + private Log logger = LogFactory.getLog(getClass()); + + + /** + * Set the {@link Log logger} that is to be used to report warnings. + *

If set to null then a default {@link Log logger} set to + * the name of the instance class will be used. + * @param logger the {@link Log logger} that is to be used to report warnings + */ + public void setLogger(Log logger) { + this.logger = (logger != null ? logger : LogFactory.getLog(getClass())); + } + + + /** + * Throws a {@link BeanDefinitionParsingException} detailing the error + * that has occurred. + * @param problem the source of the error + */ + public void fatal(Problem problem) { + throw new BeanDefinitionParsingException(problem); + } + + /** + * Throws a {@link BeanDefinitionParsingException} detailing the error + * that has occurred. + * @param problem the source of the error + */ + public void error(Problem problem) { + throw new BeanDefinitionParsingException(problem); + } + + /** + * Writes the supplied {@link Problem} to the {@link Log} at WARN level. + * @param problem the source of the warning + */ + public void warning(Problem problem) { + this.logger.warn(problem, problem.getRootCause()); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/ImportDefinition.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/ImportDefinition.java new file mode 100644 index 0000000000..4b7a834d08 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/ImportDefinition.java @@ -0,0 +1,84 @@ +/* + * Copyright 2002-2007 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.parsing; + +import org.springframework.beans.BeanMetadataElement; +import org.springframework.core.io.Resource; +import org.springframework.util.Assert; + +/** + * Representation of an import that has been processed during the parsing process. + * + * @author Juergen Hoeller + * @since 2.0 + * @see ReaderEventListener#importProcessed(ImportDefinition) + */ +public class ImportDefinition implements BeanMetadataElement { + + private final String importedResource; + + private final Resource[] actualResources; + + private final Object source; + + + /** + * Create a new ImportDefinition. + * @param importedResource the location of the imported resource + */ + public ImportDefinition(String importedResource) { + this(importedResource, null, null); + } + + /** + * Create a new ImportDefinition. + * @param importedResource the location of the imported resource + * @param source the source object (may be null) + */ + public ImportDefinition(String importedResource, Object source) { + this(importedResource, null, source); + } + + /** + * Create a new ImportDefinition. + * @param importedResource the location of the imported resource + * @param source the source object (may be null) + */ + public ImportDefinition(String importedResource, Resource[] actualResources, Object source) { + Assert.notNull(importedResource, "Imported resource must not be null"); + this.importedResource = importedResource; + this.actualResources = actualResources; + this.source = source; + } + + + /** + * Return the location of the imported resource. + */ + public final String getImportedResource() { + return this.importedResource; + } + + public final Resource[] getActualResources() { + return this.actualResources; + } + + public final Object getSource() { + return this.source; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/Location.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/Location.java new file mode 100644 index 0000000000..c5cbcbecbe --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/Location.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2006 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.parsing; + +import org.springframework.core.io.Resource; +import org.springframework.util.Assert; + +/** + * Class that models an arbitrary location in a {@link Resource resource}. + * + *

Typically used to track the location of problematic or erroneous + * metadata in XML configuration files. For example, a + * {@link #getSource() source} location might be 'The bean defined on + * line 76 of beans.properties has an invalid Class'; another source might + * be the actual DOM Element from a parsed XML {@link org.w3c.dom.Document}; + * or the source object might simply be null. + * + * @author Rob Harrop + * @since 2.0 + */ +public class Location { + + private final Resource resource; + + private final Object source; + + + /** + * Create a new instance of the {@link Location} class. + * @param resource the resource with which this location is associated + */ + public Location(Resource resource) { + this(resource, null); + } + + /** + * Create a new instance of the {@link Location} class. + * @param resource the resource with which this location is associated + * @param source the actual location within the associated resource + * (may be null) + */ + public Location(Resource resource, Object source) { + Assert.notNull(resource, "Resource must not be null"); + this.resource = resource; + this.source = source; + } + + + /** + * Get the resource with which this location is associated. + */ + public Resource getResource() { + return this.resource; + } + + /** + * Get the actual location within the associated {@link #getResource() resource} + * (may be null). + *

See the {@link Location class level javadoc for this class} for examples + * of what the actual type of the returned object may be. + */ + public Object getSource() { + return this.source; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/NullSourceExtractor.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/NullSourceExtractor.java new file mode 100644 index 0000000000..d95cf1ff1c --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/NullSourceExtractor.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2006 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.parsing; + +import org.springframework.core.io.Resource; + +/** + * Simple implementation of {@link SourceExtractor} that returns null + * as the source metadata. + * + *

This is the default implementation and prevents too much metadata from being + * held in memory during normal (non-tooled) runtime usage. + * + * @author Rob Harrop + * @since 2.0 + */ +public class NullSourceExtractor implements SourceExtractor { + + /** + * This implementation simply returns null for any input. + */ + public Object extractSource(Object sourceCandidate, Resource definitionResource) { + return null; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/ParseState.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/ParseState.java new file mode 100644 index 0000000000..9f476b67fe --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/ParseState.java @@ -0,0 +1,119 @@ +/* + * Copyright 2002-2006 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.parsing; + +import java.util.Stack; + +/** + * Simple {@link Stack}-based structure for tracking the logical position during + * a parsing process. {@link Entry entries} are added to the stack at + * each point during the parse phase in a reader-specific manner. + * + *

Calling {@link #toString()} will render a tree-style view of the current logical + * position in the parse phase. This representation is intended for use in + * error messages. + * + * @author Rob Harrop + * @since 2.0 + */ +public final class ParseState { + + /** + * Tab character used when rendering the tree-style representation. + */ + private static final char TAB = '\t'; + + /** + * Internal {@link Stack} storage. + */ + private final Stack state; + + + /** + * Create a new ParseState with an empty {@link Stack}. + */ + public ParseState() { + this.state = new Stack(); + } + + /** + * Create a new ParseState whose {@link Stack} is a {@link Object#clone clone} + * of that of the passed in ParseState. + */ + private ParseState(ParseState other) { + this.state = (Stack) other.state.clone(); + } + + + /** + * Add a new {@link Entry} to the {@link Stack}. + */ + public void push(Entry entry) { + this.state.push(entry); + } + + /** + * Remove an {@link Entry} from the {@link Stack}. + */ + public void pop() { + this.state.pop(); + } + + /** + * Return the {@link Entry} currently at the top of the {@link Stack} or + * null if the {@link Stack} is empty. + */ + public Entry peek() { + return (Entry) (this.state.empty() ? null : this.state.peek()); + } + + /** + * Create a new instance of {@link ParseState} which is an independent snapshot + * of this instance. + */ + public ParseState snapshot() { + return new ParseState(this); + } + + + /** + * Returns a tree-style representation of the current ParseState. + */ + public String toString() { + StringBuffer sb = new StringBuffer(); + for (int x = 0; x < this.state.size(); x++) { + if (x > 0) { + sb.append('\n'); + for (int y = 0; y < x; y++) { + sb.append(TAB); + } + sb.append("-> "); + } + sb.append(this.state.get(x)); + } + return sb.toString(); + } + + + /** + * Marker interface for entries into the {@link ParseState}. + */ + public interface Entry { + + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/PassThroughSourceExtractor.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/PassThroughSourceExtractor.java new file mode 100644 index 0000000000..573244001d --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/PassThroughSourceExtractor.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2006 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.parsing; + +import org.springframework.core.io.Resource; + +/** + * Simple {@link SourceExtractor} implementation that just passes + * the candidate source metadata object through for attachment. + * + *

Using this implementation means that tools will get raw access to the + * underlying configuration source metadata provided by the tool. + * + *

This implementation should not be used in a production + * application since it is likely to keep too much metadata in memory + * (unnecessarily). + * + * @author Rob Harrop + * @since 2.0 + */ +public class PassThroughSourceExtractor implements SourceExtractor { + + /** + * Simply returns the supplied sourceCandidate as-is. + * @param sourceCandidate the source metadata + * @return the supplied sourceCandidate + */ + public Object extractSource(Object sourceCandidate, Resource definingResource) { + return sourceCandidate; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/Problem.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/Problem.java new file mode 100644 index 0000000000..2ffef106f7 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/Problem.java @@ -0,0 +1,128 @@ +/* + * Copyright 2002-2008 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.parsing; + +import org.springframework.util.Assert; + +/** + * Represents a problem with a bean definition configuration. + * Mainly serves as common argument passed into a {@link ProblemReporter}. + * + *

May indicate a potentially fatal problem (an error) or just a warning. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + * @see ProblemReporter + */ +public class Problem { + + private final String message; + + private final Location location; + + private final ParseState parseState; + + private final Throwable rootCause; + + + /** + * Create a new instance of the {@link Problem} class. + * @param message a message detailing the problem + * @param location the location within a bean configuration source that triggered the error + */ + public Problem(String message, Location location) { + this(message, location, null, null); + } + + /** + * Create a new instance of the {@link Problem} class. + * @param message a message detailing the problem + * @param parseState the {@link ParseState} at the time of the error + * @param location the location within a bean configuration source that triggered the error + */ + public Problem(String message, Location location, ParseState parseState) { + this(message, location, parseState, null); + } + + /** + * Create a new instance of the {@link Problem} class. + * @param message a message detailing the problem + * @param rootCause the underlying expection that caused the error (may be null) + * @param parseState the {@link ParseState} at the time of the error + * @param location the location within a bean configuration source that triggered the error + */ + public Problem(String message, Location location, ParseState parseState, Throwable rootCause) { + Assert.notNull(message, "Message must not be null"); + Assert.notNull(location, "Location must not be null"); + this.message = message; + this.location = location; + this.parseState = parseState; + this.rootCause = rootCause; + } + + + /** + * Get the message detailing the problem. + */ + public String getMessage() { + return this.message; + } + + /** + * Get the location within a bean configuration source that triggered the error. + */ + public Location getLocation() { + return this.location; + } + + /** + * Get the description of the bean configuration source that triggered the error, + * as contained within this Problem's Location object. + * @see #getLocation() + */ + public String getResourceDescription() { + return getLocation().getResource().getDescription(); + } + + /** + * Get the {@link ParseState} at the time of the error (may be null). + */ + public ParseState getParseState() { + return this.parseState; + } + + /** + * Get the underlying expection that caused the error (may be null). + */ + public Throwable getRootCause() { + return this.rootCause; + } + + + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("Configuration problem: "); + sb.append(getMessage()); + sb.append("\nOffending resource: ").append(getResourceDescription()); + if (getParseState() != null) { + sb.append('\n').append(getParseState()); + } + return sb.toString(); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/ProblemReporter.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/ProblemReporter.java new file mode 100644 index 0000000000..a9e1bedbe9 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/ProblemReporter.java @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2007 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.parsing; + +/** + * SPI interface allowing tools and other external processes to handle errors + * and warnings reported during bean definition parsing. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + * @see Problem + */ +public interface ProblemReporter { + + /** + * Called when a fatal error is encountered during the parsing process. + *

Implementations must treat the given problem as fatal, + * i.e. they have to eventually raise an exception. + * @param problem the source of the error (never null) + */ + void fatal(Problem problem); + + /** + * Called when an error is encountered during the parsing process. + *

Implementations may choose to treat errors as fatal. + * @param problem the source of the error (never null) + */ + void error(Problem problem); + + /** + * Called when a warning is raised during the parsing process. + *

Warnings are never considered to be fatal. + * @param problem the source of the warning (never null) + */ + void warning(Problem problem); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/PropertyEntry.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/PropertyEntry.java new file mode 100644 index 0000000000..403c4fa2fa --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/PropertyEntry.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2006 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.parsing; + +import org.springframework.util.StringUtils; + +/** + * {@link ParseState} entry representing a JavaBean property. + * + * @author Rob Harrop + * @since 2.0 + */ +public class PropertyEntry implements ParseState.Entry { + + private final String name; + + + /** + * Creates a new instance of the {@link PropertyEntry} class. + * @param name the name of the JavaBean property represented by this instance + * @throws IllegalArgumentException if the supplied name is null + * or consists wholly of whitespace + */ + public PropertyEntry(String name) { + if (!StringUtils.hasText(name)) { + throw new IllegalArgumentException("Invalid property name '" + name + "'."); + } + this.name = name; + } + + + public String toString() { + return "Property '" + this.name + "'"; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/QualifierEntry.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/QualifierEntry.java new file mode 100644 index 0000000000..de7f41be0e --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/QualifierEntry.java @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2007 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.parsing; + +import org.springframework.util.StringUtils; + +/** + * {@link ParseState} entry representing an autowire candidate qualifier. + * + * @author Mark Fisher + * @since 2.5 + */ +public class QualifierEntry implements ParseState.Entry { + + private String typeName; + + + public QualifierEntry(String typeName) { + if (!StringUtils.hasText(typeName)) { + throw new IllegalArgumentException("Invalid qualifier type '" + typeName + "'."); + } + this.typeName = typeName; + } + + public String toString() { + return "Qualifier '" + this.typeName + "'"; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/ReaderContext.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/ReaderContext.java new file mode 100644 index 0000000000..64689f6486 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/ReaderContext.java @@ -0,0 +1,135 @@ +/* + * Copyright 2002-2007 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.parsing; + +import org.springframework.core.io.Resource; + +/** + * Context that gets passed along a bean definition reading process, + * encapsulating all relevant configuration as well as state. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + */ +public class ReaderContext { + + private final Resource resource; + + private final ProblemReporter problemReporter; + + private final ReaderEventListener eventListener; + + private final SourceExtractor sourceExtractor; + + + public ReaderContext(Resource resource, ProblemReporter problemReporter, + ReaderEventListener eventListener, SourceExtractor sourceExtractor) { + + this.resource = resource; + this.problemReporter = problemReporter; + this.eventListener = eventListener; + this.sourceExtractor = sourceExtractor; + } + + public final Resource getResource() { + return this.resource; + } + + + public void fatal(String message, Object source) { + fatal(message, source, null, null); + } + + public void fatal(String message, Object source, Throwable ex) { + fatal(message, source, null, ex); + } + + public void fatal(String message, Object source, ParseState parseState) { + fatal(message, source, parseState, null); + } + + public void fatal(String message, Object source, ParseState parseState, Throwable cause) { + Location location = new Location(getResource(), source); + this.problemReporter.fatal(new Problem(message, location, parseState, cause)); + } + + public void error(String message, Object source) { + error(message, source, null, null); + } + + public void error(String message, Object source, Throwable ex) { + error(message, source, null, ex); + } + + public void error(String message, Object source, ParseState parseState) { + error(message, source, parseState, null); + } + + public void error(String message, Object source, ParseState parseState, Throwable cause) { + Location location = new Location(getResource(), source); + this.problemReporter.error(new Problem(message, location, parseState, cause)); + } + + public void warning(String message, Object source) { + warning(message, source, null, null); + } + + public void warning(String message, Object source, Throwable ex) { + warning(message, source, null, ex); + } + + public void warning(String message, Object source, ParseState parseState) { + warning(message, source, parseState, null); + } + + public void warning(String message, Object source, ParseState parseState, Throwable cause) { + Location location = new Location(getResource(), source); + this.problemReporter.warning(new Problem(message, location, parseState, cause)); + } + + + public void fireDefaultsRegistered(DefaultsDefinition defaultsDefinition) { + this.eventListener.defaultsRegistered(defaultsDefinition); + } + + public void fireComponentRegistered(ComponentDefinition componentDefinition) { + this.eventListener.componentRegistered(componentDefinition); + } + + public void fireAliasRegistered(String beanName, String alias, Object source) { + this.eventListener.aliasRegistered(new AliasDefinition(beanName, alias, source)); + } + + public void fireImportProcessed(String importedResource, Object source) { + this.eventListener.importProcessed(new ImportDefinition(importedResource, source)); + } + + public void fireImportProcessed(String importedResource, Resource[] actualResources, Object source) { + this.eventListener.importProcessed(new ImportDefinition(importedResource, actualResources, source)); + } + + + public SourceExtractor getSourceExtractor() { + return this.sourceExtractor; + } + + public Object extractSource(Object sourceCandidate) { + return this.sourceExtractor.extractSource(sourceCandidate, this.resource); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/ReaderEventListener.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/ReaderEventListener.java new file mode 100644 index 0000000000..545a732017 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/ReaderEventListener.java @@ -0,0 +1,58 @@ +/* + * Copyright 2002-2007 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.parsing; + +import java.util.EventListener; + +/** + * Interface that receives callbacks for component, alias and import + * registrations during a bean definition reading process. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + * @see ReaderContext + */ +public interface ReaderEventListener extends EventListener { + + /** + * Notification that the given defaults has been registered. + * @param defaultsDefinition a descriptor for the defaults + * @see org.springframework.beans.factory.xml.DocumentDefaultsDefinition + */ + void defaultsRegistered(DefaultsDefinition defaultsDefinition); + + /** + * Notification that the given component has been registered. + * @param componentDefinition a descriptor for the new component + * @see BeanComponentDefinition + */ + void componentRegistered(ComponentDefinition componentDefinition); + + /** + * Notification that the given alias has been registered. + * @param aliasDefinition a descriptor for the new alias + */ + void aliasRegistered(AliasDefinition aliasDefinition); + + /** + * Notification that the given import has been processed. + * @param importDefinition a descriptor for the import + */ + void importProcessed(ImportDefinition importDefinition); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/SourceExtractor.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/SourceExtractor.java new file mode 100644 index 0000000000..55988c3726 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/SourceExtractor.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2006 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.parsing; + +import org.springframework.core.io.Resource; + +/** + * Simple strategy allowing tools to control how source metadata is attached + * to the bean definition metadata. + * + *

Configuration parsers may provide the ability to attach + * source metadata during the parse phase. They will offer this metadata in a + * generic format which can be further modified by a {@link SourceExtractor} + * before being attached to the bean definition metadata. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + * @see org.springframework.beans.BeanMetadataElement#getSource() + * @see org.springframework.beans.factory.config.BeanDefinition + */ +public interface SourceExtractor { + + /** + * Extract the source metadata from the candidate object supplied + * by the configuration parser. + * @param sourceCandidate the original source metadata (never null) + * @param definingResource the resource that defines the given source object + * (may be null) + * @return the source metadata object to store (may be null) + */ + Object extractSource(Object sourceCandidate, Resource definingResource); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/package.html b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/package.html new file mode 100644 index 0000000000..e073217ff4 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/package.html @@ -0,0 +1,7 @@ + + + +Support infrastructure for bean definition parsing. + + + diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/serviceloader/AbstractServiceLoaderBasedFactoryBean.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/serviceloader/AbstractServiceLoaderBasedFactoryBean.java new file mode 100644 index 0000000000..6be2b2f3a6 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/serviceloader/AbstractServiceLoaderBasedFactoryBean.java @@ -0,0 +1,78 @@ +/* + * Copyright 2002-2007 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.serviceloader; + +import java.util.ServiceLoader; + +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.config.AbstractFactoryBean; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * Abstract base class for FactoryBeans operating on the + * JDK 1.6 {@link java.util.ServiceLoader} facility. + * + * @author Juergen Hoeller + * @since 2.5 + * @see java.util.ServiceLoader + */ +public abstract class AbstractServiceLoaderBasedFactoryBean extends AbstractFactoryBean + implements BeanClassLoaderAware { + + private Class serviceType; + + private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); + + + /** + * Specify the desired service type (typically the service's public API). + */ + public void setServiceType(Class serviceType) { + this.serviceType = serviceType; + } + + /** + * Return the desired service type. + */ + public Class getServiceType() { + return this.serviceType; + } + + public void setBeanClassLoader(ClassLoader beanClassLoader) { + this.beanClassLoader = beanClassLoader; + } + + + /** + * Delegates to {@link #getObjectToExpose(java.util.ServiceLoader)}. + * @return the object to expose + */ + protected Object createInstance() { + Assert.notNull(getServiceType(), "Property 'serviceType' is required"); + return getObjectToExpose(ServiceLoader.load(getServiceType(), this.beanClassLoader)); + } + + /** + * Determine the actual object to expose for the given ServiceLoader. + *

Left to concrete subclasses. + * @param serviceLoader the ServiceLoader for the configured service class + * @return the object to expose + */ + protected abstract Object getObjectToExpose(ServiceLoader serviceLoader); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/serviceloader/ServiceFactoryBean.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/serviceloader/ServiceFactoryBean.java new file mode 100644 index 0000000000..33bee8bfb7 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/serviceloader/ServiceFactoryBean.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2007 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.serviceloader; + +import java.util.Iterator; +import java.util.ServiceLoader; + +import org.springframework.beans.factory.BeanClassLoaderAware; + +/** + * {@link org.springframework.beans.factory.FactoryBean} that exposes the + * 'primary' service for the configured service class, obtained through + * the JDK 1.6 {@link java.util.ServiceLoader} facility. + * + * @author Juergen Hoeller + * @since 2.5 + * @see java.util.ServiceLoader + */ +public class ServiceFactoryBean extends AbstractServiceLoaderBasedFactoryBean implements BeanClassLoaderAware { + + protected Object getObjectToExpose(ServiceLoader serviceLoader) { + Iterator it = serviceLoader.iterator(); + if (!it.hasNext()) { + throw new IllegalStateException( + "ServiceLoader could not find service for type [" + getServiceType() + "]"); + } + return it.next(); + } + + public Class getObjectType() { + return getServiceType(); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/serviceloader/ServiceListFactoryBean.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/serviceloader/ServiceListFactoryBean.java new file mode 100644 index 0000000000..ae8b82deaa --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/serviceloader/ServiceListFactoryBean.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2007 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.serviceloader; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.ServiceLoader; + +import org.springframework.beans.factory.BeanClassLoaderAware; + +/** + * {@link org.springframework.beans.factory.FactoryBean} that exposes all + * services for the configured service class, represented as a List of service objects, + * obtained through the JDK 1.6 {@link java.util.ServiceLoader} facility. + * + * @author Juergen Hoeller + * @since 2.5 + * @see java.util.ServiceLoader + */ +public class ServiceListFactoryBean extends AbstractServiceLoaderBasedFactoryBean implements BeanClassLoaderAware { + + protected Object getObjectToExpose(ServiceLoader serviceLoader) { + List result = new LinkedList(); + Iterator it = serviceLoader.iterator(); + while (it.hasNext()) { + result.add(it.next()); + } + return result; + } + + public Class getObjectType() { + return List.class; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/serviceloader/ServiceLoaderFactoryBean.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/serviceloader/ServiceLoaderFactoryBean.java new file mode 100644 index 0000000000..acf8f2526a --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/serviceloader/ServiceLoaderFactoryBean.java @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2007 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.serviceloader; + +import java.util.ServiceLoader; + +import org.springframework.beans.factory.BeanClassLoaderAware; + +/** + * {@link org.springframework.beans.factory.FactoryBean} that exposes the + * JDK 1.6 {@link java.util.ServiceLoader} for the configured service class. + * + * @author Juergen Hoeller + * @since 2.5 + * @see java.util.ServiceLoader + */ +public class ServiceLoaderFactoryBean extends AbstractServiceLoaderBasedFactoryBean implements BeanClassLoaderAware { + + protected Object getObjectToExpose(ServiceLoader serviceLoader) { + return serviceLoader; + } + + public Class getObjectType() { + return ServiceLoader.class; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/serviceloader/package.html b/org.springframework.beans/src/main/java/org/springframework/beans/factory/serviceloader/package.html new file mode 100644 index 0000000000..82cd191061 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/serviceloader/package.html @@ -0,0 +1,7 @@ + + + +Support package for the JDK 1.6 ServiceLoader facility. + + + diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java new file mode 100644 index 0000000000..95235ccdca --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -0,0 +1,1440 @@ +/* + * Copyright 2002-2008 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.beans.PropertyDescriptor; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.BeanWrapperImpl; +import org.springframework.beans.BeansException; +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.PropertyAccessorUtils; +import org.springframework.beans.PropertyValue; +import org.springframework.beans.PropertyValues; +import org.springframework.beans.TypeConverter; +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.BeanCurrentlyInCreationException; +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.UnsatisfiedDependencyException; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.beans.factory.config.DependencyDescriptor; +import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor; +import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor; +import org.springframework.beans.factory.config.TypedStringValue; +import org.springframework.core.CollectionFactory; +import org.springframework.core.MethodParameter; +import org.springframework.core.PriorityOrdered; +import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; + +/** + * Abstract bean factory superclass that implements default bean creation, + * with the full capabilities specified by the {@link RootBeanDefinition} class. + * Implements the {@link org.springframework.beans.factory.config.AutowireCapableBeanFactory} + * interface in addition to AbstractBeanFactory's {@link #createBean} method. + * + *

Provides bean creation (with constructor resolution), property population, + * wiring (including autowiring), and initialization. Handles runtime bean + * references, resolves managed collections, calls initialization methods, etc. + * Supports autowiring constructors, properties by name, and properties by type. + * + *

The main template method to be implemented by subclasses is + * {@link #resolveDependency(DependencyDescriptor, String, Set, TypeConverter)}, + * used for autowiring by type. In case of a factory which is capable of searching + * its bean definitions, matching beans will typically be implemented through such + * a search. For other factory styles, simplified matching algorithms can be implemented. + * + *

Note that this class does not assume or implement bean definition + * registry capabilities. See {@link DefaultListableBeanFactory} for an implementation + * of the {@link org.springframework.beans.factory.ListableBeanFactory} and + * {@link BeanDefinitionRegistry} interfaces, which represent the API and SPI + * view of such a factory, respectively. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @author Rob Harrop + * @author Mark Fisher + * @since 13.02.2004 + * @see RootBeanDefinition + * @see DefaultListableBeanFactory + * @see BeanDefinitionRegistry + */ +public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory + implements AutowireCapableBeanFactory { + + private InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy(); + + /** Whether to automatically try to resolve circular references between beans */ + private boolean allowCircularReferences = true; + + /** + * Whether to resort to injecting a raw bean instance in case of circular reference, + * even if the injected bean eventually got wrapped. + */ + private boolean allowRawInjectionDespiteWrapping = false; + + /** + * Dependency types to ignore on dependency check and autowire, as Set of + * Class objects: for example, String. Default is none. + */ + private final Set ignoredDependencyTypes = new HashSet(); + + /** + * Dependency interfaces to ignore on dependency check and autowire, as Set of + * Class objects. By default, only the BeanFactory interface is ignored. + */ + private final Set ignoredDependencyInterfaces = new HashSet(); + + /** Cache of unfinished FactoryBean instances: FactoryBean name --> BeanWrapper */ + private final Map factoryBeanInstanceCache = CollectionFactory.createConcurrentMapIfPossible(16); + + /** Cache of filtered PropertyDescriptors: bean Class -> PropertyDescriptor array */ + private final Map filteredPropertyDescriptorsCache = new HashMap(); + + + /** + * Create a new AbstractAutowireCapableBeanFactory. + */ + public AbstractAutowireCapableBeanFactory() { + super(); + ignoreDependencyInterface(BeanNameAware.class); + ignoreDependencyInterface(BeanFactoryAware.class); + ignoreDependencyInterface(BeanClassLoaderAware.class); + } + + /** + * Create a new AbstractAutowireCapableBeanFactory with the given parent. + * @param parentBeanFactory parent bean factory, or null if none + */ + public AbstractAutowireCapableBeanFactory(BeanFactory parentBeanFactory) { + this(); + setParentBeanFactory(parentBeanFactory); + } + + + /** + * Set the instantiation strategy to use for creating bean instances. + * Default is CglibSubclassingInstantiationStrategy. + * @see CglibSubclassingInstantiationStrategy + */ + public void setInstantiationStrategy(InstantiationStrategy instantiationStrategy) { + this.instantiationStrategy = instantiationStrategy; + } + + /** + * Return the instantiation strategy to use for creating bean instances. + */ + protected InstantiationStrategy getInstantiationStrategy() { + return this.instantiationStrategy; + } + + /** + * Set whether to allow circular references between beans - and automatically + * try to resolve them. + *

Note that circular reference resolution means that one of the involved beans + * will receive a reference to another bean that is not fully initialized yet. + * This can lead to subtle and not-so-subtle side effects on initialization; + * it does work fine for many scenarios, though. + *

Default is "true". Turn this off to throw an exception when encountering + * a circular reference, disallowing them completely. + *

NOTE: It is generally recommended to not rely on circular references + * between your beans. Refactor your application logic to have the two beans + * involved delegate to a third bean that encapsulates their common logic. + */ + public void setAllowCircularReferences(boolean allowCircularReferences) { + this.allowCircularReferences = allowCircularReferences; + } + + /** + * Set whether to allow the raw injection of a bean instance into some other + * bean's property, despite the injected bean eventually getting wrapped + * (for example, through AOP auto-proxying). + *

This will only be used as a last resort in case of a circular reference + * that cannot be resolved otherwise: essentially, preferring a raw instance + * getting injected over a failure of the entire bean wiring process. + *

Default is "false", as of Spring 2.0. Turn this on to allow for non-wrapped + * raw beans injected into some of your references, which was Spring 1.2's + * (arguably unclean) default behavior. + *

NOTE: It is generally recommended to not rely on circular references + * between your beans, in particular with auto-proxying involved. + * @see #setAllowCircularReferences + */ + public void setAllowRawInjectionDespiteWrapping(boolean allowRawInjectionDespiteWrapping) { + this.allowRawInjectionDespiteWrapping = allowRawInjectionDespiteWrapping; + } + + /** + * Ignore the given dependency type for autowiring: + * for example, String. Default is none. + */ + public void ignoreDependencyType(Class type) { + this.ignoredDependencyTypes.add(type); + } + + /** + * Ignore the given dependency interface for autowiring. + *

This will typically be used by application contexts to register + * dependencies that are resolved in other ways, like BeanFactory through + * BeanFactoryAware or ApplicationContext through ApplicationContextAware. + *

By default, only the BeanFactoryAware interface is ignored. + * For further types to ignore, invoke this method for each type. + * @see org.springframework.beans.factory.BeanFactoryAware + * @see org.springframework.context.ApplicationContextAware + */ + public void ignoreDependencyInterface(Class ifc) { + this.ignoredDependencyInterfaces.add(ifc); + } + + + public void copyConfigurationFrom(ConfigurableBeanFactory otherFactory) { + super.copyConfigurationFrom(otherFactory); + if (otherFactory instanceof AbstractAutowireCapableBeanFactory) { + AbstractAutowireCapableBeanFactory otherAutowireFactory = + (AbstractAutowireCapableBeanFactory) otherFactory; + this.instantiationStrategy = otherAutowireFactory.instantiationStrategy; + this.allowCircularReferences = otherAutowireFactory.allowCircularReferences; + this.ignoredDependencyTypes.addAll(otherAutowireFactory.ignoredDependencyTypes); + this.ignoredDependencyInterfaces.addAll(otherAutowireFactory.ignoredDependencyInterfaces); + } + } + + + //------------------------------------------------------------------------- + // Typical methods for creating and populating external bean instances + //------------------------------------------------------------------------- + + public Object createBean(Class beanClass) throws BeansException { + // Use prototype bean definition, to avoid registering bean as dependent bean. + RootBeanDefinition bd = new RootBeanDefinition(beanClass); + bd.setScope(SCOPE_PROTOTYPE); + return createBean(beanClass.getName(), bd, null); + } + + public void autowireBean(Object existingBean) { + // Use non-singleton bean definition, to avoid registering bean as dependent bean. + RootBeanDefinition bd = new RootBeanDefinition(ClassUtils.getUserClass(existingBean)); + bd.setScope(BeanDefinition.SCOPE_PROTOTYPE); + BeanWrapper bw = new BeanWrapperImpl(existingBean); + initBeanWrapper(bw); + populateBean(bd.getBeanClass().getName(), bd, bw); + } + + public Object configureBean(Object existingBean, String beanName) throws BeansException { + markBeanAsCreated(beanName); + BeanDefinition mbd = getMergedBeanDefinition(beanName); + RootBeanDefinition bd = null; + if (mbd instanceof RootBeanDefinition) { + RootBeanDefinition rbd = (RootBeanDefinition) mbd; + if (SCOPE_PROTOTYPE.equals(rbd.getScope())) { + bd = rbd; + } + } + if (bd == null) { + bd = new RootBeanDefinition(mbd); + bd.setScope(BeanDefinition.SCOPE_PROTOTYPE); + } + BeanWrapper bw = new BeanWrapperImpl(existingBean); + initBeanWrapper(bw); + populateBean(beanName, bd, bw); + return initializeBean(beanName, existingBean, bd); + } + + public Object resolveDependency(DependencyDescriptor descriptor, String beanName) throws BeansException { + return resolveDependency(descriptor, beanName, null, null); + } + + + //------------------------------------------------------------------------- + // Specialized methods for fine-grained control over the bean lifecycle + //------------------------------------------------------------------------- + + public Object createBean(Class beanClass, int autowireMode, boolean dependencyCheck) throws BeansException { + // Use non-singleton bean definition, to avoid registering bean as dependent bean. + RootBeanDefinition bd = new RootBeanDefinition(beanClass, autowireMode, dependencyCheck); + bd.setScope(BeanDefinition.SCOPE_PROTOTYPE); + return createBean(beanClass.getName(), bd, null); + } + + public Object autowire(Class beanClass, int autowireMode, boolean dependencyCheck) throws BeansException { + // Use non-singleton bean definition, to avoid registering bean as dependent bean. + RootBeanDefinition bd = new RootBeanDefinition(beanClass, autowireMode, dependencyCheck); + bd.setScope(BeanDefinition.SCOPE_PROTOTYPE); + if (bd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR) { + return autowireConstructor(beanClass.getName(), bd, null, null).getWrappedInstance(); + } + else { + Object bean = getInstantiationStrategy().instantiate(bd, null, this); + populateBean(beanClass.getName(), bd, new BeanWrapperImpl(bean)); + return bean; + } + } + + public void autowireBeanProperties(Object existingBean, int autowireMode, boolean dependencyCheck) + throws BeansException { + + if (autowireMode == AUTOWIRE_CONSTRUCTOR) { + throw new IllegalArgumentException("AUTOWIRE_CONSTRUCTOR not supported for existing bean instance"); + } + // Use non-singleton bean definition, to avoid registering bean as dependent bean. + RootBeanDefinition bd = + new RootBeanDefinition(ClassUtils.getUserClass(existingBean), autowireMode, dependencyCheck); + bd.setScope(BeanDefinition.SCOPE_PROTOTYPE); + BeanWrapper bw = new BeanWrapperImpl(existingBean); + initBeanWrapper(bw); + populateBean(bd.getBeanClass().getName(), bd, bw); + } + + public void applyBeanPropertyValues(Object existingBean, String beanName) throws BeansException { + markBeanAsCreated(beanName); + BeanDefinition bd = getMergedBeanDefinition(beanName); + BeanWrapper bw = new BeanWrapperImpl(existingBean); + initBeanWrapper(bw); + applyPropertyValues(beanName, bd, bw, bd.getPropertyValues()); + } + + public Object initializeBean(Object existingBean, String beanName) { + return initializeBean(beanName, existingBean, null); + } + + public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName) + throws BeansException { + + Object result = existingBean; + for (Iterator it = getBeanPostProcessors().iterator(); it.hasNext();) { + BeanPostProcessor beanProcessor = (BeanPostProcessor) it.next(); + result = beanProcessor.postProcessBeforeInitialization(result, beanName); + } + return result; + } + + public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) + throws BeansException { + + Object result = existingBean; + for (Iterator it = getBeanPostProcessors().iterator(); it.hasNext();) { + BeanPostProcessor beanProcessor = (BeanPostProcessor) it.next(); + result = beanProcessor.postProcessAfterInitialization(result, beanName); + } + return result; + } + + + //--------------------------------------------------------------------- + // Implementation of relevant AbstractBeanFactory template methods + //--------------------------------------------------------------------- + + /** + * Central method of this class: creates a bean instance, + * populates the bean instance, applies post-processors, etc. + * @see #doCreateBean + */ + protected Object createBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) + throws BeanCreationException { + + AccessControlContext acc = AccessController.getContext(); + return AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + if (logger.isDebugEnabled()) { + logger.debug("Creating instance of bean '" + beanName + "'"); + } + // Make sure bean class is actually resolved at this point. + resolveBeanClass(mbd, beanName); + + // Prepare method overrides. + try { + mbd.prepareMethodOverrides(); + } + catch (BeanDefinitionValidationException ex) { + throw new BeanDefinitionStoreException(mbd.getResourceDescription(), + beanName, "Validation of method overrides failed", ex); + } + + try { + // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance. + Object bean = resolveBeforeInstantiation(beanName, mbd); + if (bean != null) { + return bean; + } + } + catch (Throwable ex) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "BeanPostProcessor before instantiation of bean failed", ex); + } + + Object beanInstance = doCreateBean(beanName, mbd, args); + if (logger.isDebugEnabled()) { + logger.debug("Finished creating instance of bean '" + beanName + "'"); + } + return beanInstance; + } + }, acc); + } + + /** + * Actually create the specified bean. Pre-creation processing has already happened + * at this point, e.g. checking postProcessBeforeInstantiation callbacks. + *

Differentiates between default bean instantiation, use of a + * factory method, and autowiring a constructor. + * @param beanName the name of the bean + * @param mbd the merged bean definition for the bean + * @param args arguments to use if creating a prototype using explicit arguments to a + * static factory method. This parameter must be null except in this case. + * @return a new instance of the bean + * @throws BeanCreationException if the bean could not be created + * @see #instantiateBean + * @see #instantiateUsingFactoryMethod + * @see #autowireConstructor + */ + protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) { + // Instantiate the bean. + BeanWrapper instanceWrapper = null; + if (mbd.isSingleton()) { + instanceWrapper = (BeanWrapper) this.factoryBeanInstanceCache.remove(beanName); + } + if (instanceWrapper == null) { + instanceWrapper = createBeanInstance(beanName, mbd, args); + } + final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null); + Class beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null); + + // Allow post-processors to modify the merged bean definition. + synchronized (mbd.postProcessingLock) { + if (!mbd.postProcessed) { + applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); + mbd.postProcessed = true; + } + } + + // Eagerly cache singletons to be able to resolve circular references + // even when triggered by lifecycle interfaces like BeanFactoryAware. + boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && + isSingletonCurrentlyInCreation(beanName)); + if (earlySingletonExposure) { + if (logger.isDebugEnabled()) { + logger.debug("Eagerly caching bean '" + beanName + + "' to allow for resolving potential circular references"); + } + addSingletonFactory(beanName, new ObjectFactory() { + public Object getObject() throws BeansException { + return getEarlyBeanReference(beanName, mbd, bean); + } + }); + } + + // Initialize the bean instance. + Object exposedObject = bean; + try { + populateBean(beanName, mbd, instanceWrapper); + exposedObject = initializeBean(beanName, exposedObject, mbd); + } + catch (Throwable ex) { + if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) { + throw (BeanCreationException) ex; + } + else { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex); + } + } + + if (earlySingletonExposure) { + Object earlySingletonReference = getSingleton(beanName, false); + if (earlySingletonReference != null) { + if (exposedObject == bean) { + exposedObject = earlySingletonReference; + } + else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { + String[] dependentBeans = getDependentBeans(beanName); + Set actualDependentBeans = new LinkedHashSet(dependentBeans.length); + for (int i = 0; i < dependentBeans.length; i++) { + String dependentBean = dependentBeans[i]; + if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) { + actualDependentBeans.add(dependentBean); + } + } + if (!actualDependentBeans.isEmpty()) { + throw new BeanCurrentlyInCreationException(beanName, + "Bean with name '" + beanName + "' has been injected into other beans [" + + StringUtils.collectionToCommaDelimitedString(actualDependentBeans) + + "] in its raw version as part of a circular reference, but has eventually been " + + "wrapped. This means that said other beans do not use the final version of the " + + "bean. This is often the result of over-eager type matching - consider using " + + "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example."); + } + } + } + } + + // Register bean as disposable. + registerDisposableBeanIfNecessary(beanName, bean, mbd); + + return exposedObject; + } + + protected Class predictBeanType(String beanName, RootBeanDefinition mbd, Class[] typesToMatch) { + Class beanClass = null; + if (mbd.getFactoryMethodName() != null) { + beanClass = getTypeForFactoryMethod(beanName, mbd, typesToMatch); + } + else { + beanClass = resolveBeanClass(mbd, beanName, typesToMatch); + } + // Apply SmartInstantiationAwareBeanPostProcessors to predict the + // eventual type after a before-instantiation shortcut. + if (beanClass != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { + for (Iterator it = getBeanPostProcessors().iterator(); it.hasNext(); ) { + BeanPostProcessor bp = (BeanPostProcessor) it.next(); + if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { + SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; + Class processedType = ibp.predictBeanType(beanClass, beanName); + if (processedType != null) { + return processedType; + } + } + } + } + return beanClass; + } + + /** + * Determine the bean type for the given bean definition which is based on + * a factory method. Only called if there is no singleton instance registered + * for the target bean already. + *

This implementation determines the type matching {@link #createBean}'s + * different creation strategies. As far as possible, we'll perform static + * type checking to avoid creation of the target bean. + * @param beanName the name of the bean (for error handling purposes) + * @param mbd the merged bean definition for the bean + * @param typesToMatch the types to match in case of internal type matching purposes + * (also signals that the returned Class will never be exposed to application code) + * @return the type for the bean if determinable, or null else + * @see #createBean + */ + protected Class getTypeForFactoryMethod(String beanName, RootBeanDefinition mbd, Class[] typesToMatch) { + Class factoryClass = null; + boolean isStatic = true; + + String factoryBeanName = mbd.getFactoryBeanName(); + if (factoryBeanName != null) { + if (factoryBeanName.equals(beanName)) { + throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName, + "factory-bean reference points back to the same bean definition"); + } + // Check declared factory method return type on factory class. + factoryClass = getType(factoryBeanName); + isStatic = false; + } + else { + // Check declared factory method return type on bean class. + factoryClass = resolveBeanClass(mbd, beanName, typesToMatch); + } + + if (factoryClass == null) { + return null; + } + + // If all factory methods have the same return type, return that type. + // Can't clearly figure out exact method due to type converting / autowiring! + int minNrOfArgs = mbd.getConstructorArgumentValues().getArgumentCount(); + Method[] candidates = ReflectionUtils.getAllDeclaredMethods(factoryClass); + Set returnTypes = new HashSet(1); + for (int i = 0; i < candidates.length; i++) { + Method factoryMethod = candidates[i]; + if (Modifier.isStatic(factoryMethod.getModifiers()) == isStatic && + factoryMethod.getName().equals(mbd.getFactoryMethodName()) && + factoryMethod.getParameterTypes().length >= minNrOfArgs) { + returnTypes.add(factoryMethod.getReturnType()); + } + } + + if (returnTypes.size() == 1) { + // Clear return type found: all factory methods return same type. + return (Class) returnTypes.iterator().next(); + } + else { + // Ambiguous return types found: return null to indicate "not determinable". + return null; + } + } + + /** + * This implementation checks the FactoryBean's getObjectType method + * on a plain instance of the FactoryBean, without bean properties applied yet. + * If this doesn't return a type yet, a full creation of the FactoryBean is + * used as fallback (through delegation to the superclass's implementation). + *

The shortcut check for a FactoryBean is only applied in case of a singleton + * FactoryBean. If the FactoryBean instance itself is not kept as singleton, + * it will be fully created to check the type of its exposed object. + */ + protected Class getTypeForFactoryBean(String beanName, RootBeanDefinition mbd) { + FactoryBean fb = (mbd.isSingleton() ? + getSingletonFactoryBeanForTypeCheck(beanName, mbd) : + getNonSingletonFactoryBeanForTypeCheck(beanName, mbd)); + + if (fb != null) { + // Try to obtain the FactoryBean's object type from this early stage of the instance. + Class objectType = getTypeForFactoryBean(fb); + if (objectType != null) { + return objectType; + } + } + + // No type found - fall back to full creation of the FactoryBean instance. + return super.getTypeForFactoryBean(beanName, mbd); + } + + /** + * Obtain a reference for early access to the specified bean, + * typically for the purpose of resolving a circular reference. + * @param beanName the name of the bean (for error handling purposes) + * @param mbd the merged bean definition for the bean + * @param bean the raw bean instance + * @return the object to expose as bean reference + */ + protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { + Object exposedObject = bean; + if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { + for (Iterator it = getBeanPostProcessors().iterator(); it.hasNext(); ) { + BeanPostProcessor bp = (BeanPostProcessor) it.next(); + if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { + SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; + exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); + } + } + } + return exposedObject; + } + + + //--------------------------------------------------------------------- + // Implementation methods + //--------------------------------------------------------------------- + + /** + * Obtain a "shortcut" singleton FactoryBean instance to use for a + * getObjectType() call, without full initialization + * of the FactoryBean. + * @param beanName the name of the bean + * @param mbd the bean definition for the bean + * @return the FactoryBean instance, or null to indicate + * that we couldn't obtain a shortcut FactoryBean instance + */ + private FactoryBean getSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd) { + synchronized (getSingletonMutex()) { + BeanWrapper bw = (BeanWrapper) this.factoryBeanInstanceCache.get(beanName); + if (bw != null) { + return (FactoryBean) bw.getWrappedInstance(); + } + if (isSingletonCurrentlyInCreation(beanName)) { + return null; + } + Object instance = null; + try { + // Mark this bean as currently in creation, even if just partially. + beforeSingletonCreation(beanName); + // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance. + instance = resolveBeforeInstantiation(beanName, mbd); + if (instance == null) { + bw = createBeanInstance(beanName, mbd, null); + instance = bw.getWrappedInstance(); + } + } + finally { + // Finished partial creation of this bean. + afterSingletonCreation(beanName); + } + FactoryBean fb = getFactoryBean(beanName, instance); + if (bw != null) { + this.factoryBeanInstanceCache.put(beanName, bw); + } + return fb; + } + } + + /** + * Obtain a "shortcut" non-singleton FactoryBean instance to use for a + * getObjectType() call, without full initialization + * of the FactoryBean. + * @param beanName the name of the bean + * @param mbd the bean definition for the bean + * @return the FactoryBean instance, or null to indicate + * that we couldn't obtain a shortcut FactoryBean instance + */ + private FactoryBean getNonSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd) { + if (isPrototypeCurrentlyInCreation(beanName)) { + return null; + } + Object instance = null; + try { + // Mark this bean as currently in creation, even if just partially. + beforePrototypeCreation(beanName); + // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance. + instance = resolveBeforeInstantiation(beanName, mbd); + if (instance == null) { + BeanWrapper bw = createBeanInstance(beanName, mbd, null); + instance = bw.getWrappedInstance(); + } + } + finally { + // Finished partial creation of this bean. + afterPrototypeCreation(beanName); + } + return getFactoryBean(beanName, instance); + } + + /** + * Apply MergedBeanDefinitionPostProcessors to the specified bean definition, + * invoking their postProcessMergedBeanDefinition methods. + * @param mbd the merged bean definition for the bean + * @param beanType the actual type of the managed bean instance + * @param beanName the name of the bean + * @throws BeansException if any post-processing failed + * @see MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition + */ + protected void applyMergedBeanDefinitionPostProcessors(RootBeanDefinition mbd, Class beanType, String beanName) + throws BeansException { + + for (Iterator it = getBeanPostProcessors().iterator(); it.hasNext();) { + BeanPostProcessor beanProcessor = (BeanPostProcessor) it.next(); + if (beanProcessor instanceof MergedBeanDefinitionPostProcessor) { + MergedBeanDefinitionPostProcessor bdp = (MergedBeanDefinitionPostProcessor) beanProcessor; + bdp.postProcessMergedBeanDefinition(mbd, beanType, beanName); + } + } + } + + /** + * Apply before-instantiation post-processors, resolving whether there is a + * before-instantiation shortcut for the specified bean. + * @param beanName the name of the bean + * @param mbd the bean definition for the bean + * @return the shortcut-determined bean instance, or null if none + */ + protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) { + Object bean = null; + if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) { + // Make sure bean class is actually resolved at this point. + if (mbd.hasBeanClass() && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { + bean = applyBeanPostProcessorsBeforeInstantiation(mbd.getBeanClass(), beanName); + if (bean != null) { + bean = applyBeanPostProcessorsAfterInitialization(bean, beanName); + } + } + mbd.beforeInstantiationResolved = Boolean.valueOf(bean != null); + } + return bean; + } + + /** + * Apply InstantiationAwareBeanPostProcessors to the specified bean definition + * (by class and name), invoking their postProcessBeforeInstantiation methods. + *

Any returned object will be used as the bean instead of actually instantiating + * the target bean. A null return value from the post-processor will + * result in the target bean being instantiated. + * @param beanClass the class of the bean to be instantiated + * @param beanName the name of the bean + * @return the bean object to use instead of a default instance of the target bean, or null + * @throws BeansException if any post-processing failed + * @see InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation + */ + protected Object applyBeanPostProcessorsBeforeInstantiation(Class beanClass, String beanName) + throws BeansException { + + for (Iterator it = getBeanPostProcessors().iterator(); it.hasNext();) { + BeanPostProcessor beanProcessor = (BeanPostProcessor) it.next(); + if (beanProcessor instanceof InstantiationAwareBeanPostProcessor) { + InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) beanProcessor; + Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName); + if (result != null) { + return result; + } + } + } + return null; + } + + /** + * Create a new instance for the specified bean, using an appropriate instantiation strategy: + * factory method, constructor autowiring, or simple instantiation. + * @param beanName the name of the bean + * @param mbd the bean definition for the bean + * @param args arguments to use if creating a prototype using explicit arguments to a + * static factory method. It is invalid to use a non-null args value in any other case. + * @return BeanWrapper for the new instance + * @see #instantiateUsingFactoryMethod + * @see #autowireConstructor + * @see #instantiateBean + */ + protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) { + // Make sure bean class is actually resolved at this point. + Class beanClass = resolveBeanClass(mbd, beanName); + + if (mbd.getFactoryMethodName() != null) { + return instantiateUsingFactoryMethod(beanName, mbd, args); + } + + // Shortcut when re-creating the same bean... + if (mbd.resolvedConstructorOrFactoryMethod != null) { + if (mbd.constructorArgumentsResolved) { + return autowireConstructor(beanName, mbd, null, args); + } + else { + return instantiateBean(beanName, mbd); + } + } + + // Need to determine the constructor... + Constructor[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName); + if (ctors != null || + mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR || + mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) { + return autowireConstructor(beanName, mbd, ctors, args); + } + + // No special handling: simply use no-arg constructor. + return instantiateBean(beanName, mbd); + } + + /** + * Determine candidate constructors to use for the given bean, checking all registered + * {@link SmartInstantiationAwareBeanPostProcessor SmartInstantiationAwareBeanPostProcessors}. + * @param beanClass the raw class of the bean + * @param beanName the name of the bean + * @return the candidate constructors, or null if none specified + * @throws org.springframework.beans.BeansException in case of errors + * @see org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor#determineCandidateConstructors + */ + protected Constructor[] determineConstructorsFromBeanPostProcessors(Class beanClass, String beanName) + throws BeansException { + + if (beanClass != null && hasInstantiationAwareBeanPostProcessors()) { + for (Iterator it = getBeanPostProcessors().iterator(); it.hasNext();) { + BeanPostProcessor beanProcessor = (BeanPostProcessor) it.next(); + if (beanProcessor instanceof SmartInstantiationAwareBeanPostProcessor) { + SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) beanProcessor; + Constructor[] ctors = ibp.determineCandidateConstructors(beanClass, beanName); + if (ctors != null) { + return ctors; + } + } + } + } + return null; + } + + /** + * Instantiate the given bean using its default constructor. + * @param beanName the name of the bean + * @param mbd the bean definition for the bean + * @return BeanWrapper for the new instance + */ + protected BeanWrapper instantiateBean(String beanName, RootBeanDefinition mbd) { + try { + Object beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, this); + BeanWrapper bw = new BeanWrapperImpl(beanInstance); + initBeanWrapper(bw); + return bw; + } + catch (Throwable ex) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex); + } + } + + /** + * Instantiate the bean using a named factory method. The method may be static, if the + * mbd parameter specifies a class, rather than a factoryBean, or an instance variable + * on a factory object itself configured using Dependency Injection. + * @param beanName the name of the bean + * @param mbd the bean definition for the bean + * @param explicitArgs argument values passed in programmatically via the getBean method, + * or null if none (-> use constructor argument values from bean definition) + * @return BeanWrapper for the new instance + * @see #getBean(String, Object[]) + */ + protected BeanWrapper instantiateUsingFactoryMethod( + String beanName, RootBeanDefinition mbd, Object[] explicitArgs) { + + ConstructorResolver constructorResolver = + new ConstructorResolver(this, this, getInstantiationStrategy(), getCustomTypeConverter()); + return constructorResolver.instantiateUsingFactoryMethod(beanName, mbd, explicitArgs); + } + + /** + * "autowire constructor" (with constructor arguments by type) behavior. + * Also applied if explicit constructor argument values are specified, + * matching all remaining arguments with beans from the bean factory. + *

This corresponds to constructor injection: In this mode, a Spring + * bean factory is able to host components that expect constructor-based + * dependency resolution. + * @param beanName the name of the bean + * @param mbd the bean definition for the bean + * @param ctors the chosen candidate constructors + * @param explicitArgs argument values passed in programmatically via the getBean method, + * or null if none (-> use constructor argument values from bean definition) + * @return BeanWrapper for the new instance + */ + protected BeanWrapper autowireConstructor( + String beanName, RootBeanDefinition mbd, Constructor[] ctors, Object[] explicitArgs) { + + ConstructorResolver constructorResolver = + new ConstructorResolver(this, this, getInstantiationStrategy(), getCustomTypeConverter()); + return constructorResolver.autowireConstructor(beanName, mbd, ctors, explicitArgs); + } + + /** + * Populate the bean instance in the given BeanWrapper with the property values + * from the bean definition. + * @param beanName the name of the bean + * @param mbd the bean definition for the bean + * @param bw BeanWrapper with bean instance + */ + protected void populateBean(String beanName, AbstractBeanDefinition mbd, BeanWrapper bw) { + PropertyValues pvs = mbd.getPropertyValues(); + + if (bw == null) { + if (!pvs.isEmpty()) { + throw new BeanCreationException( + mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance"); + } + else { + // Skip property population phase for null instance. + return; + } + } + + // Give any InstantiationAwareBeanPostProcessors the opportunity to modify the + // state of the bean before properties are set. This can be used, for example, + // to support styles of field injection. + boolean continueWithPropertyPopulation = true; + + if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { + for (Iterator it = getBeanPostProcessors().iterator(); it.hasNext();) { + BeanPostProcessor beanProcessor = (BeanPostProcessor) it.next(); + if (beanProcessor instanceof InstantiationAwareBeanPostProcessor) { + InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) beanProcessor; + if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) { + continueWithPropertyPopulation = false; + break; + } + } + } + } + + if (!continueWithPropertyPopulation) { + return; + } + + if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME || + mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) { + MutablePropertyValues newPvs = new MutablePropertyValues(pvs); + + // Add property values based on autowire by name if applicable. + if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME) { + autowireByName(beanName, mbd, bw, newPvs); + } + + // Add property values based on autowire by type if applicable. + if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) { + autowireByType(beanName, mbd, bw, newPvs); + } + + pvs = newPvs; + } + + boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors(); + boolean needsDepCheck = (mbd.getDependencyCheck() != RootBeanDefinition.DEPENDENCY_CHECK_NONE); + + if (hasInstAwareBpps || needsDepCheck) { + PropertyDescriptor[] filteredPds = filterPropertyDescriptorsForDependencyCheck(bw); + if (hasInstAwareBpps) { + for (Iterator it = getBeanPostProcessors().iterator(); it.hasNext(); ) { + BeanPostProcessor beanProcessor = (BeanPostProcessor) it.next(); + if (beanProcessor instanceof InstantiationAwareBeanPostProcessor) { + InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) beanProcessor; + pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName); + if (pvs == null) { + return; + } + } + } + } + if (needsDepCheck) { + checkDependencies(beanName, mbd, filteredPds, pvs); + } + } + + applyPropertyValues(beanName, mbd, bw, pvs); + } + + /** + * Fill in any missing property values with references to + * other beans in this factory if autowire is set to "byName". + * @param beanName the name of the bean we're wiring up. + * Useful for debugging messages; not used functionally. + * @param mbd bean definition to update through autowiring + * @param bw BeanWrapper from which we can obtain information about the bean + * @param pvs the PropertyValues to register wired objects with + */ + protected void autowireByName( + String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) { + + String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw); + for (int i = 0; i < propertyNames.length; i++) { + String propertyName = propertyNames[i]; + if (containsBean(propertyName)) { + Object bean = getBean(propertyName); + pvs.addPropertyValue(propertyName, bean); + registerDependentBean(propertyName, beanName); + if (logger.isDebugEnabled()) { + logger.debug("Added autowiring by name from bean name '" + beanName + + "' via property '" + propertyName + "' to bean named '" + propertyName + "'"); + } + } + else { + if (logger.isTraceEnabled()) { + logger.trace("Not autowiring property '" + propertyName + "' of bean '" + beanName + + "' by name: no matching bean found"); + } + } + } + } + + /** + * Abstract method defining "autowire by type" (bean properties by type) behavior. + *

This is like PicoContainer default, in which there must be exactly one bean + * of the property type in the bean factory. This makes bean factories simple to + * configure for small namespaces, but doesn't work as well as standard Spring + * behavior for bigger applications. + * @param beanName the name of the bean to autowire by type + * @param mbd the merged bean definition to update through autowiring + * @param bw BeanWrapper from which we can obtain information about the bean + * @param pvs the PropertyValues to register wired objects with + */ + protected void autowireByType( + String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) { + + TypeConverter converter = getCustomTypeConverter(); + if (converter == null) { + converter = bw; + } + + Set autowiredBeanNames = new LinkedHashSet(4); + String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw); + for (int i = 0; i < propertyNames.length; i++) { + String propertyName = propertyNames[i]; + try { + PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName); + MethodParameter methodParam = BeanUtils.getWriteMethodParameter(pd); + // Do not allow eager init for type matching in case of a prioritized post-processor. + boolean eager = !PriorityOrdered.class.isAssignableFrom(bw.getWrappedClass()); + DependencyDescriptor desc = new DependencyDescriptor(methodParam, false, eager); + + Object autowiredArgument = resolveDependency(desc, beanName, autowiredBeanNames, converter); + if (autowiredArgument != null) { + pvs.addPropertyValue(propertyName, autowiredArgument); + } + for (Iterator it = autowiredBeanNames.iterator(); it.hasNext();) { + String autowiredBeanName = (String) it.next(); + registerDependentBean(autowiredBeanName, beanName); + if (logger.isDebugEnabled()) { + logger.debug("Autowiring by type from bean name '" + beanName + "' via property '" + + propertyName + "' to bean named '" + autowiredBeanName + "'"); + } + } + autowiredBeanNames.clear(); + } + catch (BeansException ex) { + throw new UnsatisfiedDependencyException(mbd.getResourceDescription(), beanName, propertyName, ex); + } + } + } + + + /** + * Return an array of non-simple bean properties that are unsatisfied. + * These are probably unsatisfied references to other beans in the + * factory. Does not include simple properties like primitives or Strings. + * @param mbd the merged bean definition the bean was created with + * @param bw the BeanWrapper the bean was created with + * @return an array of bean property names + * @see org.springframework.beans.BeanUtils#isSimpleProperty + */ + protected String[] unsatisfiedNonSimpleProperties(AbstractBeanDefinition mbd, BeanWrapper bw) { + Set result = new TreeSet(); + PropertyValues pvs = mbd.getPropertyValues(); + PropertyDescriptor[] pds = bw.getPropertyDescriptors(); + for (int i = 0; i < pds.length; i++) { + if (pds[i].getWriteMethod() != null && !isExcludedFromDependencyCheck(pds[i]) && + !pvs.contains(pds[i].getName()) && !BeanUtils.isSimpleProperty(pds[i].getPropertyType())) { + result.add(pds[i].getName()); + } + } + return StringUtils.toStringArray(result); + } + + /** + * Extract a filtered set of PropertyDescriptors from the given BeanWrapper, + * excluding ignored dependency types or properties defined on ignored + * dependency interfaces. + * @param bw the BeanWrapper the bean was created with + * @return the filtered PropertyDescriptors + * @see #isExcludedFromDependencyCheck + */ + protected PropertyDescriptor[] filterPropertyDescriptorsForDependencyCheck(BeanWrapper bw) { + synchronized (this.filteredPropertyDescriptorsCache) { + PropertyDescriptor[] filtered = (PropertyDescriptor[]) + this.filteredPropertyDescriptorsCache.get(bw.getWrappedClass()); + if (filtered == null) { + List pds = new LinkedList(Arrays.asList(bw.getPropertyDescriptors())); + for (Iterator it = pds.iterator(); it.hasNext();) { + PropertyDescriptor pd = (PropertyDescriptor) it.next(); + if (isExcludedFromDependencyCheck(pd)) { + it.remove(); + } + } + filtered = (PropertyDescriptor[]) pds.toArray(new PropertyDescriptor[pds.size()]); + this.filteredPropertyDescriptorsCache.put(bw.getWrappedClass(), filtered); + } + return filtered; + } + } + + /** + * Determine whether the given bean property is excluded from dependency checks. + *

This implementation excludes properties defined by CGLIB and + * properties whose type matches an ignored dependency type or which + * are defined by an ignored dependency interface. + * @param pd the PropertyDescriptor of the bean property + * @return whether the bean property is excluded + * @see #ignoreDependencyType(Class) + * @see #ignoreDependencyInterface(Class) + */ + protected boolean isExcludedFromDependencyCheck(PropertyDescriptor pd) { + return (AutowireUtils.isExcludedFromDependencyCheck(pd) || + this.ignoredDependencyTypes.contains(pd.getPropertyType()) || + AutowireUtils.isSetterDefinedInInterface(pd, this.ignoredDependencyInterfaces)); + } + + /** + * Perform a dependency check that all properties exposed have been set, + * if desired. Dependency checks can be objects (collaborating beans), + * simple (primitives and String), or all (both). + * @param beanName the name of the bean + * @param mbd the merged bean definition the bean was created with + * @param pds the relevant property descriptors for the target bean + * @param pvs the property values to be applied to the bean + * @see #isExcludedFromDependencyCheck(java.beans.PropertyDescriptor) + */ + protected void checkDependencies( + String beanName, AbstractBeanDefinition mbd, PropertyDescriptor[] pds, PropertyValues pvs) + throws UnsatisfiedDependencyException { + + int dependencyCheck = mbd.getDependencyCheck(); + for (int i = 0; i < pds.length; i++) { + if (pds[i].getWriteMethod() != null && !pvs.contains(pds[i].getName())) { + boolean isSimple = BeanUtils.isSimpleProperty(pds[i].getPropertyType()); + boolean unsatisfied = (dependencyCheck == RootBeanDefinition.DEPENDENCY_CHECK_ALL) || + (isSimple && dependencyCheck == RootBeanDefinition.DEPENDENCY_CHECK_SIMPLE) || + (!isSimple && dependencyCheck == RootBeanDefinition.DEPENDENCY_CHECK_OBJECTS); + if (unsatisfied) { + throw new UnsatisfiedDependencyException( + mbd.getResourceDescription(), beanName, pds[i].getName(), + "Set this property value or disable dependency checking for this bean."); + } + } + } + } + + /** + * Apply the given property values, resolving any runtime references + * to other beans in this bean factory. Must use deep copy, so we + * don't permanently modify this property. + * @param beanName the bean name passed for better exception information + * @param mbd the merged bean definition + * @param bw the BeanWrapper wrapping the target object + * @param pvs the new property values + */ + protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) { + if (pvs == null || pvs.isEmpty()) { + return; + } + + MutablePropertyValues mpvs = null; + List original = null; + + if (pvs instanceof MutablePropertyValues) { + mpvs = (MutablePropertyValues) pvs; + if (mpvs.isConverted()) { + // Shortcut: use the pre-converted values as-is. + try { + bw.setPropertyValues(mpvs); + return; + } + catch (BeansException ex) { + throw new BeanCreationException( + mbd.getResourceDescription(), beanName, "Error setting property values", ex); + } + } + original = mpvs.getPropertyValueList(); + } + else { + original = Arrays.asList(pvs.getPropertyValues()); + } + + TypeConverter converter = getCustomTypeConverter(); + if (converter == null) { + converter = bw; + } + BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter); + + // Create a deep copy, resolving any references for values. + List deepCopy = new ArrayList(original.size()); + boolean resolveNecessary = false; + for (Iterator it = original.iterator(); it.hasNext();) { + PropertyValue pv = (PropertyValue) it.next(); + if (pv.isConverted()) { + deepCopy.add(pv); + } + else { + String propertyName = pv.getName(); + Object originalValue = pv.getValue(); + Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue); + Object convertedValue = resolvedValue; + boolean convertible = bw.isWritableProperty(propertyName) && + !PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName); + if (convertible) { + convertedValue = convertForProperty(resolvedValue, propertyName, bw, converter); + } + // Possibly store converted value in merged bean definition, + // in order to avoid re-conversion for every created bean instance. + if (resolvedValue == originalValue) { + if (convertible) { + pv.setConvertedValue(convertedValue); + } + deepCopy.add(pv); + } + else if (originalValue instanceof TypedStringValue && convertible) { + pv.setConvertedValue(convertedValue); + deepCopy.add(pv); + } + else { + resolveNecessary = true; + deepCopy.add(new PropertyValue(pv, convertedValue)); + } + } + } + if (mpvs != null && !resolveNecessary) { + mpvs.setConverted(); + } + + // Set our (possibly massaged) deep copy. + try { + bw.setPropertyValues(new MutablePropertyValues(deepCopy)); + } + catch (BeansException ex) { + throw new BeanCreationException( + mbd.getResourceDescription(), beanName, "Error setting property values", ex); + } + } + + /** + * Convert the given value for the specified target property. + */ + private Object convertForProperty(Object value, String propertyName, BeanWrapper bw, TypeConverter converter) { + if (converter instanceof BeanWrapperImpl) { + return ((BeanWrapperImpl) converter).convertForProperty(value, propertyName); + } + else { + PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName); + MethodParameter methodParam = BeanUtils.getWriteMethodParameter(pd); + return converter.convertIfNecessary(value, pd.getPropertyType(), methodParam); + } + } + + + /** + * Initialize the given bean instance, applying factory callbacks + * as well as init methods and bean post processors. + *

Called from {@link #createBean} for traditionally defined beans, + * and from {@link #initializeBean} for existing bean instances. + * @param beanName the bean name in the factory (for debugging purposes) + * @param bean the new bean instance we may need to initialize + * @param mbd the bean definition that the bean was created with + * (can also be null, if given an existing bean instance) + * @return the initialized bean instance (potentially wrapped) + * @see BeanNameAware + * @see BeanClassLoaderAware + * @see BeanFactoryAware + * @see #applyBeanPostProcessorsBeforeInitialization + * @see #invokeInitMethods + * @see #applyBeanPostProcessorsAfterInitialization + */ + protected Object initializeBean(String beanName, Object bean, RootBeanDefinition mbd) { + if (bean instanceof BeanNameAware) { + ((BeanNameAware) bean).setBeanName(beanName); + } + + if (bean instanceof BeanClassLoaderAware) { + ((BeanClassLoaderAware) bean).setBeanClassLoader(getBeanClassLoader()); + } + + if (bean instanceof BeanFactoryAware) { + ((BeanFactoryAware) bean).setBeanFactory(this); + } + + Object wrappedBean = bean; + if (mbd == null || !mbd.isSynthetic()) { + wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); + } + + try { + invokeInitMethods(beanName, wrappedBean, mbd); + } + catch (Throwable ex) { + throw new BeanCreationException( + (mbd != null ? mbd.getResourceDescription() : null), + beanName, "Invocation of init method failed", ex); + } + + if (mbd == null || !mbd.isSynthetic()) { + wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); + } + return wrappedBean; + } + + /** + * Give a bean a chance to react now all its properties are set, + * and a chance to know about its owning bean factory (this object). + * This means checking whether the bean implements InitializingBean or defines + * a custom init method, and invoking the necessary callback(s) if it does. + * @param beanName the bean name in the factory (for debugging purposes) + * @param bean the new bean instance we may need to initialize + * @param mbd the merged bean definition that the bean was created with + * (can also be null, if given an existing bean instance) + * @throws Throwable if thrown by init methods or by the invocation process + * @see #invokeCustomInitMethod + */ + protected void invokeInitMethods(String beanName, Object bean, RootBeanDefinition mbd) + throws Throwable { + + boolean isInitializingBean = (bean instanceof InitializingBean); + if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) { + if (logger.isDebugEnabled()) { + logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'"); + } + ((InitializingBean) bean).afterPropertiesSet(); + } + + String initMethodName = (mbd != null ? mbd.getInitMethodName() : null); + if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) && + !mbd.isExternallyManagedInitMethod(initMethodName)) { + invokeCustomInitMethod(beanName, bean, initMethodName, mbd.isEnforceInitMethod()); + } + } + + /** + * Invoke the specified custom init method on the given bean. + * Called by invokeInitMethods. + *

Can be overridden in subclasses for custom resolution of init + * methods with arguments. + * @param beanName the bean name in the factory (for debugging purposes) + * @param bean the new bean instance we may need to initialize + * @param initMethodName the name of the custom init method + * @param enforceInitMethod indicates whether the defined init method needs to exist + * @see #invokeInitMethods + */ + protected void invokeCustomInitMethod( + String beanName, Object bean, String initMethodName, boolean enforceInitMethod) throws Throwable { + + Method initMethod = BeanUtils.findMethod(bean.getClass(), initMethodName, null); + if (initMethod == null) { + if (enforceInitMethod) { + throw new NoSuchMethodException("Couldn't find an init method named '" + initMethodName + + "' on bean with name '" + beanName + "'"); + } + else { + if (logger.isDebugEnabled()) { + logger.debug("No default init method named '" + initMethodName + + "' found on bean with name '" + beanName + "'"); + } + // Ignore non-existent default lifecycle methods. + return; + } + } + + if (logger.isDebugEnabled()) { + logger.debug("Invoking init method '" + initMethodName + "' on bean with name '" + beanName + "'"); + } + ReflectionUtils.makeAccessible(initMethod); + try { + initMethod.invoke(bean, (Object[]) null); + } + catch (InvocationTargetException ex) { + throw ex.getTargetException(); + } + } + + + /** + * Applies the postProcessAfterInitialization callback of all + * registered BeanPostProcessors, giving them a chance to post-process the + * object obtained from FactoryBeans (for example, to auto-proxy them). + * @see #applyBeanPostProcessorsAfterInitialization + */ + protected Object postProcessObjectFromFactoryBean(Object object, String beanName) { + return applyBeanPostProcessorsAfterInitialization(object, beanName); + } + + /** + * Overridden to clear FactoryBean instance cache as well. + */ + protected void removeSingleton(String beanName) { + super.removeSingleton(beanName); + this.factoryBeanInstanceCache.remove(beanName); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java new file mode 100644 index 0000000000..9988172508 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java @@ -0,0 +1,1000 @@ +/* + * Copyright 2002-2008 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.Constructor; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import org.springframework.beans.BeanMetadataAttributeAccessor; +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConstructorArgumentValues; +import org.springframework.core.io.DescriptiveResource; +import org.springframework.core.io.Resource; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; + +/** + * Base class for concrete, full-fledged + * {@link org.springframework.beans.factory.config.BeanDefinition} classes, + * factoring out common properties of {@link RootBeanDefinition} and + * {@link ChildBeanDefinition}. + * + *

The autowire constants match the ones defined in the + * {@link org.springframework.beans.factory.config.AutowireCapableBeanFactory} + * interface. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @author Rob Harrop + * @author Mark Fisher + * @see RootBeanDefinition + * @see ChildBeanDefinition + */ +public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor + implements BeanDefinition, Cloneable { + + /** + * Constant that indicates no autowiring at all. + * @see #setAutowireMode + */ + public static final int AUTOWIRE_NO = AutowireCapableBeanFactory.AUTOWIRE_NO; + + /** + * Constant that indicates autowiring bean properties by name. + * @see #setAutowireMode + */ + public static final int AUTOWIRE_BY_NAME = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME; + + /** + * Constant that indicates autowiring bean properties by type. + * @see #setAutowireMode + */ + public static final int AUTOWIRE_BY_TYPE = AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE; + + /** + * Constant that indicates autowiring a constructor. + * @see #setAutowireMode + */ + public static final int AUTOWIRE_CONSTRUCTOR = AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR; + + /** + * Constant that indicates determining an appropriate autowire strategy + * through introspection of the bean class. + * @see #setAutowireMode + */ + public static final int AUTOWIRE_AUTODETECT = AutowireCapableBeanFactory.AUTOWIRE_AUTODETECT; + + + /** + * Constant that indicates no dependency check at all. + * @see #setDependencyCheck + */ + public static final int DEPENDENCY_CHECK_NONE = 0; + + /** + * Constant that indicates dependency checking for object references. + * @see #setDependencyCheck + */ + public static final int DEPENDENCY_CHECK_OBJECTS = 1; + + /** + * Constant that indicates dependency checking for "simple" properties. + * @see #setDependencyCheck + * @see org.springframework.beans.BeanUtils#isSimpleProperty + */ + public static final int DEPENDENCY_CHECK_SIMPLE = 2; + + /** + * Constant that indicates dependency checking for all properties + * (object references as well as "simple" properties). + * @see #setDependencyCheck + */ + public static final int DEPENDENCY_CHECK_ALL = 3; + + + private volatile Object beanClass; + + private String scope = SCOPE_SINGLETON; + + private boolean singleton = true; + + private boolean prototype = false; + + private boolean abstractFlag = false; + + private boolean lazyInit = false; + + private int autowireMode = AUTOWIRE_NO; + + private int dependencyCheck = DEPENDENCY_CHECK_NONE; + + private String[] dependsOn; + + private boolean autowireCandidate = true; + + private final Map qualifiers = new LinkedHashMap(); + + private boolean primary = false; + + private ConstructorArgumentValues constructorArgumentValues; + + private MutablePropertyValues propertyValues; + + private MethodOverrides methodOverrides = new MethodOverrides(); + + private String factoryBeanName; + + private String factoryMethodName; + + private String initMethodName; + + private String destroyMethodName; + + private boolean enforceInitMethod = true; + + private boolean enforceDestroyMethod = true; + + private boolean synthetic = false; + + private int role = BeanDefinition.ROLE_APPLICATION; + + private String description; + + private Resource resource; + + + /** + * Create a new AbstractBeanDefinition with default settings. + */ + protected AbstractBeanDefinition() { + this(null, null); + } + + /** + * Create a new AbstractBeanDefinition with the given + * constructor argument values and property values. + */ + protected AbstractBeanDefinition(ConstructorArgumentValues cargs, MutablePropertyValues pvs) { + setConstructorArgumentValues(cargs); + setPropertyValues(pvs); + } + + /** + * Create a new AbstractBeanDefinition as deep copy of the given + * bean definition. + * @param original the original bean definition to copy from + * @deprecated since Spring 2.5, in favor of {@link #AbstractBeanDefinition(BeanDefinition)} + */ + protected AbstractBeanDefinition(AbstractBeanDefinition original) { + this((BeanDefinition) original); + } + + /** + * Create a new AbstractBeanDefinition as deep copy of the given + * bean definition. + * @param original the original bean definition to copy from + */ + protected AbstractBeanDefinition(BeanDefinition original) { + setParentName(original.getParentName()); + setBeanClassName(original.getBeanClassName()); + setFactoryBeanName(original.getFactoryBeanName()); + setFactoryMethodName(original.getFactoryMethodName()); + setScope(original.getScope()); + setAbstract(original.isAbstract()); + setLazyInit(original.isLazyInit()); + setRole(original.getRole()); + setConstructorArgumentValues(new ConstructorArgumentValues(original.getConstructorArgumentValues())); + setPropertyValues(new MutablePropertyValues(original.getPropertyValues())); + setSource(original.getSource()); + copyAttributesFrom(original); + + if (original instanceof AbstractBeanDefinition) { + AbstractBeanDefinition originalAbd = (AbstractBeanDefinition) original; + if (originalAbd.hasBeanClass()) { + setBeanClass(originalAbd.getBeanClass()); + } + setAutowireMode(originalAbd.getAutowireMode()); + setDependencyCheck(originalAbd.getDependencyCheck()); + setDependsOn(originalAbd.getDependsOn()); + setAutowireCandidate(originalAbd.isAutowireCandidate()); + copyQualifiersFrom(originalAbd); + setPrimary(originalAbd.isPrimary()); + setInitMethodName(originalAbd.getInitMethodName()); + setEnforceInitMethod(originalAbd.isEnforceInitMethod()); + setDestroyMethodName(originalAbd.getDestroyMethodName()); + setEnforceDestroyMethod(originalAbd.isEnforceDestroyMethod()); + setMethodOverrides(new MethodOverrides(originalAbd.getMethodOverrides())); + setSynthetic(originalAbd.isSynthetic()); + setResource(originalAbd.getResource()); + } + else { + setResourceDescription(original.getResourceDescription()); + } + } + + + /** + * Override settings in this bean definition (assumably a copied parent + * from a parent-child inheritance relationship) from the given bean + * definition (assumably the child). + * @deprecated since Spring 2.5, in favor of {@link #overrideFrom(BeanDefinition)} + */ + public void overrideFrom(AbstractBeanDefinition other) { + overrideFrom((BeanDefinition) other); + } + + /** + * Override settings in this bean definition (assumably a copied parent + * from a parent-child inheritance relationship) from the given bean + * definition (assumably the child). + *

    + *
  • Will override beanClass if specified in the given bean definition. + *
  • Will always take abstract, scope, + * lazyInit, autowireMode, dependencyCheck, + * and dependsOn from the given bean definition. + *
  • Will add constructorArgumentValues, propertyValues, + * methodOverrides from the given bean definition to existing ones. + *
  • Will override factoryBeanName, factoryMethodName, + * initMethodName, and destroyMethodName if specified + * in the given bean definition. + *
+ */ + public void overrideFrom(BeanDefinition other) { + if (other.getBeanClassName() != null) { + setBeanClassName(other.getBeanClassName()); + } + if (other.getFactoryBeanName() != null) { + setFactoryBeanName(other.getFactoryBeanName()); + } + if (other.getFactoryMethodName() != null) { + setFactoryMethodName(other.getFactoryMethodName()); + } + setScope(other.getScope()); + setAbstract(other.isAbstract()); + setLazyInit(other.isLazyInit()); + setRole(other.getRole()); + getConstructorArgumentValues().addArgumentValues(other.getConstructorArgumentValues()); + getPropertyValues().addPropertyValues(other.getPropertyValues()); + setSource(other.getSource()); + copyAttributesFrom(other); + + if (other instanceof AbstractBeanDefinition) { + AbstractBeanDefinition otherAbd = (AbstractBeanDefinition) other; + if (otherAbd.hasBeanClass()) { + setBeanClass(otherAbd.getBeanClass()); + } + setAutowireCandidate(otherAbd.isAutowireCandidate()); + setAutowireMode(otherAbd.getAutowireMode()); + copyQualifiersFrom(otherAbd); + setPrimary(otherAbd.isPrimary()); + setDependencyCheck(otherAbd.getDependencyCheck()); + setDependsOn(otherAbd.getDependsOn()); + if (otherAbd.getInitMethodName() != null) { + setInitMethodName(otherAbd.getInitMethodName()); + setEnforceInitMethod(otherAbd.isEnforceInitMethod()); + } + if (otherAbd.getDestroyMethodName() != null) { + setDestroyMethodName(otherAbd.getDestroyMethodName()); + setEnforceDestroyMethod(otherAbd.isEnforceDestroyMethod()); + } + getMethodOverrides().addOverrides(otherAbd.getMethodOverrides()); + setSynthetic(otherAbd.isSynthetic()); + setResource(otherAbd.getResource()); + } + else { + setResourceDescription(other.getResourceDescription()); + } + } + + /** + * Apply the provided default values to this bean. + * @param defaults the defaults to apply + */ + public void applyDefaults(BeanDefinitionDefaults defaults) { + setLazyInit(defaults.isLazyInit()); + setDependencyCheck(defaults.getDependencyCheck()); + setAutowireMode(defaults.getAutowireMode()); + setInitMethodName(defaults.getInitMethodName()); + setEnforceInitMethod(false); + setDestroyMethodName(defaults.getDestroyMethodName()); + setEnforceDestroyMethod(false); + } + + + /** + * Return whether this definition specifies a bean class. + */ + public boolean hasBeanClass() { + return (this.beanClass instanceof Class); + } + + /** + * Specify the class for this bean. + */ + public void setBeanClass(Class beanClass) { + this.beanClass = beanClass; + } + + /** + * Return the class of the wrapped bean, if already resolved. + * @return the bean class, or null if none defined + * @throws IllegalStateException if the bean definition does not define a bean class, + * or a specified bean class name has not been resolved into an actual Class + */ + public Class getBeanClass() throws IllegalStateException { + Object beanClassObject = this.beanClass; + if (beanClassObject == null) { + throw new IllegalStateException("No bean class specified on bean definition"); + } + if (!(beanClassObject instanceof Class)) { + throw new IllegalStateException( + "Bean class name [" + beanClassObject + "] has not been resolved into an actual Class"); + } + return (Class) beanClassObject; + } + + public void setBeanClassName(String beanClassName) { + this.beanClass = beanClassName; + } + + public String getBeanClassName() { + Object beanClassObject = this.beanClass; + if (beanClassObject instanceof Class) { + return ((Class) beanClassObject).getName(); + } + else { + return (String) beanClassObject; + } + } + + /** + * Determine the class of the wrapped bean, resolving it from a + * specified class name if necessary. Will also reload a specified + * Class from its name when called with the bean class already resolved. + * @param classLoader the ClassLoader to use for resolving a (potential) class name + * @return the resolved bean class + * @throws ClassNotFoundException if the class name could be resolved + */ + public Class resolveBeanClass(ClassLoader classLoader) throws ClassNotFoundException { + String className = getBeanClassName(); + if (className == null) { + return null; + } + Class resolvedClass = ClassUtils.forName(className, classLoader); + this.beanClass = resolvedClass; + return resolvedClass; + } + + + /** + * Set the name of the target scope for the bean. + *

Default is "singleton"; the out-of-the-box alternative is "prototype". + * Extended bean factories might support further scopes. + * @see #SCOPE_SINGLETON + * @see #SCOPE_PROTOTYPE + */ + public void setScope(String scope) { + Assert.notNull(scope, "Scope must not be null"); + this.scope = scope; + this.singleton = SCOPE_SINGLETON.equals(scope); + this.prototype = SCOPE_PROTOTYPE.equals(scope); + } + + /** + * Return the name of the target scope for the bean. + */ + public String getScope() { + return this.scope; + } + + /** + * Set if this a Singleton, with a single, shared instance returned + * on all calls. In case of "false", the BeanFactory will apply the Prototype + * design pattern, with each caller requesting an instance getting an independent + * instance. How this is exactly defined will depend on the BeanFactory. + *

"Singletons" are the commoner type, so the default is "true". + * Note that as of Spring 2.0, this flag is just an alternative way to + * specify scope="singleton" or scope="prototype". + * @deprecated since Spring 2.5, in favor of {@link #setScope} + * @see #setScope + * @see #SCOPE_SINGLETON + * @see #SCOPE_PROTOTYPE + */ + public void setSingleton(boolean singleton) { + this.scope = (singleton ? SCOPE_SINGLETON : SCOPE_PROTOTYPE); + this.singleton = singleton; + this.prototype = !singleton; + } + + /** + * Return whether this a Singleton, with a single shared instance + * returned from all calls. + * @see #SCOPE_SINGLETON + */ + public boolean isSingleton() { + return this.singleton; + } + + /** + * Return whether this a Prototype, with an independent instance + * returned for each call. + * @see #SCOPE_PROTOTYPE + */ + public boolean isPrototype() { + return this.prototype; + } + + /** + * Set if this bean is "abstract", i.e. not meant to be instantiated itself but + * rather just serving as parent for concrete child bean definitions. + *

Default is "false". Specify true to tell the bean factory to not try to + * instantiate that particular bean in any case. + */ + public void setAbstract(boolean abstractFlag) { + this.abstractFlag = abstractFlag; + } + + /** + * Return whether this bean is "abstract", i.e. not meant to be instantiated + * itself but rather just serving as parent for concrete child bean definitions. + */ + public boolean isAbstract() { + return this.abstractFlag; + } + + /** + * Set whether this bean should be lazily initialized. + *

If false, the bean will get instantiated on startup by bean + * factories that perform eager initialization of singletons. + */ + public void setLazyInit(boolean lazyInit) { + this.lazyInit = lazyInit; + } + + /** + * Return whether this bean should be lazily initialized, i.e. not + * eagerly instantiated on startup. Only applicable to a singleton bean. + */ + public boolean isLazyInit() { + return this.lazyInit; + } + + + /** + * Set the autowire mode. This determines whether any automagical detection + * and setting of bean references will happen. Default is AUTOWIRE_NO, + * which means there's no autowire. + * @param autowireMode the autowire mode to set. + * Must be one of the constants defined in this class. + * @see #AUTOWIRE_NO + * @see #AUTOWIRE_BY_NAME + * @see #AUTOWIRE_BY_TYPE + * @see #AUTOWIRE_CONSTRUCTOR + * @see #AUTOWIRE_AUTODETECT + */ + public void setAutowireMode(int autowireMode) { + this.autowireMode = autowireMode; + } + + /** + * Return the autowire mode as specified in the bean definition. + */ + public int getAutowireMode() { + return this.autowireMode; + } + + /** + * Return the resolved autowire code, + * (resolving AUTOWIRE_AUTODETECT to AUTOWIRE_CONSTRUCTOR or AUTOWIRE_BY_TYPE). + * @see #AUTOWIRE_AUTODETECT + * @see #AUTOWIRE_CONSTRUCTOR + * @see #AUTOWIRE_BY_TYPE + */ + public int getResolvedAutowireMode() { + if (this.autowireMode == AUTOWIRE_AUTODETECT) { + // Work out whether to apply setter autowiring or constructor autowiring. + // If it has a no-arg constructor it's deemed to be setter autowiring, + // otherwise we'll try constructor autowiring. + Constructor[] constructors = getBeanClass().getConstructors(); + for (int i = 0; i < constructors.length; i++) { + if (constructors[i].getParameterTypes().length == 0) { + return AUTOWIRE_BY_TYPE; + } + } + return AUTOWIRE_CONSTRUCTOR; + } + else { + return this.autowireMode; + } + } + + /** + * Set the dependency check code. + * @param dependencyCheck the code to set. + * Must be one of the four constants defined in this class. + * @see #DEPENDENCY_CHECK_NONE + * @see #DEPENDENCY_CHECK_OBJECTS + * @see #DEPENDENCY_CHECK_SIMPLE + * @see #DEPENDENCY_CHECK_ALL + */ + public void setDependencyCheck(int dependencyCheck) { + this.dependencyCheck = dependencyCheck; + } + + /** + * Return the dependency check code. + */ + public int getDependencyCheck() { + return this.dependencyCheck; + } + + /** + * Set the names of the beans that this bean depends on being initialized. + * The bean factory will guarantee that these beans get initialized before. + *

Note that dependencies are normally expressed through bean properties or + * constructor arguments. This property should just be necessary for other kinds + * of dependencies like statics (*ugh*) or database preparation on startup. + */ + public void setDependsOn(String[] dependsOn) { + this.dependsOn = dependsOn; + } + + /** + * Return the bean names that this bean depends on. + */ + public String[] getDependsOn() { + return this.dependsOn; + } + + /** + * Set whether this bean is a candidate for getting autowired into some other bean. + */ + public void setAutowireCandidate(boolean autowireCandidate) { + this.autowireCandidate = autowireCandidate; + } + + /** + * Return whether this bean is a candidate for getting autowired into some other bean. + */ + public boolean isAutowireCandidate() { + return this.autowireCandidate; + } + + /** + * Register a qualifier to be used for autowire candidate resolution, + * keyed by the qualifier's type name. + * @see AutowireCandidateQualifier#getTypeName() + */ + public void addQualifier(AutowireCandidateQualifier qualifier) { + this.qualifiers.put(qualifier.getTypeName(), qualifier); + } + + /** + * Return whether this bean has the specified qualifier. + */ + public boolean hasQualifier(String typeName) { + return this.qualifiers.keySet().contains(typeName); + } + + /** + * Return the qualifier mapped to the provided type name. + */ + public AutowireCandidateQualifier getQualifier(String typeName) { + return (AutowireCandidateQualifier) this.qualifiers.get(typeName); + } + + /** + * Return all registered qualifiers. + * @return the Set of {@link AutowireCandidateQualifier} objects. + */ + public Set getQualifiers() { + return new LinkedHashSet(this.qualifiers.values()); + } + + /** + * Copy the qualifiers from the supplied AbstractBeanDefinition to this bean definition. + * @param source the AbstractBeanDefinition to copy from + */ + protected void copyQualifiersFrom(AbstractBeanDefinition source) { + Assert.notNull(source, "Source must not be null"); + this.qualifiers.putAll(source.qualifiers); + } + + /** + * Set whether this bean is a primary autowire candidate. + * If this value is true for exactly one bean among multiple + * matching candidates, it will serve as a tie-breaker. + */ + public void setPrimary(boolean primary) { + this.primary = primary; + } + + /** + * Return whether this bean is a primary autowire candidate. + * If this value is true for exactly one bean among multiple + * matching candidates, it will serve as a tie-breaker. + */ + public boolean isPrimary() { + return this.primary; + } + + + /** + * Specify constructor argument values for this bean. + */ + public void setConstructorArgumentValues(ConstructorArgumentValues constructorArgumentValues) { + this.constructorArgumentValues = + (constructorArgumentValues != null ? constructorArgumentValues : new ConstructorArgumentValues()); + } + + /** + * Return constructor argument values for this bean (never null). + */ + public ConstructorArgumentValues getConstructorArgumentValues() { + return this.constructorArgumentValues; + } + + /** + * Return if there are constructor argument values defined for this bean. + */ + public boolean hasConstructorArgumentValues() { + return !this.constructorArgumentValues.isEmpty(); + } + + /** + * Specify property values for this bean, if any. + */ + public void setPropertyValues(MutablePropertyValues propertyValues) { + this.propertyValues = (propertyValues != null ? propertyValues : new MutablePropertyValues()); + } + + /** + * Return property values for this bean (never null). + */ + public MutablePropertyValues getPropertyValues() { + return this.propertyValues; + } + + /** + * Specify method overrides for the bean, if any. + */ + public void setMethodOverrides(MethodOverrides methodOverrides) { + this.methodOverrides = (methodOverrides != null ? methodOverrides : new MethodOverrides()); + } + + /** + * Return information about methods to be overridden by the IoC + * container. This will be empty if there are no method overrides. + * Never returns null. + */ + public MethodOverrides getMethodOverrides() { + return this.methodOverrides; + } + + + public void setFactoryBeanName(String factoryBeanName) { + this.factoryBeanName = factoryBeanName; + } + + public String getFactoryBeanName() { + return this.factoryBeanName; + } + + public void setFactoryMethodName(String factoryMethodName) { + this.factoryMethodName = factoryMethodName; + } + + public String getFactoryMethodName() { + return this.factoryMethodName; + } + + /** + * Set the name of the initializer method. The default is null + * in which case there is no initializer method. + */ + public void setInitMethodName(String initMethodName) { + this.initMethodName = initMethodName; + } + + /** + * Return the name of the initializer method. + */ + public String getInitMethodName() { + return this.initMethodName; + } + + /** + * Specify whether or not the configured init method is the default. + * Default value is false. + * @see #setInitMethodName + */ + public void setEnforceInitMethod(boolean enforceInitMethod) { + this.enforceInitMethod = enforceInitMethod; + } + + /** + * Indicate whether the configured init method is the default. + * @see #getInitMethodName() + */ + public boolean isEnforceInitMethod() { + return this.enforceInitMethod; + } + + /** + * Set the name of the destroy method. The default is null + * in which case there is no destroy method. + */ + public void setDestroyMethodName(String destroyMethodName) { + this.destroyMethodName = destroyMethodName; + } + + /** + * Return the name of the destroy method. + */ + public String getDestroyMethodName() { + return this.destroyMethodName; + } + + /** + * Specify whether or not the configured destroy method is the default. + * Default value is false. + * @see #setDestroyMethodName + */ + public void setEnforceDestroyMethod(boolean enforceDestroyMethod) { + this.enforceDestroyMethod = enforceDestroyMethod; + } + + /** + * Indicate whether the configured destroy method is the default. + * @see #getDestroyMethodName + */ + public boolean isEnforceDestroyMethod() { + return this.enforceDestroyMethod; + } + + + /** + * Set whether this bean definition is 'synthetic', that is, not defined + * by the application itself (for example, an infrastructure bean such + * as a helper for auto-proxying, created through <aop:config>). + */ + public void setSynthetic(boolean synthetic) { + this.synthetic = synthetic; + } + + /** + * Return whether this bean definition is 'synthetic', that is, + * not defined by the application itself. + */ + public boolean isSynthetic() { + return this.synthetic; + } + + /** + * Set the role hint for this BeanDefinition. + */ + public void setRole(int role) { + this.role = role; + } + + /** + * Return the role hint for this BeanDefinition. + */ + public int getRole() { + return this.role; + } + + + /** + * Set a human-readable description of this bean definition. + */ + public void setDescription(String description) { + this.description = description; + } + + public String getDescription() { + return this.description; + } + + /** + * Set the resource that this bean definition came from + * (for the purpose of showing context in case of errors). + */ + public void setResource(Resource resource) { + this.resource = resource; + } + + /** + * Return the resource that this bean definition came from. + */ + public Resource getResource() { + return this.resource; + } + + /** + * Set a description of the resource that this bean definition + * came from (for the purpose of showing context in case of errors). + */ + public void setResourceDescription(String resourceDescription) { + this.resource = new DescriptiveResource(resourceDescription); + } + + public String getResourceDescription() { + return (this.resource != null ? this.resource.getDescription() : null); + } + + /** + * Set the originating (e.g. decorated) BeanDefinition, if any. + */ + public void setOriginatingBeanDefinition(BeanDefinition originatingBd) { + this.resource = new BeanDefinitionResource(originatingBd); + } + + public BeanDefinition getOriginatingBeanDefinition() { + return (this.resource instanceof BeanDefinitionResource ? + ((BeanDefinitionResource) this.resource).getBeanDefinition() : null); + } + + /** + * Validate this bean definition. + * @throws BeanDefinitionValidationException in case of validation failure + */ + public void validate() throws BeanDefinitionValidationException { + if (!getMethodOverrides().isEmpty() && getFactoryMethodName() != null) { + throw new BeanDefinitionValidationException( + "Cannot combine static factory method with method overrides: " + + "the static factory method must create the instance"); + } + + if (hasBeanClass()) { + prepareMethodOverrides(); + } + } + + /** + * Validate and prepare the method overrides defined for this bean. + * Checks for existence of a method with the specified name. + * @throws BeanDefinitionValidationException in case of validation failure + */ + public void prepareMethodOverrides() throws BeanDefinitionValidationException { + // Check that lookup methods exists. + MethodOverrides methodOverrides = getMethodOverrides(); + if (!methodOverrides.isEmpty()) { + for (Iterator it = methodOverrides.getOverrides().iterator(); it.hasNext(); ) { + MethodOverride mo = (MethodOverride) it.next(); + prepareMethodOverride(mo); + } + } + } + + /** + * Validate and prepare the given method override. + * Checks for existence of a method with the specified name, + * marking it as not overloaded if none found. + * @param mo the MethodOverride object to validate + * @throws BeanDefinitionValidationException in case of validation failure + */ + protected void prepareMethodOverride(MethodOverride mo) throws BeanDefinitionValidationException { + int count = ClassUtils.getMethodCountForName(getBeanClass(), mo.getMethodName()); + if (count == 0) { + throw new BeanDefinitionValidationException( + "Invalid method override: no method with name '" + mo.getMethodName() + + "' on class [" + getBeanClassName() + "]"); + } + else if (count == 1) { + // Mark override as not overloaded, to avoid the overhead of arg type checking. + mo.setOverloaded(false); + } + } + + + /** + * Public declaration of Object's clone() method. + * Delegates to {@link #cloneBeanDefinition()}. + * @see java.lang.Object#clone() + */ + public Object clone() { + return cloneBeanDefinition(); + } + + /** + * Clone this bean definition. + * To be implemented by concrete subclasses. + * @return the cloned bean definition object + */ + public abstract AbstractBeanDefinition cloneBeanDefinition(); + + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof AbstractBeanDefinition)) { + return false; + } + + AbstractBeanDefinition that = (AbstractBeanDefinition) other; + + if (!ObjectUtils.nullSafeEquals(getBeanClassName(), that.getBeanClassName())) return false; + if (!ObjectUtils.nullSafeEquals(this.scope, that.scope)) return false; + if (this.abstractFlag != that.abstractFlag) return false; + if (this.lazyInit != that.lazyInit) return false; + + if (this.autowireMode != that.autowireMode) return false; + if (this.dependencyCheck != that.dependencyCheck) return false; + if (!Arrays.equals(this.dependsOn, that.dependsOn)) return false; + if (this.autowireCandidate != that.autowireCandidate) return false; + if (!ObjectUtils.nullSafeEquals(this.qualifiers, that.qualifiers)) return false; + if (this.primary != that.primary) return false; + + if (!ObjectUtils.nullSafeEquals(this.constructorArgumentValues, that.constructorArgumentValues)) return false; + if (!ObjectUtils.nullSafeEquals(this.propertyValues, that.propertyValues)) return false; + if (!ObjectUtils.nullSafeEquals(this.methodOverrides, that.methodOverrides)) return false; + + if (!ObjectUtils.nullSafeEquals(this.factoryBeanName, that.factoryBeanName)) return false; + if (!ObjectUtils.nullSafeEquals(this.factoryMethodName, that.factoryMethodName)) return false; + if (!ObjectUtils.nullSafeEquals(this.initMethodName, that.initMethodName)) return false; + if (this.enforceInitMethod != that.enforceInitMethod) return false; + if (!ObjectUtils.nullSafeEquals(this.destroyMethodName, that.destroyMethodName)) return false; + if (this.enforceDestroyMethod != that.enforceDestroyMethod) return false; + + if (this.synthetic != that.synthetic) return false; + if (this.role != that.role) return false; + + return super.equals(other); + } + + public int hashCode() { + int hashCode = ObjectUtils.nullSafeHashCode(getBeanClassName()); + hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.scope); + hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.constructorArgumentValues); + hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.propertyValues); + hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.factoryBeanName); + hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.factoryMethodName); + hashCode = 29 * hashCode + super.hashCode(); + return hashCode; + } + + public String toString() { + StringBuffer sb = new StringBuffer("class ["); + sb.append(getBeanClassName()).append("]"); + sb.append("; scope=").append(this.scope); + sb.append("; abstract=").append(this.abstractFlag); + sb.append("; lazyInit=").append(this.lazyInit); + sb.append("; autowireMode=").append(this.autowireMode); + sb.append("; dependencyCheck=").append(this.dependencyCheck); + sb.append("; autowireCandidate=").append(this.autowireCandidate); + sb.append("; primary=").append(this.primary); + sb.append("; factoryBeanName=").append(this.factoryBeanName); + sb.append("; factoryMethodName=").append(this.factoryMethodName); + sb.append("; initMethodName=").append(this.initMethodName); + sb.append("; destroyMethodName=").append(this.destroyMethodName); + if (this.resource != null) { + sb.append("; defined in ").append(this.resource.getDescription()); + } + return sb.toString(); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinitionReader.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinitionReader.java new file mode 100644 index 0000000000..be2ccba23c --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinitionReader.java @@ -0,0 +1,217 @@ +/* + * Copyright 2002-2007 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.io.IOException; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.util.Assert; + +/** + * Abstract base class for bean definition readers which implement + * the {@link BeanDefinitionReader} interface. + * + *

Provides common properties like the bean factory to work on + * and the class loader to use for loading bean classes. + * + * @author Juergen Hoeller + * @since 11.12.2003 + * @see BeanDefinitionReaderUtils + */ +public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader { + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + private final BeanDefinitionRegistry registry; + + private ResourceLoader resourceLoader; + + private ClassLoader beanClassLoader; + + private BeanNameGenerator beanNameGenerator = new DefaultBeanNameGenerator(); + + + /** + * Create a new AbstractBeanDefinitionReader for the given bean factory. + *

If the passed-in bean factory does not only implement the BeanDefinitionRegistry + * interface but also the ResourceLoader interface, it will be used as default + * ResourceLoader as well. This will usually be the case for + * {@link org.springframework.context.ApplicationContext} implementations. + *

If given a plain BeanDefinitionRegistry, the default ResourceLoader will be a + * {@link org.springframework.core.io.support.PathMatchingResourcePatternResolver}. + * @param registry the BeanFactory to load bean definitions into, + * in the form of a BeanDefinitionRegistry + * @see #setResourceLoader + */ + protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) { + Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); + this.registry = registry; + + // Determine ResourceLoader to use. + if (this.registry instanceof ResourceLoader) { + this.resourceLoader = (ResourceLoader) this.registry; + } + else { + this.resourceLoader = new PathMatchingResourcePatternResolver(); + } + } + + + public final BeanDefinitionRegistry getBeanFactory() { + return this.registry; + } + + public final BeanDefinitionRegistry getRegistry() { + return this.registry; + } + + /** + * Set the ResourceLoader to use for resource locations. + * If specifying a ResourcePatternResolver, the bean definition reader + * will be capable of resolving resource patterns to Resource arrays. + *

Default is PathMatchingResourcePatternResolver, also capable of + * resource pattern resolving through the ResourcePatternResolver interface. + *

Setting this to null suggests that absolute resource loading + * is not available for this bean definition reader. + * @see org.springframework.core.io.support.ResourcePatternResolver + * @see org.springframework.core.io.support.PathMatchingResourcePatternResolver + */ + public void setResourceLoader(ResourceLoader resourceLoader) { + this.resourceLoader = resourceLoader; + } + + public ResourceLoader getResourceLoader() { + return this.resourceLoader; + } + + /** + * Set the ClassLoader to use for bean classes. + *

Default is null, which suggests to not load bean classes + * eagerly but rather to just register bean definitions with class names, + * with the corresponding Classes to be resolved later (or never). + * @see java.lang.Thread#getContextClassLoader() + */ + public void setBeanClassLoader(ClassLoader beanClassLoader) { + this.beanClassLoader = beanClassLoader; + } + + public ClassLoader getBeanClassLoader() { + return this.beanClassLoader; + } + + /** + * Set the BeanNameGenerator to use for anonymous beans + * (without explicit bean name specified). + *

Default is a {@link DefaultBeanNameGenerator}. + */ + public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) { + this.beanNameGenerator = (beanNameGenerator != null ? beanNameGenerator : new DefaultBeanNameGenerator()); + } + + public BeanNameGenerator getBeanNameGenerator() { + return this.beanNameGenerator; + } + + + public int loadBeanDefinitions(Resource[] resources) throws BeanDefinitionStoreException { + Assert.notNull(resources, "Resource array must not be null"); + int counter = 0; + for (int i = 0; i < resources.length; i++) { + counter += loadBeanDefinitions(resources[i]); + } + return counter; + } + + public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException { + return loadBeanDefinitions(location, null); + } + + /** + * Load bean definitions from the specified resource location. + *

The location can also be a location pattern, provided that the + * ResourceLoader of this bean definition reader is a ResourcePatternResolver. + * @param location the resource location, to be loaded with the ResourceLoader + * (or ResourcePatternResolver) of this bean definition reader + * @param actualResources a Set to be filled with the actual Resource objects + * that have been resolved during the loading process. May be null + * to indicate that the caller is not interested in those Resource objects. + * @return the number of bean definitions found + * @throws BeanDefinitionStoreException in case of loading or parsing errors + * @see #getResourceLoader() + * @see #loadBeanDefinitions(org.springframework.core.io.Resource) + * @see #loadBeanDefinitions(org.springframework.core.io.Resource[]) + */ + public int loadBeanDefinitions(String location, Set actualResources) throws BeanDefinitionStoreException { + ResourceLoader resourceLoader = getResourceLoader(); + if (resourceLoader == null) { + throw new BeanDefinitionStoreException( + "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available"); + } + + if (resourceLoader instanceof ResourcePatternResolver) { + // Resource pattern matching available. + try { + Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); + int loadCount = loadBeanDefinitions(resources); + if (actualResources != null) { + for (int i = 0; i < resources.length; i++) { + actualResources.add(resources[i]); + } + } + if (logger.isDebugEnabled()) { + logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]"); + } + return loadCount; + } + catch (IOException ex) { + throw new BeanDefinitionStoreException( + "Could not resolve bean definition resource pattern [" + location + "]", ex); + } + } + else { + // Can only load single resources by absolute URL. + Resource resource = resourceLoader.getResource(location); + int loadCount = loadBeanDefinitions(resource); + if (actualResources != null) { + actualResources.add(resource); + } + if (logger.isDebugEnabled()) { + logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]"); + } + return loadCount; + } + } + + public int loadBeanDefinitions(String[] locations) throws BeanDefinitionStoreException { + Assert.notNull(locations, "Location array must not be null"); + int counter = 0; + for (int i = 0; i < locations.length; i++) { + counter += loadBeanDefinitions(locations[i]); + } + return counter; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java new file mode 100644 index 0000000000..e6aa0066e2 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java @@ -0,0 +1,1411 @@ +/* + * Copyright 2002-2008 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.beans.PropertyEditor; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.BeansException; +import org.springframework.beans.PropertyEditorRegistrar; +import org.springframework.beans.PropertyEditorRegistry; +import org.springframework.beans.PropertyEditorRegistrySupport; +import org.springframework.beans.SimpleTypeConverter; +import org.springframework.beans.TypeConverter; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.BeanCurrentlyInCreationException; +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.BeanIsAbstractException; +import org.springframework.beans.factory.BeanIsNotAFactoryException; +import org.springframework.beans.factory.BeanNotOfRequiredTypeException; +import org.springframework.beans.factory.CannotLoadBeanClassException; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.SmartFactoryBean; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor; +import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor; +import org.springframework.beans.factory.config.Scope; +import org.springframework.core.CollectionFactory; +import org.springframework.core.DecoratingClassLoader; +import org.springframework.core.NamedThreadLocal; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; + +/** + * Abstract base class for {@link org.springframework.beans.factory.BeanFactory} + * implementations, providing the full capabilities of the + * {@link org.springframework.beans.factory.config.ConfigurableBeanFactory} SPI. + * Does not assume a listable bean factory: can therefore also be used + * as base class for bean factory implementations which obtain bean definitions + * from some backend resource (where bean definition access is an expensive operation). + * + *

This class provides a singleton cache (through its base class + * {@link org.springframework.beans.factory.support.DefaultSingletonBeanRegistry}, + * singleton/prototype determination, {@link org.springframework.beans.factory.FactoryBean} + * handling, aliases, bean definition merging for child bean definitions, + * and bean destruction ({@link org.springframework.beans.factory.DisposableBean} + * interface, custom destroy methods). Furthermore, it can manage a bean factory + * hierarchy (delegating to the parent in case of an unknown bean), through implementing + * the {@link org.springframework.beans.factory.HierarchicalBeanFactory} interface. + * + *

The main template methods to be implemented by subclasses are + * {@link #getBeanDefinition} and {@link #createBean}, retrieving a bean definition + * for a given bean name and creating a bean instance for a given bean definition, + * respectively. Default implementations of those operations can be found in + * {@link DefaultListableBeanFactory} and {@link AbstractAutowireCapableBeanFactory}. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 15 April 2001 + * @see #getBeanDefinition + * @see #createBean + * @see AbstractAutowireCapableBeanFactory#createBean + * @see DefaultListableBeanFactory#getBeanDefinition + */ +public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory { + + /** Parent bean factory, for bean inheritance support */ + private BeanFactory parentBeanFactory; + + /** ClassLoader to resolve bean class names with, if necessary */ + private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); + + /** ClassLoader to temporarily resolve bean class names with, if necessary */ + private ClassLoader tempClassLoader; + + /** Whether to cache bean metadata or rather reobtain it for every access */ + private boolean cacheBeanMetadata = true; + + /** Custom PropertyEditorRegistrars to apply to the beans of this factory */ + private final Set propertyEditorRegistrars = new LinkedHashSet(4); + + /** Custom PropertyEditors to apply to the beans of this factory */ + private final Map customEditors = new HashMap(4); + + /** A custom TypeConverter to use, overriding the default PropertyEditor mechanism */ + private TypeConverter typeConverter; + + /** BeanPostProcessors to apply in createBean */ + private final List beanPostProcessors = new ArrayList(); + + /** Indicates whether any InstantiationAwareBeanPostProcessors have been registered */ + private boolean hasInstantiationAwareBeanPostProcessors; + + /** Indicates whether any DestructionAwareBeanPostProcessors have been registered */ + private boolean hasDestructionAwareBeanPostProcessors; + + /** Map from scope identifier String to corresponding Scope */ + private final Map scopes = new HashMap(); + + /** Map from bean name to merged RootBeanDefinition */ + private final Map mergedBeanDefinitions = CollectionFactory.createConcurrentMapIfPossible(16); + + /** Names of beans that have already been created at least once */ + private final Set alreadyCreated = Collections.synchronizedSet(new HashSet()); + + /** Names of beans that are currently in creation */ + private final ThreadLocal prototypesCurrentlyInCreation = + new NamedThreadLocal("Prototype beans currently in creation"); + + + /** + * Create a new AbstractBeanFactory. + */ + public AbstractBeanFactory() { + } + + /** + * Create a new AbstractBeanFactory with the given parent. + * @param parentBeanFactory parent bean factory, or null if none + * @see #getBean + */ + public AbstractBeanFactory(BeanFactory parentBeanFactory) { + this.parentBeanFactory = parentBeanFactory; + } + + + //--------------------------------------------------------------------- + // Implementation of BeanFactory interface + //--------------------------------------------------------------------- + + public Object getBean(String name) throws BeansException { + return getBean(name, null, null); + } + + public Object getBean(String name, Class requiredType) throws BeansException { + return getBean(name, requiredType, null); + } + + public Object getBean(String name, Object[] args) throws BeansException { + return getBean(name, null, args); + } + + /** + * Return an instance, which may be shared or independent, of the specified bean. + * @param name the name of the bean to retrieve + * @param requiredType the required type of the bean to retrieve + * @param args arguments to use if creating a prototype using explicit arguments to a + * static factory method. It is invalid to use a non-null args value in any other case. + * @return an instance of the bean + * @throws BeansException if the bean could not be created + */ + public Object getBean(String name, Class requiredType, Object[] args) throws BeansException { + return doGetBean(name, requiredType, args, false); + } + + /** + * Return an instance, which may be shared or independent, of the specified bean. + * @param name the name of the bean to retrieve + * @param requiredType the required type of the bean to retrieve + * @param args arguments to use if creating a prototype using explicit arguments to a + * static factory method. It is invalid to use a non-null args value in any other case. + * @param typeCheckOnly whether the instance is obtained for a type check, + * not for actual use + * @return an instance of the bean + * @throws BeansException if the bean could not be created + */ + protected Object doGetBean( + final String name, final Class requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException { + + final String beanName = transformedBeanName(name); + Object bean = null; + + // Eagerly check singleton cache for manually registered singletons. + Object sharedInstance = getSingleton(beanName); + if (sharedInstance != null && args == null) { + if (logger.isDebugEnabled()) { + if (isSingletonCurrentlyInCreation(beanName)) { + logger.debug("Returning eagerly cached instance of singleton bean '" + beanName + + "' that is not fully initialized yet - a consequence of a circular reference"); + } + else { + logger.debug("Returning cached instance of singleton bean '" + beanName + "'"); + } + } + bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); + } + + else { + // Fail if we're already creating this bean instance: + // We're assumably within a circular reference. + if (isPrototypeCurrentlyInCreation(beanName)) { + throw new BeanCurrentlyInCreationException(beanName); + } + + // Check if bean definition exists in this factory. + BeanFactory parentBeanFactory = getParentBeanFactory(); + if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { + // Not found -> check parent. + String nameToLookup = originalBeanName(name); + if (args != null) { + // Delegation to parent with explicit args. + return parentBeanFactory.getBean(nameToLookup, args); + } + else { + // No args -> delegate to standard getBean method. + return parentBeanFactory.getBean(nameToLookup, requiredType); + } + } + + if (!typeCheckOnly) { + markBeanAsCreated(beanName); + } + + final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); + checkMergedBeanDefinition(mbd, beanName, args); + + // Guarantee initialization of beans that the current bean depends on. + String[] dependsOn = mbd.getDependsOn(); + if (dependsOn != null) { + for (int i = 0; i < dependsOn.length; i++) { + String dependsOnBean = dependsOn[i]; + getBean(dependsOnBean); + registerDependentBean(dependsOnBean, beanName); + } + } + + // Create bean instance. + if (mbd.isSingleton()) { + sharedInstance = getSingleton(beanName, new ObjectFactory() { + public Object getObject() throws BeansException { + try { + return createBean(beanName, mbd, args); + } + catch (BeansException ex) { + // Explicitly remove instance from singleton cache: It might have been put there + // eagerly by the creation process, to allow for circular reference resolution. + // Also remove any beans that received a temporary reference to the bean. + destroySingleton(beanName); + throw ex; + } + } + }); + bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); + } + + else if (mbd.isPrototype()) { + // It's a prototype -> create a new instance. + Object prototypeInstance = null; + try { + beforePrototypeCreation(beanName); + prototypeInstance = createBean(beanName, mbd, args); + } + finally { + afterPrototypeCreation(beanName); + } + bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); + } + + else { + String scopeName = mbd.getScope(); + final Scope scope = (Scope) this.scopes.get(scopeName); + if (scope == null) { + throw new IllegalStateException("No Scope registered for scope '" + scopeName + "'"); + } + try { + Object scopedInstance = scope.get(beanName, new ObjectFactory() { + public Object getObject() throws BeansException { + beforePrototypeCreation(beanName); + try { + return createBean(beanName, mbd, args); + } + finally { + afterPrototypeCreation(beanName); + } + } + }); + bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); + } + catch (IllegalStateException ex) { + throw new BeanCreationException(beanName, + "Scope '" + scopeName + "' is not active for the current thread; " + + "consider defining a scoped proxy for this bean if you intend to refer to it from a singleton", + ex); + } + } + } + + // Check if required type matches the type of the actual bean instance. + if (requiredType != null && bean != null && !requiredType.isAssignableFrom(bean.getClass())) { + throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); + } + return bean; + } + + public boolean containsBean(String name) { + String beanName = transformedBeanName(name); + if (containsSingleton(beanName) || containsBeanDefinition(beanName)) { + return (!BeanFactoryUtils.isFactoryDereference(name) || isFactoryBean(name)); + } + // Not found -> check parent. + BeanFactory parentBeanFactory = getParentBeanFactory(); + return (parentBeanFactory != null && parentBeanFactory.containsBean(originalBeanName(name))); + } + + public boolean isSingleton(String name) throws NoSuchBeanDefinitionException { + String beanName = transformedBeanName(name); + + Object beanInstance = getSingleton(beanName, false); + if (beanInstance != null) { + if (beanInstance instanceof FactoryBean) { + return (BeanFactoryUtils.isFactoryDereference(name) || ((FactoryBean) beanInstance).isSingleton()); + } + else { + return !BeanFactoryUtils.isFactoryDereference(name); + } + } + + else { + // No singleton instance found -> check bean definition. + BeanFactory parentBeanFactory = getParentBeanFactory(); + if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { + // No bean definition found in this factory -> delegate to parent. + return parentBeanFactory.isSingleton(originalBeanName(name)); + } + + RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); + + // In case of FactoryBean, return singleton status of created object if not a dereference. + if (mbd.isSingleton()) { + if (isFactoryBean(beanName, mbd)) { + if (BeanFactoryUtils.isFactoryDereference(name)) { + return true; + } + FactoryBean factoryBean = (FactoryBean) getBean(FACTORY_BEAN_PREFIX + beanName); + return factoryBean.isSingleton(); + } + else { + return !BeanFactoryUtils.isFactoryDereference(name); + } + } + else { + return false; + } + } + } + + public boolean isPrototype(String name) throws NoSuchBeanDefinitionException { + String beanName = transformedBeanName(name); + + BeanFactory parentBeanFactory = getParentBeanFactory(); + if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { + // No bean definition found in this factory -> delegate to parent. + return parentBeanFactory.isPrototype(originalBeanName(name)); + } + + RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); + if (mbd.isPrototype()) { + // In case of FactoryBean, return singleton status of created object if not a dereference. + return (!BeanFactoryUtils.isFactoryDereference(name) || isFactoryBean(beanName, mbd)); + } + else { + // Singleton or scoped - not a prototype. + // However, FactoryBean may still produce a prototype object... + if (BeanFactoryUtils.isFactoryDereference(name)) { + return false; + } + if (isFactoryBean(beanName, mbd)) { + FactoryBean factoryBean = (FactoryBean) getBean(FACTORY_BEAN_PREFIX + beanName); + return ((factoryBean instanceof SmartFactoryBean && ((SmartFactoryBean) factoryBean).isPrototype()) || + !factoryBean.isSingleton()); + } + else { + return false; + } + } + } + + public boolean isTypeMatch(String name, Class targetType) throws NoSuchBeanDefinitionException { + String beanName = transformedBeanName(name); + Class typeToMatch = (targetType != null ? targetType : Object.class); + + // Check manually registered singletons. + Object beanInstance = getSingleton(beanName, false); + if (beanInstance != null) { + if (beanInstance instanceof FactoryBean) { + if (!BeanFactoryUtils.isFactoryDereference(name)) { + Class type = getTypeForFactoryBean((FactoryBean) beanInstance); + return (type != null && typeToMatch.isAssignableFrom(type)); + } + else { + return typeToMatch.isAssignableFrom(beanInstance.getClass()) ; + } + } + else { + return !BeanFactoryUtils.isFactoryDereference(name) && + typeToMatch.isAssignableFrom(beanInstance.getClass()); + } + } + + else { + // No singleton instance found -> check bean definition. + BeanFactory parentBeanFactory = getParentBeanFactory(); + if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { + // No bean definition found in this factory -> delegate to parent. + return parentBeanFactory.isTypeMatch(originalBeanName(name), targetType); + } + + RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); + Class beanClass = predictBeanType(beanName, mbd, new Class[] {FactoryBean.class, typeToMatch}); + if (beanClass == null) { + return false; + } + + // Check bean class whether we're dealing with a FactoryBean. + if (FactoryBean.class.isAssignableFrom(beanClass)) { + if (!BeanFactoryUtils.isFactoryDereference(name)) { + // If it's a FactoryBean, we want to look at what it creates, not the factory class. + Class type = getTypeForFactoryBean(beanName, mbd); + return (type != null && typeToMatch.isAssignableFrom(type)); + } + else { + return typeToMatch.isAssignableFrom(beanClass); + } + } + else { + return !BeanFactoryUtils.isFactoryDereference(name) && + typeToMatch.isAssignableFrom(beanClass); + } + } + } + + public Class getType(String name) throws NoSuchBeanDefinitionException { + String beanName = transformedBeanName(name); + + // Check manually registered singletons. + Object beanInstance = getSingleton(beanName, false); + if (beanInstance != null) { + if (beanInstance instanceof FactoryBean && !BeanFactoryUtils.isFactoryDereference(name)) { + return getTypeForFactoryBean((FactoryBean) beanInstance); + } + else { + return beanInstance.getClass(); + } + } + + else { + // No singleton instance found -> check bean definition. + BeanFactory parentBeanFactory = getParentBeanFactory(); + if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { + // No bean definition found in this factory -> delegate to parent. + return parentBeanFactory.getType(originalBeanName(name)); + } + + RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); + Class beanClass = predictBeanType(beanName, mbd, null); + + // Check bean class whether we're dealing with a FactoryBean. + if (beanClass != null && FactoryBean.class.isAssignableFrom(beanClass)) { + if (!BeanFactoryUtils.isFactoryDereference(name)) { + // If it's a FactoryBean, we want to look at what it creates, not the factory class. + return getTypeForFactoryBean(beanName, mbd); + } + else { + return beanClass; + } + } + else { + return (!BeanFactoryUtils.isFactoryDereference(name) ? beanClass : null); + } + } + } + + public String[] getAliases(String name) { + String beanName = transformedBeanName(name); + List aliases = new ArrayList(); + boolean factoryPrefix = name.startsWith(FACTORY_BEAN_PREFIX); + String fullBeanName = beanName; + if (factoryPrefix) { + fullBeanName = FACTORY_BEAN_PREFIX + beanName; + } + if (!fullBeanName.equals(name)) { + aliases.add(fullBeanName); + } + String[] retrievedAliases = super.getAliases(beanName); + for (int i = 0; i < retrievedAliases.length; i++) { + String alias = (factoryPrefix ? FACTORY_BEAN_PREFIX : "") + retrievedAliases[i]; + if (!alias.equals(name)) { + aliases.add(alias); + } + } + if (!containsSingleton(beanName) && !containsBeanDefinition(beanName)) { + BeanFactory parentBeanFactory = getParentBeanFactory(); + if (parentBeanFactory != null) { + aliases.addAll(Arrays.asList(parentBeanFactory.getAliases(fullBeanName))); + } + } + return StringUtils.toStringArray(aliases); + } + + + //--------------------------------------------------------------------- + // Implementation of HierarchicalBeanFactory interface + //--------------------------------------------------------------------- + + public BeanFactory getParentBeanFactory() { + return this.parentBeanFactory; + } + + public boolean containsLocalBean(String name) { + String beanName = transformedBeanName(name); + return ((containsSingleton(beanName) || containsBeanDefinition(beanName)) && + (!BeanFactoryUtils.isFactoryDereference(name) || isFactoryBean(beanName))); + } + + + //--------------------------------------------------------------------- + // Implementation of ConfigurableBeanFactory interface + //--------------------------------------------------------------------- + + public void setParentBeanFactory(BeanFactory parentBeanFactory) { + if (this.parentBeanFactory != null && this.parentBeanFactory != parentBeanFactory) { + throw new IllegalStateException("Already associated with parent BeanFactory: " + this.parentBeanFactory); + } + this.parentBeanFactory = parentBeanFactory; + } + + public void setBeanClassLoader(ClassLoader beanClassLoader) { + this.beanClassLoader = (beanClassLoader != null ? beanClassLoader : ClassUtils.getDefaultClassLoader()); + } + + public ClassLoader getBeanClassLoader() { + return this.beanClassLoader; + } + + public void setTempClassLoader(ClassLoader tempClassLoader) { + this.tempClassLoader = tempClassLoader; + } + + public ClassLoader getTempClassLoader() { + return this.tempClassLoader; + } + + public void setCacheBeanMetadata(boolean cacheBeanMetadata) { + this.cacheBeanMetadata = cacheBeanMetadata; + } + + public boolean isCacheBeanMetadata() { + return this.cacheBeanMetadata; + } + + public void addPropertyEditorRegistrar(PropertyEditorRegistrar registrar) { + Assert.notNull(registrar, "PropertyEditorRegistrar must not be null"); + this.propertyEditorRegistrars.add(registrar); + } + + /** + * Return the set of PropertyEditorRegistrars. + */ + public Set getPropertyEditorRegistrars() { + return this.propertyEditorRegistrars; + } + + public void registerCustomEditor(Class requiredType, Class propertyEditorClass) { + Assert.notNull(requiredType, "Required type must not be null"); + Assert.isAssignable(PropertyEditor.class, propertyEditorClass); + this.customEditors.put(requiredType, propertyEditorClass); + } + + public void registerCustomEditor(Class requiredType, PropertyEditor propertyEditor) { + Assert.notNull(requiredType, "Required type must not be null"); + Assert.notNull(propertyEditor, "PropertyEditor must not be null"); + this.customEditors.put(requiredType, propertyEditor); + } + + public void copyRegisteredEditorsTo(PropertyEditorRegistry registry) { + registerCustomEditors(registry); + } + + /** + * Return the map of custom editors, with Classes as keys + * and PropertyEditor instances or PropertyEditor classes as values. + */ + public Map getCustomEditors() { + return this.customEditors; + } + + public void setTypeConverter(TypeConverter typeConverter) { + this.typeConverter = typeConverter; + } + + /** + * Return the custom TypeConverter to use, if any. + * @return the custom TypeConverter, or null if none specified + */ + protected TypeConverter getCustomTypeConverter() { + return this.typeConverter; + } + + public TypeConverter getTypeConverter() { + TypeConverter customConverter = getCustomTypeConverter(); + if (customConverter != null) { + return customConverter; + } + else { + // Build default TypeConverter, registering custom editors. + SimpleTypeConverter typeConverter = new SimpleTypeConverter(); + registerCustomEditors(typeConverter); + return typeConverter; + } + } + + public void addBeanPostProcessor(BeanPostProcessor beanPostProcessor) { + Assert.notNull(beanPostProcessor, "BeanPostProcessor must not be null"); + this.beanPostProcessors.add(beanPostProcessor); + if (beanPostProcessor instanceof InstantiationAwareBeanPostProcessor) { + this.hasInstantiationAwareBeanPostProcessors = true; + } + if (beanPostProcessor instanceof DestructionAwareBeanPostProcessor) { + this.hasDestructionAwareBeanPostProcessors = true; + } + } + + public int getBeanPostProcessorCount() { + return this.beanPostProcessors.size(); + } + + /** + * Return the list of BeanPostProcessors that will get applied + * to beans created with this factory. + */ + public List getBeanPostProcessors() { + return this.beanPostProcessors; + } + + /** + * Return whether this factory holds a InstantiationAwareBeanPostProcessor + * that will get applied to singleton beans on shutdown. + * @see #addBeanPostProcessor + * @see org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor + */ + protected boolean hasInstantiationAwareBeanPostProcessors() { + return this.hasInstantiationAwareBeanPostProcessors; + } + + /** + * Return whether this factory holds a DestructionAwareBeanPostProcessor + * that will get applied to singleton beans on shutdown. + * @see #addBeanPostProcessor + * @see org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor + */ + protected boolean hasDestructionAwareBeanPostProcessors() { + return this.hasDestructionAwareBeanPostProcessors; + } + + public void registerScope(String scopeName, Scope scope) { + Assert.notNull(scopeName, "Scope identifier must not be null"); + Assert.notNull(scope, "Scope must not be null"); + if (SCOPE_SINGLETON.equals(scopeName) || SCOPE_PROTOTYPE.equals(scopeName)) { + throw new IllegalArgumentException("Cannot replace existing scopes 'singleton' and 'prototype'"); + } + this.scopes.put(scopeName, scope); + } + + public String[] getRegisteredScopeNames() { + return StringUtils.toStringArray(this.scopes.keySet()); + } + + public Scope getRegisteredScope(String scopeName) { + Assert.notNull(scopeName, "Scope identifier must not be null"); + return (Scope) this.scopes.get(scopeName); + } + + public void copyConfigurationFrom(ConfigurableBeanFactory otherFactory) { + Assert.notNull(otherFactory, "BeanFactory must not be null"); + setBeanClassLoader(otherFactory.getBeanClassLoader()); + setCacheBeanMetadata(otherFactory.isCacheBeanMetadata()); + if (otherFactory instanceof AbstractBeanFactory) { + AbstractBeanFactory otherAbstractFactory = (AbstractBeanFactory) otherFactory; + this.customEditors.putAll(otherAbstractFactory.customEditors); + this.propertyEditorRegistrars.addAll(otherAbstractFactory.propertyEditorRegistrars); + this.beanPostProcessors.addAll(otherAbstractFactory.beanPostProcessors); + this.hasInstantiationAwareBeanPostProcessors = this.hasInstantiationAwareBeanPostProcessors || + otherAbstractFactory.hasInstantiationAwareBeanPostProcessors; + this.hasDestructionAwareBeanPostProcessors = this.hasDestructionAwareBeanPostProcessors || + otherAbstractFactory.hasDestructionAwareBeanPostProcessors; + this.scopes.putAll(otherAbstractFactory.scopes); + } + } + + /** + * Return a 'merged' BeanDefinition for the given bean name, + * merging a child bean definition with its parent if necessary. + *

This getMergedBeanDefinition considers bean definition + * in ancestors as well. + * @param name the name of the bean to retrieve the merged definition for + * (may be an alias) + * @return a (potentially merged) RootBeanDefinition for the given bean + * @throws NoSuchBeanDefinitionException if there is no bean with the given name + * @throws BeanDefinitionStoreException in case of an invalid bean definition + */ + public BeanDefinition getMergedBeanDefinition(String name) throws BeansException { + String beanName = transformedBeanName(name); + + // Efficiently check whether bean definition exists in this factory. + if (!containsBeanDefinition(beanName) && getParentBeanFactory() instanceof ConfigurableBeanFactory) { + return ((ConfigurableBeanFactory) getParentBeanFactory()).getMergedBeanDefinition(beanName); + } + // Resolve merged bean definition locally. + return getMergedLocalBeanDefinition(beanName); + } + + public boolean isFactoryBean(String name) throws NoSuchBeanDefinitionException { + String beanName = transformedBeanName(name); + + Object beanInstance = getSingleton(beanName, false); + if (beanInstance != null) { + return (beanInstance instanceof FactoryBean); + } + + // No singleton instance found -> check bean definition. + if (!containsBeanDefinition(beanName) && getParentBeanFactory() instanceof ConfigurableBeanFactory) { + // No bean definition found in this factory -> delegate to parent. + return ((ConfigurableBeanFactory) getParentBeanFactory()).isFactoryBean(name); + } + + return isFactoryBean(beanName, getMergedLocalBeanDefinition(beanName)); + } + + /** + * Callback before prototype creation. + *

The default implementation register the prototype as currently in creation. + * @param beanName the name of the prototype about to be created + * @see #isPrototypeCurrentlyInCreation + */ + protected void beforePrototypeCreation(String beanName) { + Object curVal = this.prototypesCurrentlyInCreation.get(); + if (curVal == null) { + this.prototypesCurrentlyInCreation.set(beanName); + } + else if (curVal instanceof String) { + Set beanNameSet = new HashSet(2); + beanNameSet.add(curVal); + beanNameSet.add(beanName); + this.prototypesCurrentlyInCreation.set(beanNameSet); + } + else { + Set beanNameSet = (Set) curVal; + beanNameSet.add(beanName); + } + } + + /** + * Callback after prototype creation. + *

The default implementation marks the prototype as not in creation anymore. + * @param beanName the name of the prototype that has been created + * @see #isPrototypeCurrentlyInCreation + */ + protected void afterPrototypeCreation(String beanName) { + Object curVal = this.prototypesCurrentlyInCreation.get(); + if (curVal instanceof String) { + this.prototypesCurrentlyInCreation.set(null); + } + else if (curVal instanceof Set) { + Set beanNameSet = (Set) curVal; + beanNameSet.remove(beanName); + if (beanNameSet.isEmpty()) { + this.prototypesCurrentlyInCreation.set(null); + } + } + } + + /** + * Return whether the specified prototype bean is currently in creation + * (within the current thread). + * @param beanName the name of the bean + */ + protected final boolean isPrototypeCurrentlyInCreation(String beanName) { + Object curVal = this.prototypesCurrentlyInCreation.get(); + return (curVal != null && + (curVal.equals(beanName) || (curVal instanceof Set && ((Set) curVal).contains(beanName)))); + } + + public boolean isCurrentlyInCreation(String beanName) { + return isSingletonCurrentlyInCreation(beanName) || isPrototypeCurrentlyInCreation(beanName); + } + + public void destroyBean(String beanName, Object beanInstance) { + destroyBean(beanName, beanInstance, getMergedLocalBeanDefinition(beanName)); + } + + /** + * Destroy the given bean instance (usually a prototype instance + * obtained from this factory) according to the given bean definition. + * @param beanName the name of the bean definition + * @param beanInstance the bean instance to destroy + * @param mbd the merged bean definition + */ + protected void destroyBean(String beanName, Object beanInstance, RootBeanDefinition mbd) { + new DisposableBeanAdapter(beanInstance, beanName, mbd, getBeanPostProcessors()).destroy(); + } + + public void destroyScopedBean(String beanName) { + RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); + if (mbd.isSingleton() || mbd.isPrototype()) { + throw new IllegalArgumentException( + "Bean name '" + beanName + "' does not correspond to an object in a Scope"); + } + String scopeName = mbd.getScope(); + Scope scope = (Scope) this.scopes.get(scopeName); + if (scope == null) { + throw new IllegalStateException("No Scope registered for scope '" + scopeName + "'"); + } + Object bean = scope.remove(beanName); + if (bean != null) { + destroyBean(beanName, bean, mbd); + } + } + + + //--------------------------------------------------------------------- + // Implementation methods + //--------------------------------------------------------------------- + + /** + * Return the bean name, stripping out the factory dereference prefix if necessary, + * and resolving aliases to canonical names. + * @param name the user-specified name + * @return the transformed bean name + */ + protected String transformedBeanName(String name) { + return canonicalName(BeanFactoryUtils.transformedBeanName(name)); + } + + /** + * Determine the original bean name, resolving locally defined aliases to canonical names. + * @param name the user-specified name + * @return the original bean name + */ + protected String originalBeanName(String name) { + String beanName = transformedBeanName(name); + if (name.startsWith(FACTORY_BEAN_PREFIX)) { + beanName = FACTORY_BEAN_PREFIX + beanName; + } + return beanName; + } + + /** + * Initialize the given BeanWrapper with the custom editors registered + * with this factory. To be called for BeanWrappers that will create + * and populate bean instances. + *

The default implementation delegates to {@link #registerCustomEditors}. + * Can be overridden in subclasses. + * @param bw the BeanWrapper to initialize + */ + protected void initBeanWrapper(BeanWrapper bw) { + registerCustomEditors(bw); + } + + /** + * Initialize the given PropertyEditorRegistry with the custom editors + * that have been registered with this BeanFactory. + *

To be called for BeanWrappers that will create and populate bean + * instances, and for SimpleTypeConverter used for constructor argument + * and factory method type conversion. + * @param registry the PropertyEditorRegistry to initialize + */ + protected void registerCustomEditors(PropertyEditorRegistry registry) { + PropertyEditorRegistrySupport registrySupport = + (registry instanceof PropertyEditorRegistrySupport ? (PropertyEditorRegistrySupport) registry : null); + if (registrySupport != null) { + registrySupport.useConfigValueEditors(); + } + if (!this.propertyEditorRegistrars.isEmpty()) { + for (Iterator it = this.propertyEditorRegistrars.iterator(); it.hasNext();) { + PropertyEditorRegistrar registrar = (PropertyEditorRegistrar) it.next(); + try { + registrar.registerCustomEditors(registry); + } + catch (BeanCreationException ex) { + Throwable rootCause = ex.getMostSpecificCause(); + if (rootCause instanceof BeanCurrentlyInCreationException) { + BeanCreationException bce = (BeanCreationException) rootCause; + if (isCurrentlyInCreation(bce.getBeanName())) { + if (logger.isDebugEnabled()) { + logger.debug("PropertyEditorRegistrar [" + registrar.getClass().getName() + + "] failed because it tried to obtain currently created bean '" + ex.getBeanName() + + "': " + ex.getMessage()); + } + onSuppressedException(ex); + continue; + } + } + throw ex; + } + } + } + if (!this.customEditors.isEmpty()) { + for (Iterator it = this.customEditors.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + Class requiredType = (Class) entry.getKey(); + Object value = entry.getValue(); + if (value instanceof PropertyEditor) { + PropertyEditor editor = (PropertyEditor) value; + // Register the editor as shared instance, if possible, + // to make it clear that it might be used concurrently. + if (registrySupport != null) { + registrySupport.registerSharedEditor(requiredType, editor); + } + else { + registry.registerCustomEditor(requiredType, editor); + } + } + else if (value instanceof Class) { + Class editorClass = (Class) value; + registry.registerCustomEditor(requiredType, (PropertyEditor) BeanUtils.instantiateClass(editorClass)); + } + else { + throw new IllegalStateException("Illegal custom editor value type: " + value.getClass().getName()); + } + } + } + } + + + /** + * Return a merged RootBeanDefinition, traversing the parent bean definition + * if the specified bean corresponds to a child bean definition. + * @param beanName the name of the bean to retrieve the merged definition for + * @return a (potentially merged) RootBeanDefinition for the given bean + * @throws NoSuchBeanDefinitionException if there is no bean with the given name + * @throws BeanDefinitionStoreException in case of an invalid bean definition + */ + protected RootBeanDefinition getMergedLocalBeanDefinition(String beanName) throws BeansException { + // Quick check on the concurrent map first, with minimal locking. + RootBeanDefinition mbd = (RootBeanDefinition) this.mergedBeanDefinitions.get(beanName); + if (mbd != null) { + return mbd; + } + return getMergedBeanDefinition(beanName, getBeanDefinition(beanName)); + } + + /** + * Return a RootBeanDefinition for the given top-level bean, by merging with + * the parent if the given bean's definition is a child bean definition. + * @param beanName the name of the bean definition + * @param bd the original bean definition (Root/ChildBeanDefinition) + * @return a (potentially merged) RootBeanDefinition for the given bean + * @throws BeanDefinitionStoreException in case of an invalid bean definition + */ + protected RootBeanDefinition getMergedBeanDefinition(String beanName, BeanDefinition bd) + throws BeanDefinitionStoreException { + + return getMergedBeanDefinition(beanName, bd, null); + } + + /** + * Return a RootBeanDefinition for the given bean, by merging with the + * parent if the given bean's definition is a child bean definition. + * @param beanName the name of the bean definition + * @param bd the original bean definition (Root/ChildBeanDefinition) + * @param containingBd the containing bean definition in case of inner bean, + * or null in case of a top-level bean + * @return a (potentially merged) RootBeanDefinition for the given bean + * @throws BeanDefinitionStoreException in case of an invalid bean definition + */ + protected RootBeanDefinition getMergedBeanDefinition( + String beanName, BeanDefinition bd, BeanDefinition containingBd) + throws BeanDefinitionStoreException { + + synchronized (this.mergedBeanDefinitions) { + RootBeanDefinition mbd = null; + + // Check with full lock now in order to enforce the same merged instance. + if (containingBd == null) { + mbd = (RootBeanDefinition) this.mergedBeanDefinitions.get(beanName); + } + + if (mbd == null) { + if (bd.getParentName() == null) { + // Use copy of given root bean definition. + mbd = new RootBeanDefinition(bd); + } + else { + // Child bean definition: needs to be merged with parent. + BeanDefinition pbd = null; + try { + String parentBeanName = transformedBeanName(bd.getParentName()); + if (!beanName.equals(parentBeanName)) { + pbd = getMergedBeanDefinition(parentBeanName); + } + else { + if (getParentBeanFactory() instanceof ConfigurableBeanFactory) { + pbd = ((ConfigurableBeanFactory) getParentBeanFactory()).getMergedBeanDefinition(parentBeanName); + } + else { + throw new NoSuchBeanDefinitionException(bd.getParentName(), + "Parent name '" + bd.getParentName() + "' is equal to bean name '" + beanName + + "': cannot be resolved without an AbstractBeanFactory parent"); + } + } + } + catch (NoSuchBeanDefinitionException ex) { + throw new BeanDefinitionStoreException(bd.getResourceDescription(), beanName, + "Could not resolve parent bean definition '" + bd.getParentName() + "'", ex); + } + // Deep copy with overridden values. + mbd = new RootBeanDefinition(pbd); + mbd.overrideFrom(bd); + } + + // A bean contained in a non-singleton bean cannot be a singleton itself. + // Let's correct this on the fly here, since this might be the result of + // parent-child merging for the outer bean, in which case the original inner bean + // definition will not have inherited the merged outer bean's singleton status. + if (containingBd != null && !containingBd.isSingleton() && mbd.isSingleton()) { + mbd.setScope(containingBd.getScope()); + } + + // Only cache the merged bean definition if we're already about to create an + // instance of the bean, or at least have already created an instance before. + if (containingBd == null && isCacheBeanMetadata() && isBeanEligibleForMetadataCaching(beanName)) { + this.mergedBeanDefinitions.put(beanName, mbd); + } + } + + return mbd; + } + } + + /** + * Check the given merged bean definition, + * potentially throwing validation exceptions. + * @param mbd the merged bean definition to check + * @param beanName the name of the bean + * @param args the arguments for bean creation, if any + * @throws BeanDefinitionStoreException in case of validation failure + */ + protected void checkMergedBeanDefinition(RootBeanDefinition mbd, String beanName, Object[] args) + throws BeanDefinitionStoreException { + + // check if bean definition is not abstract + if (mbd.isAbstract()) { + throw new BeanIsAbstractException(beanName); + } + + // Check validity of the usage of the args parameter. This can + // only be used for prototypes constructed via a factory method. + if (args != null && !mbd.isPrototype()) { + throw new BeanDefinitionStoreException( + "Can only specify arguments for the getBean method when referring to a prototype bean definition"); + } + } + + /** + * Remove the merged bean definition for the specified bean, + * recreating it on next access. + * @param beanName the bean name to clear the merged definition for + */ + protected void clearMergedBeanDefinition(String beanName) { + this.mergedBeanDefinitions.remove(beanName); + } + + /** + * Resolve the bean class for the specified bean definition, + * resolving a bean class name into a Class reference (if necessary) + * and storing the resolved Class in the bean definition for further use. + * @param mbd the merged bean definition to determine the class for + * @param beanName the name of the bean (for error handling purposes) + * @return the resolved bean class (or null if none) + * @throws CannotLoadBeanClassException if we failed to load the class + */ + protected Class resolveBeanClass(RootBeanDefinition mbd, String beanName) { + return resolveBeanClass(mbd, beanName, null); + } + + /** + * Resolve the bean class for the specified bean definition, + * resolving a bean class name into a Class reference (if necessary) + * and storing the resolved Class in the bean definition for further use. + * @param mbd the merged bean definition to determine the class for + * @param beanName the name of the bean (for error handling purposes) + * @param typesToMatch the types to match in case of internal type matching purposes + * (also signals that the returned Class will never be exposed to application code) + * @return the resolved bean class (or null if none) + * @throws CannotLoadBeanClassException if we failed to load the class + */ + protected Class resolveBeanClass(RootBeanDefinition mbd, String beanName, Class[] typesToMatch) + throws CannotLoadBeanClassException { + try { + if (mbd.hasBeanClass()) { + return mbd.getBeanClass(); + } + if (typesToMatch != null) { + ClassLoader tempClassLoader = getTempClassLoader(); + if (tempClassLoader != null) { + if (tempClassLoader instanceof DecoratingClassLoader) { + DecoratingClassLoader dcl = (DecoratingClassLoader) tempClassLoader; + for (int i = 0; i < typesToMatch.length; i++) { + dcl.excludeClass(typesToMatch[i].getName()); + } + } + String className = mbd.getBeanClassName(); + return (className != null ? ClassUtils.forName(className, tempClassLoader) : null); + } + } + return mbd.resolveBeanClass(getBeanClassLoader()); + } + catch (ClassNotFoundException ex) { + throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), ex); + } + catch (LinkageError err) { + throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), err); + } + } + + + /** + * Predict the eventual bean type (of the processed bean instance) for the + * specified bean. Called by {@link #getType} and {@link #isTypeMatch}. + * Does not need to handle FactoryBeans specifically, since it is only + * supposed to operate on the raw bean type. + *

This implementation is simplistic in that it is not able to + * handle factory methods and InstantiationAwareBeanPostProcessors. + * It only predicts the bean type correctly for a standard bean. + * To be overridden in subclasses, applying more sophisticated type detection. + * @param beanName the name of the bean + * @param mbd the merged bean definition to determine the type for + * @param typesToMatch the types to match in case of internal type matching purposes + * (also signals that the returned Class will never be exposed to application code) + * @return the type of the bean, or null if not predictable + */ + protected Class predictBeanType(String beanName, RootBeanDefinition mbd, Class[] typesToMatch) { + if (mbd.getFactoryMethodName() != null) { + return null; + } + return resolveBeanClass(mbd, beanName, typesToMatch); + } + + /** + * Check whether the given bean is defined as a {@link FactoryBean}. + * @param beanName the name of the bean + * @param mbd the corresponding bean definition + */ + protected boolean isFactoryBean(String beanName, RootBeanDefinition mbd) { + Class beanClass = predictBeanType(beanName, mbd, new Class[] {FactoryBean.class}); + return (beanClass != null && FactoryBean.class.isAssignableFrom(beanClass)); + } + + /** + * Determine the bean type for the given FactoryBean definition, as far as possible. + * Only called if there is no singleton instance registered for the target bean already. + *

The default implementation creates the FactoryBean via getBean + * to call its getObjectType method. Subclasses are encouraged to optimize + * this, typically by just instantiating the FactoryBean but not populating it yet, + * trying whether its getObjectType method already returns a type. + * If no type found, a full FactoryBean creation as performed by this implementation + * should be used as fallback. + * @param beanName the name of the bean + * @param mbd the merged bean definition for the bean + * @return the type for the bean if determinable, or null else + * @see org.springframework.beans.factory.FactoryBean#getObjectType() + * @see #getBean(String) + */ + protected Class getTypeForFactoryBean(String beanName, RootBeanDefinition mbd) { + if (!mbd.isSingleton()) { + return null; + } + try { + FactoryBean factoryBean = + (FactoryBean) doGetBean(FACTORY_BEAN_PREFIX + beanName, FactoryBean.class, null, true); + return getTypeForFactoryBean(factoryBean); + } + catch (BeanCreationException ex) { + // Can only happen when getting a FactoryBean. + if (logger.isDebugEnabled()) { + logger.debug("Ignoring bean creation exception on FactoryBean type check: " + ex); + } + onSuppressedException(ex); + return null; + } + } + + /** + * Mark the specified bean as already created (or about to be created). + *

This allows the bean factory to optimize its caching for repeated + * creation of the specified bean. + * @param beanName the name of the bean + */ + protected void markBeanAsCreated(String beanName) { + this.alreadyCreated.add(beanName); + } + + /** + * Determine whether the specified bean is eligible for having + * its bean definition metadata cached. + * @param beanName the name of the bean + * @return true if the bean's metadata may be cached + * at this point already + */ + protected boolean isBeanEligibleForMetadataCaching(String beanName) { + return this.alreadyCreated.contains(beanName); + } + + /** + * Remove the singleton instance (if any) for the given bean name, + * but only if it hasn't been used for other purposes than type checking. + * @param beanName the name of the bean + * @return true if actually removed, false otherwise + */ + protected boolean removeSingletonIfCreatedForTypeCheckOnly(String beanName) { + if (!this.alreadyCreated.contains(beanName)) { + removeSingleton(beanName); + return true; + } + else { + return false; + } + } + + /** + * Get the object for the given bean instance, either the bean + * instance itself or its created object in case of a FactoryBean. + * @param beanInstance the shared bean instance + * @param name name that may include factory dereference prefix + * @param beanName the canonical bean name + * @param mbd the merged bean definition + * @return the object to expose for the bean + */ + protected Object getObjectForBeanInstance( + Object beanInstance, String name, String beanName, RootBeanDefinition mbd) { + + // Don't let calling code try to dereference the factory if the bean isn't a factory. + if (BeanFactoryUtils.isFactoryDereference(name) && !(beanInstance instanceof FactoryBean)) { + throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass()); + } + + // Now we have the bean instance, which may be a normal bean or a FactoryBean. + // If it's a FactoryBean, we use it to create a bean instance, unless the + // caller actually wants a reference to the factory. + if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) { + return beanInstance; + } + + Object object = null; + if (mbd == null) { + object = getCachedObjectForFactoryBean(beanName); + } + if (object == null) { + // Return bean instance from factory. + FactoryBean factory = (FactoryBean) beanInstance; + // Caches object obtained from FactoryBean if it is a singleton. + if (mbd == null && containsBeanDefinition(beanName)) { + mbd = getMergedLocalBeanDefinition(beanName); + } + boolean synthetic = (mbd != null && mbd.isSynthetic()); + object = getObjectFromFactoryBean(factory, beanName, !synthetic); + } + return object; + } + + /** + * Determine whether the given bean name is already in use within this factory, + * i.e. whether there is a local bean or alias registered under this name or + * an inner bean created with this name. + * @param beanName the name to check + */ + public boolean isBeanNameInUse(String beanName) { + return isAlias(beanName) || containsLocalBean(beanName) || hasDependentBean(beanName); + } + + /** + * Determine whether the given bean requires destruction on shutdown. + *

The default implementation checks the DisposableBean interface as well as + * a specified destroy method and registered DestructionAwareBeanPostProcessors. + * @param bean the bean instance to check + * @param mbd the corresponding bean definition + * @see org.springframework.beans.factory.DisposableBean + * @see AbstractBeanDefinition#getDestroyMethodName() + * @see org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor + */ + protected boolean requiresDestruction(Object bean, RootBeanDefinition mbd) { + return (bean instanceof DisposableBean || mbd.getDestroyMethodName() != null || + hasDestructionAwareBeanPostProcessors()); + } + + /** + * Add the given bean to the list of disposable beans in this factory, + * registering its DisposableBean interface and/or the given destroy method + * to be called on factory shutdown (if applicable). Only applies to singletons. + * @param beanName the name of the bean + * @param bean the bean instance + * @param mbd the bean definition for the bean + * @see RootBeanDefinition#isSingleton + * @see RootBeanDefinition#getDependsOn + * @see #registerDisposableBean + * @see #registerDependentBean + */ + protected void registerDisposableBeanIfNecessary(String beanName, Object bean, RootBeanDefinition mbd) { + if (!mbd.isPrototype() && requiresDestruction(bean, mbd)) { + if (mbd.isSingleton()) { + // Register a DisposableBean implementation that performs all destruction + // work for the given bean: DestructionAwareBeanPostProcessors, + // DisposableBean interface, custom destroy method. + registerDisposableBean(beanName, + new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors())); + } + else { + // A bean with a custom scope... + Scope scope = (Scope) this.scopes.get(mbd.getScope()); + if (scope == null) { + throw new IllegalStateException("No Scope registered for scope '" + mbd.getScope() + "'"); + } + scope.registerDestructionCallback(beanName, + new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors())); + } + } + } + + + //--------------------------------------------------------------------- + // Abstract methods to be implemented by subclasses + //--------------------------------------------------------------------- + + /** + * Check if this bean factory contains a bean definition with the given name. + * Does not consider any hierarchy this factory may participate in. + * Invoked by containsBean when no cached singleton instance is found. + *

Depending on the nature of the concrete bean factory implementation, + * this operation might be expensive (for example, because of directory lookups + * in external registries). However, for listable bean factories, this usually + * just amounts to a local hash lookup: The operation is therefore part of the + * public interface there. The same implementation can serve for both this + * template method and the public interface method in that case. + * @param beanName the name of the bean to look for + * @return if this bean factory contains a bean definition with the given name + * @see #containsBean + * @see org.springframework.beans.factory.ListableBeanFactory#containsBeanDefinition + */ + protected abstract boolean containsBeanDefinition(String beanName); + + /** + * Return the bean definition for the given bean name. + * Subclasses should normally implement caching, as this method is invoked + * by this class every time bean definition metadata is needed. + *

Depending on the nature of the concrete bean factory implementation, + * this operation might be expensive (for example, because of directory lookups + * in external registries). However, for listable bean factories, this usually + * just amounts to a local hash lookup: The operation is therefore part of the + * public interface there. The same implementation can serve for both this + * template method and the public interface method in that case. + * @param beanName the name of the bean to find a definition for + * @return the BeanDefinition for this prototype name (never null) + * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException + * if the bean definition cannot be resolved + * @throws BeansException in case of errors + * @see RootBeanDefinition + * @see ChildBeanDefinition + * @see org.springframework.beans.factory.config.ConfigurableListableBeanFactory#getBeanDefinition + */ + protected abstract BeanDefinition getBeanDefinition(String beanName) throws BeansException; + + /** + * Create a bean instance for the given bean definition. + * The bean definition will already have been merged with the parent + * definition in case of a child definition. + *

All the other methods in this class invoke this method, although + * beans may be cached after being instantiated by this method. All bean + * instantiation within this class is performed by this method. + * @param beanName the name of the bean + * @param mbd the merged bean definition for the bean + * @param args arguments to use if creating a prototype using explicit arguments to a + * static factory method. This parameter must be null except in this case. + * @return a new instance of the bean + * @throws BeanCreationException if the bean could not be created + */ + protected abstract Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) + throws BeanCreationException; + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AutowireCandidateQualifier.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AutowireCandidateQualifier.java new file mode 100644 index 0000000000..58c560c917 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AutowireCandidateQualifier.java @@ -0,0 +1,96 @@ +/* + * Copyright 2002-2007 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 org.springframework.beans.BeanMetadataAttributeAccessor; +import org.springframework.util.Assert; + +/** + * Qualifier for resolving autowire candidates. A bean definition that + * includes one or more such qualifiers enables fine-grained matching + * against annotations on a field or parameter to be autowired. + * + * @author Mark Fisher + * @author Juergen Hoeller + * @since 2.5 + * @see org.springframework.beans.factory.annotation.Qualifier + */ +public class AutowireCandidateQualifier extends BeanMetadataAttributeAccessor { + + public static String VALUE_KEY = "value"; + + private final String typeName; + + + /** + * Construct a qualifier to match against an annotation of the + * given type. + * @param type the annotation type + */ + public AutowireCandidateQualifier(Class type) { + this(type.getName()); + } + + /** + * Construct a qualifier to match against an annotation of the + * given type name. + *

The type name may match the fully-qualified class name of + * the annotation or the short class name (without the package). + * @param typeName the name of the annotation type + */ + public AutowireCandidateQualifier(String typeName) { + Assert.notNull(typeName, "Type name must not be null"); + this.typeName = typeName; + } + + /** + * Construct a qualifier to match against an annotation of the + * given type whose value attribute also matches + * the specified value. + * @param type the annotation type + * @param value the annotation value to match + */ + public AutowireCandidateQualifier(Class type, Object value) { + this(type.getName(), value); + } + + /** + * Construct a qualifier to match against an annotation of the + * given type name whose value attribute also matches + * the specified value. + *

The type name may match the fully-qualified class name of + * the annotation or the short class name (without the package). + * @param typeName the name of the annotation type + * @param value the annotation value to match + */ + public AutowireCandidateQualifier(String typeName, Object value) { + Assert.notNull(typeName, "Type name must not be null"); + this.typeName = typeName; + setAttribute(VALUE_KEY, value); + } + + + /** + * Retrieve the type name. This value will be the same as the + * type name provided to the constructor or the fully-qualified + * class name if a Class instance was provided to the constructor. + */ + public String getTypeName() { + return this.typeName; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AutowireCandidateResolver.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AutowireCandidateResolver.java new file mode 100644 index 0000000000..282c60e071 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AutowireCandidateResolver.java @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2007 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 org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.config.DependencyDescriptor; + +/** + * Strategy interface for determining whether a specific bean definition + * qualifies as an autowire candidate for a specific dependency. + * + * @author Mark Fisher + * @author Juergen Hoeller + * @since 2.5 + */ +public interface AutowireCandidateResolver { + + /** + * Determine whether the given bean definition qualifies as an + * autowire candidate for the given dependency. + * @param bdHolder the bean definition including bean name and aliases + * @param descriptor the descriptor for the target method parameter or field + * @return whether the bean definition qualifies as autowire candidate + */ + boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java new file mode 100644 index 0000000000..a13f97a53f --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java @@ -0,0 +1,134 @@ +/* + * Copyright 2002-2008 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.beans.PropertyDescriptor; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Iterator; +import java.util.Set; + +import org.springframework.core.JdkVersion; +import org.springframework.util.ClassUtils; + +/** + * Utility class that contains various methods useful for + * the implementation of autowire-capable bean factories. + * + * @author Juergen Hoeller + * @author Mark Fisher + * @since 1.1.2 + * @see AbstractAutowireCapableBeanFactory + */ +abstract class AutowireUtils { + + private static final String QUALIFIED_ANNOTATION_AUTOWIRE_CANDIDATE_RESOLVER_CLASS_NAME = + "org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver"; + + + /** + * Sort the given constructors, preferring public constructors and "greedy" ones + * with a maximum of arguments. The result will contain public constructors first, + * with decreasing number of arguments, then non-public constructors, again with + * decreasing number of arguments. + * @param constructors the constructor array to sort + */ + public static void sortConstructors(Constructor[] constructors) { + Arrays.sort(constructors, new Comparator() { + public int compare(Object o1, Object o2) { + Constructor c1 = (Constructor) o1; + Constructor c2 = (Constructor) o2; + boolean p1 = Modifier.isPublic(c1.getModifiers()); + boolean p2 = Modifier.isPublic(c2.getModifiers()); + if (p1 != p2) { + return (p1 ? -1 : 1); + } + int c1pl = c1.getParameterTypes().length; + int c2pl = c2.getParameterTypes().length; + return (new Integer(c1pl)).compareTo(new Integer(c2pl)) * -1; + } + }); + } + + /** + * Determine whether the given bean property is excluded from dependency checks. + *

This implementation excludes properties defined by CGLIB. + * @param pd the PropertyDescriptor of the bean property + * @return whether the bean property is excluded + */ + public static boolean isExcludedFromDependencyCheck(PropertyDescriptor pd) { + Method wm = pd.getWriteMethod(); + if (wm == null) { + return false; + } + if (wm.getDeclaringClass().getName().indexOf("$$") == -1) { + // Not a CGLIB method so it's OK. + return false; + } + // It was declared by CGLIB, but we might still want to autowire it + // if it was actually declared by the superclass. + Class superclass = wm.getDeclaringClass().getSuperclass(); + return !ClassUtils.hasMethod(superclass, wm.getName(), wm.getParameterTypes()); + } + + /** + * Return whether the setter method of the given bean property is defined + * in any of the given interfaces. + * @param pd the PropertyDescriptor of the bean property + * @param interfaces the Set of interfaces (Class objects) + * @return whether the setter method is defined by an interface + */ + public static boolean isSetterDefinedInInterface(PropertyDescriptor pd, Set interfaces) { + Method setter = pd.getWriteMethod(); + if (setter != null) { + Class targetClass = setter.getDeclaringClass(); + for (Iterator it = interfaces.iterator(); it.hasNext();) { + Class ifc = (Class) it.next(); + if (ifc.isAssignableFrom(targetClass) && + ClassUtils.hasMethod(ifc, setter.getName(), setter.getParameterTypes())) { + return true; + } + } + } + return false; + } + + /** + * If at least Java 1.5, this will return an annotation-aware resolver. + * Otherwise it returns a resolver that checks the bean definition only. + */ + public static AutowireCandidateResolver createAutowireCandidateResolver() { + if (JdkVersion.isAtLeastJava15()) { + try { + Class resolverClass = ClassUtils.forName( + QUALIFIED_ANNOTATION_AUTOWIRE_CANDIDATE_RESOLVER_CLASS_NAME, AutowireUtils.class.getClassLoader()); + return (AutowireCandidateResolver) resolverClass.newInstance(); + } + catch (Throwable ex) { + throw new IllegalStateException("Unable to load Java 1.5 dependent class [" + + QUALIFIED_ANNOTATION_AUTOWIRE_CANDIDATE_RESOLVER_CLASS_NAME + "]", ex); + } + } + else { + return new SimpleAutowireCandidateResolver(); + } + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionBuilder.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionBuilder.java new file mode 100644 index 0000000000..febba86600 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionBuilder.java @@ -0,0 +1,336 @@ +/* + * Copyright 2002-2008 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 org.springframework.beans.PropertyValue; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.util.ObjectUtils; + +/** + * Programmatic means of constructing + * {@link org.springframework.beans.factory.config.BeanDefinition BeanDefinitions} + * using the builder pattern. Intended primarily for use when implementing Spring 2.0 + * {@link org.springframework.beans.factory.xml.NamespaceHandler NamespaceHandlers}. + * + * @author Rod Johnson + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + */ +public class BeanDefinitionBuilder { + + /** + * Create a new BeanDefinitionBuilder used to construct a {@link GenericBeanDefinition}. + */ + public static BeanDefinitionBuilder genericBeanDefinition() { + BeanDefinitionBuilder builder = new BeanDefinitionBuilder(); + builder.beanDefinition = new GenericBeanDefinition(); + return builder; + } + + /** + * Create a new BeanDefinitionBuilder used to construct a {@link GenericBeanDefinition}. + * @param beanClass the Class of the bean that the definition is being created for + */ + public static BeanDefinitionBuilder genericBeanDefinition(Class beanClass) { + BeanDefinitionBuilder builder = new BeanDefinitionBuilder(); + builder.beanDefinition = new GenericBeanDefinition(); + builder.beanDefinition.setBeanClass(beanClass); + return builder; + } + + /** + * Create a new BeanDefinitionBuilder used to construct a {@link GenericBeanDefinition}. + * @param beanClassName the class name for the bean that the definition is being created for + */ + public static BeanDefinitionBuilder genericBeanDefinition(String beanClassName) { + BeanDefinitionBuilder builder = new BeanDefinitionBuilder(); + builder.beanDefinition = new GenericBeanDefinition(); + builder.beanDefinition.setBeanClassName(beanClassName); + return builder; + } + + /** + * Create a new BeanDefinitionBuilder used to construct a {@link RootBeanDefinition}. + * @param beanClass the Class of the bean that the definition is being created for + */ + public static BeanDefinitionBuilder rootBeanDefinition(Class beanClass) { + return rootBeanDefinition(beanClass, null); + } + + /** + * Create a new BeanDefinitionBuilder used to construct a {@link RootBeanDefinition}. + * @param beanClass the Class of the bean that the definition is being created for + * @param factoryMethodName the name of the method to use to construct the bean instance + */ + public static BeanDefinitionBuilder rootBeanDefinition(Class beanClass, String factoryMethodName) { + BeanDefinitionBuilder builder = new BeanDefinitionBuilder(); + builder.beanDefinition = new RootBeanDefinition(); + builder.beanDefinition.setBeanClass(beanClass); + builder.beanDefinition.setFactoryMethodName(factoryMethodName); + return builder; + } + + /** + * Create a new BeanDefinitionBuilder used to construct a {@link RootBeanDefinition}. + * @param beanClassName the class name for the bean that the definition is being created for + */ + public static BeanDefinitionBuilder rootBeanDefinition(String beanClassName) { + return rootBeanDefinition(beanClassName, null); + } + + /** + * Create a new BeanDefinitionBuilder used to construct a {@link RootBeanDefinition}. + * @param beanClassName the class name for the bean that the definition is being created for + * @param factoryMethodName the name of the method to use to construct the bean instance + */ + public static BeanDefinitionBuilder rootBeanDefinition(String beanClassName, String factoryMethodName) { + BeanDefinitionBuilder builder = new BeanDefinitionBuilder(); + builder.beanDefinition = new RootBeanDefinition(); + builder.beanDefinition.setBeanClassName(beanClassName); + builder.beanDefinition.setFactoryMethodName(factoryMethodName); + return builder; + } + + /** + * Create a new BeanDefinitionBuilder used to construct a {@link ChildBeanDefinition}. + * @param parentName the name of the parent bean + */ + public static BeanDefinitionBuilder childBeanDefinition(String parentName) { + BeanDefinitionBuilder builder = new BeanDefinitionBuilder(); + builder.beanDefinition = new ChildBeanDefinition(parentName); + return builder; + } + + + /** + * The BeanDefinition instance we are creating. + */ + private AbstractBeanDefinition beanDefinition; + + /** + * Our current position with respect to constructor args. + */ + private int constructorArgIndex; + + + /** + * Enforce the use of factory methods. + */ + private BeanDefinitionBuilder() { + } + + /** + * Return the current BeanDefinition object in its raw (unvalidated) form. + * @see #getBeanDefinition() + */ + public AbstractBeanDefinition getRawBeanDefinition() { + return this.beanDefinition; + } + + /** + * Validate and return the created BeanDefinition object. + */ + public AbstractBeanDefinition getBeanDefinition() { + this.beanDefinition.validate(); + return this.beanDefinition; + } + + + /** + * Set the name of the parent definition of this bean definition. + */ + public BeanDefinitionBuilder setParentName(String parentName) { + this.beanDefinition.setParentName(parentName); + return this; + } + + /** + * Set the name of the factory method to use for this definition. + */ + public BeanDefinitionBuilder setFactoryMethod(String factoryMethod) { + this.beanDefinition.setFactoryMethodName(factoryMethod); + return this; + } + + /** + * Set the name of the factory bean to use for this definition. + * @deprecated since Spring 2.5, in favor of preparing this on the + * {@link #getRawBeanDefinition() raw BeanDefinition object} + */ + public BeanDefinitionBuilder setFactoryBean(String factoryBean, String factoryMethod) { + this.beanDefinition.setFactoryBeanName(factoryBean); + this.beanDefinition.setFactoryMethodName(factoryMethod); + return this; + } + + /** + * Add an indexed constructor arg value. The current index is tracked internally + * and all additions are at the present point. + * @deprecated since Spring 2.5, in favor of {@link #addConstructorArgValue} + */ + public BeanDefinitionBuilder addConstructorArg(Object value) { + return addConstructorArgValue(value); + } + + /** + * Add an indexed constructor arg value. The current index is tracked internally + * and all additions are at the present point. + */ + public BeanDefinitionBuilder addConstructorArgValue(Object value) { + this.beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(this.constructorArgIndex++, value); + return this; + } + + /** + * Add a reference to a named bean as a constructor arg. + * @see #addConstructorArgValue(Object) + */ + public BeanDefinitionBuilder addConstructorArgReference(String beanName) { + return addConstructorArgValue(new RuntimeBeanReference(beanName)); + } + + /** + * Add the supplied property value under the given name. + */ + public BeanDefinitionBuilder addPropertyValue(String name, Object value) { + this.beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, value)); + return this; + } + + /** + * Add a reference to the specified bean name under the property specified. + * @param name the name of the property to add the reference to + * @param beanName the name of the bean being referenced + */ + public BeanDefinitionBuilder addPropertyReference(String name, String beanName) { + return addPropertyValue(name, new RuntimeBeanReference(beanName)); + } + + /** + * Set the init method for this definition. + */ + public BeanDefinitionBuilder setInitMethodName(String methodName) { + this.beanDefinition.setInitMethodName(methodName); + return this; + } + + /** + * Set the destroy method for this definition. + */ + public BeanDefinitionBuilder setDestroyMethodName(String methodName) { + this.beanDefinition.setDestroyMethodName(methodName); + return this; + } + + + /** + * Set the scope of this definition. + * @see org.springframework.beans.factory.config.BeanDefinition#SCOPE_SINGLETON + * @see org.springframework.beans.factory.config.BeanDefinition#SCOPE_PROTOTYPE + */ + public BeanDefinitionBuilder setScope(String scope) { + this.beanDefinition.setScope(scope); + return this; + } + + /** + * Set whether or not this definition describes a singleton bean, + * as alternative to {@link #setScope}. + * @deprecated since Spring 2.5, in favor of {@link #setScope} + */ + public BeanDefinitionBuilder setSingleton(boolean singleton) { + this.beanDefinition.setSingleton(singleton); + return this; + } + + /** + * Set whether or not this definition is abstract. + */ + public BeanDefinitionBuilder setAbstract(boolean flag) { + this.beanDefinition.setAbstract(flag); + return this; + } + + /** + * Set whether beans for this definition should be lazily initialized or not. + */ + public BeanDefinitionBuilder setLazyInit(boolean lazy) { + this.beanDefinition.setLazyInit(lazy); + return this; + } + + /** + * Set the autowire mode for this definition. + */ + public BeanDefinitionBuilder setAutowireMode(int autowireMode) { + beanDefinition.setAutowireMode(autowireMode); + return this; + } + + /** + * Set the depency check mode for this definition. + */ + public BeanDefinitionBuilder setDependencyCheck(int dependencyCheck) { + beanDefinition.setDependencyCheck(dependencyCheck); + return this; + } + + /** + * Append the specified bean name to the list of beans that this definition + * depends on. + */ + public BeanDefinitionBuilder addDependsOn(String beanName) { + if (this.beanDefinition.getDependsOn() == null) { + this.beanDefinition.setDependsOn(new String[] {beanName}); + } + else { + String[] added = (String[]) ObjectUtils.addObjectToArray(this.beanDefinition.getDependsOn(), beanName); + this.beanDefinition.setDependsOn(added); + } + return this; + } + + /** + * Set the role of this definition. + */ + public BeanDefinitionBuilder setRole(int role) { + this.beanDefinition.setRole(role); + return this; + } + + /** + * Set the source of this definition. + * @deprecated since Spring 2.5, in favor of preparing this on the + * {@link #getRawBeanDefinition() raw BeanDefinition object} + */ + public BeanDefinitionBuilder setSource(Object source) { + this.beanDefinition.setSource(source); + return this; + } + + /** + * Set the description associated with this definition. + * @deprecated since Spring 2.5, in favor of preparing this on the + * {@link #getRawBeanDefinition() raw BeanDefinition object} + */ + public BeanDefinitionBuilder setResourceDescription(String resourceDescription) { + this.beanDefinition.setResourceDescription(resourceDescription); + return this; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionDefaults.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionDefaults.java new file mode 100644 index 0000000000..382cc32069 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionDefaults.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2007 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 org.springframework.util.StringUtils; + +/** + * A simple holder for BeanDefinition property defaults. + * + * @author Mark Fisher + * @since 2.5 + */ +public class BeanDefinitionDefaults { + + private boolean lazyInit; + + private int dependencyCheck = AbstractBeanDefinition.DEPENDENCY_CHECK_NONE; + + private int autowireMode = AbstractBeanDefinition.AUTOWIRE_NO; + + private String initMethodName; + + private String destroyMethodName; + + + public void setLazyInit(boolean lazyInit) { + this.lazyInit = lazyInit; + } + + public boolean isLazyInit() { + return this.lazyInit; + } + + public void setDependencyCheck(int dependencyCheck) { + this.dependencyCheck = dependencyCheck; + } + + public int getDependencyCheck() { + return this.dependencyCheck; + } + + public void setAutowireMode(int autowireMode) { + this.autowireMode = autowireMode; + } + + public int getAutowireMode() { + return this.autowireMode; + } + + public void setInitMethodName(String initMethodName) { + this.initMethodName = (StringUtils.hasText(initMethodName)) ? initMethodName : null; + } + + public String getInitMethodName() { + return this.initMethodName; + } + + public void setDestroyMethodName(String destroyMethodName) { + this.destroyMethodName = (StringUtils.hasText(destroyMethodName)) ? destroyMethodName : null; + } + + public String getDestroyMethodName() { + return this.destroyMethodName; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReader.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReader.java new file mode 100644 index 0000000000..c23ae3e0b3 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReader.java @@ -0,0 +1,129 @@ +/* + * Copyright 2002-2007 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 org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; + +/** + * Simple interface for bean definition readers. + * Specifies load methods with Resource parameters. + * + *

Concrete bean definition readers can of course add additional + * load and register methods for bean definitions, specific to + * their bean definition format. + * + *

Note that a bean definition reader does not have to implement + * this interface. It only serves as suggestion for bean definition + * readers that want to follow standard naming conventions. + * + * @author Juergen Hoeller + * @since 1.1 + * @see org.springframework.core.io.Resource + */ +public interface BeanDefinitionReader { + +/** + * Return the bean factory to register the bean definitions with. + *

The factory is exposed through the BeanDefinitionRegistry interface, + * encapsulating the methods that are relevant for bean definition handling. + * @deprecated in favor of the uniformly named {@link #getRegistry()} + */ + BeanDefinitionRegistry getBeanFactory(); + + /** + * Return the bean factory to register the bean definitions with. + *

The factory is exposed through the BeanDefinitionRegistry interface, + * encapsulating the methods that are relevant for bean definition handling. + */ + BeanDefinitionRegistry getRegistry(); + + /** + * Return the resource loader to use for resource locations. + * Can be checked for the ResourcePatternResolver interface and cast + * accordingly, for loading multiple resources for a given resource pattern. + *

Null suggests that absolute resource loading is not available + * for this bean definition reader. + *

This is mainly meant to be used for importing further resources + * from within a bean definition resource, for example via the "import" + * tag in XML bean definitions. It is recommended, however, to apply + * such imports relative to the defining resource; only explicit full + * resource locations will trigger absolute resource loading. + *

There is also a loadBeanDefinitions(String) method available, + * for loading bean definitions from a resource location (or location pattern). + * This is a convenience to avoid explicit ResourceLoader handling. + * @see #loadBeanDefinitions(String) + * @see org.springframework.core.io.support.ResourcePatternResolver + */ + ResourceLoader getResourceLoader(); + + /** + * Return the class loader to use for bean classes. + *

null suggests to not load bean classes eagerly + * but rather to just register bean definitions with class names, + * with the corresponding Classes to be resolved later (or never). + */ + ClassLoader getBeanClassLoader(); + + /** + * Return the BeanNameGenerator to use for anonymous beans + * (without explicit bean name specified). + */ + BeanNameGenerator getBeanNameGenerator(); + + + /** + * Load bean definitions from the specified resource. + * @param resource the resource descriptor + * @return the number of bean definitions found + * @throws BeanDefinitionStoreException in case of loading or parsing errors + */ + int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException; + + /** + * Load bean definitions from the specified resources. + * @param resources the resource descriptors + * @return the number of bean definitions found + * @throws BeanDefinitionStoreException in case of loading or parsing errors + */ + int loadBeanDefinitions(Resource[] resources) throws BeanDefinitionStoreException; + + /** + * Load bean definitions from the specified resource location. + *

The location can also be a location pattern, provided that the + * ResourceLoader of this bean definition reader is a ResourcePatternResolver. + * @param location the resource location, to be loaded with the ResourceLoader + * (or ResourcePatternResolver) of this bean definition reader + * @return the number of bean definitions found + * @throws BeanDefinitionStoreException in case of loading or parsing errors + * @see #getResourceLoader() + * @see #loadBeanDefinitions(org.springframework.core.io.Resource) + * @see #loadBeanDefinitions(org.springframework.core.io.Resource[]) + */ + int loadBeanDefinitions(String location) throws BeanDefinitionStoreException; + + /** + * Load bean definitions from the specified resource locations. + * @param locations the resource locations, to be loaded with the ResourceLoader + * (or ResourcePatternResolver) of this bean definition reader + * @return the number of bean definitions found + * @throws BeanDefinitionStoreException in case of loading or parsing errors + */ + int loadBeanDefinitions(String[] locations) throws BeanDefinitionStoreException; + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReaderUtils.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReaderUtils.java new file mode 100644 index 0000000000..09e79c3b84 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReaderUtils.java @@ -0,0 +1,203 @@ +/* + * Copyright 2002-2007 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 org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.config.ConstructorArgumentValues; +import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * Utility methods that are useful for bean definition reader implementations. + * Mainly intended for internal use. + * + * @author Juergen Hoeller + * @author Rob Harrop + * @since 1.1 + * @see PropertiesBeanDefinitionReader + * @see org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader + */ +public class BeanDefinitionReaderUtils { + + /** + * Separator for generated bean names. If a class name or parent name is not + * unique, "#1", "#2" etc will be appended, until the name becomes unique. + */ + public static final String GENERATED_BEAN_NAME_SEPARATOR = BeanFactoryUtils.GENERATED_BEAN_NAME_SEPARATOR; + + + /** + * Create a new GenericBeanDefinition for the given + * class name, parent, constructor arguments, and property values. + * @param className the name of the bean class, if any + * @param parentName the name of the parent bean, if any + * @param cargs the constructor arguments, if any + * @param pvs the property values, if any + * @param classLoader the ClassLoader to use for loading bean classes + * (can be null to just register bean classes by name) + * @return the bean definition + * @throws ClassNotFoundException if the bean class could not be loaded + * @deprecated in favor of createBeanDefinition(String, String, ClassLoader) + * @see #createBeanDefinition(String, String, ClassLoader) + */ + public static AbstractBeanDefinition createBeanDefinition( + String className, String parentName, ConstructorArgumentValues cargs, + MutablePropertyValues pvs, ClassLoader classLoader) throws ClassNotFoundException { + + AbstractBeanDefinition bd = createBeanDefinition(parentName, className, classLoader); + bd.setConstructorArgumentValues(cargs); + bd.setPropertyValues(pvs); + return bd; + } + + /** + * Create a new GenericBeanDefinition for the given parent name and class name, + * eagerly loading the bean class if a ClassLoader has been specified. + * @param parentName the name of the parent bean, if any + * @param className the name of the bean class, if any + * @param classLoader the ClassLoader to use for loading bean classes + * (can be null to just register bean classes by name) + * @return the bean definition + * @throws ClassNotFoundException if the bean class could not be loaded + */ + public static AbstractBeanDefinition createBeanDefinition( + String parentName, String className, ClassLoader classLoader) throws ClassNotFoundException { + + GenericBeanDefinition bd = new GenericBeanDefinition(); + bd.setParentName(parentName); + if (className != null) { + if (classLoader != null) { + bd.setBeanClass(ClassUtils.forName(className, classLoader)); + } + else { + bd.setBeanClassName(className); + } + } + return bd; + } + + /** + * Generate a bean name for the given bean definition, unique within the + * given bean factory. + * @param definition the bean definition to generate a bean name for + * @param registry the bean factory that the definition is going to be + * registered with (to check for existing bean names) + * @param isInnerBean whether the given bean definition will be registered + * as inner bean or as top-level bean (allowing for special name generation + * for inner beans versus top-level beans) + * @return the generated bean name + * @throws BeanDefinitionStoreException if no unique name can be generated + * for the given bean definition + */ + public static String generateBeanName( + BeanDefinition definition, BeanDefinitionRegistry registry, boolean isInnerBean) + throws BeanDefinitionStoreException { + + String generatedBeanName = definition.getBeanClassName(); + if (generatedBeanName == null) { + if (definition.getParentName() != null) { + generatedBeanName = definition.getParentName() + "$child"; + } + else if (definition.getFactoryBeanName() != null) { + generatedBeanName = definition.getFactoryBeanName() + "$created"; + } + } + if (!StringUtils.hasText(generatedBeanName)) { + throw new BeanDefinitionStoreException("Unnamed bean definition specifies neither " + + "'class' nor 'parent' nor 'factory-bean' - can't generate bean name"); + } + + String id = generatedBeanName; + if (isInnerBean) { + // Inner bean: generate identity hashcode suffix. + id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + ObjectUtils.getIdentityHexString(definition); + } + else { + // Top-level bean: use plain class name. + // Increase counter until the id is unique. + int counter = -1; + while (counter == -1 || registry.containsBeanDefinition(id)) { + counter++; + id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + counter; + } + } + return id; + } + + /** + * Generate a bean name for the given top-level bean definition, + * unique within the given bean factory. + * @param beanDefinition the bean definition to generate a bean name for + * @param registry the bean factory that the definition is going to be + * registered with (to check for existing bean names) + * @return the generated bean name + * @throws BeanDefinitionStoreException if no unique name can be generated + * for the given bean definition + */ + public static String generateBeanName(BeanDefinition beanDefinition, BeanDefinitionRegistry registry) + throws BeanDefinitionStoreException { + + return generateBeanName(beanDefinition, registry, false); + } + + /** + * Register the given bean definition with the given bean factory. + * @param definitionHolder the bean definition including name and aliases + * @param registry the bean factory to register with + * @throws BeanDefinitionStoreException if registration failed + */ + public static void registerBeanDefinition( + BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) + throws BeanDefinitionStoreException { + + // Register bean definition under primary name. + String beanName = definitionHolder.getBeanName(); + registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); + + // Register aliases for bean name, if any. + String[] aliases = definitionHolder.getAliases(); + if (aliases != null) { + for (int i = 0; i < aliases.length; i++) { + registry.registerAlias(beanName, aliases[i]); + } + } + } + + /** + * Register the given bean definition with a generated name, + * unique within the given bean factory. + * @param definition the bean definition to generate a bean name for + * @param registry the bean factory to register with + * @return the generated bean name + * @throws BeanDefinitionStoreException if no unique name can be generated + * for the given bean definition or the definition cannot be registered + */ + public static String registerWithGeneratedName( + AbstractBeanDefinition definition, BeanDefinitionRegistry registry) + throws BeanDefinitionStoreException { + + String generatedName = generateBeanName(definition, registry, false); + registry.registerBeanDefinition(generatedName, definition); + return generatedName; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionRegistry.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionRegistry.java new file mode 100644 index 0000000000..23191913de --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionRegistry.java @@ -0,0 +1,107 @@ +/* + * Copyright 2002-2008 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 org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.core.AliasRegistry; + +/** + * Interface for registries that hold bean definitions, for example RootBeanDefinition + * and ChildBeanDefinition instances. Typically implemented by BeanFactories that + * internally work with the AbstractBeanDefinition hierarchy. + * + *

This is the only interface in Spring's bean factory packages that encapsulates + * registration of bean definitions. The standard BeanFactory interfaces + * only cover access to a fully configured factory instance. + * + *

Spring's bean definition readers expect to work on an implementation of this + * interface. Known implementors within the Spring core are DefaultListableBeanFactory + * and GenericApplicationContext. + * + * @author Juergen Hoeller + * @since 26.11.2003 + * @see org.springframework.beans.factory.config.BeanDefinition + * @see AbstractBeanDefinition + * @see RootBeanDefinition + * @see ChildBeanDefinition + * @see DefaultListableBeanFactory + * @see org.springframework.context.support.GenericApplicationContext + * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader + * @see PropertiesBeanDefinitionReader + */ +public interface BeanDefinitionRegistry extends AliasRegistry { + + /** + * Register a new bean definition with this registry. + * Must support RootBeanDefinition and ChildBeanDefinition. + * @param beanName the name of the bean instance to register + * @param beanDefinition definition of the bean instance to register + * @throws BeanDefinitionStoreException if the BeanDefinition is invalid + * or if there is already a BeanDefinition for the specified bean name + * (and we are not allowed to override it) + * @see RootBeanDefinition + * @see ChildBeanDefinition + */ + void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) + throws BeanDefinitionStoreException; + + /** + * Remove the BeanDefinition for the given name. + * @param beanName the name of the bean instance to register + * @throws NoSuchBeanDefinitionException if there is no such bean definition + */ + void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException; + + /** + * Return the BeanDefinition for the given bean name. + * @param beanName name of the bean to find a definition for + * @return the BeanDefinition for the given name (never null) + * @throws NoSuchBeanDefinitionException if there is no such bean definition + */ + BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException; + + /** + * Check if this registry contains a bean definition with the given name. + * @param beanName the name of the bean to look for + * @return if this registry contains a bean definition with the given name + */ + boolean containsBeanDefinition(String beanName); + + /** + * Return the names of all beans defined in this registry. + * @return the names of all beans defined in this registry, + * or an empty array if none defined + */ + String[] getBeanDefinitionNames(); + + /** + * Return the number of beans defined in the registry. + * @return the number of beans defined in the registry + */ + int getBeanDefinitionCount(); + + /** + * Determine whether the given bean name is already in use within this registry, + * i.e. whether there is a local bean or alias registered under this name. + * @param beanName the name to check + * @return whether the given bean name is already in use + */ + boolean isBeanNameInUse(String beanName); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionResource.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionResource.java new file mode 100644 index 0000000000..6076658c2d --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionResource.java @@ -0,0 +1,91 @@ +/* + * Copyright 2002-2008 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.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.core.io.AbstractResource; +import org.springframework.util.Assert; + +/** + * Descriptive {@link org.springframework.core.io.Resource} wrapper for + * a {@link org.springframework.beans.factory.config.BeanDefinition}. + * + * @author Juergen Hoeller + * @since 2.5.2 + * @see org.springframework.core.io.DescriptiveResource + */ +class BeanDefinitionResource extends AbstractResource { + + private final BeanDefinition beanDefinition; + + + /** + * Create a new BeanDefinitionResource. + * @param beanDefinition the BeanDefinition objectto wrap + */ + public BeanDefinitionResource(BeanDefinition beanDefinition) { + Assert.notNull(beanDefinition, "BeanDefinition must not be null"); + this.beanDefinition = beanDefinition; + } + + /** + * Return the wrapped BeanDefinition object. + */ + public final BeanDefinition getBeanDefinition() { + return this.beanDefinition; + } + + + public boolean exists() { + return false; + } + + public boolean isReadable() { + return false; + } + + public InputStream getInputStream() throws IOException { + throw new FileNotFoundException( + "Resource cannot be opened because it points to " + getDescription()); + } + + public String getDescription() { + return "BeanDefinition defined in " + this.beanDefinition.getResourceDescription(); + } + + + /** + * This implementation compares the underlying BeanDefinition. + */ + public boolean equals(Object obj) { + return (obj == this || + (obj instanceof BeanDefinitionResource && + ((BeanDefinitionResource) obj).beanDefinition.equals(this.beanDefinition))); + } + + /** + * This implementation returns the hash code of the underlying BeanDefinition. + */ + public int hashCode() { + return this.beanDefinition.hashCode(); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValidationException.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValidationException.java new file mode 100644 index 0000000000..79d0d1e005 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValidationException.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2007 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 org.springframework.beans.FatalBeanException; + +/** + * Exception thrown when the validation of a bean definition failed. + * + * @author Juergen Hoeller + * @since 21.11.2003 + * @see AbstractBeanDefinition#validate() + */ +public class BeanDefinitionValidationException extends FatalBeanException { + + /** + * Create a new BeanDefinitionValidationException with the specified message. + * @param msg the detail message + */ + public BeanDefinitionValidationException(String msg) { + super(msg); + } + + /** + * Create a new BeanDefinitionValidationException with the specified message + * and root cause. + * @param msg the detail message + * @param cause the root cause + */ + public BeanDefinitionValidationException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValueResolver.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValueResolver.java new file mode 100644 index 0000000000..db4ea3ea26 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValueResolver.java @@ -0,0 +1,328 @@ +/* + * Copyright 2002-2008 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.util.ArrayList; +import java.util.Iterator; +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; + +/** + * 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. + * + *

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: + *

  • 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. + *
  • A RuntimeBeanReference, which must be resolved. + *
  • A ManagedList. This is a special collection that may contain + * RuntimeBeanReferences or Collections that will need to be resolved. + *
  • A ManagedSet. May also contain RuntimeBeanReferences or + * Collections that will need to be resolved. + *
  • A ManagedMap. In this case the value may be a RuntimeBeanReference + * or Collection that will need to be resolved. + *
  • An ordinary object or null, 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 ref = ((RuntimeBeanNameReference) value).getBeanName(); + if (!this.beanFactory.containsBean(ref)) { + throw new BeanDefinitionStoreException( + "Invalid bean name '" + ref + "' in bean reference for " + argName); + } + return ref; + } + 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 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 (Iterator it = original.entrySet().iterator(); it.hasNext();) { + Map.Entry propEntry = (Map.Entry) it.next(); + Object propKey = propEntry.getKey(); + Object propValue = propEntry.getValue(); + if (propKey instanceof TypedStringValue) { + propKey = ((TypedStringValue) propKey).getValue(); + } + if (propValue instanceof TypedStringValue) { + propValue = ((TypedStringValue) propValue).getValue(); + } + copy.put(propKey, propValue); + } + return copy; + } + else if (value instanceof TypedStringValue) { + // Convert value to target type here. + TypedStringValue typedStringValue = (TypedStringValue) value; + try { + Class resolvedTargetType = resolveTargetType(typedStringValue); + if (resolvedTargetType != null) { + return this.typeConverter.convertIfNecessary(typedStringValue.getValue(), resolvedTargetType); + } + else { + // No target type specified - no conversion necessary... + return typedStringValue.getValue(); + } + } + 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 { + // No need to resolve value... + return value; + } + } + + /** + * Resolve the target type in the given TypedStringValue. + * @param value the TypedStringValue to resolve + * @return the resolved target type (or null 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 (int i = 0; i < dependsOn.length; i++) { + String dependsOnBean = dependsOn[i]; + 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 { + if (ref.isToParent()) { + if (this.beanFactory.getParentBeanFactory() == null) { + throw new BeanCreationException( + this.beanDefinition.getResourceDescription(), this.beanName, + "Can't resolve reference to bean '" + ref.getBeanName() + + "' in parent factory: no parent factory available"); + } + return this.beanFactory.getParentBeanFactory().getBean(ref.getBeanName()); + } + else { + Object bean = this.beanFactory.getBean(ref.getBeanName()); + this.beanFactory.registerDependentBean(ref.getBeanName(), 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 ManagedList, resolve reference if necessary. + */ + private List resolveManagedList(Object argName, List ml) { + List resolved = new ArrayList(ml.size()); + for (int i = 0; i < ml.size(); i++) { + resolved.add( + resolveValueIfNecessary( + argName + " with key " + BeanWrapper.PROPERTY_KEY_PREFIX + i + BeanWrapper.PROPERTY_KEY_SUFFIX, + ml.get(i))); + } + return resolved; + } + + /** + * For each element in the ManagedList, resolve reference if necessary. + */ + private Set resolveManagedSet(Object argName, Set ms) { + Set resolved = new LinkedHashSet(ms.size()); + int i = 0; + for (Iterator it = ms.iterator(); it.hasNext();) { + resolved.add( + resolveValueIfNecessary( + argName + " with key " + BeanWrapper.PROPERTY_KEY_PREFIX + i + BeanWrapper.PROPERTY_KEY_SUFFIX, + it.next())); + i++; + } + return resolved; + } + + /** + * For each element in the ManagedMap, resolve reference if necessary. + */ + private Map resolveManagedMap(Object argName, Map mm) { + Map resolved = new LinkedHashMap(mm.size()); + Iterator it = mm.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = (Map.Entry) it.next(); + Object resolvedKey = resolveValueIfNecessary(argName, entry.getKey()); + Object resolvedValue = resolveValueIfNecessary( + argName + " with key " + BeanWrapper.PROPERTY_KEY_PREFIX + entry.getKey() + BeanWrapper.PROPERTY_KEY_SUFFIX, + entry.getValue()); + resolved.put(resolvedKey, resolvedValue); + } + return resolved; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanNameGenerator.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanNameGenerator.java new file mode 100644 index 0000000000..5f3aea6688 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanNameGenerator.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2007 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 org.springframework.beans.factory.config.BeanDefinition; + +/** + * Strategy interface for generating bean names for bean definitions. + * + * @author Juergen Hoeller + * @since 2.0.3 + */ +public interface BeanNameGenerator { + + /** + * Generate a bean name for the given bean definition. + * @param definition the bean definition to generate a name for + * @param registry the bean definition registry that the given definition + * is supposed to be registered with + * @return the generated bean name + */ + String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java new file mode 100644 index 0000000000..46ed762a71 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java @@ -0,0 +1,201 @@ +/* + * Copyright 2002-2007 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.Constructor; +import java.lang.reflect.Method; + +import net.sf.cglib.proxy.Callback; +import net.sf.cglib.proxy.CallbackFilter; +import net.sf.cglib.proxy.Enhancer; +import net.sf.cglib.proxy.MethodInterceptor; +import net.sf.cglib.proxy.MethodProxy; +import net.sf.cglib.proxy.NoOp; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.BeanFactory; + +/** + * Default object instantiation strategy for use in BeanFactories. + * Uses CGLIB to generate subclasses dynamically if methods need to be + * overridden by the container, to implement Method Injection. + * + *

    Using Method Injection features requires CGLIB on the classpath. + * However, the core IoC container will still run without CGLIB being available. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 1.1 + */ +public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationStrategy { + + /** + * Index in the CGLIB callback array for passthrough behavior, + * in which case the subclass won't override the original class. + */ + private static final int PASSTHROUGH = 0; + + /** + * Index in the CGLIB callback array for a method that should + * be overridden to provide method lookup. + */ + private static final int LOOKUP_OVERRIDE = 1; + + /** + * Index in the CGLIB callback array for a method that should + * be overridden using generic Methodreplacer functionality. + */ + private static final int METHOD_REPLACER = 2; + + + protected Object instantiateWithMethodInjection( + RootBeanDefinition beanDefinition, String beanName, BeanFactory owner) { + + // Must generate CGLIB subclass. + return new CglibSubclassCreator(beanDefinition, owner).instantiate(null, null); + } + + protected Object instantiateWithMethodInjection( + RootBeanDefinition beanDefinition, String beanName, BeanFactory owner, + Constructor ctor, Object[] args) { + + return new CglibSubclassCreator(beanDefinition, owner).instantiate(ctor, args); + } + + + /** + * An inner class so we don't have a CGLIB dependency in core. + */ + private static class CglibSubclassCreator { + + private static final Log logger = LogFactory.getLog(CglibSubclassCreator.class); + + private final RootBeanDefinition beanDefinition; + + private final BeanFactory owner; + + public CglibSubclassCreator(RootBeanDefinition beanDefinition, BeanFactory owner) { + this.beanDefinition = beanDefinition; + this.owner = owner; + } + + /** + * Create a new instance of a dynamically generated subclasses implementing the + * required lookups. + * @param ctor constructor to use. If this is null, use the + * no-arg constructor (no parameterization, or Setter Injection) + * @param args arguments to use for the constructor. + * Ignored if the ctor parameter is null. + * @return new instance of the dynamically generated class + */ + public Object instantiate(Constructor ctor, Object[] args) { + Enhancer enhancer = new Enhancer(); + enhancer.setSuperclass(this.beanDefinition.getBeanClass()); + enhancer.setCallbackFilter(new CallbackFilterImpl()); + enhancer.setCallbacks(new Callback[] { + NoOp.INSTANCE, + new LookupOverrideMethodInterceptor(), + new ReplaceOverrideMethodInterceptor() + }); + + return (ctor == null) ? + enhancer.create() : + enhancer.create(ctor.getParameterTypes(), args); + } + + + /** + * Class providing hashCode and equals methods required by CGLIB to + * ensure that CGLIB doesn't generate a distinct class per bean. + * Identity is based on class and bean definition. + */ + private class CglibIdentitySupport { + + /** + * Exposed for equals method to allow access to enclosing class field + */ + protected RootBeanDefinition getBeanDefinition() { + return beanDefinition; + } + + public boolean equals(Object other) { + return (other.getClass().equals(getClass()) && + ((CglibIdentitySupport) other).getBeanDefinition().equals(beanDefinition)); + } + + public int hashCode() { + return beanDefinition.hashCode(); + } + } + + + /** + * CGLIB MethodInterceptor to override methods, replacing them with an + * implementation that returns a bean looked up in the container. + */ + private class LookupOverrideMethodInterceptor extends CglibIdentitySupport implements MethodInterceptor { + + public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable { + // Cast is safe, as CallbackFilter filters are used selectively. + LookupOverride lo = (LookupOverride) beanDefinition.getMethodOverrides().getOverride(method); + return owner.getBean(lo.getBeanName()); + } + } + + + /** + * CGLIB MethodInterceptor to override methods, replacing them with a call + * to a generic MethodReplacer. + */ + private class ReplaceOverrideMethodInterceptor extends CglibIdentitySupport implements MethodInterceptor { + + public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable { + ReplaceOverride ro = (ReplaceOverride) beanDefinition.getMethodOverrides().getOverride(method); + // TODO could cache if a singleton for minor performance optimization + MethodReplacer mr = (MethodReplacer) owner.getBean(ro.getMethodReplacerBeanName()); + return mr.reimplement(obj, method, args); + } + } + + + /** + * CGLIB object to filter method interception behavior. + */ + private class CallbackFilterImpl extends CglibIdentitySupport implements CallbackFilter { + + public int accept(Method method) { + MethodOverride methodOverride = beanDefinition.getMethodOverrides().getOverride(method); + if (logger.isTraceEnabled()) { + logger.trace("Override for '" + method.getName() + "' is [" + methodOverride + "]"); + } + if (methodOverride == null) { + return PASSTHROUGH; + } + else if (methodOverride instanceof LookupOverride) { + return LOOKUP_OVERRIDE; + } + else if (methodOverride instanceof ReplaceOverride) { + return METHOD_REPLACER; + } + throw new UnsupportedOperationException( + "Unexpected MethodOverride subclass: " + methodOverride.getClass().getName()); + } + } + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/ChildBeanDefinition.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/ChildBeanDefinition.java new file mode 100644 index 0000000000..dd7dea67fd --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/ChildBeanDefinition.java @@ -0,0 +1,175 @@ +/* + * Copyright 2002-2008 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 org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConstructorArgumentValues; +import org.springframework.util.ObjectUtils; + +/** + * Bean definition for beans which inherit settings from their parent. + * Child bean definitions have a fixed dependency on a parent bean definition. + * + *

    A child bean definition will inherit constructor argument values, + * property values and method overrides from the parent, with the option + * to add new values. If init method, destroy method and/or static factory + * method are specified, they will override the corresponding parent settings. + * The remaining settings will always be taken from the child definition: + * depends on, autowire mode, dependency check, singleton, lazy init. + * + *

    NOTE: Since Spring 2.5, the preferred way to register bean + * definitions programmatically is the {@link GenericBeanDefinition} class, + * which allows to dynamically define parent dependencies through the + * {@link GenericBeanDefinition#setParentName} method. This effectively + * supersedes the ChildBeanDefinition class for most use cases. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see GenericBeanDefinition + * @see RootBeanDefinition + */ +public class ChildBeanDefinition extends AbstractBeanDefinition { + + private String parentName; + + + /** + * Create a new ChildBeanDefinition for the given parent, to be + * configured through its bean properties and configuration methods. + * @param parentName the name of the parent bean + * @see #setBeanClass + * @see #setBeanClassName + * @see #setScope + * @see #setAutowireMode + * @see #setDependencyCheck + * @see #setConstructorArgumentValues + * @see #setPropertyValues + */ + public ChildBeanDefinition(String parentName) { + super(); + this.parentName = parentName; + } + + /** + * Create a new ChildBeanDefinition for the given parent. + * @param parentName the name of the parent bean + * @param pvs the additional property values of the child + */ + public ChildBeanDefinition(String parentName, MutablePropertyValues pvs) { + super(null, pvs); + this.parentName = parentName; + } + + /** + * Create a new ChildBeanDefinition for the given parent. + * @param parentName the name of the parent bean + * @param cargs the constructor argument values to apply + * @param pvs the additional property values of the child + */ + public ChildBeanDefinition( + String parentName, ConstructorArgumentValues cargs, MutablePropertyValues pvs) { + + super(cargs, pvs); + this.parentName = parentName; + } + + /** + * Create a new ChildBeanDefinition for the given parent, + * providing constructor arguments and property values. + * @param parentName the name of the parent bean + * @param beanClass the class of the bean to instantiate + * @param cargs the constructor argument values to apply + * @param pvs the property values to apply + */ + public ChildBeanDefinition( + String parentName, Class beanClass, ConstructorArgumentValues cargs, MutablePropertyValues pvs) { + + super(cargs, pvs); + this.parentName = parentName; + setBeanClass(beanClass); + } + + /** + * Create a new ChildBeanDefinition for the given parent, + * providing constructor arguments and property values. + * Takes a bean class name to avoid eager loading of the bean class. + * @param parentName the name of the parent bean + * @param beanClassName the name of the class to instantiate + * @param cargs the constructor argument values to apply + * @param pvs the property values to apply + */ + public ChildBeanDefinition( + String parentName, String beanClassName, ConstructorArgumentValues cargs, MutablePropertyValues pvs) { + + super(cargs, pvs); + this.parentName = parentName; + setBeanClassName(beanClassName); + } + + /** + * Create a new ChildBeanDefinition as deep copy of the given + * bean definition. + * @param original the original bean definition to copy from + */ + public ChildBeanDefinition(ChildBeanDefinition original) { + super((BeanDefinition) original); + } + + + public void setParentName(String parentName) { + this.parentName = parentName; + } + + public String getParentName() { + return this.parentName; + } + + public void validate() throws BeanDefinitionValidationException { + super.validate(); + if (this.parentName == null) { + throw new BeanDefinitionValidationException("'parentName' must be set in ChildBeanDefinition"); + } + } + + + public AbstractBeanDefinition cloneBeanDefinition() { + return new ChildBeanDefinition(this); + } + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof ChildBeanDefinition)) { + return false; + } + ChildBeanDefinition that = (ChildBeanDefinition) other; + return (ObjectUtils.nullSafeEquals(this.parentName, that.parentName) && super.equals(other)); + } + + public int hashCode() { + return ObjectUtils.nullSafeHashCode(this.parentName) * 29 + super.hashCode(); + } + + public String toString() { + StringBuffer sb = new StringBuffer("Child bean with parent '"); + sb.append(this.parentName).append("': ").append(super.toString()); + return sb.toString(); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java new file mode 100644 index 0000000000..be3eac49ed --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java @@ -0,0 +1,668 @@ +/* + * Copyright 2002-2008 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.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.BeanWrapperImpl; +import org.springframework.beans.BeansException; +import org.springframework.beans.TypeConverter; +import org.springframework.beans.TypeMismatchException; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.UnsatisfiedDependencyException; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.beans.factory.config.ConstructorArgumentValues; +import org.springframework.beans.factory.config.DependencyDescriptor; +import org.springframework.beans.factory.config.TypedStringValue; +import org.springframework.core.GenericTypeResolver; +import org.springframework.core.JdkVersion; +import org.springframework.core.MethodParameter; +import org.springframework.util.MethodInvoker; +import org.springframework.util.ObjectUtils; +import org.springframework.util.ReflectionUtils; + +/** + * Helper class for resolving constructors and factory methods. + * Performs constructor resolution through argument matching. + * + *

    Operates on an {@link AbstractBeanFactory} and an {@link InstantiationStrategy}. + * Used by {@link AbstractAutowireCapableBeanFactory}. + * + * @author Juergen Hoeller + * @author Rob Harrop + * @author Mark Fisher + * @since 2.0 + * @see #autowireConstructor + * @see #instantiateUsingFactoryMethod + * @see AbstractAutowireCapableBeanFactory + */ +class ConstructorResolver { + + private final AbstractBeanFactory beanFactory; + + private final AutowireCapableBeanFactory autowireFactory; + + private final InstantiationStrategy instantiationStrategy; + + private final TypeConverter typeConverter; + + + /** + * Create a new ConstructorResolver for the given factory and instantiation strategy. + * @param beanFactory the BeanFactory to work with + * @param autowireFactory the BeanFactory as AutowireCapableBeanFactory + * @param instantiationStrategy the instantiate strategy for creating bean instances + * @param typeConverter the TypeConverter to use (or null for using the default) + */ + public ConstructorResolver(AbstractBeanFactory beanFactory, AutowireCapableBeanFactory autowireFactory, + InstantiationStrategy instantiationStrategy, TypeConverter typeConverter) { + + this.beanFactory = beanFactory; + this.autowireFactory = autowireFactory; + this.instantiationStrategy = instantiationStrategy; + this.typeConverter = typeConverter; + } + + + /** + * "autowire constructor" (with constructor arguments by type) behavior. + * Also applied if explicit constructor argument values are specified, + * matching all remaining arguments with beans from the bean factory. + *

    This corresponds to constructor injection: In this mode, a Spring + * bean factory is able to host components that expect constructor-based + * dependency resolution. + * @param beanName the name of the bean + * @param mbd the merged bean definition for the bean + * @param chosenCtors chosen candidate constructors (or null if none) + * @param explicitArgs argument values passed in programmatically via the getBean method, + * or null if none (-> use constructor argument values from bean definition) + * @return a BeanWrapper for the new instance + */ + protected BeanWrapper autowireConstructor( + String beanName, RootBeanDefinition mbd, Constructor[] chosenCtors, Object[] explicitArgs) { + + BeanWrapperImpl bw = new BeanWrapperImpl(); + this.beanFactory.initBeanWrapper(bw); + + Constructor constructorToUse = null; + Object[] argsToUse = null; + + if (explicitArgs != null) { + argsToUse = explicitArgs; + } + else { + constructorToUse = (Constructor) mbd.resolvedConstructorOrFactoryMethod; + if (constructorToUse != null) { + // Found a cached constructor... + argsToUse = mbd.resolvedConstructorArguments; + if (argsToUse == null) { + Class[] paramTypes = constructorToUse.getParameterTypes(); + Object[] argsToResolve = mbd.preparedConstructorArguments; + TypeConverter converter = (this.typeConverter != null ? this.typeConverter : bw); + BeanDefinitionValueResolver valueResolver = + new BeanDefinitionValueResolver(this.beanFactory, beanName, mbd, converter); + argsToUse = new Object[argsToResolve.length]; + for (int i = 0; i < argsToResolve.length; i++) { + Object argValue = argsToResolve[i]; + MethodParameter methodParam = new MethodParameter(constructorToUse, i); + if (JdkVersion.isAtLeastJava15()) { + GenericTypeResolver.resolveParameterType(methodParam, constructorToUse.getDeclaringClass()); + } + if (argValue instanceof AutowiredArgumentMarker) { + argValue = resolveAutowiredArgument(methodParam, beanName, null, converter); + } + else if (argValue instanceof BeanMetadataElement) { + argValue = valueResolver.resolveValueIfNecessary("constructor argument", argValue); + } + argsToUse[i] = converter.convertIfNecessary(argValue, paramTypes[i], methodParam); + } + } + } + } + + if (constructorToUse == null) { + // Need to resolve the constructor. + boolean autowiring = (chosenCtors != null || + mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR); + ConstructorArgumentValues resolvedValues = null; + + int minNrOfArgs = 0; + if (explicitArgs != null) { + minNrOfArgs = explicitArgs.length; + } + else { + ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues(); + resolvedValues = new ConstructorArgumentValues(); + minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues); + } + + // Take specified constructors, if any. + Constructor[] candidates = + (chosenCtors != null ? chosenCtors : mbd.getBeanClass().getDeclaredConstructors()); + AutowireUtils.sortConstructors(candidates); + int minTypeDiffWeight = Integer.MAX_VALUE; + + for (int i = 0; i < candidates.length; i++) { + Constructor candidate = candidates[i]; + Class[] paramTypes = candidate.getParameterTypes(); + + if (constructorToUse != null && argsToUse.length > paramTypes.length) { + // Already found greedy constructor that can be satisfied -> + // do not look any further, there are only less greedy constructors left. + break; + } + if (paramTypes.length < minNrOfArgs) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + minNrOfArgs + " constructor arguments specified but no matching constructor found in bean '" + + beanName + "' " + + "(hint: specify index and/or type arguments for simple parameters to avoid type ambiguities)"); + } + + ArgumentsHolder args = null; + List causes = null; + + if (resolvedValues != null) { + // Try to resolve arguments for current constructor. + try { + args = createArgumentArray( + beanName, mbd, resolvedValues, bw, paramTypes, candidate, autowiring); + } + catch (UnsatisfiedDependencyException ex) { + if (this.beanFactory.logger.isTraceEnabled()) { + this.beanFactory.logger.trace( + "Ignoring constructor [" + candidate + "] of bean '" + beanName + "': " + ex); + } + if (i == candidates.length - 1 && constructorToUse == null) { + if (causes != null) { + for (Iterator it = causes.iterator(); it.hasNext();) { + this.beanFactory.onSuppressedException((Exception) it.next()); + } + } + throw ex; + } + else { + // Swallow and try next constructor. + if (causes == null) { + causes = new LinkedList(); + } + causes.add(ex); + continue; + } + } + } + + else { + // Explicit arguments given -> arguments length must match exactly. + if (paramTypes.length != explicitArgs.length) { + continue; + } + args = new ArgumentsHolder(explicitArgs); + } + + int typeDiffWeight = args.getTypeDifferenceWeight(paramTypes); + // Choose this constructor if it represents the closest match. + if (typeDiffWeight < minTypeDiffWeight) { + constructorToUse = candidate; + argsToUse = args.arguments; + minTypeDiffWeight = typeDiffWeight; + } + } + + if (constructorToUse == null) { + throw new BeanCreationException( + mbd.getResourceDescription(), beanName, "Could not resolve matching constructor"); + } + + if (explicitArgs == null) { + mbd.resolvedConstructorOrFactoryMethod = constructorToUse; + } + } + + try { + Object beanInstance = this.instantiationStrategy.instantiate( + mbd, beanName, this.beanFactory, constructorToUse, argsToUse); + bw.setWrappedInstance(beanInstance); + return bw; + } + catch (Throwable ex) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex); + } + } + + /** + * Instantiate the bean using a named factory method. The method may be static, if the + * bean definition parameter specifies a class, rather than a "factory-bean", or + * an instance variable on a factory object itself configured using Dependency Injection. + *

    Implementation requires iterating over the static or instance methods with the + * name specified in the RootBeanDefinition (the method may be overloaded) and trying + * to match with the parameters. We don't have the types attached to constructor args, + * so trial and error is the only way to go here. The explicitArgs array may contain + * argument values passed in programmatically via the corresponding getBean method. + * @param beanName the name of the bean + * @param mbd the merged bean definition for the bean + * @param explicitArgs argument values passed in programmatically via the getBean + * method, or null if none (-> use constructor argument values from bean definition) + * @return a BeanWrapper for the new instance + */ + public BeanWrapper instantiateUsingFactoryMethod(String beanName, RootBeanDefinition mbd, Object[] explicitArgs) { + BeanWrapperImpl bw = new BeanWrapperImpl(); + this.beanFactory.initBeanWrapper(bw); + + Class factoryClass = null; + Object factoryBean = null; + boolean isStatic = true; + + String factoryBeanName = mbd.getFactoryBeanName(); + if (factoryBeanName != null) { + if (factoryBeanName.equals(beanName)) { + throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName, + "factory-bean reference points back to the same bean definition"); + } + factoryBean = this.beanFactory.getBean(factoryBeanName); + if (factoryBean == null) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "factory-bean '" + factoryBeanName + "' returned null"); + } + factoryClass = factoryBean.getClass(); + isStatic = false; + } + else { + // It's a static factory method on the bean class. + factoryClass = mbd.getBeanClass(); + } + + Method factoryMethodToUse = null; + Object[] argsToUse = null; + + if (explicitArgs != null) { + argsToUse = explicitArgs; + } + else { + factoryMethodToUse = (Method) mbd.resolvedConstructorOrFactoryMethod; + if (factoryMethodToUse != null) { + // Found a cached factory method... + argsToUse = mbd.resolvedConstructorArguments; + if (argsToUse == null) { + Class[] paramTypes = factoryMethodToUse.getParameterTypes(); + Object[] argsToResolve = mbd.preparedConstructorArguments; + TypeConverter converter = (this.typeConverter != null ? this.typeConverter : bw); + BeanDefinitionValueResolver valueResolver = + new BeanDefinitionValueResolver(this.beanFactory, beanName, mbd, converter); + argsToUse = new Object[argsToResolve.length]; + for (int i = 0; i < argsToResolve.length; i++) { + Object argValue = argsToResolve[i]; + MethodParameter methodParam = new MethodParameter(factoryMethodToUse, i); + if (JdkVersion.isAtLeastJava15()) { + GenericTypeResolver.resolveParameterType(methodParam, factoryClass); + } + if (argValue instanceof AutowiredArgumentMarker) { + argValue = resolveAutowiredArgument(methodParam, beanName, null, converter); + } + else if (argValue instanceof BeanMetadataElement) { + argValue = valueResolver.resolveValueIfNecessary("factory method argument", argValue); + } + argsToUse[i] = converter.convertIfNecessary(argValue, paramTypes[i], methodParam); + } + } + } + } + + if (factoryMethodToUse == null) { + // Need to determine the factory method... + // Try all methods with this name to see if they match the given arguments. + Method[] candidates = ReflectionUtils.getAllDeclaredMethods(factoryClass); + boolean autowiring = (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR); + int minTypeDiffWeight = Integer.MAX_VALUE; + ConstructorArgumentValues resolvedValues = null; + + int minNrOfArgs = 0; + if (explicitArgs != null) { + minNrOfArgs = explicitArgs.length; + } + else { + // We don't have arguments passed in programmatically, so we need to resolve the + // arguments specified in the constructor arguments held in the bean definition. + ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues(); + resolvedValues = new ConstructorArgumentValues(); + minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues); + } + + List causes = null; + + for (int i = 0; i < candidates.length; i++) { + Method candidate = candidates[i]; + Class[] paramTypes = candidate.getParameterTypes(); + + if (Modifier.isStatic(candidate.getModifiers()) == isStatic && + candidate.getName().equals(mbd.getFactoryMethodName()) && + paramTypes.length >= minNrOfArgs) { + + ArgumentsHolder args = null; + + if (resolvedValues != null) { + // Resolved contructor arguments: type conversion and/or autowiring necessary. + try { + args = createArgumentArray( + beanName, mbd, resolvedValues, bw, paramTypes, candidate, autowiring); + } + catch (UnsatisfiedDependencyException ex) { + if (this.beanFactory.logger.isTraceEnabled()) { + this.beanFactory.logger.trace("Ignoring factory method [" + candidate + + "] of bean '" + beanName + "': " + ex); + } + if (i == candidates.length - 1 && factoryMethodToUse == null) { + if (causes != null) { + for (Iterator it = causes.iterator(); it.hasNext();) { + this.beanFactory.onSuppressedException((Exception) it.next()); + } + } + throw ex; + } + else { + // Swallow and try next overloaded factory method. + if (causes == null) { + causes = new LinkedList(); + } + causes.add(ex); + continue; + } + } + } + + else { + // Explicit arguments given -> arguments length must match exactly. + if (paramTypes.length != explicitArgs.length) { + continue; + } + args = new ArgumentsHolder(explicitArgs); + } + + int typeDiffWeight = args.getTypeDifferenceWeight(paramTypes); + // Choose this constructor if it represents the closest match. + if (typeDiffWeight < minTypeDiffWeight) { + factoryMethodToUse = candidate; + argsToUse = args.arguments; + minTypeDiffWeight = typeDiffWeight; + } + } + } + + if (factoryMethodToUse == null) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "No matching factory method found: " + + (mbd.getFactoryBeanName() != null ? + "factory bean '" + mbd.getFactoryBeanName() + "'; " : "") + + "factory method '" + mbd.getFactoryMethodName() + "'"); + } + if (void.class.equals(factoryMethodToUse.getReturnType())) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "Invalid factory method '" + mbd.getFactoryMethodName() + + "': needs to have a non-void return type!"); + } + + if (explicitArgs == null) { + mbd.resolvedConstructorOrFactoryMethod = factoryMethodToUse; + } + } + + try { + Object beanInstance = this.instantiationStrategy.instantiate( + mbd, beanName, this.beanFactory, factoryBean, factoryMethodToUse, argsToUse); + if (beanInstance == null) { + return null; + } + bw.setWrappedInstance(beanInstance); + return bw; + } + catch (Throwable ex) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex); + } + } + + /** + * Resolve the constructor arguments for this bean into the resolvedValues object. + * This may involve looking up other beans. + * This method is also used for handling invocations of static factory methods. + */ + private int resolveConstructorArguments( + String beanName, RootBeanDefinition mbd, BeanWrapper bw, + ConstructorArgumentValues cargs, ConstructorArgumentValues resolvedValues) { + + TypeConverter converterToUse = (this.typeConverter != null ? this.typeConverter : bw); + BeanDefinitionValueResolver valueResolver = + new BeanDefinitionValueResolver(this.beanFactory, beanName, mbd, converterToUse); + + int minNrOfArgs = cargs.getArgumentCount(); + + for (Iterator it = cargs.getIndexedArgumentValues().entrySet().iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + int index = ((Integer) entry.getKey()).intValue(); + if (index < 0) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "Invalid constructor argument index: " + index); + } + if (index > minNrOfArgs) { + minNrOfArgs = index + 1; + } + ConstructorArgumentValues.ValueHolder valueHolder = + (ConstructorArgumentValues.ValueHolder) entry.getValue(); + if (valueHolder.isConverted()) { + resolvedValues.addIndexedArgumentValue(index, valueHolder); + } + else { + Object resolvedValue = + valueResolver.resolveValueIfNecessary("constructor argument", valueHolder.getValue()); + ConstructorArgumentValues.ValueHolder resolvedValueHolder = + new ConstructorArgumentValues.ValueHolder(resolvedValue, valueHolder.getType()); + resolvedValueHolder.setSource(valueHolder); + resolvedValues.addIndexedArgumentValue(index, resolvedValueHolder); + } + } + + for (Iterator it = cargs.getGenericArgumentValues().iterator(); it.hasNext();) { + ConstructorArgumentValues.ValueHolder valueHolder = + (ConstructorArgumentValues.ValueHolder) it.next(); + if (valueHolder.isConverted()) { + resolvedValues.addGenericArgumentValue(valueHolder); + } + else { + Object resolvedValue = + valueResolver.resolveValueIfNecessary("constructor argument", valueHolder.getValue()); + ConstructorArgumentValues.ValueHolder resolvedValueHolder = + new ConstructorArgumentValues.ValueHolder(resolvedValue, valueHolder.getType()); + resolvedValueHolder.setSource(valueHolder); + resolvedValues.addGenericArgumentValue(resolvedValueHolder); + } + } + + return minNrOfArgs; + } + + /** + * Create an array of arguments to invoke a constructor or factory method, + * given the resolved constructor argument values. + */ + private ArgumentsHolder createArgumentArray( + String beanName, RootBeanDefinition mbd, ConstructorArgumentValues resolvedValues, + BeanWrapper bw, Class[] paramTypes, Object methodOrCtor, boolean autowiring) + throws UnsatisfiedDependencyException { + + String methodType = (methodOrCtor instanceof Constructor ? "constructor" : "factory method"); + TypeConverter converter = (this.typeConverter != null ? this.typeConverter : bw); + + ArgumentsHolder args = new ArgumentsHolder(paramTypes.length); + Set usedValueHolders = new HashSet(paramTypes.length); + Set autowiredBeanNames = new LinkedHashSet(4); + boolean resolveNecessary = false; + + for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) { + Class paramType = paramTypes[paramIndex]; + // Try to find matching constructor argument value, either indexed or generic. + ConstructorArgumentValues.ValueHolder valueHolder = + resolvedValues.getArgumentValue(paramIndex, paramType, usedValueHolders); + // If we couldn't find a direct match and are not supposed to autowire, + // let's try the next generic, untyped argument value as fallback: + // it could match after type conversion (for example, String -> int). + if (valueHolder == null && !autowiring) { + valueHolder = resolvedValues.getGenericArgumentValue(null, usedValueHolders); + } + if (valueHolder != null) { + // We found a potential match - let's give it a try. + // Do not consider the same value definition multiple times! + usedValueHolders.add(valueHolder); + args.rawArguments[paramIndex] = valueHolder.getValue(); + if (valueHolder.isConverted()) { + Object convertedValue = valueHolder.getConvertedValue(); + args.arguments[paramIndex] = convertedValue; + args.preparedArguments[paramIndex] = convertedValue; + } + else { + try { + Object originalValue = valueHolder.getValue(); + Object convertedValue = converter.convertIfNecessary(originalValue, paramType, + MethodParameter.forMethodOrConstructor(methodOrCtor, paramIndex)); + args.arguments[paramIndex] = convertedValue; + ConstructorArgumentValues.ValueHolder sourceHolder = + (ConstructorArgumentValues.ValueHolder) valueHolder.getSource(); + Object sourceValue = sourceHolder.getValue(); + if (originalValue == sourceValue || sourceValue instanceof TypedStringValue) { + // Either a converted value or still the original one: store converted value. + sourceHolder.setConvertedValue(convertedValue); + args.preparedArguments[paramIndex] = convertedValue; + } + else { + resolveNecessary = true; + args.preparedArguments[paramIndex] = sourceValue; + } + } + catch (TypeMismatchException ex) { + throw new UnsatisfiedDependencyException( + mbd.getResourceDescription(), beanName, paramIndex, paramType, + "Could not convert " + methodType + " argument value of type [" + + ObjectUtils.nullSafeClassName(valueHolder.getValue()) + + "] to required type [" + paramType.getName() + "]: " + ex.getMessage()); + } + } + } + else { + // No explicit match found: we're either supposed to autowire or + // have to fail creating an argument array for the given constructor. + if (!autowiring) { + throw new UnsatisfiedDependencyException( + mbd.getResourceDescription(), beanName, paramIndex, paramType, + "Ambiguous " + methodType + " argument types - " + + "did you specify the correct bean references as " + methodType + " arguments?"); + } + try { + MethodParameter param = MethodParameter.forMethodOrConstructor(methodOrCtor, paramIndex); + Object autowiredArgument = resolveAutowiredArgument(param, beanName, autowiredBeanNames, converter); + args.rawArguments[paramIndex] = autowiredArgument; + args.arguments[paramIndex] = autowiredArgument; + args.preparedArguments[paramIndex] = new AutowiredArgumentMarker(); + resolveNecessary = true; + } + catch (BeansException ex) { + throw new UnsatisfiedDependencyException( + mbd.getResourceDescription(), beanName, paramIndex, paramType, ex); + } + } + } + + for (Iterator it = autowiredBeanNames.iterator(); it.hasNext();) { + String autowiredBeanName = (String) it.next(); + this.beanFactory.registerDependentBean(autowiredBeanName, beanName); + if (this.beanFactory.logger.isDebugEnabled()) { + this.beanFactory.logger.debug("Autowiring by type from bean name '" + beanName + + "' via " + methodType + " to bean named '" + autowiredBeanName + "'"); + } + } + + if (resolveNecessary) { + mbd.preparedConstructorArguments = args.preparedArguments; + } + else { + mbd.resolvedConstructorArguments = args.arguments; + } + mbd.constructorArgumentsResolved = true; + return args; + } + + /** + * Template method for resolving the specified argument which is supposed to be autowired. + */ + protected Object resolveAutowiredArgument( + MethodParameter param, String beanName, Set autowiredBeanNames, TypeConverter typeConverter) { + + return this.autowireFactory.resolveDependency( + new DependencyDescriptor(param, true), beanName, autowiredBeanNames, typeConverter); + } + + + /** + * Private inner class for holding argument combinations. + */ + private static class ArgumentsHolder { + + public Object rawArguments[]; + + public Object arguments[]; + + public Object preparedArguments[]; + + public ArgumentsHolder(int size) { + this.rawArguments = new Object[size]; + this.arguments = new Object[size]; + this.preparedArguments = new Object[size]; + } + + public ArgumentsHolder(Object[] args) { + this.rawArguments = args; + this.arguments = args; + this.preparedArguments = args; + } + + public int getTypeDifferenceWeight(Class[] paramTypes) { + // If valid arguments found, determine type difference weight. + // Try type difference weight on both the converted arguments and + // the raw arguments. If the raw weight is better, use it. + // Decrease raw weight by 1024 to prefer it over equal converted weight. + int typeDiffWeight = MethodInvoker.getTypeDifferenceWeight(paramTypes, this.arguments); + int rawTypeDiffWeight = MethodInvoker.getTypeDifferenceWeight(paramTypes, this.rawArguments) - 1024; + return (rawTypeDiffWeight < typeDiffWeight ? rawTypeDiffWeight : typeDiffWeight); + } + } + + + /** + * Marker for autowired arguments in a cached argument array. + */ + private static class AutowiredArgumentMarker { + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DefaultBeanNameGenerator.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DefaultBeanNameGenerator.java new file mode 100644 index 0000000000..065c4e2163 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DefaultBeanNameGenerator.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2007 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 org.springframework.beans.factory.config.BeanDefinition; + +/** + * Default implementation of the {@link BeanNameGenerator} interface, delegating to + * {@link BeanDefinitionReaderUtils#generateBeanName(BeanDefinition, BeanDefinitionRegistry)}. + * + * @author Juergen Hoeller + * @since 2.0.3 + */ +public class DefaultBeanNameGenerator implements BeanNameGenerator { + + public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) { + return BeanDefinitionReaderUtils.generateBeanName(definition, registry); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java new file mode 100644 index 0000000000..aee99c76d5 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -0,0 +1,747 @@ +/* + * Copyright 2002-2008 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.util.ArrayList; +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 java.util.Set; + +import org.springframework.beans.BeansException; +import org.springframework.beans.FatalBeanException; +import org.springframework.beans.TypeConverter; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.BeanCurrentlyInCreationException; +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.CannotLoadBeanClassException; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.SmartFactoryBean; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.DependencyDescriptor; +import org.springframework.core.CollectionFactory; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * Default implementation of the + * {@link org.springframework.beans.factory.ListableBeanFactory} and + * {@link BeanDefinitionRegistry} interfaces: a full-fledged bean factory + * based on bean definition objects. + * + *

    Typical usage is registering all bean definitions first (possibly read + * from a bean definition file), before accessing beans. Bean definition lookup + * is therefore an inexpensive operation in a local bean definition table, + * operating on pre-built bean definition metadata objects. + * + *

    Can be used as a standalone bean factory, or as a superclass for custom + * bean factories. Note that readers for specific bean definition formats are + * typically implemented separately rather than as bean factory subclasses: + * see for example {@link PropertiesBeanDefinitionReader} and + * {@link org.springframework.beans.factory.xml.XmlBeanDefinitionReader}. + * + *

    For an alternative implementation of the + * {@link org.springframework.beans.factory.ListableBeanFactory} interface, + * have a look at {@link StaticListableBeanFactory}, which manages existing + * bean instances rather than creating new ones based on bean definitions. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @author Sam Brannen + * @since 16 April 2001 + * @see StaticListableBeanFactory + * @see PropertiesBeanDefinitionReader + * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader + */ +public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory + implements ConfigurableListableBeanFactory, BeanDefinitionRegistry { + + /** Whether to allow re-registration of a different definition with the same name */ + private boolean allowBeanDefinitionOverriding = true; + + /** Whether to allow eager class loading even for lazy-init beans */ + private boolean allowEagerClassLoading = true; + + /** Whether bean definition metadata may be cached for all beans */ + private boolean configurationFrozen = false; + + /** Map of bean definition objects, keyed by bean name */ + private final Map beanDefinitionMap = CollectionFactory.createConcurrentMapIfPossible(16); + + /** List of bean definition names, in registration order */ + private final List beanDefinitionNames = new ArrayList(); + + /** Cached array of bean definition names in case of frozen configuration */ + private String[] frozenBeanDefinitionNames; + + /** Resolver to use for checking if a bean definition is an autowire candidate */ + private AutowireCandidateResolver autowireCandidateResolver = AutowireUtils.createAutowireCandidateResolver(); + + /** Map from dependency type to corresponding autowired value */ + private final Map resolvableDependencies = new HashMap(); + + + /** + * Create a new DefaultListableBeanFactory. + */ + public DefaultListableBeanFactory() { + super(); + } + + /** + * Create a new DefaultListableBeanFactory with the given parent. + * @param parentBeanFactory the parent BeanFactory + */ + public DefaultListableBeanFactory(BeanFactory parentBeanFactory) { + super(parentBeanFactory); + } + + + /** + * Set a custom autowire candidate resolver for this BeanFactory to use + * when deciding whether a bean definition should be considered as a + * candidate for autowiring. + */ + public void setAutowireCandidateResolver(AutowireCandidateResolver autowireCandidateResolver) { + Assert.notNull(autowireCandidateResolver, "AutowireCandidateResolver must not be null"); + this.autowireCandidateResolver = autowireCandidateResolver; + } + + /** + * Return the autowire candidate resolver for this BeanFactory (never null). + */ + public AutowireCandidateResolver getAutowireCandidateResolver() { + return this.autowireCandidateResolver; + } + + /** + * Set whether it should be allowed to override bean definitions by registering + * a different definition with the same name, automatically replacing the former. + * If not, an exception will be thrown. This also applies to overriding aliases. + *

    Default is "true". + * @see #registerBeanDefinition + */ + public void setAllowBeanDefinitionOverriding(boolean allowBeanDefinitionOverriding) { + this.allowBeanDefinitionOverriding = allowBeanDefinitionOverriding; + } + + /** + * Set whether the factory is allowed to eagerly load bean classes + * even for bean definitions that are marked as "lazy-init". + *

    Default is "true". Turn this flag off to suppress class loading + * for lazy-init beans unless such a bean is explicitly requested. + * In particular, by-type lookups will then simply ignore bean definitions + * without resolved class name, instead of loading the bean classes on + * demand just to perform a type check. + * @see AbstractBeanDefinition#setLazyInit + */ + public void setAllowEagerClassLoading(boolean allowEagerClassLoading) { + this.allowEagerClassLoading = allowEagerClassLoading; + } + + + public void copyConfigurationFrom(ConfigurableBeanFactory otherFactory) { + super.copyConfigurationFrom(otherFactory); + if (otherFactory instanceof DefaultListableBeanFactory) { + DefaultListableBeanFactory otherListableFactory = (DefaultListableBeanFactory) otherFactory; + this.allowBeanDefinitionOverriding = otherListableFactory.allowBeanDefinitionOverriding; + this.allowEagerClassLoading = otherListableFactory.allowEagerClassLoading; + } + } + + + //--------------------------------------------------------------------- + // Implementation of ListableBeanFactory interface + //--------------------------------------------------------------------- + + public boolean containsBeanDefinition(String beanName) { + return this.beanDefinitionMap.containsKey(beanName); + } + + public int getBeanDefinitionCount() { + return this.beanDefinitionMap.size(); + } + + public String[] getBeanDefinitionNames() { + synchronized (this.beanDefinitionMap) { + if (this.frozenBeanDefinitionNames != null) { + return this.frozenBeanDefinitionNames; + } + else { + return StringUtils.toStringArray(this.beanDefinitionNames); + } + } + } + + public String[] getBeanNamesForType(Class type) { + return getBeanNamesForType(type, true, true); + } + + public String[] getBeanNamesForType(Class type, boolean includeNonSingletons, boolean allowEagerInit) { + List result = new ArrayList(); + + // Check all bean definitions. + String[] beanDefinitionNames = getBeanDefinitionNames(); + for (int i = 0; i < beanDefinitionNames.length; i++) { + String beanName = beanDefinitionNames[i]; + // Only consider bean as eligible if the bean name + // is not defined as alias for some other bean. + if (!isAlias(beanName)) { + try { + RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); + // Only check bean definition if it is complete. + if (!mbd.isAbstract() && + (allowEagerInit || ((mbd.hasBeanClass() || !mbd.isLazyInit() || this.allowEagerClassLoading)) && + !requiresEagerInitForType(mbd.getFactoryBeanName()))) { + // In case of FactoryBean, match object created by FactoryBean. + boolean isFactoryBean = isFactoryBean(beanName, mbd); + boolean matchFound = + (allowEagerInit || !isFactoryBean || containsSingleton(beanName)) && + (includeNonSingletons || isSingleton(beanName)) && isTypeMatch(beanName, type); + if (!matchFound && isFactoryBean) { + // In case of FactoryBean, try to match FactoryBean instance itself next. + beanName = FACTORY_BEAN_PREFIX + beanName; + matchFound = (includeNonSingletons || mbd.isSingleton()) && isTypeMatch(beanName, type); + } + if (matchFound) { + result.add(beanName); + } + } + } + catch (CannotLoadBeanClassException ex) { + if (allowEagerInit) { + throw ex; + } + // Probably contains a placeholder: let's ignore it for type matching purposes. + if (this.logger.isDebugEnabled()) { + this.logger.debug("Ignoring bean class loading failure for bean '" + beanName + "'", ex); + } + onSuppressedException(ex); + } + catch (BeanDefinitionStoreException ex) { + if (allowEagerInit) { + throw ex; + } + // Probably contains a placeholder: let's ignore it for type matching purposes. + if (this.logger.isDebugEnabled()) { + this.logger.debug("Ignoring unresolvable metadata in bean definition '" + beanName + "'", ex); + } + onSuppressedException(ex); + } + } + } + + // Check singletons too, to catch manually registered singletons. + String[] singletonNames = getSingletonNames(); + for (int i = 0; i < singletonNames.length; i++) { + String beanName = singletonNames[i]; + // Only check if manually registered. + if (!containsBeanDefinition(beanName)) { + // In case of FactoryBean, match object created by FactoryBean. + if (isFactoryBean(beanName)) { + if ((includeNonSingletons || isSingleton(beanName)) && isTypeMatch(beanName, type)) { + result.add(beanName); + // Match found for this bean: do not match FactoryBean itself anymore. + continue; + } + // In case of FactoryBean, try to match FactoryBean itself next. + beanName = FACTORY_BEAN_PREFIX + beanName; + } + // Match raw bean instance (might be raw FactoryBean). + if (isTypeMatch(beanName, type)) { + result.add(beanName); + } + } + } + + return StringUtils.toStringArray(result); + } + + /** + * Check whether the specified bean would need to be eagerly initialized + * in order to determine its type. + * @param factoryBeanName a factory-bean reference that the bean definition + * defines a factory method for + * @return whether eager initialization is necessary + */ + private boolean requiresEagerInitForType(String factoryBeanName) { + return (factoryBeanName != null && isFactoryBean(factoryBeanName) && !containsSingleton(factoryBeanName)); + } + + public Map getBeansOfType(Class type) throws BeansException { + return getBeansOfType(type, true, true); + } + + public Map getBeansOfType(Class type, boolean includeNonSingletons, boolean allowEagerInit) + throws BeansException { + + String[] beanNames = getBeanNamesForType(type, includeNonSingletons, allowEagerInit); + Map result = new LinkedHashMap(beanNames.length); + for (int i = 0; i < beanNames.length; i++) { + String beanName = beanNames[i]; + try { + result.put(beanName, getBean(beanName)); + } + catch (BeanCreationException ex) { + Throwable rootCause = ex.getMostSpecificCause(); + if (rootCause instanceof BeanCurrentlyInCreationException) { + BeanCreationException bce = (BeanCreationException) rootCause; + if (isCurrentlyInCreation(bce.getBeanName())) { + if (this.logger.isDebugEnabled()) { + this.logger.debug("Ignoring match to currently created bean '" + beanName + "': " + ex.getMessage()); + } + onSuppressedException(ex); + // Ignore: indicates a circular reference when autowiring constructors. + // We want to find matches other than the currently created bean itself. + continue; + } + } + throw ex; + } + } + return result; + } + + + //--------------------------------------------------------------------- + // Implementation of ConfigurableListableBeanFactory interface + //--------------------------------------------------------------------- + + public void registerResolvableDependency(Class dependencyType, Object autowiredValue) { + Assert.notNull(dependencyType, "Type must not be null"); + if (autowiredValue != null) { + Assert.isTrue((autowiredValue instanceof ObjectFactory || dependencyType.isInstance(autowiredValue)), + "Value [" + autowiredValue + "] does not implement specified type [" + dependencyType.getName() + "]"); + this.resolvableDependencies.put(dependencyType, autowiredValue); + } + } + + public boolean isAutowireCandidate(String beanName, DependencyDescriptor descriptor) + throws NoSuchBeanDefinitionException { + + // Consider FactoryBeans as autowiring candidates. + boolean isFactoryBean = (descriptor != null && descriptor.getDependencyType() != null && + FactoryBean.class.isAssignableFrom(descriptor.getDependencyType())); + if (isFactoryBean) { + beanName = BeanFactoryUtils.transformedBeanName(beanName); + } + + if (!containsBeanDefinition(beanName)) { + if (containsSingleton(beanName)) { + return true; + } + else if (getParentBeanFactory() instanceof ConfigurableListableBeanFactory) { + // No bean definition found in this factory -> delegate to parent. + return ((ConfigurableListableBeanFactory) getParentBeanFactory()).isAutowireCandidate(beanName, descriptor); + } + } + + return isAutowireCandidate(beanName, getMergedLocalBeanDefinition(beanName), descriptor); + } + + /** + * Determine whether the specified bean definition qualifies as an autowire candidate, + * to be injected into other beans which declare a dependency of matching type. + * @param beanName the name of the bean definition to check + * @param mbd the merged bean definition to check + * @param descriptor the descriptor of the dependency to resolve + * @return whether the bean should be considered as autowire candidate + */ + protected boolean isAutowireCandidate(String beanName, RootBeanDefinition mbd, DependencyDescriptor descriptor) { + resolveBeanClass(mbd, beanName); + return getAutowireCandidateResolver().isAutowireCandidate( + new BeanDefinitionHolder(mbd, beanName, getAliases(beanName)), descriptor); + } + + public BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException { + BeanDefinition bd = (BeanDefinition) this.beanDefinitionMap.get(beanName); + if (bd == null) { + if (this.logger.isTraceEnabled()) { + this.logger.trace("No bean named '" + beanName + "' found in " + this); + } + throw new NoSuchBeanDefinitionException(beanName); + } + return bd; + } + + public void freezeConfiguration() { + this.configurationFrozen = true; + synchronized (this.beanDefinitionMap) { + this.frozenBeanDefinitionNames = StringUtils.toStringArray(this.beanDefinitionNames); + } + } + + public boolean isConfigurationFrozen() { + return this.configurationFrozen; + } + + /** + * Considers all beans as eligible for metdata caching + * if the factory's configuration has been marked as frozen. + * @see #freezeConfiguration() + */ + protected boolean isBeanEligibleForMetadataCaching(String beanName) { + return (this.configurationFrozen || super.isBeanEligibleForMetadataCaching(beanName)); + } + + public void preInstantiateSingletons() throws BeansException { + if (this.logger.isInfoEnabled()) { + this.logger.info("Pre-instantiating singletons in " + this); + } + + synchronized (this.beanDefinitionMap) { + for (Iterator it = this.beanDefinitionNames.iterator(); it.hasNext();) { + String beanName = (String) it.next(); + RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName); + if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) { + if (isFactoryBean(beanName)) { + FactoryBean factory = (FactoryBean) getBean(FACTORY_BEAN_PREFIX + beanName); + if (factory instanceof SmartFactoryBean && ((SmartFactoryBean) factory).isEagerInit()) { + getBean(beanName); + } + } + else { + getBean(beanName); + } + } + } + } + } + + + //--------------------------------------------------------------------- + // Implementation of BeanDefinitionRegistry interface + //--------------------------------------------------------------------- + + public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) + throws BeanDefinitionStoreException { + + Assert.hasText(beanName, "'beanName' must not be empty"); + Assert.notNull(beanDefinition, "BeanDefinition must not be null"); + + if (beanDefinition instanceof AbstractBeanDefinition) { + try { + ((AbstractBeanDefinition) beanDefinition).validate(); + } + catch (BeanDefinitionValidationException ex) { + throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, + "Validation of bean definition failed", ex); + } + } + + synchronized (this.beanDefinitionMap) { + Object oldBeanDefinition = this.beanDefinitionMap.get(beanName); + if (oldBeanDefinition != null) { + if (!this.allowBeanDefinitionOverriding) { + throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, + "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName + + "': There is already [" + oldBeanDefinition + "] bound."); + } + else { + if (this.logger.isInfoEnabled()) { + this.logger.info("Overriding bean definition for bean '" + beanName + + "': replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]"); + } + } + } + else { + this.beanDefinitionNames.add(beanName); + this.frozenBeanDefinitionNames = null; + } + this.beanDefinitionMap.put(beanName, beanDefinition); + + resetBeanDefinition(beanName); + } + } + + public void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException { + Assert.hasText(beanName, "'beanName' must not be empty"); + + synchronized (this.beanDefinitionMap) { + BeanDefinition bd = (BeanDefinition) this.beanDefinitionMap.remove(beanName); + if (bd == null) { + if (this.logger.isTraceEnabled()) { + this.logger.trace("No bean named '" + beanName + "' found in " + this); + } + throw new NoSuchBeanDefinitionException(beanName); + } + this.beanDefinitionNames.remove(beanName); + this.frozenBeanDefinitionNames = null; + + resetBeanDefinition(beanName); + } + } + + /** + * Reset all bean definition caches for the given bean, + * including the caches of beans that are derived from it. + * @param beanName the name of the bean to reset + */ + protected void resetBeanDefinition(String beanName) { + // Remove the merged bean definition for the given bean, if already created. + clearMergedBeanDefinition(beanName); + + // Remove corresponding bean from singleton cache, if any. Shouldn't usually + // be necessary, rather just meant for overriding a context's default beans + // (e.g. the default StaticMessageSource in a StaticApplicationContext). + synchronized (getSingletonMutex()) { + destroySingleton(beanName); + } + + // Reset all bean definitions that have the given bean as parent + // (recursively). + for (Iterator it = this.beanDefinitionNames.iterator(); it.hasNext();) { + String bdName = (String) it.next(); + if (!beanName.equals(bdName)) { + BeanDefinition bd = (BeanDefinition) this.beanDefinitionMap.get(bdName); + if (beanName.equals(bd.getParentName())) { + resetBeanDefinition(bdName); + } + } + } + } + + /** + * Only allows alias overriding if bean definition overriding is allowed. + */ + protected boolean allowAliasOverriding() { + return this.allowBeanDefinitionOverriding; + } + + + //--------------------------------------------------------------------- + // Implementation of superclass abstract methods + //--------------------------------------------------------------------- + + public Object resolveDependency(DependencyDescriptor descriptor, String beanName, + Set autowiredBeanNames, TypeConverter typeConverter) throws BeansException { + + Class type = descriptor.getDependencyType(); + if (type.isArray()) { + Class componentType = type.getComponentType(); + Map matchingBeans = findAutowireCandidates(beanName, componentType, descriptor); + if (matchingBeans.isEmpty()) { + if (descriptor.isRequired()) { + raiseNoSuchBeanDefinitionException(componentType, "array of " + componentType.getName(), descriptor); + } + return null; + } + if (autowiredBeanNames != null) { + autowiredBeanNames.addAll(matchingBeans.keySet()); + } + TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter()); + return converter.convertIfNecessary(matchingBeans.values(), type); + } + else if (Collection.class.isAssignableFrom(type) && type.isInterface()) { + Class elementType = descriptor.getCollectionType(); + if (elementType == null) { + if (descriptor.isRequired()) { + throw new FatalBeanException("No element type declared for collection [" + type.getName() + "]"); + } + return null; + } + Map matchingBeans = findAutowireCandidates(beanName, elementType, descriptor); + if (matchingBeans.isEmpty()) { + if (descriptor.isRequired()) { + raiseNoSuchBeanDefinitionException(elementType, "collection of " + elementType.getName(), descriptor); + } + return null; + } + if (autowiredBeanNames != null) { + autowiredBeanNames.addAll(matchingBeans.keySet()); + } + TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter()); + return converter.convertIfNecessary(matchingBeans.values(), type); + } + else if (Map.class.isAssignableFrom(type) && type.isInterface()) { + Class keyType = descriptor.getMapKeyType(); + if (keyType == null || !String.class.isAssignableFrom(keyType)) { + if (descriptor.isRequired()) { + throw new FatalBeanException("Key type [" + keyType + "] of map [" + type.getName() + + "] must be assignable to [java.lang.String]"); + } + return null; + } + Class valueType = descriptor.getMapValueType(); + if (valueType == null) { + if (descriptor.isRequired()) { + throw new FatalBeanException("No value type declared for map [" + type.getName() + "]"); + } + return null; + } + Map matchingBeans = findAutowireCandidates(beanName, valueType, descriptor); + if (matchingBeans.isEmpty()) { + if (descriptor.isRequired()) { + raiseNoSuchBeanDefinitionException(valueType, "map with value type " + valueType.getName(), descriptor); + } + return null; + } + if (autowiredBeanNames != null) { + autowiredBeanNames.addAll(matchingBeans.keySet()); + } + return matchingBeans; + } + else { + Map matchingBeans = findAutowireCandidates(beanName, type, descriptor); + if (matchingBeans.isEmpty()) { + if (descriptor.isRequired()) { + throw new NoSuchBeanDefinitionException(type, + "Unsatisfied dependency of type [" + type + "]: expected at least 1 matching bean"); + } + return null; + } + if (matchingBeans.size() > 1) { + String primaryBeanName = determinePrimaryCandidate(matchingBeans, type); + if (primaryBeanName == null) { + throw new NoSuchBeanDefinitionException(type, + "expected single matching bean but found " + matchingBeans.size() + ": " + matchingBeans.keySet()); + } + if (autowiredBeanNames != null) { + autowiredBeanNames.add(primaryBeanName); + } + return matchingBeans.get(primaryBeanName); + } + // We have exactly one match. + Map.Entry entry = (Map.Entry) matchingBeans.entrySet().iterator().next(); + if (autowiredBeanNames != null) { + autowiredBeanNames.add(entry.getKey()); + } + return entry.getValue(); + } + } + + /** + * Find bean instances that match the required type. + * Called during autowiring for the specified bean. + * @param beanName the name of the bean that is about to be wired + * @param requiredType the actual type of bean to look for + * (may be an array component type or collection element type) + * @param descriptor the descriptor of the dependency to resolve + * @return a Map of candidate names and candidate instances that match + * the required type (never null) + * @throws BeansException in case of errors + * @see #autowireByType + * @see #autowireConstructor + */ + protected Map findAutowireCandidates(String beanName, Class requiredType, DependencyDescriptor descriptor) { + String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors( + this, requiredType, true, descriptor.isEager()); + Map result = new LinkedHashMap(candidateNames.length); + for (Iterator it = this.resolvableDependencies.keySet().iterator(); it.hasNext();) { + Class autowiringType = (Class) it.next(); + if (autowiringType.isAssignableFrom(requiredType)) { + Object autowiringValue = this.resolvableDependencies.get(autowiringType); + if (autowiringValue instanceof ObjectFactory && !requiredType.isInstance(autowiringValue)) { + autowiringValue = ((ObjectFactory) autowiringValue).getObject(); + } + if (requiredType.isInstance(autowiringValue)) { + result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue); + break; + } + } + } + for (int i = 0; i < candidateNames.length; i++) { + String candidateName = candidateNames[i]; + if (!candidateName.equals(beanName) && isAutowireCandidate(candidateName, descriptor)) { + result.put(candidateName, getBean(candidateName)); + } + } + return result; + } + + /** + * Determine the primary autowire candidate in the given set of beans. + * @param candidateBeans a Map of candidate names and candidate instances + * that match the required type, as returned by {@link #findAutowireCandidates} + * @param type the required type + * @return the name of the primary candidate, or null if none found + */ + protected String determinePrimaryCandidate(Map candidateBeans, Class type) { + String primaryBeanName = null; + for (Iterator it = candidateBeans.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + String candidateBeanName = (String) entry.getKey(); + if (isPrimary(candidateBeanName, entry.getValue())) { + if (primaryBeanName != null) { + throw new NoSuchBeanDefinitionException(type, + "more than one 'primary' bean found among candidates: " + candidateBeans.keySet()); + } + primaryBeanName = candidateBeanName; + } + } + return primaryBeanName; + } + + /** + * Return whether the bean definition for the given bean name has been + * marked as a primary bean. + * @param beanName the name of the bean + * @param beanInstance the corresponding bean instance + * @return whether the given bean qualifies as primary + */ + protected boolean isPrimary(String beanName, Object beanInstance) { + if (containsBeanDefinition(beanName)) { + return getMergedLocalBeanDefinition(beanName).isPrimary(); + } + if (this.resolvableDependencies.values().contains(beanInstance)) { + return true; + } + BeanFactory parentFactory = getParentBeanFactory(); + return (parentFactory instanceof DefaultListableBeanFactory && + ((DefaultListableBeanFactory) parentFactory).isPrimary(beanName, beanInstance)); + } + + /** + * Raise a NoSuchBeanDefinitionException for an unresolvable dependency. + */ + private void raiseNoSuchBeanDefinitionException( + Class type, String dependencyDescription, DependencyDescriptor descriptor) + throws NoSuchBeanDefinitionException { + + throw new NoSuchBeanDefinitionException(type, dependencyDescription, + "expected at least 1 bean which qualifies as autowire candidate for this dependency. " + + "Dependency annotations: " + ObjectUtils.nullSafeToString(descriptor.getAnnotations())); + } + + + public String toString() { + StringBuffer sb = new StringBuffer(ObjectUtils.identityToString(this)); + sb.append(": defining beans ["); + sb.append(StringUtils.arrayToCommaDelimitedString(getBeanDefinitionNames())); + sb.append("]; "); + BeanFactory parent = getParentBeanFactory(); + if (parent == null) { + sb.append("root of factory hierarchy"); + } + else { + sb.append("parent: " + ObjectUtils.identityToString(parent)); + } + return sb.toString(); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java new file mode 100644 index 0000000000..aa8c77bad1 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java @@ -0,0 +1,530 @@ +/* + * Copyright 2002-2008 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.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.BeanCreationNotAllowedException; +import org.springframework.beans.factory.BeanCurrentlyInCreationException; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.config.SingletonBeanRegistry; +import org.springframework.core.CollectionFactory; +import org.springframework.core.SimpleAliasRegistry; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Generic registry for shared bean instances, implementing the + * {@link org.springframework.beans.factory.config.SingletonBeanRegistry}. + * Allows for registering singleton instances that should be shared + * for all callers of the registry, to be obtained via bean name. + * + *

    Also supports registration of + * {@link org.springframework.beans.factory.DisposableBean} instances, + * (which might or might not correspond to registered singletons), + * to be destroyed on shutdown of the registry. Dependencies between + * beans can be registered to enforce an appropriate shutdown order. + * + *

    This class mainly serves as base class for + * {@link org.springframework.beans.factory.BeanFactory} implementations, + * factoring out the common management of singleton bean instances. Note that + * the {@link org.springframework.beans.factory.config.ConfigurableBeanFactory} + * interface extends the {@link SingletonBeanRegistry} interface. + * + *

    Note that this class assumes neither a bean definition concept + * nor a specific creation process for bean instances, in contrast to + * {@link AbstractBeanFactory} and {@link DefaultListableBeanFactory} + * (which inherit from it). Can alternatively also be used as a nested + * helper to delegate to. + * + * @author Juergen Hoeller + * @since 2.0 + * @see #registerSingleton + * @see #registerDisposableBean + * @see org.springframework.beans.factory.DisposableBean + * @see org.springframework.beans.factory.config.ConfigurableBeanFactory + */ +public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry { + + /** + * Internal marker for a null singleton object: + * used as marker value for concurrent Maps (which don't support null values). + */ + protected static final Object NULL_OBJECT = new Object(); + + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + /** Cache of singleton objects: bean name --> bean instance */ + private final Map singletonObjects = CollectionFactory.createConcurrentMapIfPossible(16); + + /** Cache of singleton factories: bean name --> ObjectFactory */ + private final Map singletonFactories = new HashMap(); + + /** Cache of early singleton objects: bean name --> bean instance */ + private final Map earlySingletonObjects = new HashMap(); + + /** Set of registered singletons, containing the bean names in registration order */ + private final Set registeredSingletons = new LinkedHashSet(16); + + /** Names of beans that are currently in creation */ + private final Set singletonsCurrentlyInCreation = Collections.synchronizedSet(new HashSet()); + + /** List of suppressed Exceptions, available for associating related causes */ + private Set suppressedExceptions; + + /** Flag that indicates whether we're currently within destroySingletons */ + private boolean singletonsCurrentlyInDestruction = false; + + /** Disposable bean instances: bean name --> disposable instance */ + private final Map disposableBeans = new LinkedHashMap(16); + + /** Map between containing bean names: bean name --> Set of bean names that the bean contains */ + private final Map containedBeanMap = CollectionFactory.createConcurrentMapIfPossible(16); + + /** Map between dependent bean names: bean name --> Set of dependent bean names */ + private final Map dependentBeanMap = CollectionFactory.createConcurrentMapIfPossible(16); + + /** Map between depending bean names: bean name --> Set of bean names for the bean's dependencies */ + private final Map dependenciesForBeanMap = CollectionFactory.createConcurrentMapIfPossible(16); + + + public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException { + Assert.notNull(beanName, "'beanName' must not be null"); + synchronized (this.singletonObjects) { + Object oldObject = this.singletonObjects.get(beanName); + if (oldObject != null) { + throw new IllegalStateException("Could not register object [" + singletonObject + + "] under bean name '" + beanName + "': there is already object [" + oldObject + "] bound"); + } + addSingleton(beanName, singletonObject); + } + } + + /** + * Add the given singleton object to the singleton cache of this factory. + *

    To be called for eager registration of singletons. + * @param beanName the name of the bean + * @param singletonObject the singleton object + */ + protected void addSingleton(String beanName, Object singletonObject) { + synchronized (this.singletonObjects) { + this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT)); + this.singletonFactories.remove(beanName); + this.earlySingletonObjects.remove(beanName); + this.registeredSingletons.add(beanName); + } + } + + /** + * Add the given singleton factory for building the specified singleton + * if necessary. + *

    To be called for eager registration of singletons, e.g. to be able to + * resolve circular references. + * @param beanName the name of the bean + * @param singletonFactory the factory for the singleton object + */ + protected void addSingletonFactory(String beanName, ObjectFactory singletonFactory) { + Assert.notNull(singletonFactory, "Singleton factory must not be null"); + synchronized (this.singletonObjects) { + if (!this.singletonObjects.containsKey(beanName)) { + this.singletonFactories.put(beanName, singletonFactory); + this.earlySingletonObjects.remove(beanName); + this.registeredSingletons.add(beanName); + } + } + } + + public Object getSingleton(String beanName) { + return getSingleton(beanName, true); + } + + /** + * Return the (raw) singleton object registered under the given name. + *

    Checks already instantiated singletons and also allows for an early + * reference to a currently created singleton (resolving a circular reference). + * @param beanName the name of the bean to look for + * @param allowEarlyReference whether early references should be created or not + * @return the registered singleton object, or null if none found + */ + protected Object getSingleton(String beanName, boolean allowEarlyReference) { + Object singletonObject = this.singletonObjects.get(beanName); + if (singletonObject == null) { + synchronized (this.singletonObjects) { + singletonObject = this.earlySingletonObjects.get(beanName); + if (singletonObject == null && allowEarlyReference) { + ObjectFactory singletonFactory = (ObjectFactory) this.singletonFactories.get(beanName); + if (singletonFactory != null) { + singletonObject = singletonFactory.getObject(); + this.earlySingletonObjects.put(beanName, singletonObject); + this.singletonFactories.remove(beanName); + } + } + } + } + return (singletonObject != NULL_OBJECT ? singletonObject : null); + } + + /** + * Return the (raw) singleton object registered under the given name, + * creating and registering a new one if none registered yet. + * @param beanName the name of the bean + * @param singletonFactory the ObjectFactory to lazily create the singleton + * with, if necessary + * @return the registered singleton object + */ + public Object getSingleton(String beanName, ObjectFactory singletonFactory) { + Assert.notNull(beanName, "'beanName' must not be null"); + synchronized (this.singletonObjects) { + Object singletonObject = this.singletonObjects.get(beanName); + if (singletonObject == null) { + if (this.singletonsCurrentlyInDestruction) { + throw new BeanCreationNotAllowedException(beanName, + "Singleton bean creation not allowed while the singletons of this factory are in destruction " + + "(Do not request a bean from a BeanFactory in a destroy method implementation!)"); + } + if (logger.isDebugEnabled()) { + logger.debug("Creating shared instance of singleton bean '" + beanName + "'"); + } + beforeSingletonCreation(beanName); + boolean recordSuppressedExceptions = (this.suppressedExceptions == null); + if (recordSuppressedExceptions) { + this.suppressedExceptions = new LinkedHashSet(); + } + try { + singletonObject = singletonFactory.getObject(); + } + catch (BeanCreationException ex) { + if (recordSuppressedExceptions) { + for (Iterator it = this.suppressedExceptions.iterator(); it.hasNext();) { + ex.addRelatedCause((Exception) it.next()); + } + } + throw ex; + } + finally { + if (recordSuppressedExceptions) { + this.suppressedExceptions = null; + } + afterSingletonCreation(beanName); + } + addSingleton(beanName, singletonObject); + } + return (singletonObject != NULL_OBJECT ? singletonObject : null); + } + } + + /** + * Register an Exception that happened to get suppressed during the creation of a + * singleton bean instance, e.g. a temporary circular reference resolution problem. + * @param ex the Exception to register + */ + protected void onSuppressedException(Exception ex) { + synchronized (this.singletonObjects) { + if (this.suppressedExceptions != null) { + this.suppressedExceptions.add(ex); + } + } + } + + /** + * Remove the bean with the given name from the singleton cache of this factory, + * to be able to clean up eager registration of a singleton if creation failed. + * @param beanName the name of the bean + * @see #getSingletonMutex() + */ + protected void removeSingleton(String beanName) { + synchronized (this.singletonObjects) { + this.singletonObjects.remove(beanName); + this.singletonFactories.remove(beanName); + this.earlySingletonObjects.remove(beanName); + this.registeredSingletons.remove(beanName); + } + } + + public boolean containsSingleton(String beanName) { + return (this.singletonObjects.containsKey(beanName)); + } + + public String[] getSingletonNames() { + synchronized (this.singletonObjects) { + return StringUtils.toStringArray(this.registeredSingletons); + } + } + + public int getSingletonCount() { + synchronized (this.singletonObjects) { + return this.registeredSingletons.size(); + } + } + + + /** + * Callback before singleton creation. + *

    Default implementation register the singleton as currently in creation. + * @param beanName the name of the singleton about to be created + * @see #isSingletonCurrentlyInCreation + */ + protected void beforeSingletonCreation(String beanName) { + if (!this.singletonsCurrentlyInCreation.add(beanName)) { + throw new BeanCurrentlyInCreationException(beanName); + } + } + + /** + * Callback after singleton creation. + *

    Default implementation marks the singleton as not in creation anymore. + * @param beanName the name of the singleton that has been created + * @see #isSingletonCurrentlyInCreation + */ + protected void afterSingletonCreation(String beanName) { + if (!this.singletonsCurrentlyInCreation.remove(beanName)) { + throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation"); + } + } + + /** + * Return whether the specified singleton bean is currently in creation + * (within the entire factory). + * @param beanName the name of the bean + */ + public final boolean isSingletonCurrentlyInCreation(String beanName) { + return this.singletonsCurrentlyInCreation.contains(beanName); + } + + + /** + * Add the given bean to the list of disposable beans in this registry. + * Disposable beans usually correspond to registered singletons, + * matching the bean name but potentially being a different instance + * (for example, a DisposableBean adapter for a singleton that does not + * naturally implement Spring's DisposableBean interface). + * @param beanName the name of the bean + * @param bean the bean instance + */ + public void registerDisposableBean(String beanName, DisposableBean bean) { + synchronized (this.disposableBeans) { + this.disposableBeans.put(beanName, bean); + } + } + + /** + * Register a containment relationship between two beans, + * e.g. between an inner bean and its containing outer bean. + *

    Also registers the containing bean as dependent on the contained bean + * in terms of destruction order. + * @param containedBeanName the name of the contained (inner) bean + * @param containingBeanName the name of the containing (outer) bean + * @see #registerDependentBean + */ + public void registerContainedBean(String containedBeanName, String containingBeanName) { + synchronized (this.containedBeanMap) { + Set containedBeans = (Set) this.containedBeanMap.get(containingBeanName); + if (containedBeans == null) { + containedBeans = new LinkedHashSet(8); + this.containedBeanMap.put(containingBeanName, containedBeans); + } + containedBeans.add(containedBeanName); + } + registerDependentBean(containedBeanName, containingBeanName); + } + + /** + * Register a dependent bean for the given bean, + * to be destroyed before the given bean is destroyed. + * @param beanName the name of the bean + * @param dependentBeanName the name of the dependent bean + */ + public void registerDependentBean(String beanName, String dependentBeanName) { + synchronized (this.dependentBeanMap) { + Set dependentBeans = (Set) this.dependentBeanMap.get(beanName); + if (dependentBeans == null) { + dependentBeans = new LinkedHashSet(8); + this.dependentBeanMap.put(beanName, dependentBeans); + } + dependentBeans.add(dependentBeanName); + } + synchronized (this.dependenciesForBeanMap) { + Set dependenciesForBean = (Set) this.dependenciesForBeanMap.get(dependentBeanName); + if (dependenciesForBean == null) { + dependenciesForBean = new LinkedHashSet(8); + this.dependenciesForBeanMap.put(dependentBeanName, dependenciesForBean); + } + dependenciesForBean.add(beanName); + } + } + + /** + * Determine whether a dependent bean has been registered for the given name. + * @param beanName the name of the bean to check + */ + protected boolean hasDependentBean(String beanName) { + return this.dependentBeanMap.containsKey(beanName); + } + + /** + * Return the names of all beans which depend on the specified bean, if any. + * @param beanName the name of the bean + * @return the array of dependent bean names, or an empty array if none + */ + public String[] getDependentBeans(String beanName) { + Set dependentBeans = (Set) this.dependentBeanMap.get(beanName); + if (dependentBeans == null) { + return new String[0]; + } + return (String[]) dependentBeans.toArray(new String[dependentBeans.size()]); + } + + /** + * Return the names of all beans that the specified bean depends on, if any. + * @param beanName the name of the bean + * @return the array of names of beans which the bean depends on, + * or an empty array if none + */ + public String[] getDependenciesForBean(String beanName) { + Set dependenciesForBean = (Set) this.dependenciesForBeanMap.get(beanName); + if (dependenciesForBean == null) { + return new String[0]; + } + return (String[]) dependenciesForBean.toArray(new String[dependenciesForBean.size()]); + } + + public void destroySingletons() { + if (logger.isInfoEnabled()) { + logger.info("Destroying singletons in " + this); + } + synchronized (this.singletonObjects) { + this.singletonsCurrentlyInDestruction = true; + } + + synchronized (this.disposableBeans) { + String[] disposableBeanNames = StringUtils.toStringArray(this.disposableBeans.keySet()); + for (int i = disposableBeanNames.length - 1; i >= 0; i--) { + destroySingleton(disposableBeanNames[i]); + } + } + + this.containedBeanMap.clear(); + this.dependentBeanMap.clear(); + this.dependenciesForBeanMap.clear(); + + synchronized (this.singletonObjects) { + this.singletonObjects.clear(); + this.singletonFactories.clear(); + this.earlySingletonObjects.clear(); + this.registeredSingletons.clear(); + this.singletonsCurrentlyInDestruction = false; + } + } + + /** + * Destroy the given bean. Delegates to destroyBean + * if a corresponding disposable bean instance is found. + * @param beanName the name of the bean + * @see #destroyBean + */ + public void destroySingleton(String beanName) { + // Remove a registered singleton of the given name, if any. + removeSingleton(beanName); + + // Destroy the corresponding DisposableBean instance. + DisposableBean disposableBean = null; + synchronized (this.disposableBeans) { + disposableBean = (DisposableBean) this.disposableBeans.remove(beanName); + } + destroyBean(beanName, disposableBean); + } + + /** + * Destroy the given bean. Must destroy beans that depend on the given + * bean before the bean itself. Should not throw any exceptions. + * @param beanName the name of the bean + * @param bean the bean instance to destroy + */ + protected void destroyBean(String beanName, DisposableBean bean) { + // Trigger destruction of dependent beans first... + Set dependencies = (Set) this.dependentBeanMap.remove(beanName); + if (dependencies != null) { + if (logger.isDebugEnabled()) { + logger.debug("Retrieved dependent beans for bean '" + beanName + "': " + dependencies); + } + for (Iterator it = dependencies.iterator(); it.hasNext();) { + String dependentBeanName = (String) it.next(); + destroySingleton(dependentBeanName); + } + } + + // Actually destroy the bean now... + if (bean != null) { + try { + bean.destroy(); + } + catch (Throwable ex) { + logger.error("Destroy method on bean with name '" + beanName + "' threw an exception", ex); + } + } + + // Trigger destruction of contained beans... + Set containedBeans = (Set) this.containedBeanMap.remove(beanName); + if (containedBeans != null) { + for (Iterator it = containedBeans.iterator(); it.hasNext();) { + String containedBeanName = (String) it.next(); + destroySingleton(containedBeanName); + } + } + + // Remove destroyed bean from other beans' dependencies. + synchronized (this.dependentBeanMap) { + for (Iterator it = this.dependentBeanMap.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + Set dependenciesToClean = (Set) entry.getValue(); + dependenciesToClean.remove(beanName); + if (dependenciesToClean.isEmpty()) { + it.remove(); + } + } + } + + // Remove destroyed bean's prepared dependency information. + this.dependenciesForBeanMap.remove(beanName); + } + + /** + * Expose the singleton mutex to subclasses. + *

    Subclasses should synchronize on the given Object if they perform + * any sort of extended singleton creation phase. In particular, subclasses + * should not have their own mutexes involved in singleton creation, + * to avoid the potential for deadlocks in lazy-init situations. + */ + protected final Object getSingletonMutex() { + return this.singletonObjects; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java new file mode 100644 index 0000000000..907ede0a96 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java @@ -0,0 +1,254 @@ +/* + * Copyright 2002-2008 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.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; + +/** + * Adapter that implements the {@link DisposableBean} interface + * performing various destruction steps on a given bean instance: + *

      + *
    • DestructionAwareBeanPostProcessors + *
    • the bean implementing DisposableBean itself + *
    • a custom destroy method specified on the bean definition + *
    + * + * @author Juergen Hoeller + * @since 2.0 + * @see AbstractBeanFactory + * @see org.springframework.beans.factory.DisposableBean + * @see org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor + * @see AbstractBeanDefinition#getDestroyMethodName() + */ +class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { + + private static final Log logger = LogFactory.getLog(DisposableBeanAdapter.class); + + private final Object bean; + + private final String beanName; + + private final boolean invokeDisposableBean; + + private final String destroyMethodName; + + private final boolean enforceDestroyMethod; + + private List beanPostProcessors; + + + /** + * Create a new DisposableBeanAdapter for the given bean. + * @param bean the bean instance (never null) + * @param beanName the name of the bean + * @param beanDefinition the merged bean definition + * @param postProcessors the List of BeanPostProcessors + * (potentially DestructionAwareBeanPostProcessor), if any + */ + public DisposableBeanAdapter( + Object bean, String beanName, RootBeanDefinition beanDefinition, List postProcessors) { + + Assert.notNull(bean, "Bean must not be null"); + this.bean = bean; + this.beanName = beanName; + this.invokeDisposableBean = !beanDefinition.isExternallyManagedDestroyMethod("destroy"); + this.destroyMethodName = + (!beanDefinition.isExternallyManagedDestroyMethod(beanDefinition.getDestroyMethodName()) ? + beanDefinition.getDestroyMethodName() : null); + this.enforceDestroyMethod = beanDefinition.isEnforceDestroyMethod(); + this.beanPostProcessors = filterPostProcessors(postProcessors); + } + + /** + * Create a new DisposableBeanAdapter for the given bean. + * @param bean the bean instance (never null) + * @param beanName the name of the bean + * @param invokeDisposableBean whether to actually invoke + * DisposableBean's destroy method here + * @param destroyMethodName the name of the custom destroy method + * (null if there is none) + * @param enforceDestroyMethod whether to the specified custom + * destroy method (if any) has to be present on the bean object + * @param postProcessors the List of DestructionAwareBeanPostProcessors, if any + */ + private DisposableBeanAdapter(Object bean, String beanName, boolean invokeDisposableBean, + String destroyMethodName, boolean enforceDestroyMethod, List postProcessors) { + + this.bean = bean; + this.beanName = beanName; + this.invokeDisposableBean = invokeDisposableBean; + this.destroyMethodName = destroyMethodName; + this.enforceDestroyMethod = enforceDestroyMethod; + this.beanPostProcessors = postProcessors; + } + + /** + * Search for all DestructionAwareBeanPostProcessors in the List. + * @param postProcessors the List to search + * @return the filtered List of DestructionAwareBeanPostProcessors + */ + private List filterPostProcessors(List postProcessors) { + List filteredPostProcessors = null; + if (postProcessors != null && !postProcessors.isEmpty()) { + filteredPostProcessors = new ArrayList(postProcessors.size()); + for (Iterator it = postProcessors.iterator(); it.hasNext();) { + Object postProcessor = it.next(); + if (postProcessor instanceof DestructionAwareBeanPostProcessor) { + filteredPostProcessors.add(postProcessor); + } + } + } + return filteredPostProcessors; + } + + + public void run() { + destroy(); + } + + public void destroy() { + if (this.beanPostProcessors != null && !this.beanPostProcessors.isEmpty()) { + for (int i = this.beanPostProcessors.size() - 1; i >= 0; i--) { + ((DestructionAwareBeanPostProcessor) this.beanPostProcessors.get(i)).postProcessBeforeDestruction( + this.bean, this.beanName); + } + } + + boolean isDisposableBean = (this.bean instanceof DisposableBean); + if (isDisposableBean && this.invokeDisposableBean) { + if (logger.isDebugEnabled()) { + logger.debug("Invoking destroy() on bean with name '" + this.beanName + "'"); + } + try { + ((DisposableBean) this.bean).destroy(); + } + catch (Throwable ex) { + String msg = "Invocation of destroy method failed on bean with name '" + this.beanName + "'"; + if (logger.isDebugEnabled()) { + logger.warn(msg, ex); + } + else { + logger.warn(msg + ": " + ex); + } + } + } + + if (this.destroyMethodName != null && !(isDisposableBean && "destroy".equals(this.destroyMethodName))) { + invokeCustomDestroyMethod(); + } + } + + /** + * Invoke the specified custom destroy method on the given bean. + *

    This implementation invokes a no-arg method if found, else checking + * for a method with a single boolean argument (passing in "true", + * assuming a "force" parameter), else logging an error. + */ + private void invokeCustomDestroyMethod() { + try { + Method destroyMethod = + BeanUtils.findMethodWithMinimalParameters(this.bean.getClass(), this.destroyMethodName); + if (destroyMethod == null) { + if (this.enforceDestroyMethod) { + logger.warn("Couldn't find a destroy method named '" + this.destroyMethodName + + "' on bean with name '" + this.beanName + "'"); + } + } + + else { + Class[] paramTypes = destroyMethod.getParameterTypes(); + if (paramTypes.length > 1) { + logger.error("Method '" + this.destroyMethodName + "' of bean '" + this.beanName + + "' has more than one parameter - not supported as destroy method"); + } + else if (paramTypes.length == 1 && !paramTypes[0].equals(boolean.class)) { + logger.error("Method '" + this.destroyMethodName + "' of bean '" + this.beanName + + "' has a non-boolean parameter - not supported as destroy method"); + } + + else { + Object[] args = new Object[paramTypes.length]; + if (paramTypes.length == 1) { + args[0] = Boolean.TRUE; + } + if (logger.isDebugEnabled()) { + logger.debug("Invoking destroy method '" + this.destroyMethodName + + "' on bean with name '" + this.beanName + "'"); + } + ReflectionUtils.makeAccessible(destroyMethod); + try { + destroyMethod.invoke(this.bean, args); + } + catch (InvocationTargetException ex) { + String msg = "Invocation of destroy method '" + this.destroyMethodName + + "' failed on bean with name '" + this.beanName + "'"; + if (logger.isDebugEnabled()) { + logger.warn(msg, ex.getTargetException()); + } + else { + logger.warn(msg + ": " + ex.getTargetException()); + } + } + catch (Throwable ex) { + logger.error("Couldn't invoke destroy method '" + this.destroyMethodName + + "' on bean with name '" + this.beanName + "'", ex); + } + } + } + } + catch (IllegalArgumentException ex) { + // thrown from findMethodWithMinimalParameters + logger.error("Couldn't find a unique destroy method on bean with name '" + + this.beanName + ": " + ex.getMessage()); + } + } + + + /** + * Serializes a copy of the state of this class, + * filtering out non-serializable BeanPostProcessors. + */ + protected Object writeReplace() { + List serializablePostProcessors = null; + if (this.beanPostProcessors != null) { + serializablePostProcessors = new ArrayList(); + for (Iterator it = this.beanPostProcessors.iterator(); it.hasNext();) { + Object postProcessor = it.next(); + if (postProcessor instanceof Serializable) { + serializablePostProcessors.add(postProcessor); + } + } + } + return new DisposableBeanAdapter(this.bean, this.beanName, this.invokeDisposableBean, + this.destroyMethodName, this.enforceDestroyMethod, serializablePostProcessors); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/FactoryBeanRegistrySupport.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/FactoryBeanRegistrySupport.java new file mode 100644 index 0000000000..2154caca73 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/FactoryBeanRegistrySupport.java @@ -0,0 +1,188 @@ +/* + * Copyright 2002-2008 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.security.AccessControlContext; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Map; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.BeanCurrentlyInCreationException; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.FactoryBeanNotInitializedException; +import org.springframework.core.CollectionFactory; + +/** + * Support base class for singleton registries which need to handle + * {@link org.springframework.beans.factory.FactoryBean} instances, + * integrated with {@link DefaultSingletonBeanRegistry}'s singleton management. + * + *

    Serves as base class for {@link AbstractBeanFactory}. + * + * @author Juergen Hoeller + * @since 2.5.1 + */ +public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanRegistry { + + /** Cache of singleton objects created by FactoryBeans: FactoryBean name --> object */ + private final Map factoryBeanObjectCache = CollectionFactory.createConcurrentMapIfPossible(16); + + + /** + * Determine the type for the given FactoryBean. + * @param factoryBean the FactoryBean instance to check + * @return the FactoryBean's object type, + * or null if the type cannot be determined yet + */ + protected Class getTypeForFactoryBean(FactoryBean factoryBean) { + try { + return factoryBean.getObjectType(); + } + catch (Throwable ex) { + // Thrown from the FactoryBean's getObjectType implementation. + logger.warn("FactoryBean threw exception from getObjectType, despite the contract saying " + + "that it should return null if the type of its object cannot be determined yet", ex); + return null; + } + } + + /** + * Obtain an object to expose from the given FactoryBean, if available + * in cached form. Quick check for minimal synchronization. + * @param beanName the name of the bean + * @return the object obtained from the FactoryBean, + * or null if not available + */ + protected Object getCachedObjectForFactoryBean(String beanName) { + Object object = this.factoryBeanObjectCache.get(beanName); + return (object != NULL_OBJECT ? object : null); + } + + /** + * Obtain an object to expose from the given FactoryBean. + * @param factory the FactoryBean instance + * @param beanName the name of the bean + * @param shouldPostProcess whether the bean is subject for post-processing + * @return the object obtained from the FactoryBean + * @throws BeanCreationException if FactoryBean object creation failed + * @see org.springframework.beans.factory.FactoryBean#getObject() + */ + protected Object getObjectFromFactoryBean(FactoryBean factory, String beanName, boolean shouldPostProcess) { + if (factory.isSingleton() && containsSingleton(beanName)) { + synchronized (getSingletonMutex()) { + Object object = this.factoryBeanObjectCache.get(beanName); + if (object == null) { + object = doGetObjectFromFactoryBean(factory, beanName, shouldPostProcess); + this.factoryBeanObjectCache.put(beanName, (object != null ? object : NULL_OBJECT)); + } + return (object != NULL_OBJECT ? object : null); + } + } + else { + return doGetObjectFromFactoryBean(factory, beanName, shouldPostProcess); + } + } + + /** + * Obtain an object to expose from the given FactoryBean. + * @param factory the FactoryBean instance + * @param beanName the name of the bean + * @param shouldPostProcess whether the bean is subject for post-processing + * @return the object obtained from the FactoryBean + * @throws BeanCreationException if FactoryBean object creation failed + * @see org.springframework.beans.factory.FactoryBean#getObject() + */ + private Object doGetObjectFromFactoryBean( + final FactoryBean factory, final String beanName, final boolean shouldPostProcess) + throws BeanCreationException { + + AccessControlContext acc = AccessController.getContext(); + return AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + Object object; + + try { + object = factory.getObject(); + } + catch (FactoryBeanNotInitializedException ex) { + throw new BeanCurrentlyInCreationException(beanName, ex.toString()); + } + catch (Throwable ex) { + throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", ex); + } + + // Do not accept a null value for a FactoryBean that's not fully + // initialized yet: Many FactoryBeans just return null then. + if (object == null && isSingletonCurrentlyInCreation(beanName)) { + throw new BeanCurrentlyInCreationException( + beanName, "FactoryBean which is currently in creation returned null from getObject"); + } + + if (object != null && shouldPostProcess) { + try { + object = postProcessObjectFromFactoryBean(object, beanName); + } + catch (Throwable ex) { + throw new BeanCreationException(beanName, "Post-processing of the FactoryBean's object failed", ex); + } + } + + return object; + } + }, acc); + } + + /** + * Post-process the given object that has been obtained from the FactoryBean. + * The resulting object will get exposed for bean references. + *

    The default implementation simply returns the given object as-is. + * Subclasses may override this, for example, to apply post-processors. + * @param object the object obtained from the FactoryBean. + * @param beanName the name of the bean + * @return the object to expose + * @throws org.springframework.beans.BeansException if any post-processing failed + */ + protected Object postProcessObjectFromFactoryBean(Object object, String beanName) throws BeansException { + return object; + } + + /** + * Get a FactoryBean for the given bean if possible. + * @param beanName the name of the bean + * @param beanInstance the corresponding bean instance + * @return the bean instance as FactoryBean + * @throws BeansException if the given bean cannot be exposed as a FactoryBean + */ + protected FactoryBean getFactoryBean(String beanName, Object beanInstance) throws BeansException { + if (!(beanInstance instanceof FactoryBean)) { + throw new BeanCreationException(beanName, + "Bean instance of type [" + beanInstance.getClass() + "] is not a FactoryBean"); + } + return (FactoryBean) beanInstance; + } + + /** + * Overridden to clear the FactoryBean object cache as well. + */ + protected void removeSingleton(String beanName) { + super.removeSingleton(beanName); + this.factoryBeanObjectCache.remove(beanName); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/GenericBeanDefinition.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/GenericBeanDefinition.java new file mode 100644 index 0000000000..a2a6bc6bd1 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/GenericBeanDefinition.java @@ -0,0 +1,89 @@ +/* + * Copyright 2002-2008 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 org.springframework.beans.factory.config.BeanDefinition; + +/** + * GenericBeanDefinition is a one-stop shop for standard bean definition purposes. + * Like any bean definition, it allows for specifying a class plus optionally + * constructor argument values and property values. Additionally, deriving from a + * parent bean definition can be flexibly configured through the "parentName" property. + * + *

    In general, use this GenericBeanDefinition class for the purpose of + * registering user-visible bean definitions (which a post-processor might operate on, + * potentially even reconfiguring the parent name). Use RootBeanDefinition / + * ChildBeanDefinition where parent/child relationships happen to be pre-determined. + * + * @author Juergen Hoeller + * @since 2.5 + * @see #setParentName + * @see RootBeanDefinition + * @see ChildBeanDefinition + */ +public class GenericBeanDefinition extends AbstractBeanDefinition { + + private String parentName; + + + /** + * Create a new GenericBeanDefinition, to be configured through its bean + * properties and configuration methods. + * @see #setBeanClass + * @see #setBeanClassName + * @see #setScope + * @see #setAutowireMode + * @see #setDependencyCheck + * @see #setConstructorArgumentValues + * @see #setPropertyValues + */ + public GenericBeanDefinition() { + super(); + } + + /** + * Create a new GenericBeanDefinition as deep copy of the given + * bean definition. + * @param original the original bean definition to copy from + */ + public GenericBeanDefinition(BeanDefinition original) { + super(original); + } + + + public void setParentName(String parentName) { + this.parentName = parentName; + } + + public String getParentName() { + return this.parentName; + } + + + public AbstractBeanDefinition cloneBeanDefinition() { + return new GenericBeanDefinition(this); + } + + public boolean equals(Object other) { + return (this == other || (other instanceof GenericBeanDefinition && super.equals(other))); + } + + public String toString() { + return "Generic bean: " + super.toString(); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/InstantiationStrategy.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/InstantiationStrategy.java new file mode 100644 index 0000000000..13355a69ee --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/InstantiationStrategy.java @@ -0,0 +1,85 @@ +/* + * Copyright 2002-2005 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.Constructor; +import java.lang.reflect.Method; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; + +/** + * Interface responsible for creating instances corresponding to a root bean definition. + * + *

    This is pulled out into a strategy as various approaches are possible, + * including using CGLIB to create subclasses on the fly to support Method Injection. + * + * @author Rod Johnson + * @since 1.1 + */ +public interface InstantiationStrategy { + + /** + * Return an instance of the bean with the given name in this factory. + * @param beanDefinition the bean definition + * @param beanName name of the bean when it's created in this context. + * The name can be null if we're autowiring a bean that + * doesn't belong to the factory. + * @param owner owning BeanFactory + * @return a bean instance for this bean definition + * @throws BeansException if the instantiation failed + */ + Object instantiate( + RootBeanDefinition beanDefinition, String beanName, BeanFactory owner) throws BeansException; + + /** + * Return an instance of the bean with the given name in this factory, + * creating it via the given constructor. + * @param beanDefinition the bean definition + * @param beanName name of the bean when it's created in this context. + * The name can be null if we're autowiring a bean + * that doesn't belong to the factory. + * @param owner owning BeanFactory + * @param ctor the constructor to use + * @param args the constructor arguments to apply + * @return a bean instance for this bean definition + * @throws BeansException if the instantiation failed + */ + Object instantiate( + RootBeanDefinition beanDefinition, String beanName, BeanFactory owner, + Constructor ctor, Object[] args) throws BeansException; + + /** + * Return an instance of the bean with the given name in this factory, + * creating it via the given factory method. + * @param beanDefinition bean definition + * @param beanName name of the bean when it's created in this context. + * The name can be null if we're autowiring a bean + * that doesn't belong to the factory. + * @param owner owning BeanFactory + * @param factoryBean the factory bean instance to call the factory method on, + * or null in case of a static factory method + * @param factoryMethod the factory method to use + * @param args the factory method arguments to apply + * @return a bean instance for this bean definition + * @throws BeansException if the instantiation failed + */ + Object instantiate( + RootBeanDefinition beanDefinition, String beanName, BeanFactory owner, + Object factoryBean, Method factoryMethod, Object[] args) throws BeansException; + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/LookupOverride.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/LookupOverride.java new file mode 100644 index 0000000000..985f5d8581 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/LookupOverride.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2007 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.Method; + +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * Represents an override of a method that looks up an object in the same IoC context. + * + *

    Methods eligible for lookup override must not have arguments. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 1.1 + */ +public class LookupOverride extends MethodOverride { + + private final String beanName; + + + /** + * Construct a new LookupOverride. + * @param methodName the name of the method to override. + * This method must have no arguments. + * @param beanName name of the bean in the current BeanFactory + * that the overriden method should return + */ + public LookupOverride(String methodName, String beanName) { + super(methodName); + Assert.notNull(beanName, "Bean name must not be null"); + this.beanName = beanName; + } + + /** + * Return the name of the bean that should be returned by this method. + */ + public String getBeanName() { + return this.beanName; + } + + + /** + * Match method of the given name, with no parameters. + */ + public boolean matches(Method method) { + return (method.getName().equals(getMethodName()) && method.getParameterTypes().length == 0); + } + + + public String toString() { + return "LookupOverride for method '" + getMethodName() + "'; will return bean '" + this.beanName + "'"; + } + + public boolean equals(Object other) { + return (other instanceof LookupOverride && super.equals(other) && + ObjectUtils.nullSafeEquals(this.beanName, ((LookupOverride) other).beanName)); + } + + public int hashCode() { + return (29 * super.hashCode() + ObjectUtils.nullSafeHashCode(this.beanName)); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/ManagedList.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/ManagedList.java new file mode 100644 index 0000000000..b6d5c71172 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/ManagedList.java @@ -0,0 +1,89 @@ +/* + * Copyright 2002-2007 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.util.ArrayList; +import java.util.List; + +import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.Mergeable; + +/** + * Tag collection class used to hold managed List elements, which may + * include runtime bean references (to be resolved into bean objects). + * + * @author Rod Johnson + * @author Rob Harrop + * @author Juergen Hoeller + * @since 27.05.2003 + */ +public class ManagedList extends ArrayList implements Mergeable, BeanMetadataElement { + + private Object source; + + private boolean mergeEnabled; + + + public ManagedList() { + } + + public ManagedList(int initialCapacity) { + super(initialCapacity); + } + + + /** + * Set the configuration source Object for this metadata element. + *

    The exact type of the object will depend on the configuration mechanism used. + */ + public void setSource(Object source) { + this.source = source; + } + + public Object getSource() { + return this.source; + } + + /** + * Set whether merging should be enabled for this collection, + * in case of a 'parent' collection value being present. + */ + public void setMergeEnabled(boolean mergeEnabled) { + this.mergeEnabled = mergeEnabled; + } + + public boolean isMergeEnabled() { + return this.mergeEnabled; + } + + public Object merge(Object parent) { + if (!this.mergeEnabled) { + throw new IllegalStateException("Not allowed to merge when the 'mergeEnabled' property is set to 'false'"); + } + if (parent == null) { + return this; + } + if (!(parent instanceof List)) { + throw new IllegalArgumentException("Cannot merge with object of type [" + parent.getClass() + "]"); + } + List merged = new ManagedList(); + merged.addAll((List) parent); + merged.addAll(this); + return merged; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/ManagedMap.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/ManagedMap.java new file mode 100644 index 0000000000..c6d379d96c --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/ManagedMap.java @@ -0,0 +1,88 @@ +/* + * Copyright 2002-2007 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.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.Mergeable; + +/** + * Tag collection class used to hold managed Map values, which may + * include runtime bean references (to be resolved into bean objects). + * + * @author Juergen Hoeller + * @author Rob Harrop + * @since 27.05.2003 + */ +public class ManagedMap extends LinkedHashMap implements Mergeable, BeanMetadataElement { + + private Object source; + + private boolean mergeEnabled; + + + public ManagedMap() { + } + + public ManagedMap(int initialCapacity) { + super(initialCapacity); + } + + + /** + * Set the configuration source Object for this metadata element. + *

    The exact type of the object will depend on the configuration mechanism used. + */ + public void setSource(Object source) { + this.source = source; + } + + public Object getSource() { + return this.source; + } + + /** + * Set whether merging should be enabled for this collection, + * in case of a 'parent' collection value being present. + */ + public void setMergeEnabled(boolean mergeEnabled) { + this.mergeEnabled = mergeEnabled; + } + + public boolean isMergeEnabled() { + return this.mergeEnabled; + } + + public Object merge(Object parent) { + if (!this.mergeEnabled) { + throw new IllegalStateException("Not allowed to merge when the 'mergeEnabled' property is set to 'false'"); + } + if (parent == null) { + return this; + } + if (!(parent instanceof Map)) { + throw new IllegalArgumentException("Cannot merge with object of type [" + parent.getClass() + "]"); + } + Map merged = new ManagedMap(); + merged.putAll((Map) parent); + merged.putAll(this); + return merged; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/ManagedProperties.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/ManagedProperties.java new file mode 100644 index 0000000000..91a433bc6c --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/ManagedProperties.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2007 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.util.Properties; + +import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.Mergeable; + +/** + * Tag class which represents a Spring-managed {@link Properties} instance + * that supports merging of parent/child definitions. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + */ +public class ManagedProperties extends Properties implements Mergeable, BeanMetadataElement { + + private Object source; + + private boolean mergeEnabled; + + + /** + * Set the configuration source Object for this metadata element. + *

    The exact type of the object will depend on the configuration mechanism used. + */ + public void setSource(Object source) { + this.source = source; + } + + public Object getSource() { + return this.source; + } + + /** + * Set whether merging should be enabled for this collection, + * in case of a 'parent' collection value being present. + */ + public void setMergeEnabled(boolean mergeEnabled) { + this.mergeEnabled = mergeEnabled; + } + + public boolean isMergeEnabled() { + return this.mergeEnabled; + } + + + public Object merge(Object parent) { + if (!this.mergeEnabled) { + throw new IllegalStateException("Not allowed to merge when the 'mergeEnabled' property is set to 'false'"); + } + if (parent == null) { + return this; + } + if (!(parent instanceof Properties)) { + throw new IllegalArgumentException("Cannot merge with object of type [" + parent.getClass() + "]"); + } + Properties merged = new ManagedProperties(); + merged.putAll((Properties) parent); + merged.putAll(this); + return merged; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/ManagedSet.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/ManagedSet.java new file mode 100644 index 0000000000..866fee0757 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/ManagedSet.java @@ -0,0 +1,90 @@ +/* + * Copyright 2002-2007 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.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.Mergeable; + +/** + * Tag collection class used to hold managed Set values, which may + * include runtime bean references (to be resolved into bean objects). + * + * @author Juergen Hoeller + * @author Rob Harrop + * @since 21.01.2004 + */ +public class ManagedSet extends LinkedHashSet implements Mergeable, BeanMetadataElement { + + private Object source; + + private boolean mergeEnabled; + + + public ManagedSet() { + } + + public ManagedSet(int initialCapacity) { + super(initialCapacity); + } + + + /** + * Set the configuration source Object for this metadata element. + *

    The exact type of the object will depend on the configuration mechanism used. + */ + public void setSource(Object source) { + this.source = source; + } + + public Object getSource() { + return this.source; + } + + /** + * Set whether merging should be enabled for this collection, + * in case of a 'parent' collection value being present. + */ + public void setMergeEnabled(boolean mergeEnabled) { + this.mergeEnabled = mergeEnabled; + } + + public boolean isMergeEnabled() { + return this.mergeEnabled; + } + + public Object merge(Object parent) { + if (!this.mergeEnabled) { + throw new IllegalStateException("Not allowed to merge when the 'mergeEnabled' property is set to 'false'"); + } + if (parent == null) { + return this; + } + if (!(parent instanceof Set)) { + throw new IllegalArgumentException("Cannot merge with object of type [" + parent.getClass() + "]"); + } + Set merged = new ManagedSet(); + merged.addAll((Set) parent); + merged.addAll(this); + return merged; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/MergedBeanDefinitionPostProcessor.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/MergedBeanDefinitionPostProcessor.java new file mode 100644 index 0000000000..48a534a914 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/MergedBeanDefinitionPostProcessor.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2008 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 org.springframework.beans.factory.config.BeanPostProcessor; + +/** + * Post-processor callback interface for merged bean definitions at runtime. + * {@link BeanPostProcessor} implementations may implement this sub-interface in + * order to post-process the merged bean definition that the Spring BeanFactory + * uses to create a specific bean instance. + * + *

    The {@link #postProcessMergedBeanDefinition} method may for example introspect + * the bean definition in order to prepare some cached metadata before post-processing + * actual instances of a bean. It is also allowed to modify the bean definition + * but only for bean definition properties which are actually intended + * for concurrent modification. Basically, this only applies to operations + * defined on the {@link RootBeanDefinition} itself but not to the properties + * of its base classes. + * + * @author Juergen Hoeller + * @since 2.5 + */ +public interface MergedBeanDefinitionPostProcessor extends BeanPostProcessor { + + /** + * Post-process the given merged bean definition for the specified bean. + * @param beanDefinition the merged bean definition for the bean + * @param beanType the actual type of the managed bean instance + * @param beanName the name of the bean + */ + void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class beanType, String beanName); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/MethodOverride.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/MethodOverride.java new file mode 100644 index 0000000000..aa775d9395 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/MethodOverride.java @@ -0,0 +1,121 @@ +/* + * Copyright 2002-2007 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.Method; + +import org.springframework.beans.BeanMetadataElement; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * Object representing the override of a method on a managed + * object by the IoC container. + * + *

    Note that the override mechanism is not intended as a + * generic means of inserting crosscutting code: use AOP for that. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 1.1 + */ +public abstract class MethodOverride implements BeanMetadataElement { + + private final String methodName; + + private boolean overloaded = true; + + private Object source; + + + /** + * Construct a new override for the given method. + * @param methodName the name of the method to override + */ + protected MethodOverride(String methodName) { + Assert.notNull(methodName, "Method name must not be null"); + this.methodName = methodName; + } + + /** + * Return the name of the method to be overridden. + */ + public String getMethodName() { + return this.methodName; + } + + /** + * Set whether the overridden method has to be considered as overloaded + * (that is, whether arg type matching has to happen). + *

    Default is "true"; can be switched to "false" to optimize runtime performance. + */ + protected void setOverloaded(boolean overloaded) { + this.overloaded = overloaded; + } + + /** + * Return whether the overridden method has to be considered as overloaded + * (that is, whether arg type matching has to happen). + */ + protected boolean isOverloaded() { + return this.overloaded; + } + + /** + * Set the configuration source Object for this metadata element. + *

    The exact type of the object will depend on the configuration mechanism used. + */ + public void setSource(Object source) { + this.source = source; + } + + public Object getSource() { + return this.source; + } + + + /** + * Subclasses must override this to indicate whether they match + * the given method. This allows for argument list checking + * as well as method name checking. + * @param method the method to check + * @return whether this override matches the given method + */ + public abstract boolean matches(Method method); + + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof MethodOverride)) { + return false; + } + MethodOverride that = (MethodOverride) other; + return (ObjectUtils.nullSafeEquals(this.methodName, that.methodName) && + this.overloaded == that.overloaded && + ObjectUtils.nullSafeEquals(this.source, that.source)); + } + + public int hashCode() { + int hashCode = ObjectUtils.nullSafeHashCode(this.methodName); + hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.source); + hashCode = 29 * hashCode + (this.overloaded ? 1 : 0); + return hashCode; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/MethodOverrides.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/MethodOverrides.java new file mode 100644 index 0000000000..732d2682f8 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/MethodOverrides.java @@ -0,0 +1,117 @@ +/* + * Copyright 2002-2007 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.Method; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +/** + * Set of method overrides, determining which, if any, methods on a + * managed object the Spring IoC container will override at runtime. + * + *

    The currently supported {@link MethodOverride} variants are + * {@link LookupOverride} and {@link ReplaceOverride}. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 1.1 + * @see MethodOverride + */ +public class MethodOverrides { + + private final Set overrides = new HashSet(); + + + /** + * Create new MethodOverrides. + */ + public MethodOverrides() { + } + + /** + * Deep copy constructor. + */ + public MethodOverrides(MethodOverrides other) { + addOverrides(other); + } + + + /** + * Copy all given method overrides into this object. + */ + public void addOverrides(MethodOverrides other) { + if (other != null) { + this.overrides.addAll(other.getOverrides()); + } + } + + /** + * Add the given method override. + */ + public void addOverride(MethodOverride override) { + this.overrides.add(override); + } + + /** + * Return all method overrides contained by this object. + * @return Set of MethodOverride objects + * @see MethodOverride + */ + public Set getOverrides() { + return this.overrides; + } + + /** + * Return whether the set of method overrides is empty. + */ + public boolean isEmpty() { + return this.overrides.isEmpty(); + } + + /** + * Return the override for the given method, if any. + * @param method method to check for overrides for + * @return the method override, or null if none + */ + public MethodOverride getOverride(Method method) { + for (Iterator it = this.overrides.iterator(); it.hasNext();) { + MethodOverride methodOverride = (MethodOverride) it.next(); + if (methodOverride.matches(method)) { + return methodOverride; + } + } + return null; + } + + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + MethodOverrides that = (MethodOverrides) o; + + if (!this.overrides.equals(that.overrides)) return false; + + return true; + } + + public int hashCode() { + return this.overrides.hashCode(); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/MethodReplacer.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/MethodReplacer.java new file mode 100644 index 0000000000..7e04f826d6 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/MethodReplacer.java @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2005 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.Method; + +/** + * Interface to be implemented by classes that can reimplement any method + * on an IoC-managed object: the Method Injection form of + * Dependency Injection. + * + *

    Such methods may be (but need not be) abstract, in which case the + * container will create a concrete subclass to instantiate. + * + * @author Rod Johnson + * @since 1.1 + */ +public interface MethodReplacer { + + /** + * Reimplement the given method. + * @param obj the instance we're reimplementing the method for + * @param method the method to reimplement + * @param args arguments to the method + * @return return value for the method + */ + Object reimplement(Object obj, Method method, Object[] args) throws Throwable; + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/PropertiesBeanDefinitionReader.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/PropertiesBeanDefinitionReader.java new file mode 100644 index 0000000000..8c3fd4a779 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/PropertiesBeanDefinitionReader.java @@ -0,0 +1,550 @@ +/* + * Copyright 2002-2008 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.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Properties; +import java.util.ResourceBundle; + +import org.springframework.beans.BeansException; +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.PropertyAccessor; +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.CannotLoadBeanClassException; +import org.springframework.beans.factory.config.ConstructorArgumentValues; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.EncodedResource; +import org.springframework.util.DefaultPropertiesPersister; +import org.springframework.util.PropertiesPersister; +import org.springframework.util.StringUtils; + +/** + * Bean definition reader for a simple properties format. + * + *

    Provides bean definition registration methods for Map/Properties and + * ResourceBundle. Typically applied to a DefaultListableBeanFactory. + * + *

    Example: + * + *

    + * employee.(class)=MyClass       // bean is of class MyClass
    + * employee.(abstract)=true       // this bean can't be instantiated directly
    + * employee.group=Insurance       // real property
    + * employee.usesDialUp=false      // real property (potentially overridden)
    + *
    + * salesrep.(parent)=employee     // derives from "employee" bean definition
    + * salesrep.(lazy-init)=true      // lazily initialize this singleton bean
    + * salesrep.manager(ref)=tony     // reference to another bean
    + * salesrep.department=Sales      // real property
    + *
    + * techie.(parent)=employee       // derives from "employee" bean definition
    + * techie.(scope)=prototype       // bean is a prototype (not a shared instance)
    + * techie.manager(ref)=jeff       // reference to another bean
    + * techie.department=Engineering  // real property
    + * techie.usesDialUp=true         // real property (overriding parent value)
    + *
    + * ceo.$0(ref)=secretary          // inject 'secretary' bean as 0th constructor arg
    + * ceo.$1=1000000                 // inject value '1000000' at 1st constructor arg
    + * 
    + * + * @author Rod Johnson + * @author Juergen Hoeller + * @author Rob Harrop + * @since 26.11.2003 + * @see DefaultListableBeanFactory + */ +public class PropertiesBeanDefinitionReader extends AbstractBeanDefinitionReader { + + /** + * Value of a T/F attribute that represents true. + * Anything else represents false. Case seNsItive. + */ + public static final String TRUE_VALUE = "true"; + + /** + * Separator between bean name and property name. + * We follow normal Java conventions. + */ + public static final String SEPARATOR = "."; + + /** + * Special key to distinguish owner.(class)=com.myapp.MyClass- + */ + public static final String CLASS_KEY = "(class)"; + + /** + * Special key to distinguish owner.class=com.myapp.MyClass. + * Deprecated in favor of .(class)= + */ + private static final String DEPRECATED_CLASS_KEY = "class"; + + /** + * Special key to distinguish owner.(parent)=parentBeanName. + */ + public static final String PARENT_KEY = "(parent)"; + + /** + * Special key to distinguish owner.(scope)=prototype. + * Default is "true". + */ + public static final String SCOPE_KEY = "(scope)"; + + /** + * Special key to distinguish owner.(singleton)=false. + * Default is "true". + */ + public static final String SINGLETON_KEY = "(singleton)"; + + /** + * Special key to distinguish owner.(abstract)=true + * Default is "false". + */ + public static final String ABSTRACT_KEY = "(abstract)"; + + /** + * Special key to distinguish owner.(lazy-init)=true + * Default is "false". + */ + public static final String LAZY_INIT_KEY = "(lazy-init)"; + + /** + * Property suffix for references to other beans in the current + * BeanFactory: e.g. owner.dog(ref)=fido. + * Whether this is a reference to a singleton or a prototype + * will depend on the definition of the target bean. + */ + public static final String REF_SUFFIX = "(ref)"; + + /** + * Prefix before values referencing other beans. + */ + public static final String REF_PREFIX = "*"; + + /** + * Prefix used to denote a constructor argument definition. + */ + public static final String CONSTRUCTOR_ARG_PREFIX = "$"; + + + private String defaultParentBean; + + private PropertiesPersister propertiesPersister = new DefaultPropertiesPersister(); + + + /** + * Create new PropertiesBeanDefinitionReader for the given bean factory. + * @param registry the BeanFactory to load bean definitions into, + * in the form of a BeanDefinitionRegistry + */ + public PropertiesBeanDefinitionReader(BeanDefinitionRegistry registry) { + super(registry); + } + + + /** + * Set the default parent bean for this bean factory. + * If a child bean definition handled by this factory provides neither + * a parent nor a class attribute, this default value gets used. + *

    Can be used e.g. for view definition files, to define a parent + * with a default view class and common attributes for all views. + * View definitions that define their own parent or carry their own + * class can still override this. + *

    Strictly speaking, the rule that a default parent setting does + * not apply to a bean definition that carries a class is there for + * backwards compatiblity reasons. It still matches the typical use case. + */ + public void setDefaultParentBean(String defaultParentBean) { + this.defaultParentBean = defaultParentBean; + } + + /** + * Return the default parent bean for this bean factory. + */ + public String getDefaultParentBean() { + return this.defaultParentBean; + } + + /** + * Set the PropertiesPersister to use for parsing properties files. + * The default is DefaultPropertiesPersister. + * @see org.springframework.util.DefaultPropertiesPersister + */ + public void setPropertiesPersister(PropertiesPersister propertiesPersister) { + this.propertiesPersister = + (propertiesPersister != null ? propertiesPersister : new DefaultPropertiesPersister()); + } + + /** + * Return the PropertiesPersister to use for parsing properties files. + */ + public PropertiesPersister getPropertiesPersister() { + return this.propertiesPersister; + } + + + /** + * Load bean definitions from the specified properties file, + * using all property keys (i.e. not filtering by prefix). + * @param resource the resource descriptor for the properties file + * @return the number of bean definitions found + * @throws BeanDefinitionStoreException in case of loading or parsing errors + * @see #loadBeanDefinitions(org.springframework.core.io.Resource, String) + */ + public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { + return loadBeanDefinitions(new EncodedResource(resource), null); + } + + /** + * Load bean definitions from the specified properties file. + * @param resource the resource descriptor for the properties file + * @param prefix a filter within the keys in the map: e.g. 'beans.' + * (can be empty or null) + * @return the number of bean definitions found + * @throws BeanDefinitionStoreException in case of loading or parsing errors + */ + public int loadBeanDefinitions(Resource resource, String prefix) throws BeanDefinitionStoreException { + return loadBeanDefinitions(new EncodedResource(resource), prefix); + } + + /** + * Load bean definitions from the specified properties file. + * @param encodedResource the resource descriptor for the properties file, + * allowing to specify an encoding to use for parsing the file + * @return the number of bean definitions found + * @throws BeanDefinitionStoreException in case of loading or parsing errors + */ + public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { + return loadBeanDefinitions(encodedResource, null); + } + + /** + * Load bean definitions from the specified properties file. + * @param encodedResource the resource descriptor for the properties file, + * allowing to specify an encoding to use for parsing the file + * @param prefix a filter within the keys in the map: e.g. 'beans.' + * (can be empty or null) + * @return the number of bean definitions found + * @throws BeanDefinitionStoreException in case of loading or parsing errors + */ + public int loadBeanDefinitions(EncodedResource encodedResource, String prefix) + throws BeanDefinitionStoreException { + + Properties props = new Properties(); + try { + InputStream is = encodedResource.getResource().getInputStream(); + try { + if (encodedResource.getEncoding() != null) { + getPropertiesPersister().load(props, new InputStreamReader(is, encodedResource.getEncoding())); + } + else { + getPropertiesPersister().load(props, is); + } + } + finally { + is.close(); + } + return registerBeanDefinitions(props, prefix, encodedResource.getResource().getDescription()); + } + catch (IOException ex) { + throw new BeanDefinitionStoreException("Could not parse properties from " + encodedResource.getResource(), ex); + } + } + + /** + * Register bean definitions contained in a resource bundle, + * using all property keys (i.e. not filtering by prefix). + * @param rb the ResourceBundle to load from + * @return the number of bean definitions found + * @throws BeanDefinitionStoreException in case of loading or parsing errors + * @see #registerBeanDefinitions(java.util.ResourceBundle, String) + */ + public int registerBeanDefinitions(ResourceBundle rb) throws BeanDefinitionStoreException { + return registerBeanDefinitions(rb, null); + } + + /** + * Register bean definitions contained in a ResourceBundle. + *

    Similar syntax as for a Map. This method is useful to enable + * standard Java internationalization support. + * @param rb the ResourceBundle to load from + * @param prefix a filter within the keys in the map: e.g. 'beans.' + * (can be empty or null) + * @return the number of bean definitions found + * @throws BeanDefinitionStoreException in case of loading or parsing errors + */ + public int registerBeanDefinitions(ResourceBundle rb, String prefix) throws BeanDefinitionStoreException { + // Simply create a map and call overloaded method. + Map map = new HashMap(); + Enumeration keys = rb.getKeys(); + while (keys.hasMoreElements()) { + String key = (String) keys.nextElement(); + map.put(key, rb.getObject(key)); + } + return registerBeanDefinitions(map, prefix); + } + + + /** + * Register bean definitions contained in a Map, + * using all property keys (i.e. not filtering by prefix). + * @param map Map: name -> property (String or Object). Property values + * will be strings if coming from a Properties file etc. Property names + * (keys) must be Strings. Class keys must be Strings. + * @return the number of bean definitions found + * @throws BeansException in case of loading or parsing errors + * @see #registerBeanDefinitions(java.util.Map, String, String) + */ + public int registerBeanDefinitions(Map map) throws BeansException { + return registerBeanDefinitions(map, null); + } + + /** + * Register bean definitions contained in a Map. + * Ignore ineligible properties. + * @param map Map name -> property (String or Object). Property values + * will be strings if coming from a Properties file etc. Property names + * (keys) must be Strings. Class keys must be Strings. + * @param prefix a filter within the keys in the map: e.g. 'beans.' + * (can be empty or null) + * @return the number of bean definitions found + * @throws BeansException in case of loading or parsing errors + */ + public int registerBeanDefinitions(Map map, String prefix) throws BeansException { + return registerBeanDefinitions(map, prefix, "Map " + map); + } + + /** + * Register bean definitions contained in a Map. + * Ignore ineligible properties. + * @param map Map name -> property (String or Object). Property values + * will be strings if coming from a Properties file etc. Property names + * (keys) must be strings. Class keys must be Strings. + * @param prefix a filter within the keys in the map: e.g. 'beans.' + * (can be empty or null) + * @param resourceDescription description of the resource that the + * Map came from (for logging purposes) + * @return the number of bean definitions found + * @throws BeansException in case of loading or parsing errors + * @see #registerBeanDefinitions(Map, String) + */ + public int registerBeanDefinitions(Map map, String prefix, String resourceDescription) + throws BeansException { + + if (prefix == null) { + prefix = ""; + } + int beanCount = 0; + + for (Iterator it = map.keySet().iterator(); it.hasNext();) { + Object key = it.next(); + if (!(key instanceof String)) { + throw new IllegalArgumentException("Illegal key [" + key + "]: only Strings allowed"); + } + String keyString = (String) key; + if (keyString.startsWith(prefix)) { + // Key is of form: prefix.property + String nameAndProperty = keyString.substring(prefix.length()); + // Find dot before property name, ignoring dots in property keys. + int sepIdx = -1; + int propKeyIdx = nameAndProperty.indexOf(PropertyAccessor.PROPERTY_KEY_PREFIX); + if (propKeyIdx != -1) { + sepIdx = nameAndProperty.lastIndexOf(SEPARATOR, propKeyIdx); + } + else { + sepIdx = nameAndProperty.lastIndexOf(SEPARATOR); + } + if (sepIdx != -1) { + String beanName = nameAndProperty.substring(0, sepIdx); + if (logger.isDebugEnabled()) { + logger.debug("Found bean name '" + beanName + "'"); + } + if (!getRegistry().containsBeanDefinition(beanName)) { + // If we haven't already registered it... + registerBeanDefinition(beanName, map, prefix + beanName, resourceDescription); + ++beanCount; + } + } + else { + // Ignore it: It wasn't a valid bean name and property, + // although it did start with the required prefix. + if (logger.isDebugEnabled()) { + logger.debug("Invalid bean name and property [" + nameAndProperty + "]"); + } + } + } + } + + return beanCount; + } + + /** + * Get all property values, given a prefix (which will be stripped) + * and add the bean they define to the factory with the given name + * @param beanName name of the bean to define + * @param map Map containing string pairs + * @param prefix prefix of each entry, which will be stripped + * @param resourceDescription description of the resource that the + * Map came from (for logging purposes) + * @throws BeansException if the bean definition could not be parsed or registered + */ + protected void registerBeanDefinition(String beanName, Map map, String prefix, String resourceDescription) + throws BeansException { + + String className = null; + String parent = null; + String scope = GenericBeanDefinition.SCOPE_SINGLETON; + boolean isAbstract = false; + boolean lazyInit = false; + + ConstructorArgumentValues cas = new ConstructorArgumentValues(); + MutablePropertyValues pvs = new MutablePropertyValues(); + + for (Iterator it = map.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + String key = StringUtils.trimWhitespace((String) entry.getKey()); + if (key.startsWith(prefix + SEPARATOR)) { + String property = key.substring(prefix.length() + SEPARATOR.length()); + if (isClassKey(property)) { + className = StringUtils.trimWhitespace((String) entry.getValue()); + } + else if (PARENT_KEY.equals(property)) { + parent = StringUtils.trimWhitespace((String) entry.getValue()); + } + else if (ABSTRACT_KEY.equals(property)) { + String val = StringUtils.trimWhitespace((String) entry.getValue()); + isAbstract = TRUE_VALUE.equals(val); + } + else if (SCOPE_KEY.equals(property)) { + // Spring 2.0 style + scope = StringUtils.trimWhitespace((String) entry.getValue()); + } + else if (SINGLETON_KEY.equals(property)) { + // Spring 1.2 style + String val = StringUtils.trimWhitespace((String) entry.getValue()); + scope = ((val == null || TRUE_VALUE.equals(val) ? + GenericBeanDefinition.SCOPE_SINGLETON : GenericBeanDefinition.SCOPE_PROTOTYPE)); + } + else if (LAZY_INIT_KEY.equals(property)) { + String val = StringUtils.trimWhitespace((String) entry.getValue()); + lazyInit = TRUE_VALUE.equals(val); + } + else if (property.startsWith(CONSTRUCTOR_ARG_PREFIX)) { + if (property.endsWith(REF_SUFFIX)) { + int index = Integer.parseInt(property.substring(1, property.length() - REF_SUFFIX.length())); + cas.addIndexedArgumentValue(index, new RuntimeBeanReference(entry.getValue().toString())); + } + else { + int index = Integer.parseInt(property.substring(1)); + cas.addIndexedArgumentValue(index, readValue(entry)); + } + } + else if (property.endsWith(REF_SUFFIX)) { + // This isn't a real property, but a reference to another prototype + // Extract property name: property is of form dog(ref) + property = property.substring(0, property.length() - REF_SUFFIX.length()); + String ref = StringUtils.trimWhitespace((String) entry.getValue()); + + // It doesn't matter if the referenced bean hasn't yet been registered: + // this will ensure that the reference is resolved at runtime. + Object val = new RuntimeBeanReference(ref); + pvs.addPropertyValue(property, val); + } + else{ + // It's a normal bean property. + pvs.addPropertyValue(property, readValue(entry)); + } + } + } + + if (logger.isDebugEnabled()) { + logger.debug("Registering bean definition for bean name '" + beanName + "' with " + pvs); + } + + // Just use default parent if we're not dealing with the parent itself, + // and if there's no class name specified. The latter has to happen for + // backwards compatibility reasons. + if (parent == null && className == null && !beanName.equals(this.defaultParentBean)) { + parent = this.defaultParentBean; + } + + try { + AbstractBeanDefinition bd = BeanDefinitionReaderUtils.createBeanDefinition( + parent, className, getBeanClassLoader()); + bd.setScope(scope); + bd.setAbstract(isAbstract); + bd.setLazyInit(lazyInit); + bd.setConstructorArgumentValues(cas); + bd.setPropertyValues(pvs); + getRegistry().registerBeanDefinition(beanName, bd); + } + catch (ClassNotFoundException ex) { + throw new CannotLoadBeanClassException(resourceDescription, beanName, className, ex); + } + catch (LinkageError err) { + throw new CannotLoadBeanClassException(resourceDescription, beanName, className, err); + } + } + + /** + * Indicates whether the supplied property matches the class property of + * the bean definition. + */ + private boolean isClassKey(String property) { + if (CLASS_KEY.equals(property)) { + return true; + } + else if (DEPRECATED_CLASS_KEY.equals(property)) { + if (logger.isWarnEnabled()) { + logger.warn("Use of 'class' property in [" + getClass().getName() + "] is deprecated in favor of '(class)'"); + } + return true; + } + return false; + } + + /** + * Reads the value of the entry. Correctly interprets bean references for + * values that are prefixed with an asterisk. + */ + private Object readValue(Map.Entry entry) { + Object val = entry.getValue(); + if (val instanceof String) { + String strVal = (String) val; + // If it starts with a reference prefix... + if (strVal.startsWith(REF_PREFIX)) { + // Expand the reference. + String targetName = strVal.substring(1); + if (targetName.startsWith(REF_PREFIX)) { + // Escaped prefix -> use plain value. + val = targetName; + } + else { + val = new RuntimeBeanReference(targetName); + } + } + } + return val; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/ReplaceOverride.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/ReplaceOverride.java new file mode 100644 index 0000000000..0b6d93e793 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/ReplaceOverride.java @@ -0,0 +1,123 @@ +/* + * Copyright 2002-2007 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.Method; +import java.util.LinkedList; +import java.util.List; + +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * Extension of MethodOverride that represents an arbitrary + * override of a method by the IoC container. + * + *

    Any non-final method can be overridden, irrespective of its + * parameters and return types. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 1.1 + */ +public class ReplaceOverride extends MethodOverride { + + private final String methodReplacerBeanName; + + /** + * List of String. Identifying signatures. + */ + private List typeIdentifiers = new LinkedList(); + + + /** + * Construct a new ReplaceOverride. + * @param methodName the name of the method to override + * @param methodReplacerBeanName the bean name of the MethodReplacer + */ + public ReplaceOverride(String methodName, String methodReplacerBeanName) { + super(methodName); + Assert.notNull(methodName, "Method replacer bean name must not be null"); + this.methodReplacerBeanName = methodReplacerBeanName; + } + + /** + * Return the name of the bean implementing MethodReplacer. + */ + public String getMethodReplacerBeanName() { + return this.methodReplacerBeanName; + } + + /** + * Add a fragment of a class string, like "Exception" + * or "java.lang.Exc", to identify a parameter type. + * @param identifier a substring of the fully qualified class name + */ + public void addTypeIdentifier(String identifier) { + this.typeIdentifiers.add(identifier); + } + + + public boolean matches(Method method) { + // TODO could cache result for efficiency + if (!method.getName().equals(getMethodName())) { + // It can't match. + return false; + } + + if (!isOverloaded()) { + // No overloaded: don't worry about arg type matching. + return true; + } + + // If we get to here, we need to insist on precise argument matching. + if (this.typeIdentifiers.size() != method.getParameterTypes().length) { + return false; + } + for (int i = 0; i < this.typeIdentifiers.size(); i++) { + String identifier = (String) this.typeIdentifiers.get(i); + if (method.getParameterTypes()[i].getName().indexOf(identifier) == -1) { + // This parameter cannot match. + return false; + } + } + return true; + } + + + public String toString() { + return "Replace override for method '" + getMethodName() + "; will call bean '" + + this.methodReplacerBeanName + "'"; + } + + public boolean equals(Object other) { + if (!(other instanceof ReplaceOverride) || !super.equals(other)) { + return false; + } + ReplaceOverride that = (ReplaceOverride) other; + return (ObjectUtils.nullSafeEquals(this.methodReplacerBeanName, that.methodReplacerBeanName) && + ObjectUtils.nullSafeEquals(this.typeIdentifiers, that.typeIdentifiers)); + } + + public int hashCode() { + int hashCode = super.hashCode(); + hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.methodReplacerBeanName); + hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.typeIdentifiers); + return hashCode; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java new file mode 100644 index 0000000000..eedba0089c --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java @@ -0,0 +1,257 @@ +/* + * Copyright 2002-2008 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.Member; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConstructorArgumentValues; + +/** + * A root bean definition represents the merged bean definition that backs + * a specific bean in a Spring BeanFactory at runtime. It might have been created + * from multiple original bean definitions that inherit from each other, + * typically registered as {@link GenericBeanDefinition GenericBeanDefinitions}. + * A root bean definition is essentially the 'unified' bean definition view at runtime. + * + *

    Root bean definitions may also be used for registering individual bean definitions + * in the configuration phase. However, since Spring 2.5, the preferred way to register + * bean definitions programmatically is the {@link GenericBeanDefinition} class. + * GenericBeanDefinition has the advantage that it allows to dynamically define + * parent dependencies, not 'hard-coding' the role as a root bean definition. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see GenericBeanDefinition + * @see ChildBeanDefinition + */ +public class RootBeanDefinition extends AbstractBeanDefinition { + + private final Set externallyManagedConfigMembers = Collections.synchronizedSet(new HashSet()); + + private final Set externallyManagedInitMethods = Collections.synchronizedSet(new HashSet()); + + private final Set externallyManagedDestroyMethods = Collections.synchronizedSet(new HashSet()); + + /** Package-visible field for caching the resolved constructor or factory method */ + volatile Object resolvedConstructorOrFactoryMethod; + + /** Package-visible field for caching fully resolved constructor arguments */ + volatile Object[] resolvedConstructorArguments; + + /** Package-visible field for caching partly prepared constructor arguments */ + volatile Object[] preparedConstructorArguments; + + /** Package-visible field that marks the constructor arguments as resolved */ + volatile boolean constructorArgumentsResolved = false; + + /** Package-visible field that indicates a before-instantiation post-processor having kicked in */ + volatile Boolean beforeInstantiationResolved; + + /** Package-visible field that indicates MergedBeanDefinitionPostProcessor having been applied */ + boolean postProcessed = false; + + final Object postProcessingLock = new Object(); + + + /** + * Create a new RootBeanDefinition, to be configured through its bean + * properties and configuration methods. + * @see #setBeanClass + * @see #setBeanClassName + * @see #setScope + * @see #setAutowireMode + * @see #setDependencyCheck + * @see #setConstructorArgumentValues + * @see #setPropertyValues + */ + public RootBeanDefinition() { + super(); + } + + /** + * Create a new RootBeanDefinition for a singleton. + * @param beanClass the class of the bean to instantiate + */ + public RootBeanDefinition(Class beanClass) { + super(); + setBeanClass(beanClass); + } + + /** + * Create a new RootBeanDefinition with the given singleton status. + * @param beanClass the class of the bean to instantiate + * @param singleton the singleton status of the bean + * @deprecated since Spring 2.5, in favor of {@link #setScope} + */ + public RootBeanDefinition(Class beanClass, boolean singleton) { + super(); + setBeanClass(beanClass); + setSingleton(singleton); + } + + /** + * Create a new RootBeanDefinition for a singleton, + * using the given autowire mode. + * @param beanClass the class of the bean to instantiate + * @param autowireMode by name or type, using the constants in this interface + */ + public RootBeanDefinition(Class beanClass, int autowireMode) { + super(); + setBeanClass(beanClass); + setAutowireMode(autowireMode); + } + + /** + * Create a new RootBeanDefinition for a singleton, + * using the given autowire mode. + * @param beanClass the class of the bean to instantiate + * @param autowireMode by name or type, using the constants in this interface + * @param dependencyCheck whether to perform a dependency check for objects + * (not applicable to autowiring a constructor, thus ignored there) + */ + public RootBeanDefinition(Class beanClass, int autowireMode, boolean dependencyCheck) { + super(); + setBeanClass(beanClass); + setAutowireMode(autowireMode); + if (dependencyCheck && getResolvedAutowireMode() != AUTOWIRE_CONSTRUCTOR) { + setDependencyCheck(RootBeanDefinition.DEPENDENCY_CHECK_OBJECTS); + } + } + + /** + * Create a new RootBeanDefinition for a singleton, + * providing property values. + * @param beanClass the class of the bean to instantiate + * @param pvs the property values to apply + */ + public RootBeanDefinition(Class beanClass, MutablePropertyValues pvs) { + super(null, pvs); + setBeanClass(beanClass); + } + + /** + * Create a new RootBeanDefinition with the given singleton status, + * providing property values. + * @param beanClass the class of the bean to instantiate + * @param pvs the property values to apply + * @param singleton the singleton status of the bean + * @deprecated since Spring 2.5, in favor of {@link #setScope} + */ + public RootBeanDefinition(Class beanClass, MutablePropertyValues pvs, boolean singleton) { + super(null, pvs); + setBeanClass(beanClass); + setSingleton(singleton); + } + + /** + * Create a new RootBeanDefinition for a singleton, + * providing constructor arguments and property values. + * @param beanClass the class of the bean to instantiate + * @param cargs the constructor argument values to apply + * @param pvs the property values to apply + */ + public RootBeanDefinition(Class beanClass, ConstructorArgumentValues cargs, MutablePropertyValues pvs) { + super(cargs, pvs); + setBeanClass(beanClass); + } + + /** + * Create a new RootBeanDefinition for a singleton, + * providing constructor arguments and property values. + *

    Takes a bean class name to avoid eager loading of the bean class. + * @param beanClassName the name of the class to instantiate + * @param cargs the constructor argument values to apply + * @param pvs the property values to apply + */ + public RootBeanDefinition(String beanClassName, ConstructorArgumentValues cargs, MutablePropertyValues pvs) { + super(cargs, pvs); + setBeanClassName(beanClassName); + } + + /** + * Create a new RootBeanDefinition as deep copy of the given + * bean definition. + * @param original the original bean definition to copy from + */ + public RootBeanDefinition(RootBeanDefinition original) { + super((BeanDefinition) original); + } + + /** + * Create a new RootBeanDefinition as deep copy of the given + * bean definition. + * @param original the original bean definition to copy from + */ + RootBeanDefinition(BeanDefinition original) { + super(original); + } + + + public String getParentName() { + return null; + } + + public void setParentName(String parentName) { + if (parentName != null) { + throw new IllegalArgumentException("Root bean cannot be changed into a child bean with parent reference"); + } + } + + + public void registerExternallyManagedConfigMember(Member configMember) { + this.externallyManagedConfigMembers.add(configMember); + } + + public boolean isExternallyManagedConfigMember(Member configMember) { + return this.externallyManagedConfigMembers.contains(configMember); + } + + public void registerExternallyManagedInitMethod(String initMethod) { + this.externallyManagedInitMethods.add(initMethod); + } + + public boolean isExternallyManagedInitMethod(String initMethod) { + return this.externallyManagedInitMethods.contains(initMethod); + } + + public void registerExternallyManagedDestroyMethod(String destroyMethod) { + this.externallyManagedDestroyMethods.add(destroyMethod); + } + + public boolean isExternallyManagedDestroyMethod(String destroyMethod) { + return this.externallyManagedDestroyMethods.contains(destroyMethod); + } + + + public AbstractBeanDefinition cloneBeanDefinition() { + return new RootBeanDefinition(this); + } + + public boolean equals(Object other) { + return (this == other || (other instanceof RootBeanDefinition && super.equals(other))); + } + + public String toString() { + return "Root bean: " + super.toString(); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/SimpleAutowireCandidateResolver.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/SimpleAutowireCandidateResolver.java new file mode 100644 index 0000000000..92830ddf46 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/SimpleAutowireCandidateResolver.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2007 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 org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.config.DependencyDescriptor; + +/** + * {@link AutowireCandidateResolver} implementation to use when Java version + * is less than 1.5 and therefore no annotation support is available. This + * implementation checks the bean definition only. + * + * @author Mark Fisher + * @since 2.5 + * @see BeanDefinition#isAutowireCandidate() + */ +public class SimpleAutowireCandidateResolver implements AutowireCandidateResolver { + + /** + * Determine if the provided bean definition is an autowire candidate. + *

    To be considered a candidate the bean's autowire-candidate + * attribute must not have been set to 'false'. + */ + public boolean isAutowireCandidate( + BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) { + + return bdHolder.getBeanDefinition().isAutowireCandidate(); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/SimpleBeanDefinitionRegistry.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/SimpleBeanDefinitionRegistry.java new file mode 100644 index 0000000000..2dd36a8a67 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/SimpleBeanDefinitionRegistry.java @@ -0,0 +1,81 @@ +/* + * Copyright 2002-2008 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.util.Map; + +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.core.CollectionFactory; +import org.springframework.core.SimpleAliasRegistry; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Simple implementation of the {@link BeanDefinitionRegistry} interface. + * Provides registry capabilities only, with no factory capabilities built in. + * Can for example be used for testing bean definition readers. + * + * @author Juergen Hoeller + * @since 2.5.2 + */ +public class SimpleBeanDefinitionRegistry extends SimpleAliasRegistry implements BeanDefinitionRegistry { + + /** Map of bean definition objects, keyed by bean name */ + private final Map beanDefinitionMap = CollectionFactory.createConcurrentMapIfPossible(16); + + + public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) + throws BeanDefinitionStoreException { + + Assert.hasText(beanName, "'beanName' must not be empty"); + Assert.notNull(beanDefinition, "BeanDefinition must not be null"); + this.beanDefinitionMap.put(beanName, beanDefinition); + } + + public void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException { + if (this.beanDefinitionMap.remove(beanName) == null) { + throw new NoSuchBeanDefinitionException(beanName); + } + } + + public BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException { + BeanDefinition bd = (BeanDefinition) this.beanDefinitionMap.get(beanName); + if (bd == null) { + throw new NoSuchBeanDefinitionException(beanName); + } + return bd; + } + + public boolean containsBeanDefinition(String beanName) { + return this.beanDefinitionMap.containsKey(beanName); + } + + public String[] getBeanDefinitionNames() { + return StringUtils.toStringArray(this.beanDefinitionMap.keySet()); + } + + public int getBeanDefinitionCount() { + return this.beanDefinitionMap.size(); + } + + public boolean isBeanNameInUse(String beanName) { + return isAlias(beanName) || containsBeanDefinition(beanName); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/SimpleInstantiationStrategy.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/SimpleInstantiationStrategy.java new file mode 100644 index 0000000000..7fdeeb029e --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/SimpleInstantiationStrategy.java @@ -0,0 +1,132 @@ +/* + * Copyright 2002-2007 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.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.springframework.beans.BeanInstantiationException; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; + +/** + * Simple object instantiation strategy for use in a BeanFactory. + * + *

    Does not support Method Injection, although it provides hooks for subclasses + * to override to add Method Injection support, for example by overriding methods. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 1.1 + */ +public class SimpleInstantiationStrategy implements InstantiationStrategy { + + public Object instantiate( + RootBeanDefinition beanDefinition, String beanName, BeanFactory owner) { + + // Don't override the class with CGLIB if no overrides. + if (beanDefinition.getMethodOverrides().isEmpty()) { + Constructor constructorToUse = (Constructor) beanDefinition.resolvedConstructorOrFactoryMethod; + if (constructorToUse == null) { + Class clazz = beanDefinition.getBeanClass(); + if (clazz.isInterface()) { + throw new BeanInstantiationException(clazz, "Specified class is an interface"); + } + try { + constructorToUse = clazz.getDeclaredConstructor((Class[]) null); + beanDefinition.resolvedConstructorOrFactoryMethod = constructorToUse; + } + catch (Exception ex) { + throw new BeanInstantiationException(clazz, "No default constructor found", ex); + } + } + return BeanUtils.instantiateClass(constructorToUse, null); + } + else { + // Must generate CGLIB subclass. + return instantiateWithMethodInjection(beanDefinition, beanName, owner); + } + } + + /** + * Subclasses can override this method, which is implemented to throw + * UnsupportedOperationException, if they can instantiate an object with + * the Method Injection specified in the given RootBeanDefinition. + * Instantiation should use a no-arg constructor. + */ + protected Object instantiateWithMethodInjection( + RootBeanDefinition beanDefinition, String beanName, BeanFactory owner) { + + throw new UnsupportedOperationException( + "Method Injection not supported in SimpleInstantiationStrategy"); + } + + public Object instantiate( + RootBeanDefinition beanDefinition, String beanName, BeanFactory owner, + Constructor ctor, Object[] args) { + + if (beanDefinition.getMethodOverrides().isEmpty()) { + return BeanUtils.instantiateClass(ctor, args); + } + else { + return instantiateWithMethodInjection(beanDefinition, beanName, owner, ctor, args); + } + } + + /** + * Subclasses can override this method, which is implemented to throw + * UnsupportedOperationException, if they can instantiate an object with + * the Method Injection specified in the given RootBeanDefinition. + * Instantiation should use the given constructor and parameters. + */ + protected Object instantiateWithMethodInjection( + RootBeanDefinition beanDefinition, String beanName, BeanFactory owner, + Constructor ctor, Object[] args) { + + throw new UnsupportedOperationException( + "Method Injection not supported in SimpleInstantiationStrategy"); + } + + public Object instantiate( + RootBeanDefinition beanDefinition, String beanName, BeanFactory owner, + Object factoryBean, Method factoryMethod, Object[] args) { + + try { + // It's a static method if the target is null. + ReflectionUtils.makeAccessible(factoryMethod); + return factoryMethod.invoke(factoryBean, args); + } + catch (IllegalArgumentException ex) { + throw new BeanDefinitionStoreException( + "Illegal arguments to factory method [" + factoryMethod + "]; " + + "args: " + StringUtils.arrayToCommaDelimitedString(args)); + } + catch (IllegalAccessException ex) { + throw new BeanDefinitionStoreException( + "Cannot access factory method [" + factoryMethod + "]; is it public?"); + } + catch (InvocationTargetException ex) { + throw new BeanDefinitionStoreException( + "Factory method [" + factoryMethod + "] threw exception", ex.getTargetException()); + } + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java new file mode 100644 index 0000000000..b7062a3863 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java @@ -0,0 +1,249 @@ +/* + * Copyright 2002-2008 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.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.BeanIsNotAFactoryException; +import org.springframework.beans.factory.BeanNotOfRequiredTypeException; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.SmartFactoryBean; +import org.springframework.util.StringUtils; + +/** + * Static {@link org.springframework.beans.factory.BeanFactory} implementation + * which allows to register existing singleton instances programmatically. + * Does not have support for prototype beans or aliases. + * + *

    Serves as example for a simple implementation of the + * {@link org.springframework.beans.factory.ListableBeanFactory} interface, + * managing existing bean instances rather than creating new ones based on bean + * definitions, and not implementing any extended SPI interfaces (such as + * {@link org.springframework.beans.factory.config.ConfigurableBeanFactory}). + * + *

    For a full-fledged factory based on bean definitions, have a look + * at {@link DefaultListableBeanFactory}. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 06.01.2003 + * @see DefaultListableBeanFactory + */ +public class StaticListableBeanFactory implements ListableBeanFactory { + + /** Map from bean name to bean instance */ + private final Map beans = new HashMap(); + + + /** + * Add a new singleton bean. + * Will overwrite any existing instance for the given name. + * @param name the name of the bean + * @param bean the bean instance + */ + public void addBean(String name, Object bean) { + this.beans.put(name, bean); + } + + + //--------------------------------------------------------------------- + // Implementation of BeanFactory interface + //--------------------------------------------------------------------- + + public Object getBean(String name) throws BeansException { + String beanName = BeanFactoryUtils.transformedBeanName(name); + Object bean = this.beans.get(beanName); + + if (bean == null) { + throw new NoSuchBeanDefinitionException(beanName, + "Defined beans are [" + StringUtils.collectionToCommaDelimitedString(this.beans.keySet()) + "]"); + } + + // Don't let calling code try to dereference the + // bean factory if the bean isn't a factory + if (BeanFactoryUtils.isFactoryDereference(name) && !(bean instanceof FactoryBean)) { + throw new BeanIsNotAFactoryException(beanName, bean.getClass()); + } + + if (bean instanceof FactoryBean && !BeanFactoryUtils.isFactoryDereference(name)) { + try { + return ((FactoryBean) bean).getObject(); + } + catch (Exception ex) { + throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", ex); + } + } + return bean; + } + + public Object getBean(String name, Class requiredType) throws BeansException { + Object bean = getBean(name); + if (requiredType != null && !requiredType.isAssignableFrom(bean.getClass())) { + throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); + } + return bean; + } + + public Object getBean(String name, Object[] args) throws BeansException { + if (args != null) { + throw new UnsupportedOperationException( + "StaticListableBeanFactory does not support explicit bean creation arguments)"); + } + return getBean(name); + } + + public boolean containsBean(String name) { + return this.beans.containsKey(name); + } + + public boolean isSingleton(String name) throws NoSuchBeanDefinitionException { + Object bean = getBean(name); + // In case of FactoryBean, return singleton status of created object. + return (bean instanceof FactoryBean && ((FactoryBean) bean).isSingleton()); + } + + public boolean isPrototype(String name) throws NoSuchBeanDefinitionException { + Object bean = getBean(name); + // In case of FactoryBean, return prototype status of created object. + return ((bean instanceof SmartFactoryBean && ((SmartFactoryBean) bean).isPrototype()) || + (bean instanceof FactoryBean && !((FactoryBean) bean).isSingleton())); + } + + public boolean isTypeMatch(String name, Class targetType) throws NoSuchBeanDefinitionException { + Class type = getType(name); + return (targetType == null || (type != null && targetType.isAssignableFrom(type))); + } + + public Class getType(String name) throws NoSuchBeanDefinitionException { + String beanName = BeanFactoryUtils.transformedBeanName(name); + + Object bean = this.beans.get(beanName); + if (bean == null) { + throw new NoSuchBeanDefinitionException(beanName, + "Defined beans are [" + StringUtils.collectionToCommaDelimitedString(this.beans.keySet()) + "]"); + } + + if (bean instanceof FactoryBean && !BeanFactoryUtils.isFactoryDereference(name)) { + // If it's a FactoryBean, we want to look at what it creates, not the factory class. + return ((FactoryBean) bean).getObjectType(); + } + return bean.getClass(); + } + + public String[] getAliases(String name) { + return new String[0]; + } + + + //--------------------------------------------------------------------- + // Implementation of ListableBeanFactory interface + //--------------------------------------------------------------------- + + public boolean containsBeanDefinition(String name) { + return this.beans.containsKey(name); + } + + public int getBeanDefinitionCount() { + return this.beans.size(); + } + + public String[] getBeanDefinitionNames() { + return StringUtils.toStringArray(this.beans.keySet()); + } + + public String[] getBeanNamesForType(Class type) { + return getBeanNamesForType(type, true, true); + } + + public String[] getBeanNamesForType(Class type, boolean includeNonSingletons, boolean includeFactoryBeans) { + boolean isFactoryType = (type != null && FactoryBean.class.isAssignableFrom(type)); + List matches = new ArrayList(); + Set keys = this.beans.keySet(); + Iterator it = keys.iterator(); + while (it.hasNext()) { + String name = (String) it.next(); + Object beanInstance = this.beans.get(name); + if (beanInstance instanceof FactoryBean && !isFactoryType) { + if (includeFactoryBeans) { + Class objectType = ((FactoryBean) beanInstance).getObjectType(); + if (objectType != null && type.isAssignableFrom(objectType)) { + matches.add(name); + } + } + } + else { + if (type.isInstance(beanInstance)) { + matches.add(name); + } + } + } + return StringUtils.toStringArray(matches); + } + + public Map getBeansOfType(Class type) throws BeansException { + return getBeansOfType(type, true, true); + } + + public Map getBeansOfType(Class type, boolean includeNonSingletons, boolean includeFactoryBeans) + throws BeansException { + + boolean isFactoryType = (type != null && FactoryBean.class.isAssignableFrom(type)); + Map matches = new HashMap(); + + Iterator it = this.beans.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = (Map.Entry) it.next(); + String beanName = (String) entry.getKey(); + Object beanInstance = entry.getValue(); + + // Is bean a FactoryBean? + if (beanInstance instanceof FactoryBean && !isFactoryType) { + if (includeFactoryBeans) { + // Match object created by FactoryBean. + FactoryBean factory = (FactoryBean) beanInstance; + Class objectType = factory.getObjectType(); + if ((includeNonSingletons || factory.isSingleton()) && + objectType != null && type.isAssignableFrom(objectType)) { + matches.put(beanName, getBean(beanName)); + } + } + } + else { + if (type.isInstance(beanInstance)) { + // If type to match is FactoryBean, return FactoryBean itself. + // Else, return bean instance. + if (isFactoryType) { + beanName = FACTORY_BEAN_PREFIX + beanName; + } + matches.put(beanName, beanInstance); + } + } + } + return matches; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/package.html b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/package.html new file mode 100644 index 0000000000..c6f83c5a1d --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/package.html @@ -0,0 +1,9 @@ + + + +Classes supporting the org.springframework.beans.factory package. +Contains abstract base classes for BeanFactory implementations. + + + + diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/wiring/BeanConfigurerSupport.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/wiring/BeanConfigurerSupport.java new file mode 100644 index 0000000000..98737c9728 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/wiring/BeanConfigurerSupport.java @@ -0,0 +1,180 @@ +/* + * Copyright 2002-2008 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.wiring; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.BeanCurrentlyInCreationException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * Convenient base class for configurers that can perform Dependency Injection + * on objects (however they may be created). Typically subclassed by AspectJ aspects. + * + *

    Subclasses may also need a custom metadata resolution strategy, in the + * {@link BeanWiringInfoResolver} interface. The default implementation looks + * for a bean with the same name as the fully-qualified class name. (This is + * the default name of the bean in a Spring XML file if the 'id' + * attribute is not used.) + + * @author Rob Harrop + * @author Rod Johnson + * @author Juergen Hoeller + * @author Adrian Colyer + * @since 2.0 + * @see #setBeanWiringInfoResolver + * @see ClassNameBeanWiringInfoResolver + */ +public class BeanConfigurerSupport implements BeanFactoryAware, InitializingBean, DisposableBean { + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + private volatile BeanWiringInfoResolver beanWiringInfoResolver; + + private volatile ConfigurableListableBeanFactory beanFactory; + + + /** + * Set the {@link BeanWiringInfoResolver} to use. + *

    The default behavior is to look for a bean with the same name as the class. + * As an alternative, consider using annotation-driven bean wiring. + * @see ClassNameBeanWiringInfoResolver + * @see org.springframework.beans.factory.annotation.AnnotationBeanWiringInfoResolver + */ + public void setBeanWiringInfoResolver(BeanWiringInfoResolver beanWiringInfoResolver) { + Assert.notNull(beanWiringInfoResolver, "BeanWiringInfoResolver must not be null"); + this.beanWiringInfoResolver = beanWiringInfoResolver; + } + + /** + * Set the {@link BeanFactory} in which this aspect must configure beans. + */ + public void setBeanFactory(BeanFactory beanFactory) { + if (!(beanFactory instanceof ConfigurableListableBeanFactory)) { + throw new IllegalArgumentException( + "Bean configurer aspect needs to run in a ConfigurableListableBeanFactory: " + beanFactory); + } + this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; + if (this.beanWiringInfoResolver == null) { + this.beanWiringInfoResolver = createDefaultBeanWiringInfoResolver(); + } + } + + /** + * Create the default BeanWiringInfoResolver to be used if none was + * specified explicitly. + *

    The default implementation builds a {@link ClassNameBeanWiringInfoResolver}. + * @return the default BeanWiringInfoResolver (never null) + */ + protected BeanWiringInfoResolver createDefaultBeanWiringInfoResolver() { + return new ClassNameBeanWiringInfoResolver(); + } + + /** + * Check that a {@link BeanFactory} has been set. + */ + public void afterPropertiesSet() { + Assert.notNull(this.beanFactory, "BeanFactory must be set"); + } + + /** + * Release references to the {@link BeanFactory} and + * {@link BeanWiringInfoResolver} when the container is destroyed. + */ + public void destroy() { + this.beanFactory = null; + this.beanWiringInfoResolver = null; + } + + + /** + * Configure the bean instance. + *

    Subclasses can override this to provide custom configuration logic. + * Typically called by an aspect, for all bean instances matched by a + * pointcut. + * @param beanInstance the bean instance to configure (must not be null) + */ + public void configureBean(Object beanInstance) { + if (this.beanFactory == null) { + if (logger.isDebugEnabled()) { + logger.debug("BeanFactory has not been set on " + ClassUtils.getShortName(getClass()) + ": " + + "Make sure this configurer runs in a Spring container. Unable to configure bean of type [" + + ClassUtils.getDescriptiveType(beanInstance) + "]. Proceeding without injection."); + } + return; + } + + BeanWiringInfo bwi = this.beanWiringInfoResolver.resolveWiringInfo(beanInstance); + if (bwi == null) { + // Skip the bean if no wiring info given. + return; + } + + try { + if (bwi.indicatesAutowiring() || + (bwi.isDefaultBeanName() && !this.beanFactory.containsBean(bwi.getBeanName()))) { + // Perform autowiring (also applying standard factory / post-processor callbacks). + this.beanFactory.autowireBeanProperties(beanInstance, bwi.getAutowireMode(), bwi.getDependencyCheck()); + Object result = this.beanFactory.initializeBean(beanInstance, bwi.getBeanName()); + checkExposedObject(result, beanInstance); + } + else { + // Perform explicit wiring based on the specified bean definition. + Object result = this.beanFactory.configureBean(beanInstance, bwi.getBeanName()); + checkExposedObject(result, beanInstance); + } + } + catch (BeanCreationException ex) { + Throwable rootCause = ex.getMostSpecificCause(); + if (rootCause instanceof BeanCurrentlyInCreationException) { + BeanCreationException bce = (BeanCreationException) rootCause; + if (this.beanFactory.isCurrentlyInCreation(bce.getBeanName())) { + String msg = ClassUtils.getShortName(getClass()) + " failed to create target bean '" + + bce.getBeanName() + "' while configuring object of type [" + + beanInstance.getClass().getName() + "] (probably due to a circular reference). " + + "Proceeding without injection."; + if (logger.isDebugEnabled()) { + logger.debug(msg, ex); + } + else if (logger.isInfoEnabled()) { + logger.info(msg); + } + return; + } + } + throw ex; + } + } + + private void checkExposedObject(Object exposedObject, Object originalBeanInstance) { + if (exposedObject != originalBeanInstance) { + throw new IllegalStateException("Post-processor tried to replace bean instance of type [" + + originalBeanInstance.getClass().getName() + "] with (proxy) object of type [" + + exposedObject.getClass().getName() + "] - not supported for aspect-configured classes!"); + } + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/wiring/BeanWiringInfo.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/wiring/BeanWiringInfo.java new file mode 100644 index 0000000000..5ed7b79f11 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/wiring/BeanWiringInfo.java @@ -0,0 +1,149 @@ +/* + * Copyright 2002-2007 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.wiring; + +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.util.Assert; + +/** + * Holder for bean wiring metadata information about a particular class. Used in + * conjunction with the {@link org.springframework.beans.factory.annotation.Configurable} + * annotation and the AspectJ AnnotationBeanConfigurerAspect. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + * @see BeanWiringInfoResolver + * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory + * @see org.springframework.beans.factory.annotation.Configurable + */ +public class BeanWiringInfo { + + /** + * Constant that indicates autowiring bean properties by name. + * @see #BeanWiringInfo(int, boolean) + * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#AUTOWIRE_BY_NAME + */ + public static final int AUTOWIRE_BY_NAME = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME; + + /** + * Constant that indicates autowiring bean properties by type. + * @see #BeanWiringInfo(int, boolean) + * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#AUTOWIRE_BY_TYPE + */ + public static final int AUTOWIRE_BY_TYPE = AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE; + + + private String beanName = null; + + private boolean isDefaultBeanName = false; + + private int autowireMode = AutowireCapableBeanFactory.AUTOWIRE_NO; + + private boolean dependencyCheck = false; + + + /** + * Create a default BeanWiringInfo that suggests plain initialization of + * factory and post-processor callbacks that the bean class may expect. + */ + public BeanWiringInfo() { + } + + /** + * Create a new BeanWiringInfo that points to the given bean name. + * @param beanName the name of the bean definition to take the property values from + * @throws IllegalArgumentException if the supplied beanName is null, + * is empty, or consists wholly of whitespace + */ + public BeanWiringInfo(String beanName) { + this(beanName, false); + } + + /** + * Create a new BeanWiringInfo that points to the given bean name. + * @param beanName the name of the bean definition to take the property values from + * @param isDefaultBeanName whether the given bean name is a suggested + * default bean name, not necessarily matching an actual bean definition + * @throws IllegalArgumentException if the supplied beanName is null, + * is empty, or consists wholly of whitespace + */ + public BeanWiringInfo(String beanName, boolean isDefaultBeanName) { + Assert.hasText(beanName, "'beanName' must not be empty"); + this.beanName = beanName; + this.isDefaultBeanName = isDefaultBeanName; + } + + /** + * Create a new BeanWiringInfo that indicates autowiring. + * @param autowireMode one of the constants {@link #AUTOWIRE_BY_NAME} / + * {@link #AUTOWIRE_BY_TYPE} + * @param dependencyCheck whether to perform a dependency check for object + * references in the bean instance (after autowiring) + * @throws IllegalArgumentException if the supplied autowireMode + * is not one of the allowed values + * @see #AUTOWIRE_BY_NAME + * @see #AUTOWIRE_BY_TYPE + */ + public BeanWiringInfo(int autowireMode, boolean dependencyCheck) { + if (autowireMode != AUTOWIRE_BY_NAME && autowireMode != AUTOWIRE_BY_TYPE) { + throw new IllegalArgumentException("Only constants AUTOWIRE_BY_NAME and AUTOWIRE_BY_TYPE supported"); + } + this.autowireMode = autowireMode; + this.dependencyCheck = dependencyCheck; + } + + + /** + * Return whether this BeanWiringInfo indicates autowiring. + */ + public boolean indicatesAutowiring() { + return (this.beanName == null); + } + + /** + * Return the specific bean name that this BeanWiringInfo points to, if any. + */ + public String getBeanName() { + return this.beanName; + } + + /** + * Return whether the specific bean name is a suggested default bean name, + * not necessarily matching an actual bean definition in the factory. + */ + public boolean isDefaultBeanName() { + return this.isDefaultBeanName; + } + + /** + * Return one of the constants {@link #AUTOWIRE_BY_NAME} / + * {@link #AUTOWIRE_BY_TYPE}, if autowiring is indicated. + */ + public int getAutowireMode() { + return this.autowireMode; + } + + /** + * Return whether to perform a dependency check for object references + * in the bean instance (after autowiring). + */ + public boolean getDependencyCheck() { + return this.dependencyCheck; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/wiring/BeanWiringInfoResolver.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/wiring/BeanWiringInfoResolver.java new file mode 100644 index 0000000000..a8b0873da4 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/wiring/BeanWiringInfoResolver.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2007 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.wiring; + +/** + * Strategy interface to be implemented by objects than can resolve bean name + * information, given a newly instantiated bean object. Invocations to the + * {@link #resolveWiringInfo} method on this interface will be driven by + * the AspectJ pointcut in the relevant concrete aspect. + * + *

    Metadata resolution strategy can be pluggable. A good default is + * {@link ClassNameBeanWiringInfoResolver}, which uses the fully-qualified + * class name as bean name. + * + * @author Rod Johnson + * @since 2.0 + * @see BeanWiringInfo + * @see ClassNameBeanWiringInfoResolver + * @see org.springframework.beans.factory.annotation.AnnotationBeanWiringInfoResolver + */ +public interface BeanWiringInfoResolver { + + /** + * Resolve the BeanWiringInfo for the given bean instance. + * @param beanInstance the bean instance to resolve info for + * @return the BeanWiringInfo, or null if not found + */ + BeanWiringInfo resolveWiringInfo(Object beanInstance); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/wiring/ClassNameBeanWiringInfoResolver.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/wiring/ClassNameBeanWiringInfoResolver.java new file mode 100644 index 0000000000..4ba5a99d43 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/wiring/ClassNameBeanWiringInfoResolver.java @@ -0,0 +1,39 @@ +/* + * Copyright 2002-2007 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.wiring; + +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * Simple default implementation of the {@link BeanWiringInfoResolver} interface, + * looking for a bean with the same name as the fully-qualified class name. + * This matches the default name of the bean in a Spring XML file if the + * bean tag's "id" attribute is not used. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + */ +public class ClassNameBeanWiringInfoResolver implements BeanWiringInfoResolver { + + public BeanWiringInfo resolveWiringInfo(Object beanInstance) { + Assert.notNull(beanInstance, "Bean instance must not be null"); + return new BeanWiringInfo(ClassUtils.getUserClass(beanInstance).getName(), true); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/wiring/package.html b/org.springframework.beans/src/main/java/org/springframework/beans/factory/wiring/package.html new file mode 100644 index 0000000000..d6e84c0004 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/wiring/package.html @@ -0,0 +1,8 @@ + + + +Mechanism to determine bean wiring metadata from a bean instance. +Foundation for aspect-driven bean configuration. + + + diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/AbstractBeanDefinitionParser.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/AbstractBeanDefinitionParser.java new file mode 100644 index 0000000000..d94cb58937 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/AbstractBeanDefinitionParser.java @@ -0,0 +1,191 @@ +/* + * Copyright 2002-2007 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.xml; + +import org.w3c.dom.Element; + +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.parsing.BeanComponentDefinition; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.util.StringUtils; + +/** + * Abstract {@link BeanDefinitionParser} implementation providing + * a number of convenience methods and a + * {@link AbstractBeanDefinitionParser#parseInternal template method} + * that subclasses must override to provide the actual parsing logic. + * + *

    Use this {@link BeanDefinitionParser} implementation when you want + * to parse some arbitrarily complex XML into one or more + * {@link BeanDefinition BeanDefinitions}. If you just want to parse some + * XML into a single BeanDefinition, you may wish to consider + * the simpler convenience extensions of this class, namely + * {@link AbstractSingleBeanDefinitionParser} and + * {@link AbstractSimpleBeanDefinitionParser}. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @author Rick Evans + * @since 2.0 + */ +public abstract class AbstractBeanDefinitionParser implements BeanDefinitionParser { + + /** Constant for the id attribute */ + public static final String ID_ATTRIBUTE = "id"; + + + public final BeanDefinition parse(Element element, ParserContext parserContext) { + AbstractBeanDefinition definition = parseInternal(element, parserContext); + if (!parserContext.isNested()) { + try { + String id = resolveId(element, definition, parserContext); + if (!StringUtils.hasText(id)) { + parserContext.getReaderContext().error( + "Id is required for element '" + element.getLocalName() + "' when used as a top-level tag", element); + } + BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id); + registerBeanDefinition(holder, parserContext.getRegistry()); + if (shouldFireEvents()) { + BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder); + postProcessComponentDefinition(componentDefinition); + parserContext.registerComponent(componentDefinition); + } + } + catch (BeanDefinitionStoreException ex) { + parserContext.getReaderContext().error(ex.getMessage(), element); + return null; + } + } + return definition; + } + + /** + * Resolve the ID for the supplied {@link BeanDefinition}. + *

    When using {@link #shouldGenerateId generation}, a name is generated automatically. + * Otherwise, the ID is extracted from the "id" attribute, potentially with a + * {@link #shouldGenerateIdAsFallback() fallback} to a generated id. + * @param element the element that the bean definition has been built from + * @param definition the bean definition to be registered + * @param parserContext the object encapsulating the current state of the parsing process; + * provides access to a {@link org.springframework.beans.factory.support.BeanDefinitionRegistry} + * @return the resolved id + * @throws BeanDefinitionStoreException if no unique name could be generated + * for the given bean definition + */ + protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) + throws BeanDefinitionStoreException { + + if (shouldGenerateId()) { + return parserContext.getReaderContext().generateBeanName(definition); + } + else { + String id = element.getAttribute(ID_ATTRIBUTE); + if (!StringUtils.hasText(id) && shouldGenerateIdAsFallback()) { + id = parserContext.getReaderContext().generateBeanName(definition); + } + return id; + } + } + + /** + * Register the supplied {@link BeanDefinitionHolder bean} with the supplied + * {@link BeanDefinitionRegistry registry}. + *

    Subclasses can override this method to control whether or not the supplied + * {@link BeanDefinitionHolder bean} is actually even registered, or to + * register even more beans. + *

    The default implementation registers the supplied {@link BeanDefinitionHolder bean} + * with the supplied {@link BeanDefinitionRegistry registry} only if the isNested + * parameter is false, because one typically does not want inner beans + * to be registered as top level beans. + * @param definition the bean definition to be registered + * @param registry the registry that the bean is to be registered with + * @see BeanDefinitionReaderUtils#registerBeanDefinition(BeanDefinitionHolder, BeanDefinitionRegistry) + */ + protected void registerBeanDefinition(BeanDefinitionHolder definition, BeanDefinitionRegistry registry) { + BeanDefinitionReaderUtils.registerBeanDefinition(definition, registry); + } + + + /** + * Central template method to actually parse the supplied {@link Element} + * into one or more {@link BeanDefinition BeanDefinitions}. + * @param element the element that is to be parsed into one or more {@link BeanDefinition BeanDefinitions} + * @param parserContext the object encapsulating the current state of the parsing process; + * provides access to a {@link org.springframework.beans.factory.support.BeanDefinitionRegistry} + * @return the primary {@link BeanDefinition} resulting from the parsing of the supplied {@link Element} + * @see #parse(org.w3c.dom.Element, ParserContext) + * @see #postProcessComponentDefinition(org.springframework.beans.factory.parsing.BeanComponentDefinition) + */ + protected abstract AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext); + + /** + * Should an ID be generated instead of read from the passed in {@link Element}? + *

    Disabled by default; subclasses can override this to enable ID generation. + * Note that this flag is about always generating an ID; the parser + * won't even check for an "id" attribute in this case. + * @return whether the parser should always generate an id + */ + protected boolean shouldGenerateId() { + return false; + } + + /** + * Should an ID be generated instead if the passed in {@link Element} does not + * specify an "id" attribute explicitly? + *

    Disabled by default; subclasses can override this to enable ID generation + * as fallback: The parser will first check for an "id" attribute in this case, + * only falling back to a generated ID if no value was specified. + * @return whether the parser should generate an id if no id was specified + */ + protected boolean shouldGenerateIdAsFallback() { + return false; + } + + /** + * Controls whether this parser is supposed to fire a + * {@link org.springframework.beans.factory.parsing.BeanComponentDefinition} + * event after parsing the bean definition. + *

    This implementation returns true by default; that is, + * an event will be fired when a bean definition has been completely parsed. + * Override this to return false in order to suppress the event. + * @return true in order to fire a component registration event + * after parsing the bean definition; false to suppress the event + * @see #postProcessComponentDefinition + * @see org.springframework.beans.factory.parsing.ReaderContext#fireComponentRegistered + */ + protected boolean shouldFireEvents() { + return true; + } + + /** + * Hook method called after the primary parsing of a + * {@link BeanComponentDefinition} but before the + * {@link BeanComponentDefinition} has been registered with a + * {@link org.springframework.beans.factory.support.BeanDefinitionRegistry}. + *

    Derived classes can override this method to supply any custom logic that + * is to be executed after all the parsing is finished. + *

    The default implementation is a no-op. + * @param componentDefinition the {@link BeanComponentDefinition} that is to be processed + */ + protected void postProcessComponentDefinition(BeanComponentDefinition componentDefinition) { + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/AbstractSimpleBeanDefinitionParser.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/AbstractSimpleBeanDefinitionParser.java new file mode 100644 index 0000000000..ac780cb070 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/AbstractSimpleBeanDefinitionParser.java @@ -0,0 +1,193 @@ +/* + * Copyright 2002-2007 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.xml; + +import org.w3c.dom.Attr; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; + +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.core.Conventions; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Convenient base class for when there exists a one-to-one mapping + * between attribute names on the element that is to be parsed and + * the property names on the {@link Class} being configured. + * + *

    Extend this parser class when you want to create a single + * bean definition from a relatively simple custom XML element. The + * resulting BeanDefinition will be automatically + * registered with the relevant + * {@link org.springframework.beans.factory.support.BeanDefinitionRegistry}. + * + *

    An example will hopefully make the use of this particular parser + * class immediately clear. Consider the following class definition: + * + *

    public class SimpleCache implements Cache {
    + * 
    + *     public void setName(String name) {...}
    + *     public void setTimeout(int timeout) {...}
    + *     public void setEvictionPolicy(EvictionPolicy policy) {...}
    + * 
    + *     // remaining class definition elided for clarity...
    + * }
    + * + *

    Then let us assume the following XML tag has been defined to + * permit the easy configuration of instances of the above class; + * + *

    <caching:cache name="..." timeout="..." eviction-policy="..."/>
    + * + *

    All that is required of the Java developer tasked with writing + * the parser to parse the above XML tag into an actual + * SimpleCache bean definition is the following: + * + *

    public class SimpleCacheBeanDefinitionParser extends AbstractSimpleBeanDefinitionParser {
    + *
    + *     protected Class getBeanClass(Element element) {
    + *         return SimpleCache.class;
    + *     }
    + * }
    + * + *

    Please note that the AbstractSimpleBeanDefinitionParser + * is limited to populating the created bean definition with property values. + * if you want to parse constructor arguments and nested elements from the + * supplied XML element, then you will have to implement the + * {@link #postProcess(org.springframework.beans.factory.support.BeanDefinitionBuilder, org.w3c.dom.Element)} + * method and do such parsing yourself, or (more likely) subclass the + * {@link AbstractSingleBeanDefinitionParser} or {@link AbstractBeanDefinitionParser} + * classes directly. + * + *

    The process of actually registering the + * SimpleCacheBeanDefinitionParser with the Spring XML parsing + * infrastructure is described in the Spring Framework reference documentation + * (in one of the appendices). + * + *

    For an example of this parser in action (so to speak), do look at + * the source code for the + * {@link org.springframework.beans.factory.xml.UtilNamespaceHandler.PropertiesBeanDefinitionParser}; + * the observant (and even not so observant) reader will immediately notice that + * there is next to no code in the implementation. The + * PropertiesBeanDefinitionParser populates a + * {@link org.springframework.beans.factory.config.PropertiesFactoryBean} + * from an XML element that looks like this: + * + *

    <util:properties location="jdbc.properties"/>
    + * + *

    The observant reader will notice that the sole attribute on the + * <util:properties/> element matches the + * {@link org.springframework.beans.factory.config.PropertiesFactoryBean#setLocation(org.springframework.core.io.Resource)} + * method name on the PropertiesFactoryBean (the general + * usage thus illustrated holds true for any number of attributes). + * All that the PropertiesBeanDefinitionParser needs + * actually do is supply an implementation of the + * {@link #getBeanClass(org.w3c.dom.Element)} method to return the + * PropertiesFactoryBean type. + * + * @author Rob Harrop + * @author Rick Evans + * @author Juergen Hoeller + * @since 2.0 + * @see Conventions#attributeNameToPropertyName(String) + */ +public abstract class AbstractSimpleBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { + + /** + * Parse the supplied {@link Element} and populate the supplied + * {@link BeanDefinitionBuilder} as required. + *

    This implementation maps any attributes present on the + * supplied element to {@link org.springframework.beans.PropertyValue} + * instances, and + * {@link BeanDefinitionBuilder#addPropertyValue(String, Object) adds them} + * to the + * {@link org.springframework.beans.factory.config.BeanDefinition builder}. + *

    The {@link #extractPropertyName(String)} method is used to + * reconcile the name of an attribute with the name of a JavaBean + * property. + * @param element the XML element being parsed + * @param builder used to define the BeanDefinition + * @see #extractPropertyName(String) + */ + protected final void doParse(Element element, BeanDefinitionBuilder builder) { + NamedNodeMap attributes = element.getAttributes(); + for (int x = 0; x < attributes.getLength(); x++) { + Attr attribute = (Attr) attributes.item(x); + if (isEligibleAttribute(attribute)) { + String propertyName = extractPropertyName(attribute.getLocalName()); + Assert.state(StringUtils.hasText(propertyName), + "Illegal property name returned from 'extractPropertyName(String)': cannot be null or empty."); + builder.addPropertyValue(propertyName, attribute.getValue()); + } + } + postProcess(builder, element); + } + + /** + * Determine whether the given attribute is eligible for being + * turned into a corresponding bean property value. + *

    The default implementation considers any attribute as eligible, + * except for the "id" attribute and namespace declaration attributes. + * @param attribute the XML attribute to check + * @see #isEligibleAttribute(String) + */ + protected boolean isEligibleAttribute(Attr attribute) { + String fullName = attribute.getName(); + return (!fullName.equals("xmlns") && !fullName.startsWith("xmlns:") && + isEligibleAttribute(attribute.getLocalName())); + } + + /** + * Determine whether the given attribute is eligible for being + * turned into a corresponding bean property value. + *

    The default implementation considers any attribute as eligible, + * except for the "id" attribute. + * @param attributeName the attribute name taken straight from the + * XML element being parsed (never null) + */ + protected boolean isEligibleAttribute(String attributeName) { + return !ID_ATTRIBUTE.equals(attributeName); + } + + /** + * Extract a JavaBean property name from the supplied attribute name. + *

    The default implementation uses the + * {@link Conventions#attributeNameToPropertyName(String)} + * method to perform the extraction. + *

    The name returned must obey the standard JavaBean property name + * conventions. For example for a class with a setter method + * 'setBingoHallFavourite(String)', the name returned had + * better be 'bingoHallFavourite' (with that exact casing). + * @param attributeName the attribute name taken straight from the + * XML element being parsed (never null) + * @return the extracted JavaBean property name (must never be null) + */ + protected String extractPropertyName(String attributeName) { + return Conventions.attributeNameToPropertyName(attributeName); + } + + /** + * Hook method that derived classes can implement to inspect/change a + * bean definition after parsing is complete. + *

    The default implementation does nothing. + * @param beanDefinition the parsed (and probably totally defined) bean definition being built + * @param element the XML element that was the source of the bean definition's metadata + */ + protected void postProcess(BeanDefinitionBuilder beanDefinition, Element element) { + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/AbstractSingleBeanDefinitionParser.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/AbstractSingleBeanDefinitionParser.java new file mode 100644 index 0000000000..1a1bd19e5c --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/AbstractSingleBeanDefinitionParser.java @@ -0,0 +1,152 @@ +/* + * Copyright 2002-2008 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.xml; + +import org.w3c.dom.Element; + +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; + +/** + * Base class for those {@link BeanDefinitionParser} implementations that + * need to parse and define just a single BeanDefinition. + * + *

    Extend this parser class when you want to create a single bean definition + * from an arbitrarily complex XML element. You may wish to consider extending + * the {@link AbstractSimpleBeanDefinitionParser} when you want to create a + * single bean definition from a relatively simple custom XML element. + * + *

    The resulting BeanDefinition will be automatically registered + * with the {@link org.springframework.beans.factory.support.BeanDefinitionRegistry}. + * Your job simply is to {@link #doParse parse} the custom XML {@link Element} + * into a single BeanDefinition. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @author Rick Evans + * @since 2.0 + * @see #getBeanClass + * @see #getBeanClassName + * @see #doParse + */ +public abstract class AbstractSingleBeanDefinitionParser extends AbstractBeanDefinitionParser { + + /** + * Creates a {@link BeanDefinitionBuilder} instance for the + * {@link #getBeanClass bean Class} and passes it to the + * {@link #doParse} strategy method. + * @param element the element that is to be parsed into a single BeanDefinition + * @param parserContext the object encapsulating the current state of the parsing process + * @return the BeanDefinition resulting from the parsing of the supplied {@link Element} + * @throws IllegalStateException if the bean {@link Class} returned from + * {@link #getBeanClass(org.w3c.dom.Element)} is null + * @see #doParse + */ + protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { + BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(); + String parentName = getParentName(element); + if (parentName != null) { + builder.getRawBeanDefinition().setParentName(parentName); + } + Class beanClass = getBeanClass(element); + if (beanClass != null) { + builder.getRawBeanDefinition().setBeanClass(beanClass); + } + else { + String beanClassName = getBeanClassName(element); + if (beanClassName != null) { + builder.getRawBeanDefinition().setBeanClassName(beanClassName); + } + } + builder.getRawBeanDefinition().setSource(parserContext.extractSource(element)); + if (parserContext.isNested()) { + // Inner bean definition must receive same scope as containing bean. + builder.setScope(parserContext.getContainingBeanDefinition().getScope()); + } + if (parserContext.isDefaultLazyInit()) { + // Default-lazy-init applies to custom bean definitions as well. + builder.setLazyInit(true); + } + doParse(element, parserContext, builder); + return builder.getBeanDefinition(); + } + + /** + * Determine the name for the parent of the currently parsed bean, + * in case of the current bean being defined as a child bean. + *

    The default implementation returns null, + * indicating a root bean definition. + * @param element the Element that is being parsed + * @return the name of the parent bean for the currently parsed bean, + * or null if none + */ + protected String getParentName(Element element) { + return null; + } + + /** + * Determine the bean class corresponding to the supplied {@link Element}. + *

    Note that, for application classes, it is generally preferable to + * override {@link #getBeanClassName} instead, in order to avoid a direct + * dependence on the bean implementation class. The BeanDefinitionParser + * and its NamespaceHandler can be used within an IDE plugin then, even + * if the application classes are not available on the plugin's classpath. + * @param element the Element that is being parsed + * @return the {@link Class} of the bean that is being defined via parsing + * the supplied Element, or null if none + * @see #getBeanClassName + */ + protected Class getBeanClass(Element element) { + return null; + } + + /** + * Determine the bean class name corresponding to the supplied {@link Element}. + * @param element the Element that is being parsed + * @return the class name of the bean that is being defined via parsing + * the supplied Element, or null if none + * @see #getBeanClass + */ + protected String getBeanClassName(Element element) { + return null; + } + + /** + * Parse the supplied {@link Element} and populate the supplied + * {@link BeanDefinitionBuilder} as required. + *

    The default implementation delegates to the doParse + * version without ParserContext argument. + * @param element the XML element being parsed + * @param parserContext the object encapsulating the current state of the parsing process + * @param builder used to define the BeanDefinition + * @see #doParse(Element, BeanDefinitionBuilder) + */ + protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { + doParse(element, builder); + } + + /** + * Parse the supplied {@link Element} and populate the supplied + * {@link BeanDefinitionBuilder} as required. + *

    The default implementation does nothing. + * @param element the XML element being parsed + * @param builder used to define the BeanDefinition + */ + protected void doParse(Element element, BeanDefinitionBuilder builder) { + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionDecorator.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionDecorator.java new file mode 100644 index 0000000000..b1a1d2c9e6 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionDecorator.java @@ -0,0 +1,72 @@ +/* + * Copyright 2002-2006 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.xml; + +import org.w3c.dom.Node; + +import org.springframework.beans.factory.config.BeanDefinitionHolder; + +/** + * Interface used by the {@link org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader} + * to handle custom, nested (directly under a <bean>) tags. + * + *

    Decoration may also occur based on custom attributes applied to the + * <bean> tag. Implementations are free to turn the metadata in the + * custom tag into as many + * {@link org.springframework.beans.factory.config.BeanDefinition BeanDefinitions} as + * required and to transform the + * {@link org.springframework.beans.factory.config.BeanDefinition} of the enclosing + * <bean> tag, potentially even returning a completely different + * {@link org.springframework.beans.factory.config.BeanDefinition} to replace the + * original. + * + *

    {@link BeanDefinitionDecorator BeanDefinitionDecorators} should be aware that + * they may be part of a chain. In particular, a {@link BeanDefinitionDecorator} should + * be aware that a previous {@link BeanDefinitionDecorator} may have replaced the + * original {@link org.springframework.beans.factory.config.BeanDefinition} with a + * {@link org.springframework.aop.framework.ProxyFactoryBean} definition allowing for + * custom {@link org.aopalliance.intercept.MethodInterceptor interceptors} to be added. + * + *

    {@link BeanDefinitionDecorator BeanDefinitionDecorators} that wish to add an + * interceptor to the enclosing bean should extend + * {@link org.springframework.aop.config.AbstractInterceptorDrivenBeanDefinitionDecorator} + * which handles the chaining ensuring that only one proxy is created and that it + * contains all interceptors from the chain. + * + *

    The parser locates a {@link BeanDefinitionDecorator} from the + * {@link NamespaceHandler} for the namespace in which the custom tag resides. + * + * @author Rob Harrop + * @since 2.0 + * @see NamespaceHandler + * @see BeanDefinitionParser + */ +public interface BeanDefinitionDecorator { + + /** + * Parse the specified {@link Node} (either an element or an attribute) and decorate + * the supplied {@link org.springframework.beans.factory.config.BeanDefinition}, + * returning the decorated definition. + *

    Implementations may choose to return a completely new definition, which will + * replace the original definition in the resulting + * {@link org.springframework.beans.factory.BeanFactory}. + *

    The supplied {@link ParserContext} can be used to register any additional + * beans needed to support the main definition. + */ + BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionDocumentReader.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionDocumentReader.java new file mode 100644 index 0000000000..decc6cd7e5 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionDocumentReader.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2006 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.xml; + +import org.w3c.dom.Document; + +import org.springframework.beans.factory.BeanDefinitionStoreException; + +/** + * SPI for parsing an XML document that contains Spring bean definitions. + * Used by XmlBeanDefinitionReader for actually parsing a DOM document. + * + *

    Instantiated per document to parse: Implementations can hold + * state in instance variables during the execution of the + * registerBeanDefinitions method, for example global + * settings that are defined for all bean definitions in the document. + * + * @author Juergen Hoeller + * @author Rob Harrop + * @since 18.12.2003 + * @see XmlBeanDefinitionReader#setDocumentReaderClass + */ +public interface BeanDefinitionDocumentReader { + + /** + * Read bean definitions from the given DOM document, + * and register them with the given bean factory. + * @param doc the DOM document + * @param readerContext the current context of the reader. Includes the resource being parsed + * @throws BeanDefinitionStoreException in case of parsing errors + */ + void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) + throws BeanDefinitionStoreException; + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionParser.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionParser.java new file mode 100644 index 0000000000..aab07a52d5 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionParser.java @@ -0,0 +1,58 @@ +/* + * Copyright 2002-2006 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.xml; + +import org.w3c.dom.Element; + +import org.springframework.beans.factory.config.BeanDefinition; + +/** + * Interface used by the + * {@link org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader} to + * handle custom, top-level (directly under <beans>) tags. + * + *

    Implementations are free to turn the metadata in the custom tag into as many + * {@link BeanDefinition BeanDefinitions} as required. + * + *

    The parser locates a {@link BeanDefinitionParser} from the associated + * {@link NamespaceHandler} for the namespace in which the custom tag resides. + * + * @author Rob Harrop + * @since 2.0 + * @see NamespaceHandler + * @see org.springframework.beans.factory.xml.BeanDefinitionDecorator + * @see AbstractBeanDefinitionParser + */ +public interface BeanDefinitionParser { + + /** + * Parse the specified {@link Element} and register the resulting + * {@link BeanDefinition BeanDefinition(s)} with the + * {@link org.springframework.beans.factory.xml.ParserContext#getRegistry()} BeanDefinitionRegistry} + * embedded in the supplied {@link ParserContext}. + *

    Implementations must return the primary {@link BeanDefinition} that results + * from the parse if they will ever be used in a nested fashion (for example as + * an inner tag in a <property/> tag). Implementations may return + * null if they will not be used in a nested fashion. + * @param element the element that is to be parsed into one or more {@link BeanDefinition BeanDefinitions} + * @param parserContext the object encapsulating the current state of the parsing process; + * provides access to a {@link org.springframework.beans.factory.support.BeanDefinitionRegistry} + * @return the primary {@link BeanDefinition} + */ + BeanDefinition parse(Element element, ParserContext parserContext); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionParserDelegate.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionParserDelegate.java new file mode 100644 index 0000000000..367eea0dc8 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionParserDelegate.java @@ -0,0 +1,1384 @@ +/* + * Copyright 2002-2008 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.xml; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import org.springframework.beans.BeanMetadataAttribute; +import org.springframework.beans.BeanMetadataAttributeAccessor; +import org.springframework.beans.PropertyValue; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.config.ConstructorArgumentValues; +import org.springframework.beans.factory.config.RuntimeBeanNameReference; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.config.TypedStringValue; +import org.springframework.beans.factory.parsing.BeanEntry; +import org.springframework.beans.factory.parsing.ConstructorArgumentEntry; +import org.springframework.beans.factory.parsing.ParseState; +import org.springframework.beans.factory.parsing.PropertyEntry; +import org.springframework.beans.factory.parsing.QualifierEntry; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.AutowireCandidateQualifier; +import org.springframework.beans.factory.support.BeanDefinitionDefaults; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; +import org.springframework.beans.factory.support.LookupOverride; +import org.springframework.beans.factory.support.ManagedList; +import org.springframework.beans.factory.support.ManagedMap; +import org.springframework.beans.factory.support.ManagedProperties; +import org.springframework.beans.factory.support.ManagedSet; +import org.springframework.beans.factory.support.MethodOverrides; +import org.springframework.beans.factory.support.ReplaceOverride; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; +import org.springframework.util.PatternMatchUtils; +import org.springframework.util.StringUtils; +import org.springframework.util.xml.DomUtils; + +/** + * Stateful delegate class used to parse XML bean definitions. Intended for use + * by both the main parser and any extension + * {@link BeanDefinitionParser BeanDefinitionParsers} or + * {@link BeanDefinitionDecorator BeanDefinitionDecorators}. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @author Rod Johnson + * @author Mark Fisher + * @since 2.0 + * @see ParserContext + * @see DefaultBeanDefinitionDocumentReader + */ +public class BeanDefinitionParserDelegate { + + public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans"; + + public static final String BEAN_NAME_DELIMITERS = ",; "; + + /** + * Value of a T/F attribute that represents true. Anything else represents + * false. Case seNsItive. + */ + public static final String TRUE_VALUE = "true"; + + public static final String DEFAULT_VALUE = "default"; + + public static final String DESCRIPTION_ELEMENT = "description"; + + public static final String AUTOWIRE_BY_NAME_VALUE = "byName"; + + public static final String AUTOWIRE_BY_TYPE_VALUE = "byType"; + + public static final String AUTOWIRE_CONSTRUCTOR_VALUE = "constructor"; + + public static final String AUTOWIRE_AUTODETECT_VALUE = "autodetect"; + + public static final String DEPENDENCY_CHECK_ALL_ATTRIBUTE_VALUE = "all"; + + public static final String DEPENDENCY_CHECK_SIMPLE_ATTRIBUTE_VALUE = "simple"; + + public static final String DEPENDENCY_CHECK_OBJECTS_ATTRIBUTE_VALUE = "objects"; + + public static final String NAME_ATTRIBUTE = "name"; + + public static final String BEAN_ELEMENT = "bean"; + + public static final String META_ELEMENT = "meta"; + + public static final String ID_ATTRIBUTE = "id"; + + public static final String PARENT_ATTRIBUTE = "parent"; + + public static final String CLASS_ATTRIBUTE = "class"; + + public static final String ABSTRACT_ATTRIBUTE = "abstract"; + + public static final String SCOPE_ATTRIBUTE = "scope"; + + public static final String SINGLETON_ATTRIBUTE = "singleton"; + + public static final String LAZY_INIT_ATTRIBUTE = "lazy-init"; + + public static final String AUTOWIRE_ATTRIBUTE = "autowire"; + + public static final String AUTOWIRE_CANDIDATE_ATTRIBUTE = "autowire-candidate"; + + public static final String PRIMARY_ATTRIBUTE = "primary"; + + public static final String DEPENDENCY_CHECK_ATTRIBUTE = "dependency-check"; + + public static final String DEPENDS_ON_ATTRIBUTE = "depends-on"; + + public static final String INIT_METHOD_ATTRIBUTE = "init-method"; + + public static final String DESTROY_METHOD_ATTRIBUTE = "destroy-method"; + + public static final String FACTORY_METHOD_ATTRIBUTE = "factory-method"; + + public static final String FACTORY_BEAN_ATTRIBUTE = "factory-bean"; + + public static final String CONSTRUCTOR_ARG_ELEMENT = "constructor-arg"; + + public static final String INDEX_ATTRIBUTE = "index"; + + public static final String TYPE_ATTRIBUTE = "type"; + + public static final String VALUE_TYPE_ATTRIBUTE = "value-type"; + + public static final String KEY_TYPE_ATTRIBUTE = "key-type"; + + public static final String PROPERTY_ELEMENT = "property"; + + public static final String REF_ATTRIBUTE = "ref"; + + public static final String VALUE_ATTRIBUTE = "value"; + + public static final String LOOKUP_METHOD_ELEMENT = "lookup-method"; + + public static final String REPLACED_METHOD_ELEMENT = "replaced-method"; + + public static final String REPLACER_ATTRIBUTE = "replacer"; + + public static final String ARG_TYPE_ELEMENT = "arg-type"; + + public static final String ARG_TYPE_MATCH_ATTRIBUTE = "match"; + + public static final String REF_ELEMENT = "ref"; + + public static final String IDREF_ELEMENT = "idref"; + + public static final String BEAN_REF_ATTRIBUTE = "bean"; + + public static final String LOCAL_REF_ATTRIBUTE = "local"; + + public static final String PARENT_REF_ATTRIBUTE = "parent"; + + public static final String VALUE_ELEMENT = "value"; + + public static final String NULL_ELEMENT = "null"; + + public static final String LIST_ELEMENT = "list"; + + public static final String SET_ELEMENT = "set"; + + public static final String MAP_ELEMENT = "map"; + + public static final String ENTRY_ELEMENT = "entry"; + + public static final String KEY_ELEMENT = "key"; + + public static final String KEY_ATTRIBUTE = "key"; + + public static final String KEY_REF_ATTRIBUTE = "key-ref"; + + public static final String VALUE_REF_ATTRIBUTE = "value-ref"; + + public static final String PROPS_ELEMENT = "props"; + + public static final String PROP_ELEMENT = "prop"; + + public static final String MERGE_ATTRIBUTE = "merge"; + + public static final String QUALIFIER_ELEMENT = "qualifier"; + + public static final String QUALIFIER_ATTRIBUTE_ELEMENT = "attribute"; + + public static final String DEFAULT_LAZY_INIT_ATTRIBUTE = "default-lazy-init"; + + public static final String DEFAULT_MERGE_ATTRIBUTE = "default-merge"; + + public static final String DEFAULT_AUTOWIRE_ATTRIBUTE = "default-autowire"; + + public static final String DEFAULT_DEPENDENCY_CHECK_ATTRIBUTE = "default-dependency-check"; + + public static final String DEFAULT_AUTOWIRE_CANDIDATES_ATTRIBUTE = "default-autowire-candidates"; + + public static final String DEFAULT_INIT_METHOD_ATTRIBUTE = "default-init-method"; + + public static final String DEFAULT_DESTROY_METHOD_ATTRIBUTE = "default-destroy-method"; + + protected final Log logger = LogFactory.getLog(getClass()); + + private final XmlReaderContext readerContext; + + private DocumentDefaultsDefinition defaults; + + private ParseState parseState = new ParseState(); + + /** + * Stores all used bean names so we can enforce uniqueness on a per file + * basis. + */ + private final Set usedNames = new HashSet(); + + + /** + * Create a new BeanDefinitionParserDelegate associated with the supplied + * {@link XmlReaderContext}. + */ + public BeanDefinitionParserDelegate(XmlReaderContext readerContext) { + Assert.notNull(readerContext, "XmlReaderContext must not be null"); + this.readerContext = readerContext; + } + + /** + * Get the {@link XmlReaderContext} associated with this helper instance. + */ + public final XmlReaderContext getReaderContext() { + return this.readerContext; + } + + /** + * Invoke the + * {@link org.springframework.beans.factory.parsing.SourceExtractor} to pull + * the source metadata from the supplied {@link Element}. + */ + protected Object extractSource(Element ele) { + return this.readerContext.extractSource(ele); + } + + /** + * Report an error with the given message for the given source element. + */ + protected void error(String message, Node source) { + this.readerContext.error(message, source, this.parseState.snapshot()); + } + + /** + * Report an error with the given message for the given source element. + */ + protected void error(String message, Element source) { + this.readerContext.error(message, source, this.parseState.snapshot()); + } + + /** + * Report an error with the given message for the given source element. + */ + protected void error(String message, Element source, Throwable cause) { + this.readerContext.error(message, source, this.parseState.snapshot(), cause); + } + + /** + * Initialize the default lazy-init, autowire, dependency check settings, + * init-method, destroy-method and merge settings. + * + * @see #getDefaults() + */ + public void initDefaults(Element root) { + DocumentDefaultsDefinition defaults = new DocumentDefaultsDefinition(); + defaults.setLazyInit(root.getAttribute(DEFAULT_LAZY_INIT_ATTRIBUTE)); + defaults.setMerge(root.getAttribute(DEFAULT_MERGE_ATTRIBUTE)); + defaults.setAutowire(root.getAttribute(DEFAULT_AUTOWIRE_ATTRIBUTE)); + defaults.setDependencyCheck(root.getAttribute(DEFAULT_DEPENDENCY_CHECK_ATTRIBUTE)); + if (root.hasAttribute(DEFAULT_AUTOWIRE_CANDIDATES_ATTRIBUTE)) { + defaults.setAutowireCandidates(root.getAttribute(DEFAULT_AUTOWIRE_CANDIDATES_ATTRIBUTE)); + } + if (root.hasAttribute(DEFAULT_INIT_METHOD_ATTRIBUTE)) { + defaults.setInitMethod(root.getAttribute(DEFAULT_INIT_METHOD_ATTRIBUTE)); + } + if (root.hasAttribute(DEFAULT_DESTROY_METHOD_ATTRIBUTE)) { + defaults.setDestroyMethod(root.getAttribute(DEFAULT_DESTROY_METHOD_ATTRIBUTE)); + } + defaults.setSource(this.readerContext.extractSource(root)); + + this.defaults = defaults; + this.readerContext.fireDefaultsRegistered(defaults); + } + + /** + * Return the defaults definition object, or null if the + * defaults have been initialized yet. + */ + public DocumentDefaultsDefinition getDefaults() { + return this.defaults; + } + + /** + * Return the default settings for bean definitions as indicated within the + * attributes of the top-level <beans/> element. + */ + public BeanDefinitionDefaults getBeanDefinitionDefaults() { + BeanDefinitionDefaults bdd = new BeanDefinitionDefaults(); + if (this.defaults != null) { + bdd.setLazyInit("TRUE".equalsIgnoreCase(this.defaults.getLazyInit())); + bdd.setDependencyCheck(this.getDependencyCheck(DEFAULT_VALUE)); + bdd.setAutowireMode(this.getAutowireMode(DEFAULT_VALUE)); + bdd.setInitMethodName(this.defaults.getInitMethod()); + bdd.setDestroyMethodName(this.defaults.getDestroyMethod()); + } + return bdd; + } + + /** + * Return any patterns provided in the 'default-autowire-candidates' + * attribute of the top-level <beans/> element. + */ + public String[] getAutowireCandidatePatterns() { + String candidatePattern = this.defaults.getAutowireCandidates(); + return candidatePattern == null ? null : StringUtils.commaDelimitedListToStringArray(candidatePattern); + } + + /** + * Parses the supplied <bean> element. May return + * null if there were errors during parse. Errors are + * reported to the + * {@link org.springframework.beans.factory.parsing.ProblemReporter}. + */ + public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) { + return parseBeanDefinitionElement(ele, null); + } + + /** + * Parses the supplied <bean> element. May return + * null if there were errors during parse. Errors are + * reported to the + * {@link org.springframework.beans.factory.parsing.ProblemReporter}. + */ + public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) { + String id = ele.getAttribute(ID_ATTRIBUTE); + String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); + + List aliases = new ArrayList(); + if (StringUtils.hasLength(nameAttr)) { + String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, BEAN_NAME_DELIMITERS); + aliases.addAll(Arrays.asList(nameArr)); + } + + String beanName = id; + if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) { + beanName = (String) aliases.remove(0); + if (logger.isDebugEnabled()) { + logger.debug("No XML 'id' specified - using '" + beanName + "' as bean name and " + aliases + + " as aliases"); + } + } + + if (containingBean == null) { + checkNameUniqueness(beanName, aliases, ele); + } + + AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean); + if (beanDefinition != null) { + if (!StringUtils.hasText(beanName)) { + try { + if (containingBean != null) { + beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, + this.readerContext.getRegistry(), true); + } + else { + beanName = this.readerContext.generateBeanName(beanDefinition); + // Register an alias for the plain bean class name, if still possible, + // if the generator returned the class name plus a suffix. + // This is expected for Spring 1.2/2.0 backwards compatibility. + String beanClassName = beanDefinition.getBeanClassName(); + if (beanClassName != null && beanName.startsWith(beanClassName) + && beanName.length() > beanClassName.length() + && !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) { + aliases.add(beanClassName); + } + } + if (logger.isDebugEnabled()) { + logger.debug("Neither XML 'id' nor 'name' specified - " + "using generated bean name [" + + beanName + "]"); + } + } + catch (Exception ex) { + error(ex.getMessage(), ele); + return null; + } + } + String[] aliasesArray = StringUtils.toStringArray(aliases); + return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray); + } + + return null; + } + + /** + * Validate that the specified bean name and aliases have not been used + * already. + */ + protected void checkNameUniqueness(String beanName, List aliases, Element beanElement) { + String foundName = null; + + if (StringUtils.hasText(beanName) && this.usedNames.contains(beanName)) { + foundName = beanName; + } + if (foundName == null) { + foundName = (String) CollectionUtils.findFirstMatch(this.usedNames, aliases); + } + if (foundName != null) { + error("Bean name '" + foundName + "' is already used in this file", beanElement); + } + + this.usedNames.add(beanName); + this.usedNames.addAll(aliases); + } + + /** + * Parse the bean definition itself, without regard to name or aliases. May + * return null if problems occured during the parse of the + * bean definition. + */ + public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, BeanDefinition containingBean) { + + this.parseState.push(new BeanEntry(beanName)); + + String className = null; + if (ele.hasAttribute(CLASS_ATTRIBUTE)) { + className = ele.getAttribute(CLASS_ATTRIBUTE).trim(); + } + + try { + String parent = null; + if (ele.hasAttribute(PARENT_ATTRIBUTE)) { + parent = ele.getAttribute(PARENT_ATTRIBUTE); + } + AbstractBeanDefinition bd = createBeanDefinition(className, parent); + + parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); + + bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT)); + + parseMetaElements(ele, bd); + parseLookupOverrideSubElements(ele, bd.getMethodOverrides()); + parseReplacedMethodSubElements(ele, bd.getMethodOverrides()); + + parseConstructorArgElements(ele, bd); + parsePropertyElements(ele, bd); + parseQualifierElements(ele, bd); + + bd.setResource(this.readerContext.getResource()); + bd.setSource(extractSource(ele)); + + return bd; + } + catch (ClassNotFoundException ex) { + error("Bean class [" + className + "] not found", ele, ex); + } + catch (NoClassDefFoundError err) { + error("Class that bean class [" + className + "] depends on not found", ele, err); + } + catch (Throwable ex) { + error("Unexpected failure during bean definition parsing", ele, ex); + } + finally { + this.parseState.pop(); + } + + return null; + } + + /** + * Apply the attributes of the given bean element to the given bean + * definition. + * + * @param ele bean declaration element + * @param beanName bean name + * @param containingBean containing bean definition + * @return a bean definition initialized according to the bean element + * attributes + */ + public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName, + BeanDefinition containingBean, AbstractBeanDefinition bd) { + + if (ele.hasAttribute(SCOPE_ATTRIBUTE)) { + // Spring 2.x "scope" attribute + bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE)); + if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) { + error("Specify either 'scope' or 'singleton', not both", ele); + } + } + else if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) { + // Spring 1.x "singleton" attribute + bd.setScope(TRUE_VALUE.equals(ele.getAttribute(SINGLETON_ATTRIBUTE)) ? BeanDefinition.SCOPE_SINGLETON + : BeanDefinition.SCOPE_PROTOTYPE); + } + else if (containingBean != null) { + // Take default from containing bean in case of an inner bean definition. + bd.setScope(containingBean.getScope()); + } + + if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) { + bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE))); + } + + String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE); + if (DEFAULT_VALUE.equals(lazyInit) && bd.isSingleton()) { + // Just apply default to singletons, as lazy-init has no meaning for prototypes. + lazyInit = this.defaults.getLazyInit(); + } + bd.setLazyInit(TRUE_VALUE.equals(lazyInit)); + + String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE); + bd.setAutowireMode(getAutowireMode(autowire)); + + String dependencyCheck = ele.getAttribute(DEPENDENCY_CHECK_ATTRIBUTE); + bd.setDependencyCheck(getDependencyCheck(dependencyCheck)); + + if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) { + String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE); + bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, BEAN_NAME_DELIMITERS)); + } + + String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE); + if ("".equals(autowireCandidate) || DEFAULT_VALUE.equals(autowireCandidate)) { + String candidatePattern = this.defaults.getAutowireCandidates(); + if (candidatePattern != null) { + String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern); + bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName)); + } + } + else { + bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate)); + } + + if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) { + bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE))); + } + + if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) { + String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE); + if (!"".equals(initMethodName)) { + bd.setInitMethodName(initMethodName); + } + } + else { + if (this.defaults.getInitMethod() != null) { + bd.setInitMethodName(this.defaults.getInitMethod()); + bd.setEnforceInitMethod(false); + } + } + + if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) { + String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE); + if (!"".equals(destroyMethodName)) { + bd.setDestroyMethodName(destroyMethodName); + } + } + else { + if (this.defaults.getDestroyMethod() != null) { + bd.setDestroyMethodName(this.defaults.getDestroyMethod()); + bd.setEnforceDestroyMethod(false); + } + } + + if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) { + bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE)); + } + if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) { + bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE)); + } + + return bd; + } + + /** + * Create a bean definition for the given class name and parent name. + * + * @param className the name of the bean class + * @param parentName the name of the bean's parent bean + * @return the newly created bean definition + * @throws ClassNotFoundException if bean class resolution was attempted but + * failed + */ + protected AbstractBeanDefinition createBeanDefinition(String className, String parentName) + throws ClassNotFoundException { + + return BeanDefinitionReaderUtils.createBeanDefinition(parentName, className, + this.readerContext.getBeanClassLoader()); + } + + public void parseMetaElements(Element ele, BeanMetadataAttributeAccessor attributeAccessor) { + NodeList nl = ele.getChildNodes(); + for (int i = 0; i < nl.getLength(); i++) { + Node node = nl.item(i); + if (node instanceof Element && DomUtils.nodeNameEquals(node, META_ELEMENT)) { + Element metaElement = (Element) node; + String key = metaElement.getAttribute(KEY_ATTRIBUTE); + String value = metaElement.getAttribute(VALUE_ATTRIBUTE); + BeanMetadataAttribute attribute = new BeanMetadataAttribute(key, value); + attribute.setSource(extractSource(metaElement)); + attributeAccessor.addMetadataAttribute(attribute); + } + } + } + + public int getAutowireMode(String attValue) { + String att = attValue; + if (DEFAULT_VALUE.equals(att)) { + att = this.defaults.getAutowire(); + } + int autowire = AbstractBeanDefinition.AUTOWIRE_NO; + if (AUTOWIRE_BY_NAME_VALUE.equals(att)) { + autowire = AbstractBeanDefinition.AUTOWIRE_BY_NAME; + } + else if (AUTOWIRE_BY_TYPE_VALUE.equals(att)) { + autowire = AbstractBeanDefinition.AUTOWIRE_BY_TYPE; + } + else if (AUTOWIRE_CONSTRUCTOR_VALUE.equals(att)) { + autowire = AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR; + } + else if (AUTOWIRE_AUTODETECT_VALUE.equals(att)) { + autowire = AbstractBeanDefinition.AUTOWIRE_AUTODETECT; + } + // Else leave default value. + return autowire; + } + + public int getDependencyCheck(String attValue) { + String att = attValue; + if (DEFAULT_VALUE.equals(att)) { + att = this.defaults.getDependencyCheck(); + } + int dependencyCheckCode = AbstractBeanDefinition.DEPENDENCY_CHECK_NONE; + if (DEPENDENCY_CHECK_ALL_ATTRIBUTE_VALUE.equals(att)) { + dependencyCheckCode = AbstractBeanDefinition.DEPENDENCY_CHECK_ALL; + } + else if (DEPENDENCY_CHECK_SIMPLE_ATTRIBUTE_VALUE.equals(att)) { + dependencyCheckCode = AbstractBeanDefinition.DEPENDENCY_CHECK_SIMPLE; + } + else if (DEPENDENCY_CHECK_OBJECTS_ATTRIBUTE_VALUE.equals(att)) { + dependencyCheckCode = AbstractBeanDefinition.DEPENDENCY_CHECK_OBJECTS; + } + // Else leave default value. + return dependencyCheckCode; + } + + /** + * Parse constructor-arg sub-elements of the given bean element. + */ + public void parseConstructorArgElements(Element beanEle, BeanDefinition bd) { + NodeList nl = beanEle.getChildNodes(); + for (int i = 0; i < nl.getLength(); i++) { + Node node = nl.item(i); + if (node instanceof Element && DomUtils.nodeNameEquals(node, CONSTRUCTOR_ARG_ELEMENT)) { + parseConstructorArgElement((Element) node, bd); + } + } + } + + /** + * Parse property sub-elements of the given bean element. + */ + public void parsePropertyElements(Element beanEle, BeanDefinition bd) { + NodeList nl = beanEle.getChildNodes(); + for (int i = 0; i < nl.getLength(); i++) { + Node node = nl.item(i); + if (node instanceof Element && DomUtils.nodeNameEquals(node, PROPERTY_ELEMENT)) { + parsePropertyElement((Element) node, bd); + } + } + } + + /** + * Parse qualifier sub-elements of the given bean element. + */ + public void parseQualifierElements(Element beanEle, AbstractBeanDefinition bd) { + NodeList nl = beanEle.getChildNodes(); + for (int i = 0; i < nl.getLength(); i++) { + Node node = nl.item(i); + if (node instanceof Element && DomUtils.nodeNameEquals(node, QUALIFIER_ELEMENT)) { + parseQualifierElement((Element) node, bd); + } + } + } + + /** + * Parse lookup-override sub-elements of the given bean element. + */ + public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) { + NodeList nl = beanEle.getChildNodes(); + for (int i = 0; i < nl.getLength(); i++) { + Node node = nl.item(i); + if (node instanceof Element && DomUtils.nodeNameEquals(node, LOOKUP_METHOD_ELEMENT)) { + Element ele = (Element) node; + String methodName = ele.getAttribute(NAME_ATTRIBUTE); + String beanRef = ele.getAttribute(BEAN_ELEMENT); + LookupOverride override = new LookupOverride(methodName, beanRef); + override.setSource(extractSource(ele)); + overrides.addOverride(override); + } + } + } + + /** + * Parse replaced-method sub-elements of the given bean element. + */ + public void parseReplacedMethodSubElements(Element beanEle, MethodOverrides overrides) { + NodeList nl = beanEle.getChildNodes(); + for (int i = 0; i < nl.getLength(); i++) { + Node node = nl.item(i); + if (node instanceof Element && DomUtils.nodeNameEquals(node, REPLACED_METHOD_ELEMENT)) { + Element replacedMethodEle = (Element) node; + String name = replacedMethodEle.getAttribute(NAME_ATTRIBUTE); + String callback = replacedMethodEle.getAttribute(REPLACER_ATTRIBUTE); + ReplaceOverride replaceOverride = new ReplaceOverride(name, callback); + // Look for arg-type match elements. + List argTypeEles = DomUtils.getChildElementsByTagName(replacedMethodEle, ARG_TYPE_ELEMENT); + for (Iterator it = argTypeEles.iterator(); it.hasNext();) { + Element argTypeEle = (Element) it.next(); + replaceOverride.addTypeIdentifier(argTypeEle.getAttribute(ARG_TYPE_MATCH_ATTRIBUTE)); + } + replaceOverride.setSource(extractSource(replacedMethodEle)); + overrides.addOverride(replaceOverride); + } + } + } + + /** + * Parse a constructor-arg element. + */ + public void parseConstructorArgElement(Element ele, BeanDefinition bd) { + String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE); + String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE); + if (StringUtils.hasLength(indexAttr)) { + try { + int index = Integer.parseInt(indexAttr); + if (index < 0) { + error("'index' cannot be lower than 0", ele); + } + else { + try { + this.parseState.push(new ConstructorArgumentEntry(index)); + Object value = parsePropertyValue(ele, bd, null); + ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder( + value); + if (StringUtils.hasLength(typeAttr)) { + valueHolder.setType(typeAttr); + } + valueHolder.setSource(extractSource(ele)); + bd.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder); + } + finally { + this.parseState.pop(); + } + } + } + catch (NumberFormatException ex) { + error("Attribute 'index' of tag 'constructor-arg' must be an integer", ele); + } + } + else { + try { + this.parseState.push(new ConstructorArgumentEntry()); + Object value = parsePropertyValue(ele, bd, null); + ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value); + if (StringUtils.hasLength(typeAttr)) { + valueHolder.setType(typeAttr); + } + valueHolder.setSource(extractSource(ele)); + bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder); + } + finally { + this.parseState.pop(); + } + } + } + + /** + * Parse a property element. + */ + public void parsePropertyElement(Element ele, BeanDefinition bd) { + String propertyName = ele.getAttribute(NAME_ATTRIBUTE); + if (!StringUtils.hasLength(propertyName)) { + error("Tag 'property' must have a 'name' attribute", ele); + return; + } + this.parseState.push(new PropertyEntry(propertyName)); + try { + if (bd.getPropertyValues().contains(propertyName)) { + error("Multiple 'property' definitions for property '" + propertyName + "'", ele); + return; + } + Object val = parsePropertyValue(ele, bd, propertyName); + PropertyValue pv = new PropertyValue(propertyName, val); + parseMetaElements(ele, pv); + pv.setSource(extractSource(ele)); + bd.getPropertyValues().addPropertyValue(pv); + } + finally { + this.parseState.pop(); + } + } + + /** + * Parse a qualifier element. + */ + public void parseQualifierElement(Element ele, AbstractBeanDefinition bd) { + String typeName = ele.getAttribute(TYPE_ATTRIBUTE); + if (!StringUtils.hasLength(typeName)) { + error("Tag 'qualifier' must have a 'type' attribute", ele); + return; + } + this.parseState.push(new QualifierEntry(typeName)); + try { + AutowireCandidateQualifier qualifier = new AutowireCandidateQualifier(typeName); + qualifier.setSource(extractSource(ele)); + String value = ele.getAttribute(VALUE_ATTRIBUTE); + if (StringUtils.hasLength(value)) { + qualifier.setAttribute(AutowireCandidateQualifier.VALUE_KEY, value); + } + NodeList nl = ele.getChildNodes(); + for (int i = 0; i < nl.getLength(); i++) { + Node node = nl.item(i); + if (node instanceof Element && DomUtils.nodeNameEquals(node, QUALIFIER_ATTRIBUTE_ELEMENT)) { + Element attributeEle = (Element) node; + String attributeName = attributeEle.getAttribute(KEY_ATTRIBUTE); + String attributeValue = attributeEle.getAttribute(VALUE_ATTRIBUTE); + if (StringUtils.hasLength(attributeName) && StringUtils.hasLength(attributeValue)) { + BeanMetadataAttribute attribute = new BeanMetadataAttribute(attributeName, attributeValue); + attribute.setSource(extractSource(attributeEle)); + qualifier.addMetadataAttribute(attribute); + } + else { + error("Qualifier 'attribute' tag must have a 'name' and 'value'", attributeEle); + return; + } + } + } + bd.addQualifier(qualifier); + } + finally { + this.parseState.pop(); + } + } + + /** + * Get the value of a property element. May be a list etc. Also used for + * constructor arguments, "propertyName" being null in this case. + */ + public Object parsePropertyValue(Element ele, BeanDefinition bd, String propertyName) { + String elementName = (propertyName != null) ? " element for property '" + propertyName + "'" + : " element"; + + // Should only have one child element: ref, value, list, etc. + NodeList nl = ele.getChildNodes(); + Element subElement = null; + for (int i = 0; i < nl.getLength(); i++) { + Node node = nl.item(i); + if (node instanceof Element && !DomUtils.nodeNameEquals(node, DESCRIPTION_ELEMENT) + && !DomUtils.nodeNameEquals(node, META_ELEMENT)) { + // Child element is what we're looking for. + if (subElement != null) { + error(elementName + " must not contain more than one sub-element", ele); + } + else { + subElement = (Element) node; + } + } + } + + boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE); + boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE); + if ((hasRefAttribute && hasValueAttribute) || ((hasRefAttribute || hasValueAttribute) && subElement != null)) { + error(elementName + + " is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele); + } + + if (hasRefAttribute) { + String refName = ele.getAttribute(REF_ATTRIBUTE); + if (!StringUtils.hasText(refName)) { + error(elementName + " contains empty 'ref' attribute", ele); + } + RuntimeBeanReference ref = new RuntimeBeanReference(refName); + ref.setSource(extractSource(ele)); + return ref; + } + else if (hasValueAttribute) { + TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE)); + valueHolder.setSource(extractSource(ele)); + return valueHolder; + } + else if (subElement != null) { + return parsePropertySubElement(subElement, bd); + } + else { + // Neither child element nor "ref" or "value" attribute found. + error(elementName + " must specify a ref or value", ele); + return null; + } + } + + public Object parsePropertySubElement(Element ele, BeanDefinition bd) { + return parsePropertySubElement(ele, bd, null); + } + + /** + * Parse a value, ref or collection sub-element of a property or + * constructor-arg element. + * + * @param ele subelement of property element; we don't know which yet + * @param defaultTypeClassName the default type (class name) for any + * <value> tag that might be created + */ + public Object parsePropertySubElement(Element ele, BeanDefinition bd, String defaultTypeClassName) { + if (!isDefaultNamespace(ele.getNamespaceURI())) { + return parseNestedCustomElement(ele, bd); + } + else if (DomUtils.nodeNameEquals(ele, BEAN_ELEMENT)) { + BeanDefinitionHolder nestedBd = parseBeanDefinitionElement(ele, bd); + if (nestedBd != null) { + nestedBd = decorateBeanDefinitionIfRequired(ele, nestedBd, bd); + } + return nestedBd; + } + else if (DomUtils.nodeNameEquals(ele, REF_ELEMENT)) { + // A generic reference to any name of any bean. + String refName = ele.getAttribute(BEAN_REF_ATTRIBUTE); + boolean toParent = false; + if (!StringUtils.hasLength(refName)) { + // A reference to the id of another bean in the same XML file. + refName = ele.getAttribute(LOCAL_REF_ATTRIBUTE); + if (!StringUtils.hasLength(refName)) { + // A reference to the id of another bean in a parent context. + refName = ele.getAttribute(PARENT_REF_ATTRIBUTE); + toParent = true; + if (!StringUtils.hasLength(refName)) { + error("'bean', 'local' or 'parent' is required for element", ele); + return null; + } + } + } + if (!StringUtils.hasText(refName)) { + error(" element contains empty target attribute", ele); + return null; + } + RuntimeBeanReference ref = new RuntimeBeanReference(refName, toParent); + ref.setSource(extractSource(ele)); + return ref; + } + else if (DomUtils.nodeNameEquals(ele, IDREF_ELEMENT)) { + return parseIdRefElement(ele); + } + else if (DomUtils.nodeNameEquals(ele, VALUE_ELEMENT)) { + return parseValueElement(ele, defaultTypeClassName); + } + else if (DomUtils.nodeNameEquals(ele, NULL_ELEMENT)) { + // It's a distinguished null value. Let's wrap it in a TypedStringValue + // object in order to preserve the source location. + TypedStringValue nullHolder = new TypedStringValue(null); + nullHolder.setSource(extractSource(ele)); + return nullHolder; + } + else if (DomUtils.nodeNameEquals(ele, LIST_ELEMENT)) { + return parseListElement(ele, bd); + } + else if (DomUtils.nodeNameEquals(ele, SET_ELEMENT)) { + return parseSetElement(ele, bd); + } + else if (DomUtils.nodeNameEquals(ele, MAP_ELEMENT)) { + return parseMapElement(ele, bd); + } + else if (DomUtils.nodeNameEquals(ele, PROPS_ELEMENT)) { + return parsePropsElement(ele); + } + else { + error("Unknown property sub-element: [" + ele.getNodeName() + "]", ele); + return null; + } + } + + /** + * Return a typed String value Object for the given 'idref' element. + * + * @param ele + * @param bd + * @return + */ + public Object parseIdRefElement(Element ele) { + // A generic reference to any name of any bean. + String refName = ele.getAttribute(BEAN_REF_ATTRIBUTE); + if (!StringUtils.hasLength(refName)) { + // A reference to the id of another bean in the same XML file. + refName = ele.getAttribute(LOCAL_REF_ATTRIBUTE); + if (!StringUtils.hasLength(refName)) { + error("Either 'bean' or 'local' is required for element", ele); + return null; + } + } + if (!StringUtils.hasText(refName)) { + error(" element contains empty target attribute", ele); + return null; + } + RuntimeBeanNameReference ref = new RuntimeBeanNameReference(refName); + ref.setSource(extractSource(ele)); + return ref; + } + + /** + * Return a typed String value Object for the given value element. + * + * @param ele element + * @param defaultTypeClassName type class name + * @return typed String value Object + */ + public Object parseValueElement(Element ele, String defaultTypeClassName) { + // It's a literal value. + String value = DomUtils.getTextValue(ele); + String typeClassName = ele.getAttribute(TYPE_ATTRIBUTE); + if (!StringUtils.hasText(typeClassName)) { + typeClassName = defaultTypeClassName; + } + try { + return buildTypedStringValue(value, typeClassName, ele); + } + catch (ClassNotFoundException ex) { + error("Type class [" + typeClassName + "] not found for element", ele, ex); + return value; + } + } + + /** + * Build a typed String value Object for the given raw value. + * + * @see org.springframework.beans.factory.config.TypedStringValue + */ + protected Object buildTypedStringValue(String value, String targetTypeName, Element ele) + throws ClassNotFoundException { + + ClassLoader classLoader = this.readerContext.getBeanClassLoader(); + TypedStringValue typedValue = null; + if (!StringUtils.hasText(targetTypeName)) { + typedValue = new TypedStringValue(value); + } + else if (classLoader != null) { + Class targetType = ClassUtils.forName(targetTypeName, classLoader); + typedValue = new TypedStringValue(value, targetType); + } + else { + typedValue = new TypedStringValue(value, targetTypeName); + } + typedValue.setSource(extractSource(ele)); + return typedValue; + } + + /** + * Parse a list element. + */ + public List parseListElement(Element collectionEle, BeanDefinition bd) { + String defaultTypeClassName = collectionEle.getAttribute(VALUE_TYPE_ATTRIBUTE); + NodeList nl = collectionEle.getChildNodes(); + ManagedList list = new ManagedList(nl.getLength()); + list.setSource(extractSource(collectionEle)); + list.setMergeEnabled(parseMergeAttribute(collectionEle)); + for (int i = 0; i < nl.getLength(); i++) { + Node node = nl.item(i); + if (node instanceof Element && !DomUtils.nodeNameEquals(node, DESCRIPTION_ELEMENT)) { + list.add(parsePropertySubElement((Element) node, bd, defaultTypeClassName)); + } + } + return list; + } + + /** + * Parse a set element. + */ + public Set parseSetElement(Element collectionEle, BeanDefinition bd) { + String defaultTypeClassName = collectionEle.getAttribute(VALUE_TYPE_ATTRIBUTE); + NodeList nl = collectionEle.getChildNodes(); + ManagedSet set = new ManagedSet(nl.getLength()); + set.setSource(extractSource(collectionEle)); + set.setMergeEnabled(parseMergeAttribute(collectionEle)); + for (int i = 0; i < nl.getLength(); i++) { + Node node = nl.item(i); + if (node instanceof Element && !DomUtils.nodeNameEquals(node, DESCRIPTION_ELEMENT)) { + set.add(parsePropertySubElement((Element) node, bd, defaultTypeClassName)); + } + } + return set; + } + + /** + * Parse a map element. + */ + public Map parseMapElement(Element mapEle, BeanDefinition bd) { + String defaultKeyTypeClassName = mapEle.getAttribute(KEY_TYPE_ATTRIBUTE); + String defaultValueTypeClassName = mapEle.getAttribute(VALUE_TYPE_ATTRIBUTE); + + List entryEles = DomUtils.getChildElementsByTagName(mapEle, ENTRY_ELEMENT); + ManagedMap map = new ManagedMap(entryEles.size()); + map.setMergeEnabled(parseMergeAttribute(mapEle)); + map.setSource(extractSource(mapEle)); + + for (Iterator it = entryEles.iterator(); it.hasNext();) { + Element entryEle = (Element) it.next(); + // Should only have one value child element: ref, value, list, etc. + // Optionally, there might be a key child element. + NodeList entrySubNodes = entryEle.getChildNodes(); + + Element keyEle = null; + Element valueEle = null; + for (int j = 0; j < entrySubNodes.getLength(); j++) { + Node node = entrySubNodes.item(j); + if (node instanceof Element) { + Element candidateEle = (Element) node; + if (DomUtils.nodeNameEquals(candidateEle, KEY_ELEMENT)) { + if (keyEle != null) { + error(" element is only allowed to contain one sub-element", entryEle); + } + else { + keyEle = candidateEle; + } + } + else { + // Child element is what we're looking for. + if (valueEle != null) { + error(" element must not contain more than one value sub-element", entryEle); + } + else { + valueEle = candidateEle; + } + } + } + } + + // Extract key from attribute or sub-element. + Object key = null; + boolean hasKeyAttribute = entryEle.hasAttribute(KEY_ATTRIBUTE); + boolean hasKeyRefAttribute = entryEle.hasAttribute(KEY_REF_ATTRIBUTE); + if ((hasKeyAttribute && hasKeyRefAttribute) || ((hasKeyAttribute || hasKeyRefAttribute)) && keyEle != null) { + error(" element is only allowed to contain either " + + "a 'key' attribute OR a 'key-ref' attribute OR a sub-element", entryEle); + } + if (hasKeyAttribute) { + key = buildTypedStringValueForMap(entryEle.getAttribute(KEY_ATTRIBUTE), defaultKeyTypeClassName, + entryEle); + } + else if (hasKeyRefAttribute) { + String refName = entryEle.getAttribute(KEY_REF_ATTRIBUTE); + if (!StringUtils.hasText(refName)) { + error(" element contains empty 'key-ref' attribute", entryEle); + } + RuntimeBeanReference ref = new RuntimeBeanReference(refName); + ref.setSource(extractSource(entryEle)); + key = ref; + } + else if (keyEle != null) { + key = parseKeyElement(keyEle, bd, defaultKeyTypeClassName); + } + else { + error(" element must specify a key", entryEle); + } + + // Extract value from attribute or sub-element. + Object value = null; + boolean hasValueAttribute = entryEle.hasAttribute(VALUE_ATTRIBUTE); + boolean hasValueRefAttribute = entryEle.hasAttribute(VALUE_REF_ATTRIBUTE); + if ((hasValueAttribute && hasValueRefAttribute) || ((hasValueAttribute || hasValueRefAttribute)) + && valueEle != null) { + error(" element is only allowed to contain either " + + "'value' attribute OR 'value-ref' attribute OR sub-element", entryEle); + } + if (hasValueAttribute) { + value = buildTypedStringValueForMap(entryEle.getAttribute(VALUE_ATTRIBUTE), defaultValueTypeClassName, + entryEle); + } + else if (hasValueRefAttribute) { + String refName = entryEle.getAttribute(VALUE_REF_ATTRIBUTE); + if (!StringUtils.hasText(refName)) { + error(" element contains empty 'value-ref' attribute", entryEle); + } + RuntimeBeanReference ref = new RuntimeBeanReference(refName); + ref.setSource(extractSource(entryEle)); + value = ref; + } + else if (valueEle != null) { + value = parsePropertySubElement(valueEle, bd, defaultValueTypeClassName); + } + else { + error(" element must specify a value", entryEle); + } + + // Add final key and value to the Map. + map.put(key, value); + } + + return map; + } + + /** + * Build a typed String value Object for the given raw value. + * + * @see org.springframework.beans.factory.config.TypedStringValue + */ + protected final Object buildTypedStringValueForMap(String value, String defaultTypeClassName, Element entryEle) { + try { + return buildTypedStringValue(value, defaultTypeClassName, entryEle); + } + catch (ClassNotFoundException ex) { + error("Type class [" + defaultTypeClassName + "] not found for Map key/value type", entryEle, ex); + return value; + } + } + + /** + * Parse a key sub-element of a map element. + */ + public Object parseKeyElement(Element keyEle, BeanDefinition bd, String defaultKeyTypeClassName) { + NodeList nl = keyEle.getChildNodes(); + Element subElement = null; + for (int i = 0; i < nl.getLength(); i++) { + Node node = nl.item(i); + if (node instanceof Element) { + // Child element is what we're looking for. + if (subElement != null) { + error(" element must not contain more than one value sub-element", keyEle); + } + else { + subElement = (Element) node; + } + } + } + return parsePropertySubElement(subElement, bd, defaultKeyTypeClassName); + } + + /** + * Parse a props element. + */ + public Properties parsePropsElement(Element propsEle) { + ManagedProperties props = new ManagedProperties(); + props.setSource(extractSource(propsEle)); + props.setMergeEnabled(parseMergeAttribute(propsEle)); + + List propEles = DomUtils.getChildElementsByTagName(propsEle, PROP_ELEMENT); + for (Iterator it = propEles.iterator(); it.hasNext();) { + Element propEle = (Element) it.next(); + String key = propEle.getAttribute(KEY_ATTRIBUTE); + // Trim the text value to avoid unwanted whitespace + // caused by typical XML formatting. + String value = DomUtils.getTextValue(propEle).trim(); + + TypedStringValue keyHolder = new TypedStringValue(key); + keyHolder.setSource(extractSource(propEle)); + TypedStringValue valueHolder = new TypedStringValue(value); + valueHolder.setSource(extractSource(propEle)); + props.put(keyHolder, valueHolder); + } + + return props; + } + + /** + * Parse the merge attribute of a collection element, if any. + */ + public boolean parseMergeAttribute(Element collectionElement) { + String value = collectionElement.getAttribute(MERGE_ATTRIBUTE); + if (DEFAULT_VALUE.equals(value)) { + value = this.defaults.getMerge(); + } + return TRUE_VALUE.equals(value); + } + + public BeanDefinition parseCustomElement(Element ele) { + return parseCustomElement(ele, null); + } + + public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) { + String namespaceUri = ele.getNamespaceURI(); + NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); + if (handler == null) { + error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele); + return null; + } + return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); + } + + public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder) { + return decorateBeanDefinitionIfRequired(ele, definitionHolder, null); + } + + public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder, + BeanDefinition containingBd) { + + BeanDefinitionHolder finalDefinition = definitionHolder; + + // Decorate based on custom attributes first. + NamedNodeMap attributes = ele.getAttributes(); + for (int i = 0; i < attributes.getLength(); i++) { + Node node = attributes.item(i); + finalDefinition = decorateIfRequired(node, finalDefinition, containingBd); + } + + // Decorate based on custom nested elements. + NodeList children = ele.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + Node node = children.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE) { + finalDefinition = decorateIfRequired(node, finalDefinition, containingBd); + } + } + return finalDefinition; + } + + private BeanDefinitionHolder decorateIfRequired(Node node, BeanDefinitionHolder originalDef, + BeanDefinition containingBd) { + + String namespaceUri = node.getNamespaceURI(); + if (!isDefaultNamespace(namespaceUri)) { + NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); + if (handler != null) { + return handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd)); + } + else if (namespaceUri.startsWith("http://www.springframework.org/")) { + error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", node); + } + else { + // A custom namespace, not to be handled by Spring - maybe "xml:...". + if (logger.isDebugEnabled()) { + logger.debug("No Spring NamespaceHandler found for XML schema namespace [" + namespaceUri + "]"); + } + } + } + return originalDef; + } + + public boolean isDefaultNamespace(String namespaceUri) { + return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri)); + } + + private BeanDefinitionHolder parseNestedCustomElement(Element ele, BeanDefinition containingBd) { + BeanDefinition innerDefinition = parseCustomElement(ele, containingBd); + if (innerDefinition == null) { + error("Incorrect usage of element '" + ele.getNodeName() + "' in a nested manner. " + + "This tag cannot be used nested inside .", ele); + return null; + } + String id = ele.getNodeName() + BeanDefinitionReaderUtils.GENERATED_BEAN_NAME_SEPARATOR + + ObjectUtils.getIdentityHexString(innerDefinition); + if (logger.isDebugEnabled()) { + logger.debug("Using generated bean name [" + id + "] for nested custom element '" + ele.getNodeName() + "'"); + } + return new BeanDefinitionHolder(innerDefinition, id); + } +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/BeansDtdResolver.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/BeansDtdResolver.java new file mode 100644 index 0000000000..86766d90c6 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/BeansDtdResolver.java @@ -0,0 +1,90 @@ +/* + * Copyright 2002-2007 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.xml; + +import java.io.IOException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.xml.sax.EntityResolver; +import org.xml.sax.InputSource; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; + +/** + * EntityResolver implementation for the Spring beans DTD, + * to load the DTD from the Spring class path (or JAR file). + * + *

    Fetches "spring-beans-2.0.dtd" from the class path resource + * "/org/springframework/beans/factory/xml/spring-beans-2.0.dtd", + * no matter whether specified as some local URL that includes "spring-beans" + * in the DTD name or as "http://www.springframework.org/dtd/spring-beans-2.0.dtd". + * + * @author Juergen Hoeller + * @author Colin Sampaleanu + * @since 04.06.2003 + * @see ResourceEntityResolver + */ +public class BeansDtdResolver implements EntityResolver { + + private static final String DTD_EXTENSION = ".dtd"; + + private static final String[] DTD_NAMES = {"spring-beans-2.0", "spring-beans"}; + + private static final Log logger = LogFactory.getLog(BeansDtdResolver.class); + + + public InputSource resolveEntity(String publicId, String systemId) throws IOException { + if (logger.isTraceEnabled()) { + logger.trace("Trying to resolve XML entity with public ID [" + publicId + + "] and system ID [" + systemId + "]"); + } + if (systemId != null && systemId.endsWith(DTD_EXTENSION)) { + int lastPathSeparator = systemId.lastIndexOf("/"); + for (int i = 0; i < DTD_NAMES.length; ++i) { + int dtdNameStart = systemId.indexOf(DTD_NAMES[i]); + if (dtdNameStart > lastPathSeparator) { + String dtdFile = systemId.substring(dtdNameStart); + if (logger.isTraceEnabled()) { + logger.trace("Trying to locate [" + dtdFile + "] in Spring jar"); + } + try { + Resource resource = new ClassPathResource(dtdFile, getClass()); + InputSource source = new InputSource(resource.getInputStream()); + source.setPublicId(publicId); + source.setSystemId(systemId); + if (logger.isDebugEnabled()) { + logger.debug("Found beans DTD [" + systemId + "] in classpath: " + dtdFile); + } + return source; + } + catch (IOException ex) { + if (logger.isDebugEnabled()) { + logger.debug("Could not resolve beans DTD [" + systemId + "]: not found in class path", ex); + } + } + + } + } + } + + // Use the default behavior -> download from website or wherever. + return null; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/DefaultBeanDefinitionDocumentReader.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/DefaultBeanDefinitionDocumentReader.java new file mode 100644 index 0000000000..aeba1cdfcf --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/DefaultBeanDefinitionDocumentReader.java @@ -0,0 +1,282 @@ +/* + * Copyright 2002-2008 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.xml; + +import java.io.IOException; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.parsing.BeanComponentDefinition; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.ResourcePatternUtils; +import org.springframework.util.StringUtils; +import org.springframework.util.SystemPropertyUtils; +import org.springframework.util.xml.DomUtils; + +/** + * Default implementation of the {@link BeanDefinitionDocumentReader} interface. + * Reads bean definitions according to the "spring-beans" DTD and XSD format + * (Spring's default XML bean definition format). + * + *

    The structure, elements and attribute names of the required XML document + * are hard-coded in this class. (Of course a transform could be run if necessary + * to produce this format). <beans> doesn't need to be the root + * element of the XML document: This class will parse all bean definition elements + * in the XML file, not regarding the actual root element. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @author Rob Harrop + * @author Erik Wiersma + * @since 18.12.2003 + */ +public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader { + + public static final String BEAN_ELEMENT = BeanDefinitionParserDelegate.BEAN_ELEMENT; + + public static final String ALIAS_ELEMENT = "alias"; + + public static final String NAME_ATTRIBUTE = "name"; + + public static final String ALIAS_ATTRIBUTE = "alias"; + + public static final String IMPORT_ELEMENT = "import"; + + public static final String RESOURCE_ATTRIBUTE = "resource"; + + + protected final Log logger = LogFactory.getLog(getClass()); + + private XmlReaderContext readerContext; + + + /** + * Parses bean definitions according to the "spring-beans" DTD. + *

    Opens a DOM Document; then initializes the default settings + * specified at <beans> level; then parses + * the contained bean definitions. + */ + public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { + this.readerContext = readerContext; + + logger.debug("Loading bean definitions"); + Element root = doc.getDocumentElement(); + + BeanDefinitionParserDelegate delegate = createHelper(readerContext, root); + + preProcessXml(root); + parseBeanDefinitions(root, delegate); + postProcessXml(root); + } + + protected BeanDefinitionParserDelegate createHelper(XmlReaderContext readerContext, Element root) { + BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext); + delegate.initDefaults(root); + return delegate; + } + + /** + * Return the descriptor for the XML resource that this parser works on. + */ + protected final XmlReaderContext getReaderContext() { + return this.readerContext; + } + + /** + * Invoke the {@link org.springframework.beans.factory.parsing.SourceExtractor} to pull the + * source metadata from the supplied {@link Element}. + */ + protected Object extractSource(Element ele) { + return this.readerContext.extractSource(ele); + } + + + /** + * Parse the elements at the root level in the document: + * "import", "alias", "bean". + * @param root the DOM root element of the document + */ + protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { + if (delegate.isDefaultNamespace(root.getNamespaceURI())) { + NodeList nl = root.getChildNodes(); + for (int i = 0; i < nl.getLength(); i++) { + Node node = nl.item(i); + if (node instanceof Element) { + Element ele = (Element) node; + String namespaceUri = ele.getNamespaceURI(); + if (delegate.isDefaultNamespace(namespaceUri)) { + parseDefaultElement(ele, delegate); + } + else { + delegate.parseCustomElement(ele); + } + } + } + } + else { + delegate.parseCustomElement(root); + } + } + + private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { + if (DomUtils.nodeNameEquals(ele, IMPORT_ELEMENT)) { + importBeanDefinitionResource(ele); + } + else if (DomUtils.nodeNameEquals(ele, ALIAS_ELEMENT)) { + processAliasRegistration(ele); + } + else if (DomUtils.nodeNameEquals(ele, BEAN_ELEMENT)) { + processBeanDefinition(ele, delegate); + } + } + + /** + * Parse an "import" element and load the bean definitions + * from the given resource into the bean factory. + */ + protected void importBeanDefinitionResource(Element ele) { + String location = ele.getAttribute(RESOURCE_ATTRIBUTE); + if (!StringUtils.hasText(location)) { + getReaderContext().error("Resource location must not be empty", ele); + return; + } + + // Resolve system properties: e.g. "${user.dir}" + location = SystemPropertyUtils.resolvePlaceholders(location); + + if (ResourcePatternUtils.isUrl(location)) { + try { + Set actualResources = new LinkedHashSet(4); + int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources); + if (logger.isDebugEnabled()) { + logger.debug("Imported " + importCount + " bean definitions from URL location [" + location + "]"); + } + Resource[] actResArray = (Resource[]) actualResources.toArray(new Resource[actualResources.size()]); + getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele)); + } + catch (BeanDefinitionStoreException ex) { + getReaderContext().error( + "Failed to import bean definitions from URL location [" + location + "]", ele, ex); + } + } + else { + // No URL -> considering resource location as relative to the current file. + try { + Resource relativeResource = getReaderContext().getResource().createRelative(location); + int importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource); + if (logger.isDebugEnabled()) { + logger.debug("Imported " + importCount + " bean definitions from relative location [" + location + "]"); + } + getReaderContext().fireImportProcessed(location, new Resource[] {relativeResource}, extractSource(ele)); + } + catch (IOException ex) { + getReaderContext().error( + "Invalid relative resource location [" + location + "] to import bean definitions from", ele, ex); + } + catch (BeanDefinitionStoreException ex) { + getReaderContext().error( + "Failed to import bean definitions from relative location [" + location + "]", ele, ex); + } + } + } + + /** + * Process the given alias element, registering the alias with the registry. + */ + protected void processAliasRegistration(Element ele) { + String name = ele.getAttribute(NAME_ATTRIBUTE); + String alias = ele.getAttribute(ALIAS_ATTRIBUTE); + boolean valid = true; + if (!StringUtils.hasText(name)) { + getReaderContext().error("Name must not be empty", ele); + valid = false; + } + if (!StringUtils.hasText(alias)) { + getReaderContext().error("Alias must not be empty", ele); + valid = false; + } + if (valid) { + try { + getReaderContext().getRegistry().registerAlias(name, alias); + } + catch (Exception ex) { + getReaderContext().error("Failed to register alias '" + alias + + "' for bean with name '" + name + "'", ele, ex); + } + getReaderContext().fireAliasRegistered(name, alias, extractSource(ele)); + } + } + + /** + * Process the given bean element, parsing the bean definition + * and registering it with the registry. + */ + protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { + BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); + if (bdHolder != null) { + bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); + try { + // Register the final decorated instance. + BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); + } + catch (BeanDefinitionStoreException ex) { + getReaderContext().error("Failed to register bean definition with name '" + + bdHolder.getBeanName() + "'", ele, ex); + } + // Send registration event. + getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); + } + } + + + /** + * Allow the XML to be extensible by processing any custom element types first, + * before we start to process the bean definitions. This method is a natural + * extension point for any other custom pre-processing of the XML. + *

    The default implementation is empty. Subclasses can override this method to + * convert custom elements into standard Spring bean definitions, for example. + * Implementors have access to the parser's bean definition reader and the + * underlying XML resource, through the corresponding accessors. + * @see #getReaderContext() + */ + protected void preProcessXml(Element root) { + } + + /** + * Allow the XML to be extensible by processing any custom element types last, + * after we finished processing the bean definitions. This method is a natural + * extension point for any other custom post-processing of the XML. + *

    The default implementation is empty. Subclasses can override this method to + * convert custom elements into standard Spring bean definitions, for example. + * Implementors have access to the parser's bean definition reader and the + * underlying XML resource, through the corresponding accessors. + * @see #getReaderContext() + */ + protected void postProcessXml(Element root) { + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/DefaultDocumentLoader.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/DefaultDocumentLoader.java new file mode 100644 index 0000000000..d057cf7e51 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/DefaultDocumentLoader.java @@ -0,0 +1,140 @@ +/* + * Copyright 2002-2007 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.xml; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.w3c.dom.Document; +import org.xml.sax.EntityResolver; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; + +import org.springframework.util.xml.XmlValidationModeDetector; + +/** + * Spring's default {@link DocumentLoader} implementation. + * + *

    Simply loads {@link Document documents} using the standard JAXP-configured + * XML parser. If you want to change the {@link DocumentBuilder} that is used to + * load documents, then one strategy is to define a corresponding Java system property + * when starting your JVM. For example, to use the Oracle {@link DocumentBuilder}, + * you might start your application like as follows: + * + *

    java -Djavax.xml.parsers.DocumentBuilderFactory=oracle.xml.jaxp.JXDocumentBuilderFactory MyMainClass
    + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + */ +public class DefaultDocumentLoader implements DocumentLoader { + + /** + * JAXP attribute used to configure the schema language for validation. + */ + private static final String SCHEMA_LANGUAGE_ATTRIBUTE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage"; + + /** + * JAXP attribute value indicating the XSD schema language. + */ + private static final String XSD_SCHEMA_LANGUAGE = "http://www.w3.org/2001/XMLSchema"; + + + private static final Log logger = LogFactory.getLog(DefaultDocumentLoader.class); + + + /** + * Load the {@link Document} at the supplied {@link InputSource} using the standard JAXP-configured + * XML parser. + */ + public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, + ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception { + + DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware); + if (logger.isDebugEnabled()) { + logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]"); + } + DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler); + return builder.parse(inputSource); + } + + /** + * Create the {@link DocumentBuilderFactory} instance. + * @param validationMode the type of validation: {@link XmlValidationModeDetector#VALIDATION_DTD DTD} + * or {@link XmlValidationModeDetector#VALIDATION_XSD XSD}) + * @param namespaceAware whether the returned factory is to provide support for XML namespaces + * @return the JAXP DocumentBuilderFactory + * @throws ParserConfigurationException if we failed to build a proper DocumentBuilderFactory + */ + protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware) + throws ParserConfigurationException { + + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(namespaceAware); + + if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) { + factory.setValidating(true); + + if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) { + // Enforce namespace aware for XSD... + factory.setNamespaceAware(true); + try { + factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE); + } + catch (IllegalArgumentException ex) { + ParserConfigurationException pcex = new ParserConfigurationException( + "Unable to validate using XSD: Your JAXP provider [" + factory + + "] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " + + "Upgrade to Apache Xerces (or Java 1.5) for full XSD support."); + pcex.initCause(ex); + throw pcex; + } + } + } + + return factory; + } + + /** + * Create a JAXP DocumentBuilder that this bean definition reader + * will use for parsing XML documents. Can be overridden in subclasses, + * adding further initialization of the builder. + * @param factory the JAXP DocumentBuilderFactory that the DocumentBuilder + * should be created with + * @param entityResolver the SAX EntityResolver to use + * @param errorHandler the SAX ErrorHandler to use + * @return the JAXP DocumentBuilder + * @throws ParserConfigurationException if thrown by JAXP methods + */ + protected DocumentBuilder createDocumentBuilder( + DocumentBuilderFactory factory, EntityResolver entityResolver, ErrorHandler errorHandler) + throws ParserConfigurationException { + + DocumentBuilder docBuilder = factory.newDocumentBuilder(); + if (entityResolver != null) { + docBuilder.setEntityResolver(entityResolver); + } + if (errorHandler != null) { + docBuilder.setErrorHandler(errorHandler); + } + return docBuilder; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/DefaultNamespaceHandlerResolver.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/DefaultNamespaceHandlerResolver.java new file mode 100644 index 0000000000..e6653a66c9 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/DefaultNamespaceHandlerResolver.java @@ -0,0 +1,167 @@ +/* + * Copyright 2002-2008 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.xml; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.FatalBeanException; +import org.springframework.core.io.support.PropertiesLoaderUtils; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * Default implementation of the {@link NamespaceHandlerResolver} interface. + * Resolves namespace URIs to implementation classes based on the mappings + * contained in mapping file. + * + *

    By default, this implementation looks for the mapping file at + * META-INF/spring.handlers, but this can be changed using the + * {@link #DefaultNamespaceHandlerResolver(ClassLoader, String)} constructor. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + * @see NamespaceHandler + * @see DefaultBeanDefinitionDocumentReader + */ +public class DefaultNamespaceHandlerResolver implements NamespaceHandlerResolver { + + /** + * The location to look for the mapping files. Can be present in multiple JAR files. + */ + public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers"; + + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + /** ClassLoader to use for NamespaceHandler classes */ + private final ClassLoader classLoader; + + /** Resource location to search for */ + private final String handlerMappingsLocation; + + /** Stores the mappings from namespace URI to NamespaceHandler class name / instance */ + private Map handlerMappings; + + + /** + * Create a new DefaultNamespaceHandlerResolver using the + * default mapping file location. + *

    This constructor will result in the thread context ClassLoader being used + * to load resources. + * @see #DEFAULT_HANDLER_MAPPINGS_LOCATION + */ + public DefaultNamespaceHandlerResolver() { + this(null, DEFAULT_HANDLER_MAPPINGS_LOCATION); + } + + /** + * Create a new DefaultNamespaceHandlerResolver using the + * default mapping file location. + * @param classLoader the {@link ClassLoader} instance used to load mapping resources + * (may be null, in which case the thread context ClassLoader will be used) + * @see #DEFAULT_HANDLER_MAPPINGS_LOCATION + */ + public DefaultNamespaceHandlerResolver(ClassLoader classLoader) { + this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION); + } + + /** + * Create a new DefaultNamespaceHandlerResolver using the + * supplied mapping file location. + * @param classLoader the {@link ClassLoader} instance used to load mapping resources + * may be null, in which case the thread context ClassLoader will be used) + * @param handlerMappingsLocation the mapping file location + */ + public DefaultNamespaceHandlerResolver(ClassLoader classLoader, String handlerMappingsLocation) { + Assert.notNull(handlerMappingsLocation, "Handler mappings location must not be null"); + this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader()); + this.handlerMappingsLocation = handlerMappingsLocation; + } + + + /** + * Locate the {@link NamespaceHandler} for the supplied namespace URI + * from the configured mappings. + * @param namespaceUri the relevant namespace URI + * @return the located {@link NamespaceHandler}, or null if none found + */ + public NamespaceHandler resolve(String namespaceUri) { + Map handlerMappings = getHandlerMappings(); + Object handlerOrClassName = handlerMappings.get(namespaceUri); + if (handlerOrClassName == null) { + return null; + } + else if (handlerOrClassName instanceof NamespaceHandler) { + return (NamespaceHandler) handlerOrClassName; + } + else { + String className = (String) handlerOrClassName; + try { + Class handlerClass = ClassUtils.forName(className, this.classLoader); + if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) { + throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri + + "] does not implement the [" + NamespaceHandler.class.getName() + "] interface"); + } + NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass); + namespaceHandler.init(); + handlerMappings.put(namespaceUri, namespaceHandler); + return namespaceHandler; + } + catch (ClassNotFoundException ex) { + throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" + + namespaceUri + "] not found", ex); + } + catch (LinkageError err) { + throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" + + namespaceUri + "]: problem with handler class file or dependent class", err); + } + } + } + + /** + * Load the specified NamespaceHandler mappings lazily. + */ + private Map getHandlerMappings() { + if (this.handlerMappings == null) { + try { + Properties mappings = + PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader); + if (logger.isDebugEnabled()) { + logger.debug("Loaded mappings [" + mappings + "]"); + } + this.handlerMappings = new HashMap(mappings); + } + catch (IOException ex) { + IllegalStateException ise = new IllegalStateException( + "Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]"); + ise.initCause(ex); + throw ise; + } + } + return this.handlerMappings; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/DelegatingEntityResolver.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/DelegatingEntityResolver.java new file mode 100644 index 0000000000..d39b0b060c --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/DelegatingEntityResolver.java @@ -0,0 +1,91 @@ +/* + * Copyright 2002-2007 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.xml; + +import java.io.IOException; + +import org.xml.sax.EntityResolver; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import org.springframework.util.Assert; + +/** + * {@link EntityResolver} implementation that delegates to a {@link BeansDtdResolver} + * and a {@link PluggableSchemaResolver} for DTDs and XML schemas, respectively. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @author Rick Evans + * @since 2.0 + * @see BeansDtdResolver + * @see PluggableSchemaResolver + */ +public class DelegatingEntityResolver implements EntityResolver { + + /** Suffix for DTD files */ + public static final String DTD_SUFFIX = ".dtd"; + + /** Suffix for schema definition files */ + public static final String XSD_SUFFIX = ".xsd"; + + + private final EntityResolver dtdResolver; + + private final EntityResolver schemaResolver; + + + /** + * Create a new DelegatingEntityResolver that delegates to + * a default {@link BeansDtdResolver} and a default {@link PluggableSchemaResolver}. + *

    Configures the {@link PluggableSchemaResolver} with the supplied + * {@link ClassLoader}. + * @param classLoader the ClassLoader to use for loading + * (can be null) to use the default ClassLoader) + */ + public DelegatingEntityResolver(ClassLoader classLoader) { + this.dtdResolver = new BeansDtdResolver(); + this.schemaResolver = new PluggableSchemaResolver(classLoader); + } + + /** + * Create a new DelegatingEntityResolver that delegates to + * the given {@link EntityResolver EntityResolvers}. + * @param dtdResolver the EntityResolver to resolve DTDs with + * @param schemaResolver the EntityResolver to resolve XML schemas with + */ + public DelegatingEntityResolver(EntityResolver dtdResolver, EntityResolver schemaResolver) { + Assert.notNull(dtdResolver, "'dtdResolver' is required"); + Assert.notNull(schemaResolver, "'schemaResolver' is required"); + this.dtdResolver = dtdResolver; + this.schemaResolver = schemaResolver; + } + + + public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { + if (systemId != null) { + if (systemId.endsWith(DTD_SUFFIX)) { + return this.dtdResolver.resolveEntity(publicId, systemId); + } + else if (systemId.endsWith(XSD_SUFFIX)) { + return this.schemaResolver.resolveEntity(publicId, systemId); + } + } + return null; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/DocumentDefaultsDefinition.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/DocumentDefaultsDefinition.java new file mode 100644 index 0000000000..3c53f939e3 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/DocumentDefaultsDefinition.java @@ -0,0 +1,160 @@ +/* + * Copyright 2002-2007 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.xml; + +import org.springframework.beans.factory.parsing.DefaultsDefinition; + +/** + * Simple JavaBean that holds the defaults specified at the %lt;beans> + * level in a standard Spring XML bean definition document: + * default-lazy-init, default-autowire, etc + * + * @author Juergen Hoeller + * @since 2.0.2 + */ +public class DocumentDefaultsDefinition implements DefaultsDefinition { + + private String lazyInit; + + private String merge; + + private String autowire; + + private String dependencyCheck; + + private String autowireCandidates; + + private String initMethod; + + private String destroyMethod; + + private Object source; + + + /** + * Set the default lazy-init flag for the document that's currently parsed. + */ + public void setLazyInit(String lazyInit) { + this.lazyInit = lazyInit; + } + + /** + * Return the default lazy-init flag for the document that's currently parsed. + */ + public String getLazyInit() { + return this.lazyInit; + } + + /** + * Set the default merge setting for the document that's currently parsed. + */ + public void setMerge(String merge) { + this.merge = merge; + } + + /** + * Return the default merge setting for the document that's currently parsed. + */ + public String getMerge() { + return this.merge; + } + + /** + * Set the default autowire setting for the document that's currently parsed. + */ + public void setAutowire(String autowire) { + this.autowire = autowire; + } + + /** + * Return the default autowire setting for the document that's currently parsed. + */ + public String getAutowire() { + return this.autowire; + } + + /** + * Set the default dependency-check setting for the document that's currently parsed. + */ + public void setDependencyCheck(String dependencyCheck) { + this.dependencyCheck = dependencyCheck; + } + + /** + * Return the default dependency-check setting for the document that's currently parsed. + */ + public String getDependencyCheck() { + return this.dependencyCheck; + } + + /** + * Set the default autowire-candidate pattern for the document that's currently parsed. + * Also accepts a comma-separated list of patterns. + */ + public void setAutowireCandidates(String autowireCandidates) { + this.autowireCandidates = autowireCandidates; + } + + /** + * Return the default autowire-candidate pattern for the document that's currently parsed. + * May also return a comma-separated list of patterns. + */ + public String getAutowireCandidates() { + return this.autowireCandidates; + } + + /** + * Set the default init-method setting for the document that's currently parsed. + */ + public void setInitMethod(String initMethod) { + this.initMethod = initMethod; + } + + /** + * Return the default init-method setting for the document that's currently parsed. + */ + public String getInitMethod() { + return this.initMethod; + } + + /** + * Set the default destroy-method setting for the document that's currently parsed. + */ + public void setDestroyMethod(String destroyMethod) { + this.destroyMethod = destroyMethod; + } + + /** + * Return the default destroy-method setting for the document that's currently parsed. + */ + public String getDestroyMethod() { + return this.destroyMethod; + } + + /** + * Set the configuration source Object for this metadata element. + *

    The exact type of the object will depend on the configuration mechanism used. + */ + public void setSource(Object source) { + this.source = source; + } + + public Object getSource() { + return this.source; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/DocumentLoader.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/DocumentLoader.java new file mode 100644 index 0000000000..778483ce3f --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/DocumentLoader.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2007 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.xml; + +import org.w3c.dom.Document; +import org.xml.sax.EntityResolver; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; + +/** + * Strategy interface for loading an XML {@link Document}. + * + * @author Rob Harrop + * @since 2.0 + * @see DefaultDocumentLoader + */ +public interface DocumentLoader { + + /** + * Load a {@link Document document} from the supplied {@link InputSource source}. + * @param inputSource the source of the document that is to be loaded + * @param entityResolver the resolver that is to be used to resolve any entities + * @param errorHandler used to report any errors during document loading + * @param validationMode the type of validation + * {@link org.springframework.util.xml.XmlValidationModeDetector#VALIDATION_DTD DTD} + * or {@link org.springframework.util.xml.XmlValidationModeDetector#VALIDATION_XSD XSD}) + * @param namespaceAware true if the loading is provide support for XML namespaces + * @return the loaded {@link Document document} + * @throws Exception if an error occurs + */ + Document loadDocument( + InputSource inputSource, EntityResolver entityResolver, + ErrorHandler errorHandler, int validationMode, boolean namespaceAware) + throws Exception; + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/NamespaceHandler.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/NamespaceHandler.java new file mode 100644 index 0000000000..a6553b1ce4 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/NamespaceHandler.java @@ -0,0 +1,94 @@ +/* + * Copyright 2002-2007 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.xml; + +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; + +/** + * Base interface used by the {@link DefaultBeanDefinitionDocumentReader} + * for handling custom namespaces in a Spring XML configuration file. + * + *

    Implementations are expected to return implementations of the + * {@link BeanDefinitionParser} interface for custom top-level tags and + * implementations of the {@link BeanDefinitionDecorator} interface for + * custom nested tags. + * + *

    The parser will call {@link #parse} when it encounters a custom tag + * directly under the <beans> tags and {@link #decorate} when + * it encounters a custom tag directly under a <bean> tag. + * + *

    Developers writing their own custom element extensions typically will + * not implement this interface drectly, but rather make use of the provided + * {@link NamespaceHandlerSupport} class. + * + * @author Rob Harrop + * @author Erik Wiersma + * @since 2.0 + * @see DefaultBeanDefinitionDocumentReader + * @see NamespaceHandlerResolver + */ +public interface NamespaceHandler { + + /** + * Invoked by the {@link DefaultBeanDefinitionDocumentReader} after + * construction but before any custom elements are parsed. + * @see NamespaceHandlerSupport#registerBeanDefinitionParser(String, BeanDefinitionParser) + */ + void init(); + + /** + * Parse the specified {@link Element} and register any resulting + * {@link BeanDefinition BeanDefinitions} with the + * {@link org.springframework.beans.factory.support.BeanDefinitionRegistry} + * that is embedded in the supplied {@link ParserContext}. + *

    Implementations should return the primary BeanDefinition + * that results from the parse phase if they wish to be used nested + * inside (for example) a <property> tag. + *

    Implementations may return null if they will + * not be used in a nested scenario. + * @param element the element that is to be parsed into one or more BeanDefinitions + * @param parserContext the object encapsulating the current state of the parsing process + * @return the primary BeanDefinition (can be null as explained above) + */ + BeanDefinition parse(Element element, ParserContext parserContext); + + /** + * Parse the specified {@link Node} and decorate the supplied + * {@link BeanDefinitionHolder}, returning the decorated definition. + *

    The {@link Node} may be either an {@link org.w3c.dom.Attr} or an + * {@link Element}, depending on whether a custom attribute or element + * is being parsed. + *

    Implementations may choose to return a completely new definition, + * which will replace the original definition in the resulting + * {@link org.springframework.beans.factory.BeanFactory}. + *

    The supplied {@link ParserContext} can be used to register any + * additional beans needed to support the main definition. + * @param source the source element or attribute that is to be parsed + * @param definition the current bean definition + * @param parserContext the object encapsulating the current state of the parsing process + * @return the decorated definition (to be registered in the BeanFactory), + * or simply the original bean definition if no decoration is required. + * A null value is strictly speaking invalid, but will be leniently + * treated like the case where the original bean definition gets returned. + */ + BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder definition, ParserContext parserContext); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/NamespaceHandlerResolver.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/NamespaceHandlerResolver.java new file mode 100644 index 0000000000..ba9d173b1d --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/NamespaceHandlerResolver.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2006 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.xml; + +/** + * Used by the {@link org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader} to + * locate a {@link NamespaceHandler} implementation for a particular namespace URI. + * + * @author Rob Harrop + * @since 2.0 + * @see NamespaceHandler + * @see org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader + */ +public interface NamespaceHandlerResolver { + + /** + * Resolve the namespace URI and return the located {@link NamespaceHandler} + * implementation. + * @param namespaceUri the relevant namespace URI + * @return the located {@link NamespaceHandler} (may be null) + */ + NamespaceHandler resolve(String namespaceUri); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/NamespaceHandlerSupport.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/NamespaceHandlerSupport.java new file mode 100644 index 0000000000..360b05cb1d --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/NamespaceHandlerSupport.java @@ -0,0 +1,188 @@ +/* + * Copyright 2002-2007 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.xml; + +import java.util.HashMap; +import java.util.Map; + +import org.w3c.dom.Attr; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; + +/** + * Support class for implementing custom {@link NamespaceHandler NamespaceHandlers}. Parsing and + * decorating of individual {@link Node Nodes} is done via {@link BeanDefinitionParser} and + * {@link BeanDefinitionDecorator} strategy interfaces respectively. Provides the + * {@link #registerBeanDefinitionParser}, {@link #registerBeanDefinitionDecorator} methods + * for registering a {@link BeanDefinitionParser} or {@link BeanDefinitionDecorator} to handle + * a specific element. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + * @see #registerBeanDefinitionParser(String, BeanDefinitionParser) + * @see #registerBeanDefinitionDecorator(String, BeanDefinitionDecorator) + */ +public abstract class NamespaceHandlerSupport implements NamespaceHandler { + + /** + * Stores the {@link BeanDefinitionParser} implementations keyed by the + * local name of the {@link Element Elements} they handle. + */ + private final Map parsers = new HashMap(); + + /** + * Stores the {@link BeanDefinitionDecorator} implementations keyed by the + * local name of the {@link Element Elements} they handle. + */ + private final Map decorators = new HashMap(); + + /** + * Stores the {@link BeanDefinitionParser} implementations keyed by the local + * name of the {@link Attr Attrs} they handle. + */ + private final Map attributeDecorators = new HashMap(); + + + /** + * Parses the supplied {@link Element} by delegating to the {@link BeanDefinitionParser} that is + * registered for that {@link Element}. + */ + public final BeanDefinition parse(Element element, ParserContext parserContext) { + return findParserForElement(element, parserContext).parse(element, parserContext); + } + + /** + * Locates the {@link BeanDefinitionParser} from the register implementations using + * the local name of the supplied {@link Element}. + */ + private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) { + BeanDefinitionParser parser = (BeanDefinitionParser) this.parsers.get(element.getLocalName()); + if (parser == null) { + parserContext.getReaderContext().fatal( + "Cannot locate BeanDefinitionParser for element [" + element.getLocalName() + "]", element); + } + return parser; + } + + /** + * Locate the {@link BeanDefinitionParser} from the register implementations using + * the local name of the supplied {@link Element}. + * @deprecated as of Spring 2.0.2; there should be no need to call this directly. + */ + protected final BeanDefinitionParser findParserForElement(Element element) { + BeanDefinitionParser parser = (BeanDefinitionParser) this.parsers.get(element.getLocalName()); + if (parser == null) { + throw new IllegalStateException( + "Cannot locate BeanDefinitionParser for element [" + element.getLocalName() + "]"); + } + return parser; + } + + /** + * Decorates the supplied {@link Node} by delegating to the {@link BeanDefinitionDecorator} that + * is registered to handle that {@link Node}. + */ + public final BeanDefinitionHolder decorate( + Node node, BeanDefinitionHolder definition, ParserContext parserContext) { + + return findDecoratorForNode(node, parserContext).decorate(node, definition, parserContext); + } + + /** + * Locates the {@link BeanDefinitionParser} from the register implementations using + * the local name of the supplied {@link Node}. Supports both {@link Element Elements} + * and {@link Attr Attrs}. + */ + private BeanDefinitionDecorator findDecoratorForNode(Node node, ParserContext parserContext) { + BeanDefinitionDecorator decorator = null; + if (node instanceof Element) { + decorator = (BeanDefinitionDecorator) this.decorators.get(node.getLocalName()); + } + else if (node instanceof Attr) { + decorator = (BeanDefinitionDecorator) this.attributeDecorators.get(node.getLocalName()); + } + else { + parserContext.getReaderContext().fatal( + "Cannot decorate based on Nodes of type [" + node.getClass().getName() + "]", node); + } + if (decorator == null) { + parserContext.getReaderContext().fatal("Cannot locate BeanDefinitionDecorator for " + + (node instanceof Element ? "element" : "attribute") + " [" + node.getLocalName() + "]", node); + } + return decorator; + } + + /** + * Locate the {@link BeanDefinitionParser} from the register implementations using + * the local name of the supplied {@link Node}. Supports both {@link Element Elements} + * and {@link Attr Attrs}. + * @deprecated as of Spring 2.0.2; there should be no need to call this directly. + */ + protected final BeanDefinitionDecorator findDecoratorForNode(Node node) { + BeanDefinitionDecorator decorator = null; + if (node instanceof Element) { + decorator = (BeanDefinitionDecorator) this.decorators.get(node.getLocalName()); + } + else if (node instanceof Attr) { + decorator = (BeanDefinitionDecorator) this.attributeDecorators.get(node.getLocalName()); + } + else { + throw new IllegalStateException( + "Cannot decorate based on Nodes of type [" + node.getClass().getName() + "]"); + } + if (decorator == null) { + throw new IllegalStateException("Cannot locate BeanDefinitionDecorator for " + + (node instanceof Element ? "element" : "attribute") + " [" + node.getLocalName() + "]"); + } + return decorator; + } + + + /** + * Subclasses can call this to register the supplied {@link BeanDefinitionParser} to + * handle the specified element. The element name is the local (non-namespace qualified) + * name. + */ + protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) { + this.parsers.put(elementName, parser); + } + + /** + * Subclasses can call this to register the supplied {@link BeanDefinitionDecorator} to + * handle the specified element. The element name is the local (non-namespace qualified) + * name. + */ + protected final void registerBeanDefinitionDecorator(String elementName, BeanDefinitionDecorator decorator) { + this.decorators.put(elementName, decorator); + } + + /** + * Subclasses can call this to register the supplied {@link BeanDefinitionDecorator} to + * handle the specified attribute. The attribute name is the local (non-namespace qualified) + * name. + */ + protected final void registerBeanDefinitionDecoratorForAttribute( + String attributeName, BeanDefinitionDecorator decorator) { + + this.attributeDecorators.put(attributeName, decorator); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/ParserContext.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/ParserContext.java new file mode 100644 index 0000000000..7d982d5dd9 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/ParserContext.java @@ -0,0 +1,124 @@ +/* + * Copyright 2002-2007 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.xml; + +import java.util.Stack; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.parsing.BeanComponentDefinition; +import org.springframework.beans.factory.parsing.ComponentDefinition; +import org.springframework.beans.factory.parsing.CompositeComponentDefinition; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; + +/** + * Context that gets passed along a bean definition parsing process, + * encapsulating all relevant configuration as well as state. + * Nested inside an {@link XmlReaderContext}. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + * @see XmlReaderContext + * @see BeanDefinitionParserDelegate + */ +public final class ParserContext { + + private final XmlReaderContext readerContext; + + private final BeanDefinitionParserDelegate delegate; + + private BeanDefinition containingBeanDefinition; + + private final Stack containingComponents = new Stack(); + + + public ParserContext(XmlReaderContext readerContext, BeanDefinitionParserDelegate delegate) { + this.readerContext = readerContext; + this.delegate = delegate; + } + + public ParserContext(XmlReaderContext readerContext, BeanDefinitionParserDelegate delegate, + BeanDefinition containingBeanDefinition) { + + this.readerContext = readerContext; + this.delegate = delegate; + this.containingBeanDefinition = containingBeanDefinition; + } + + + public final XmlReaderContext getReaderContext() { + return this.readerContext; + } + + public final BeanDefinitionRegistry getRegistry() { + return this.readerContext.getRegistry(); + } + + public final BeanDefinitionParserDelegate getDelegate() { + return this.delegate; + } + + public final BeanDefinition getContainingBeanDefinition() { + return this.containingBeanDefinition; + } + + public final boolean isNested() { + return (this.containingBeanDefinition != null); + } + + public boolean isDefaultLazyInit() { + return BeanDefinitionParserDelegate.TRUE_VALUE.equals(this.delegate.getDefaults().getLazyInit()); + } + + public Object extractSource(Object sourceCandidate) { + return this.readerContext.extractSource(sourceCandidate); + } + + public CompositeComponentDefinition getContainingComponent() { + return (!this.containingComponents.isEmpty() ? + (CompositeComponentDefinition) this.containingComponents.lastElement() : null); + } + + public void pushContainingComponent(CompositeComponentDefinition containingComponent) { + this.containingComponents.push(containingComponent); + } + + public CompositeComponentDefinition popContainingComponent() { + return (CompositeComponentDefinition) this.containingComponents.pop(); + } + + public void popAndRegisterContainingComponent() { + registerComponent(popContainingComponent()); + } + + public void registerComponent(ComponentDefinition component) { + CompositeComponentDefinition containingComponent = getContainingComponent(); + if (containingComponent != null) { + containingComponent.addNestedComponent(component); + } + else { + this.readerContext.fireComponentRegistered(component); + } + } + + public void registerBeanComponent(BeanComponentDefinition component) { + BeanDefinitionReaderUtils.registerBeanDefinition(component, getRegistry()); + registerComponent(component); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/PluggableSchemaResolver.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/PluggableSchemaResolver.java new file mode 100644 index 0000000000..72468e85e9 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/PluggableSchemaResolver.java @@ -0,0 +1,142 @@ +/* + * Copyright 2002-2007 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.xml; + +import java.io.IOException; +import java.util.Properties; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.xml.sax.EntityResolver; +import org.xml.sax.InputSource; + +import org.springframework.beans.FatalBeanException; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PropertiesLoaderUtils; +import org.springframework.util.Assert; + +/** + * {@link EntityResolver} implementation that attempts to resolve schema URLs into + * local {@link ClassPathResource classpath resources} using a set of mappings files. + * + *

    By default, this class will look for mapping files in the classpath using the pattern: + * META-INF/spring.schemas allowing for multiple files to exist on the + * classpath at any one time. + * + * The format of META-INF/spring.schemas is a properties + * file where each line should be of the form systemId=schema-location + * where schema-location should also be a schema file in the classpath. + * Since systemId is commonly a URL, one must be careful to escape any ':' characters + * which are treated as delimiters in properties files. + * + *

    The pattern for the mapping files can be overidden using the + * {@link #PluggableSchemaResolver(ClassLoader, String)} constructor + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + */ +public class PluggableSchemaResolver implements EntityResolver { + + /** + * The location of the file that defines schema mappings. + * Can be present in multiple JAR files. + */ + public static final String DEFAULT_SCHEMA_MAPPINGS_LOCATION = "META-INF/spring.schemas"; + + + private static final Log logger = LogFactory.getLog(PluggableSchemaResolver.class); + + private final ClassLoader classLoader; + + private final String schemaMappingsLocation; + + /** Stores the mapping of schema URL -> local schema path */ + private Properties schemaMappings; + + + /** + * Loads the schema URL -> schema file location mappings using the default + * mapping file pattern "META-INF/spring.schemas". + * @param classLoader the ClassLoader to use for loading + * (can be null) to use the default ClassLoader) + * @see PropertiesLoaderUtils#loadAllProperties(String, ClassLoader) + */ + public PluggableSchemaResolver(ClassLoader classLoader) { + this.classLoader = classLoader; + this.schemaMappingsLocation = DEFAULT_SCHEMA_MAPPINGS_LOCATION; + } + + /** + * Loads the schema URL -> schema file location mappings using the given + * mapping file pattern. + * @param classLoader the ClassLoader to use for loading + * (can be null) to use the default ClassLoader) + * @param schemaMappingsLocation the location of the file that defines schema mappings + * (must not be empty) + * @see PropertiesLoaderUtils#loadAllProperties(String, ClassLoader) + */ + public PluggableSchemaResolver(ClassLoader classLoader, String schemaMappingsLocation) { + Assert.hasText(schemaMappingsLocation, "'schemaMappingsLocation' must not be empty"); + this.classLoader = classLoader; + this.schemaMappingsLocation = schemaMappingsLocation; + } + + + public InputSource resolveEntity(String publicId, String systemId) throws IOException { + if (logger.isTraceEnabled()) { + logger.trace("Trying to resolve XML entity with public id [" + publicId + + "] and system id [" + systemId + "]"); + } + if (systemId != null) { + String resourceLocation = getSchemaMapping(systemId); + if (resourceLocation != null) { + Resource resource = new ClassPathResource(resourceLocation, this.classLoader); + InputSource source = new InputSource(resource.getInputStream()); + source.setPublicId(publicId); + source.setSystemId(systemId); + if (logger.isDebugEnabled()) { + logger.debug("Found XML schema [" + systemId + "] in classpath: " + resourceLocation); + } + return source; + } + } + return null; + } + + protected String getSchemaMapping(String systemId) { + if (this.schemaMappings == null) { + if (logger.isDebugEnabled()) { + logger.debug("Loading schema mappings from [" + this.schemaMappingsLocation + "]"); + } + try { + this.schemaMappings = + PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader); + if (logger.isDebugEnabled()) { + logger.debug("Loaded schema mappings: " + this.schemaMappings); + } + } + catch (IOException ex) { + throw new FatalBeanException( + "Unable to load schema mappings from location [" + this.schemaMappingsLocation + "]", ex); + } + } + return this.schemaMappings.getProperty(systemId); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/ResourceEntityResolver.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/ResourceEntityResolver.java new file mode 100644 index 0000000000..78f0e04636 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/ResourceEntityResolver.java @@ -0,0 +1,109 @@ +/* + * Copyright 2002-2007 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.xml; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.net.URLDecoder; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; + +/** + * EntityResolver implementation that tries to resolve entity references + * through a {@link org.springframework.core.io.ResourceLoader} (usually, + * relative to the resource base of an ApplicationContext), if applicable. + * Extends {@link DelegatingEntityResolver} to also provide DTD and XSD lookup. + * + *

    Allows to use standard XML entities to include XML snippets into an + * application context definition, for example to split a large XML file + * into various modules. The include paths can be relative to the + * application context's resource base as usual, instead of relative + * to the JVM working directory (the XML parser's default). + * + *

    Note: In addition to relative paths, every URL that specifies a + * file in the current system root, i.e. the JVM working directory, + * will be interpreted relative to the application context too. + * + * @author Juergen Hoeller + * @since 31.07.2003 + * @see org.springframework.core.io.ResourceLoader + * @see org.springframework.context.ApplicationContext + */ +public class ResourceEntityResolver extends DelegatingEntityResolver { + + private static final Log logger = LogFactory.getLog(ResourceEntityResolver.class); + + private final ResourceLoader resourceLoader; + + + /** + * Create a ResourceEntityResolver for the specified ResourceLoader + * (usually, an ApplicationContext). + * @param resourceLoader the ResourceLoader (or ApplicationContext) + * to load XML entity includes with + */ + public ResourceEntityResolver(ResourceLoader resourceLoader) { + super(resourceLoader.getClassLoader()); + this.resourceLoader = resourceLoader; + } + + + public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { + InputSource source = super.resolveEntity(publicId, systemId); + if (source == null && systemId != null) { + String resourcePath = null; + try { + String decodedSystemId = URLDecoder.decode(systemId); + String givenUrl = new URL(decodedSystemId).toString(); + String systemRootUrl = new File("").toURL().toString(); + // Try relative to resource base if currently in system root. + if (givenUrl.startsWith(systemRootUrl)) { + resourcePath = givenUrl.substring(systemRootUrl.length()); + } + } + catch (Exception ex) { + // Typically a MalformedURLException or AccessControlException. + if (logger.isDebugEnabled()) { + logger.debug("Could not resolve XML entity [" + systemId + "] against system root URL", ex); + } + // No URL (or no resolvable URL) -> try relative to resource base. + resourcePath = systemId; + } + if (resourcePath != null) { + if (logger.isTraceEnabled()) { + logger.trace("Trying to locate XML entity [" + systemId + "] as resource [" + resourcePath + "]"); + } + Resource resource = this.resourceLoader.getResource(resourcePath); + source = new InputSource(resource.getInputStream()); + source.setPublicId(publicId); + source.setSystemId(systemId); + if (logger.isDebugEnabled()) { + logger.debug("Found XML entity [" + systemId + "]: " + resource); + } + } + } + return source; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/SimplePropertyNamespaceHandler.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/SimplePropertyNamespaceHandler.java new file mode 100644 index 0000000000..8516d37a88 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/SimplePropertyNamespaceHandler.java @@ -0,0 +1,85 @@ +/* + * Copyright 2002-2007 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.xml; + +import org.w3c.dom.Attr; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.core.Conventions; + +/** + * Simple NamespaceHandler implementation that maps custom attributes + * directly through to bean properties. An important point to note is that this + * NamespaceHandler does not have a corresponding schema since there + * is no way to know in advance all possible attribute names. + * + *

    An example of the usage of this NamespaceHandler is shown below: + * + *

    + * <bean id="rob" class="..TestBean" p:name="Rob Harrop" p:spouse-ref="sally"/>
    + * + * Here the 'p:name' corresponds directly to the 'name' + * property on class 'TestBean'. The 'p:spouse-ref' + * attributes corresponds to the 'spouse' property and, rather + * than being the concrete value, it contains the name of the bean that will + * be injected into that property. + * + * @author Rob Harrop + * @since 2.0 + */ +public class SimplePropertyNamespaceHandler implements NamespaceHandler { + + private static final String REF_SUFFIX = "-ref"; + + + public void init() { + } + + public BeanDefinition parse(Element element, ParserContext parserContext) { + parserContext.getReaderContext().error( + "Class [" + getClass().getName() + "] does not support custom elements.", element); + return null; + } + + public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) { + if (node instanceof Attr) { + Attr attr = (Attr) node; + String propertyName = attr.getLocalName(); + String propertyValue = attr.getValue(); + MutablePropertyValues pvs = definition.getBeanDefinition().getPropertyValues(); + if (pvs.contains(propertyName)) { + parserContext.getReaderContext().error("Property '" + propertyName + "' is already defined using " + + "both and inline syntax. Only one approach may be used per property.", attr); + } + if (propertyName.endsWith(REF_SUFFIX)) { + propertyName = propertyName.substring(0, propertyName.length() - REF_SUFFIX.length()); + pvs.addPropertyValue( + Conventions.attributeNameToPropertyName(propertyName), new RuntimeBeanReference(propertyValue)); + } + else { + pvs.addPropertyValue(Conventions.attributeNameToPropertyName(propertyName), propertyValue); + } + } + return definition; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/UtilNamespaceHandler.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/UtilNamespaceHandler.java new file mode 100644 index 0000000000..c43d083b37 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/UtilNamespaceHandler.java @@ -0,0 +1,192 @@ +/* + * Copyright 2002-2007 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.xml; + +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import org.w3c.dom.Element; + +import org.springframework.beans.factory.config.FieldRetrievingFactoryBean; +import org.springframework.beans.factory.config.ListFactoryBean; +import org.springframework.beans.factory.config.MapFactoryBean; +import org.springframework.beans.factory.config.PropertiesFactoryBean; +import org.springframework.beans.factory.config.PropertyPathFactoryBean; +import org.springframework.beans.factory.config.SetFactoryBean; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.util.StringUtils; + +/** + * {@link NamespaceHandler} for the util namespace. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + */ +public class UtilNamespaceHandler extends NamespaceHandlerSupport { + + private static final String SCOPE_ATTRIBUTE = "scope"; + + + public void init() { + registerBeanDefinitionParser("constant", new ConstantBeanDefinitionParser()); + registerBeanDefinitionParser("property-path", new PropertyPathBeanDefinitionParser()); + registerBeanDefinitionParser("list", new ListBeanDefinitionParser()); + registerBeanDefinitionParser("set", new SetBeanDefinitionParser()); + registerBeanDefinitionParser("map", new MapBeanDefinitionParser()); + registerBeanDefinitionParser("properties", new PropertiesBeanDefinitionParser()); + } + + + private static class ConstantBeanDefinitionParser extends AbstractSimpleBeanDefinitionParser { + + protected Class getBeanClass(Element element) { + return FieldRetrievingFactoryBean.class; + } + + protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) { + String id = super.resolveId(element, definition, parserContext); + if (!StringUtils.hasText(id)) { + id = element.getAttribute("static-field"); + } + return id; + } + } + + + private static class PropertyPathBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { + + protected Class getBeanClass(Element element) { + return PropertyPathFactoryBean.class; + } + + protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { + String path = element.getAttribute("path"); + if (!StringUtils.hasText(path)) { + parserContext.getReaderContext().error("Attribute 'path' must not be empty", element); + return; + } + int dotIndex = path.indexOf("."); + if (dotIndex == -1) { + parserContext.getReaderContext().error( + "Attribute 'path' must follow pattern 'beanName.propertyName'", element); + return; + } + String beanName = path.substring(0, dotIndex); + String propertyPath = path.substring(dotIndex + 1); + builder.addPropertyValue("targetBeanName", beanName); + builder.addPropertyValue("propertyPath", propertyPath); + } + + protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) { + String id = super.resolveId(element, definition, parserContext); + if (!StringUtils.hasText(id)) { + id = element.getAttribute("path"); + } + return id; + } + } + + + private static class ListBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { + + protected Class getBeanClass(Element element) { + return ListFactoryBean.class; + } + + protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { + String listClass = element.getAttribute("list-class"); + List parsedList = parserContext.getDelegate().parseListElement(element, builder.getRawBeanDefinition()); + builder.addPropertyValue("sourceList", parsedList); + if (StringUtils.hasText(listClass)) { + builder.addPropertyValue("targetListClass", listClass); + } + String scope = element.getAttribute(SCOPE_ATTRIBUTE); + if (StringUtils.hasLength(scope)) { + builder.setScope(scope); + } + } + } + + + private static class SetBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { + + protected Class getBeanClass(Element element) { + return SetFactoryBean.class; + } + + protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { + String setClass = element.getAttribute("set-class"); + Set parsedSet = parserContext.getDelegate().parseSetElement(element, builder.getRawBeanDefinition()); + builder.addPropertyValue("sourceSet", parsedSet); + if (StringUtils.hasText(setClass)) { + builder.addPropertyValue("targetSetClass", setClass); + } + String scope = element.getAttribute(SCOPE_ATTRIBUTE); + if (StringUtils.hasLength(scope)) { + builder.setScope(scope); + } + } + } + + + private static class MapBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { + + protected Class getBeanClass(Element element) { + return MapFactoryBean.class; + } + + protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { + String mapClass = element.getAttribute("map-class"); + Map parsedMap = parserContext.getDelegate().parseMapElement(element, builder.getRawBeanDefinition()); + builder.addPropertyValue("sourceMap", parsedMap); + if (StringUtils.hasText(mapClass)) { + builder.addPropertyValue("targetMapClass", mapClass); + } + String scope = element.getAttribute(SCOPE_ATTRIBUTE); + if (StringUtils.hasLength(scope)) { + builder.setScope(scope); + } + } + } + + + private static class PropertiesBeanDefinitionParser extends AbstractSimpleBeanDefinitionParser { + + protected Class getBeanClass(Element element) { + return PropertiesFactoryBean.class; + } + + protected boolean isEligibleAttribute(String attributeName) { + return super.isEligibleAttribute(attributeName) && !SCOPE_ATTRIBUTE.equals(attributeName); + } + + protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { + super.doParse(element, parserContext, builder); + Properties parsedProps = parserContext.getDelegate().parsePropsElement(element); + builder.addPropertyValue("properties", parsedProps); + String scope = element.getAttribute(SCOPE_ATTRIBUTE); + if (StringUtils.hasLength(scope)) { + builder.setScope(scope); + } + } + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionParser.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionParser.java new file mode 100644 index 0000000000..de1a0621b4 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionParser.java @@ -0,0 +1,57 @@ +/* + * Copyright 2002-2006 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.xml; + +import org.w3c.dom.Document; + +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.support.BeanDefinitionReader; +import org.springframework.core.io.Resource; + +/** + * Strategy interface for parsing XML bean definitions. + * Used by XmlBeanDefinitionReader for actually parsing a DOM document. + * + *

    Instantiated per document to parse: Implementations can hold + * state in instance variables during the execution of the + * registerBeanDefinitions method, for example global + * settings that are defined for all bean definitions in the document. + * + * @author Juergen Hoeller + * @since 18.12.2003 + * @deprecated as of Spring 2.0: superseded by BeanDefinitionDocumentReader + * @see BeanDefinitionDocumentReader + * @see XmlBeanDefinitionReader#setParserClass + */ +public interface XmlBeanDefinitionParser { + + /** + * Parse bean definitions from the given DOM document, + * and register them with the given bean factory. + * @param reader the bean definition reader, containing the bean factory + * to work on and the bean class loader to use. Can also be used to load + * further bean definition files referenced by the given document. + * @param doc the DOM document + * @param resource descriptor of the original XML resource + * (useful for displaying parse errors) + * @return the number of bean definitions found + * @throws BeanDefinitionStoreException in case of parsing errors + */ + int registerBeanDefinitions(BeanDefinitionReader reader, Document doc, Resource resource) + throws BeanDefinitionStoreException; + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java new file mode 100644 index 0000000000..a1db6369be --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java @@ -0,0 +1,540 @@ +/* + * Copyright 2002-2008 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.xml; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashSet; +import java.util.Set; + +import javax.xml.parsers.ParserConfigurationException; + +import org.w3c.dom.Document; +import org.xml.sax.EntityResolver; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.parsing.EmptyReaderEventListener; +import org.springframework.beans.factory.parsing.FailFastProblemReporter; +import org.springframework.beans.factory.parsing.NullSourceExtractor; +import org.springframework.beans.factory.parsing.ProblemReporter; +import org.springframework.beans.factory.parsing.ReaderEventListener; +import org.springframework.beans.factory.parsing.SourceExtractor; +import org.springframework.beans.factory.support.AbstractBeanDefinitionReader; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.core.Constants; +import org.springframework.core.NamedThreadLocal; +import org.springframework.core.io.DescriptiveResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.io.support.EncodedResource; +import org.springframework.util.Assert; +import org.springframework.util.xml.SimpleSaxErrorHandler; +import org.springframework.util.xml.XmlValidationModeDetector; + +/** + * Bean definition reader for XML bean definitions. + * Delegates the actual XML document reading to an implementation + * of the {@link BeanDefinitionDocumentReader} interface. + * + *

    Typically applied to a + * {@link org.springframework.beans.factory.support.DefaultListableBeanFactory} + * or a {@link org.springframework.context.support.GenericApplicationContext}. + * + *

    This class loads a DOM document and applies the BeanDefinitionDocumentReader to it. + * The document reader will register each bean definition with the given bean factory, + * talking to the latter's implementation of the + * {@link org.springframework.beans.factory.support.BeanDefinitionRegistry} interface. + * + * @author Juergen Hoeller + * @author Rob Harrop + * @since 26.11.2003 + * @see #setDocumentReaderClass + * @see BeanDefinitionDocumentReader + * @see DefaultBeanDefinitionDocumentReader + * @see BeanDefinitionRegistry + * @see org.springframework.beans.factory.support.DefaultListableBeanFactory + * @see org.springframework.context.support.GenericApplicationContext + */ +public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { + + /** + * Indicates that the validation should be disabled. + */ + public static final int VALIDATION_NONE = XmlValidationModeDetector.VALIDATION_NONE; + + /** + * Indicates that the validation mode should be detected automatically. + */ + public static final int VALIDATION_AUTO = XmlValidationModeDetector.VALIDATION_AUTO; + + /** + * Indicates that DTD validation should be used. + */ + public static final int VALIDATION_DTD = XmlValidationModeDetector.VALIDATION_DTD; + + /** + * Indicates that XSD validation should be used. + */ + public static final int VALIDATION_XSD = XmlValidationModeDetector.VALIDATION_XSD; + + + /** Constants instance for this class */ + private static final Constants constants = new Constants(XmlBeanDefinitionReader.class); + + private boolean namespaceAware; + + private int validationMode = VALIDATION_AUTO; + + private Class parserClass; + + private Class documentReaderClass = DefaultBeanDefinitionDocumentReader.class; + + private ProblemReporter problemReporter = new FailFastProblemReporter(); + + private ReaderEventListener eventListener = new EmptyReaderEventListener(); + + private SourceExtractor sourceExtractor = new NullSourceExtractor(); + + private NamespaceHandlerResolver namespaceHandlerResolver; + + private DocumentLoader documentLoader = new DefaultDocumentLoader(); + + private EntityResolver entityResolver; + + private ErrorHandler errorHandler = new SimpleSaxErrorHandler(logger); + + private final XmlValidationModeDetector validationModeDetector = new XmlValidationModeDetector(); + + private final ThreadLocal resourcesCurrentlyBeingLoaded = + new NamedThreadLocal("XML bean definition resources currently being loaded"); + + + /** + * Create new XmlBeanDefinitionReader for the given bean factory. + * @param registry the BeanFactory to load bean definitions into, + * in the form of a BeanDefinitionRegistry + */ + public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) { + super(registry); + } + + + /** + * Set whether or not the XML parser should be XML namespace aware. + * Default is "false". + */ + public void setNamespaceAware(boolean namespaceAware) { + this.namespaceAware = namespaceAware; + } + + /** + * Return whether or not the XML parser should be XML namespace aware. + */ + public boolean isNamespaceAware() { + return this.namespaceAware; + } + + /** + * Set if the XML parser should validate the document and thus enforce a DTD. + * @deprecated as of Spring 2.0: superseded by "validationMode" + * @see #setValidationMode + */ + public void setValidating(boolean validating) { + this.validationMode = (validating ? VALIDATION_AUTO : VALIDATION_NONE); + } + + /** + * Set the validation mode to use by name. Defaults to {@link #VALIDATION_AUTO}. + */ + public void setValidationModeName(String validationModeName) { + setValidationMode(constants.asNumber(validationModeName).intValue()); + } + + /** + * Set the validation mode to use. Defaults to {@link #VALIDATION_AUTO}. + */ + public void setValidationMode(int validationMode) { + this.validationMode = validationMode; + } + + /** + * Return the validation mode to use. + */ + public int getValidationMode() { + return this.validationMode; + } + + /** + * Specify which {@link org.springframework.beans.factory.parsing.ProblemReporter} to use. Default implementation is + * {@link org.springframework.beans.factory.parsing.FailFastProblemReporter} which exhibits fail fast behaviour. External tools + * can provide an alternative implementation that collates errors and warnings for + * display in the tool UI. + */ + public void setProblemReporter(ProblemReporter problemReporter) { + this.problemReporter = (problemReporter != null ? problemReporter : new FailFastProblemReporter()); + } + + /** + * Specify which {@link ReaderEventListener} to use. Default implementation is + * EmptyReaderEventListener which discards every event notification. External tools + * can provide an alternative implementation to monitor the components being registered + * in the BeanFactory. + */ + public void setEventListener(ReaderEventListener eventListener) { + this.eventListener = (eventListener != null ? eventListener : new EmptyReaderEventListener()); + } + + /** + * Specify the {@link SourceExtractor} to use. The default implementation is + * {@link NullSourceExtractor} which simply returns null as the source object. + * This means that during normal runtime execution no additional source metadata is attached + * to the bean configuration metadata. + */ + public void setSourceExtractor(SourceExtractor sourceExtractor) { + this.sourceExtractor = (sourceExtractor != null ? sourceExtractor : new NullSourceExtractor()); + } + + /** + * Specify the {@link NamespaceHandlerResolver} to use. If none is specified a default + * instance will be created by {@link #createDefaultNamespaceHandlerResolver()}. + */ + public void setNamespaceHandlerResolver(NamespaceHandlerResolver namespaceHandlerResolver) { + this.namespaceHandlerResolver = namespaceHandlerResolver; + } + + /** + * Specify the {@link DocumentLoader} to use. The default implementation is + * {@link DefaultDocumentLoader} which loads {@link Document} instances using JAXP. + */ + public void setDocumentLoader(DocumentLoader documentLoader) { + this.documentLoader = (documentLoader != null ? documentLoader : new DefaultDocumentLoader()); + } + + /** + * Set a SAX entity resolver to be used for parsing. By default, + * BeansDtdResolver will be used. Can be overridden for custom entity + * resolution, for example relative to some specific base path. + * @see BeansDtdResolver + */ + public void setEntityResolver(EntityResolver entityResolver) { + this.entityResolver = entityResolver; + } + + /** + * Return the EntityResolver to use, building a default resolver + * if none specified. + */ + protected EntityResolver getEntityResolver() { + if (this.entityResolver == null) { + // Determine default EntityResolver to use. + ResourceLoader resourceLoader = getResourceLoader(); + if (resourceLoader != null) { + this.entityResolver = new ResourceEntityResolver(resourceLoader); + } + else { + this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader()); + } + } + return this.entityResolver; + } + + /** + * Set an implementation of the org.xml.sax.ErrorHandler + * interface for custom handling of XML parsing errors and warnings. + *

    If not set, a default SimpleSaxErrorHandler is used that simply + * logs warnings using the logger instance of the view class, + * and rethrows errors to discontinue the XML transformation. + * @see SimpleSaxErrorHandler + */ + public void setErrorHandler(ErrorHandler errorHandler) { + this.errorHandler = errorHandler; + } + + /** + * Set the XmlBeanDefinitionParser implementation to use, + * responsible for the actual parsing of XML bean definitions. + * @deprecated as of Spring 2.0: superseded by "documentReaderClass" + * @see #setDocumentReaderClass + * @see XmlBeanDefinitionParser + */ + public void setParserClass(Class parserClass) { + if (this.parserClass == null || !XmlBeanDefinitionParser.class.isAssignableFrom(parserClass)) { + throw new IllegalArgumentException("'parserClass' must be an XmlBeanDefinitionParser"); + } + this.parserClass = parserClass; + } + + /** + * Specify the BeanDefinitionDocumentReader implementation to use, + * responsible for the actual reading of the XML bean definition document. + *

    Default is DefaultBeanDefinitionDocumentReader. + * @param documentReaderClass the desired BeanDefinitionDocumentReader implementation class + * @see BeanDefinitionDocumentReader + * @see DefaultBeanDefinitionDocumentReader + */ + public void setDocumentReaderClass(Class documentReaderClass) { + if (documentReaderClass == null || !BeanDefinitionDocumentReader.class.isAssignableFrom(documentReaderClass)) { + throw new IllegalArgumentException( + "documentReaderClass must be an implementation of the BeanDefinitionDocumentReader interface"); + } + this.documentReaderClass = documentReaderClass; + } + + + /** + * Load bean definitions from the specified XML file. + * @param resource the resource descriptor for the XML file + * @return the number of bean definitions found + * @throws BeanDefinitionStoreException in case of loading or parsing errors + */ + public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { + return loadBeanDefinitions(new EncodedResource(resource)); + } + + /** + * Load bean definitions from the specified XML file. + * @param encodedResource the resource descriptor for the XML file, + * allowing to specify an encoding to use for parsing the file + * @return the number of bean definitions found + * @throws BeanDefinitionStoreException in case of loading or parsing errors + */ + public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { + Assert.notNull(encodedResource, "EncodedResource must not be null"); + if (logger.isInfoEnabled()) { + logger.info("Loading XML bean definitions from " + encodedResource.getResource()); + } + + Set currentResources = (Set) this.resourcesCurrentlyBeingLoaded.get(); + if (currentResources == null) { + currentResources = new HashSet(4); + this.resourcesCurrentlyBeingLoaded.set(currentResources); + } + if (!currentResources.add(encodedResource)) { + throw new BeanDefinitionStoreException( + "Detected recursive loading of " + encodedResource + " - check your import definitions!"); + } + try { + InputStream inputStream = encodedResource.getResource().getInputStream(); + try { + InputSource inputSource = new InputSource(inputStream); + if (encodedResource.getEncoding() != null) { + inputSource.setEncoding(encodedResource.getEncoding()); + } + return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); + } + finally { + inputStream.close(); + } + } + catch (IOException ex) { + throw new BeanDefinitionStoreException( + "IOException parsing XML document from " + encodedResource.getResource(), ex); + } + finally { + currentResources.remove(encodedResource); + if (currentResources.isEmpty()) { + this.resourcesCurrentlyBeingLoaded.set(null); + } + } + } + + /** + * Load bean definitions from the specified XML file. + * @param inputSource the SAX InputSource to read from + * @return the number of bean definitions found + * @throws BeanDefinitionStoreException in case of loading or parsing errors + */ + public int loadBeanDefinitions(InputSource inputSource) throws BeanDefinitionStoreException { + return loadBeanDefinitions(inputSource, "resource loaded through SAX InputSource"); + } + + /** + * Load bean definitions from the specified XML file. + * @param inputSource the SAX InputSource to read from + * @param resourceDescription a description of the resource + * (can be null or empty) + * @return the number of bean definitions found + * @throws BeanDefinitionStoreException in case of loading or parsing errors + */ + public int loadBeanDefinitions(InputSource inputSource, String resourceDescription) + throws BeanDefinitionStoreException { + + return doLoadBeanDefinitions(inputSource, new DescriptiveResource(resourceDescription)); + } + + + /** + * Actually load bean definitions from the specified XML file. + * @param inputSource the SAX InputSource to read from + * @param resource the resource descriptor for the XML file + * @return the number of bean definitions found + * @throws BeanDefinitionStoreException in case of loading or parsing errors + */ + protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) + throws BeanDefinitionStoreException { + try { + int validationMode = getValidationModeForResource(resource); + Document doc = this.documentLoader.loadDocument( + inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware()); + return registerBeanDefinitions(doc, resource); + } + catch (BeanDefinitionStoreException ex) { + throw ex; + } + catch (SAXParseException ex) { + throw new XmlBeanDefinitionStoreException(resource.getDescription(), + "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex); + } + catch (SAXException ex) { + throw new XmlBeanDefinitionStoreException(resource.getDescription(), + "XML document from " + resource + " is invalid", ex); + } + catch (ParserConfigurationException ex) { + throw new BeanDefinitionStoreException(resource.getDescription(), + "Parser configuration exception parsing XML from " + resource, ex); + } + catch (IOException ex) { + throw new BeanDefinitionStoreException(resource.getDescription(), + "IOException parsing XML document from " + resource, ex); + } + catch (Throwable ex) { + throw new BeanDefinitionStoreException(resource.getDescription(), + "Unexpected exception parsing XML document from " + resource, ex); + } + } + + + /** + * Gets the validation mode for the specified {@link Resource}. If no explicit + * validation mode has been configured then the validation mode is + * {@link #detectValidationMode detected}. + *

    Override this method if you would like full control over the validation + * mode, even when something other than {@link #VALIDATION_AUTO} was set. + */ + protected int getValidationModeForResource(Resource resource) { + int validationModeToUse = getValidationMode(); + if (validationModeToUse != VALIDATION_AUTO) { + return validationModeToUse; + } + int detectedMode = detectValidationMode(resource); + if (detectedMode != VALIDATION_AUTO) { + return detectedMode; + } + // Hmm, we didn't get a clear indication... Let's assume XSD, + // since apparently no DTD declaration has been found up until + // detection stopped (before finding the document's root tag). + return VALIDATION_XSD; + } + + /** + * Detects which kind of validation to perform on the XML file identified + * by the supplied {@link Resource}. If the file has a DOCTYPE + * definition then DTD validation is used otherwise XSD validation is assumed. + *

    Override this method if you would like to customize resolution + * of the {@link #VALIDATION_AUTO} mode. + */ + protected int detectValidationMode(Resource resource) { + if (resource.isOpen()) { + throw new BeanDefinitionStoreException( + "Passed-in Resource [" + resource + "] contains an open stream: " + + "cannot determine validation mode automatically. Either pass in a Resource " + + "that is able to create fresh streams, or explicitly specify the validationMode " + + "on your XmlBeanDefinitionReader instance."); + } + + InputStream inputStream; + try { + inputStream = resource.getInputStream(); + } + catch (IOException ex) { + throw new BeanDefinitionStoreException( + "Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " + + "Did you attempt to load directly from a SAX InputSource without specifying the " + + "validationMode on your XmlBeanDefinitionReader instance?", ex); + } + + try { + return this.validationModeDetector.detectValidationMode(inputStream); + } + catch (IOException ex) { + throw new BeanDefinitionStoreException("Unable to determine validation mode for [" + + resource + "]: an error occurred whilst reading from the InputStream.", ex); + } + } + + /** + * Register the bean definitions contained in the given DOM document. + * Called by loadBeanDefinitions. + *

    Creates a new instance of the parser class and invokes + * registerBeanDefinitions on it. + * @param doc the DOM document + * @param resource the resource descriptor (for context information) + * @return the number of bean definitions found + * @throws BeanDefinitionStoreException in case of parsing errors + * @see #loadBeanDefinitions + * @see #setDocumentReaderClass + * @see BeanDefinitionDocumentReader#registerBeanDefinitions + */ + public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { + // Support old XmlBeanDefinitionParser SPI for backwards-compatibility. + if (this.parserClass != null) { + XmlBeanDefinitionParser parser = + (XmlBeanDefinitionParser) BeanUtils.instantiateClass(this.parserClass); + return parser.registerBeanDefinitions(this, doc, resource); + } + // Read document based on new BeanDefinitionDocumentReader SPI. + BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); + int countBefore = getRegistry().getBeanDefinitionCount(); + documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); + return getRegistry().getBeanDefinitionCount() - countBefore; + } + + /** + * Create the {@link BeanDefinitionDocumentReader} to use for actually + * reading bean definitions from an XML document. + *

    Default implementation instantiates the specified "documentReaderClass". + * @see #setDocumentReaderClass + */ + protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() { + return (BeanDefinitionDocumentReader) BeanUtils.instantiateClass(this.documentReaderClass); + } + + /** + * Create the {@link XmlReaderContext} to pass over to the document reader. + */ + protected XmlReaderContext createReaderContext(Resource resource) { + if (this.namespaceHandlerResolver == null) { + this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver(); + } + return new XmlReaderContext(resource, this.problemReporter, this.eventListener, + this.sourceExtractor, this, this.namespaceHandlerResolver); + } + + /** + * Create the default implementation of {@link NamespaceHandlerResolver} used if none is specified. + * Default implementation returns an instance of {@link DefaultNamespaceHandlerResolver}. + */ + protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() { + return new DefaultNamespaceHandlerResolver(getResourceLoader().getClassLoader()); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionStoreException.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionStoreException.java new file mode 100644 index 0000000000..87c4617c4d --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionStoreException.java @@ -0,0 +1,60 @@ +/* + * Copyright 2002-2006 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.xml; + +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +import org.springframework.beans.factory.BeanDefinitionStoreException; + +/** + * XML-specific BeanDefinitionStoreException subclass that wraps a + * {@link org.xml.sax.SAXException}, typically a {@link org.xml.sax.SAXParseException} + * which contains information about the error location. + * + * @author Juergen Hoeller + * @since 2.0.2 + * @see #getLineNumber() + * @see org.xml.sax.SAXParseException + */ +public class XmlBeanDefinitionStoreException extends BeanDefinitionStoreException { + + /** + * Create a new XmlBeanDefinitionStoreException. + * @param resourceDescription description of the resource that the bean definition came from + * @param msg the detail message (used as exception message as-is) + * @param cause the SAXException (typically a SAXParseException) root cause + * @see org.xml.sax.SAXParseException + */ + public XmlBeanDefinitionStoreException(String resourceDescription, String msg, SAXException cause) { + super(resourceDescription, msg, cause); + } + + /** + * Return the line number in the XML resource that failed. + * @return the line number if available (in case of a SAXParseException); -1 else + * @see org.xml.sax.SAXParseException#getLineNumber() + */ + public int getLineNumber() { + Throwable cause = getCause(); + if (cause instanceof SAXParseException) { + return ((SAXParseException) cause).getLineNumber(); + } + return -1; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/XmlBeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/XmlBeanFactory.java new file mode 100644 index 0000000000..d5c42c719d --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/XmlBeanFactory.java @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2007 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.xml; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.core.io.Resource; + +/** + * Convenience extension of {@link DefaultListableBeanFactory} that reads bean definitions + * from an XML document. Delegates to {@link XmlBeanDefinitionReader} underneath; effectively + * equivalent to using an XmlBeanDefinitionReader with a DefaultListableBeanFactory. + * + *

    The structure, element and attribute names of the required XML document + * are hard-coded in this class. (Of course a transform could be run if necessary + * to produce this format). "beans" doesn't need to be the root element of the XML + * document: This class will parse all bean definition elements in the XML file. + * + *

    This class registers each bean definition with the {@link DefaultListableBeanFactory} + * superclass, and relies on the latter's implementation of the {@link BeanFactory} interface. + * It supports singletons, prototypes, and references to either of these kinds of bean. + * See "spring-beans-2.0.dtd" for details on options and configuration style. + * + *

    For advanced needs, consider using a {@link DefaultListableBeanFactory} with + * an {@link XmlBeanDefinitionReader}. The latter allows for reading from multiple XML + * resources and is highly configurable in its actual XML parsing behavior. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 15 April 2001 + * @see org.springframework.beans.factory.support.DefaultListableBeanFactory + * @see XmlBeanDefinitionReader + */ +public class XmlBeanFactory extends DefaultListableBeanFactory { + + private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); + + + /** + * Create a new XmlBeanFactory with the given resource, + * which must be parsable using DOM. + * @param resource XML resource to load bean definitions from + * @throws BeansException in case of loading or parsing errors + */ + public XmlBeanFactory(Resource resource) throws BeansException { + this(resource, null); + } + + /** + * Create a new XmlBeanFactory with the given input stream, + * which must be parsable using DOM. + * @param resource XML resource to load bean definitions from + * @param parentBeanFactory parent bean factory + * @throws BeansException in case of loading or parsing errors + */ + public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { + super(parentBeanFactory); + this.reader.loadBeanDefinitions(resource); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/XmlReaderContext.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/XmlReaderContext.java new file mode 100644 index 0000000000..cbb5a0d504 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/XmlReaderContext.java @@ -0,0 +1,86 @@ +/* + * Copyright 2002-2007 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.xml; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.parsing.ProblemReporter; +import org.springframework.beans.factory.parsing.ReaderContext; +import org.springframework.beans.factory.parsing.ReaderEventListener; +import org.springframework.beans.factory.parsing.SourceExtractor; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; + +/** + * Extension of {@link org.springframework.beans.factory.parsing.ReaderContext}, + * specific to use with an {@link XmlBeanDefinitionReader}. Provides access to the + * {@link NamespaceHandlerResolver} configured in the {@link XmlBeanDefinitionReader}. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + */ +public class XmlReaderContext extends ReaderContext { + + private final XmlBeanDefinitionReader reader; + + private final NamespaceHandlerResolver namespaceHandlerResolver; + + + public XmlReaderContext( + Resource resource, ProblemReporter problemReporter, + ReaderEventListener eventListener, SourceExtractor sourceExtractor, + XmlBeanDefinitionReader reader, NamespaceHandlerResolver namespaceHandlerResolver) { + + super(resource, problemReporter, eventListener, sourceExtractor); + this.reader = reader; + this.namespaceHandlerResolver = namespaceHandlerResolver; + } + + + public final XmlBeanDefinitionReader getReader() { + return this.reader; + } + + public final BeanDefinitionRegistry getRegistry() { + return this.reader.getRegistry(); + } + + public final ResourceLoader getResourceLoader() { + return this.reader.getResourceLoader(); + } + + public final ClassLoader getBeanClassLoader() { + return this.reader.getBeanClassLoader(); + } + + public final NamespaceHandlerResolver getNamespaceHandlerResolver() { + return this.namespaceHandlerResolver; + } + + + public String generateBeanName(BeanDefinition beanDefinition) { + return this.reader.getBeanNameGenerator().generateBeanName(beanDefinition, getRegistry()); + } + + public String registerWithGeneratedName(BeanDefinition beanDefinition) { + String generatedName = generateBeanName(beanDefinition); + getRegistry().registerBeanDefinition(generatedName, beanDefinition); + return generatedName; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/package.html b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/package.html new file mode 100644 index 0000000000..c0c04b3495 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/package.html @@ -0,0 +1,8 @@ + + + +Contains an abstract XML-based BeanFactory implementation, +including a standard "spring-beans" DTD. + + + diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/spring-beans-2.0.dtd b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/spring-beans-2.0.dtd new file mode 100644 index 0000000000..edb9aca7b3 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/spring-beans-2.0.dtd @@ -0,0 +1,679 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/spring-beans-2.0.xsd b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/spring-beans-2.0.xsd new file mode 100644 index 0000000000..60d002f563 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/spring-beans-2.0.xsd @@ -0,0 +1,1077 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /' element. + ]]> + + + + + + + + + + /' element. + ]]> + + + + + + + + + + + + + + /' element. + ]]> + + + + + + + + + + + + + /' element. + ]]> + + + + + /' element. + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + element (or "ref" + attribute). We recommend this in most cases as it makes documentation + more explicit. + + 2. "byName" + Autowiring by property name. If a bean of class Cat exposes a dog + property, Spring will try to set this to the value of the bean "dog" + in the current container. If there is no matching bean by name, nothing + special happens; use dependency-check="objects" to raise an error in + that case. + + 3. "byType" + Autowiring if there is exactly one bean of the property type in the + container. If there is more than one, a fatal error is raised, and + you cannot use byType autowiring for that bean. If there is none, + nothing special happens; use dependency-check="objects" to raise an + error in that case. + + 4. "constructor" + Analogous to "byType" for constructor arguments. If there is not exactly + one bean of the constructor argument type in the bean factory, a fatal + error is raised. + + 5. "autodetect" + Chooses "constructor" or "byType" through introspection of the bean + class. If a default constructor is found, "byType" gets applied. + + Note that explicit dependencies, i.e. "property" and "constructor-arg" + elements, always override autowiring. Autowire behavior can be combined + with dependency checking, which will be performed after all autowiring + has been completed. + + Note: This attribute will not be inherited by child bean definitions. + Hence, it needs to be specified per concrete bean definition. + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + " element. + ]]> + + + + + ..." element. + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ". + ]]> + + + + + ..." + element. + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ". + ]]> + + + + + ..." + element. + ]]> + + + + + ". + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/spring-beans-2.5.xsd b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/spring-beans-2.5.xsd new file mode 100644 index 0000000000..769830c69d --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/spring-beans-2.5.xsd @@ -0,0 +1,1164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /' element. + ]]> + + + + + + + + + + /' element. + ]]> + + + + + + + + + + + + + + /' element. + ]]> + + + + + + + + + + + + + ' element for the semantic details of autowire candidate beans. + ]]> + + + + + /' element. + ]]> + + + + + /' element. + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + element (or "ref" + attribute). We recommend this in most cases as it makes documentation + more explicit. + + 2. "byName" + Autowiring by property name. If a bean of class Cat exposes a dog + property, Spring will try to set this to the value of the bean "dog" + in the current container. If there is no matching bean by name, nothing + special happens; use dependency-check="objects" to raise an error in + that case. + + 3. "byType" + Autowiring if there is exactly one bean of the property type in the + container. If there is more than one, a fatal error is raised, and + you cannot use byType autowiring for that bean. If there is none, + nothing special happens; use dependency-check="objects" to raise an + error in that case. + + 4. "constructor" + Analogous to "byType" for constructor arguments. If there is not exactly + one bean of the constructor argument type in the bean factory, a fatal + error is raised. + + 5. "autodetect" + Chooses "constructor" or "byType" through introspection of the bean + class. If a default constructor is found, "byType" gets applied. + + Note that explicit dependencies, i.e. "property" and "constructor-arg" + elements, always override autowiring. Autowire behavior can be combined + with dependency checking, which will be performed after all autowiring + has been completed. + + Note: This attribute will not be inherited by child bean definitions. + Hence, it needs to be specified per concrete bean definition. + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + " element. + ]]> + + + + + ..." element. + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ". + ]]> + + + + + ..." element. + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ". + ]]> + + + + + ..." + element. + ]]> + + + + + ". + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/spring-beans.dtd b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/spring-beans.dtd new file mode 100644 index 0000000000..fe521abffb --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/spring-beans.dtd @@ -0,0 +1,606 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/spring-tool-2.0.xsd b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/spring-tool-2.0.xsd new file mode 100644 index 0000000000..94f40e8ecb --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/spring-tool-2.0.xsd @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/spring-tool-2.5.xsd b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/spring-tool-2.5.xsd new file mode 100644 index 0000000000..17bec6dbec --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/spring-tool-2.5.xsd @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/spring-util-2.0.xsd b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/spring-util-2.0.xsd new file mode 100644 index 0000000000..dd990b0de7 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/spring-util-2.0.xsd @@ -0,0 +1,203 @@ + + + + + + + + + + + Reference a public, static field on a type and expose its value as + a bean. For example <util:constant static-field="java.lang.Integer.MAX_VALUE"/>. + + + + + + + + + + + + Reference a property on a bean (or as a nested value) and expose its values as + a bean. For example <util:property-path path="order.customer.name"/>. + + + + + + + + + + + + Builds a List instance of the specified type, populated with the specified content. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Builds a Set instance of the specified type, populated with the specified content. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Builds a Map instance of the specified type, populated with the specified content. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Loads a Properties instance from the resource location specified by the 'location' attribute. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/spring-util-2.5.xsd b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/spring-util-2.5.xsd new file mode 100644 index 0000000000..68796d8b36 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/spring-util-2.5.xsd @@ -0,0 +1,212 @@ + + + + + + + + + + + Reference a public, static field on a type and expose its value as + a bean. For example <util:constant static-field="java.lang.Integer.MAX_VALUE"/>. + + + + + + + + + + + + Reference a property on a bean (or as a nested value) and expose its values as + a bean. For example <util:property-path path="order.customer.name"/>. + + + + + + + + + + + + Builds a List instance of the specified type, populated with the specified content. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Builds a Set instance of the specified type, populated with the specified content. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Builds a Map instance of the specified type, populated with the specified content. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Loads a Properties instance from the resource location specified by the 'location' attribute. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/package.html b/org.springframework.beans/src/main/java/org/springframework/beans/package.html new file mode 100644 index 0000000000..97724c7fe0 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/package.html @@ -0,0 +1,15 @@ + + + +This package contains interfaces and classes for manipulating Java beans. +It is used by most other Spring packages. + +

    A BeanWrapper object may be used to set and get bean properties, +singly or in bulk. + +

    The classes in this package are discussed in Chapter 11 of +Expert One-On-One J2EE Design and Development +by Rod Johnson (Wrox, 2002). + + + diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/ByteArrayPropertyEditor.java b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/ByteArrayPropertyEditor.java new file mode 100644 index 0000000000..6655bc2a42 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/ByteArrayPropertyEditor.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2006 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.propertyeditors; + +import java.beans.PropertyEditorSupport; + +/** + * Editor for byte arrays. Strings will simply be converted to + * their corresponding byte representations. + * + * @author Juergen Hoeller + * @since 1.0.1 + * @see java.lang.String#getBytes + */ +public class ByteArrayPropertyEditor extends PropertyEditorSupport { + + public void setAsText(String text) { + setValue(text != null ? text.getBytes() : null); + } + + public String getAsText() { + byte[] value = (byte[]) getValue(); + return (value != null ? new String(value) : ""); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/CharArrayPropertyEditor.java b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/CharArrayPropertyEditor.java new file mode 100644 index 0000000000..088312d6da --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/CharArrayPropertyEditor.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2006 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.propertyeditors; + +import java.beans.PropertyEditorSupport; + +/** + * Editor for char arrays. Strings will simply be converted to + * their corresponding char representations. + * + * @author Juergen Hoeller + * @since 1.2.8 + * @see String#toCharArray() + */ +public class CharArrayPropertyEditor extends PropertyEditorSupport { + + public void setAsText(String text) { + setValue(text != null ? text.toCharArray() : null); + } + + public String getAsText() { + char[] value = (char[]) getValue(); + return (value != null ? new String(value) : ""); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/CharacterEditor.java b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/CharacterEditor.java new file mode 100644 index 0000000000..5f24fb900d --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/CharacterEditor.java @@ -0,0 +1,107 @@ +/* + * Copyright 2002-2008 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.propertyeditors; + +import java.beans.PropertyEditorSupport; + +import org.springframework.util.StringUtils; + +/** + * Editor for a {@link java.lang.Character}, to populate a property + * of type Character or char from a String value. + * + *

    Note that the JDK does not contain a default + * {@link java.beans.PropertyEditor property editor} for char! + * {@link org.springframework.beans.BeanWrapperImpl} will register this + * editor by default. + * + *

    Also supports conversion from a Unicode character sequence; e.g. + * u0041 ('A'). + * + * @author Juergen Hoeller + * @author Rob Harrop + * @author Rick Evans + * @since 1.2 + * @see java.lang.Character + * @see org.springframework.beans.BeanWrapperImpl + */ +public class CharacterEditor extends PropertyEditorSupport { + + /** + * The prefix that identifies a string as being a Unicode character sequence. + */ + private static final String UNICODE_PREFIX = "\\u"; + + /** + * The length of a Unicode character sequence. + */ + private static final int UNICODE_LENGTH = 6; + + + private final boolean allowEmpty; + + + /** + * Create a new CharacterEditor instance. + *

    The "allowEmpty" parameter controls whether an empty String is + * to be allowed in parsing, i.e. be interpreted as the null + * value when {@link #setAsText(String) text is being converted}. If + * false, an {@link IllegalArgumentException} will be thrown + * at that time. + * @param allowEmpty if empty strings are to be allowed + */ + public CharacterEditor(boolean allowEmpty) { + this.allowEmpty = allowEmpty; + } + + + public void setAsText(String text) throws IllegalArgumentException { + if (this.allowEmpty && !StringUtils.hasLength(text)) { + // Treat empty String as null value. + setValue(null); + } + else if (text == null) { + throw new IllegalArgumentException("null String cannot be converted to char type"); + } + else if (isUnicodeCharacterSequence(text)) { + setAsUnicode(text); + } + else if (text.length() != 1) { + throw new IllegalArgumentException("String [" + text + "] with length " + + text.length() + " cannot be converted to char type"); + } + else { + setValue(new Character(text.charAt(0))); + } + } + + public String getAsText() { + Object value = getValue(); + return (value != null ? value.toString() : ""); + } + + + private boolean isUnicodeCharacterSequence(String sequence) { + return (sequence.startsWith(UNICODE_PREFIX) && sequence.length() == UNICODE_LENGTH); + } + + private void setAsUnicode(String text) { + int code = Integer.parseInt(text.substring(UNICODE_PREFIX.length()), 16); + setValue(new Character((char) code)); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/CharsetEditor.java b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/CharsetEditor.java new file mode 100644 index 0000000000..67a9fe5a6d --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/CharsetEditor.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2008 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.propertyeditors; + +import java.beans.PropertyEditorSupport; +import java.nio.charset.Charset; + +import org.springframework.util.StringUtils; + +/** + * Editor for {@link Charset}, to directly populate a Charset property. + * + *

    Expects the same syntax as Charset's {@link java.nio.charset.Charset#name()}, + * e.g. UTF-8, ISO-8859-16, etc. + * + * @author Arjen Poutsma + * @since 2.5.4 + * @see Charset + */ +public class CharsetEditor extends PropertyEditorSupport { + + public void setAsText(String text) throws IllegalArgumentException { + if (StringUtils.hasText(text)) { + setValue(Charset.forName(text)); + } + else { + setValue(null); + } + } + + public String getAsText() { + Charset value = (Charset) getValue(); + return (value != null ? value.name() : ""); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/ClassArrayEditor.java b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/ClassArrayEditor.java new file mode 100644 index 0000000000..f42b2f97aa --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/ClassArrayEditor.java @@ -0,0 +1,95 @@ +/* + * Copyright 2002-2006 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.propertyeditors; + +import java.beans.PropertyEditorSupport; + +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; + +/** + * Property editor for an array of {@link java.lang.Class Classes}, to enable + * the direct population of a Class[] property without having to + * use a String class name property as bridge. + * + *

    Also supports "java.lang.String[]"-style array class names, in contrast + * to the standard {@link Class#forName(String)} method. + * + * @author Rob Harrop + * @since 2.0 + */ +public class ClassArrayEditor extends PropertyEditorSupport { + + private final ClassLoader classLoader; + + + /** + * Create a default ClassEditor, using the thread + * context ClassLoader. + */ + public ClassArrayEditor() { + this(null); + } + + /** + * Create a default ClassArrayEditor, using the given + * ClassLoader. + * @param classLoader the ClassLoader to use + * (or pass null for the thread context ClassLoader) + */ + public ClassArrayEditor(ClassLoader classLoader) { + this.classLoader = classLoader != null + ? classLoader : ClassUtils.getDefaultClassLoader(); + } + + + public void setAsText(String text) throws IllegalArgumentException { + if (StringUtils.hasText(text)) { + String[] classNames = StringUtils.commaDelimitedListToStringArray(text); + Class[] classes = new Class[classNames.length]; + for (int i = 0; i < classNames.length; i++) { + String className = classNames[i].trim(); + classes[i] = ClassUtils.resolveClassName(className, this.classLoader); + } + setValue(classes); + } + else { + setValue(null); + } + } + + public String getAsText() { + Class[] classes = (Class[]) getValue(); + if (classes == null || classes.length == 0) { + return ""; + } + return toCommaDelimitedString(classes); + } + + + private static String toCommaDelimitedString(Class[] classes) { + StringBuffer buffer = new StringBuffer(); + for (int i = 0; i < classes.length; ++i) { + if (i > 0) { + buffer.append(","); + } + buffer.append(ClassUtils.getQualifiedName(classes[i])); + } + return buffer.toString(); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/ClassEditor.java b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/ClassEditor.java new file mode 100644 index 0000000000..c2e8c695de --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/ClassEditor.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2006 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.propertyeditors; + +import java.beans.PropertyEditorSupport; + +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; + +/** + * Property editor for {@link java.lang.Class java.lang.Class}, to enable the direct + * population of a Class property without recourse to having to use a + * String class name property as bridge. + * + *

    Also supports "java.lang.String[]"-style array class names, in contrast to the + * standard {@link Class#forName(String)} method. + * + * @author Juergen Hoeller + * @author Rick Evans + * @since 13.05.2003 + * @see java.lang.Class#forName + * @see org.springframework.util.ClassUtils#forName(String, ClassLoader) + */ +public class ClassEditor extends PropertyEditorSupport { + + private final ClassLoader classLoader; + + + /** + * Create a default ClassEditor, using the thread context ClassLoader. + */ + public ClassEditor() { + this(null); + } + + /** + * Create a default ClassEditor, using the given ClassLoader. + * @param classLoader the ClassLoader to use + * (or null for the thread context ClassLoader) + */ + public ClassEditor(ClassLoader classLoader) { + this.classLoader = + (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader()); + } + + + public void setAsText(String text) throws IllegalArgumentException { + if (StringUtils.hasText(text)) { + setValue(ClassUtils.resolveClassName(text.trim(), this.classLoader)); + } + else { + setValue(null); + } + } + + public String getAsText() { + Class clazz = (Class) getValue(); + if (clazz != null) { + return ClassUtils.getQualifiedName(clazz); + } + else { + return ""; + } + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/CustomBooleanEditor.java b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/CustomBooleanEditor.java new file mode 100644 index 0000000000..a0dedcef22 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/CustomBooleanEditor.java @@ -0,0 +1,139 @@ +/* + * Copyright 2002-2007 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.propertyeditors; + +import java.beans.PropertyEditorSupport; + +import org.springframework.util.StringUtils; + +/** + * Property editor for Boolean/boolean properties. + * + *

    This is not meant to be used as system PropertyEditor but rather as + * locale-specific Boolean editor within custom controller code, to parse + * UI-caused boolean strings into boolean properties of beans and check + * them in the UI form. + * + *

    In web MVC code, this editor will typically be registered with + * binder.registerCustomEditor calls in an implementation + * of BaseCommandController's initBinder method. + * + * @author Juergen Hoeller + * @since 10.06.2003 + * @see org.springframework.validation.DataBinder#registerCustomEditor + * @see org.springframework.web.servlet.mvc.BaseCommandController#initBinder + */ +public class CustomBooleanEditor extends PropertyEditorSupport { + + public static final String VALUE_TRUE = "true"; + public static final String VALUE_FALSE = "false"; + + public static final String VALUE_ON = "on"; + public static final String VALUE_OFF = "off"; + + public static final String VALUE_YES = "yes"; + public static final String VALUE_NO = "no"; + + public static final String VALUE_1 = "1"; + public static final String VALUE_0 = "0"; + + + private final String trueString; + + private final String falseString; + + private final boolean allowEmpty; + + + /** + * Create a new CustomBooleanEditor instance, with "true"/"on"/"yes" + * and "false"/"off"/"no" as recognized String values. + *

    The "allowEmpty" parameter states if an empty String should + * be allowed for parsing, i.e. get interpreted as null value. + * Else, an IllegalArgumentException gets thrown in that case. + * @param allowEmpty if empty strings should be allowed + */ + public CustomBooleanEditor(boolean allowEmpty) { + this(null, null, allowEmpty); + } + + /** + * Create a new CustomBooleanEditor instance, + * with configurable String values for true and false. + *

    The "allowEmpty" parameter states if an empty String should + * be allowed for parsing, i.e. get interpreted as null value. + * Else, an IllegalArgumentException gets thrown in that case. + * @param trueString the String value that represents true: + * for example, "true" (VALUE_TRUE), "on" (VALUE_ON), + * "yes" (VALUE_YES) or some custom value + * @param falseString the String value that represents false: + * for example, "false" (VALUE_FALSE), "off" (VALUE_OFF), + * "no" (VALUE_NO) or some custom value + * @param allowEmpty if empty strings should be allowed + * @see #VALUE_TRUE + * @see #VALUE_FALSE + * @see #VALUE_ON + * @see #VALUE_OFF + * @see #VALUE_YES + * @see #VALUE_NO + */ + public CustomBooleanEditor(String trueString, String falseString, boolean allowEmpty) { + this.trueString = trueString; + this.falseString = falseString; + this.allowEmpty = allowEmpty; + } + + public void setAsText(String text) throws IllegalArgumentException { + String input = (text != null ? text.trim() : null); + if (this.allowEmpty && !StringUtils.hasLength(input)) { + // Treat empty String as null value. + setValue(null); + } + else if (this.trueString != null && input.equalsIgnoreCase(this.trueString)) { + setValue(Boolean.TRUE); + } + else if (this.falseString != null && input.equalsIgnoreCase(this.falseString)) { + setValue(Boolean.FALSE); + } + else if (this.trueString == null && + (input.equalsIgnoreCase(VALUE_TRUE) || input.equalsIgnoreCase(VALUE_ON) || + input.equalsIgnoreCase(VALUE_YES) || input.equals(VALUE_1))) { + setValue(Boolean.TRUE); + } + else if (this.falseString == null && + (input.equalsIgnoreCase(VALUE_FALSE) || input.equalsIgnoreCase(VALUE_OFF) || + input.equalsIgnoreCase(VALUE_NO) || input.equals(VALUE_0))) { + setValue(Boolean.FALSE); + } + else { + throw new IllegalArgumentException("Invalid boolean value [" + text + "]"); + } + } + + public String getAsText() { + if (Boolean.TRUE.equals(getValue())) { + return (this.trueString != null ? this.trueString : VALUE_TRUE); + } + else if (Boolean.FALSE.equals(getValue())) { + return (this.falseString != null ? this.falseString : VALUE_FALSE); + } + else { + return ""; + } + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/CustomCollectionEditor.java b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/CustomCollectionEditor.java new file mode 100644 index 0000000000..705109758c --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/CustomCollectionEditor.java @@ -0,0 +1,206 @@ +/* + * Copyright 2002-2007 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.propertyeditors; + +import java.beans.PropertyEditorSupport; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.SortedSet; +import java.util.TreeSet; + +/** + * Property editor for Collections, converting any source Collection + * to a given target Collection type. + * + *

    By default registered for Set, SortedSet and List, + * to automatically convert any given Collection to one of those + * target types if the type does not match the target property. + * + * @author Juergen Hoeller + * @since 1.1.3 + * @see java.util.Collection + * @see java.util.Set + * @see java.util.SortedSet + * @see java.util.List + */ +public class CustomCollectionEditor extends PropertyEditorSupport { + + private final Class collectionType; + + private final boolean nullAsEmptyCollection; + + + /** + * Create a new CustomCollectionEditor for the given target type, + * keeping an incoming null as-is. + * @param collectionType the target type, which needs to be a + * sub-interface of Collection or a concrete Collection class + * @see java.util.Collection + * @see java.util.ArrayList + * @see java.util.TreeSet + * @see java.util.LinkedHashSet + */ + public CustomCollectionEditor(Class collectionType) { + this(collectionType, false); + } + + /** + * Create a new CustomCollectionEditor for the given target type. + *

    If the incoming value is of the given type, it will be used as-is. + * If it is a different Collection type or an array, it will be converted + * to a default implementation of the given Collection type. + * If the value is anything else, a target Collection with that single + * value will be created. + *

    The default Collection implementations are: ArrayList for List, + * TreeSet for SortedSet, and LinkedHashSet for Set. + * @param collectionType the target type, which needs to be a + * sub-interface of Collection or a concrete Collection class + * @param nullAsEmptyCollection whether to convert an incoming null + * value to an empty Collection (of the appropriate type) + * @see java.util.Collection + * @see java.util.ArrayList + * @see java.util.TreeSet + * @see java.util.LinkedHashSet + */ + public CustomCollectionEditor(Class collectionType, boolean nullAsEmptyCollection) { + if (collectionType == null) { + throw new IllegalArgumentException("Collection type is required"); + } + if (!Collection.class.isAssignableFrom(collectionType)) { + throw new IllegalArgumentException( + "Collection type [" + collectionType.getName() + "] does not implement [java.util.Collection]"); + } + this.collectionType = collectionType; + this.nullAsEmptyCollection = nullAsEmptyCollection; + } + + + /** + * Convert the given text value to a Collection with a single element. + */ + public void setAsText(String text) throws IllegalArgumentException { + setValue(text); + } + + /** + * Convert the given value to a Collection of the target type. + */ + public void setValue(Object value) { + if (value == null && this.nullAsEmptyCollection) { + super.setValue(createCollection(this.collectionType, 0)); + } + else if (value == null || (this.collectionType.isInstance(value) && !alwaysCreateNewCollection())) { + // Use the source value as-is, as it matches the target type. + super.setValue(value); + } + else if (value instanceof Collection) { + // Convert Collection elements. + Collection source = (Collection) value; + Collection target = createCollection(this.collectionType, source.size()); + for (Iterator it = source.iterator(); it.hasNext();) { + target.add(convertElement(it.next())); + } + super.setValue(target); + } + else if (value.getClass().isArray()) { + // Convert array elements to Collection elements. + int length = Array.getLength(value); + Collection target = createCollection(this.collectionType, length); + for (int i = 0; i < length; i++) { + target.add(convertElement(Array.get(value, i))); + } + super.setValue(target); + } + else { + // A plain value: convert it to a Collection with a single element. + Collection target = createCollection(this.collectionType, 1); + target.add(convertElement(value)); + super.setValue(target); + } + } + + /** + * Create a Collection of the given type, with the given + * initial capacity (if supported by the Collection type). + * @param collectionType a sub-interface of Collection + * @param initialCapacity the initial capacity + * @return the new Collection instance + */ + protected Collection createCollection(Class collectionType, int initialCapacity) { + if (!collectionType.isInterface()) { + try { + return (Collection) collectionType.newInstance(); + } + catch (Exception ex) { + throw new IllegalArgumentException( + "Could not instantiate collection class [" + collectionType.getName() + "]: " + ex.getMessage()); + } + } + else if (List.class.equals(collectionType)) { + return new ArrayList(initialCapacity); + } + else if (SortedSet.class.equals(collectionType)) { + return new TreeSet(); + } + else { + return new LinkedHashSet(initialCapacity); + } + } + + /** + * Return whether to always create a new Collection, + * even if the type of the passed-in Collection already matches. + *

    Default is "false"; can be overridden to enforce creation of a + * new Collection, for example to convert elements in any case. + * @see #convertElement + */ + protected boolean alwaysCreateNewCollection() { + return false; + } + + /** + * Hook to convert each encountered Collection/array element. + * The default implementation simply returns the passed-in element as-is. + *

    Can be overridden to perform conversion of certain elements, + * for example String to Integer if a String array comes in and + * should be converted to a Set of Integer objects. + *

    Only called if actually creating a new Collection! + * This is by default not the case if the type of the passed-in Collection + * already matches. Override {@link #alwaysCreateNewCollection()} to + * enforce creating a new Collection in every case. + * @param element the source element + * @return the element to be used in the target Collection + * @see #alwaysCreateNewCollection() + */ + protected Object convertElement(Object element) { + return element; + } + + + /** + * This implementation returns null to indicate that + * there is no appropriate text representation. + */ + public String getAsText() { + return null; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/CustomDateEditor.java b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/CustomDateEditor.java new file mode 100644 index 0000000000..42b094acb6 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/CustomDateEditor.java @@ -0,0 +1,125 @@ +/* + * Copyright 2002-2008 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.propertyeditors; + +import java.beans.PropertyEditorSupport; +import java.text.DateFormat; +import java.text.ParseException; +import java.util.Date; + +import org.springframework.util.StringUtils; + +/** + * Property editor for java.util.Date, + * supporting a custom java.text.DateFormat. + * + *

    This is not meant to be used as system PropertyEditor but rather + * as locale-specific date editor within custom controller code, + * parsing user-entered number strings into Date properties of beans + * and rendering them in the UI form. + * + *

    In web MVC code, this editor will typically be registered with + * binder.registerCustomEditor calls in a custom + * initBinder method. + * + * @author Juergen Hoeller + * @since 28.04.2003 + * @see java.util.Date + * @see java.text.DateFormat + * @see org.springframework.validation.DataBinder#registerCustomEditor + * @see org.springframework.web.servlet.mvc.BaseCommandController#initBinder + */ +public class CustomDateEditor extends PropertyEditorSupport { + + private final DateFormat dateFormat; + + private final boolean allowEmpty; + + private final int exactDateLength; + + + /** + * Create a new CustomDateEditor instance, using the given DateFormat + * for parsing and rendering. + *

    The "allowEmpty" parameter states if an empty String should + * be allowed for parsing, i.e. get interpreted as null value. + * Otherwise, an IllegalArgumentException gets thrown in that case. + * @param dateFormat DateFormat to use for parsing and rendering + * @param allowEmpty if empty strings should be allowed + */ + public CustomDateEditor(DateFormat dateFormat, boolean allowEmpty) { + this.dateFormat = dateFormat; + this.allowEmpty = allowEmpty; + this.exactDateLength = -1; + } + + /** + * Create a new CustomDateEditor instance, using the given DateFormat + * for parsing and rendering. + *

    The "allowEmpty" parameter states if an empty String should + * be allowed for parsing, i.e. get interpreted as null value. + * Otherwise, an IllegalArgumentException gets thrown in that case. + *

    The "exactDateLength" parameter states that IllegalArgumentException gets + * thrown if the String does not exactly match the length specified. This is useful + * because SimpleDateFormat does not enforce strict parsing of the year part, + * not even with setLenient(false). Without an "exactDateLength" + * specified, the "01/01/05" would get parsed to "01/01/0005". + * @param dateFormat DateFormat to use for parsing and rendering + * @param allowEmpty if empty strings should be allowed + * @param exactDateLength the exact expected length of the date String + */ + public CustomDateEditor(DateFormat dateFormat, boolean allowEmpty, int exactDateLength) { + this.dateFormat = dateFormat; + this.allowEmpty = allowEmpty; + this.exactDateLength = exactDateLength; + } + + + /** + * Parse the Date from the given text, using the specified DateFormat. + */ + public void setAsText(String text) throws IllegalArgumentException { + if (this.allowEmpty && !StringUtils.hasText(text)) { + // Treat empty String as null value. + setValue(null); + } + else if (text != null && this.exactDateLength >= 0 && text.length() != this.exactDateLength) { + throw new IllegalArgumentException( + "Could not parse date: it is not exactly" + this.exactDateLength + "characters long"); + } + else { + try { + setValue(this.dateFormat.parse(text)); + } + catch (ParseException ex) { + IllegalArgumentException iae = + new IllegalArgumentException("Could not parse date: " + ex.getMessage()); + iae.initCause(ex); + throw iae; + } + } + } + + /** + * Format the Date as String, using the specified DateFormat. + */ + public String getAsText() { + Date value = (Date) getValue(); + return (value != null ? this.dateFormat.format(value) : ""); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/CustomMapEditor.java b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/CustomMapEditor.java new file mode 100644 index 0000000000..5bab7ded5c --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/CustomMapEditor.java @@ -0,0 +1,199 @@ +/* + * Copyright 2002-2007 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.propertyeditors; + +import java.beans.PropertyEditorSupport; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * Property editor for Maps, converting any source Map + * to a given target Map type. + * + * @author Juergen Hoeller + * @since 2.0.1 + * @see java.util.Map + * @see java.util.SortedMap + */ +public class CustomMapEditor extends PropertyEditorSupport { + + private final Class mapType; + + private final boolean nullAsEmptyMap; + + + /** + * Create a new CustomMapEditor for the given target type, + * keeping an incoming null as-is. + * @param mapType the target type, which needs to be a + * sub-interface of Map or a concrete Map class + * @see java.util.Map + * @see java.util.HashMap + * @see java.util.TreeMap + * @see java.util.LinkedHashMap + */ + public CustomMapEditor(Class mapType) { + this(mapType, false); + } + + /** + * Create a new CustomMapEditor for the given target type. + *

    If the incoming value is of the given type, it will be used as-is. + * If it is a different Map type or an array, it will be converted + * to a default implementation of the given Map type. + * If the value is anything else, a target Map with that single + * value will be created. + *

    The default Map implementations are: TreeMap for SortedMap, + * and LinkedHashMap for Map. + * @param mapType the target type, which needs to be a + * sub-interface of Map or a concrete Map class + * @param nullAsEmptyMap ap whether to convert an incoming null + * value to an empty Map (of the appropriate type) + * @see java.util.Map + * @see java.util.TreeMap + * @see java.util.LinkedHashMap + */ + public CustomMapEditor(Class mapType, boolean nullAsEmptyMap) { + if (mapType == null) { + throw new IllegalArgumentException("Map type is required"); + } + if (!Map.class.isAssignableFrom(mapType)) { + throw new IllegalArgumentException( + "Map type [" + mapType.getName() + "] does not implement [java.util.Map]"); + } + this.mapType = mapType; + this.nullAsEmptyMap = nullAsEmptyMap; + } + + + /** + * Convert the given text value to a Map with a single element. + */ + public void setAsText(String text) throws IllegalArgumentException { + setValue(text); + } + + /** + * Convert the given value to a Map of the target type. + */ + public void setValue(Object value) { + if (value == null && this.nullAsEmptyMap) { + super.setValue(createMap(this.mapType, 0)); + } + else if (value == null || (this.mapType.isInstance(value) && !alwaysCreateNewMap())) { + // Use the source value as-is, as it matches the target type. + super.setValue(value); + } + else if (value instanceof Map) { + // Convert Map elements. + Map source = (Map) value; + Map target = createMap(this.mapType, source.size()); + for (Iterator it = source.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + target.put(convertKey(entry.getKey()), convertValue(entry.getValue())); + } + super.setValue(target); + } + else { + throw new IllegalArgumentException("Value cannot be converted to Map: " + value); + } + } + + /** + * Create a Map of the given type, with the given + * initial capacity (if supported by the Map type). + * @param mapType a sub-interface of Map + * @param initialCapacity the initial capacity + * @return the new Map instance + */ + protected Map createMap(Class mapType, int initialCapacity) { + if (!mapType.isInterface()) { + try { + return (Map) mapType.newInstance(); + } + catch (Exception ex) { + throw new IllegalArgumentException( + "Could not instantiate map class [" + mapType.getName() + "]: " + ex.getMessage()); + } + } + else if (SortedMap.class.equals(mapType)) { + return new TreeMap(); + } + else { + return new LinkedHashMap(initialCapacity); + } + } + + /** + * Return whether to always create a new Map, + * even if the type of the passed-in Map already matches. + *

    Default is "false"; can be overridden to enforce creation of a + * new Map, for example to convert elements in any case. + * @see #convertKey + * @see #convertValue + */ + protected boolean alwaysCreateNewMap() { + return false; + } + + /** + * Hook to convert each encountered Map key. + * The default implementation simply returns the passed-in key as-is. + *

    Can be overridden to perform conversion of certain keys, + * for example from String to Integer. + *

    Only called if actually creating a new Map! + * This is by default not the case if the type of the passed-in Map + * already matches. Override {@link #alwaysCreateNewMap()} to + * enforce creating a new Map in every case. + * @param key the source key + * @return the key to be used in the target Map + * @see #alwaysCreateNewMap + */ + protected Object convertKey(Object key) { + return key; + } + + /** + * Hook to convert each encountered Map value. + * The default implementation simply returns the passed-in value as-is. + *

    Can be overridden to perform conversion of certain values, + * for example from String to Integer. + *

    Only called if actually creating a new Map! + * This is by default not the case if the type of the passed-in Map + * already matches. Override {@link #alwaysCreateNewMap()} to + * enforce creating a new Map in every case. + * @param value the source value + * @return the value to be used in the target Map + * @see #alwaysCreateNewMap + */ + protected Object convertValue(Object value) { + return value; + } + + + /** + * This implementation returns null to indicate that + * there is no appropriate text representation. + */ + public String getAsText() { + return null; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/CustomNumberEditor.java b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/CustomNumberEditor.java new file mode 100644 index 0000000000..3be62066f1 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/CustomNumberEditor.java @@ -0,0 +1,148 @@ +/* + * Copyright 2002-2008 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.propertyeditors; + +import java.beans.PropertyEditorSupport; +import java.text.NumberFormat; + +import org.springframework.util.NumberUtils; +import org.springframework.util.StringUtils; + +/** + * Property editor for any Number subclass such as Short, Integer, Long, + * BigInteger, Float, Double, BigDecimal. Can use a given NumberFormat for + * (locale-specific) parsing and rendering, or alternatively the default + * decode / valueOf / toString methods. + * + *

    This is not meant to be used as system PropertyEditor but rather + * as locale-specific number editor within custom controller code, + * parsing user-entered number strings into Number properties of beans + * and rendering them in the UI form. + * + *

    In web MVC code, this editor will typically be registered with + * binder.registerCustomEditor calls in a custom + * initBinder method. + * + * @author Juergen Hoeller + * @since 06.06.2003 + * @see java.lang.Number + * @see java.text.NumberFormat + * @see org.springframework.validation.DataBinder#registerCustomEditor + * @see org.springframework.web.servlet.mvc.BaseCommandController#initBinder + */ +public class CustomNumberEditor extends PropertyEditorSupport { + + private final Class numberClass; + + private final NumberFormat numberFormat; + + private final boolean allowEmpty; + + + /** + * Create a new CustomNumberEditor instance, using the default + * valueOf methods for parsing and toString + * methods for rendering. + *

    The "allowEmpty" parameter states if an empty String should + * be allowed for parsing, i.e. get interpreted as null value. + * Else, an IllegalArgumentException gets thrown in that case. + * @param numberClass Number subclass to generate + * @param allowEmpty if empty strings should be allowed + * @throws IllegalArgumentException if an invalid numberClass has been specified + * @see org.springframework.util.NumberUtils#parseNumber(String, Class) + * @see Integer#valueOf + * @see Integer#toString + */ + public CustomNumberEditor(Class numberClass, boolean allowEmpty) throws IllegalArgumentException { + this(numberClass, null, allowEmpty); + } + + /** + * Create a new CustomNumberEditor instance, using the given NumberFormat + * for parsing and rendering. + *

    The allowEmpty parameter states if an empty String should + * be allowed for parsing, i.e. get interpreted as null value. + * Else, an IllegalArgumentException gets thrown in that case. + * @param numberClass Number subclass to generate + * @param numberFormat NumberFormat to use for parsing and rendering + * @param allowEmpty if empty strings should be allowed + * @throws IllegalArgumentException if an invalid numberClass has been specified + * @see org.springframework.util.NumberUtils#parseNumber(String, Class, java.text.NumberFormat) + * @see java.text.NumberFormat#parse + * @see java.text.NumberFormat#format + */ + public CustomNumberEditor(Class numberClass, NumberFormat numberFormat, boolean allowEmpty) + throws IllegalArgumentException { + + if (numberClass == null || !Number.class.isAssignableFrom(numberClass)) { + throw new IllegalArgumentException("Property class must be a subclass of Number"); + } + this.numberClass = numberClass; + this.numberFormat = numberFormat; + this.allowEmpty = allowEmpty; + } + + + /** + * Parse the Number from the given text, using the specified NumberFormat. + */ + public void setAsText(String text) throws IllegalArgumentException { + if (this.allowEmpty && !StringUtils.hasText(text)) { + // Treat empty String as null value. + setValue(null); + } + else if (this.numberFormat != null) { + // Use given NumberFormat for parsing text. + setValue(NumberUtils.parseNumber(text, this.numberClass, this.numberFormat)); + } + else { + // Use default valueOf methods for parsing text. + setValue(NumberUtils.parseNumber(text, this.numberClass)); + } + } + + /** + * Coerce a Number value into the required target class, if necessary. + */ + public void setValue(Object value) { + if (value instanceof Number) { + super.setValue(NumberUtils.convertNumberToTargetClass((Number) value, this.numberClass)); + } + else { + super.setValue(value); + } + } + + /** + * Format the Number as String, using the specified NumberFormat. + */ + public String getAsText() { + Object value = getValue(); + if (value == null) { + return ""; + } + if (this.numberFormat != null) { + // Use NumberFormat for rendering value. + return this.numberFormat.format(value); + } + else { + // Use toString method for rendering value. + return value.toString(); + } + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/FileEditor.java b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/FileEditor.java new file mode 100644 index 0000000000..3c4e53ed54 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/FileEditor.java @@ -0,0 +1,116 @@ +/* + * Copyright 2002-2006 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.propertyeditors; + +import java.beans.PropertyEditorSupport; +import java.io.File; +import java.io.IOException; + +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceEditor; +import org.springframework.util.Assert; +import org.springframework.util.ResourceUtils; +import org.springframework.util.StringUtils; + +/** + * Editor for java.io.File, to directly populate a File property + * from a Spring resource location. + * + *

    Supports Spring-style URL notation: any fully qualified standard URL + * ("file:", "http:", etc) and Spring's special "classpath:" pseudo-URL. + * + *

    NOTE: The behavior of this editor has changed in Spring 2.0. + * Previously, it created a File instance directly from a filename. + * As of Spring 2.0, it takes a standard Spring resource location as input; + * this is consistent with URLEditor and InputStreamEditor now. + * + *

    NOTE: In Spring 2.5 the following modification was made. + * If a file name is specified without a URL prefix or without an absolute path + * then we try to locate the file using standard ResourceLoader semantics. + * If the file was not found, then a File instance is created assuming the file + * name refers to a relative file location. + * + * @author Juergen Hoeller + * @author Thomas Risberg + * @since 09.12.2003 + * @see java.io.File + * @see org.springframework.core.io.ResourceEditor + * @see org.springframework.core.io.ResourceLoader + * @see URLEditor + * @see InputStreamEditor + */ +public class FileEditor extends PropertyEditorSupport { + + private final ResourceEditor resourceEditor; + + + /** + * Create a new FileEditor, + * using the default ResourceEditor underneath. + */ + public FileEditor() { + this.resourceEditor = new ResourceEditor(); + } + + /** + * Create a new FileEditor, + * using the given ResourceEditor underneath. + * @param resourceEditor the ResourceEditor to use + */ + public FileEditor(ResourceEditor resourceEditor) { + Assert.notNull(resourceEditor, "ResourceEditor must not be null"); + this.resourceEditor = resourceEditor; + } + + + public void setAsText(String text) throws IllegalArgumentException { + // Check whether we got an absolute file path without "file:" prefix. + // For backwards compatibility, we'll consider those as straight file path. + if (StringUtils.hasText(text) && !ResourceUtils.isUrl(text)) { + File file = new File(text); + if (file.isAbsolute()) { + setValue(file); + return; + } + } + + // Proceed with standard resource location parsing. + this.resourceEditor.setAsText(text); + Resource resource = (Resource) this.resourceEditor.getValue(); + // Non URLs will be treated as relative paths if the resource was not found + if(ResourceUtils.isUrl(text) || resource.exists()) { + try { + setValue(resource != null ? resource.getFile() : null); + } + catch (IOException ex) { + throw new IllegalArgumentException( + "Could not retrieve File for " + resource + ": " + ex.getMessage()); + } + } + else { + // Create a relative File reference and hope for the best + File file = new File(text); + setValue(file); + } + } + + public String getAsText() { + File value = (File) getValue(); + return (value != null ? value.getPath() : ""); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/InputStreamEditor.java b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/InputStreamEditor.java new file mode 100644 index 0000000000..5b0e5a5f5f --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/InputStreamEditor.java @@ -0,0 +1,89 @@ +/* + * Copyright 2002-2006 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.propertyeditors; + +import java.beans.PropertyEditorSupport; +import java.io.IOException; + +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceEditor; +import org.springframework.util.Assert; + +/** + * One-way PropertyEditor, which can convert from a text string to a + * java.io.InputStream, allowing InputStream properties + * to be set directly as a text string. + * + *

    Supports Spring-style URL notation: any fully qualified standard URL + * ("file:", "http:", etc) and Spring's special "classpath:" pseudo-URL. + * + *

    Note that in the default usage, the stream is not closed by Spring itself! + * + * @author Juergen Hoeller + * @since 1.0.1 + * @see java.io.InputStream + * @see org.springframework.core.io.ResourceEditor + * @see org.springframework.core.io.ResourceLoader + * @see URLEditor + * @see FileEditor + */ +public class InputStreamEditor extends PropertyEditorSupport { + + private final ResourceEditor resourceEditor; + + + /** + * Create a new InputStreamEditor, + * using the default ResourceEditor underneath. + */ + public InputStreamEditor() { + this.resourceEditor = new ResourceEditor(); + } + + /** + * Create a new InputStreamEditor, + * using the given ResourceEditor underneath. + * @param resourceEditor the ResourceEditor to use + */ + public InputStreamEditor(ResourceEditor resourceEditor) { + Assert.notNull(resourceEditor, "ResourceEditor must not be null"); + this.resourceEditor = resourceEditor; + } + + + public void setAsText(String text) throws IllegalArgumentException { + this.resourceEditor.setAsText(text); + Resource resource = (Resource) this.resourceEditor.getValue(); + try { + setValue(resource != null ? resource.getInputStream() : null); + } + catch (IOException ex) { + throw new IllegalArgumentException( + "Could not retrieve InputStream for " + resource + ": " + ex.getMessage()); + } + } + + /** + * This implementation returns null to indicate that + * there is no appropriate text representation. + */ + public String getAsText() { + return null; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/LocaleEditor.java b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/LocaleEditor.java new file mode 100644 index 0000000000..92d4eeaace --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/LocaleEditor.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2005 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.propertyeditors; + +import java.beans.PropertyEditorSupport; + +import org.springframework.util.StringUtils; + +/** + * Editor for java.util.Locale, to directly populate a Locale property. + * + *

    Expects the same syntax as Locale's toString, i.e. language + + * optionally country + optionally variant, separated by "_" (e.g. "en", "en_US"). + * Also accepts spaces as separators, as alternative to underscores. + * + * @author Juergen Hoeller + * @since 26.05.2003 + * @see java.util.Locale + * @see org.springframework.util.StringUtils#parseLocaleString + */ +public class LocaleEditor extends PropertyEditorSupport { + + public void setAsText(String text) { + setValue(StringUtils.parseLocaleString(text)); + } + + public String getAsText() { + Object value = getValue(); + return (value != null ? value.toString() : ""); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/PatternEditor.java b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/PatternEditor.java new file mode 100644 index 0000000000..0a0d82c73d --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/PatternEditor.java @@ -0,0 +1,67 @@ +/* + * Copyright 2002-2007 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.propertyeditors; + +import java.beans.PropertyEditorSupport; +import java.util.regex.Pattern; + +/** + * Editor for java.util.regex.Pattern, to directly populate a Pattern property. + * Expects the same syntax as Pattern's compile method. + * + * @author Juergen Hoeller + * @since 2.0.1 + * @see java.util.regex.Pattern + * @see java.util.regex.Pattern#compile(String) + */ +public class PatternEditor extends PropertyEditorSupport { + + private final int flags; + + + /** + * Create a new PatternEditor with default settings. + */ + public PatternEditor() { + this.flags = 0; + } + + /** + * Create a new PatternEditor with the given settings. + * @param flags the java.util.regex.Pattern flags to apply + * @see java.util.regex.Pattern#compile(String, int) + * @see java.util.regex.Pattern#CASE_INSENSITIVE + * @see java.util.regex.Pattern#MULTILINE + * @see java.util.regex.Pattern#DOTALL + * @see java.util.regex.Pattern#UNICODE_CASE + * @see java.util.regex.Pattern#CANON_EQ + */ + public PatternEditor(int flags) { + this.flags = flags; + } + + + public void setAsText(String text) { + setValue(text != null ? Pattern.compile(text, this.flags) : null); + } + + public String getAsText() { + Pattern value = (Pattern) getValue(); + return (value != null ? value.pattern() : ""); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/PropertiesEditor.java b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/PropertiesEditor.java new file mode 100644 index 0000000000..932ff42118 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/PropertiesEditor.java @@ -0,0 +1,83 @@ +/* + * Copyright 2002-2007 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.propertyeditors; + +import java.beans.PropertyEditorSupport; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.Map; +import java.util.Properties; + +/** + * Custom {@link java.beans.PropertyEditor} for {@link Properties} objects. + * + *

    Handles conversion from content {@link String} to Properties object. + * Also handles {@link Map} to Properties conversion, for populating + * a Properties object via XML "map" entries. + * + *

    The required format is defined in the standard Properties + * documentation. Each property must be on a new line. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see java.util.Properties#load + */ +public class PropertiesEditor extends PropertyEditorSupport { + + /** + * Any of these characters, if they're first after whitespace or first + * on a line, mean that the line is a comment and should be ignored. + */ + private final static String COMMENT_MARKERS = "#!"; + + + /** + * Convert {@link String} into {@link Properties}, considering it as + * properties content. + * @param text the text to be so converted + */ + public void setAsText(String text) throws IllegalArgumentException { + Properties props = new Properties(); + if (text != null) { + try { + // Must use the ISO-8859-1 encoding because Properties.load(stream) expects it. + props.load(new ByteArrayInputStream(text.getBytes("ISO-8859-1"))); + } + catch (IOException ex) { + // Should never happen. + throw new IllegalArgumentException( + "Failed to parse [" + text + "] into Properties: " + ex.getMessage()); + } + } + setValue(props); + } + + /** + * Take {@link Properties} as-is; convert {@link Map} into Properties. + */ + public void setValue(Object value) { + if (!(value instanceof Properties) && value instanceof Map) { + Properties props = new Properties(); + props.putAll((Map) value); + super.setValue(props); + } + else { + super.setValue(value); + } + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/ResourceBundleEditor.java b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/ResourceBundleEditor.java new file mode 100644 index 0000000000..eb8406d73f --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/ResourceBundleEditor.java @@ -0,0 +1,104 @@ +/* + * Copyright 2002-2006 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.propertyeditors; + +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import java.beans.PropertyEditorSupport; +import java.util.Locale; +import java.util.ResourceBundle; + +/** + * {@link java.beans.PropertyEditor} implementation for + * {@link java.util.ResourceBundle ResourceBundles}. + * + *

    Only supports conversion from a String, but not + * to a String. + * + * Find below some examples of using this class in a + * (properly configured) Spring container using XML-based metadata: + * + *

     <bean id="errorDialog" class="...">
    + *    <!--
    + *        the 'messages' property is of type java.util.ResourceBundle.
    + *        the 'DialogMessages.properties' file exists at the root of the CLASSPATH
    + *    -->
    + *    <property name="messages" value="DialogMessages"/>
    + * </bean>
    + * + *
     <bean id="errorDialog" class="...">
    + *    <!--
    + *        the 'DialogMessages.properties' file exists in the 'com/messages' package
    + *    -->
    + *    <property name="messages" value="com/messages/DialogMessages"/>
    + * </bean>
    + * + *

    A 'properly configured' Spring {@link org.springframework.context.ApplicationContext container} + * might contain a {@link org.springframework.beans.factory.config.CustomEditorConfigurer} + * definition such that the conversion can be effected transparently: + * + *

     <bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    + *    <property name="customEditors">
    + *        <map>
    + *            <entry key="java.util.ResourceBundle">
    + *                <bean class="org.springframework.beans.propertyeditors.ResourceBundleEditor"/>
    + *            </entry>
    + *        </map>
    + *    </property>
    + * </bean>
    + * + *

    Please note that this {@link java.beans.PropertyEditor} is + * not registered by default with any of the Spring infrastructure. + * + *

    Thanks to David Leal Valmana for the suggestion and initial prototype. + * + * @author Rick Evans + * @since 2.0 + */ +public class ResourceBundleEditor extends PropertyEditorSupport { + + /** + * The separator used to distinguish between the base name and the + * locale (if any) when {@link #setAsText(String) converting from a String}. + */ + public static final String BASE_NAME_SEPARATOR = "_"; + + + public void setAsText(String text) throws IllegalArgumentException { + Assert.hasText(text, "'text' must not be empty"); + ResourceBundle bundle; + String rawBaseName = text.trim(); + int indexOfBaseNameSeparator = rawBaseName.indexOf(BASE_NAME_SEPARATOR); + if (indexOfBaseNameSeparator == -1) { + bundle = ResourceBundle.getBundle(rawBaseName); + } else { + // it potentially has locale information + String baseName = rawBaseName.substring(0, indexOfBaseNameSeparator); + if (!StringUtils.hasText(baseName)) { + throw new IllegalArgumentException("Bad ResourceBundle name : received '" + text + "' as argument to 'setAsText(String value)'."); + } + String localeString = rawBaseName.substring(indexOfBaseNameSeparator + 1); + Locale locale = StringUtils.parseLocaleString(localeString); + bundle = (StringUtils.hasText(localeString)) + ? ResourceBundle.getBundle(baseName, locale) + : ResourceBundle.getBundle(baseName); + } + setValue(bundle); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/StringArrayPropertyEditor.java b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/StringArrayPropertyEditor.java new file mode 100644 index 0000000000..11aedb09e7 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/StringArrayPropertyEditor.java @@ -0,0 +1,107 @@ +/* + * Copyright 2002-2008 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.propertyeditors; + +import java.beans.PropertyEditorSupport; + +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * Custom {@link java.beans.PropertyEditor} for String arrays. + * + *

    Strings must be in CSV format, with a customizable separator. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see org.springframework.util.StringUtils#delimitedListToStringArray + * @see org.springframework.util.StringUtils#arrayToDelimitedString + */ +public class StringArrayPropertyEditor extends PropertyEditorSupport { + + /** + * Default separator for splitting a String: a comma (",") + */ + public static final String DEFAULT_SEPARATOR = ","; + + + private final String separator; + + private final String charsToDelete; + + private final boolean emptyArrayAsNull; + + + /** + * Create a new StringArrayPropertyEditor with the default separator + * (a comma). + *

    An empty text (without elements) will be turned into an empty array. + */ + public StringArrayPropertyEditor() { + this(DEFAULT_SEPARATOR, null, false); + } + + /** + * Create a new StringArrayPropertyEditor with the given separator. + *

    An empty text (without elements) will be turned into an empty array. + * @param separator the separator to use for splitting a {@link String} + */ + public StringArrayPropertyEditor(String separator) { + this(separator, null, false); + } + + /** + * Create a new StringArrayPropertyEditor with the given separator. + * @param separator the separator to use for splitting a {@link String} + * @param emptyArrayAsNull true if an empty String array + * is to be transformed into null + */ + public StringArrayPropertyEditor(String separator, boolean emptyArrayAsNull) { + this(separator, null, emptyArrayAsNull); + } + + /** + * Create a new StringArrayPropertyEditor with the given separator. + * @param separator the separator to use for splitting a {@link String} + * @param charsToDelete a set of characters to delete, in addition to + * trimming an input String. Useful for deleting unwanted line breaks: + * e.g. "\r\n\f" will delete all new lines and line feeds in a String. + * @param emptyArrayAsNull true if an empty String array + * is to be transformed into null + */ + public StringArrayPropertyEditor(String separator, String charsToDelete, boolean emptyArrayAsNull) { + this.separator = separator; + this.charsToDelete = charsToDelete; + this.emptyArrayAsNull = emptyArrayAsNull; + } + + + public void setAsText(String text) throws IllegalArgumentException { + String[] array = StringUtils.delimitedListToStringArray(text, this.separator, this.charsToDelete); + if (this.emptyArrayAsNull && array.length == 0) { + setValue(null); + } + else { + setValue(array); + } + } + + public String getAsText() { + return StringUtils.arrayToDelimitedString(ObjectUtils.toObjectArray(getValue()), this.separator); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/StringTrimmerEditor.java b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/StringTrimmerEditor.java new file mode 100644 index 0000000000..b253d2fd1f --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/StringTrimmerEditor.java @@ -0,0 +1,87 @@ +/* + * Copyright 2002-2007 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.propertyeditors; + +import java.beans.PropertyEditorSupport; + +import org.springframework.util.StringUtils; + +/** + * Property editor that trims Strings. + * + *

    Optionally allows transforming an empty string into a null value. + * Needs to be explictly registered, e.g. for command binding. + * + * @author Juergen Hoeller + * @see org.springframework.validation.DataBinder#registerCustomEditor + * @see org.springframework.web.servlet.mvc.BaseCommandController#initBinder + */ +public class StringTrimmerEditor extends PropertyEditorSupport { + + private final String charsToDelete; + + private final boolean emptyAsNull; + + + /** + * Create a new StringTrimmerEditor. + * @param emptyAsNull true if an empty String is to be + * transformed into null + */ + public StringTrimmerEditor(boolean emptyAsNull) { + this.charsToDelete = null; + this.emptyAsNull = emptyAsNull; + } + + /** + * Create a new StringTrimmerEditor. + * @param charsToDelete a set of characters to delete, in addition to + * trimming an input String. Useful for deleting unwanted line breaks: + * e.g. "\r\n\f" will delete all new lines and line feeds in a String. + * @param emptyAsNull true if an empty String is to be + * transformed into null + */ + public StringTrimmerEditor(String charsToDelete, boolean emptyAsNull) { + this.charsToDelete = charsToDelete; + this.emptyAsNull = emptyAsNull; + } + + + public void setAsText(String text) { + if (text == null) { + setValue(null); + } + else { + String value = text.trim(); + if (this.charsToDelete != null) { + value = StringUtils.deleteAny(value, this.charsToDelete); + } + if (this.emptyAsNull && "".equals(value)) { + setValue(null); + } + else { + setValue(value); + } + } + } + + public String getAsText() { + Object value = getValue(); + return (value != null ? value.toString() : ""); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/URIEditor.java b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/URIEditor.java new file mode 100644 index 0000000000..efb5e9aa55 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/URIEditor.java @@ -0,0 +1,119 @@ +/* + * Copyright 2002-2007 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.propertyeditors; + +import java.beans.PropertyEditorSupport; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.util.ClassUtils; +import org.springframework.util.ResourceUtils; +import org.springframework.util.StringUtils; + +/** + * Editor for java.net.URI, to directly populate a URI property + * instead of using a String property as bridge. + * + *

    Supports Spring-style URI notation: any fully qualified standard URI + * ("file:", "http:", etc) and Spring's special "classpath:" pseudo-URL, + * which will be resolved to a corresponding URI. + * + *

    Note: A URI is more relaxed than a URL in that it does not require + * a valid protocol to be specified. Any scheme within a valid URI syntax + * is allowed, even without a matching protocol handler being registered. + * + * @author Juergen Hoeller + * @since 2.0.2 + * @see java.net.URI + * @see URLEditor + */ +public class URIEditor extends PropertyEditorSupport { + + private final ClassLoader classLoader; + + + /** + * Create a new URIEditor, converting "classpath:" locations into + * standard URIs (not trying to resolve them into physical resources). + */ + public URIEditor() { + this.classLoader = null; + } + + /** + * Create a new URIEditor, using the given ClassLoader to resolve + * "classpath:" locations into physical resource URLs. + * @param classLoader the ClassLoader to use for resolving "classpath:" locations + * (may be null to indicate the default ClassLoader) + */ + public URIEditor(ClassLoader classLoader) { + this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader()); + } + + + public void setAsText(String text) throws IllegalArgumentException { + if (StringUtils.hasText(text)) { + String uri = text.trim(); + if (this.classLoader != null && uri.startsWith(ResourceUtils.CLASSPATH_URL_PREFIX)) { + ClassPathResource resource = + new ClassPathResource(uri.substring(ResourceUtils.CLASSPATH_URL_PREFIX.length()), this.classLoader); + try { + String url = resource.getURL().toString(); + setValue(createURI(url)); + } + catch (IOException ex) { + throw new IllegalArgumentException("Could not retrieve URI for " + resource + ": " + ex.getMessage()); + } + catch (URISyntaxException ex) { + throw new IllegalArgumentException("Invalid URI syntax: " + ex); + } + } + else { + try { + setValue(createURI(uri)); + } + catch (URISyntaxException ex) { + throw new IllegalArgumentException("Invalid URI syntax: " + ex); + } + } + } + else { + setValue(null); + } + } + + /** + * Create a URI instance for the given (resolved) String value. + *

    The default implementation uses the URI(String) + * constructor, replacing spaces with "%20" quotes first. + * @param value the value to convert into a URI instance + * @return the URI instance + * @throws URISyntaxException if URI conversion failed + */ + protected URI createURI(String value) throws URISyntaxException { + return new URI(StringUtils.replace(value, " ", "%20")); + } + + + public String getAsText() { + URI value = (URI) getValue(); + return (value != null ? value.toString() : ""); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/URLEditor.java b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/URLEditor.java new file mode 100644 index 0000000000..c5c89ebf13 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/URLEditor.java @@ -0,0 +1,85 @@ +/* + * Copyright 2002-2006 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.propertyeditors; + +import java.beans.PropertyEditorSupport; +import java.io.IOException; +import java.net.URL; + +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceEditor; +import org.springframework.util.Assert; + +/** + * Editor for java.net.URL, to directly populate a URL property + * instead of using a String property as bridge. + * + *

    Supports Spring-style URL notation: any fully qualified standard URL + * ("file:", "http:", etc) and Spring's special "classpath:" pseudo-URL, + * as well as Spring's context-specific relative file paths. + * + *

    Note: A URL must specify a valid protocol, else it will be rejected + * upfront. However, the target resource does not necessarily have to exist + * at the time of URL creation; this depends on the specific resource type. + * + * @author Juergen Hoeller + * @since 15.12.2003 + * @see java.net.URL + * @see org.springframework.core.io.ResourceEditor + * @see org.springframework.core.io.ResourceLoader + * @see FileEditor + * @see InputStreamEditor + */ +public class URLEditor extends PropertyEditorSupport { + + private final ResourceEditor resourceEditor; + + + /** + * Create a new URLEditor, using the default ResourceEditor underneath. + */ + public URLEditor() { + this.resourceEditor = new ResourceEditor(); + } + + /** + * Create a new URLEditor, using the given ResourceEditor underneath. + * @param resourceEditor the ResourceEditor to use + */ + public URLEditor(ResourceEditor resourceEditor) { + Assert.notNull(resourceEditor, "ResourceEditor must not be null"); + this.resourceEditor = resourceEditor; + } + + + public void setAsText(String text) throws IllegalArgumentException { + this.resourceEditor.setAsText(text); + Resource resource = (Resource) this.resourceEditor.getValue(); + try { + setValue(resource != null ? resource.getURL() : null); + } + catch (IOException ex) { + throw new IllegalArgumentException("Could not retrieve URL for " + resource + ": " + ex.getMessage()); + } + } + + public String getAsText() { + URL value = (URL) getValue(); + return (value != null ? value.toExternalForm() : ""); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/package.html b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/package.html new file mode 100644 index 0000000000..de4e3c5e42 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/package.html @@ -0,0 +1,12 @@ + + + +Properties editors used to convert from String values to object +types such as java.util.Properties. + +

    Some of these editors are registered automatically by BeanWrapperImpl. +"CustomXxxEditor" classes are intended for manual registration in +specific binding processes, as they are localized or the like. + + + diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/support/ArgumentConvertingMethodInvoker.java b/org.springframework.beans/src/main/java/org/springframework/beans/support/ArgumentConvertingMethodInvoker.java new file mode 100644 index 0000000000..3f6f341d5b --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/support/ArgumentConvertingMethodInvoker.java @@ -0,0 +1,178 @@ +/* + * Copyright 2002-2008 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.support; + +import java.beans.PropertyEditor; +import java.lang.reflect.Method; + +import org.springframework.beans.PropertyEditorRegistry; +import org.springframework.beans.SimpleTypeConverter; +import org.springframework.beans.TypeConverter; +import org.springframework.beans.TypeMismatchException; +import org.springframework.util.MethodInvoker; +import org.springframework.util.ReflectionUtils; + +/** + * Subclass of {@link MethodInvoker} that tries to convert the given + * arguments for the actual target method via a {@link TypeConverter}. + * + *

    Supports flexible argument conversions, in particular for + * invoking a specific overloaded method. + * + * @author Juergen Hoeller + * @since 1.1 + * @see org.springframework.beans.BeanWrapperImpl#convertIfNecessary + */ +public class ArgumentConvertingMethodInvoker extends MethodInvoker { + + private TypeConverter typeConverter; + + private boolean useDefaultConverter = true; + + + /** + * Set a TypeConverter to use for argument type conversion. + *

    Default is a {@link org.springframework.beans.SimpleTypeConverter}. + * Can be overridden with any TypeConverter implementation, typically + * a pre-configured SimpleTypeConverter or a BeanWrapperImpl instance. + * @see org.springframework.beans.SimpleTypeConverter + * @see org.springframework.beans.BeanWrapperImpl + */ + public void setTypeConverter(TypeConverter typeConverter) { + this.typeConverter = typeConverter; + this.useDefaultConverter = false; + } + + /** + * Return the TypeConverter used for argument type conversion. + *

    Can be cast to {@link org.springframework.beans.PropertyEditorRegistry} + * if direct access to the underlying PropertyEditors is desired + * (provided that the present TypeConverter actually implements the + * PropertyEditorRegistry interface). + */ + public TypeConverter getTypeConverter() { + if (this.typeConverter == null && this.useDefaultConverter) { + this.typeConverter = getDefaultTypeConverter(); + } + return this.typeConverter; + } + + /** + * Obtain the default TypeConverter for this method invoker. + *

    Called if no explicit TypeConverter has been specified. + * The default implementation builds a + * {@link org.springframework.beans.SimpleTypeConverter}. + * Can be overridden in subclasses. + */ + protected TypeConverter getDefaultTypeConverter() { + return new SimpleTypeConverter(); + } + + /** + * Register the given custom property editor for all properties of the given type. + *

    Typically used in conjunction with the default + * {@link org.springframework.beans.SimpleTypeConverter}; will work with any + * TypeConverter that implements the PropertyEditorRegistry interface as well. + * @param requiredType type of the property + * @param propertyEditor editor to register + * @see #setTypeConverter + * @see org.springframework.beans.PropertyEditorRegistry#registerCustomEditor + */ + public void registerCustomEditor(Class requiredType, PropertyEditor propertyEditor) { + TypeConverter converter = getTypeConverter(); + if (!(converter instanceof PropertyEditorRegistry)) { + throw new IllegalStateException( + "TypeConverter does not implement PropertyEditorRegistry interface: " + converter); + } + ((PropertyEditorRegistry) converter).registerCustomEditor(requiredType, propertyEditor); + } + + + /** + * This implementation looks for a method with matching parameter types. + * @see #doFindMatchingMethod + */ + protected Method findMatchingMethod() { + Method matchingMethod = super.findMatchingMethod(); + // Second pass: look for method where arguments can be converted to parameter types. + if (matchingMethod == null) { + // Interpret argument array as individual method arguments. + matchingMethod = doFindMatchingMethod(getArguments()); + } + if (matchingMethod == null) { + // Interpret argument array as single method argument of array type. + matchingMethod = doFindMatchingMethod(new Object[] {getArguments()}); + } + return matchingMethod; + } + + /** + * Actually find a method with matching parameter type, i.e. where each + * argument value is assignable to the corresponding parameter type. + * @param arguments the argument values to match against method parameters + * @return a matching method, or null if none + */ + protected Method doFindMatchingMethod(Object[] arguments) { + TypeConverter converter = getTypeConverter(); + if (converter != null) { + String targetMethod = getTargetMethod(); + Method matchingMethod = null; + int argCount = arguments.length; + Method[] candidates = ReflectionUtils.getAllDeclaredMethods(getTargetClass()); + int minTypeDiffWeight = Integer.MAX_VALUE; + Object[] argumentsToUse = null; + + for (int i = 0; i < candidates.length; i++) { + Method candidate = candidates[i]; + if (candidate.getName().equals(targetMethod)) { + // Check if the inspected method has the correct number of parameters. + Class[] paramTypes = candidate.getParameterTypes(); + if (paramTypes.length == argCount) { + Object[] convertedArguments = new Object[argCount]; + boolean match = true; + for (int j = 0; j < argCount && match; j++) { + // Verify that the supplied argument is assignable to the method parameter. + try { + convertedArguments[j] = converter.convertIfNecessary(arguments[j], paramTypes[j]); + } + catch (TypeMismatchException ex) { + // Ignore -> simply doesn't match. + match = false; + } + } + if (match) { + int typeDiffWeight = getTypeDifferenceWeight(paramTypes, convertedArguments); + if (typeDiffWeight < minTypeDiffWeight) { + minTypeDiffWeight = typeDiffWeight; + matchingMethod = candidate; + argumentsToUse = convertedArguments; + } + } + } + } + } + + if (matchingMethod != null) { + setArguments(argumentsToUse); + return matchingMethod; + } + } + + return null; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/support/MutableSortDefinition.java b/org.springframework.beans/src/main/java/org/springframework/beans/support/MutableSortDefinition.java new file mode 100644 index 0000000000..4992a3869c --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/support/MutableSortDefinition.java @@ -0,0 +1,171 @@ +/* + * Copyright 2002-2007 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.support; + +import java.io.Serializable; + +import org.springframework.util.StringUtils; + +/** + * Mutable implementation of the {@link SortDefinition} interface. + * Supports toggling the ascending value on setting the same property again. + * + * @author Juergen Hoeller + * @author Jean-Pierre Pawlak + * @since 26.05.2003 + * @see #setToggleAscendingOnProperty + */ +public class MutableSortDefinition implements SortDefinition, Serializable { + + private String property = ""; + + private boolean ignoreCase = true; + + private boolean ascending = true; + + private boolean toggleAscendingOnProperty = false; + + + /** + * Create an empty MutableSortDefinition, + * to be populated via its bean properties. + * @see #setProperty + * @see #setIgnoreCase + * @see #setAscending + */ + public MutableSortDefinition() { + } + + /** + * Copy constructor: create a new MutableSortDefinition + * that mirrors the given sort definition. + * @param source the original sort definition + */ + public MutableSortDefinition(SortDefinition source) { + this.property = source.getProperty(); + this.ignoreCase = source.isIgnoreCase(); + this.ascending = source.isAscending(); + } + + /** + * Create a MutableSortDefinition for the given settings. + * @param property the property to compare + * @param ignoreCase whether upper and lower case in String values should be ignored + * @param ascending whether to sort ascending (true) or descending (false) + */ + public MutableSortDefinition(String property, boolean ignoreCase, boolean ascending) { + this.property = property; + this.ignoreCase = ignoreCase; + this.ascending = ascending; + } + + /** + * Create a new MutableSortDefinition. + * @param toggleAscendingOnSameProperty whether to toggle the ascending flag + * if the same property gets set again (that is, setProperty gets + * called with already set property name again). + */ + public MutableSortDefinition(boolean toggleAscendingOnSameProperty) { + this.toggleAscendingOnProperty = toggleAscendingOnSameProperty; + } + + + /** + * Set the property to compare. + *

    If the property was the same as the current, the sort is reversed if + * "toggleAscendingOnProperty" is activated, else simply ignored. + * @see #setToggleAscendingOnProperty + */ + public void setProperty(String property) { + if (!StringUtils.hasLength(property)) { + this.property = ""; + } + else { + // Implicit toggling of ascending? + if (isToggleAscendingOnProperty()) { + this.ascending = (!property.equals(this.property) || !this.ascending); + } + this.property = property; + } + } + + public String getProperty() { + return this.property; + } + + /** + * Set whether upper and lower case in String values should be ignored. + */ + public void setIgnoreCase(boolean ignoreCase) { + this.ignoreCase = ignoreCase; + } + + public boolean isIgnoreCase() { + return this.ignoreCase; + } + + /** + * Set whether to sort ascending (true) or descending (false). + */ + public void setAscending(boolean ascending) { + this.ascending = ascending; + } + + public boolean isAscending() { + return this.ascending; + } + + /** + * Set whether to toggle the ascending flag if the same property gets set again + * (that is, {@link #setProperty} gets called with already set property name again). + *

    This is particularly useful for parameter binding through a web request, + * where clicking on the field header again might be supposed to trigger a + * resort for the same field but opposite order. + */ + public void setToggleAscendingOnProperty(boolean toggleAscendingOnProperty) { + this.toggleAscendingOnProperty = toggleAscendingOnProperty; + } + + /** + * Return whether to toggle the ascending flag if the same property gets set again + * (that is, {@link #setProperty} gets called with already set property name again). + */ + public boolean isToggleAscendingOnProperty() { + return this.toggleAscendingOnProperty; + } + + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof SortDefinition)) { + return false; + } + SortDefinition otherSd = (SortDefinition) other; + return (getProperty().equals(otherSd.getProperty()) && + isAscending() == otherSd.isAscending() && isIgnoreCase() == otherSd.isIgnoreCase()); + } + + public int hashCode() { + int hashCode = getProperty().hashCode(); + hashCode = 29 * hashCode + (isIgnoreCase() ? 1 : 0); + hashCode = 29 * hashCode + (isAscending() ? 1 : 0); + return hashCode; + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/support/PagedListHolder.java b/org.springframework.beans/src/main/java/org/springframework/beans/support/PagedListHolder.java new file mode 100644 index 0000000000..8262a1fb6d --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/support/PagedListHolder.java @@ -0,0 +1,332 @@ +/* + * Copyright 2002-2008 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.support; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.springframework.util.Assert; + +/** + * PagedListHolder is a simple state holder for handling lists of objects, + * separating them into pages. Page numbering starts with 0. + * + *

    This is mainly targetted at usage in web UIs. Typically, an instance will be + * instantiated with a list of beans, put into the session, and exported as model. + * The properties can all be set/get programmatically, but the most common way will + * be data binding, i.e. populating the bean from request parameters. The getters + * will mainly be used by the view. + * + *

    Supports sorting the underlying list via a {@link SortDefinition} implementation, + * available as property "sort". By default, a {@link MutableSortDefinition} instance + * will be used, toggling the ascending value on setting the same property again. + * + *

    The data binding names have to be called "pageSize" and "sort.ascending", + * as expected by BeanWrapper. Note that the names and the nesting syntax match + * the respective JSTL EL expressions, like "myModelAttr.pageSize" and + * "myModelAttr.sort.ascending". + * + * @author Juergen Hoeller + * @since 19.05.2003 + * @see #getPageList() + * @see org.springframework.beans.support.MutableSortDefinition + */ +public class PagedListHolder implements Serializable { + + public static final int DEFAULT_PAGE_SIZE = 10; + + public static final int DEFAULT_MAX_LINKED_PAGES = 10; + + + private List source; + + private Date refreshDate; + + private SortDefinition sort; + + private SortDefinition sortUsed; + + private int pageSize = DEFAULT_PAGE_SIZE; + + private int page = 0; + + private boolean newPageSet; + + private int maxLinkedPages = DEFAULT_MAX_LINKED_PAGES; + + + /** + * Create a new holder instance. + * You'll need to set a source list to be able to use the holder. + * @see #setSource + */ + public PagedListHolder() { + this(new ArrayList(0)); + } + + /** + * Create a new holder instance with the given source list, starting with + * a default sort definition (with "toggleAscendingOnProperty" activated). + * @param source the source List + * @see MutableSortDefinition#setToggleAscendingOnProperty + */ + public PagedListHolder(List source) { + this(source, new MutableSortDefinition(true)); + } + + /** + * Create a new holder instance with the given source list. + * @param source the source List + * @param sort the SortDefinition to start with + */ + public PagedListHolder(List source, SortDefinition sort) { + setSource(source); + setSort(sort); + } + + + /** + * Set the source list for this holder. + */ + public void setSource(List source) { + Assert.notNull(source, "Source List must not be null"); + this.source = source; + this.refreshDate = new Date(); + this.sortUsed = null; + } + + /** + * Return the source list for this holder. + */ + public List getSource() { + return this.source; + } + + /** + * Return the last time the list has been fetched from the source provider. + */ + public Date getRefreshDate() { + return this.refreshDate; + } + + /** + * Set the sort definition for this holder. + * Typically an instance of MutableSortDefinition. + * @see org.springframework.beans.support.MutableSortDefinition + */ + public void setSort(SortDefinition sort) { + this.sort = sort; + } + + /** + * Return the sort definition for this holder. + */ + public SortDefinition getSort() { + return this.sort; + } + + /** + * Set the current page size. + * Resets the current page number if changed. + *

    Default value is 10. + */ + public void setPageSize(int pageSize) { + if (pageSize != this.pageSize) { + this.pageSize = pageSize; + if (!this.newPageSet) { + this.page = 0; + } + } + } + + /** + * Return the current page size. + */ + public int getPageSize() { + return this.pageSize; + } + + /** + * Set the current page number. + * Page numbering starts with 0. + */ + public void setPage(int page) { + this.page = page; + this.newPageSet = true; + } + + /** + * Return the current page number. + * Page numbering starts with 0. + */ + public int getPage() { + this.newPageSet = false; + if (this.page >= getPageCount()) { + this.page = getPageCount() - 1; + } + return this.page; + } + + /** + * Set the maximum number of page links to a few pages around the current one. + */ + public void setMaxLinkedPages(int maxLinkedPages) { + this.maxLinkedPages = maxLinkedPages; + } + + /** + * Return the maximum number of page links to a few pages around the current one. + */ + public int getMaxLinkedPages() { + return this.maxLinkedPages; + } + + + /** + * Return the number of pages for the current source list. + */ + public int getPageCount() { + float nrOfPages = (float) getNrOfElements() / getPageSize(); + return (int) ((nrOfPages > (int) nrOfPages || nrOfPages == 0.0) ? nrOfPages + 1 : nrOfPages); + } + + /** + * Return if the current page is the first one. + */ + public boolean isFirstPage() { + return getPage() == 0; + } + + /** + * Return if the current page is the last one. + */ + public boolean isLastPage() { + return getPage() == getPageCount() -1; + } + + /** + * Switch to previous page. + * Will stay on first page if already on first page. + */ + public void previousPage() { + if (!isFirstPage()) { + this.page--; + } + } + + /** + * Switch to next page. + * Will stay on last page if already on last page. + */ + public void nextPage() { + if (!isLastPage()) { + this.page++; + } + } + + /** + * Return the total number of elements in the source list. + */ + public int getNrOfElements() { + return getSource().size(); + } + + /** + * Return the element index of the first element on the current page. + * Element numbering starts with 0. + */ + public int getFirstElementOnPage() { + return (getPageSize() * getPage()); + } + + /** + * Return the element index of the last element on the current page. + * Element numbering starts with 0. + */ + public int getLastElementOnPage() { + int endIndex = getPageSize() * (getPage() + 1); + int size = getNrOfElements(); + return (endIndex > size ? size : endIndex) - 1; + } + + /** + * Return a sub-list representing the current page. + */ + public List getPageList() { + return getSource().subList(getFirstElementOnPage(), getLastElementOnPage() + 1); + } + + /** + * Return the first page to which create a link around the current page. + */ + public int getFirstLinkedPage() { + return Math.max(0, getPage() - (getMaxLinkedPages() / 2)); + } + + /** + * Return the last page to which create a link around the current page. + */ + public int getLastLinkedPage() { + return Math.min(getFirstLinkedPage() + getMaxLinkedPages() - 1, getPageCount() - 1); + } + + + /** + * Resort the list if necessary, i.e. if the current sort instance + * isn't equal to the backed-up sortUsed instance. + *

    Calls doSort to trigger actual sorting. + * @see #doSort + */ + public void resort() { + SortDefinition sort = getSort(); + if (sort != null && !sort.equals(this.sortUsed)) { + this.sortUsed = copySortDefinition(sort); + doSort(getSource(), sort); + setPage(0); + } + } + + /** + * Create a deep copy of the given sort definition, + * for use as state holder to compare a modified sort definition against. + *

    Default implementation creates a MutableSortDefinition instance. + * Can be overridden in subclasses, in particular in case of custom + * extensions to the SortDefinition interface. Is allowed to return + * null, which means that no sort state will be held, triggering + * actual sorting for each resort call. + * @param sort the current SortDefinition object + * @return a deep copy of the SortDefinition object + * @see MutableSortDefinition#MutableSortDefinition(SortDefinition) + */ + protected SortDefinition copySortDefinition(SortDefinition sort) { + return new MutableSortDefinition(sort); + } + + /** + * Actually perform sorting of the given source list, according to + * the given sort definition. + *

    The default implementation uses Spring's PropertyComparator. + * Can be overridden in subclasses. + * @see PropertyComparator#sort(java.util.List, SortDefinition) + */ + protected void doSort(List source, SortDefinition sort) { + PropertyComparator.sort(source, sort); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/support/PagedListSourceProvider.java b/org.springframework.beans/src/main/java/org/springframework/beans/support/PagedListSourceProvider.java new file mode 100644 index 0000000000..dee1d50354 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/support/PagedListSourceProvider.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2008 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.support; + +import java.util.List; +import java.util.Locale; + +/** + * Callback that provides the source for a reloadable List. + * Used by {@link RefreshablePagedListHolder}. + * + * @author Jean-Pierre Pawlak + * @author Juergen Hoeller + * @deprecated as of Spring 2.5, to be removed in Spring 3.0 + * @see org.springframework.beans.support.RefreshablePagedListHolder#setSourceProvider + */ +public interface PagedListSourceProvider { + + /** + * Load the List for the given Locale and filter settings. + * The filter object can be of any custom class, preferably a bean + * for easy data binding from a request. An instance will simply + * get passed through to this callback method. + * @param locale Locale that the List should be loaded for, + * or null if not locale-specific + * @param filter object representing filter settings, + * or null if no filter options are used + * @return the loaded List + * @see org.springframework.beans.support.RefreshablePagedListHolder#setLocale + * @see org.springframework.beans.support.RefreshablePagedListHolder#setFilter + */ + List loadList(Locale locale, Object filter); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/support/PropertyComparator.java b/org.springframework.beans/src/main/java/org/springframework/beans/support/PropertyComparator.java new file mode 100644 index 0000000000..d9f9a95bbb --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/support/PropertyComparator.java @@ -0,0 +1,152 @@ +/* + * Copyright 2002-2008 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.support; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.BeanWrapperImpl; +import org.springframework.beans.BeansException; +import org.springframework.util.StringUtils; + +/** + * PropertyComparator performs a comparison of two beans, + * evaluating the specified bean property via a BeanWrapper. + * + * @author Juergen Hoeller + * @author Jean-Pierre Pawlak + * @since 19.05.2003 + * @see org.springframework.beans.BeanWrapper + */ +public class PropertyComparator implements Comparator { + + protected final Log logger = LogFactory.getLog(getClass()); + + private final SortDefinition sortDefinition; + + private final BeanWrapperImpl beanWrapper = new BeanWrapperImpl(false); + + + /** + * Create a new PropertyComparator for the given SortDefinition. + * @see MutableSortDefinition + */ + public PropertyComparator(SortDefinition sortDefinition) { + this.sortDefinition = sortDefinition; + } + + /** + * Create a PropertyComparator for the given settings. + * @param property the property to compare + * @param ignoreCase whether upper and lower case in String values should be ignored + * @param ascending whether to sort ascending (true) or descending (false) + */ + public PropertyComparator(String property, boolean ignoreCase, boolean ascending) { + this.sortDefinition = new MutableSortDefinition(property, ignoreCase, ascending); + } + + /** + * Return the SortDefinition that this comparator uses. + */ + public final SortDefinition getSortDefinition() { + return this.sortDefinition; + } + + + public int compare(Object o1, Object o2) { + Object v1 = getPropertyValue(o1); + Object v2 = getPropertyValue(o2); + if (this.sortDefinition.isIgnoreCase() && (v1 instanceof String) && (v2 instanceof String)) { + v1 = ((String) v1).toLowerCase(); + v2 = ((String) v2).toLowerCase(); + } + + int result; + + // Put an object with null property at the end of the sort result. + try { + if (v1 != null) { + result = (v2 != null ? ((Comparable) v1).compareTo(v2) : -1); + } + else { + result = (v2 != null ? 1 : 0); + } + } + catch (RuntimeException ex) { + if (logger.isWarnEnabled()) { + logger.warn("Could not sort objects [" + o1 + "] and [" + o2 + "]", ex); + } + return 0; + } + + return (this.sortDefinition.isAscending() ? result : -result); + } + + /** + * Get the SortDefinition's property value for the given object. + * @param obj the object to get the property value for + * @return the property value + */ + private Object getPropertyValue(Object obj) { + // If a nested property cannot be read, simply return null + // (similar to JSTL EL). If the property doesn't exist in the + // first place, let the exception through. + try { + this.beanWrapper.setWrappedInstance(obj); + return this.beanWrapper.getPropertyValue(this.sortDefinition.getProperty()); + } + catch (BeansException ex) { + logger.info("PropertyComparator could not access property - treating as null for sorting", ex); + return null; + } + } + + + /** + * Sort the given List according to the given sort definition. + *

    Note: Contained objects have to provide the given property + * in the form of a bean property, i.e. a getXXX method. + * @param source the input List + * @param sortDefinition the parameters to sort by + * @throws java.lang.IllegalArgumentException in case of a missing propertyName + */ + public static void sort(List source, SortDefinition sortDefinition) throws BeansException { + if (StringUtils.hasText(sortDefinition.getProperty())) { + Collections.sort(source, new PropertyComparator(sortDefinition)); + } + } + + /** + * Sort the given source according to the given sort definition. + *

    Note: Contained objects have to provide the given property + * in the form of a bean property, i.e. a getXXX method. + * @param source input source + * @param sortDefinition the parameters to sort by + * @throws java.lang.IllegalArgumentException in case of a missing propertyName + */ + public static void sort(Object[] source, SortDefinition sortDefinition) throws BeansException { + if (StringUtils.hasText(sortDefinition.getProperty())) { + Arrays.sort(source, new PropertyComparator(sortDefinition)); + } + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/support/RefreshablePagedListHolder.java b/org.springframework.beans/src/main/java/org/springframework/beans/support/RefreshablePagedListHolder.java new file mode 100644 index 0000000000..3d1d908caf --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/support/RefreshablePagedListHolder.java @@ -0,0 +1,180 @@ +/* + * Copyright 2002-2008 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.support; + +import java.util.Locale; + +import org.springframework.beans.BeanUtils; + +/** + * RefreshablePagedListHolder is a PagedListHolder subclass with reloading capabilities. + * It automatically re-requests the List from the source provider, in case of Locale or + * filter changes. + * + *

    Data binding works just like with PagedListHolder. The locale can be specified in + * Locale's toString syntax, e.g. "locale=en_US". The filter object can be of any + * custom class, preferably a bean for easy data binding from a request. An instance + * will simply get passed through to PagedListSourceProvider.loadList. + * A filter property can be specified via "filter.myFilterProperty", for example. + * + *

    The scenario in the controller could be: + * + * RefreshablePagedListHolder holder = request.getSession("mySessionAttr");
    + * if (holder == null) {
    + * holder = new RefreshablePagedListHolder();
    + * holder.setSourceProvider(new MyAnonymousOrEmbeddedSourceProvider());
    + * holder.setFilter(new MyAnonymousOrEmbeddedFilter());
    + * request.getSession().setAttribute("mySessionAttr", holder);
    + * }
    + * holder.refresh(false); + * BindException ex = BindUtils.bind(request, listHolder, "myModelAttr");
    + * return ModelAndView("myViewName", ex.getModel());
    + *
    + * ...
    + *
    + * private class MyAnonymousOrEmbeddedSourceProvider implements PagedListSourceProvider {
    + * public List loadList(Locale locale, Object filter) {
    + * MyAnonymousOrEmbeddedFilter filter = (MyAnonymousOrEmbeddedFilter) filter; + * }
    + *
    + * private class MyAnonymousOrEmbeddedFilter {
    + * private String name = "";
    + * public String getName() {
    + * return name; + * public void setName(String name) {
    + * this.name = name;
    + * }
    + * }
    + *
    + * + * @author Jean-Pierre Pawlak + * @author Juergen Hoeller + * @since 24.05.2003 + * @deprecated as of Spring 2.5, to be removed in Spring 3.0 + * @see org.springframework.beans.support.PagedListSourceProvider + * @see org.springframework.beans.propertyeditors.LocaleEditor + */ +public class RefreshablePagedListHolder extends PagedListHolder { + + private PagedListSourceProvider sourceProvider; + + private Locale locale; + + private Locale localeUsed; + + private Object filter; + + private Object filterUsed; + + + /** + * Create a new list holder. + * You'll need to set a source provider to be able to use the holder. + * @see #setSourceProvider + */ + public RefreshablePagedListHolder() { + super(); + } + + /** + * Create a new list holder with the given source provider. + */ + public RefreshablePagedListHolder(PagedListSourceProvider sourceProvider) { + super(); + this.sourceProvider = sourceProvider; + } + + + /** + * Set the callback class for reloading the List when necessary. + * If the list is definitely not modifiable, i.e. not locale aware + * and no filtering, use PagedListHolder. + * @see org.springframework.beans.support.PagedListHolder + */ + public void setSourceProvider(PagedListSourceProvider sourceProvider) { + this.sourceProvider = sourceProvider; + } + + /** + * Return the callback class for reloading the List when necessary. + */ + public PagedListSourceProvider getSourceProvider() { + return this.sourceProvider; + } + + /** + * Set the Locale that the source provider should use for loading the list. + * This can either be populated programmatically (e.g. with the request locale), + * or via binding (using Locale's toString syntax, e.g. "locale=en_US"). + * @param locale the current Locale, or null + */ + public void setLocale(Locale locale) { + this.locale = locale; + } + + /** + * Return the Locale that the source provider should use for loading the list. + * @return the current Locale, or null + */ + public Locale getLocale() { + return this.locale; + } + + /** + * Set the filter object that the source provider should use for loading the list. + * This will typically be a bean, for easy data binding. + * @param filter the filter object, or null + */ + public void setFilter(Object filter) { + this.filter = filter; + } + + /** + * Return the filter that the source provider should use for loading the list. + * @return the current filter, or null + */ + public Object getFilter() { + return this.filter; + } + + + /** + * Reload the underlying list from the source provider if necessary + * (i.e. if the locale and/or the filter has changed), and resort it. + * @param force whether a reload should be performed in any case + */ + public void refresh(boolean force) { + if (this.sourceProvider != null && (force || + (this.locale != null && !this.locale.equals(this.localeUsed)) || + (this.filter != null && !this.filter.equals(this.filterUsed)))) { + setSource(this.sourceProvider.loadList(this.locale, this.filter)); + if (this.filter != null && !this.filter.equals(this.filterUsed)) { + this.setPage(0); + } + this.localeUsed = this.locale; + if (this.filter != null) { + this.filterUsed = BeanUtils.instantiateClass(this.filter.getClass()); + BeanUtils.copyProperties(this.filter, this.filterUsed); + } + } + resort(); + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/support/ResourceEditorRegistrar.java b/org.springframework.beans/src/main/java/org/springframework/beans/support/ResourceEditorRegistrar.java new file mode 100644 index 0000000000..22c0e452a2 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/support/ResourceEditorRegistrar.java @@ -0,0 +1,95 @@ +/* + * Copyright 2002-2007 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.support; + +import java.io.File; +import java.io.InputStream; +import java.net.URI; +import java.net.URL; + +import org.springframework.beans.PropertyEditorRegistrar; +import org.springframework.beans.PropertyEditorRegistry; +import org.springframework.beans.propertyeditors.ClassEditor; +import org.springframework.beans.propertyeditors.FileEditor; +import org.springframework.beans.propertyeditors.InputStreamEditor; +import org.springframework.beans.propertyeditors.URIEditor; +import org.springframework.beans.propertyeditors.URLEditor; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceEditor; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.io.support.ResourceArrayPropertyEditor; +import org.springframework.core.io.support.ResourcePatternResolver; + +/** + * PropertyEditorRegistrar implementation that populates a given + * {@link org.springframework.beans.PropertyEditorRegistry} + * (typically a {@link org.springframework.beans.BeanWrapper} used for bean + * creation within an {@link org.springframework.context.ApplicationContext}) + * with resource editors. Used by + * {@link org.springframework.context.support.AbstractApplicationContext}. + * + * @author Juergen Hoeller + * @since 2.0 + */ +public class ResourceEditorRegistrar implements PropertyEditorRegistrar { + + private final ResourceLoader resourceLoader; + + + /** + * Create a new ResourceEditorRegistrar for the given ResourceLoader + * @param resourceLoader the ResourceLoader (or ResourcePatternResolver) + * to create editors for (usually an ApplicationContext) + * @see org.springframework.core.io.support.ResourcePatternResolver + * @see org.springframework.context.ApplicationContext + */ + public ResourceEditorRegistrar(ResourceLoader resourceLoader) { + this.resourceLoader = resourceLoader; + } + + + /** + * Populate the given bean factory with the following resource editors: + * ResourceEditor, InputStreamEditor, FileEditor, URLEditor, ClassEditor, URIEditor. + *

    In case of a {@link org.springframework.core.io.support.ResourcePatternResolver}, + * a ResourceArrayPropertyEditor will be registered as well. + * @see org.springframework.core.io.ResourceEditor + * @see org.springframework.beans.propertyeditors.InputStreamEditor + * @see org.springframework.beans.propertyeditors.FileEditor + * @see org.springframework.beans.propertyeditors.URLEditor + * @see org.springframework.beans.propertyeditors.ClassEditor + * @see org.springframework.beans.propertyeditors.URIEditor + * @see org.springframework.core.io.support.ResourceArrayPropertyEditor + */ + public void registerCustomEditors(PropertyEditorRegistry registry) { + ResourceEditor baseEditor = new ResourceEditor(this.resourceLoader); + registry.registerCustomEditor(Resource.class, baseEditor); + registry.registerCustomEditor(InputStream.class, new InputStreamEditor(baseEditor)); + registry.registerCustomEditor(File.class, new FileEditor(baseEditor)); + registry.registerCustomEditor(URL.class, new URLEditor(baseEditor)); + + ClassLoader classLoader = this.resourceLoader.getClassLoader(); + registry.registerCustomEditor(Class.class, new ClassEditor(classLoader)); + registry.registerCustomEditor(URI.class, new URIEditor(classLoader)); + + if (this.resourceLoader instanceof ResourcePatternResolver) { + registry.registerCustomEditor(Resource[].class, + new ResourceArrayPropertyEditor((ResourcePatternResolver) this.resourceLoader)); + } + } + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/support/SortDefinition.java b/org.springframework.beans/src/main/java/org/springframework/beans/support/SortDefinition.java new file mode 100644 index 0000000000..869701bc59 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/support/SortDefinition.java @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2005 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.support; + +/** + * Definition for sorting bean instances by a property. + * + * @author Juergen Hoeller + * @since 26.05.2003 + */ +public interface SortDefinition { + + /** + * Return the name of the bean property to compare. + * Can also be a nested bean property path. + */ + String getProperty(); + + /** + * Return whether upper and lower case in String values should be ignored. + */ + boolean isIgnoreCase(); + + /** + * Return whether to sort ascending (true) or descending (false). + */ + boolean isAscending(); + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/support/package.html b/org.springframework.beans/src/main/java/org/springframework/beans/support/package.html new file mode 100644 index 0000000000..af05e5ae93 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/support/package.html @@ -0,0 +1,8 @@ + + + +Classes supporting the org.springframework.beans package, +such as utility classes for sorting and holding lists of beans. + + + diff --git a/org.springframework.beans/template.mf b/org.springframework.beans/template.mf index 14ff7d929d..a4871d4dee 100644 --- a/org.springframework.beans/template.mf +++ b/org.springframework.beans/template.mf @@ -2,5 +2,13 @@ Bundle-SymbolicName: org.springframework.beans Bundle-Name: Spring Beans Bundle-Vendor: SpringSource Bundle-ManifestVersion: 2 -Import-Template: - org.apache.commons.logging;version="[1.1.1, 2.0.0)", +Import-Template: + javax.el.*;version="[2.1.0, 3.0.0)";resolution:=optional, + net.sf.cglib.*;version="[2.1.3, 2.2.0)";resolution:=optional, + org.apache.commons.logging.*;version="[1.1.1, 2.0.0)", + org.springframework.core.*;version="[3.0.0, 3.0.0]", + org.springframework.util.*;version="[3.0.0, 3.0.0]", +Unversioned-Imports: + javax.xml.parsers.*, + org.w3c.dom.*, + org.xml.sax.* \ No newline at end of file