diff --git a/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfo.java b/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfo.java index 1b63f37a3d2..211d1803ecb 100644 --- a/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfo.java +++ b/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfo.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -35,6 +35,7 @@ import java.util.TreeSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; @@ -74,12 +75,6 @@ class ExtendedBeanInfo implements BeanInfo { public ExtendedBeanInfo(BeanInfo delegate) throws IntrospectionException { this.delegate = delegate; - // PropertyDescriptor instances from the delegate object are never added directly, but always - // copied to the local collection of #propertyDescriptors and returned by calls to - // #getPropertyDescriptors(). this algorithm iterates through all methods (method descriptors) - // in the wrapped BeanInfo object, copying any existing PropertyDescriptor or creating a new - // one for any non-standard setter methods found. - ALL_METHODS: for (MethodDescriptor md : delegate.getMethodDescriptors()) { Method method = md.getMethod(); @@ -136,19 +131,13 @@ class ExtendedBeanInfo implements BeanInfo { Method indexedReadMethod = ipd.getIndexedReadMethod(); Method indexedWriteMethod = ipd.getIndexedWriteMethod(); // has the setter already been found by the wrapped BeanInfo? - if (indexedWriteMethod != null - && indexedWriteMethod.getName().equals(method.getName())) { - // yes -> copy it, including corresponding getter method (if any -- may be null) - this.addOrUpdatePropertyDescriptor(pd, propertyName, readMethod, writeMethod, indexedReadMethod, indexedWriteMethod); - continue ALL_METHODS; - } - // has a getter corresponding to this setter already been found by the wrapped BeanInfo? - if (indexedReadMethod != null - && indexedReadMethod.getName().equals(getterMethodNameFor(propertyName)) - && indexedReadMethod.getReturnType().equals(method.getParameterTypes()[1])) { - this.addOrUpdatePropertyDescriptor(pd, propertyName, readMethod, writeMethod, indexedReadMethod, method); - continue ALL_METHODS; + if (!(indexedWriteMethod != null + && indexedWriteMethod.getName().equals(method.getName()))) { + indexedWriteMethod = method; } + // yes -> copy it, including corresponding getter method (if any -- may be null) + this.addOrUpdatePropertyDescriptor(pd, propertyName, readMethod, writeMethod, indexedReadMethod, indexedWriteMethod); + continue ALL_METHODS; } // the INDEXED setter method was not found by the wrapped BeanInfo -> add a new PropertyDescriptor // for it. no corresponding INDEXED getter was detected, so the 'indexed read method' parameter is null. @@ -159,24 +148,27 @@ class ExtendedBeanInfo implements BeanInfo { // the method is not a setter, but is it a getter? for (PropertyDescriptor pd : delegate.getPropertyDescriptors()) { // have we already copied this read method to a property descriptor locally? + String propertyName = pd.getName(); + Method readMethod = pd.getReadMethod(); + Method mostSpecificReadMethod = ClassUtils.getMostSpecificMethod(readMethod, method.getDeclaringClass()); for (PropertyDescriptor existingPD : this.propertyDescriptors) { - if (method.equals(pd.getReadMethod()) - && existingPD.getName().equals(pd.getName())) { + if (method.equals(mostSpecificReadMethod) + && existingPD.getName().equals(propertyName)) { if (existingPD.getReadMethod() == null) { // no -> add it now - this.addOrUpdatePropertyDescriptor(pd, pd.getName(), method, pd.getWriteMethod()); + this.addOrUpdatePropertyDescriptor(pd, propertyName, method, pd.getWriteMethod()); } // yes -> do not add a duplicate continue ALL_METHODS; } } - if (method == pd.getReadMethod() - || (pd instanceof IndexedPropertyDescriptor && method == ((IndexedPropertyDescriptor) pd).getIndexedReadMethod())) { + if (method.equals(mostSpecificReadMethod) + || (pd instanceof IndexedPropertyDescriptor && method.equals(((IndexedPropertyDescriptor) pd).getIndexedReadMethod()))) { // yes -> copy it, including corresponding setter method (if any -- may be null) if (pd instanceof IndexedPropertyDescriptor) { - this.addOrUpdatePropertyDescriptor(pd, pd.getName(), pd.getReadMethod(), pd.getWriteMethod(), ((IndexedPropertyDescriptor)pd).getIndexedReadMethod(), ((IndexedPropertyDescriptor)pd).getIndexedWriteMethod()); + this.addOrUpdatePropertyDescriptor(pd, propertyName, readMethod, pd.getWriteMethod(), ((IndexedPropertyDescriptor)pd).getIndexedReadMethod(), ((IndexedPropertyDescriptor)pd).getIndexedWriteMethod()); } else { - this.addOrUpdatePropertyDescriptor(pd, pd.getName(), pd.getReadMethod(), pd.getWriteMethod()); + this.addOrUpdatePropertyDescriptor(pd, propertyName, readMethod, pd.getWriteMethod()); } continue ALL_METHODS; } @@ -221,7 +213,9 @@ class ExtendedBeanInfo implements BeanInfo { } } // update the existing descriptor's write method - if (writeMethod != null) { + if (writeMethod != null + && !(existingPD instanceof IndexedPropertyDescriptor && + !writeMethod.getParameterTypes()[0].isArray())) { existingPD.setWriteMethod(writeMethod); } @@ -278,7 +272,7 @@ class ExtendedBeanInfo implements BeanInfo { } this.propertyDescriptors.add(pd); } catch (IntrospectionException ex) { - logger.warn(format("Could not create new PropertyDescriptor for readMethod [%s] writeMethod [%s] " + + logger.debug(format("Could not create new PropertyDescriptor for readMethod [%s] writeMethod [%s] " + "indexedReadMethod [%s] indexedWriteMethod [%s] for property [%s]. Reason: %s", readMethod, writeMethod, indexedReadMethod, indexedWriteMethod, propertyName, ex.getMessage())); // suppress exception and attempt to continue @@ -289,10 +283,20 @@ class ExtendedBeanInfo implements BeanInfo { try { pd.setWriteMethod(writeMethod); } catch (IntrospectionException ex) { - logger.warn(format("Could not add write method [%s] for property [%s]. Reason: %s", + logger.debug(format("Could not add write method [%s] for property [%s]. Reason: %s", writeMethod, propertyName, ex.getMessage())); // fall through -> add property descriptor as best we can } + if (pd instanceof IndexedPropertyDescriptor) { + ((IndexedPropertyDescriptor)pd).setIndexedReadMethod(indexedReadMethod); + try { + ((IndexedPropertyDescriptor)pd).setIndexedWriteMethod(indexedWriteMethod); + } catch (IntrospectionException ex) { + logger.debug(format("Could not add indexed write method [%s] for property [%s]. Reason: %s", + indexedWriteMethod, propertyName, ex.getMessage())); + // fall through -> add property descriptor as best we can + } + } this.propertyDescriptors.add(pd); } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AnnotatedGenericBeanDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AnnotatedGenericBeanDefinition.java index 3f76c252219..5f4ee7f5559 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AnnotatedGenericBeanDefinition.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AnnotatedGenericBeanDefinition.java @@ -47,7 +47,7 @@ public class AnnotatedGenericBeanDefinition extends GenericBeanDefinition implem */ public AnnotatedGenericBeanDefinition(Class beanClass) { setBeanClass(beanClass); - this.annotationMetadata = new StandardAnnotationMetadata(beanClass); + this.annotationMetadata = new StandardAnnotationMetadata(beanClass, true); } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java index 1a3a2c0bcd8..a54002c0b04 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 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. @@ -21,6 +21,7 @@ import java.io.ObjectInputStream; import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import org.springframework.core.GenericCollectionTypeResolver; @@ -56,6 +57,8 @@ public class DependencyDescriptor implements Serializable { private final boolean eager; + private int nestingLevel = 1; + private transient Annotation[] fieldAnnotations; @@ -153,6 +156,13 @@ public class DependencyDescriptor implements Serializable { } + public void increaseNestingLevel() { + this.nestingLevel++; + if (this.methodParameter != null) { + this.methodParameter.increaseNestingLevel(); + } + } + /** * Initialize parameter name discovery for the underlying method parameter, if any. *

This method does not actually try to retrieve the parameter name at @@ -178,15 +188,30 @@ public class DependencyDescriptor implements Serializable { * @return the declared type (never null) */ public Class getDependencyType() { - return (this.field != null ? this.field.getType() : this.methodParameter.getParameterType()); - } - - /** - * Determine the generic type of the wrapped parameter/field. - * @return the generic type (never null) - */ - public Type getGenericDependencyType() { - return (this.field != null ? this.field.getGenericType() : this.methodParameter.getGenericParameterType()); + if (this.field != null) { + if (this.nestingLevel > 1) { + Type type = this.field.getGenericType(); + if (type instanceof ParameterizedType) { + Type arg = ((ParameterizedType) type).getActualTypeArguments()[0]; + if (arg instanceof Class) { + return (Class) arg; + } + else if (arg instanceof ParameterizedType) { + arg = ((ParameterizedType) arg).getRawType(); + if (arg instanceof Class) { + return (Class) arg; + } + } + } + return Object.class; + } + else { + return this.field.getType(); + } + } + else { + return this.methodParameter.getNestedParameterType(); + } } /** @@ -195,7 +220,7 @@ public class DependencyDescriptor implements Serializable { */ public Class getCollectionType() { return (this.field != null ? - GenericCollectionTypeResolver.getCollectionFieldType(this.field) : + GenericCollectionTypeResolver.getCollectionFieldType(this.field, this.nestingLevel) : GenericCollectionTypeResolver.getCollectionParameterType(this.methodParameter)); } @@ -205,7 +230,7 @@ public class DependencyDescriptor implements Serializable { */ public Class getMapKeyType() { return (this.field != null ? - GenericCollectionTypeResolver.getMapKeyFieldType(this.field) : + GenericCollectionTypeResolver.getMapKeyFieldType(this.field, this.nestingLevel) : GenericCollectionTypeResolver.getMapKeyParameterType(this.methodParameter)); } @@ -215,7 +240,7 @@ public class DependencyDescriptor implements Serializable { */ public Class getMapValueType() { return (this.field != null ? - GenericCollectionTypeResolver.getMapValueFieldType(this.field) : + GenericCollectionTypeResolver.getMapValueFieldType(this.field, this.nestingLevel) : GenericCollectionTypeResolver.getMapValueParameterType(this.methodParameter)); } @@ -248,13 +273,18 @@ public class DependencyDescriptor implements Serializable { if (this.fieldName != null) { this.field = this.declaringClass.getDeclaredField(this.fieldName); } - else if (this.methodName != null) { - this.methodParameter = new MethodParameter( - this.declaringClass.getDeclaredMethod(this.methodName, this.parameterTypes), this.parameterIndex); - } else { - this.methodParameter = new MethodParameter( - this.declaringClass.getDeclaredConstructor(this.parameterTypes), this.parameterIndex); + if (this.methodName != null) { + this.methodParameter = new MethodParameter( + this.declaringClass.getDeclaredMethod(this.methodName, this.parameterTypes), this.parameterIndex); + } + else { + this.methodParameter = new MethodParameter( + this.declaringClass.getDeclaredConstructor(this.parameterTypes), this.parameterIndex); + } + for (int i = 1; i < this.nestingLevel; i++) { + this.methodParameter.increaseNestingLevel(); + } } } catch (Throwable ex) { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java index 783945fe49a..296577b9304 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -1328,8 +1328,9 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp * @param mbd the corresponding bean definition */ protected boolean isFactoryBean(String beanName, RootBeanDefinition mbd) { - Class beanClass = predictBeanType(beanName, mbd, FactoryBean.class); - return (beanClass != null && FactoryBean.class.isAssignableFrom(beanClass)); + Class predictedType = predictBeanType(beanName, mbd, FactoryBean.class); + return (predictedType != null && FactoryBean.class.isAssignableFrom(predictedType)) || + (mbd.hasBeanClass() && FactoryBean.class.isAssignableFrom(mbd.getBeanClass())); } /** diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index 55c4c7860b8..6f7e75c9e58 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -1000,27 +1000,14 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto private final String beanName; - private final Class type; - public DependencyObjectFactory(DependencyDescriptor descriptor, String beanName) { this.descriptor = descriptor; this.beanName = beanName; - this.type = determineObjectFactoryType(); - } - - private Class determineObjectFactoryType() { - Type type = this.descriptor.getGenericDependencyType(); - if (type instanceof ParameterizedType) { - Type arg = ((ParameterizedType) type).getActualTypeArguments()[0]; - if (arg instanceof Class) { - return (Class) arg; - } - } - return Object.class; + this.descriptor.increaseNestingLevel(); } public Object getObject() throws BeansException { - return doResolveDependency(this.descriptor, this.type, this.beanName, null, null); + return doResolveDependency(this.descriptor, this.descriptor.getDependencyType(), this.beanName, null, null); } } diff --git a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperEnumTests.java b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperEnumTests.java index d41aa7a215e..3946cccb25e 100644 --- a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperEnumTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperEnumTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 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. @@ -16,11 +16,12 @@ package org.springframework.beans; -import static org.junit.Assert.*; import org.junit.Test; import test.beans.CustomEnum; import test.beans.GenericBean; +import static org.junit.Assert.*; + /** * @author Juergen Hoeller * @author Chris Beams @@ -109,4 +110,14 @@ public final class BeanWrapperEnumTests { assertTrue(gb.getCustomEnumSet().contains(CustomEnum.VALUE_2)); } + @Test + public void testCustomEnumSetWithGetterSetterMismatch() { + GenericBean gb = new GenericBean(); + BeanWrapper bw = new BeanWrapperImpl(gb); + bw.setPropertyValue("customEnumSetMismatch", new String[] {"VALUE_1", "VALUE_2"}); + assertEquals(2, gb.getCustomEnumSet().size()); + assertTrue(gb.getCustomEnumSet().contains(CustomEnum.VALUE_1)); + assertTrue(gb.getCustomEnumSet().contains(CustomEnum.VALUE_2)); + } + } diff --git a/spring-beans/src/test/java/org/springframework/beans/ExtendedBeanInfoTests.java b/spring-beans/src/test/java/org/springframework/beans/ExtendedBeanInfoTests.java index 144ce6f13af..2049039a03f 100644 --- a/spring-beans/src/test/java/org/springframework/beans/ExtendedBeanInfoTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/ExtendedBeanInfoTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -16,13 +16,13 @@ package org.springframework.beans; -import static java.lang.String.format; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.lessThan; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.beans.BeanInfo; @@ -32,9 +32,10 @@ import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; -import org.junit.Ignore; import org.junit.Test; import org.springframework.beans.ExtendedBeanInfo.PropertyDescriptorComparator; +import org.springframework.core.JdkVersion; +import org.springframework.util.ClassUtils; import test.beans.TestBean; @@ -429,11 +430,12 @@ public class ExtendedBeanInfoTests { } BeanInfo bi = Introspector.getBeanInfo(C.class); - BeanInfo ebi = new ExtendedBeanInfo(Introspector.getBeanInfo(C.class)); assertThat(hasIndexedReadMethodForProperty(bi, "foos"), is(true)); // interesting! standard Inspector picks up non-void return types on indexed write methods by default - assertThat(hasIndexedWriteMethodForProperty(bi, "foos"), is(true)); + assertThat(hasIndexedWriteMethodForProperty(bi, "foos"), is(trueUntilJdk17())); + + BeanInfo ebi = new ExtendedBeanInfo(Introspector.getBeanInfo(C.class)); assertThat(hasIndexedReadMethodForProperty(ebi, "foos"), is(true)); assertThat(hasIndexedWriteMethodForProperty(ebi, "foos"), is(true)); @@ -456,13 +458,12 @@ public class ExtendedBeanInfoTests { assertThat(hasIndexedReadMethodForProperty(bi, "foos"), is(true)); assertThat(hasWriteMethodForProperty(bi, "foos"), is(false)); // again as above, standard Inspector picks up non-void return types on indexed write methods by default - assertThat(hasIndexedWriteMethodForProperty(bi, "foos"), is(true)); + assertThat(hasIndexedWriteMethodForProperty(bi, "foos"), is(trueUntilJdk17())); BeanInfo ebi = new ExtendedBeanInfo(Introspector.getBeanInfo(C.class)); assertThat(hasIndexedReadMethodForProperty(bi, "foos"), is(true)); assertThat(hasWriteMethodForProperty(bi, "foos"), is(true)); - // again as above, standard Inspector picks up non-void return types on indexed write methods by default assertThat(hasIndexedWriteMethodForProperty(bi, "foos"), is(true)); assertThat(hasIndexedReadMethodForProperty(ebi, "foos"), is(true)); @@ -550,17 +551,17 @@ public class ExtendedBeanInfoTests { assertThat(hasReadMethodForProperty(bi, "dateFormat"), is(false)); assertThat(hasWriteMethodForProperty(bi, "dateFormat"), is(false)); assertThat(hasIndexedReadMethodForProperty(bi, "dateFormat"), is(false)); - assertThat(hasIndexedWriteMethodForProperty(bi, "dateFormat"), is(true)); + assertThat(hasIndexedWriteMethodForProperty(bi, "dateFormat"), is(trueUntilJdk17())); ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi); assertThat(hasReadMethodForProperty(bi, "dateFormat"), is(false)); - assertThat(hasWriteMethodForProperty(bi, "dateFormat"), is(true)); + assertThat(hasWriteMethodForProperty(bi, "dateFormat"), is(false)); assertThat(hasIndexedReadMethodForProperty(bi, "dateFormat"), is(false)); - assertThat(hasIndexedWriteMethodForProperty(bi, "dateFormat"), is(true)); + assertThat(hasIndexedWriteMethodForProperty(bi, "dateFormat"), is(trueUntilJdk17())); assertThat(hasReadMethodForProperty(ebi, "dateFormat"), is(false)); - assertThat(hasWriteMethodForProperty(ebi, "dateFormat"), is(true)); + assertThat(hasWriteMethodForProperty(ebi, "dateFormat"), is(false)); assertThat(hasIndexedReadMethodForProperty(ebi, "dateFormat"), is(false)); assertThat(hasIndexedWriteMethodForProperty(ebi, "dateFormat"), is(true)); } @@ -663,52 +664,18 @@ public class ExtendedBeanInfoTests { return false; } - @Test - public void reproSpr8806_y() throws IntrospectionException, SecurityException, NoSuchMethodException { - Introspector.getBeanInfo(LawLibrary.class); + private boolean trueUntilJdk17() { + return JdkVersion.getMajorJavaVersion() < JdkVersion.JAVA_17; } - @Ignore @Test - public void reproSpr8806_x() throws IntrospectionException, SecurityException, NoSuchMethodException { - BeanInfo info = Introspector.getBeanInfo(LawLibrary.class); - for (PropertyDescriptor d : info.getPropertyDescriptors()) { - if (d.getName().equals("book")) { - Method readMethod = d.getReadMethod(); - Method writeMethod = d.getWriteMethod(); - System.out.println(format("READ : %s.%s (bridge:%s)", - readMethod.getDeclaringClass().getSimpleName(), readMethod.getName(), readMethod.isBridge())); - System.out.println(format("WRITE: %s.%s (bridge:%s)", - writeMethod.getDeclaringClass().getSimpleName(), writeMethod.getName(), writeMethod.isBridge())); - new PropertyDescriptor("book", readMethod, writeMethod); - } - } - - Method readMethod = LawLibrary.class.getMethod("getBook"); - Method writeMethod = LawLibrary.class.getMethod("setBook", Book.class); - - System.out.println(format("read : %s.%s (bridge:%s)", - readMethod.getDeclaringClass().getSimpleName(), readMethod.getName(), readMethod.isBridge())); - System.out.println(format("write: %s.%s (bridge:%s)", - writeMethod.getDeclaringClass().getSimpleName(), writeMethod.getName(), writeMethod.isBridge())); - - - System.out.println("--------"); - for (Method m : LawLibrary.class.getMethods()) { - if (m.getDeclaringClass() == Object.class) continue; - System.out.println(format("%s %s.%s(%s) [bridge:%s]", - m.getReturnType().getSimpleName(), m.getDeclaringClass().getSimpleName(), - m.getName(), - m.getParameterTypes().length == 1 ? m.getParameterTypes()[0].getSimpleName() : "", - m.isBridge())); - } - - //new ExtendedBeanInfo(info); - } @Test public void reproSpr8806() throws IntrospectionException { - BeanInfo bi = Introspector.getBeanInfo(LawLibrary.class); - new ExtendedBeanInfo(bi); // throws + // does not throw + Introspector.getBeanInfo(LawLibrary.class); + + // does not throw after the changes introduced in SPR-8806 + new ExtendedBeanInfo(Introspector.getBeanInfo(LawLibrary.class)); } interface Book { } @@ -734,4 +701,74 @@ public class ExtendedBeanInfoTests { class LawLibrary extends Library implements TextBookOperations { public LawBook getBook() { return null; } } + + + @Test + public void cornerSpr8949() throws IntrospectionException { + class A { + @SuppressWarnings("unused") + public boolean isTargetMethod() { + return false; + } + } + + class B extends A { + @Override + public boolean isTargetMethod() { + return false; + } + } + + BeanInfo bi = Introspector.getBeanInfo(B.class); + + /* first, demonstrate the 'problem': + * java.beans.Introspector returns the "wrong" declaring class for overridden read + * methods, which in turn violates expectations in {@link ExtendedBeanInfo} regarding + * method equality. Spring's {@link ClassUtils#getMostSpecificMethod(Method, Class)} + * helps out here, and is now put into use in ExtendedBeanInfo as well + */ + for (PropertyDescriptor pd : bi.getPropertyDescriptors()) { + if ("targetMethod".equals(pd.getName())) { + Method readMethod = pd.getReadMethod(); + assertTrue(readMethod.getDeclaringClass().equals(A.class)); // we expected B! + + Method msReadMethod = ClassUtils.getMostSpecificMethod(readMethod, B.class); + assertTrue(msReadMethod.getDeclaringClass().equals(B.class)); // and now we get it. + } + } + + // and now demonstrate that we've indeed fixed the problem + ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi); + + assertThat(hasReadMethodForProperty(bi, "targetMethod"), is(true)); + assertThat(hasWriteMethodForProperty(bi, "targetMethod"), is(false)); + + assertThat(hasReadMethodForProperty(ebi, "targetMethod"), is(true)); + assertThat(hasWriteMethodForProperty(ebi, "targetMethod"), is(false)); + } + + @Test + public void cornerSpr8937() throws IntrospectionException { + @SuppressWarnings("unused") class A { + public void setAddress(String addr){ } + public void setAddress(int index, String addr) { } + public String getAddress(int index){ return null; } + } + + { // baseline. ExtendedBeanInfo needs to behave exactly like the following + BeanInfo bi = Introspector.getBeanInfo(A.class); + assertThat(hasReadMethodForProperty(bi, "address"), is(false)); + assertThat(hasWriteMethodForProperty(bi, "address"), is(false)); + assertThat(hasIndexedReadMethodForProperty(bi, "address"), is(true)); + assertThat(hasIndexedWriteMethodForProperty(bi, "address"), is(true)); + } + { + ExtendedBeanInfo bi = new ExtendedBeanInfo(Introspector.getBeanInfo(A.class)); + assertThat(hasReadMethodForProperty(bi, "address"), is(false)); + assertThat(hasWriteMethodForProperty(bi, "address"), is(false)); + assertThat(hasIndexedReadMethodForProperty(bi, "address"), is(true)); + assertThat(hasIndexedWriteMethodForProperty(bi, "address"), is(true)); + } + } + } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/InjectAnnotationBeanPostProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/InjectAnnotationBeanPostProcessorTests.java index 0621e87415f..51ae86cd6c0 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/InjectAnnotationBeanPostProcessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/InjectAnnotationBeanPostProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 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. @@ -23,7 +23,6 @@ import javax.inject.Inject; import javax.inject.Named; import javax.inject.Provider; -import static org.junit.Assert.*; import org.junit.Test; import test.beans.ITestBean; import test.beans.IndexedTestBean; @@ -40,6 +39,8 @@ import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.util.SerializationTestUtils; +import static org.junit.Assert.*; + /** * Unit tests for {@link org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor} * processing the JSR-303 {@link javax.inject.Inject} annotation. @@ -206,8 +207,8 @@ public class InjectAnnotationBeanPostProcessorTests { AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); bpp.setBeanFactory(bf); bf.addBeanPostProcessor(bpp); - bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition( - ConstructorsCollectionResourceInjectionBean.class)); + bf.registerBeanDefinition("annotatedBean", + new RootBeanDefinition(ConstructorsCollectionResourceInjectionBean.class)); TestBean tb = new TestBean(); bf.registerSingleton("testBean", tb); NestedTestBean ntb1 = new NestedTestBean(); @@ -415,6 +416,74 @@ public class InjectAnnotationBeanPostProcessorTests { bf.destroySingletons(); } + @Test + public void testObjectFactoryWithTypedListField() throws Exception { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryListFieldInjectionBean.class)); + bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class)); + bf.setSerializationId("test"); + + ObjectFactoryListFieldInjectionBean bean = (ObjectFactoryListFieldInjectionBean) bf.getBean("annotatedBean"); + assertSame(bf.getBean("testBean"), bean.getTestBean()); + bean = (ObjectFactoryListFieldInjectionBean) SerializationTestUtils.serializeAndDeserialize(bean); + assertSame(bf.getBean("testBean"), bean.getTestBean()); + bf.destroySingletons(); + } + + @Test + public void testObjectFactoryWithTypedListMethod() throws Exception { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryListMethodInjectionBean.class)); + bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class)); + bf.setSerializationId("test"); + + ObjectFactoryListMethodInjectionBean bean = (ObjectFactoryListMethodInjectionBean) bf.getBean("annotatedBean"); + assertSame(bf.getBean("testBean"), bean.getTestBean()); + bean = (ObjectFactoryListMethodInjectionBean) SerializationTestUtils.serializeAndDeserialize(bean); + assertSame(bf.getBean("testBean"), bean.getTestBean()); + bf.destroySingletons(); + } + + @Test + public void testObjectFactoryWithTypedMapField() throws Exception { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryMapFieldInjectionBean.class)); + bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class)); + bf.setSerializationId("test"); + + ObjectFactoryMapFieldInjectionBean bean = (ObjectFactoryMapFieldInjectionBean) bf.getBean("annotatedBean"); + assertSame(bf.getBean("testBean"), bean.getTestBean()); + bean = (ObjectFactoryMapFieldInjectionBean) SerializationTestUtils.serializeAndDeserialize(bean); + assertSame(bf.getBean("testBean"), bean.getTestBean()); + bf.destroySingletons(); + } + + @Test + public void testObjectFactoryWithTypedMapMethod() throws Exception { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryMapMethodInjectionBean.class)); + bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class)); + bf.setSerializationId("test"); + + ObjectFactoryMapMethodInjectionBean bean = (ObjectFactoryMapMethodInjectionBean) bf.getBean("annotatedBean"); + assertSame(bf.getBean("testBean"), bean.getTestBean()); + bean = (ObjectFactoryMapMethodInjectionBean) SerializationTestUtils.serializeAndDeserialize(bean); + assertSame(bf.getBean("testBean"), bean.getTestBean()); + bf.destroySingletons(); + } + /** * Verifies that a dependency on a {@link org.springframework.beans.factory.FactoryBean} can be autowired via * {@link org.springframework.beans.factory.annotation.Autowired @Inject}, specifically addressing the JIRA issue @@ -835,6 +904,66 @@ public class InjectAnnotationBeanPostProcessorTests { } + public static class ObjectFactoryListFieldInjectionBean implements Serializable { + + @Inject + private Provider> testBeanFactory; + + public void setTestBeanFactory(Provider> testBeanFactory) { + this.testBeanFactory = testBeanFactory; + } + + public TestBean getTestBean() { + return this.testBeanFactory.get().get(0); + } + } + + + public static class ObjectFactoryListMethodInjectionBean implements Serializable { + + private Provider> testBeanFactory; + + @Inject + public void setTestBeanFactory(Provider> testBeanFactory) { + this.testBeanFactory = testBeanFactory; + } + + public TestBean getTestBean() { + return this.testBeanFactory.get().get(0); + } + } + + + public static class ObjectFactoryMapFieldInjectionBean implements Serializable { + + @Inject + private Provider> testBeanFactory; + + public void setTestBeanFactory(Provider> testBeanFactory) { + this.testBeanFactory = testBeanFactory; + } + + public TestBean getTestBean() { + return this.testBeanFactory.get().values().iterator().next(); + } + } + + + public static class ObjectFactoryMapMethodInjectionBean implements Serializable { + + private Provider> testBeanFactory; + + @Inject + public void setTestBeanFactory(Provider> testBeanFactory) { + this.testBeanFactory = testBeanFactory; + } + + public TestBean getTestBean() { + return this.testBeanFactory.get().values().iterator().next(); + } + } + + /** * Bean with a dependency on a {@link org.springframework.beans.factory.FactoryBean}. */ diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/Spr8954Tests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/Spr8954Tests.java new file mode 100644 index 00000000000..0cd040b4695 --- /dev/null +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/Spr8954Tests.java @@ -0,0 +1,96 @@ +/* + * Copyright 2002-2012 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 static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import java.util.Map; + +import org.junit.Test; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.support.RootBeanDefinition; + +/** + * Unit tests for SPR-8954, in which a custom {@link InstantiationAwareBeanPostProcessor} + * forces the predicted type of a FactoryBean, effectively preventing retrieval of the + * bean from calls to #getBeansOfType(FactoryBean.class). The implementation of + * {@link AbstractBeanFactory#isFactoryBean(String, RootBeanDefinition)} now ensures + * that not only the predicted bean type is considered, but also the original bean + * definition's beanClass. + * + * @author Chris Beams + * @author Oliver Gierke + */ +public class Spr8954Tests { + + @Test + public void repro() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.registerBeanDefinition("foo", new RootBeanDefinition(FooFactoryBean.class)); + bf.addBeanPostProcessor(new PredictingBPP()); + + assertThat(bf.getBean("foo"), instanceOf(Foo.class)); + assertThat(bf.getBean("&foo"), instanceOf(FooFactoryBean.class)); + + @SuppressWarnings("rawtypes") + Map fbBeans = bf.getBeansOfType(FactoryBean.class); + assertThat(1, equalTo(fbBeans.size())); + assertThat("&foo", equalTo(fbBeans.keySet().iterator().next())); + + Map aiBeans = bf.getBeansOfType(AnInterface.class); + assertThat(1, equalTo(aiBeans.size())); + assertThat("&foo", equalTo(aiBeans.keySet().iterator().next())); + } + + static class FooFactoryBean implements FactoryBean, AnInterface { + + public Foo getObject() throws Exception { + return new Foo(); + } + + public Class getObjectType() { + return Foo.class; + } + + public boolean isSingleton() { + return true; + } + } + + interface AnInterface { + } + + static class Foo { + } + + interface PredictedType { + + } + + static class PredictingBPP extends InstantiationAwareBeanPostProcessorAdapter { + + @Override + public Class predictBeanType(Class beanClass, String beanName) { + return FactoryBean.class.isAssignableFrom(beanClass) ? + PredictedType.class : + super.predictBeanType(beanClass, beanName); + } + } +} diff --git a/spring-beans/src/test/java/test/beans/GenericBean.java b/spring-beans/src/test/java/test/beans/GenericBean.java index acb9bdb76e5..12ff808dc84 100644 --- a/spring-beans/src/test/java/test/beans/GenericBean.java +++ b/spring-beans/src/test/java/test/beans/GenericBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 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. @@ -21,6 +21,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -244,6 +245,17 @@ public class GenericBean { this.customEnumSet = customEnumSet; } + public Set getCustomEnumSetMismatch() { + return customEnumSet; + } + + public void setCustomEnumSetMismatch(Set customEnumSet) { + this.customEnumSet = new HashSet(customEnumSet.size()); + for (Iterator iterator = customEnumSet.iterator(); iterator.hasNext(); ) { + this.customEnumSet.add(CustomEnum.valueOf(iterator.next())); + } + } + public static GenericBean createInstance(Set integerSet) { return new GenericBean(integerSet); } diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/JobDetailFactoryBean.java b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/JobDetailFactoryBean.java index d779c102064..6df333fcca5 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/JobDetailFactoryBean.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/JobDetailFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -60,6 +60,10 @@ public class JobDetailFactoryBean private JobDataMap jobDataMap = new JobDataMap(); + private boolean durability = false; + + private String description; + private String beanName; private ApplicationContext applicationContext; @@ -120,6 +124,21 @@ public class JobDetailFactoryBean getJobDataMap().putAll(jobDataAsMap); } + /** + * Specify the job's durability, i.e. whether it should remain stored + * in the job store even if no triggers point to it anymore. + */ + public void setDurability(boolean durability) { + this.durability = durability; + } + + /** + * Set a textual description for this job. + */ + public void setDescription(String description) { + this.description = description; + } + public void setBeanName(String beanName) { this.beanName = beanName; } @@ -172,6 +191,8 @@ public class JobDetailFactoryBean jdi.setGroup(this.group); jdi.setJobClass(this.jobClass); jdi.setJobDataMap(this.jobDataMap); + jdi.setDurability(this.durability); + jdi.setDescription(this.description); this.jobDetail = jdi; */ @@ -188,6 +209,8 @@ public class JobDetailFactoryBean pvs.add("group", this.group); pvs.add("jobClass", this.jobClass); pvs.add("jobDataMap", this.jobDataMap); + pvs.add("durability", this.durability); + pvs.add("description", this.description); bw.setPropertyValues(pvs); this.jobDetail = (JobDetail) bw.getWrappedInstance(); } diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java b/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java index 7e511b01aa1..4937eeb5e38 100644 --- a/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java +++ b/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -17,7 +17,6 @@ package org.springframework.cache.annotation; import java.util.Collection; -import java.util.Map; import javax.annotation.PostConstruct; @@ -26,6 +25,7 @@ import org.springframework.cache.CacheManager; import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportAware; +import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; @@ -41,8 +41,7 @@ import org.springframework.util.CollectionUtils; @Configuration public abstract class AbstractCachingConfiguration implements ImportAware { - /** Parsed annotation metadata for {@code @EnableCaching} on the importing class. */ - protected Map enableCaching; + protected AnnotationAttributes enableCaching; protected CacheManager cacheManager; protected KeyGenerator keyGenerator; @@ -52,8 +51,8 @@ public abstract class AbstractCachingConfiguration implements ImportAware { private Collection cachingConfigurers; public void setImportMetadata(AnnotationMetadata importMetadata) { - this.enableCaching = importMetadata.getAnnotationAttributes( - EnableCaching.class.getName(), false); + this.enableCaching = AnnotationAttributes.fromMap( + importMetadata.getAnnotationAttributes(EnableCaching.class.getName(), false)); Assert.notNull(this.enableCaching, "@EnableCaching is not present on importing class " + importMetadata.getClassName()); diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/ProxyCachingConfiguration.java b/spring-context/src/main/java/org/springframework/cache/annotation/ProxyCachingConfiguration.java index 476590beac2..91e5366f5dd 100644 --- a/spring-context/src/main/java/org/springframework/cache/annotation/ProxyCachingConfiguration.java +++ b/spring-context/src/main/java/org/springframework/cache/annotation/ProxyCachingConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -44,7 +44,7 @@ public class ProxyCachingConfiguration extends AbstractCachingConfiguration { new BeanFactoryCacheOperationSourceAdvisor(); advisor.setCacheOperationSource(cacheOperationSource()); advisor.setAdvice(cacheInterceptor()); - advisor.setOrder(((Integer)this.enableCaching.get("order"))); + advisor.setOrder(this.enableCaching.getNumber("order")); return advisor; } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AdviceModeImportSelector.java b/spring-context/src/main/java/org/springframework/context/annotation/AdviceModeImportSelector.java index ecfa675a752..8c4651bc37d 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/AdviceModeImportSelector.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/AdviceModeImportSelector.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -16,10 +16,12 @@ package org.springframework.context.annotation; +import static org.springframework.context.annotation.MetadataUtils.attributesFor; + import java.lang.annotation.Annotation; -import java.util.Map; import org.springframework.core.GenericTypeResolver; +import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; import org.springframework.util.Assert; @@ -64,26 +66,16 @@ public abstract class AdviceModeImportSelector implements * returns {@code null} */ public final String[] selectImports(AnnotationMetadata importingClassMetadata) { - Class annoType = GenericTypeResolver.resolveTypeArgument(getClass(), AdviceModeImportSelector.class); + Class annoType = GenericTypeResolver.resolveTypeArgument(this.getClass(), AdviceModeImportSelector.class); - Map attributes = importingClassMetadata.getAnnotationAttributes(annoType.getName()); + AnnotationAttributes attributes = attributesFor(importingClassMetadata, annoType); Assert.notNull(attributes, String.format( "@%s is not present on importing class '%s' as expected", annoType.getSimpleName(), importingClassMetadata.getClassName())); - String modeAttrName = getAdviceModeAttributeName(); - Assert.hasText(modeAttrName); + AdviceMode adviceMode = attributes.getEnum(this.getAdviceModeAttributeName()); - Object adviceMode = attributes.get(modeAttrName); - Assert.notNull(adviceMode, String.format( - "Advice mode attribute @%s#%s() does not exist", - annoType.getSimpleName(), modeAttrName)); - - Assert.isInstanceOf(AdviceMode.class, adviceMode, String.format( - "Incorrect type for advice mode attribute '@%s#%s()': ", - annoType.getSimpleName(), modeAttrName)); - - String[] imports = selectImports((AdviceMode) adviceMode); + String[] imports = selectImports(adviceMode); Assert.notNull(imports, String.format("Unknown AdviceMode: '%s'", adviceMode)); return imports; diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java index 2d3ee5ab8ea..82cea7c49c0 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -24,6 +24,7 @@ import org.springframework.beans.factory.support.AutowireCandidateQualifier; import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; +import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.env.Environment; import org.springframework.core.env.EnvironmentCapable; import org.springframework.core.env.StandardEnvironment; @@ -136,8 +137,9 @@ public class AnnotatedBeanDefinitionReader { AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass); AnnotationMetadata metadata = abd.getMetadata(); - if (ProfileHelper.isProfileAnnotationPresent(metadata)) { - if (!this.environment.acceptsProfiles(ProfileHelper.getCandidateProfiles(metadata))) { + if (metadata.isAnnotated(Profile.class.getName())) { + AnnotationAttributes profile = MetadataUtils.attributesFor(metadata, Profile.class); + if (!this.environment.acceptsProfiles(profile.getStringArray("value"))) { return; } } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java index bb80b3157cc..f0780a8fe23 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 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. @@ -24,6 +24,7 @@ import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; +import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; @@ -85,7 +86,7 @@ public class AnnotationBeanNameGenerator implements BeanNameGenerator { Set types = amd.getAnnotationTypes(); String beanName = null; for (String type : types) { - Map attributes = amd.getAnnotationAttributes(type); + AnnotationAttributes attributes = MetadataUtils.attributesFor(amd, type); if (isStereotypeWithNameValue(type, amd.getMetaAnnotationTypes(type), attributes)) { String value = (String) attributes.get("value"); if (StringUtils.hasLength(value)) { diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java index 42a053c2d3c..4b296177b99 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java @@ -16,6 +16,8 @@ package org.springframework.context.annotation; +import static org.springframework.context.annotation.MetadataUtils.attributesFor; + import java.util.LinkedHashSet; import java.util.Set; @@ -27,6 +29,7 @@ import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.core.type.AnnotationMetadata; import org.springframework.util.ClassUtils; /** @@ -217,21 +220,20 @@ public class AnnotationConfigUtils { } static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd) { - if (abd.getMetadata().isAnnotated(Primary.class.getName())) { + AnnotationMetadata metadata = abd.getMetadata(); + if (metadata.isAnnotated(Primary.class.getName())) { abd.setPrimary(true); } - if (abd.getMetadata().isAnnotated(Lazy.class.getName())) { - Boolean value = (Boolean) abd.getMetadata().getAnnotationAttributes(Lazy.class.getName()).get("value"); - abd.setLazyInit(value); + if (metadata.isAnnotated(Lazy.class.getName())) { + abd.setLazyInit(attributesFor(metadata, Lazy.class).getBoolean("value")); } - if (abd.getMetadata().isAnnotated(DependsOn.class.getName())) { - String[] value = (String[]) abd.getMetadata().getAnnotationAttributes(DependsOn.class.getName()).get("value"); - abd.setDependsOn(value); + if (metadata.isAnnotated(DependsOn.class.getName())) { + abd.setDependsOn(attributesFor(metadata, DependsOn.class).getStringArray("value")); } if (abd instanceof AbstractBeanDefinition) { - if (abd.getMetadata().isAnnotated(Role.class.getName())) { - int value = (Integer) abd.getMetadata().getAnnotationAttributes(Role.class.getName()).get("value"); - ((AbstractBeanDefinition)abd).setRole(value); + if (metadata.isAnnotated(Role.class.getName())) { + int role = attributesFor(metadata, Role.class).getNumber("value"); + ((AbstractBeanDefinition)abd).setRole(role); } } } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationScopeMetadataResolver.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationScopeMetadataResolver.java index 62f07731908..0d655830b39 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationScopeMetadataResolver.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationScopeMetadataResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -16,11 +16,13 @@ package org.springframework.context.annotation; +import static org.springframework.context.annotation.MetadataUtils.attributesFor; + import java.lang.annotation.Annotation; -import java.util.Map; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.util.Assert; /** @@ -75,11 +77,11 @@ public class AnnotationScopeMetadataResolver implements ScopeMetadataResolver { ScopeMetadata metadata = new ScopeMetadata(); if (definition instanceof AnnotatedBeanDefinition) { AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition; - Map attributes = - annDef.getMetadata().getAnnotationAttributes(this.scopeAnnotationType.getName()); + AnnotationAttributes attributes = + attributesFor(annDef.getMetadata(), this.scopeAnnotationType); if (attributes != null) { - metadata.setScopeName((String) attributes.get("value")); - ScopedProxyMode proxyMode = (ScopedProxyMode) attributes.get("proxyMode"); + metadata.setScopeName(attributes.getString("value")); + ScopedProxyMode proxyMode = attributes.getEnum("proxyMode"); if (proxyMode == null || proxyMode == ScopedProxyMode.DEFAULT) { proxyMode = this.defaultProxyMode; } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AspectJAutoProxyRegistrar.java b/spring-context/src/main/java/org/springframework/context/annotation/AspectJAutoProxyRegistrar.java index 3cb2f2448f6..b41fa7da253 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/AspectJAutoProxyRegistrar.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/AspectJAutoProxyRegistrar.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -16,10 +16,11 @@ package org.springframework.context.annotation; -import java.util.Map; +import static org.springframework.context.annotation.MetadataUtils.attributesFor; import org.springframework.aop.config.AopConfigUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; /** @@ -41,12 +42,11 @@ class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar { public void registerBeanDefinitions( AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { - Map enableAJAutoProxy = - importingClassMetadata.getAnnotationAttributes(EnableAspectJAutoProxy.class.getName()); - AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry); - if ((Boolean)enableAJAutoProxy.get("proxyTargetClass")) { + AnnotationAttributes enableAJAutoProxy = + attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class); + if (enableAJAutoProxy.getBoolean("proxyTargetClass")) { AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); } } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AutoProxyRegistrar.java b/spring-context/src/main/java/org/springframework/context/annotation/AutoProxyRegistrar.java index 2e063a85fc2..804dbca526b 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/AutoProxyRegistrar.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/AutoProxyRegistrar.java @@ -16,6 +16,8 @@ package org.springframework.context.annotation; +import static org.springframework.context.annotation.MetadataUtils.attributesFor; + import java.util.Map; import java.util.Set; @@ -23,6 +25,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.aop.config.AopConfigUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; /** @@ -58,7 +61,7 @@ public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar { boolean candidateFound = false; Set annoTypes = importingClassMetadata.getAnnotationTypes(); for (String annoType : annoTypes) { - Map candidate = importingClassMetadata.getAnnotationAttributes(annoType); + AnnotationAttributes candidate = attributesFor(importingClassMetadata, annoType); Object mode = candidate.get("mode"); Object proxyTargetClass = candidate.get("proxyTargetClass"); if (mode != null && proxyTargetClass != null diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java index 877ba1bddad..04a249566d9 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 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. @@ -29,6 +29,7 @@ import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.ResourceLoaderAware; +import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.env.StandardEnvironment; import org.springframework.core.env.Environment; import org.springframework.core.env.EnvironmentCapable; @@ -302,10 +303,11 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC for (TypeFilter tf : this.includeFilters) { if (tf.match(metadataReader, this.metadataReaderFactory)) { AnnotationMetadata metadata = metadataReader.getAnnotationMetadata(); - if (!ProfileHelper.isProfileAnnotationPresent(metadata)) { + if (!metadata.isAnnotated(Profile.class.getName())) { return true; } - return this.environment.acceptsProfiles(ProfileHelper.getCandidateProfiles(metadata)); + AnnotationAttributes profile = MetadataUtils.attributesFor(metadata, Profile.class); + return this.environment.acceptsProfiles(profile.getStringArray("value")); } } return false; diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java index 6042babd37a..9fa82e40c78 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -19,20 +19,20 @@ package org.springframework.context.annotation; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.Set; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; -import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.env.Environment; import org.springframework.core.io.ResourceLoader; import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.core.type.filter.AssignableTypeFilter; import org.springframework.core.type.filter.TypeFilter; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; /** @@ -46,18 +46,23 @@ import org.springframework.util.StringUtils; class ComponentScanAnnotationParser { private final ResourceLoader resourceLoader; + private final Environment environment; + private final BeanDefinitionRegistry registry; - public ComponentScanAnnotationParser(ResourceLoader resourceLoader, Environment environment, BeanDefinitionRegistry registry) { + + public ComponentScanAnnotationParser( + ResourceLoader resourceLoader, Environment environment, BeanDefinitionRegistry registry) { this.resourceLoader = resourceLoader; this.environment = environment; this.registry = registry; } - public Set parse(Map componentScanAttributes) { + + public Set parse(AnnotationAttributes componentScan) { ClassPathBeanDefinitionScanner scanner = - new ClassPathBeanDefinitionScanner(registry, (Boolean)componentScanAttributes.get("useDefaultFilters")); + new ClassPathBeanDefinitionScanner(registry, componentScan.getBoolean("useDefaultFilters")); Assert.notNull(this.environment, "Environment must not be null"); scanner.setEnvironment(this.environment); @@ -65,46 +70,43 @@ class ComponentScanAnnotationParser { Assert.notNull(this.resourceLoader, "ResourceLoader must not be null"); scanner.setResourceLoader(this.resourceLoader); - scanner.setBeanNameGenerator(BeanUtils.instantiateClass( - (Class)componentScanAttributes.get("nameGenerator"), BeanNameGenerator.class)); + Class generatorClass = componentScan.getClass("nameGenerator"); + scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass)); - ScopedProxyMode scopedProxyMode = (ScopedProxyMode) componentScanAttributes.get("scopedProxy"); + ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy"); if (scopedProxyMode != ScopedProxyMode.DEFAULT) { scanner.setScopedProxyMode(scopedProxyMode); } else { - scanner.setScopeMetadataResolver(BeanUtils.instantiateClass( - (Class)componentScanAttributes.get("scopeResolver"), ScopeMetadataResolver.class)); + Class resolverClass = componentScan.getClass("scopeResolver"); + scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass)); } - scanner.setResourcePattern((String)componentScanAttributes.get("resourcePattern")); + scanner.setResourcePattern(componentScan.getString("resourcePattern")); - for (Filter filterAnno : (Filter[])componentScanAttributes.get("includeFilters")) { - for (TypeFilter typeFilter : typeFiltersFor(filterAnno)) { + for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) { + for (TypeFilter typeFilter : typeFiltersFor(filter)) { scanner.addIncludeFilter(typeFilter); } } - for (Filter filterAnno : (Filter[])componentScanAttributes.get("excludeFilters")) { - for (TypeFilter typeFilter : typeFiltersFor(filterAnno)) { + for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) { + for (TypeFilter typeFilter : typeFiltersFor(filter)) { scanner.addExcludeFilter(typeFilter); } } List basePackages = new ArrayList(); - for (String pkg : (String[])componentScanAttributes.get("value")) { + for (String pkg : componentScan.getStringArray("value")) { if (StringUtils.hasText(pkg)) { basePackages.add(pkg); } } - for (String pkg : (String[])componentScanAttributes.get("basePackages")) { + for (String pkg : componentScan.getStringArray("basePackages")) { if (StringUtils.hasText(pkg)) { basePackages.add(pkg); } } - for (Class clazz : (Class[])componentScanAttributes.get("basePackageClasses")) { - // TODO: loading user types directly here. implications on load-time - // weaving may mean we need to revert to stringified class names in - // annotation metadata - basePackages.add(clazz.getPackage().getName()); + for (Class clazz : componentScan.getClassArray("basePackageClasses")) { + basePackages.add(ClassUtils.getPackageName(clazz)); } if (basePackages.isEmpty()) { @@ -114,10 +116,12 @@ class ComponentScanAnnotationParser { return scanner.doScan(basePackages.toArray(new String[]{})); } - private List typeFiltersFor(Filter filterAnno) { + private List typeFiltersFor(AnnotationAttributes filterAttributes) { List typeFilters = new ArrayList(); - for (Class filterClass : (Class[])filterAnno.value()) { - switch (filterAnno.type()) { + FilterType filterType = filterAttributes.getEnum("type"); + + for (Class filterClass : filterAttributes.getClassArray("value")) { + switch (filterType) { case ANNOTATION: Assert.isAssignable(Annotation.class, filterClass, "An error occured when processing a @ComponentScan " + @@ -136,7 +140,7 @@ class ComponentScanAnnotationParser { typeFilters.add(BeanUtils.instantiateClass(filterClass, TypeFilter.class)); break; default: - throw new IllegalArgumentException("unknown filter type " + filterAnno.type()); + throw new IllegalArgumentException("unknown filter type " + filterType); } } return typeFilters; diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java index e7a8baa24e1..5268e633aa0 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2012 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. @@ -25,11 +25,13 @@ import java.util.Set; import org.springframework.beans.factory.parsing.Location; import org.springframework.beans.factory.parsing.Problem; import org.springframework.beans.factory.parsing.ProblemReporter; +import org.springframework.beans.factory.support.BeanDefinitionReader; import org.springframework.core.io.DescriptiveResource; import org.springframework.core.io.Resource; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.StandardAnnotationMetadata; import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.util.Assert; import org.springframework.util.ClassUtils; /** @@ -49,25 +51,73 @@ final class ConfigurationClass { private final Resource resource; - private final Map> importedResources = new LinkedHashMap>(); + private final Map> importedResources = + new LinkedHashMap>(); private final Set beanMethods = new LinkedHashSet(); private String beanName; + private final boolean imported; + + /** + * Create a new {@link ConfigurationClass} with the given name. + * @param metadataReader reader used to parse the underlying {@link Class} + * @param beanName must not be {@code null} + * @throws IllegalArgumentException if beanName is null (as of Spring 3.1.1) + * @see ConfigurationClass#ConfigurationClass(Class, boolean) + */ public ConfigurationClass(MetadataReader metadataReader, String beanName) { + Assert.hasText(beanName, "bean name must not be null"); this.metadata = metadataReader.getAnnotationMetadata(); this.resource = metadataReader.getResource(); this.beanName = beanName; + this.imported = false; } + /** + * Create a new {@link ConfigurationClass} representing a class that was imported + * using the {@link Import} annotation or automatically processed as a nested + * configuration class (if imported is {@code true}). + * @param metadataReader reader used to parse the underlying {@link Class} + * @param beanName name of the {@code @Configuration} class bean + * @since 3.1.1 + */ + public ConfigurationClass(MetadataReader metadataReader, boolean imported) { + this.metadata = metadataReader.getAnnotationMetadata(); + this.resource = metadataReader.getResource(); + this.imported = imported; + } + + /** + * Create a new {@link ConfigurationClass} with the given name. + * @param clazz the underlying {@link Class} to represent + * @param beanName name of the {@code @Configuration} class bean + * @throws IllegalArgumentException if beanName is null (as of Spring 3.1.1) + * @see ConfigurationClass#ConfigurationClass(Class, boolean) + */ public ConfigurationClass(Class clazz, String beanName) { - this.metadata = new StandardAnnotationMetadata(clazz); + Assert.hasText(beanName, "bean name must not be null"); + this.metadata = new StandardAnnotationMetadata(clazz, true); this.resource = new DescriptiveResource(clazz.toString()); this.beanName = beanName; + this.imported = false; } + /** + * Create a new {@link ConfigurationClass} representing a class that was imported + * using the {@link Import} annotation or automatically processed as a nested + * configuration class (if imported is {@code true}). + * @param clazz the underlying {@link Class} to represent + * @param beanName name of the {@code @Configuration} class bean + * @since 3.1.1 + */ + public ConfigurationClass(Class clazz, boolean imported) { + this.metadata = new StandardAnnotationMetadata(clazz, true); + this.resource = new DescriptiveResource(clazz.toString()); + this.imported = imported; + } public AnnotationMetadata getMetadata() { return this.metadata; @@ -81,6 +131,15 @@ final class ConfigurationClass { return ClassUtils.getShortName(getMetadata().getClassName()); } + /** + * Return whether this configuration class was registered via @{@link Import} or + * automatically registered due to being nested within another configuration class. + * @since 3.1.1 + */ + public boolean isImported() { + return this.imported; + } + public void setBeanName(String beanName) { this.beanName = beanName; } @@ -97,11 +156,12 @@ final class ConfigurationClass { return this.beanMethods; } - public void addImportedResource(String importedResource, Class readerClass) { + public void addImportedResource( + String importedResource, Class readerClass) { this.importedResources.put(importedResource, readerClass); } - public Map> getImportedResources() { + public Map> getImportedResources() { return this.importedResources; } @@ -181,5 +241,4 @@ final class ConfigurationClass { } } - } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java index 1ed1d786df4..ffa0e08d821 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -27,6 +27,7 @@ import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor; @@ -43,6 +44,8 @@ import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.env.Environment; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.type.AnnotationMetadata; @@ -51,6 +54,8 @@ import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.util.StringUtils; +import static org.springframework.context.annotation.MetadataUtils.*; + /** * Reads a given fully-populated set of ConfigurationClass instances, registering bean * definitions with the given {@link BeanDefinitionRegistry} based on its contents. @@ -76,7 +81,10 @@ class ConfigurationClassBeanDefinitionReader { private final MetadataReaderFactory metadataReaderFactory; - private ResourceLoader resourceLoader; + private final ResourceLoader resourceLoader; + + private final Environment environment; + /** * Create a new {@link ConfigurationClassBeanDefinitionReader} instance that will be used @@ -86,13 +94,14 @@ class ConfigurationClassBeanDefinitionReader { */ public ConfigurationClassBeanDefinitionReader(BeanDefinitionRegistry registry, SourceExtractor sourceExtractor, ProblemReporter problemReporter, MetadataReaderFactory metadataReaderFactory, - ResourceLoader resourceLoader) { + ResourceLoader resourceLoader, Environment environment) { this.registry = registry; this.sourceExtractor = sourceExtractor; this.problemReporter = problemReporter; this.metadataReaderFactory = metadataReaderFactory; this.resourceLoader = resourceLoader; + this.environment = environment; } @@ -122,32 +131,43 @@ class ConfigurationClassBeanDefinitionReader { * Register the {@link Configuration} class itself as a bean definition. */ private void doLoadBeanDefinitionForConfigurationClassIfNecessary(ConfigurationClass configClass) { - if (configClass.getBeanName() != null) { - // a bean definition already exists for this configuration class -> nothing to do + if (!configClass.isImported()) { return; } - // no bean definition exists yet -> this must be an imported configuration class (@Import). BeanDefinition configBeanDef = new GenericBeanDefinition(); String className = configClass.getMetadata().getClassName(); configBeanDef.setBeanClassName(className); + MetadataReader reader; + try { + reader = this.metadataReaderFactory.getMetadataReader(className); + } + catch (IOException ex) { + throw new IllegalStateException("Could not create MetadataReader for class " + className); + } if (ConfigurationClassUtils.checkConfigurationClassCandidate(configBeanDef, this.metadataReaderFactory)) { - String configBeanName = BeanDefinitionReaderUtils.registerWithGeneratedName((AbstractBeanDefinition)configBeanDef, this.registry); + Map configAttributes = + reader.getAnnotationMetadata().getAnnotationAttributes(Configuration.class.getName()); + + // has the 'value' attribute of @Configuration been set? + String configBeanName = (String) configAttributes.get("value"); + if (StringUtils.hasText(configBeanName)) { + // yes -> register the configuration class bean with this name + this.registry.registerBeanDefinition(configBeanName, configBeanDef); + } + else { + // no -> register the configuration class bean with a generated name + configBeanName = BeanDefinitionReaderUtils.registerWithGeneratedName((AbstractBeanDefinition)configBeanDef, this.registry); + } configClass.setBeanName(configBeanName); if (logger.isDebugEnabled()) { logger.debug(String.format("Registered bean definition for imported @Configuration class %s", configBeanName)); } } else { - try { - MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className); - AnnotationMetadata metadata = reader.getAnnotationMetadata(); - this.problemReporter.error( - new InvalidConfigurationImportProblem(className, reader.getResource(), metadata)); - } - catch (IOException ex) { - throw new IllegalStateException("Could not create MetadataReader for class " + className); - } + AnnotationMetadata metadata = reader.getAnnotationMetadata(); + this.problemReporter.error( + new InvalidConfigurationImportProblem(className, reader.getResource(), metadata)); } } @@ -176,15 +196,14 @@ class ConfigurationClassBeanDefinitionReader { beanDef.setAttribute(RequiredAnnotationBeanPostProcessor.SKIP_REQUIRED_CHECK_ATTRIBUTE, Boolean.TRUE); // consider role - Map roleAttributes = metadata.getAnnotationAttributes(Role.class.getName()); - if (roleAttributes != null) { - int role = (Integer) roleAttributes.get("value"); - beanDef.setRole(role); + AnnotationAttributes role = attributesFor(metadata, Role.class); + if (role != null) { + beanDef.setRole(role.getNumber("value")); } // consider name and any aliases - Map beanAttributes = metadata.getAnnotationAttributes(Bean.class.getName()); - List names = new ArrayList(Arrays.asList((String[]) beanAttributes.get("name"))); + AnnotationAttributes bean = attributesFor(metadata, Bean.class); + List names = new ArrayList(Arrays.asList(bean.getStringArray("name"))); String beanName = (names.size() > 0 ? names.remove(0) : beanMethod.getMetadata().getMethodName()); for (String alias : names) { this.registry.registerAlias(beanName, alias); @@ -211,40 +230,43 @@ class ConfigurationClassBeanDefinitionReader { // is this bean to be instantiated lazily? if (metadata.isAnnotated(Lazy.class.getName())) { - beanDef.setLazyInit((Boolean) metadata.getAnnotationAttributes(Lazy.class.getName()).get("value")); + AnnotationAttributes lazy = attributesFor(metadata, Lazy.class); + beanDef.setLazyInit(lazy.getBoolean("value")); } else if (configClass.getMetadata().isAnnotated(Lazy.class.getName())){ - beanDef.setLazyInit((Boolean) configClass.getMetadata().getAnnotationAttributes(Lazy.class.getName()).get("value")); + AnnotationAttributes lazy = attributesFor(configClass.getMetadata(), Lazy.class); + beanDef.setLazyInit(lazy.getBoolean("value")); } if (metadata.isAnnotated(DependsOn.class.getName())) { - String[] dependsOn = (String[]) metadata.getAnnotationAttributes(DependsOn.class.getName()).get("value"); - if (dependsOn.length > 0) { - beanDef.setDependsOn(dependsOn); + AnnotationAttributes dependsOn = attributesFor(metadata, DependsOn.class); + String[] otherBeans = dependsOn.getStringArray("value"); + if (otherBeans.length > 0) { + beanDef.setDependsOn(otherBeans); } } - Autowire autowire = (Autowire) beanAttributes.get("autowire"); + Autowire autowire = bean.getEnum("autowire"); if (autowire.isAutowire()) { beanDef.setAutowireMode(autowire.value()); } - String initMethodName = (String) beanAttributes.get("initMethod"); + String initMethodName = bean.getString("initMethod"); if (StringUtils.hasText(initMethodName)) { beanDef.setInitMethodName(initMethodName); } - String destroyMethodName = (String) beanAttributes.get("destroyMethod"); + String destroyMethodName = bean.getString("destroyMethod"); if (StringUtils.hasText(destroyMethodName)) { beanDef.setDestroyMethodName(destroyMethodName); } // consider scoping ScopedProxyMode proxyMode = ScopedProxyMode.NO; - Map scopeAttributes = metadata.getAnnotationAttributes(Scope.class.getName()); - if (scopeAttributes != null) { - beanDef.setScope((String) scopeAttributes.get("value")); - proxyMode = (ScopedProxyMode) scopeAttributes.get("proxyMode"); + AnnotationAttributes scope = attributesFor(metadata, Scope.class); + if (scope != null) { + beanDef.setScope(scope.getString("value")); + proxyMode = scope.getEnum("proxyMode"); if (proxyMode == ScopedProxyMode.DEFAULT) { proxyMode = ScopedProxyMode.NO; } @@ -266,22 +288,24 @@ class ConfigurationClassBeanDefinitionReader { } - private void loadBeanDefinitionsFromImportedResources(Map> importedResources) { + private void loadBeanDefinitionsFromImportedResources( + Map> importedResources) { + Map, BeanDefinitionReader> readerInstanceCache = new HashMap, BeanDefinitionReader>(); - for (Map.Entry> entry : importedResources.entrySet()) { + for (Map.Entry> entry : importedResources.entrySet()) { String resource = entry.getKey(); - Class readerClass = entry.getValue(); + Class readerClass = entry.getValue(); if (!readerInstanceCache.containsKey(readerClass)) { try { // Instantiate the specified BeanDefinitionReader - BeanDefinitionReader readerInstance = (BeanDefinitionReader) + BeanDefinitionReader readerInstance = readerClass.getConstructor(BeanDefinitionRegistry.class).newInstance(this.registry); - // Delegate the current ResourceLoader to it if possible if (readerInstance instanceof AbstractBeanDefinitionReader) { - ((AbstractBeanDefinitionReader)readerInstance).setResourceLoader(this.resourceLoader); + AbstractBeanDefinitionReader abdr = ((AbstractBeanDefinitionReader) readerInstance); + abdr.setResourceLoader(this.resourceLoader); + abdr.setEnvironment(this.environment); } - readerInstanceCache.put(readerClass, readerInstance); } catch (Exception ex) { @@ -336,6 +360,7 @@ class ConfigurationClassBeanDefinitionReader { * declare at least one {@link Bean @Bean} method. */ private static class InvalidConfigurationImportProblem extends Problem { + public InvalidConfigurationImportProblem(String className, Resource resource, AnnotationMetadata metadata) { super(String.format("%s was @Import'ed but is not annotated with @Configuration " + "nor does it declare any @Bean methods; it does not implement ImportSelector " + diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java index 00528aa87d7..a6533c91117 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -16,6 +16,8 @@ package org.springframework.context.annotation; +import static org.springframework.context.annotation.MetadataUtils.attributesFor; + import java.io.IOException; import java.lang.annotation.Annotation; import java.util.ArrayList; @@ -34,7 +36,9 @@ import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.parsing.Location; import org.springframework.beans.factory.parsing.Problem; import org.springframework.beans.factory.parsing.ProblemReporter; +import org.springframework.beans.factory.support.BeanDefinitionReader; import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.env.Environment; import org.springframework.core.env.PropertySource; import org.springframework.core.io.ResourceLoader; @@ -119,8 +123,7 @@ class ConfigurationClassParser { /** * Parse the specified {@link Configuration @Configuration} class. * @param clazz the Class to parse - * @param beanName may be null, but if populated represents the bean id - * (assumes that this configuration class was configured via XML) + * @param beanName must not be null (as of Spring 3.1.1) */ public void parse(Class clazz, String beanName) throws IOException { processConfigurationClass(new ConfigurationClass(clazz, beanName)); @@ -128,8 +131,9 @@ class ConfigurationClassParser { protected void processConfigurationClass(ConfigurationClass configClass) throws IOException { AnnotationMetadata metadata = configClass.getMetadata(); - if (this.environment != null && ProfileHelper.isProfileAnnotationPresent(metadata)) { - if (!this.environment.acceptsProfiles(ProfileHelper.getCandidateProfiles(metadata))) { + if (this.environment != null && metadata.isAnnotated(Profile.class.getName())) { + AnnotationAttributes profile = MetadataUtils.attributesFor(metadata, Profile.class); + if (!this.environment.acceptsProfiles(profile.getStringArray("value"))) { return; } } @@ -140,7 +144,7 @@ class ConfigurationClassParser { if (superClassName != null && !Object.class.getName().equals(superClassName)) { if (metadata instanceof StandardAnnotationMetadata) { Class clazz = ((StandardAnnotationMetadata) metadata).getIntrospectedClass(); - metadata = new StandardAnnotationMetadata(clazz.getSuperclass()); + metadata = new StandardAnnotationMetadata(clazz.getSuperclass(), true); } else { MetadataReader reader = this.metadataReaderFactory.getMetadataReader(superClassName); @@ -167,16 +171,16 @@ class ConfigurationClassParser { MetadataReader reader = this.metadataReaderFactory.getMetadataReader(memberClassName); AnnotationMetadata memberClassMetadata = reader.getAnnotationMetadata(); if (ConfigurationClassUtils.isConfigurationCandidate(memberClassMetadata)) { - processConfigurationClass(new ConfigurationClass(reader, null)); + processConfigurationClass(new ConfigurationClass(reader, true)); } } // process any @PropertySource annotations - Map propertySourceAttributes = - metadata.getAnnotationAttributes(org.springframework.context.annotation.PropertySource.class.getName()); - if (propertySourceAttributes != null) { - String name = (String) propertySourceAttributes.get("name"); - String[] locations = (String[]) propertySourceAttributes.get("value"); + AnnotationAttributes propertySource = + attributesFor(metadata, org.springframework.context.annotation.PropertySource.class); + if (propertySource != null) { + String name = propertySource.getString("name"); + String[] locations = propertySource.getStringArray("value"); ClassLoader classLoader = this.resourceLoader.getClassLoader(); for (String location : locations) { location = this.environment.resolveRequiredPlaceholders(location); @@ -188,34 +192,31 @@ class ConfigurationClassParser { } // process any @ComponentScan annotions - Map componentScanAttributes = metadata.getAnnotationAttributes(ComponentScan.class.getName()); - if (componentScanAttributes != null) { + AnnotationAttributes componentScan = attributesFor(metadata, ComponentScan.class); + if (componentScan != null) { // the config class is annotated with @ComponentScan -> perform the scan immediately - Set scannedBeanDefinitions = this.componentScanParser.parse(componentScanAttributes); + Set scannedBeanDefinitions = this.componentScanParser.parse(componentScan); // check the set of scanned definitions for any further config classes and parse recursively if necessary for (BeanDefinitionHolder holder : scannedBeanDefinitions) { - if (ConfigurationClassUtils.checkConfigurationClassCandidate(holder.getBeanDefinition(), metadataReaderFactory)) { + if (ConfigurationClassUtils.checkConfigurationClassCandidate(holder.getBeanDefinition(), this.metadataReaderFactory)) { this.parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName()); } } } // process any @Import annotations - List> allImportAttribs = + List imports = findAllAnnotationAttributes(Import.class, metadata.getClassName(), true); - for (Map importAttribs : allImportAttribs) { - processImport(configClass, (String[]) importAttribs.get("value"), true); + for (AnnotationAttributes importAnno : imports) { + processImport(configClass, importAnno.getStringArray("value"), true); } // process any @ImportResource annotations if (metadata.isAnnotated(ImportResource.class.getName())) { - String[] resources = (String[]) metadata.getAnnotationAttributes(ImportResource.class.getName()).get("value"); - Class readerClass = (Class) metadata.getAnnotationAttributes(ImportResource.class.getName()).get("reader"); - if (readerClass == null) { - throw new IllegalStateException("No reader class associated with imported resources: " + - StringUtils.arrayToCommaDelimitedString(resources)); - } + AnnotationAttributes importResource = attributesFor(metadata, ImportResource.class); + String[] resources = importResource.getStringArray("value"); + Class readerClass = importResource.getClass("reader"); for (String resource : resources) { configClass.addImportedResource(resource, readerClass); } @@ -239,11 +240,11 @@ class ConfigurationClassParser { * @param annotatedClassName the class to inspect * @param classValuesAsString whether class attributes should be returned as strings */ - private List> findAllAnnotationAttributes( + private List findAllAnnotationAttributes( Class targetAnnotation, String annotatedClassName, boolean classValuesAsString) throws IOException { - List> allAttribs = new ArrayList>(); + List allAttribs = new ArrayList(); MetadataReader reader = this.metadataReaderFactory.getMetadataReader(annotatedClassName); AnnotationMetadata metadata = reader.getAnnotationMetadata(); @@ -253,16 +254,17 @@ class ConfigurationClassParser { if (annotationType.equals(targetAnnotationType)) { continue; } - MetadataReader metaReader = this.metadataReaderFactory.getMetadataReader(annotationType); - Map targetAttribs = - metaReader.getAnnotationMetadata().getAnnotationAttributes(targetAnnotationType, classValuesAsString); + AnnotationMetadata metaAnnotations = + this.metadataReaderFactory.getMetadataReader(annotationType).getAnnotationMetadata(); + AnnotationAttributes targetAttribs = + AnnotationAttributes.fromMap(metaAnnotations.getAnnotationAttributes(targetAnnotationType, classValuesAsString)); if (targetAttribs != null) { allAttribs.add(targetAttribs); } } - Map localAttribs = - metadata.getAnnotationAttributes(targetAnnotationType, classValuesAsString); + AnnotationAttributes localAttribs = + AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(targetAnnotationType, classValuesAsString)); if (localAttribs != null) { allAttribs.add(localAttribs); } @@ -300,7 +302,7 @@ class ConfigurationClassParser { else { // the candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -> process it as a @Configuration class this.importStack.registerImport(importingClassMetadata.getClassName(), candidate); - processConfigurationClass(new ConfigurationClass(reader, null)); + processConfigurationClass(new ConfigurationClass(reader, true)); } } this.importStack.pop(); diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java index 919ab4f9952..8e724106de8 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -199,8 +199,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo private ConfigurationClassBeanDefinitionReader getConfigurationClassBeanDefinitionReader(BeanDefinitionRegistry registry) { if (this.reader == null) { - this.reader = new ConfigurationClassBeanDefinitionReader( - registry, this.sourceExtractor, this.problemReporter, this.metadataReaderFactory, this.resourceLoader); + this.reader = new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, + this.problemReporter, this.metadataReaderFactory, this.resourceLoader, this.environment); } return this.reader; } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java index 5a909771056..645203933af 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -60,7 +60,8 @@ abstract class ConfigurationClassUtils { // Check already loaded Class if present... // since we possibly can't even load the class file for this Class. if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) { - metadata = new StandardAnnotationMetadata(((AbstractBeanDefinition) beanDef).getBeanClass()); + Class beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass(); + metadata = new StandardAnnotationMetadata(beanClass, true); } else { String className = beanDef.getBeanClassName(); diff --git a/spring-context/src/main/java/org/springframework/context/annotation/LoadTimeWeavingConfiguration.java b/spring-context/src/main/java/org/springframework/context/annotation/LoadTimeWeavingConfiguration.java index 7acf3abbf0f..3f74a3d7795 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/LoadTimeWeavingConfiguration.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/LoadTimeWeavingConfiguration.java @@ -18,8 +18,6 @@ package org.springframework.context.annotation; import static org.springframework.context.weaving.AspectJWeavingEnabler.ASPECTJ_AOP_XML_RESOURCE; -import java.util.Map; - import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanDefinition; @@ -27,6 +25,7 @@ import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.EnableLoadTimeWeaving.AspectJWeaving; import org.springframework.context.weaving.AspectJWeavingEnabler; import org.springframework.context.weaving.DefaultContextLoadTimeWeaver; +import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; import org.springframework.instrument.classloading.LoadTimeWeaver; import org.springframework.util.Assert; @@ -46,7 +45,7 @@ import org.springframework.util.Assert; @Configuration public class LoadTimeWeavingConfiguration implements ImportAware, BeanClassLoaderAware { - private Map enableLTW; + private AnnotationAttributes enableLTW; @Autowired(required=false) private LoadTimeWeavingConfigurer ltwConfigurer; @@ -54,7 +53,7 @@ public class LoadTimeWeavingConfiguration implements ImportAware, BeanClassLoade private ClassLoader beanClassLoader; public void setImportMetadata(AnnotationMetadata importMetadata) { - this.enableLTW = importMetadata.getAnnotationAttributes(EnableLoadTimeWeaving.class.getName(), false); + this.enableLTW = MetadataUtils.attributesFor(importMetadata, EnableLoadTimeWeaving.class); Assert.notNull(this.enableLTW, "@EnableLoadTimeWeaving is not present on importing class " + importMetadata.getClassName()); @@ -79,7 +78,8 @@ public class LoadTimeWeavingConfiguration implements ImportAware, BeanClassLoade loadTimeWeaver = new DefaultContextLoadTimeWeaver(this.beanClassLoader); } - switch ((AspectJWeaving) this.enableLTW.get("aspectjWeaving")) { + AspectJWeaving aspectJWeaving = this.enableLTW.getEnum("aspectjWeaving"); + switch (aspectJWeaving) { case DISABLED: // AJ weaving is disabled -> do nothing break; diff --git a/spring-context/src/main/java/org/springframework/context/annotation/MetadataUtils.java b/spring-context/src/main/java/org/springframework/context/annotation/MetadataUtils.java new file mode 100644 index 00000000000..a2dc4b36105 --- /dev/null +++ b/spring-context/src/main/java/org/springframework/context/annotation/MetadataUtils.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2012 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.context.annotation; + +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.MethodMetadata; + +/** + * Convenience methods adapting {@link AnnotationMetadata} and {@link MethodMetadata} + * annotation attribute maps to the {@link AnnotationAttributes} API. As of Spring 3.1.1, + * both the reflection- and ASM-based implementations of these SPIs return + * {@link AnnotationAttributes} instances anyway, but for backward-compatibility, their + * signatures still return Maps. Therefore, for the usual case, these methods perform + * little more than a cast from Map to AnnotationAttributes. + * + * @author Chris Beams + * @since 3.1.1 + * @see AnnotationAttributes#fromMap(java.util.Map) + */ +class MetadataUtils { + + public static AnnotationAttributes attributesFor(AnnotationMetadata metadata, Class annoClass) { + return attributesFor(metadata, annoClass.getName()); + } + + public static AnnotationAttributes attributesFor(AnnotationMetadata metadata, String annoClassName) { + return AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(annoClassName, false)); + } + + public static AnnotationAttributes attributesFor(MethodMetadata metadata, Class targetAnno) { + return AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(targetAnno.getName())); + } + +} diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ProfileHelper.java b/spring-context/src/main/java/org/springframework/context/annotation/ProfileHelper.java deleted file mode 100644 index 309aa877f10..00000000000 --- a/spring-context/src/main/java/org/springframework/context/annotation/ProfileHelper.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2002-2011 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.context.annotation; - -import org.springframework.core.type.AnnotationMetadata; - -class ProfileHelper { - /** - * Return whether the given metadata includes Profile information, whether directly or - * through meta-annotation. - */ - static boolean isProfileAnnotationPresent(AnnotationMetadata metadata) { - return metadata.isAnnotated(Profile.class.getName()); - } - - /** - * Return the String[] of candidate profiles from {@link Profile#value()}. - */ - static String[] getCandidateProfiles(AnnotationMetadata metadata) { - return (String[])metadata.getAnnotationAttributes(Profile.class.getName()).get("value"); - } -} \ No newline at end of file diff --git a/spring-context/src/main/java/org/springframework/context/support/ReloadableResourceBundleMessageSource.java b/spring-context/src/main/java/org/springframework/context/support/ReloadableResourceBundleMessageSource.java index a51d6fc4072..da8af17fb07 100644 --- a/spring-context/src/main/java/org/springframework/context/support/ReloadableResourceBundleMessageSource.java +++ b/spring-context/src/main/java/org/springframework/context/support/ReloadableResourceBundleMessageSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 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. @@ -135,7 +135,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource * @see java.util.ResourceBundle */ public void setBasename(String basename) { - setBasenames(new String[] {basename}); + setBasenames(basename); } /** @@ -153,7 +153,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource * @see #setBasename * @see java.util.ResourceBundle */ - public void setBasenames(String[] basenames) { + public void setBasenames(String... basenames) { if (basenames != null) { this.basenames = new String[basenames.length]; for (int i = 0; i < basenames.length; i++) { diff --git a/spring-context/src/main/java/org/springframework/context/support/ResourceBundleMessageSource.java b/spring-context/src/main/java/org/springframework/context/support/ResourceBundleMessageSource.java index 9080ebd3332..a3426dc1d5b 100644 --- a/spring-context/src/main/java/org/springframework/context/support/ResourceBundleMessageSource.java +++ b/spring-context/src/main/java/org/springframework/context/support/ResourceBundleMessageSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2012 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. @@ -100,7 +100,7 @@ public class ResourceBundleMessageSource extends AbstractMessageSource implement * @see java.util.ResourceBundle#getBundle(String) */ public void setBasename(String basename) { - setBasenames(new String[] {basename}); + setBasenames(basename); } /** @@ -119,7 +119,7 @@ public class ResourceBundleMessageSource extends AbstractMessageSource implement * @see #setBasename * @see java.util.ResourceBundle#getBundle(String) */ - public void setBasenames(String[] basenames) { + public void setBasenames(String... basenames) { if (basenames != null) { this.basenames = new String[basenames.length]; for (int i = 0; i < basenames.length; i++) { diff --git a/spring-context/src/main/java/org/springframework/instrument/classloading/jboss/JBossLoadTimeWeaver.java b/spring-context/src/main/java/org/springframework/instrument/classloading/jboss/JBossLoadTimeWeaver.java index 243724af27d..706fda38300 100644 --- a/spring-context/src/main/java/org/springframework/instrument/classloading/jboss/JBossLoadTimeWeaver.java +++ b/spring-context/src/main/java/org/springframework/instrument/classloading/jboss/JBossLoadTimeWeaver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -36,6 +36,7 @@ import org.springframework.util.ClassUtils; *

Thanks to Ales Justin and Marius Bogoevici for the initial prototype. * * @author Costin Leau + * @author Juergen Hoeller * @since 3.0 */ public class JBossLoadTimeWeaver implements LoadTimeWeaver { @@ -55,23 +56,18 @@ public class JBossLoadTimeWeaver implements LoadTimeWeaver { /** * Create a new instance of the {@link JBossLoadTimeWeaver} class using * the supplied {@link ClassLoader}. - * @param classLoader the ClassLoader to delegate to for - * weaving (must not be null) + * @param classLoader the ClassLoader to delegate to for weaving + * (must not be null) */ public JBossLoadTimeWeaver(ClassLoader classLoader) { Assert.notNull(classLoader, "ClassLoader must not be null"); - String loaderClassName = classLoader.getClass().getName(); - - if (loaderClassName.startsWith("org.jboss.classloader")) { - // JBoss AS 5 or JBoss AS 6 - this.adapter = new JBossMCAdapter(classLoader); - } - else if (loaderClassName.startsWith("org.jboss.modules")) { + if (classLoader.getClass().getName().startsWith("org.jboss.modules")) { // JBoss AS 7 this.adapter = new JBossModulesAdapter(classLoader); } else { - throw new IllegalArgumentException("Unexpected ClassLoader type: " + loaderClassName); + // JBoss AS 5 or JBoss AS 6 + this.adapter = new JBossMCAdapter(classLoader); } } diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/AbstractAsyncConfiguration.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/AbstractAsyncConfiguration.java index d0bbe8899b4..57fd7b69452 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/AbstractAsyncConfiguration.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/AbstractAsyncConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -17,12 +17,12 @@ package org.springframework.scheduling.annotation; import java.util.Collection; -import java.util.Map; import java.util.concurrent.Executor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportAware; +import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; import org.springframework.util.Assert; @@ -37,12 +37,13 @@ import org.springframework.util.Assert; @Configuration public abstract class AbstractAsyncConfiguration implements ImportAware { - protected Map enableAsync; + protected AnnotationAttributes enableAsync; protected Executor executor; public void setImportMetadata(AnnotationMetadata importMetadata) { - enableAsync = importMetadata.getAnnotationAttributes(EnableAsync.class.getName(), false); - Assert.notNull(enableAsync, + this.enableAsync = AnnotationAttributes.fromMap( + importMetadata.getAnnotationAttributes(EnableAsync.class.getName(), false)); + Assert.notNull(this.enableAsync, "@EnableAsync is not present on importing class " + importMetadata.getClassName()); } diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/ProxyAsyncConfiguration.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/ProxyAsyncConfiguration.java index 93589de7852..8ec439db2e5 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/ProxyAsyncConfiguration.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/ProxyAsyncConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -42,13 +42,11 @@ public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration { @Bean(name=AnnotationConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public AsyncAnnotationBeanPostProcessor asyncAdvisor() { - Assert.notNull(enableAsync, "@EnableAsync annotation metadata was not injected"); + Assert.notNull(this.enableAsync, "@EnableAsync annotation metadata was not injected"); AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor(); - @SuppressWarnings("unchecked") - Class customAsyncAnnotation = - (Class) enableAsync.get("annotation"); + Class customAsyncAnnotation = enableAsync.getClass("annotation"); if (customAsyncAnnotation != AnnotationUtils.getDefaultValue(EnableAsync.class, "annotation")) { bpp.setAsyncAnnotationType(customAsyncAnnotation); } @@ -57,9 +55,8 @@ public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration { bpp.setExecutor(this.executor); } - bpp.setProxyTargetClass((Boolean) enableAsync.get("proxyTargetClass")); - - bpp.setOrder(((Integer) enableAsync.get("order"))); + bpp.setProxyTargetClass(this.enableAsync.getBoolean("proxyTargetClass")); + bpp.setOrder(this.enableAsync.getNumber("order")); return bpp; } diff --git a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ForkJoinPoolFactoryBean.java b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ForkJoinPoolFactoryBean.java index 488e5008edd..873daf3f8b3 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ForkJoinPoolFactoryBean.java +++ b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ForkJoinPoolFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -28,7 +28,7 @@ import org.springframework.beans.factory.InitializingBean; * (ideally on the VM bootstrap classpath). * *

For details on the ForkJoinPool API and its use with RecursiveActions, see the - * JDK 7 javadoc. + * JDK 7 javadoc. * *

jsr166.jar, containing java.util.concurrent updates for Java 6, can be obtained * from the concurrency interest website. diff --git a/spring-context/src/main/java/org/springframework/validation/AbstractBindingResult.java b/spring-context/src/main/java/org/springframework/validation/AbstractBindingResult.java index 1cf65266562..7525a3dede9 100644 --- a/spring-context/src/main/java/org/springframework/validation/AbstractBindingResult.java +++ b/spring-context/src/main/java/org/springframework/validation/AbstractBindingResult.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -120,13 +120,6 @@ public abstract class AbstractBindingResult extends AbstractErrors implements Bi this.errors.addAll(errors.getAllErrors()); } - /** - * Resolve the given error code into message codes. - * Calls the MessageCodesResolver with appropriate parameters. - * @param errorCode the error code to resolve into message codes - * @return the resolved message codes - * @see #setMessageCodesResolver - */ public String[] resolveMessageCodes(String errorCode) { return getMessageCodesResolver().resolveMessageCodes(errorCode, getObjectName()); } diff --git a/spring-context/src/main/java/org/springframework/validation/BindException.java b/spring-context/src/main/java/org/springframework/validation/BindException.java index 8c401edaa1c..e7896996b3c 100644 --- a/spring-context/src/main/java/org/springframework/validation/BindException.java +++ b/spring-context/src/main/java/org/springframework/validation/BindException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2012 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. @@ -220,6 +220,10 @@ public class BindException extends Exception implements BindingResult { this.bindingResult.addError(error); } + public String[] resolveMessageCodes(String errorCode) { + return this.bindingResult.resolveMessageCodes(errorCode); + } + public String[] resolveMessageCodes(String errorCode, String field) { return this.bindingResult.resolveMessageCodes(errorCode, field); } diff --git a/spring-context/src/main/java/org/springframework/validation/BindingResult.java b/spring-context/src/main/java/org/springframework/validation/BindingResult.java index 848c3e0292b..3069a42a17e 100644 --- a/spring-context/src/main/java/org/springframework/validation/BindingResult.java +++ b/spring-context/src/main/java/org/springframework/validation/BindingResult.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 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. @@ -112,6 +112,14 @@ public interface BindingResult extends Errors { */ void addError(ObjectError error); + /** + * Resolve the given error code into message codes. + *

Calls the configured {@link MessageCodesResolver} with appropriate parameters. + * @param errorCode the error code to resolve into message codes + * @return the resolved message codes + */ + String[] resolveMessageCodes(String errorCode); + /** * Resolve the given error code into message codes for the given field. *

Calls the configured {@link MessageCodesResolver} with appropriate parameters. diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/LocalValidatorFactoryBean.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/LocalValidatorFactoryBean.java index bc3bc6b2d12..a885fddb733 100644 --- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/LocalValidatorFactoryBean.java +++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/LocalValidatorFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2012 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. @@ -28,7 +28,6 @@ import javax.validation.Validation; import javax.validation.Validator; import javax.validation.ValidatorContext; import javax.validation.ValidatorFactory; -import javax.validation.spi.ValidationProvider; import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator; @@ -87,7 +86,7 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter * @see javax.validation.Validation#byDefaultProvider() */ @SuppressWarnings("rawtypes") - public void setProviderClass(Class providerClass) { + public void setProviderClass(Class providerClass) { this.providerClass = providerClass; } diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java index 91fd75ab60e..82f1d913909 100644 --- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java +++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -119,12 +119,11 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation. // can do custom FieldError registration with invalid value from ConstraintViolation, // as necessary for Hibernate Validator compatibility (non-indexed set path in field) BindingResult bindingResult = (BindingResult) errors; - String[] errorCodes = bindingResult.resolveMessageCodes(errorCode, field); String nestedField = bindingResult.getNestedPath() + field; - ObjectError error; if ("".equals(nestedField)) { - error = new ObjectError( - errors.getObjectName(), errorCodes, errorArgs, violation.getMessage()); + String[] errorCodes = bindingResult.resolveMessageCodes(errorCode); + bindingResult.addError(new ObjectError( + errors.getObjectName(), errorCodes, errorArgs, violation.getMessage())); } else { Object invalidValue = violation.getInvalidValue(); @@ -132,11 +131,11 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation. // bean constraint with property path: retrieve the actual property value invalidValue = bindingResult.getRawFieldValue(field); } - error = new FieldError( + String[] errorCodes = bindingResult.resolveMessageCodes(errorCode, field); + bindingResult.addError(new FieldError( errors.getObjectName(), nestedField, invalidValue, false, - errorCodes, errorArgs, violation.getMessage()); + errorCodes, errorArgs, violation.getMessage())); } - bindingResult.addError(error); } else { // got no BindingResult - can only do standard rejectValue call diff --git a/spring-context/src/test/java/org/springframework/context/annotation/AbstractCircularImportDetectionTests.java b/spring-context/src/test/java/org/springframework/context/annotation/AbstractCircularImportDetectionTests.java index 1a02ed5297f..57d42c88c8e 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/AbstractCircularImportDetectionTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/AbstractCircularImportDetectionTests.java @@ -42,7 +42,7 @@ public abstract class AbstractCircularImportDetectionTests { public void simpleCircularImportIsDetected() throws Exception { boolean threw = false; try { - newParser().parse(loadAsConfigurationSource(A.class), null); + newParser().parse(loadAsConfigurationSource(A.class), "A"); } catch (BeanDefinitionParsingException ex) { assertTrue("Wrong message. Got: " + ex.getMessage(), ex.getMessage().contains( @@ -59,7 +59,7 @@ public abstract class AbstractCircularImportDetectionTests { public void complexCircularImportIsDetected() throws Exception { boolean threw = false; try { - newParser().parse(loadAsConfigurationSource(X.class), null); + newParser().parse(loadAsConfigurationSource(X.class), "X"); } catch (BeanDefinitionParsingException ex) { assertTrue("Wrong message. Got: " + ex.getMessage(), diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ImportAwareTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ImportAwareTests.java index 6d38318b216..180176452b5 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ImportAwareTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ImportAwareTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -19,18 +19,19 @@ package org.springframework.context.annotation; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.junit.Assert.assertThat; +import static org.springframework.context.annotation.MetadataUtils.attributesFor; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import java.util.Map; import org.junit.Test; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; import org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor; @@ -55,8 +56,8 @@ public class ImportAwareTests { AnnotationMetadata importMetadata = importAwareConfig.importMetadata; assertThat("import metadata was not injected", importMetadata, notNullValue()); assertThat(importMetadata.getClassName(), is(ImportingConfig.class.getName())); - Map importAttribs = importMetadata.getAnnotationAttributes(Import.class.getName()); - Class[] importedClasses = (Class[])importAttribs.get("value"); + AnnotationAttributes importAttribs = attributesFor(importMetadata, Import.class); + Class[] importedClasses = importAttribs.getClassArray("value"); assertThat(importedClasses[0].getName(), is(ImportedConfig.class.getName())); } @@ -72,8 +73,8 @@ public class ImportAwareTests { AnnotationMetadata importMetadata = importAwareConfig.importMetadata; assertThat("import metadata was not injected", importMetadata, notNullValue()); assertThat(importMetadata.getClassName(), is(IndirectlyImportingConfig.class.getName())); - Map enableAttribs = importMetadata.getAnnotationAttributes(EnableImportedConfig.class.getName()); - String foo = (String)enableAttribs.get("foo"); + AnnotationAttributes enableAttribs = attributesFor(importMetadata, EnableImportedConfig.class); + String foo = enableAttribs.getString("foo"); assertThat(foo, is("xyz")); } @@ -129,7 +130,6 @@ public class ImportAwareTests { } public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - System.out.println("ImportAwareTests.BPP.setBeanFactory()"); } } } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ImportTests.java b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ImportTests.java index 8756425e146..1866ea80405 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ImportTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ImportTests.java @@ -25,6 +25,7 @@ import test.beans.TestBean; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ConfigurationClassPostProcessor; @@ -53,13 +54,6 @@ public class ImportTests { assertThat(beanFactory.getBeanDefinitionCount(), equalTo(expectedCount)); } - @Test - public void testProcessImports() { - int configClasses = 2; - int beansInClasses = 2; - assertBeanDefinitionCount((configClasses + beansInClasses), ConfigurationWithImportAnnotation.class); - } - @Test public void testProcessImportsWithAsm() { int configClasses = 2; @@ -315,4 +309,36 @@ public class ImportTests { static class ConfigAnnotated { } static class NonConfigAnnotated { } + + // ------------------------------------------------------------------------ + + /** + * Test that values supplied to @Configuration(value="...") are propagated as the + * bean name for the configuration class even in the case of inclusion via @Import + * or in the case of automatic registration via nesting + */ + @Test + public void reproSpr9023() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(B.class); + ctx.refresh(); + System.out.println(ctx.getBeanFactory()); + assertThat(ctx.getBeanNamesForType(B.class)[0], is("config-b")); + assertThat(ctx.getBeanNamesForType(A.class)[0], is("config-a")); + } + + @Configuration("config-a") + static class A { } + + @Configuration("config-b") + @Import(A.class) + static class B { } + + @Test + public void testProcessImports() { + int configClasses = 2; + int beansInClasses = 2; + assertBeanDefinitionCount((configClasses + beansInClasses), ConfigurationWithImportAnnotation.class); + } + } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/configuration/spr9031/Spr9031Tests.java b/spring-context/src/test/java/org/springframework/context/annotation/configuration/spr9031/Spr9031Tests.java new file mode 100644 index 00000000000..201918a4329 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/context/annotation/configuration/spr9031/Spr9031Tests.java @@ -0,0 +1,79 @@ +/* + * Copyright 2002-2012 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.context.annotation.configuration.spr9031; + +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertThat; + +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.configuration.spr9031.scanpackage.Spr9031Component; + +/** + * Unit tests cornering bug SPR-9031. + * + * @author Chris Beams + * @since 3.1.1 + */ +public class Spr9031Tests { + + /** + * Use of @Import to register LowLevelConfig results in ASM-based annotation + * processing. + */ + @Test + public void withAsmAnnotationProcessing() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(HighLevelConfig.class); + ctx.refresh(); + assertThat(ctx.getBean(LowLevelConfig.class).scanned, not(nullValue())); + } + + /** + * Direct registration of LowLevelConfig results in reflection-based annotation + * processing. + */ + @Test + public void withoutAsmAnnotationProcessing() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(LowLevelConfig.class); + ctx.refresh(); + assertThat(ctx.getBean(LowLevelConfig.class).scanned, not(nullValue())); + } + + @Configuration + @Import(LowLevelConfig.class) + static class HighLevelConfig {} + + @Configuration + @ComponentScan( + basePackages = "org.springframework.context.annotation.configuration.spr9031.scanpackage", + includeFilters = { @Filter(MarkerAnnotation.class) }) + static class LowLevelConfig { + // fails to wire when LowLevelConfig is processed with ASM because nested @Filter + // annotation is not parsed + @Autowired Spr9031Component scanned; + } + + public @interface MarkerAnnotation {} +} diff --git a/spring-context/src/test/java/org/springframework/context/annotation/configuration/spr9031/scanpackage/Spr9031Component.java b/spring-context/src/test/java/org/springframework/context/annotation/configuration/spr9031/scanpackage/Spr9031Component.java new file mode 100644 index 00000000000..1537b4cbd89 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/context/annotation/configuration/spr9031/scanpackage/Spr9031Component.java @@ -0,0 +1,24 @@ +/* + * Copyright 2002-2012 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.context.annotation.configuration.spr9031.scanpackage; + +import org.springframework.context.annotation.configuration.spr9031.Spr9031Tests.MarkerAnnotation; + +@MarkerAnnotation +public class Spr9031Component { + +} diff --git a/spring-context/src/test/java/org/springframework/validation/beanvalidation/ValidatorFactoryTests.java b/spring-context/src/test/java/org/springframework/validation/beanvalidation/ValidatorFactoryTests.java index 4e69535108d..dbfe0b7d1a8 100644 --- a/spring-context/src/test/java/org/springframework/validation/beanvalidation/ValidatorFactoryTests.java +++ b/spring-context/src/test/java/org/springframework/validation/beanvalidation/ValidatorFactoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2012 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. @@ -110,11 +110,22 @@ public class ValidatorFactoryTests { assertEquals(2, result.getErrorCount()); FieldError fieldError = result.getFieldError("name"); assertEquals("name", fieldError.getField()); - System.out.println(Arrays.asList(fieldError.getCodes())); + List errorCodes = Arrays.asList(fieldError.getCodes()); + assertEquals(4, errorCodes.size()); + assertTrue(errorCodes.contains("NotNull.person.name")); + assertTrue(errorCodes.contains("NotNull.name")); + assertTrue(errorCodes.contains("NotNull.java.lang.String")); + assertTrue(errorCodes.contains("NotNull")); System.out.println(fieldError.getDefaultMessage()); fieldError = result.getFieldError("address.street"); assertEquals("address.street", fieldError.getField()); - System.out.println(Arrays.asList(fieldError.getCodes())); + errorCodes = Arrays.asList(fieldError.getCodes()); + assertEquals(5, errorCodes.size()); + assertTrue(errorCodes.contains("NotNull.person.address.street")); + assertTrue(errorCodes.contains("NotNull.address.street")); + assertTrue(errorCodes.contains("NotNull.street")); + assertTrue(errorCodes.contains("NotNull.java.lang.String")); + assertTrue(errorCodes.contains("NotNull")); System.out.println(fieldError.getDefaultMessage()); } @@ -129,7 +140,10 @@ public class ValidatorFactoryTests { validator.validate(person, result); assertEquals(1, result.getErrorCount()); ObjectError globalError = result.getGlobalError(); - System.out.println(Arrays.asList(globalError.getCodes())); + List errorCodes = Arrays.asList(globalError.getCodes()); + assertEquals(2, errorCodes.size()); + assertTrue(errorCodes.contains("NameAddressValid.person")); + assertTrue(errorCodes.contains("NameAddressValid")); System.out.println(globalError.getDefaultMessage()); } diff --git a/spring-core/src/main/java/org/springframework/core/MethodParameter.java b/spring-core/src/main/java/org/springframework/core/MethodParameter.java index 361bae7eedc..9c33cb25783 100644 --- a/spring-core/src/main/java/org/springframework/core/MethodParameter.java +++ b/spring-core/src/main/java/org/springframework/core/MethodParameter.java @@ -21,6 +21,7 @@ import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; import java.lang.reflect.Member; import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.HashMap; @@ -233,6 +234,29 @@ public class MethodParameter { return this.genericParameterType; } + public Class getNestedParameterType() { + if (this.nestingLevel > 1) { + Type type = getGenericParameterType(); + if (type instanceof ParameterizedType) { + Integer index = getTypeIndexForCurrentLevel(); + Type arg = ((ParameterizedType) type).getActualTypeArguments()[index != null ? index : 0]; + if (arg instanceof Class) { + return (Class) arg; + } + else if (arg instanceof ParameterizedType) { + arg = ((ParameterizedType) arg).getRawType(); + if (arg instanceof Class) { + return (Class) arg; + } + } + } + return Object.class; + } + else { + return getParameterType(); + } + } + /** * Return the annotations associated with the target method/constructor itself. */ diff --git a/spring-core/src/main/java/org/springframework/core/OrderComparator.java b/spring-core/src/main/java/org/springframework/core/OrderComparator.java index 829ee4c275c..2cadb0a45bb 100644 --- a/spring-core/src/main/java/org/springframework/core/OrderComparator.java +++ b/spring-core/src/main/java/org/springframework/core/OrderComparator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 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. @@ -40,7 +40,7 @@ public class OrderComparator implements Comparator { /** * Shared default instance of OrderComparator. */ - public static OrderComparator INSTANCE = new OrderComparator(); + public static final OrderComparator INSTANCE = new OrderComparator(); public int compare(Object o1, Object o2) { diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java new file mode 100644 index 00000000000..1ea4ba3a470 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java @@ -0,0 +1,132 @@ +/* + * Copyright 2002-2012 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.core.annotation; + +import static java.lang.String.format; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.util.Assert; + +/** + * {@link LinkedHashMap} subclass representing annotation attribute key/value pairs + * as read by Spring's reflection- or ASM-based {@link + * org.springframework.core.type.AnnotationMetadata AnnotationMetadata} implementations. + * Provides 'pseudo-reification' to avoid noisy Map generics in the calling code as well + * as convenience methods for looking up annotation attributes in a type-safe fashion. + * + * @author Chris Beams + * @since 3.1.1 + */ +@SuppressWarnings("serial") +public class AnnotationAttributes extends LinkedHashMap { + + /** + * Create a new, empty {@link AnnotationAttributes} instance. + */ + public AnnotationAttributes() { + } + + /** + * Create a new, empty {@link AnnotationAttributes} instance with the given initial + * capacity to optimize performance. + * @param initialCapacity initial size of the underlying map + */ + public AnnotationAttributes(int initialCapacity) { + super(initialCapacity); + } + + /** + * Create a new {@link AnnotationAttributes} instance, wrapping the provided map + * and all its key/value pairs. + * @param map original source of annotation attribute key/value pairs to wrap + * @see #fromMap(Map) + */ + public AnnotationAttributes(Map map) { + super(map); + } + + /** + * Return an {@link AnnotationAttributes} instance based on the given map; if the map + * is already an {@code AnnotationAttributes} instance, it is casted and returned + * immediately without creating any new instance; otherwise create a new instance by + * wrapping the map with the {@link #AnnotationAttributes(Map)} constructor. + * @param map original source of annotation attribute key/value pairs + */ + public static AnnotationAttributes fromMap(Map map) { + if (map == null) { + return null; + } + + if (map instanceof AnnotationAttributes) { + return (AnnotationAttributes) map; + } + + return new AnnotationAttributes(map); + } + + public String getString(String attributeName) { + return doGet(attributeName, String.class); + } + + public String[] getStringArray(String attributeName) { + return doGet(attributeName, String[].class); + } + + public boolean getBoolean(String attributeName) { + return doGet(attributeName, Boolean.class); + } + + @SuppressWarnings("unchecked") + public N getNumber(String attributeName) { + return (N) doGet(attributeName, Integer.class); + } + + @SuppressWarnings("unchecked") + public > E getEnum(String attributeName) { + return (E) doGet(attributeName, Enum.class); + } + + @SuppressWarnings("unchecked") + public Class getClass(String attributeName) { + return (Class)doGet(attributeName, Class.class); + } + + public Class[] getClassArray(String attributeName) { + return doGet(attributeName, Class[].class); + } + + public AnnotationAttributes getAnnotation(String attributeName) { + return doGet(attributeName, AnnotationAttributes.class); + } + + public AnnotationAttributes[] getAnnotationArray(String attributeName) { + return doGet(attributeName, AnnotationAttributes[].class); + } + + @SuppressWarnings("unchecked") + private T doGet(String attributeName, Class expectedType) { + Assert.hasText(attributeName, "attributeName must not be null or empty"); + Object value = this.get(attributeName); + Assert.notNull(value, format("Attribute '%s' not found", attributeName)); + Assert.isAssignable(expectedType, value.getClass(), + format("Attribute '%s' is of type [%s], but [%s] was expected. Cause: ", + attributeName, value.getClass().getSimpleName(), expectedType.getSimpleName())); + return (T) value; + } +} \ No newline at end of file diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java index fa08d2b2f54..c8ddfe1fc02 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -20,7 +20,6 @@ import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.util.Arrays; -import java.util.HashMap; import java.util.Map; import java.util.WeakHashMap; @@ -300,25 +299,58 @@ public abstract class AnnotationUtils { } /** - * Retrieve the given annotation's attributes as a Map, preserving all attribute types as-is. + * Retrieve the given annotation's attributes as a Map, preserving all attribute types + * as-is. + *

Note: As of Spring 3.1.1, the returned map is actually an + * {@link AnnotationAttributes} instance, however the Map signature of this method has + * been preserved for binary compatibility. * @param annotation the annotation to retrieve the attributes for * @return the Map of annotation attributes, with attribute names as keys and * corresponding attribute values as values */ public static Map getAnnotationAttributes(Annotation annotation) { - return getAnnotationAttributes(annotation, false); + return getAnnotationAttributes(annotation, false, false); } /** - * Retrieve the given annotation's attributes as a Map. + * Retrieve the given annotation's attributes as a Map. Equivalent to calling + * {@link #getAnnotationAttributes(Annotation, boolean, boolean)} with + * the {@code nestedAnnotationsAsMap} parameter set to {@code false}. + *

Note: As of Spring 3.1.1, the returned map is actually an + * {@link AnnotationAttributes} instance, however the Map signature of this method has + * been preserved for binary compatibility. * @param annotation the annotation to retrieve the attributes for - * @param classValuesAsString whether to turn Class references into Strings (for compatibility with - * {@link org.springframework.core.type.AnnotationMetadata} or to preserve them as Class references + * @param classValuesAsString whether to turn Class references into Strings (for + * compatibility with {@link org.springframework.core.type.AnnotationMetadata} or to + * preserve them as Class references * @return the Map of annotation attributes, with attribute names as keys and * corresponding attribute values as values */ public static Map getAnnotationAttributes(Annotation annotation, boolean classValuesAsString) { - Map attrs = new HashMap(); + return getAnnotationAttributes(annotation, classValuesAsString, false); + } + + /** + * Retrieve the given annotation's attributes as an {@link AnnotationAttributes} + * map structure. Implemented in Spring 3.1.1 to provide fully recursive annotation + * reading capabilities on par with that of the reflection-based + * {@link org.springframework.core.type.StandardAnnotationMetadata}. + * @param annotation the annotation to retrieve the attributes for + * @param classValuesAsString whether to turn Class references into Strings (for + * compatibility with {@link org.springframework.core.type.AnnotationMetadata} or to + * preserve them as Class references + * @param nestedAnnotationsAsMap whether to turn nested Annotation instances into + * {@link AnnotationAttributes} maps (for compatibility with + * {@link org.springframework.core.type.AnnotationMetadata} or to preserve them as + * Annotation instances + * @return the annotation attributes (a specialized Map) with attribute names as keys + * and corresponding attribute values as values + * @since 3.1.1 + */ + public static AnnotationAttributes getAnnotationAttributes( + Annotation annotation, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { + + AnnotationAttributes attrs = new AnnotationAttributes(); Method[] methods = annotation.annotationType().getDeclaredMethods(); for (Method method : methods) { if (method.getParameterTypes().length == 0 && method.getReturnType() != void.class) { @@ -337,7 +369,22 @@ public abstract class AnnotationUtils { value = newValue; } } - attrs.put(method.getName(), value); + if (nestedAnnotationsAsMap && value instanceof Annotation) { + attrs.put(method.getName(), getAnnotationAttributes( + (Annotation)value, classValuesAsString, nestedAnnotationsAsMap)); + } + else if (nestedAnnotationsAsMap && value instanceof Annotation[]) { + Annotation[] realAnnotations = (Annotation[])value; + AnnotationAttributes[] mappedAnnotations = new AnnotationAttributes[realAnnotations.length]; + for (int i = 0; i < realAnnotations.length; i++) { + mappedAnnotations[i] = getAnnotationAttributes( + realAnnotations[i], classValuesAsString, nestedAnnotationsAsMap); + } + attrs.put(method.getName(), mappedAnnotations); + } + else { + attrs.put(method.getName(), value); + } } catch (Exception ex) { throw new IllegalStateException("Could not obtain annotation attribute values", ex); diff --git a/spring-core/src/main/java/org/springframework/core/convert/Property.java b/spring-core/src/main/java/org/springframework/core/convert/Property.java index 7cdf0bff801..09a1e6ada07 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/Property.java +++ b/spring-core/src/main/java/org/springframework/core/convert/Property.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.core.convert; import java.lang.annotation.Annotation; @@ -141,13 +142,20 @@ public final class Property { private MethodParameter resolveMethodParameter() { MethodParameter read = resolveReadMethodParameter(); MethodParameter write = resolveWriteMethodParameter(); - if (read == null && write == null) { - throw new IllegalStateException("Property is neither readable nor writeable"); + if (write == null) { + if (read == null) { + throw new IllegalStateException("Property is neither readable nor writeable"); + } + return read; } - if (read != null && write != null && !write.getParameterType().isAssignableFrom(read.getParameterType())) { - throw new IllegalStateException("Write parameter is not assignable from read parameter"); + if (read != null) { + Class readType = read.getParameterType(); + Class writeType = write.getParameterType(); + if (!writeType.equals(readType) && writeType.isAssignableFrom(readType)) { + return read; + } } - return read != null ? read : write; + return write; } private MethodParameter resolveReadMethodParameter() { diff --git a/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java b/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java index ff277afa30d..f2ce7ed6280 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java +++ b/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.core.convert; import java.lang.annotation.Annotation; @@ -59,6 +60,7 @@ public class TypeDescriptor { typeDescriptorCache.put(String.class, new TypeDescriptor(String.class)); } + private final Class type; private final TypeDescriptor elementTypeDescriptor; @@ -69,6 +71,7 @@ public class TypeDescriptor { private final Annotation[] annotations; + /** * Create a new type descriptor from a {@link MethodParameter}. * Use this constructor when a source or target conversion point is a constructor parameter, method parameter, or method return value. @@ -96,6 +99,7 @@ public class TypeDescriptor { this(new BeanPropertyDescriptor(property)); } + /** * Create a new type descriptor from the given type. * Use this to instruct the conversion system to convert an object to a specific target type, when no type location such as a method parameter or field is available to provide additional conversion context. @@ -207,6 +211,7 @@ public class TypeDescriptor { return (source != null ? valueOf(source.getClass()) : null); } + /** * The type of the backing class, method parameter, field, or property described by this TypeDescriptor. * Returns primitive types as-is. @@ -477,6 +482,7 @@ public class TypeDescriptor { private TypeDescriptor(Class type, TypeDescriptor elementTypeDescriptor, TypeDescriptor mapKeyTypeDescriptor, TypeDescriptor mapValueTypeDescriptor, Annotation[] annotations) { + this.type = type; this.elementTypeDescriptor = elementTypeDescriptor; this.mapKeyTypeDescriptor = mapKeyTypeDescriptor; @@ -538,15 +544,24 @@ public class TypeDescriptor { return false; } TypeDescriptor other = (TypeDescriptor) obj; - boolean annotatedTypeEquals = ObjectUtils.nullSafeEquals(getType(), other.getType()) && ObjectUtils.nullSafeEquals(getAnnotations(), other.getAnnotations()); - if (!annotatedTypeEquals) { + if (!ObjectUtils.nullSafeEquals(getType(), other.getType())) { return false; } + Annotation[] annotations = getAnnotations(); + if (annotations.length != other.getAnnotations().length) { + return false; + } + for (Annotation ann : annotations) { + if (other.getAnnotation(ann.annotationType()) == null) { + return false; + } + } if (isCollection() || isArray()) { return ObjectUtils.nullSafeEquals(getElementTypeDescriptor(), other.getElementTypeDescriptor()); } else if (isMap()) { - return ObjectUtils.nullSafeEquals(getMapKeyTypeDescriptor(), other.getMapKeyTypeDescriptor()) && ObjectUtils.nullSafeEquals(getMapValueTypeDescriptor(), other.getMapValueTypeDescriptor()); + return ObjectUtils.nullSafeEquals(getMapKeyTypeDescriptor(), other.getMapKeyTypeDescriptor()) && + ObjectUtils.nullSafeEquals(getMapValueTypeDescriptor(), other.getMapValueTypeDescriptor()); } else { return true; diff --git a/spring-core/src/main/java/org/springframework/core/io/AbstractResource.java b/spring-core/src/main/java/org/springframework/core/io/AbstractResource.java index a6b06ab7bb6..2f6c8e7d7dd 100644 --- a/spring-core/src/main/java/org/springframework/core/io/AbstractResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/AbstractResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -150,11 +150,11 @@ public abstract class AbstractResource implements Resource { } /** - * This implementation always throws IllegalStateException, - * assuming that the resource does not have a filename. + * This implementation always returns null, + * assuming that this resource type does not have a filename. */ public String getFilename() throws IllegalStateException { - throw new IllegalStateException(getDescription() + " does not have a filename"); + return null; } diff --git a/spring-core/src/main/java/org/springframework/core/io/Resource.java b/spring-core/src/main/java/org/springframework/core/io/Resource.java index aafb1a9dab4..f90b13c7ed3 100644 --- a/spring-core/src/main/java/org/springframework/core/io/Resource.java +++ b/spring-core/src/main/java/org/springframework/core/io/Resource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -116,8 +116,10 @@ public interface Resource extends InputStreamSource { Resource createRelative(String relativePath) throws IOException; /** - * Return a filename for this resource, i.e. typically the last + * Determine a filename for this resource, i.e. typically the last * part of the path: for example, "myfile.txt". + *

Returns null if this type of resource does not + * have a filename. */ String getFilename(); diff --git a/spring-core/src/main/java/org/springframework/core/io/VfsResource.java b/spring-core/src/main/java/org/springframework/core/io/VfsResource.java index e5450de45bf..c161858ad65 100644 --- a/spring-core/src/main/java/org/springframework/core/io/VfsResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/VfsResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2012 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. @@ -27,7 +27,7 @@ import org.springframework.util.Assert; /** * VFS based {@link Resource} implementation. - * Supports the corresponding VFS API versions on JBoss AS 5.x as well as 6.x. + * Supports the corresponding VFS API versions on JBoss AS 5.x as well as 6.x and 7.x. * * @author Ales Justin * @author Juergen Hoeller @@ -47,22 +47,21 @@ public class VfsResource extends AbstractResource { } - public boolean exists() { - return VfsUtils.exists(this.resource); - } - - public boolean isReadable() { - return VfsUtils.isReadable(this.resource); - } - - public long lastModified() throws IOException { - return VfsUtils.getLastModified(this.resource); - } - public InputStream getInputStream() throws IOException { return VfsUtils.getInputStream(this.resource); } + @Override + public boolean exists() { + return VfsUtils.exists(this.resource); + } + + @Override + public boolean isReadable() { + return VfsUtils.isReadable(this.resource); + } + + @Override public URL getURL() throws IOException { try { return VfsUtils.getURL(this.resource); @@ -72,6 +71,7 @@ public class VfsResource extends AbstractResource { } } + @Override public URI getURI() throws IOException { try { return VfsUtils.getURI(this.resource); @@ -81,10 +81,17 @@ public class VfsResource extends AbstractResource { } } + @Override public File getFile() throws IOException { return VfsUtils.getFile(this.resource); } + @Override + public long lastModified() throws IOException { + return VfsUtils.getLastModified(this.resource); + } + + @Override public Resource createRelative(String relativePath) throws IOException { if (!relativePath.startsWith(".") && relativePath.contains("/")) { try { @@ -98,6 +105,7 @@ public class VfsResource extends AbstractResource { return new VfsResource(VfsUtils.getRelative(new URL(getURL(), relativePath))); } + @Override public String getFilename() { return VfsUtils.getName(this.resource); } diff --git a/spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderSupport.java b/spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderSupport.java index e4b4147a401..1b427611c4b 100644 --- a/spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderSupport.java +++ b/spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2012 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. @@ -23,7 +23,7 @@ import java.util.Properties; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.core.io.AbstractFileResolvingResource; + import org.springframework.core.io.Resource; import org.springframework.util.CollectionUtils; import org.springframework.util.DefaultPropertiesPersister; @@ -179,9 +179,7 @@ public abstract class PropertiesLoaderSupport { InputStream is = null; try { is = location.getInputStream(); - - String filename = (location instanceof AbstractFileResolvingResource) ? - location.getFilename() : null; + String filename = location.getFilename(); if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) { this.propertiesPersister.loadFromXml(props, is); } diff --git a/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java b/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java index 5e413033bdf..1e5d00a9ca9 100644 --- a/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java +++ b/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 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. @@ -22,24 +22,45 @@ import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; +import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.AnnotationUtils; /** * {@link AnnotationMetadata} implementation that uses standard reflection - * to introspect a given Class. + * to introspect a given {@link Class}. * * @author Juergen Hoeller * @author Mark Fisher + * @author Chris Beams * @since 2.5 */ public class StandardAnnotationMetadata extends StandardClassMetadata implements AnnotationMetadata { + private final boolean nestedAnnotationsAsMap; + + /** - * Create a new StandardAnnotationMetadata wrapper for the given Class. + * Create a new {@code StandardAnnotationMetadata} wrapper for the given Class. * @param introspectedClass the Class to introspect + * @see #StandardAnnotationMetadata(Class, boolean) */ - public StandardAnnotationMetadata(Class introspectedClass) { + public StandardAnnotationMetadata(Class introspectedClass) { + this(introspectedClass, false); + } + + /** + * Create a new {@link StandardAnnotationMetadata} wrapper for the given Class, + * providing the option to return any nested annotations or annotation arrays in the + * form of {@link AnnotationAttributes} instead of actual {@link Annotation} instances. + * @param introspectedClass the Class to instrospect + * @param nestedAnnotationsAsMap return nested annotations and annotation arrays as + * {@link AnnotationAttributes} for compatibility with ASM-based + * {@link AnnotationMetadata} implementations + * @since 3.1.1 + */ + public StandardAnnotationMetadata(Class introspectedClass, boolean nestedAnnotationsAsMap) { super(introspectedClass); + this.nestedAnnotationsAsMap = nestedAnnotationsAsMap; } @@ -114,18 +135,20 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements } public Map getAnnotationAttributes(String annotationType) { - return getAnnotationAttributes(annotationType, false); + return this.getAnnotationAttributes(annotationType, false); } public Map getAnnotationAttributes(String annotationType, boolean classValuesAsString) { Annotation[] anns = getIntrospectedClass().getAnnotations(); for (Annotation ann : anns) { if (ann.annotationType().getName().equals(annotationType)) { - return AnnotationUtils.getAnnotationAttributes(ann, classValuesAsString); + return AnnotationUtils.getAnnotationAttributes( + ann, classValuesAsString, this.nestedAnnotationsAsMap); } for (Annotation metaAnn : ann.annotationType().getAnnotations()) { if (metaAnn.annotationType().getName().equals(annotationType)) { - return AnnotationUtils.getAnnotationAttributes(metaAnn, classValuesAsString); + return AnnotationUtils.getAnnotationAttributes( + metaAnn, classValuesAsString, this.nestedAnnotationsAsMap); } } } @@ -157,13 +180,13 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements for (Method method : methods) { for (Annotation ann : method.getAnnotations()) { if (ann.annotationType().getName().equals(annotationType)) { - annotatedMethods.add(new StandardMethodMetadata(method)); + annotatedMethods.add(new StandardMethodMetadata(method, this.nestedAnnotationsAsMap)); break; } else { for (Annotation metaAnn : ann.annotationType().getAnnotations()) { if (metaAnn.annotationType().getName().equals(annotationType)) { - annotatedMethods.add(new StandardMethodMetadata(method)); + annotatedMethods.add(new StandardMethodMetadata(method, this.nestedAnnotationsAsMap)); break; } } diff --git a/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java b/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java index ead840cc21b..aca42b9d946 100644 --- a/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java +++ b/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 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. @@ -37,14 +37,27 @@ public class StandardMethodMetadata implements MethodMetadata { private final Method introspectedMethod; + private final boolean nestedAnnotationsAsMap; + /** * Create a new StandardMethodMetadata wrapper for the given Method. * @param introspectedMethod the Method to introspect */ public StandardMethodMetadata(Method introspectedMethod) { + this(introspectedMethod, false); + } + + /** + * Create a new StandardMethodMetadata wrapper for the given Method. + * @param introspectedMethod the Method to introspect + * @param nestedAnnotationsAsMap + * @since 3.1.1 + */ + public StandardMethodMetadata(Method introspectedMethod, boolean nestedAnnotationsAsMap) { Assert.notNull(introspectedMethod, "Method must not be null"); this.introspectedMethod = introspectedMethod; + this.nestedAnnotationsAsMap = nestedAnnotationsAsMap; } /** @@ -94,11 +107,13 @@ public class StandardMethodMetadata implements MethodMetadata { Annotation[] anns = this.introspectedMethod.getAnnotations(); for (Annotation ann : anns) { if (ann.annotationType().getName().equals(annotationType)) { - return AnnotationUtils.getAnnotationAttributes(ann, true); + return AnnotationUtils.getAnnotationAttributes( + ann, true, nestedAnnotationsAsMap); } for (Annotation metaAnn : ann.annotationType().getAnnotations()) { if (metaAnn.annotationType().getName().equals(annotationType)) { - return AnnotationUtils.getAnnotationAttributes(metaAnn, true); + return AnnotationUtils.getAnnotationAttributes( + metaAnn, true, this.nestedAnnotationsAsMap); } } } diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java index 244808b3235..7f48ece4fd5 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 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. @@ -20,131 +20,242 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.util.LinkedHashMap; +import java.util.ArrayList; import java.util.LinkedHashSet; +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.asm.AnnotationVisitor; import org.springframework.asm.Type; -import org.springframework.asm.commons.EmptyVisitor; +import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; + /** - * ASM visitor which looks for the annotations defined on a class or method. - * + * @author Chris Beams * @author Juergen Hoeller - * @since 3.0 + * @since 3.1.1 */ -final class AnnotationAttributesReadingVisitor implements AnnotationVisitor { +abstract class AbstractRecursiveAnnotationVisitor implements AnnotationVisitor { - private final String annotationType; + protected final Log logger = LogFactory.getLog(this.getClass()); - private final Map> attributesMap; + protected final AnnotationAttributes attributes; - private final Map> metaAnnotationMap; - - private final ClassLoader classLoader; - - private final Map localAttributes = new LinkedHashMap(); + protected final ClassLoader classLoader; - public AnnotationAttributesReadingVisitor( - String annotationType, Map> attributesMap, - Map> metaAnnotationMap, ClassLoader classLoader) { - - this.annotationType = annotationType; - this.attributesMap = attributesMap; - this.metaAnnotationMap = metaAnnotationMap; + public AbstractRecursiveAnnotationVisitor(ClassLoader classLoader, AnnotationAttributes attributes) { this.classLoader = classLoader; + this.attributes = attributes; } - public void visit(String name, Object value) { - this.localAttributes.put(name, value); + public void visit(String attributeName, Object attributeValue) { + this.attributes.put(attributeName, attributeValue); } - public void visitEnum(String name, String desc, String value) { - Object valueToUse = value; + public AnnotationVisitor visitAnnotation(String attributeName, String asmTypeDescriptor) { + String annotationType = Type.getType(asmTypeDescriptor).getClassName(); + AnnotationAttributes nestedAttributes = new AnnotationAttributes(); + this.attributes.put(attributeName, nestedAttributes); + return new RecursiveAnnotationAttributesVisitor(annotationType, nestedAttributes, this.classLoader); + } + + public AnnotationVisitor visitArray(String attributeName) { + return new RecursiveAnnotationArrayVisitor(attributeName, this.attributes, this.classLoader); + } + + public void visitEnum(String attributeName, String asmTypeDescriptor, String attributeValue) { + Object valueToUse = attributeValue; try { - Class enumType = this.classLoader.loadClass(Type.getType(desc).getClassName()); - Field enumConstant = ReflectionUtils.findField(enumType, value); + Class enumType = this.classLoader.loadClass(Type.getType(asmTypeDescriptor).getClassName()); + Field enumConstant = ReflectionUtils.findField(enumType, attributeValue); if (enumConstant != null) { valueToUse = enumConstant.get(null); } } catch (Exception ex) { - // Class not found - can't resolve class reference in annotation attribute. + logNonFatalException(ex); } - this.localAttributes.put(name, valueToUse); + this.attributes.put(attributeName, valueToUse); } - public AnnotationVisitor visitAnnotation(String name, String desc) { - return new EmptyVisitor(); + + protected void logNonFatalException(Exception ex) { + this.logger.warn("Failed to classload type while reading annotation metadata. " + + "This is a non-fatal error, but certain annotation metadata may be " + + "unavailable.", ex); + } +} + + +/** + * @author Chris Beams + * @author Juergen Hoeller + * @since 3.1.1 + */ +final class RecursiveAnnotationArrayVisitor extends AbstractRecursiveAnnotationVisitor { + + private final String attributeName; + + private final List allNestedAttributes = new ArrayList(); + + + public RecursiveAnnotationArrayVisitor( + String attributeName, AnnotationAttributes attributes, ClassLoader classLoader) { + super(classLoader, attributes); + this.attributeName = attributeName; } - public AnnotationVisitor visitArray(final String attrName) { - return new AnnotationVisitor() { - public void visit(String name, Object value) { - Object newValue = value; - Object existingValue = localAttributes.get(attrName); - if (existingValue != null) { - newValue = ObjectUtils.addObjectToArray((Object[]) existingValue, newValue); - } - else { - Object[] newArray = (Object[]) Array.newInstance(newValue.getClass(), 1); - newArray[0] = newValue; - newValue = newArray; - } - localAttributes.put(attrName, newValue); - } - public void visitEnum(String name, String desc, String value) { - } - public AnnotationVisitor visitAnnotation(String name, String desc) { - return new EmptyVisitor(); - } - public AnnotationVisitor visitArray(String name) { - return new EmptyVisitor(); - } - public void visitEnd() { - } - }; + @Override + public void visit(String attributeName, Object attributeValue) { + Object newValue = attributeValue; + Object existingValue = this.attributes.get(this.attributeName); + if (existingValue != null) { + newValue = ObjectUtils.addObjectToArray((Object[]) existingValue, newValue); + } + else { + Object[] newArray = (Object[]) Array.newInstance(newValue.getClass(), 1); + newArray[0] = newValue; + newValue = newArray; + } + this.attributes.put(this.attributeName, newValue); + } + + @Override + public AnnotationVisitor visitAnnotation(String attributeName, String asmTypeDescriptor) { + String annotationType = Type.getType(asmTypeDescriptor).getClassName(); + AnnotationAttributes nestedAttributes = new AnnotationAttributes(); + this.allNestedAttributes.add(nestedAttributes); + return new RecursiveAnnotationAttributesVisitor(annotationType, nestedAttributes, this.classLoader); } public void visitEnd() { - this.attributesMap.put(this.annotationType, this.localAttributes); + if (!this.allNestedAttributes.isEmpty()) { + this.attributes.put(this.attributeName, this.allNestedAttributes.toArray( + new AnnotationAttributes[this.allNestedAttributes.size()])); + } + } +} + + +/** + * @author Chris Beams + * @author Juergen Hoeller + * @since 3.1.1 + */ +class RecursiveAnnotationAttributesVisitor extends AbstractRecursiveAnnotationVisitor { + + private final String annotationType; + + + public RecursiveAnnotationAttributesVisitor( + String annotationType, AnnotationAttributes attributes, ClassLoader classLoader) { + super(classLoader, attributes); + this.annotationType = annotationType; + } + + + public final void visitEnd() { try { Class annotationClass = this.classLoader.loadClass(this.annotationType); - // Check declared default values of attributes in the annotation type. - Method[] annotationAttributes = annotationClass.getMethods(); - for (Method annotationAttribute : annotationAttributes) { - String attributeName = annotationAttribute.getName(); - Object defaultValue = annotationAttribute.getDefaultValue(); - if (defaultValue != null && !this.localAttributes.containsKey(attributeName)) { - this.localAttributes.put(attributeName, defaultValue); - } - } - // Register annotations that the annotation type is annotated with. - Set metaAnnotationTypeNames = new LinkedHashSet(); - for (Annotation metaAnnotation : annotationClass.getAnnotations()) { - metaAnnotationTypeNames.add(metaAnnotation.annotationType().getName()); - if (!this.attributesMap.containsKey(metaAnnotation.annotationType().getName())) { - this.attributesMap.put(metaAnnotation.annotationType().getName(), - AnnotationUtils.getAnnotationAttributes(metaAnnotation, true)); - } - for (Annotation metaMetaAnnotation : metaAnnotation.annotationType().getAnnotations()) { - metaAnnotationTypeNames.add(metaMetaAnnotation.annotationType().getName()); - } - } - if (this.metaAnnotationMap != null) { - this.metaAnnotationMap.put(this.annotationType, metaAnnotationTypeNames); - } + this.doVisitEnd(annotationClass); } catch (ClassNotFoundException ex) { - // Class not found - can't determine meta-annotations. + logNonFatalException(ex); } } + protected void doVisitEnd(Class annotationClass) { + registerDefaultValues(annotationClass); + } + + private void registerDefaultValues(Class annotationClass) { + // Check declared default values of attributes in the annotation type. + Method[] annotationAttributes = annotationClass.getMethods(); + for (Method annotationAttribute : annotationAttributes) { + String attributeName = annotationAttribute.getName(); + Object defaultValue = annotationAttribute.getDefaultValue(); + if (defaultValue != null && !this.attributes.containsKey(attributeName)) { + if (defaultValue instanceof Annotation) { + defaultValue = AnnotationAttributes.fromMap( + AnnotationUtils.getAnnotationAttributes((Annotation)defaultValue, false, true)); + } + else if (defaultValue instanceof Annotation[]) { + Annotation[] realAnnotations = (Annotation[]) defaultValue; + AnnotationAttributes[] mappedAnnotations = new AnnotationAttributes[realAnnotations.length]; + for (int i = 0; i < realAnnotations.length; i++) { + mappedAnnotations[i] = AnnotationAttributes.fromMap( + AnnotationUtils.getAnnotationAttributes(realAnnotations[i], false, true)); + } + defaultValue = mappedAnnotations; + } + this.attributes.put(attributeName, defaultValue); + } + } + } } + + +/** + * ASM visitor which looks for the annotations defined on a class or method, including + * tracking meta-annotations. + * + *

As of Spring 3.1.1, this visitor is fully recursive, taking into account any nested + * annotations or nested annotation arrays. These annotations are in turn read into + * {@link AnnotationAttributes} map structures. + * + * @author Juergen Hoeller + * @author Chris Beams + * @since 3.0 + */ +final class AnnotationAttributesReadingVisitor extends RecursiveAnnotationAttributesVisitor { + + private final String annotationType; + + private final Map attributesMap; + + private final Map> metaAnnotationMap; + + + public AnnotationAttributesReadingVisitor( + String annotationType, Map attributesMap, + Map> metaAnnotationMap, ClassLoader classLoader) { + + super(annotationType, new AnnotationAttributes(), classLoader); + this.annotationType = annotationType; + this.attributesMap = attributesMap; + this.metaAnnotationMap = metaAnnotationMap; + } + + @Override + public void doVisitEnd(Class annotationClass) { + super.doVisitEnd(annotationClass); + this.attributesMap.put(this.annotationType, this.attributes); + registerMetaAnnotations(annotationClass); + } + + private void registerMetaAnnotations(Class annotationClass) { + // Register annotations that the annotation type is annotated with. + Set metaAnnotationTypeNames = new LinkedHashSet(); + for (Annotation metaAnnotation : annotationClass.getAnnotations()) { + metaAnnotationTypeNames.add(metaAnnotation.annotationType().getName()); + if (!this.attributesMap.containsKey(metaAnnotation.annotationType().getName())) { + this.attributesMap.put(metaAnnotation.annotationType().getName(), + AnnotationUtils.getAnnotationAttributes(metaAnnotation, true, true)); + } + for (Annotation metaMetaAnnotation : metaAnnotation.annotationType().getAnnotations()) { + metaAnnotationTypeNames.add(metaMetaAnnotation.annotationType().getName()); + } + } + if (this.metaAnnotationMap != null) { + this.metaAnnotationMap.put(annotationClass.getName(), metaAnnotationTypeNames); + } + } +} \ No newline at end of file diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java index cf76c807c91..97a4897f815 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -26,6 +26,7 @@ import java.util.Set; import org.springframework.asm.AnnotationVisitor; import org.springframework.asm.MethodVisitor; import org.springframework.asm.Type; +import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.MethodMetadata; import org.springframework.util.CollectionUtils; @@ -50,7 +51,7 @@ final class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor private final Map> metaAnnotationMap = new LinkedHashMap>(4); - private final Map> attributeMap = new LinkedHashMap>(4); + private final Map attributeMap = new LinkedHashMap(4); private final MultiValueMap methodMetadataMap = new LinkedMultiValueMap(); @@ -99,20 +100,41 @@ final class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor return this.attributeMap.containsKey(annotationType); } - public Map getAnnotationAttributes(String annotationType) { + public AnnotationAttributes getAnnotationAttributes(String annotationType) { return getAnnotationAttributes(annotationType, false); } - public Map getAnnotationAttributes(String annotationType, boolean classValuesAsString) { - Map raw = this.attributeMap.get(annotationType); - if (raw == null) { + public AnnotationAttributes getAnnotationAttributes(String annotationType, boolean classValuesAsString) { + return getAnnotationAttributes(annotationType, classValuesAsString, false); + } + + public AnnotationAttributes getAnnotationAttributes( + String annotationType, boolean classValuesAsString, boolean nestedAttributesAsMap) { + + AnnotationAttributes raw = this.attributeMap.get(annotationType); + return convertClassValues(raw, classValuesAsString, nestedAttributesAsMap); + } + + private AnnotationAttributes convertClassValues( + AnnotationAttributes original, boolean classValuesAsString, boolean nestedAttributesAsMap) { + + if (original == null) { return null; } - Map result = new LinkedHashMap(raw.size()); - for (Map.Entry entry : raw.entrySet()) { + AnnotationAttributes result = new AnnotationAttributes(original.size()); + for (Map.Entry entry : original.entrySet()) { try { Object value = entry.getValue(); - if (value instanceof Type) { + if (value instanceof AnnotationAttributes) { + value = convertClassValues((AnnotationAttributes)value, classValuesAsString, nestedAttributesAsMap); + } + else if (value instanceof AnnotationAttributes[]) { + AnnotationAttributes[] values = (AnnotationAttributes[])value; + for (int i = 0; i < values.length; i++) { + values[i] = convertClassValues(values[i], classValuesAsString, nestedAttributesAsMap); + } + } + else if (value instanceof Type) { value = (classValuesAsString ? ((Type) value).getClassName() : this.classLoader.loadClass(((Type) value).getClassName())); } @@ -127,10 +149,10 @@ final class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor } else if (classValuesAsString) { if (value instanceof Class) { - value = ((Class) value).getName(); + value = ((Class) value).getName(); } else if (value instanceof Class[]) { - Class[] clazzArray = (Class[]) value; + Class[] clazzArray = (Class[]) value; String[] newValue = new String[clazzArray.length]; for (int i = 0; i < clazzArray.length; i++) { newValue[i] = clazzArray[i].getName(); diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java index 1bb40cbbee8..43476425aad 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 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. @@ -24,6 +24,7 @@ import org.springframework.asm.MethodAdapter; import org.springframework.asm.Opcodes; import org.springframework.asm.Type; import org.springframework.asm.commons.EmptyVisitor; +import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.MethodMetadata; import org.springframework.util.MultiValueMap; @@ -35,6 +36,7 @@ import org.springframework.util.MultiValueMap; * @author Juergen Hoeller * @author Mark Pollack * @author Costin Leau + * @author Chris Beams * @since 3.0 */ final class MethodMetadataReadingVisitor extends MethodAdapter implements MethodMetadata { @@ -49,7 +51,7 @@ final class MethodMetadataReadingVisitor extends MethodAdapter implements Method private final MultiValueMap methodMetadataMap; - private final Map> attributeMap = new LinkedHashMap>(2); + private final Map attributeMap = new LinkedHashMap(2); public MethodMetadataReadingVisitor(String name, int access, String declaringClassName, ClassLoader classLoader, MultiValueMap methodMetadataMap) { @@ -88,7 +90,7 @@ final class MethodMetadataReadingVisitor extends MethodAdapter implements Method return this.attributeMap.containsKey(annotationType); } - public Map getAnnotationAttributes(String annotationType) { + public AnnotationAttributes getAnnotationAttributes(String annotationType) { return this.attributeMap.get(annotationType); } diff --git a/spring-core/src/main/java/org/springframework/util/ClassUtils.java b/spring-core/src/main/java/org/springframework/util/ClassUtils.java index db0524c25cb..cbd6bae5801 100644 --- a/spring-core/src/main/java/org/springframework/util/ClassUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ClassUtils.java @@ -22,6 +22,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; +import java.security.AccessControlException; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -712,6 +713,9 @@ public abstract class ClassUtils { * Call {@link org.springframework.core.BridgeMethodResolver#findBridgedMethod} * if bridge method resolution is desirable (e.g. for obtaining metadata from * the original method definition). + *

NOTE:Since Spring 3.1.1, if java security settings disallow reflective + * access (e.g. calls to {@code Class#getDeclaredMethods} etc, this implementation + * will fall back to returning the originally provided method. * @param method the method to be invoked, which may come from an interface * @param targetClass the target class for the current invocation. * May be null or may not even implement the method. @@ -722,7 +726,12 @@ public abstract class ClassUtils { Method specificMethod = null; if (method != null && isOverridable(method, targetClass) && targetClass != null && !targetClass.equals(method.getDeclaringClass())) { - specificMethod = ReflectionUtils.findMethod(targetClass, method.getName(), method.getParameterTypes()); + try { + specificMethod = ReflectionUtils.findMethod(targetClass, method.getName(), method.getParameterTypes()); + } catch (AccessControlException ex) { + // security settings are disallowing reflective access; leave + // 'specificMethod' null and fall back to 'method' below + } } return (specificMethod != null ? specificMethod : method); } diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationAttributesTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationAttributesTests.java new file mode 100644 index 00000000000..487b3399216 --- /dev/null +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationAttributesTests.java @@ -0,0 +1,110 @@ +/* + * Copyright 2002-2012 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.core.annotation; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.junit.Test; + +/** + * Unit tests for {@link AnnotationAttributes}. + * + * @author Chris Beams + * @since 3.1.1 + */ +public class AnnotationAttributesTests { + + enum Color { RED, WHITE, BLUE } + + @Test + public void testTypeSafeAttributeAccess() { + AnnotationAttributes a = new AnnotationAttributes(); + a.put("name", "dave"); + a.put("names", new String[] { "dave", "frank", "hal" }); + a.put("bool1", true); + a.put("bool2", false); + a.put("color", Color.RED); + a.put("clazz", Integer.class); + a.put("classes", new Class[] { Number.class, Short.class, Integer.class }); + a.put("number", 42); + a.put("numbers", new int[] { 42, 43 }); + AnnotationAttributes anno = new AnnotationAttributes(); + anno.put("value", 10); + anno.put("name", "algernon"); + a.put("anno", anno); + a.put("annoArray", new AnnotationAttributes[] { anno }); + + assertThat(a.getString("name"), equalTo("dave")); + assertThat(a.getStringArray("names"), equalTo(new String[] { "dave", "frank", "hal" })); + assertThat(a.getBoolean("bool1"), equalTo(true)); + assertThat(a.getBoolean("bool2"), equalTo(false)); + assertThat(a.getEnum("color"), equalTo(Color.RED)); + assertTrue(a.getClass("clazz").equals(Integer.class)); + assertThat(a.getClassArray("classes"), equalTo(new Class[] { Number.class, Short.class, Integer.class })); + assertThat(a.getNumber("number"), equalTo(42)); + assertThat(a.getAnnotation("anno").getNumber("value"), equalTo(10)); + assertThat(a.getAnnotationArray("annoArray")[0].getString("name"), equalTo("algernon")); + } + + @Test + public void getEnum_emptyAttributeName() { + AnnotationAttributes a = new AnnotationAttributes(); + a.put("color", "RED"); + try { + a.getEnum(""); + fail(); + } catch (IllegalArgumentException ex) { + assertThat(ex.getMessage(), equalTo("attributeName must not be null or empty")); + } + try { + a.getEnum(null); + fail(); + } catch (IllegalArgumentException ex) { + assertThat(ex.getMessage(), equalTo("attributeName must not be null or empty")); + } + } + + @Test + public void getEnum_notFound() { + AnnotationAttributes a = new AnnotationAttributes(); + a.put("color", "RED"); + try { + a.getEnum("colour"); + fail(); + } catch (IllegalArgumentException ex) { + assertThat(ex.getMessage(), equalTo("Attribute 'colour' not found")); + } + } + + @Test + public void getEnum_typeMismatch() { + AnnotationAttributes a = new AnnotationAttributes(); + a.put("color", "RED"); + try { + a.getEnum("color"); + fail(); + } catch (IllegalArgumentException ex) { + String expected = + "Attribute 'color' is of type [String], but [Enum] was expected"; + assertThat(ex.getMessage().substring(0, expected.length()), equalTo(expected)); + } + } + +} diff --git a/spring-core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java b/spring-core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java index 1b8fcbdb2f9..b9cd47bf170 100644 --- a/spring-core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java +++ b/spring-core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 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. @@ -16,98 +16,203 @@ package org.springframework.core.type; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + import java.io.IOException; import java.io.Serializable; +import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import java.util.Map; import java.util.Set; -import junit.framework.TestCase; +import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; import org.springframework.stereotype.Component; /** + * Unit tests demonstrating that the reflection-based {@link StandardAnnotationMetadata} + * and ASM-based {@code AnnotationMetadataReadingVisitor} produce identical output. + * * @author Juergen Hoeller + * @author Chris Beams */ -public class AnnotationMetadataTests extends TestCase { +public class AnnotationMetadataTests { + @Test public void testStandardAnnotationMetadata() throws IOException { - StandardAnnotationMetadata annInfo = new StandardAnnotationMetadata(AnnotatedComponent.class); - doTestAnnotationInfo(annInfo); - doTestMethodAnnotationInfo(annInfo); + AnnotationMetadata metadata = new StandardAnnotationMetadata(AnnotatedComponent.class, true); + doTestAnnotationInfo(metadata); + doTestMethodAnnotationInfo(metadata); } + @Test public void testAsmAnnotationMetadata() throws IOException { MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory(); MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(AnnotatedComponent.class.getName()); - doTestAnnotationInfo(metadataReader.getAnnotationMetadata()); - doTestMethodAnnotationInfo(metadataReader.getAnnotationMetadata()); + AnnotationMetadata metadata = metadataReader.getAnnotationMetadata(); + doTestAnnotationInfo(metadata); + doTestMethodAnnotationInfo(metadata); + } + + /** + * In order to preserve backward-compatibility, {@link StandardAnnotationMetadata} + * defaults to return nested annotations and annotation arrays as actual + * Annotation instances. It is recommended for compatibility with ASM-based + * AnnotationMetadata implementations to set the 'nestedAnnotationsAsMap' flag to + * 'true' as is done in the main test above. + */ + @Test + public void testStandardAnnotationMetadata_nestedAnnotationsAsMap_false() throws IOException { + AnnotationMetadata metadata = new StandardAnnotationMetadata(AnnotatedComponent.class); + + AnnotationAttributes specialAttrs = (AnnotationAttributes) metadata.getAnnotationAttributes(SpecialAttr.class.getName()); + Annotation[] nestedAnnoArray = (Annotation[])specialAttrs.get("nestedAnnoArray"); + assertThat(nestedAnnoArray[0], instanceOf(NestedAnno.class)); } private void doTestAnnotationInfo(AnnotationMetadata metadata) { - assertEquals(AnnotatedComponent.class.getName(), metadata.getClassName()); - assertFalse(metadata.isInterface()); - assertFalse(metadata.isAbstract()); - assertTrue(metadata.isConcrete()); - assertTrue(metadata.hasSuperClass()); - assertEquals(Object.class.getName(), metadata.getSuperClassName()); - assertEquals(1, metadata.getInterfaceNames().length); - assertEquals(Serializable.class.getName(), metadata.getInterfaceNames()[0]); + assertThat(metadata.getClassName(), is(AnnotatedComponent.class.getName())); + assertThat(metadata.isInterface(), is(false)); + assertThat(metadata.isAbstract(), is(false)); + assertThat(metadata.isConcrete(), is(true)); + assertThat(metadata.hasSuperClass(), is(true)); + assertThat(metadata.getSuperClassName(), is(Object.class.getName())); + assertThat(metadata.getInterfaceNames().length, is(1)); + assertThat(metadata.getInterfaceNames()[0], is(Serializable.class.getName())); - assertTrue(metadata.hasAnnotation(Component.class.getName())); - assertTrue(metadata.hasAnnotation(Scope.class.getName())); - assertTrue(metadata.hasAnnotation(SpecialAttr.class.getName())); - assertEquals(3, metadata.getAnnotationTypes().size()); - assertTrue(metadata.getAnnotationTypes().contains(Component.class.getName())); - assertTrue(metadata.getAnnotationTypes().contains(Scope.class.getName())); - assertTrue(metadata.getAnnotationTypes().contains(SpecialAttr.class.getName())); + assertThat(metadata.hasAnnotation(Component.class.getName()), is(true)); + assertThat(metadata.hasAnnotation(Scope.class.getName()), is(true)); + assertThat(metadata.hasAnnotation(SpecialAttr.class.getName()), is(true)); + assertThat(metadata.getAnnotationTypes().size(), is(3)); + assertThat(metadata.getAnnotationTypes().contains(Component.class.getName()), is(true)); + assertThat(metadata.getAnnotationTypes().contains(Scope.class.getName()), is(true)); + assertThat(metadata.getAnnotationTypes().contains(SpecialAttr.class.getName()), is(true)); - Map compAttrs = metadata.getAnnotationAttributes(Component.class.getName()); - assertEquals(1, compAttrs.size()); - assertEquals("myName", compAttrs.get("value")); - Map scopeAttrs = metadata.getAnnotationAttributes(Scope.class.getName()); - assertEquals(1, scopeAttrs.size()); - assertEquals("myScope", scopeAttrs.get("value")); - Map specialAttrs = metadata.getAnnotationAttributes(SpecialAttr.class.getName()); - assertEquals(2, specialAttrs.size()); - assertEquals(String.class, specialAttrs.get("clazz")); - assertEquals(Thread.State.NEW, specialAttrs.get("state")); - Map specialAttrsString = metadata.getAnnotationAttributes(SpecialAttr.class.getName(), true); - assertEquals(String.class.getName(), specialAttrsString .get("clazz")); - assertEquals(Thread.State.NEW, specialAttrsString.get("state")); + AnnotationAttributes compAttrs = (AnnotationAttributes) metadata.getAnnotationAttributes(Component.class.getName()); + assertThat(compAttrs.size(), is(1)); + assertThat(compAttrs.getString("value"), is("myName")); + AnnotationAttributes scopeAttrs = (AnnotationAttributes) metadata.getAnnotationAttributes(Scope.class.getName()); + assertThat(scopeAttrs.size(), is(1)); + assertThat(scopeAttrs.getString("value"), is("myScope")); + { // perform tests with classValuesAsString = false (the default) + AnnotationAttributes specialAttrs = (AnnotationAttributes) metadata.getAnnotationAttributes(SpecialAttr.class.getName()); + assertThat(specialAttrs.size(), is(6)); + assertTrue(String.class.isAssignableFrom(specialAttrs.getClass("clazz"))); + assertTrue(specialAttrs.getEnum("state").equals(Thread.State.NEW)); + + AnnotationAttributes nestedAnno = specialAttrs.getAnnotation("nestedAnno"); + assertThat("na", is(nestedAnno.getString("value"))); + assertTrue(nestedAnno.getEnum("anEnum").equals(SomeEnum.LABEL1)); + assertArrayEquals(new Class[]{String.class}, (Class[])nestedAnno.get("classArray")); + + AnnotationAttributes[] nestedAnnoArray = specialAttrs.getAnnotationArray("nestedAnnoArray"); + assertThat(nestedAnnoArray.length, is(2)); + assertThat(nestedAnnoArray[0].getString("value"), is("default")); + assertTrue(nestedAnnoArray[0].getEnum("anEnum").equals(SomeEnum.DEFAULT)); + assertArrayEquals(new Class[]{Void.class}, (Class[])nestedAnnoArray[0].get("classArray")); + assertThat(nestedAnnoArray[1].getString("value"), is("na1")); + assertTrue(nestedAnnoArray[1].getEnum("anEnum").equals(SomeEnum.LABEL2)); + assertArrayEquals(new Class[]{Number.class}, (Class[])nestedAnnoArray[1].get("classArray")); + assertArrayEquals(new Class[]{Number.class}, nestedAnnoArray[1].getClassArray("classArray")); + + AnnotationAttributes optional = specialAttrs.getAnnotation("optional"); + assertThat(optional.getString("value"), is("optional")); + assertTrue(optional.getEnum("anEnum").equals(SomeEnum.DEFAULT)); + assertArrayEquals(new Class[]{Void.class}, (Class[])optional.get("classArray")); + assertArrayEquals(new Class[]{Void.class}, optional.getClassArray("classArray")); + + AnnotationAttributes[] optionalArray = specialAttrs.getAnnotationArray("optionalArray"); + assertThat(optionalArray.length, is(1)); + assertThat(optionalArray[0].getString("value"), is("optional")); + assertTrue(optionalArray[0].getEnum("anEnum").equals(SomeEnum.DEFAULT)); + assertArrayEquals(new Class[]{Void.class}, (Class[])optionalArray[0].get("classArray")); + assertArrayEquals(new Class[]{Void.class}, optionalArray[0].getClassArray("classArray")); + } + { // perform tests with classValuesAsString = true + AnnotationAttributes specialAttrs = (AnnotationAttributes) metadata.getAnnotationAttributes(SpecialAttr.class.getName(), true); + assertThat(specialAttrs.size(), is(6)); + assertThat(specialAttrs.get("clazz"), is((Object)String.class.getName())); + assertThat(specialAttrs.getString("clazz"), is(String.class.getName())); + + AnnotationAttributes nestedAnno = specialAttrs.getAnnotation("nestedAnno"); + assertArrayEquals(new String[]{String.class.getName()}, (String[])nestedAnno.getStringArray("classArray")); + assertArrayEquals(new String[]{String.class.getName()}, nestedAnno.getStringArray("classArray")); + + AnnotationAttributes[] nestedAnnoArray = specialAttrs.getAnnotationArray("nestedAnnoArray"); + assertArrayEquals(new String[]{Void.class.getName()}, (String[])nestedAnnoArray[0].get("classArray")); + assertArrayEquals(new String[]{Void.class.getName()}, nestedAnnoArray[0].getStringArray("classArray")); + assertArrayEquals(new String[]{Number.class.getName()}, (String[])nestedAnnoArray[1].get("classArray")); + assertArrayEquals(new String[]{Number.class.getName()}, nestedAnnoArray[1].getStringArray("classArray")); + + AnnotationAttributes optional = specialAttrs.getAnnotation("optional"); + assertArrayEquals(new String[]{Void.class.getName()}, (String[])optional.get("classArray")); + assertArrayEquals(new String[]{Void.class.getName()}, optional.getStringArray("classArray")); + + AnnotationAttributes[] optionalArray = specialAttrs.getAnnotationArray("optionalArray"); + assertArrayEquals(new String[]{Void.class.getName()}, (String[])optionalArray[0].get("classArray")); + assertArrayEquals(new String[]{Void.class.getName()}, optionalArray[0].getStringArray("classArray")); + } } private void doTestMethodAnnotationInfo(AnnotationMetadata classMetadata) { Set methods = classMetadata.getAnnotatedMethods(Autowired.class.getName()); - assertEquals(1, methods.size()); + assertThat(methods.size(), is(1)); for (MethodMetadata methodMetadata : methods) { - assertTrue(methodMetadata.isAnnotated(Autowired.class.getName())); + assertThat(methodMetadata.isAnnotated(Autowired.class.getName()), is(true)); } - } + public static enum SomeEnum { + LABEL1, LABEL2, DEFAULT; + } + + @Target({}) + @Retention(RetentionPolicy.RUNTIME) + public @interface NestedAnno { + String value() default "default"; + SomeEnum anEnum() default SomeEnum.DEFAULT; + Class[] classArray() default Void.class; + } @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface SpecialAttr { - Class clazz(); + Class clazz(); Thread.State state(); + + NestedAnno nestedAnno(); + + NestedAnno[] nestedAnnoArray(); + + NestedAnno optional() default @NestedAnno(value="optional", anEnum=SomeEnum.DEFAULT, classArray=Void.class); + + NestedAnno[] optionalArray() default {@NestedAnno(value="optional", anEnum=SomeEnum.DEFAULT, classArray=Void.class)}; } @Component("myName") @Scope("myScope") - @SpecialAttr(clazz = String.class, state = Thread.State.NEW) + @SpecialAttr(clazz = String.class, state = Thread.State.NEW, + nestedAnno = @NestedAnno(value = "na", anEnum = SomeEnum.LABEL1, classArray = {String.class}), + nestedAnnoArray = { + @NestedAnno, + @NestedAnno(value = "na1", anEnum = SomeEnum.LABEL2, classArray = {Number.class}) + }) + @SuppressWarnings({"serial", "unused"}) private static class AnnotatedComponent implements Serializable { @Autowired diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java index d40daa3cbae..33d1f510b17 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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. @@ -38,27 +38,30 @@ import org.springframework.expression.spel.SpelMessage; import org.springframework.util.CollectionUtils; /** - * A method resolver that uses reflection to locate the method that should be invoked. + * Reflection-based {@link MethodResolver} used by default in + * {@link StandardEvaluationContext} unless explicit method resolvers have been specified. * * @author Andy Clement * @author Juergen Hoeller + * @author Chris Beams * @since 3.0 + * @see StandardEvaluationContext#addMethodResolver(MethodResolver) */ public class ReflectiveMethodResolver implements MethodResolver { private static Method[] NO_METHODS = new Method[0]; private Map, MethodFilter> filters = null; - + // Using distance will ensure a more accurate match is discovered, // more closely following the Java rules. private boolean useDistance = false; - + public ReflectiveMethodResolver() { - + } - + /** * This constructors allows the ReflectiveMethodResolver to be configured such that it will * use a distance computation to check which is the better of two close matches (when there @@ -71,7 +74,7 @@ public class ReflectiveMethodResolver implements MethodResolver { public ReflectiveMethodResolver(boolean useDistance) { this.useDistance = useDistance; } - + /** * Locate a method on a type. There are three kinds of match that might occur: *

    @@ -87,15 +90,15 @@ public class ReflectiveMethodResolver implements MethodResolver { try { TypeConverter typeConverter = context.getTypeConverter(); Class type = (targetObject instanceof Class ? (Class) targetObject : targetObject.getClass()); - Method[] methods = type.getMethods(); - + Method[] methods = getMethods(type); + // If a filter is registered for this type, call it MethodFilter filter = (this.filters != null ? this.filters.get(type) : null); if (filter != null) { - List methodsForFiltering = new ArrayList(); - for (Method method: methods) { - methodsForFiltering.add(method); - } + List methodsForFiltering = new ArrayList(); + for (Method method: methods) { + methodsForFiltering.add(method); + } List methodsFiltered = filter.filter(methodsForFiltering); if (CollectionUtils.isEmpty(methodsFiltered)) { methods = NO_METHODS; @@ -124,7 +127,7 @@ public class ReflectiveMethodResolver implements MethodResolver { continue; } if (method.getName().equals(name)) { - Class[] paramTypes = method.getParameterTypes(); + Class[] paramTypes = method.getParameterTypes(); List paramDescriptors = new ArrayList(paramTypes.length); for (int i = 0; i < paramTypes.length; i++) { paramDescriptors.add(new TypeDescriptor(new MethodParameter(method, i))); @@ -194,4 +197,16 @@ public class ReflectiveMethodResolver implements MethodResolver { } } + /** + * Return the set of methods for this type. The default implementation returns the + * result of Class#getMethods for the given {@code type}, but subclasses may override + * in order to alter the results, e.g. specifying static methods declared elsewhere. + * + * @param type the class for which to return the methods + * @since 3.1.1 + */ + protected Method[] getMethods(Class type) { + return type.getMethods(); + } + } diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SpringEL300Tests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SpringEL300Tests.java index c947e27e025..0fa4d1fbd1d 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/SpringEL300Tests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/SpringEL300Tests.java @@ -17,8 +17,10 @@ package org.springframework.expression.spel; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; @@ -38,6 +40,7 @@ import org.springframework.expression.EvaluationException; import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; import org.springframework.expression.MethodExecutor; +import org.springframework.expression.MethodResolver; import org.springframework.expression.ParserContext; import org.springframework.expression.PropertyAccessor; import org.springframework.expression.TypedValue; @@ -50,12 +53,12 @@ import org.springframework.expression.spel.support.StandardTypeLocator; /** * Tests based on Jiras up to the release of Spring 3.0.0 - * + * * @author Andy Clement * @author Clark Duplichien */ public class SpringEL300Tests extends ExpressionTestCase { - + @Test public void testNPE_SPR5661() { evaluate("joinThreeStrings('a',null,'c')", "anullc", String.class); @@ -66,7 +69,7 @@ public class SpringEL300Tests extends ExpressionTestCase { public void testSWF1086() { evaluate("printDouble(T(java.math.BigDecimal).valueOf(14.35))", "anullc", String.class); } - + @Test public void testDoubleCoercion() { evaluate("printDouble(14.35)", "14.35", String.class); @@ -92,15 +95,15 @@ public class SpringEL300Tests extends ExpressionTestCase { // success } eContext.setTypeLocator(new MyTypeLocator()); - + // varargs expr = new SpelExpressionParser().parseRaw("tryToInvokeWithNull3(null,'a','b')"); Assert.assertEquals("ab",expr.getValue(eContext)); - + // varargs 2 - null is packed into the varargs expr = new SpelExpressionParser().parseRaw("tryToInvokeWithNull3(12,'a',null,'c')"); Assert.assertEquals("anullc",expr.getValue(eContext)); - + // check we can find the ctor ok expr = new SpelExpressionParser().parseRaw("new Spr5899Class().toString()"); Assert.assertEquals("instance",expr.getValue(eContext)); @@ -116,7 +119,7 @@ public class SpringEL300Tests extends ExpressionTestCase { expr = new SpelExpressionParser().parseRaw("new Spr5899Class(null,'a', null, 'b').toString()"); Assert.assertEquals("instance",expr.getValue(eContext)); } - + static class MyTypeLocator extends StandardTypeLocator { public Class findType(String typename) throws EvaluationException { @@ -132,12 +135,12 @@ public class SpringEL300Tests extends ExpressionTestCase { static class Spr5899Class { public Spr5899Class() {} - public Spr5899Class(Integer i) { } - public Spr5899Class(Integer i, String... s) { } - + public Spr5899Class(Integer i) { } + public Spr5899Class(Integer i, String... s) { } + public Integer tryToInvokeWithNull(Integer value) { return value; } public Integer tryToInvokeWithNull2(int i) { return new Integer(i); } - public String tryToInvokeWithNull3(Integer value,String... strings) { + public String tryToInvokeWithNull3(Integer value,String... strings) { StringBuilder sb = new StringBuilder(); for (int i=0;i m = new HashMap(); m.put("foo", "bar"); StandardEvaluationContext eContext = new StandardEvaluationContext(m); // root is a map instance eContext.addPropertyAccessor(new MapAccessor()); Expression expr = new SpelExpressionParser().parseRaw("['foo']"); Assert.assertEquals("bar", expr.getValue(eContext)); } - + @Test public void testSPR5847() throws Exception { StandardEvaluationContext eContext = new StandardEvaluationContext(new TestProperties()); String name = null; Expression expr = null; - + expr = new SpelExpressionParser().parseRaw("jdbcProperties['username']"); name = expr.getValue(eContext,String.class); Assert.assertEquals("Dave",name); - + expr = new SpelExpressionParser().parseRaw("jdbcProperties[username]"); name = expr.getValue(eContext,String.class); Assert.assertEquals("Dave",name); @@ -209,7 +211,7 @@ public class SpringEL300Tests extends ExpressionTestCase { eContext.addPropertyAccessor(new MapAccessor()); name = expr.getValue(eContext,String.class); Assert.assertEquals("Dave",name); - + // --- dotted property names // lookup foo on the root, then bar on that, then use that as the key into jdbcProperties @@ -222,9 +224,9 @@ public class SpringEL300Tests extends ExpressionTestCase { expr = new SpelExpressionParser().parseRaw("jdbcProperties['foo.bar']"); eContext.addPropertyAccessor(new MapAccessor()); name = expr.getValue(eContext,String.class); - Assert.assertEquals("Elephant",name); + Assert.assertEquals("Elephant",name); } - + static class TestProperties { public Properties jdbcProperties = new Properties(); public Properties foo = new Properties(); @@ -239,11 +241,11 @@ public class SpringEL300Tests extends ExpressionTestCase { static class MapAccessor implements PropertyAccessor { public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException { - return (((Map) target).containsKey(name)); + return (((Map) target).containsKey(name)); } public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException { - return new TypedValue(((Map) target).get(name)); + return new TypedValue(((Map) target).get(name)); } public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException { @@ -252,22 +254,22 @@ public class SpringEL300Tests extends ExpressionTestCase { @SuppressWarnings("unchecked") public void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException { - ((Map) target).put(name, newValue); + ((Map) target).put(name, newValue); } - public Class[] getSpecificTargetClasses() { + public Class[] getSpecificTargetClasses() { return new Class[] {Map.class}; } - + } - + @Test public void testNPE_SPR5673() throws Exception { ParserContext hashes = TemplateExpressionParsingTests.HASH_DELIMITED_PARSER_CONTEXT; ParserContext dollars = TemplateExpressionParsingTests.DEFAULT_TEMPLATE_PARSER_CONTEXT; - + checkTemplateParsing("abc${'def'} ghi","abcdef ghi"); - + checkTemplateParsingError("abc${ {}( 'abc'","Missing closing ')' for '(' at position 8"); checkTemplateParsingError("abc${ {}[ 'abc'","Missing closing ']' for '[' at position 8"); checkTemplateParsingError("abc${ {}{ 'abc'","Missing closing '}' for '{' at position 8"); @@ -278,7 +280,7 @@ public class SpringEL300Tests extends ExpressionTestCase { checkTemplateParsingError("abc${ ] }","Found closing ']' at position 6 without an opening '['"); checkTemplateParsingError("abc${ } }","No expression defined within delimiter '${}' at character 3"); checkTemplateParsingError("abc$[ } ]",DOLLARSQUARE_TEMPLATE_PARSER_CONTEXT,"Found closing '}' at position 6 without an opening '{'"); - + checkTemplateParsing("abc ${\"def''g}hi\"} jkl","abc def'g}hi jkl"); checkTemplateParsing("abc ${'def''g}hi'} jkl","abc def'g}hi jkl"); checkTemplateParsing("}","}"); @@ -295,7 +297,7 @@ public class SpringEL300Tests extends ExpressionTestCase { checkTemplateParsing("Hello ${'inner literal that''s got {[(])]}an escaped quote in it'} World","Hello inner literal that's got {[(])]}an escaped quote in it World"); checkTemplateParsingError("Hello ${","No ending suffix '}' for expression starting at character 6: ${"); } - + @Test public void testAccessingNullPropertyViaReflection_SPR5663() throws AccessException { PropertyAccessor propertyAccessor = new ReflectivePropertyAccessor(); @@ -315,33 +317,33 @@ public class SpringEL300Tests extends ExpressionTestCase { // success } } - + @Test public void testNestedProperties_SPR6923() { StandardEvaluationContext eContext = new StandardEvaluationContext(new Foo()); String name = null; Expression expr = null; - + expr = new SpelExpressionParser().parseRaw("resource.resource.server"); name = expr.getValue(eContext,String.class); Assert.assertEquals("abc",name); } - + static class Foo { public ResourceSummary resource = new ResourceSummary(); } - + static class ResourceSummary { ResourceSummary() { this.resource = new Resource(); } private final Resource resource; public Resource getResource() { - return resource; - } + return resource; + } } - + static class Resource { public String getServer() { return "abc"; @@ -374,9 +376,8 @@ public class SpringEL300Tests extends ExpressionTestCase { name = expr.getValue(eContext,String.class); // will be using the cached accessor this time Assert.assertEquals("hello",name); } - + /** $ related identifiers */ - @SuppressWarnings("unchecked") @Test public void testDollarPrefixedIdentifier_SPR7100() { Holder h = new Holder(); @@ -390,7 +391,7 @@ public class SpringEL300Tests extends ExpressionTestCase { h.map.put("$_$","tribble"); String name = null; Expression expr = null; - + expr = new SpelExpressionParser().parseRaw("map.$foo"); name = expr.getValue(eContext,String.class); Assert.assertEquals("wibble",name); @@ -430,8 +431,11 @@ public class SpringEL300Tests extends ExpressionTestCase { name = expr.getValue(eContext,String.class); // will be using the cached accessor this time Assert.assertEquals("wobble",name); } - - /** Should be accessing (setting) Goo.wibble field because 'bar' variable evaluates to "wibble" */ + + /** + * Should be accessing (setting) Goo.wibble field because 'bar' variable evaluates to + * "wibble" + */ @Test public void testIndexingAsAPropertyAccess_SPR6968_4() { Goo g = Goo.instance; @@ -458,7 +462,7 @@ public class SpringEL300Tests extends ExpressionTestCase { expr.getValue(eContext,String.class); // will be using the cached accessor this time Assert.assertEquals("world",g.value); } - + @Test public void testDollars() { StandardEvaluationContext eContext = new StandardEvaluationContext(new XX()); @@ -467,7 +471,7 @@ public class SpringEL300Tests extends ExpressionTestCase { eContext.setVariable("file_name","$foo"); Assert.assertEquals("wibble",expr.getValue(eContext,String.class)); } - + @Test public void testDollars2() { StandardEvaluationContext eContext = new StandardEvaluationContext(new XX()); @@ -476,12 +480,12 @@ public class SpringEL300Tests extends ExpressionTestCase { eContext.setVariable("file_name","$foo"); Assert.assertEquals("wibble",expr.getValue(eContext,String.class)); } - + static class XX { public Map m; - + public String floo ="bar"; - + public XX() { m = new HashMap(); m.put("$foo","wibble"); @@ -490,34 +494,34 @@ public class SpringEL300Tests extends ExpressionTestCase { } static class Goo { - + public static Goo instance = new Goo(); public String bar = "key"; public String value = null; public String wibble = "wobble"; - + public String getKey() { return "hello"; } - + public void setKey(String s) { value = s; } - + } - + static class Holder { - - public Map map = new HashMap(); + + public Map map = new HashMap(); } - + // --- private void checkTemplateParsing(String expression, String expectedValue) throws Exception { checkTemplateParsing(expression,TemplateExpressionParsingTests.DEFAULT_TEMPLATE_PARSER_CONTEXT, expectedValue); } - + private void checkTemplateParsing(String expression, ParserContext context, String expectedValue) throws Exception { SpelExpressionParser parser = new SpelExpressionParser(); Expression expr = parser.parseExpression(expression,context); @@ -527,7 +531,7 @@ public class SpringEL300Tests extends ExpressionTestCase { private void checkTemplateParsingError(String expression,String expectedMessage) throws Exception { checkTemplateParsingError(expression, TemplateExpressionParsingTests.DEFAULT_TEMPLATE_PARSER_CONTEXT,expectedMessage); } - + private void checkTemplateParsingError(String expression,ParserContext context, String expectedMessage) throws Exception { SpelExpressionParser parser = new SpelExpressionParser(); try { @@ -540,7 +544,7 @@ public class SpringEL300Tests extends ExpressionTestCase { Assert.assertEquals(expectedMessage,e.getMessage()); } } - + private static final ParserContext DOLLARSQUARE_TEMPLATE_PARSER_CONTEXT = new ParserContext() { public String getExpressionPrefix() { return "$["; @@ -552,28 +556,13 @@ public class SpringEL300Tests extends ExpressionTestCase { return true; } }; - -// @Test -// public void testFails() { -// -// StandardEvaluationContext evaluationContext = new StandardEvaluationContext(); -// evaluationContext.setVariable("target", new Foo2()); -// for (int i = 0; i < 300000; i++) { -// evaluationContext.addPropertyAccessor(new MapAccessor()); -// ExpressionParser parser = new SpelExpressionParser(); -// Expression expression = parser.parseExpression("#target.execute(payload)"); -// Message message = new Message(); -// message.setPayload(i+""); -// expression.getValue(evaluationContext, message); -// } -// } - + static class Foo2 { public void execute(String str){ System.out.println("Value: " + str); } } - + static class Message{ private String payload; @@ -587,7 +576,7 @@ public class SpringEL300Tests extends ExpressionTestCase { } // bean resolver tests - + @Test public void beanResolution() { StandardEvaluationContext eContext = new StandardEvaluationContext(new XX()); @@ -601,7 +590,7 @@ public class SpringEL300Tests extends ExpressionTestCase { Assert.assertEquals(SpelMessage.NO_BEAN_RESOLVER_REGISTERED,see.getMessageCode()); Assert.assertEquals("foo",see.getInserts()[0]); } - + eContext.setBeanResolver(new MyBeanResolver()); // bean exists @@ -622,11 +611,11 @@ public class SpringEL300Tests extends ExpressionTestCase { Assert.assertTrue(see.getCause() instanceof AccessException); Assert.assertTrue(((AccessException)see.getCause()).getMessage().startsWith("DONT")); } - + // bean exists expr = new SpelExpressionParser().parseRaw("@'foo.bar'"); Assert.assertEquals("trouble",expr.getValue(eContext,String.class)); - + // bean exists try { expr = new SpelExpressionParser().parseRaw("@378"); @@ -635,7 +624,7 @@ public class SpringEL300Tests extends ExpressionTestCase { Assert.assertEquals(SpelMessage.INVALID_BEAN_REFERENCE,spe.getMessageCode()); } } - + static class MyBeanResolver implements BeanResolver { public Object resolve(EvaluationContext context, String beanname) throws AccessException { if (beanname.equals("foo")) { @@ -648,14 +637,14 @@ public class SpringEL300Tests extends ExpressionTestCase { return null; } } - + // end bean resolver tests @Test public void elvis_SPR7209_1() { StandardEvaluationContext eContext = new StandardEvaluationContext(new XX()); Expression expr = null; - + // Different parts of elvis expression are null expr = new SpelExpressionParser().parseRaw("(?:'default')"); Assert.assertEquals("default", expr.getValue()); @@ -696,68 +685,67 @@ public class SpringEL300Tests extends ExpressionTestCase { expr = new SpelExpressionParser().parseRaw("''?:'default'"); Assert.assertEquals("default", expr.getValue()); } - - @Test - @SuppressWarnings("unchecked") - public void testMapOfMap_SPR7244() throws Exception { - Map map = new LinkedHashMap(); - map.put("uri", "http:"); - Map nameMap = new LinkedHashMap(); - nameMap.put("givenName", "Arthur"); - map.put("value", nameMap); - - StandardEvaluationContext ctx = new StandardEvaluationContext(map); - ExpressionParser parser = new SpelExpressionParser(); - String el1 = "#root['value'].get('givenName')"; - Expression exp = parser.parseExpression(el1); - Object evaluated = exp.getValue(ctx); - Assert.assertEquals("Arthur", evaluated); - String el2 = "#root['value']['givenName']"; - exp = parser.parseExpression(el2); - evaluated = exp.getValue(ctx); - Assert.assertEquals("Arthur",evaluated); - } - + @Test + public void testMapOfMap_SPR7244() throws Exception { + Map map = new LinkedHashMap(); + map.put("uri", "http:"); + Map nameMap = new LinkedHashMap(); + nameMap.put("givenName", "Arthur"); + map.put("value", nameMap); + + StandardEvaluationContext ctx = new StandardEvaluationContext(map); + ExpressionParser parser = new SpelExpressionParser(); + String el1 = "#root['value'].get('givenName')"; + Expression exp = parser.parseExpression(el1); + Object evaluated = exp.getValue(ctx); + Assert.assertEquals("Arthur", evaluated); + + String el2 = "#root['value']['givenName']"; + exp = parser.parseExpression(el2); + evaluated = exp.getValue(ctx); + Assert.assertEquals("Arthur",evaluated); + } + @Test public void testProjectionTypeDescriptors_1() throws Exception { - StandardEvaluationContext ctx = new StandardEvaluationContext(new C()); - SpelExpressionParser parser = new SpelExpressionParser(); - String el1 = "ls.![#this.equals('abc')]"; - SpelExpression exp = parser.parseRaw(el1); - List value = (List)exp.getValue(ctx); - // value is list containing [true,false] - Assert.assertEquals(Boolean.class,value.get(0).getClass()); - TypeDescriptor evaluated = exp.getValueTypeDescriptor(ctx); - Assert.assertEquals(null, evaluated.getElementTypeDescriptor()); + StandardEvaluationContext ctx = new StandardEvaluationContext(new C()); + SpelExpressionParser parser = new SpelExpressionParser(); + String el1 = "ls.![#this.equals('abc')]"; + SpelExpression exp = parser.parseRaw(el1); + List value = (List)exp.getValue(ctx); + // value is list containing [true,false] + Assert.assertEquals(Boolean.class,value.get(0).getClass()); + TypeDescriptor evaluated = exp.getValueTypeDescriptor(ctx); + Assert.assertEquals(null, evaluated.getElementTypeDescriptor()); } - + @Test public void testProjectionTypeDescriptors_2() throws Exception { - StandardEvaluationContext ctx = new StandardEvaluationContext(new C()); - SpelExpressionParser parser = new SpelExpressionParser(); - String el1 = "as.![#this.equals('abc')]"; - SpelExpression exp = parser.parseRaw(el1); - Object[] value = (Object[])exp.getValue(ctx); - // value is array containing [true,false] - Assert.assertEquals(Boolean.class,value[0].getClass()); - TypeDescriptor evaluated = exp.getValueTypeDescriptor(ctx); - Assert.assertEquals(Boolean.class, evaluated.getElementTypeDescriptor().getType()); + StandardEvaluationContext ctx = new StandardEvaluationContext(new C()); + SpelExpressionParser parser = new SpelExpressionParser(); + String el1 = "as.![#this.equals('abc')]"; + SpelExpression exp = parser.parseRaw(el1); + Object[] value = (Object[])exp.getValue(ctx); + // value is array containing [true,false] + Assert.assertEquals(Boolean.class,value[0].getClass()); + TypeDescriptor evaluated = exp.getValueTypeDescriptor(ctx); + Assert.assertEquals(Boolean.class, evaluated.getElementTypeDescriptor().getType()); } - + @Test public void testProjectionTypeDescriptors_3() throws Exception { - StandardEvaluationContext ctx = new StandardEvaluationContext(new C()); - SpelExpressionParser parser = new SpelExpressionParser(); - String el1 = "ms.![key.equals('abc')]"; - SpelExpression exp = parser.parseRaw(el1); - List value = (List)exp.getValue(ctx); - // value is list containing [true,false] - Assert.assertEquals(Boolean.class,value.get(0).getClass()); - TypeDescriptor evaluated = exp.getValueTypeDescriptor(ctx); - Assert.assertEquals(null, evaluated.getElementTypeDescriptor()); + StandardEvaluationContext ctx = new StandardEvaluationContext(new C()); + SpelExpressionParser parser = new SpelExpressionParser(); + String el1 = "ms.![key.equals('abc')]"; + SpelExpression exp = parser.parseRaw(el1); + List value = (List)exp.getValue(ctx); + // value is list containing [true,false] + Assert.assertEquals(Boolean.class,value.get(0).getClass()); + TypeDescriptor evaluated = exp.getValueTypeDescriptor(ctx); + Assert.assertEquals(null, evaluated.getElementTypeDescriptor()); } - + static class C { public List ls; public String[] as; @@ -772,19 +760,19 @@ public class SpringEL300Tests extends ExpressionTestCase { ms.put("def","pqr"); } } - + static class D { public String a; - + private D(String s) { a=s; } - + public String toString() { return "D("+a+")"; } } - + @Test public void testGreaterThanWithNulls_SPR7840() throws Exception { List list = new ArrayList(); @@ -794,30 +782,31 @@ public class SpringEL300Tests extends ExpressionTestCase { list.add(new D("ccc")); list.add(new D(null)); list.add(new D("zzz")); - - StandardEvaluationContext ctx = new StandardEvaluationContext(list); - SpelExpressionParser parser = new SpelExpressionParser(); - String el1 = "#root.?[a < 'hhh']"; - SpelExpression exp = parser.parseRaw(el1); - Object value = exp.getValue(ctx); - assertEquals("[D(aaa), D(bbb), D(null), D(ccc), D(null)]",value.toString()); + StandardEvaluationContext ctx = new StandardEvaluationContext(list); + SpelExpressionParser parser = new SpelExpressionParser(); - String el2 = "#root.?[a > 'hhh']"; - SpelExpression exp2 = parser.parseRaw(el2); - Object value2 = exp2.getValue(ctx); - assertEquals("[D(zzz)]",value2.toString()); - - // trim out the nulls first - String el3 = "#root.?[a!=null].?[a < 'hhh']"; - SpelExpression exp3 = parser.parseRaw(el3); - Object value3 = exp3.getValue(ctx); - assertEquals("[D(aaa), D(bbb), D(ccc)]",value3.toString()); + String el1 = "#root.?[a < 'hhh']"; + SpelExpression exp = parser.parseRaw(el1); + Object value = exp.getValue(ctx); + assertEquals("[D(aaa), D(bbb), D(null), D(ccc), D(null)]",value.toString()); + + String el2 = "#root.?[a > 'hhh']"; + SpelExpression exp2 = parser.parseRaw(el2); + Object value2 = exp2.getValue(ctx); + assertEquals("[D(zzz)]",value2.toString()); + + // trim out the nulls first + String el3 = "#root.?[a!=null].?[a < 'hhh']"; + SpelExpression exp3 = parser.parseRaw(el3); + Object value3 = exp3.getValue(ctx); + assertEquals("[D(aaa), D(bbb), D(ccc)]",value3.toString()); } /** - * Test whether {@link ReflectiveMethodResolver} follows Java Method Invocation Conversion order. And more precisely - * that widening reference conversion is 'higher' than a unboxing conversion. + * Test whether {@link ReflectiveMethodResolver} follows Java Method Invocation + * Conversion order. And more precisely that widening reference conversion is 'higher' + * than a unboxing conversion. */ @Test public void testConversionPriority_8224() throws Exception { @@ -904,16 +893,16 @@ public class SpringEL300Tests extends ExpressionTestCase { } - + @Test public void varargsAndPrimitives_SPR8174() throws Exception { EvaluationContext emptyEvalContext = new StandardEvaluationContext(); List args = new ArrayList(); - + args.add(TypeDescriptor.forObject(34L)); ReflectionUtil ru = new ReflectionUtil(); MethodExecutor me = new ReflectiveMethodResolver().resolve(emptyEvalContext,ru,"methodToCall",args); - + args.set(0,TypeDescriptor.forObject(23)); me = new ReflectiveMethodResolver().resolve(emptyEvalContext,ru,"foo",args); me.execute(emptyEvalContext, ru, 45); @@ -941,127 +930,126 @@ public class SpringEL300Tests extends ExpressionTestCase { args.set(0,TypeDescriptor.forObject((byte)23)); me = new ReflectiveMethodResolver().resolve(emptyEvalContext,ru,"foo",args); me.execute(emptyEvalContext, ru, (byte)23); - + args.set(0,TypeDescriptor.forObject(true)); me = new ReflectiveMethodResolver().resolve(emptyEvalContext,ru,"foo",args); me.execute(emptyEvalContext, ru, true); - + // trickier: args.set(0,TypeDescriptor.forObject(12)); args.add(TypeDescriptor.forObject(23f)); me = new ReflectiveMethodResolver().resolve(emptyEvalContext,ru,"bar",args); me.execute(emptyEvalContext, ru, 12,23f); - + } - - - + + + public class ReflectionUtil { - public Object methodToCall(T param) { - System.out.println(param+" "+param.getClass()); - return "Object methodToCall(T param)"; - } - - public void foo(int... array) { - if (array.length==0) { - throw new RuntimeException(); - } - } - public void foo(float...array) { - if (array.length==0) { - throw new RuntimeException(); - } - } - public void foo(double...array) { - if (array.length==0) { - throw new RuntimeException(); - } - } - public void foo(short...array) { - if (array.length==0) { - throw new RuntimeException(); - } - } - public void foo(long...array) { - if (array.length==0) { - throw new RuntimeException(); - } - } - public void foo(boolean...array) { - if (array.length==0) { - throw new RuntimeException(); - } - } - public void foo(char...array) { - if (array.length==0) { - throw new RuntimeException(); - } - } - public void foo(byte...array) { - if (array.length==0) { - throw new RuntimeException(); - } - } - - public void bar(int... array) { - if (array.length==0) { - throw new RuntimeException(); - } - } - } - + public Object methodToCall(T param) { + System.out.println(param+" "+param.getClass()); + return "Object methodToCall(T param)"; + } + + public void foo(int... array) { + if (array.length==0) { + throw new RuntimeException(); + } + } + public void foo(float...array) { + if (array.length==0) { + throw new RuntimeException(); + } + } + public void foo(double...array) { + if (array.length==0) { + throw new RuntimeException(); + } + } + public void foo(short...array) { + if (array.length==0) { + throw new RuntimeException(); + } + } + public void foo(long...array) { + if (array.length==0) { + throw new RuntimeException(); + } + } + public void foo(boolean...array) { + if (array.length==0) { + throw new RuntimeException(); + } + } + public void foo(char...array) { + if (array.length==0) { + throw new RuntimeException(); + } + } + public void foo(byte...array) { + if (array.length==0) { + throw new RuntimeException(); + } + } + + public void bar(int... array) { + if (array.length==0) { + throw new RuntimeException(); + } + } + } + @Test public void testReservedWords_8228() throws Exception { // "DIV","EQ","GE","GT","LE","LT","MOD","NE","NOT" - @SuppressWarnings("unused") - class Reserver { - public Reserver getReserver() { - return this; - } - public String NE = "abc"; - public String ne = "def"; - - public int DIV = 1; - public int div = 3; - - public Map m = new HashMap(); - - @SuppressWarnings("unchecked") + @SuppressWarnings("unused") + class Reserver { + public Reserver getReserver() { + return this; + } + public String NE = "abc"; + public String ne = "def"; + + public int DIV = 1; + public int div = 3; + + public Map m = new HashMap(); + Reserver() { - m.put("NE","xyz"); - } - } - StandardEvaluationContext ctx = new StandardEvaluationContext(new Reserver()); - SpelExpressionParser parser = new SpelExpressionParser(); - String ex = "getReserver().NE"; - SpelExpression exp = null; - exp = parser.parseRaw(ex); - String value = (String)exp.getValue(ctx); - Assert.assertEquals("abc",value); + m.put("NE","xyz"); + } + } + StandardEvaluationContext ctx = new StandardEvaluationContext(new Reserver()); + SpelExpressionParser parser = new SpelExpressionParser(); + String ex = "getReserver().NE"; + SpelExpression exp = null; + exp = parser.parseRaw(ex); + String value = (String)exp.getValue(ctx); + Assert.assertEquals("abc",value); - ex = "getReserver().ne"; - exp = parser.parseRaw(ex); - value = (String)exp.getValue(ctx); - Assert.assertEquals("def",value); + ex = "getReserver().ne"; + exp = parser.parseRaw(ex); + value = (String)exp.getValue(ctx); + Assert.assertEquals("def",value); - ex = "getReserver().m[NE]"; - exp = parser.parseRaw(ex); - value = (String)exp.getValue(ctx); - Assert.assertEquals("xyz",value); - - ex = "getReserver().DIV"; - exp = parser.parseRaw(ex); - Assert.assertEquals(1,exp.getValue(ctx)); + ex = "getReserver().m[NE]"; + exp = parser.parseRaw(ex); + value = (String)exp.getValue(ctx); + Assert.assertEquals("xyz",value); - ex = "getReserver().div"; - exp = parser.parseRaw(ex); - Assert.assertEquals(3,exp.getValue(ctx)); - - exp = parser.parseRaw("NE"); - Assert.assertEquals("abc",exp.getValue(ctx)); + ex = "getReserver().DIV"; + exp = parser.parseRaw(ex); + Assert.assertEquals(1,exp.getValue(ctx)); + + ex = "getReserver().div"; + exp = parser.parseRaw(ex); + Assert.assertEquals(3,exp.getValue(ctx)); + + exp = parser.parseRaw("NE"); + Assert.assertEquals("abc",exp.getValue(ctx)); } - + /** * We add property accessors in the order: * First, Second, Third, Fourth. @@ -1071,39 +1059,24 @@ public class SpringEL300Tests extends ExpressionTestCase { @Test public void testPropertyAccessorOrder_8211() { ExpressionParser expressionParser = new SpelExpressionParser(); - StandardEvaluationContext evaluationContext = + StandardEvaluationContext evaluationContext = new StandardEvaluationContext(new ContextObject()); - + evaluationContext.addPropertyAccessor(new TestPropertyAccessor("firstContext")); evaluationContext.addPropertyAccessor(new TestPropertyAccessor("secondContext")); evaluationContext.addPropertyAccessor(new TestPropertyAccessor("thirdContext")); evaluationContext.addPropertyAccessor(new TestPropertyAccessor("fourthContext")); - - assertEquals("first", + + assertEquals("first", expressionParser.parseExpression("shouldBeFirst").getValue(evaluationContext)); - assertEquals("second", + assertEquals("second", expressionParser.parseExpression("shouldBeSecond").getValue(evaluationContext)); - assertEquals("third", + assertEquals("third", expressionParser.parseExpression("shouldBeThird").getValue(evaluationContext)); - assertEquals("fourth", + assertEquals("fourth", expressionParser.parseExpression("shouldBeFourth").getValue(evaluationContext)); } - - // test not quite complete, it doesn't check that the list of resolvers at the end of - // PropertyOrFieldReference.getPropertyAccessorsToTry() doesn't contain duplicates, which - // is what it is trying to test by having a property accessor that returns specific - // classes Integer and Number -// @Test -// public void testPropertyAccessorOrder_8211_2() { -// ExpressionParser expressionParser = new SpelExpressionParser(); -// StandardEvaluationContext evaluationContext = -// new StandardEvaluationContext(new Integer(42)); -// -// evaluationContext.addPropertyAccessor(new TestPropertyAccessor2()); -// -// assertEquals("42", expressionParser.parseExpression("x").getValue(evaluationContext)); -// } - + class TestPropertyAccessor implements PropertyAccessor { private String mapName; public TestPropertyAccessor(String mapName) { @@ -1139,38 +1112,13 @@ public class SpringEL300Tests extends ExpressionTestCase { } } - -// class TestPropertyAccessor2 implements PropertyAccessor { -// -// public Class[] getSpecificTargetClasses() { -// return new Class[]{Integer.class,Number.class}; -// } -// -// public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException { -// return true; -// } -// -// public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException { -// return new TypedValue(target.toString(),TypeDescriptor.valueOf(String.class)); -// } -// -// public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException { -// return false; -// } -// -// public void write(EvaluationContext context, Object target, String name, Object newValue) -// throws AccessException { -// throw new UnsupportedOperationException(); -// } -// -// } class ContextObject { public Map firstContext = new HashMap(); public Map secondContext = new HashMap(); public Map thirdContext = new HashMap(); public Map fourthContext = new HashMap(); - + public ContextObject() { firstContext.put("shouldBeFirst", "first"); secondContext.put("shouldBeFirst", "second"); @@ -1180,10 +1128,10 @@ public class SpringEL300Tests extends ExpressionTestCase { secondContext.put("shouldBeSecond", "second"); thirdContext.put("shouldBeSecond", "third"); fourthContext.put("shouldBeSecond", "fourth"); - + thirdContext.put("shouldBeThird", "third"); fourthContext.put("shouldBeThird", "fourth"); - + fourthContext.put("shouldBeFourth", "fourth"); } public Map getFirstContext() {return firstContext;} @@ -1192,6 +1140,41 @@ public class SpringEL300Tests extends ExpressionTestCase { public Map getFourthContext() {return fourthContext;} } + /** + * Test the ability to subclass the ReflectiveMethodResolver and change how it + * determines the set of methods for a type. + */ + @Test + public void testCustomStaticFunctions_SPR9038() { + try { + ExpressionParser parser = new SpelExpressionParser(); + StandardEvaluationContext context = new StandardEvaluationContext(); + List methodResolvers = new ArrayList(); + methodResolvers.add(new ReflectiveMethodResolver() { + @Override + protected Method[] getMethods(Class type) { + try { + return new Method[] { + Integer.class.getDeclaredMethod("parseInt", new Class[] { + String.class, Integer.TYPE }) }; + } catch (NoSuchMethodException e1) { + return new Method[0]; + } + } + }); + + context.setMethodResolvers(methodResolvers); + org.springframework.expression.Expression expression = + parser.parseExpression("parseInt('-FF', 16)"); + + Integer result = expression.getValue(context, "", Integer.class); + assertEquals("Equal assertion failed: ", -255, result.intValue()); + } catch (Exception e) { + e.printStackTrace(); + fail("Unexpected exception: "+e.toString()); + } + } + } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/GenericTableMetaDataProvider.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/GenericTableMetaDataProvider.java index fc160ec8b2c..1a4e28772fc 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/GenericTableMetaDataProvider.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/GenericTableMetaDataProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -181,8 +181,13 @@ public class GenericTableMetaDataProvider implements TableMetaDataProvider { setGeneratedKeysColumnNameArraySupported(false); } else { - logger.debug("GeneratedKeysColumnNameArray is supported for " + databaseProductName); - setGeneratedKeysColumnNameArraySupported(true); + if (isGetGeneratedKeysSupported()) { + logger.debug("GeneratedKeysColumnNameArray is supported for " + databaseProductName); + setGeneratedKeysColumnNameArraySupported(true); + } + else { + setGeneratedKeysColumnNameArraySupported(false); + } } } catch (SQLException se) { @@ -307,7 +312,7 @@ public class GenericTableMetaDataProvider implements TableMetaDataProvider { tmd.setTableName(tables.getString("TABLE_NAME")); tmd.setType(tables.getString("TABLE_TYPE")); if (tmd.getSchemaName() == null) { - tableMeta.put(userName.toUpperCase(), tmd); + tableMeta.put(userName != null ? userName.toUpperCase() : "", tmd); } else { tableMeta.put(tmd.getSchemaName().toUpperCase(), tmd); @@ -335,7 +340,7 @@ public class GenericTableMetaDataProvider implements TableMetaDataProvider { if (schemaName == null) { tmd = tableMeta.get(getDefaultSchema()); if (tmd == null) { - tmd = tableMeta.get(userName.toUpperCase()); + tmd = tableMeta.get(userName != null ? userName.toUpperCase() : ""); } if (tmd == null) { tmd = tableMeta.get("PUBLIC"); diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterUtils.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterUtils.java index 1ab1f86bff7..ccc668502c5 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterUtils.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -85,12 +85,18 @@ public abstract class NamedParameterUtils { int escapes = 0; int i = 0; while (i < statement.length) { - int skipToPosition = skipCommentsAndQuotes(statement, i); - if (i != skipToPosition) { - if (skipToPosition >= statement.length) { + int skipToPosition = i; + while (i < statement.length) { + skipToPosition = skipCommentsAndQuotes(statement, i); + if (i == skipToPosition) { break; } - i = skipToPosition; + else { + i = skipToPosition; + } + } + if (i >= statement.length) { + break; } char c = statement[i]; if (c == ':' || c == '&') { @@ -252,6 +258,9 @@ public abstract class NamedParameterUtils { actualSql.append(originalSql.substring(lastIndex, startIndex)); if (paramSource != null && paramSource.hasValue(paramName)) { Object value = paramSource.getValue(paramName); + if (value instanceof SqlParameterValue) { + value = ((SqlParameterValue) value).getValue(); + } if (value instanceof Collection) { Iterator entryIter = ((Collection) value).iterator(); int k = 0; diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ResourceDatabasePopulator.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ResourceDatabasePopulator.java index 3da2b77fd72..b33a9d008a0 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ResourceDatabasePopulator.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ResourceDatabasePopulator.java @@ -181,9 +181,10 @@ public class ResourceDatabasePopulator implements DatabasePopulator { for (String statement : statements) { lineNumber++; try { - int rowsAffected = stmt.executeUpdate(statement); + stmt.execute(statement); + int rowsAffected = stmt.getUpdateCount(); if (logger.isDebugEnabled()) { - logger.debug(rowsAffected + " rows affected by SQL: " + statement); + logger.debug(rowsAffected + " returned as updateCount for SQL: " + statement); } } catch (SQLException ex) { diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/CustomSQLExceptionTranslatorRegistrar.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/CustomSQLExceptionTranslatorRegistrar.java new file mode 100644 index 00000000000..e9806b8c0bc --- /dev/null +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/CustomSQLExceptionTranslatorRegistrar.java @@ -0,0 +1,68 @@ +/* + * Copyright 2002-2012 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.jdbc.support; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.InitializingBean; + +/** + * Registry for registering custom {@link org.springframework.jdbc.support.SQLExceptionTranslator}. + * + * @author Thomas Risberg + * @since 3.1 + */ +public class CustomSQLExceptionTranslatorRegistrar implements InitializingBean { + + private static final Log logger = LogFactory.getLog(CustomSQLExceptionTranslatorRegistrar.class); + + /** + * Map registry to hold custom translators specific databases. + * Key is the database product name as defined in the + * {@link org.springframework.jdbc.support.SQLErrorCodesFactory}. + */ + private final Map sqlExceptionTranslators = + new HashMap(); + + /** + * Setter for a Map of translators where the key must be the database name as defined in the + * sql-error-codes.xml file. This method is used when this registry is used in an application context. + *

    Note that any existing translators will remain unless there is a match in the database name at which + * point the new translator will replace the existing one. + * + * @param sqlExceptionTranslators + */ + public void setSqlExceptionTranslators(Map sqlExceptionTranslators) { + this.sqlExceptionTranslators.putAll(sqlExceptionTranslators); + } + + public void afterPropertiesSet() throws Exception { + if (logger.isDebugEnabled()) { + logger.debug("Registering custom SQL exception translators for database(s): " + + sqlExceptionTranslators.keySet()); + } + for (String dbName : sqlExceptionTranslators.keySet()) { + CustomSQLExceptionTranslatorRegistry.getInstance() + .registerSqlExceptionTranslator(dbName, sqlExceptionTranslators.get(dbName)); + } + } +} diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/CustomSQLExceptionTranslatorRegistry.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/CustomSQLExceptionTranslatorRegistry.java new file mode 100644 index 00000000000..89db6b89023 --- /dev/null +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/CustomSQLExceptionTranslatorRegistry.java @@ -0,0 +1,102 @@ +/* + * Copyright 2002-2012 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.jdbc.support; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.WeakHashMap; +import javax.sql.DataSource; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.util.Assert; +import org.springframework.util.PatternMatchUtils; + +/** + * Registry for custom {@link org.springframework.jdbc.support.SQLExceptionTranslator} instances associated with + * specific databases allowing for overriding translation based on values contained in the configuration file + * named "sql-error-codes.xml". + * + * @author Thomas Risberg + * @since 3.1 + * @see SQLErrorCodesFactory + */ +public class CustomSQLExceptionTranslatorRegistry { + + private static final Log logger = LogFactory.getLog(CustomSQLExceptionTranslatorRegistry.class); + + /** + * Map registry to hold custom translators specific databases. + * Key is the database product name as defined in the + * {@link org.springframework.jdbc.support.SQLErrorCodesFactory}. + */ + private final Map sqlExceptionTranslatorRegistry = + new HashMap(); + + + /** + * Keep track of a single instance so we can return it to classes that request it. + */ + private static final CustomSQLExceptionTranslatorRegistry instance = new CustomSQLExceptionTranslatorRegistry(); + + + /** + * Return the singleton instance. + */ + public static CustomSQLExceptionTranslatorRegistry getInstance() { + return instance; + } + + + /** + * Create a new instance of the {@link org.springframework.jdbc.support.CustomSQLExceptionTranslatorRegistry} class. + *

    Not public to enforce Singleton design pattern. + */ + private CustomSQLExceptionTranslatorRegistry() { + } + + /** + * Register a new custom translator for the specified database name. + * + * @param dbName the database name + * @param sqlExceptionTranslator the custom translator + */ + public void registerSqlExceptionTranslator(String dbName, SQLExceptionTranslator sqlExceptionTranslator) { + SQLExceptionTranslator replaced = sqlExceptionTranslatorRegistry.put(dbName, sqlExceptionTranslator); + if (replaced != null) { + logger.warn("Replacing custom translator '" + replaced + + "' for database " + dbName + + " with '" + sqlExceptionTranslator + "'"); + } + else { + logger.info("Adding custom translator '" + sqlExceptionTranslator.getClass().getSimpleName() + + "' for database " + dbName); + } + } + + public SQLExceptionTranslator findSqlExceptionTranslatorForDatabase(String dbName) { + return sqlExceptionTranslatorRegistry.get(dbName); + } + +} diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodes.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodes.java index c7d49126ab2..8dde372beca 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodes.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodes.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2012 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. @@ -104,6 +104,10 @@ public class SQLErrorCodes { return customSqlExceptionTranslator; } + public void setCustomSqlExceptionTranslator(SQLExceptionTranslator customSqlExceptionTranslator) { + this.customSqlExceptionTranslator = customSqlExceptionTranslator; + } + public void setCustomSqlExceptionTranslatorClass(Class customSqlExceptionTranslatorClass) { if (customSqlExceptionTranslatorClass != null) { try { diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodesFactory.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodesFactory.java index 4b984e7f577..bdaf16c0e1d 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodesFactory.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodesFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2012 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. @@ -17,6 +17,7 @@ package org.springframework.jdbc.support; import java.util.Collections; +import java.util.HashMap; import java.util.Map; import java.util.WeakHashMap; import javax.sql.DataSource; @@ -130,7 +131,7 @@ public class SQLErrorCodesFactory { logger.warn("Error loading SQL error codes from config file", ex); errorCodes = Collections.emptyMap(); } - + this.errorCodesMap = errorCodes; } @@ -159,7 +160,7 @@ public class SQLErrorCodesFactory { */ public SQLErrorCodes getErrorCodes(String dbName) { Assert.notNull(dbName, "Database product name must not be null"); - + SQLErrorCodes sec = this.errorCodesMap.get(dbName); if (sec == null) { for (SQLErrorCodes candidate : this.errorCodesMap.values()) { @@ -170,6 +171,7 @@ public class SQLErrorCodesFactory { } } if (sec != null) { + checkSqlExceptionTranslatorRegistry(dbName, sec); if (logger.isDebugEnabled()) { logger.debug("SQL error codes for '" + dbName + "' found"); } @@ -246,4 +248,24 @@ public class SQLErrorCodesFactory { } } + private void checkSqlExceptionTranslatorRegistry(String dbName, SQLErrorCodes dbCodes) { + // Check the custom sql exception translator registry for any entries + SQLExceptionTranslator customTranslator = + CustomSQLExceptionTranslatorRegistry.getInstance().findSqlExceptionTranslatorForDatabase(dbName); + if (customTranslator != null) { + if (dbCodes.getCustomSqlExceptionTranslator() != null) { + logger.warn("Overriding already defined custom translator '" + + dbCodes.getCustomSqlExceptionTranslator().getClass().getSimpleName() + + " with '" + customTranslator.getClass().getSimpleName() + + "' found in the CustomSQLExceptionTranslatorRegistry for database " + dbName); + } + else { + logger.info("Using custom translator '" + customTranslator.getClass().getSimpleName() + + "' found in the CustomSQLExceptionTranslatorRegistry for database " + dbName); + } + dbCodes.setCustomSqlExceptionTranslator(customTranslator); + } + + } + } diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterUtilsTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterUtilsTests.java index ed4c54e0ee8..48875c60f07 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterUtilsTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -268,4 +268,30 @@ public class NamedParameterUtilsTests { assertEquals(expectedSql, newSql); } + /* + * SPR-8280 + */ + @Test + public void parseSqlStatementWithQuotedSingleQuote() { + String sql = "SELECT ':foo'':doo', :xxx FROM DUAL"; + ParsedSql psql = NamedParameterUtils.parseSqlStatement(sql); + assertEquals(1, psql.getTotalParameterCount()); + assertEquals("xxx", psql.getParameterNames().get(0)); + } + + @Test + public void parseSqlStatementWithQuotesAndCommentBefore() { + String sql = "SELECT /*:doo*/':foo', :xxx FROM DUAL"; + ParsedSql psql = NamedParameterUtils.parseSqlStatement(sql); + assertEquals(1, psql.getTotalParameterCount()); + assertEquals("xxx", psql.getParameterNames().get(0)); + } + + @Test + public void parseSqlStatementWithQuotesAndCommentAfter() { + String sql2 = "SELECT ':foo'/*:doo*/, :xxx FROM DUAL"; + ParsedSql psql2 = NamedParameterUtils.parseSqlStatement(sql2); + assertEquals(1, psql2.getTotalParameterCount()); + assertEquals("xxx", psql2.getParameterNames().get(0)); + } } diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/DatabasePopulatorTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/DatabasePopulatorTests.java index 8154fa526ca..04d59698cce 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/DatabasePopulatorTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/DatabasePopulatorTests.java @@ -20,8 +20,6 @@ import static org.junit.Assert.assertEquals; import java.sql.Connection; -import javax.sql.DataSource; - import org.junit.After; import org.junit.Test; import org.springframework.core.io.ClassRelativeResourceLoader; @@ -49,7 +47,7 @@ public class DatabasePopulatorTests { assertEquals(name, jdbcTemplate.queryForObject("select NAME from T_TEST", String.class)); } - private void assertUsersDatabaseCreated(DataSource db) { + private void assertUsersDatabaseCreated() { assertEquals("Sam", jdbcTemplate.queryForObject("select first_name from users where last_name = 'Brannen'", String.class)); } @@ -191,7 +189,22 @@ public class DatabasePopulatorTests { connection.close(); } - assertUsersDatabaseCreated(db); + assertUsersDatabaseCreated(); + } + + @Test + public void testBuildWithSelectStatements() throws Exception { + databasePopulator.addScript(resourceLoader.getResource("db-schema.sql")); + databasePopulator.addScript(resourceLoader.getResource("db-test-data-select.sql")); + Connection connection = db.getConnection(); + try { + databasePopulator.populate(connection); + } finally { + connection.close(); + } + + assertEquals(1, jdbcTemplate.queryForInt("select COUNT(NAME) from T_TEST where NAME='Keith'")); + assertEquals(1, jdbcTemplate.queryForInt("select COUNT(NAME) from T_TEST where NAME='Dave'")); } } diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/support/CustomSQLExceptionTranslatorRegistrarTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/support/CustomSQLExceptionTranslatorRegistrarTests.java new file mode 100644 index 00000000000..21ed920479e --- /dev/null +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/support/CustomSQLExceptionTranslatorRegistrarTests.java @@ -0,0 +1,67 @@ +/* + * Copyright 2002-2012 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.jdbc.support; + +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; + +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.TransientDataAccessResourceException; +import org.springframework.jdbc.BadSqlGrammarException; + +import static org.junit.Assert.*; + +/** + * Tests for custom translator. + * + * @author Thomas Risberg + */ +public class CustomSQLExceptionTranslatorRegistrarTests { + + @Before + public void setUp() { + new ClassPathXmlApplicationContext("test-custom-translators-context.xml", + CustomSQLExceptionTranslatorRegistrarTests.class); + } + + @Test + public void testCustomErrorCodeTranslation() { + + SQLErrorCodes codes = SQLErrorCodesFactory.getInstance().getErrorCodes("H2"); + SQLErrorCodeSQLExceptionTranslator sext = new SQLErrorCodeSQLExceptionTranslator(); + sext.setSqlErrorCodes(codes); + + DataAccessException exFor4200 = sext.doTranslate("", "", new SQLException("Ouch", "42000", 42000)); + assertNotNull("Should have been translated", exFor4200); + assertTrue("Should have been instance of BadSqlGrammarException", + BadSqlGrammarException.class.isAssignableFrom(exFor4200.getClass())); + + DataAccessException exFor2 = sext.doTranslate("", "", new SQLException("Ouch", "42000", 2)); + assertNotNull("Should have been translated", exFor2); + assertTrue("Should have been instance of TransientDataAccessResourceException", + TransientDataAccessResourceException.class.isAssignableFrom(exFor2.getClass())); + + DataAccessException exFor3 = sext.doTranslate("", "", new SQLException("Ouch", "42000", 3)); + assertNull("Should not have been translated", exFor3); + } + +} diff --git a/spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/init/db-test-data-select.sql b/spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/init/db-test-data-select.sql new file mode 100644 index 00000000000..a000cb1b96a --- /dev/null +++ b/spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/init/db-test-data-select.sql @@ -0,0 +1,3 @@ +insert into T_TEST (NAME) values ('Keith'); +insert into T_TEST (NAME) values ('Dave'); +select NAME from T_TEST where NAME = 'Keith'; diff --git a/spring-jdbc/src/test/resources/org/springframework/jdbc/support/test-custom-translators-context.xml b/spring-jdbc/src/test/resources/org/springframework/jdbc/support/test-custom-translators-context.xml new file mode 100644 index 00000000000..df3a6258dab --- /dev/null +++ b/spring-jdbc/src/test/resources/org/springframework/jdbc/support/test-custom-translators-context.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/package-info.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/package-info.java index b15057a340e..ce3b14842b7 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/package-info.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/package-info.java @@ -2,15 +2,15 @@ /** * * Package providing integration of - * Hibernate3 + * Hibernate 3.x * with Spring concepts. - * + * *

    Contains SessionFactory helper classes, a template plus callback * for Hibernate access, and an implementation of Spring's transaction SPI * for local Hibernate transactions. - * + * *

    This package supports Hibernate 3.x only. - * See the org.springframework.orm.hibernate package for Hibernate 2.1 support. + * See the org.springframework.orm.hibernate4 package for Hibernate 4.x support. * */ package org.springframework.orm.hibernate3; diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate4/package-info.java b/spring-orm/src/main/java/org/springframework/orm/hibernate4/package-info.java index 86f12a5b38f..ef73ff78f93 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate4/package-info.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate4/package-info.java @@ -2,15 +2,15 @@ /** * * Package providing integration of - * Hibernate 4.0 + * Hibernate 4.x * with Spring concepts. - * + * *

    Contains an implementation of Spring's transaction SPI for local Hibernate transactions. - * This package is intentionally rather minimal, relying on native Hibernate builder APIs - * for building a SessionFactory (for example in an @Bean method in a @Configuration class). + * This package is intentionally rather minimal, with no template classes or the like, + * in order to follow native Hibernate recommendations as closely as possible. * *

    This package supports Hibernate 4.x only. - * See the org.springframework.orm.hibernate3 package for Hibernate 3.x support. + * See the org.springframework.orm.hibernate3 package for Hibernate 3.x support. * */ package org.springframework.orm.hibernate4; diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java b/spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java index 9196b8dee4f..02963e2fda0 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -306,7 +306,7 @@ public abstract class EntityManagerFactoryUtils { return new JpaObjectRetrievalFailureException((EntityNotFoundException) ex); } if (ex instanceof NoResultException) { - return new EmptyResultDataAccessException(ex.getMessage(), 1); + return new EmptyResultDataAccessException(ex.getMessage(), 1, ex); } if (ex instanceof NonUniqueResultException) { return new IncorrectResultSizeDataAccessException(ex.getMessage(), 1, ex); diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.java b/spring-orm/src/main/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.java index 6774691c979..da80e700099 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -123,6 +123,17 @@ public class LocalContainerEntityManagerFactoryBean extends AbstractEntityManage this.internalPersistenceUnitManager.setPersistenceXmlLocation(persistenceXmlLocation); } + /** + * Uses the specified persistence unit name as the name of the default + * persistence unit, if applicable. + *

    NOTE: Only applied if no external PersistenceUnitManager specified. + */ + @Override + public void setPersistenceUnitName(String persistenceUnitName) { + super.setPersistenceUnitName(persistenceUnitName); + this.internalPersistenceUnitManager.setDefaultPersistenceUnitName(persistenceUnitName); + } + /** * Set whether to use Spring-based scanning for entity classes in the classpath * instead of using JPA's standard scanning of jar files with persistence.xml diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/DefaultPersistenceUnitManager.java b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/DefaultPersistenceUnitManager.java index 36d446b81e1..807b8ae2d7f 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/DefaultPersistenceUnitManager.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/DefaultPersistenceUnitManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -55,6 +55,7 @@ import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup; import org.springframework.jdbc.datasource.lookup.MapDataSourceLookup; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; +import org.springframework.util.ResourceUtils; /** * Default implementation of the {@link PersistenceUnitManager} interface. @@ -328,7 +329,7 @@ public class DefaultPersistenceUnitManager /** * Prepare the PersistenceUnitInfos according to the configuration * of this manager: scanning for persistence.xml files, - * parsing all matching files, configurating and post-processing them. + * parsing all matching files, configuring and post-processing them. *

    PersistenceUnitInfos cannot be obtained before this preparation * method has been invoked. * @see #obtainDefaultPersistenceUnitInfo() @@ -404,6 +405,12 @@ public class DefaultPersistenceUnitManager String className = reader.getClassMetadata().getClassName(); if (matchesFilter(reader, readerFactory)) { scannedUnit.addManagedClassName(className); + if (scannedUnit.getPersistenceUnitRootUrl() == null) { + URL url = resource.getURL(); + if (ResourceUtils.isJarURL(url)) { + scannedUnit.setPersistenceUnitRootUrl(ResourceUtils.extractJarFileURL(url)); + } + } } } } diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitReader.java b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitReader.java index f8d474ea18f..d2460ec94c1 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitReader.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2012 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. @@ -43,7 +43,7 @@ import org.springframework.util.xml.DomUtils; import org.springframework.util.xml.SimpleSaxErrorHandler; /** - * Internal helper class for reading persistence.xml files. + * Internal helper class for reading JPA-compliant persistence.xml files. * * @author Costin Leau * @author Juergen Hoeller @@ -227,7 +227,7 @@ class PersistenceUnitReader { /** * Parse the unit info DOM element. */ - protected SpringPersistenceUnitInfo parsePersistenceUnitInfo(Element persistenceUnit, String version) throws IOException { // JC: Changed + protected SpringPersistenceUnitInfo parsePersistenceUnitInfo(Element persistenceUnit, String version) throws IOException { SpringPersistenceUnitInfo unitInfo = new SpringPersistenceUnitInfo(); // set JPA version (1.0 or 2.0) diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/eclipselink/eclipselink-manager.xml b/spring-orm/src/test/java/org/springframework/orm/jpa/eclipselink/eclipselink-manager.xml index 625605e8278..a2e4f06ab81 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/eclipselink/eclipselink-manager.xml +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/eclipselink/eclipselink-manager.xml @@ -5,6 +5,10 @@ + diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DefaultProfileAnnotationConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DefaultProfileAnnotationConfigTests.java new file mode 100644 index 00000000000..dd0717bf105 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DefaultProfileAnnotationConfigTests.java @@ -0,0 +1,57 @@ +/* + * Copyright 2002-2012 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.test.context.junit4.profile.importresource; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.Employee; +import org.springframework.beans.Pet; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +import static org.junit.Assert.*; + +/** + * @author Juergen Hoeller + * @since 3.1 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = DefaultProfileConfig.class, loader = AnnotationConfigContextLoader.class) +public class DefaultProfileAnnotationConfigTests { + + @Autowired + protected Pet pet; + + @Autowired(required = false) + protected Employee employee; + + + @Test + public void pet() { + assertNotNull(pet); + assertEquals("Fido", pet.getName()); + } + + @Test + public void employee() { + assertNull("employee bean should not be created for the default profile", employee); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DefaultProfileConfig.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DefaultProfileConfig.java new file mode 100644 index 00000000000..a4af38435a4 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DefaultProfileConfig.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2012 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.test.context.junit4.profile.importresource; + +import org.springframework.beans.Pet; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ImportResource; + +/** + * @author Juergen Hoeller + * @since 3.1 + */ +@Configuration +@ImportResource("org/springframework/test/context/junit4/profile/importresource/import.xml") +public class DefaultProfileConfig { + + @Bean + public Pet pet() { + return new Pet("Fido"); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DevProfileAnnotationConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DevProfileAnnotationConfigTests.java new file mode 100644 index 00000000000..9e6ff731697 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DevProfileAnnotationConfigTests.java @@ -0,0 +1,39 @@ +/* + * Copyright 2002-2012 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.test.context.junit4.profile.importresource; + +import org.junit.Test; + +import org.springframework.test.context.ActiveProfiles; + +import static org.junit.Assert.*; + +/** + * @author Juergen Hoeller + * @since 3.1 + */ +@ActiveProfiles("dev") +public class DevProfileAnnotationConfigTests extends DefaultProfileAnnotationConfigTests { + + @Test + @Override + public void employee() { + assertNotNull("employee bean should be loaded for the 'dev' profile", employee); + assertEquals("John Smith", employee.getName()); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/import.xml b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/import.xml new file mode 100644 index 00000000000..8427a674a3d --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/import.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + diff --git a/spring-transaction/src/main/java/org/springframework/dao/QueryTimeoutException.java b/spring-transaction/src/main/java/org/springframework/dao/QueryTimeoutException.java new file mode 100644 index 00000000000..5df9cb3c3f5 --- /dev/null +++ b/spring-transaction/src/main/java/org/springframework/dao/QueryTimeoutException.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2012 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.dao; + +/** + * Exception to be thrown on a query timeout. This could have different causes depending on + * the database API in use but most likely thrown after the database interrupts or stops + * the processing of a query before it has completed. + * + *

    This exception can be thrown by user code trapping the native database exception or + * by exception translation. + * + * @author Thomas Risberg + * @since 3.1 + */ +public class QueryTimeoutException extends TransientDataAccessException { + + /** + * Constructor for QueryTimeoutException. + * @param msg the detail message + */ + public QueryTimeoutException(String msg) { + super(msg); + } + + /** + * Constructor for QueryTimeoutException. + * @param msg the detail message + * @param cause the root cause from the data access API in use + */ + public QueryTimeoutException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/spring-tx/src/main/java/org/springframework/dao/EmptyResultDataAccessException.java b/spring-tx/src/main/java/org/springframework/dao/EmptyResultDataAccessException.java index f10e84e3d98..dfbb86d54bc 100644 --- a/spring-tx/src/main/java/org/springframework/dao/EmptyResultDataAccessException.java +++ b/spring-tx/src/main/java/org/springframework/dao/EmptyResultDataAccessException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2006 the original author or authors. + * Copyright 2002-2012 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. @@ -43,4 +43,14 @@ public class EmptyResultDataAccessException extends IncorrectResultSizeDataAcces super(msg, expectedSize, 0); } + /** + * Constructor for EmptyResultDataAccessException. + * @param msg the detail message + * @param expectedSize the expected result size + * @param ex the wrapped exception + */ + public EmptyResultDataAccessException(String msg, int expectedSize, Throwable ex) { + super(msg, expectedSize, 0, ex); + } + } diff --git a/spring-tx/src/main/java/org/springframework/dao/IncorrectResultSizeDataAccessException.java b/spring-tx/src/main/java/org/springframework/dao/IncorrectResultSizeDataAccessException.java index f2af289b972..98f8379b332 100644 --- a/spring-tx/src/main/java/org/springframework/dao/IncorrectResultSizeDataAccessException.java +++ b/spring-tx/src/main/java/org/springframework/dao/IncorrectResultSizeDataAccessException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2006 the original author or authors. + * Copyright 2002-2012 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. @@ -68,8 +68,8 @@ public class IncorrectResultSizeDataAccessException extends DataRetrievalFailure /** * Constructor for IncorrectResultSizeDataAccessException. * @param msg the detail message - * @param ex the wrapped exception * @param expectedSize the expected result size + * @param ex the wrapped exception */ public IncorrectResultSizeDataAccessException(String msg, int expectedSize, Throwable ex) { super(msg, ex); @@ -89,19 +89,32 @@ public class IncorrectResultSizeDataAccessException extends DataRetrievalFailure this.actualSize = actualSize; } + /** + * Constructor for IncorrectResultSizeDataAccessException. + * @param msg the detail message + * @param expectedSize the expected result size + * @param actualSize the actual result size (or -1 if unknown) + * @param ex the wrapped exception + */ + public IncorrectResultSizeDataAccessException(String msg, int expectedSize, int actualSize, Throwable ex) { + super(msg, ex); + this.expectedSize = expectedSize; + this.actualSize = actualSize; + } + /** * Return the expected result size. */ public int getExpectedSize() { - return expectedSize; + return this.expectedSize; } /** * Return the actual result size (or -1 if unknown). */ public int getActualSize() { - return actualSize; + return this.actualSize; } } diff --git a/spring-tx/src/main/java/org/springframework/transaction/annotation/AbstractTransactionManagementConfiguration.java b/spring-tx/src/main/java/org/springframework/transaction/annotation/AbstractTransactionManagementConfiguration.java index 9ef8ad23f24..d5ea077155f 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/annotation/AbstractTransactionManagementConfiguration.java +++ b/spring-tx/src/main/java/org/springframework/transaction/annotation/AbstractTransactionManagementConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -17,11 +17,11 @@ package org.springframework.transaction.annotation; import java.util.Collection; -import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportAware; +import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.util.Assert; @@ -37,11 +37,12 @@ import org.springframework.util.Assert; @Configuration public abstract class AbstractTransactionManagementConfiguration implements ImportAware { - protected Map enableTx; + protected AnnotationAttributes enableTx; protected PlatformTransactionManager txManager; public void setImportMetadata(AnnotationMetadata importMetadata) { - this.enableTx = importMetadata.getAnnotationAttributes(EnableTransactionManagement.class.getName(), false); + this.enableTx = AnnotationAttributes.fromMap( + importMetadata.getAnnotationAttributes(EnableTransactionManagement.class.getName(), false)); Assert.notNull(this.enableTx, "@EnableTransactionManagement is not present on importing class " + importMetadata.getClassName()); diff --git a/spring-tx/src/main/java/org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.java b/spring-tx/src/main/java/org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.java index 48a6436ace1..c1602479b4e 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.java +++ b/spring-tx/src/main/java/org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -44,7 +44,7 @@ public class ProxyTransactionManagementConfiguration extends AbstractTransaction new BeanFactoryTransactionAttributeSourceAdvisor(); advisor.setTransactionAttributeSource(transactionAttributeSource()); advisor.setAdvice(transactionInterceptor()); - advisor.setOrder(((Integer)this.enableTx.get("order"))); + advisor.setOrder(this.enableTx.getNumber("order")); return advisor; } diff --git a/spring-web/src/main/java/org/springframework/http/MediaType.java b/spring-web/src/main/java/org/springframework/http/MediaType.java index d467e1feaeb..6d0f75fe4f5 100644 --- a/spring-web/src/main/java/org/springframework/http/MediaType.java +++ b/spring-web/src/main/java/org/springframework/http/MediaType.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -450,6 +450,14 @@ public class MediaType implements Comparable { return this.parameters.get(name); } + /** + * Return all generic parameter values. + * @return a read-only map, possibly empty, never null + */ + public Map getParameters() { + return parameters; + } + /** * Indicate whether this {@code MediaType} includes the given media type. *

    For instance, {@code text/*} includes {@code text/plain} and {@code text/html}, and {@code application/*+xml} diff --git a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java index e93367b6515..5dd0dbd4249 100644 --- a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -25,15 +25,19 @@ import java.io.Writer; import java.net.URI; import java.net.URISyntaxException; import java.net.URLEncoder; +import java.nio.charset.Charset; import java.util.Arrays; import java.util.Enumeration; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; + import javax.servlet.http.HttpServletRequest; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; import org.springframework.util.Assert; /** @@ -90,14 +94,31 @@ public class ServletServerHttpRequest implements ServerHttpRequest { public HttpHeaders getHeaders() { if (this.headers == null) { this.headers = new HttpHeaders(); - for (Enumeration headerNames = this.servletRequest.getHeaderNames(); headerNames.hasMoreElements();) { + for (Enumeration headerNames = this.servletRequest.getHeaderNames(); headerNames.hasMoreElements();) { String headerName = (String) headerNames.nextElement(); - for (Enumeration headerValues = this.servletRequest.getHeaders(headerName); + for (Enumeration headerValues = this.servletRequest.getHeaders(headerName); headerValues.hasMoreElements();) { String headerValue = (String) headerValues.nextElement(); this.headers.add(headerName, headerValue); } } + // HttpServletRequest exposes some headers as properties: we should include those if not already present + if (this.headers.getContentType() == null && this.servletRequest.getContentType() != null) { + MediaType contentType = MediaType.parseMediaType(this.servletRequest.getContentType()); + this.headers.setContentType(contentType); + } + if (this.headers.getContentType() != null && this.headers.getContentType().getCharSet() == null && + this.servletRequest.getCharacterEncoding() != null) { + MediaType oldContentType = this.headers.getContentType(); + Charset charSet = Charset.forName(this.servletRequest.getCharacterEncoding()); + Map params = new HashMap(oldContentType.getParameters()); + params.put("charset", charSet.toString()); + MediaType newContentType = new MediaType(oldContentType.getType(), oldContentType.getSubtype(), params); + this.headers.setContentType(newContentType); + } + if (this.headers.getContentLength() == -1 && this.servletRequest.getContentLength() != -1) { + this.headers.setContentLength(this.servletRequest.getContentLength()); + } } return this.headers; } diff --git a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java index 9752c4259d8..985085e51e9 100644 --- a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -83,6 +83,14 @@ public class ServletServerHttpResponse implements ServerHttpResponse { this.servletResponse.addHeader(headerName, headerValue); } } + // HttpServletResponse exposes some headers as properties: we should include those if not already present + if (this.servletResponse.getContentType() == null && this.headers.getContentType() != null) { + this.servletResponse.setContentType(this.headers.getContentType().toString()); + } + if (this.servletResponse.getCharacterEncoding() == null && this.headers.getContentType() != null && + this.headers.getContentType().getCharSet() != null) { + this.servletResponse.setCharacterEncoding(this.headers.getContentType().getCharSet().name()); + } this.headersWritten = true; } } diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java index 7a4ff06b2e5..00ad724649d 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java +++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -24,17 +24,18 @@ import java.lang.annotation.Target; /** * Annotation for mapping web requests onto specific handler classes and/or - * handler methods. Provides consistent style between Servlet and Portlet + * handler methods. Provides a consistent style between Servlet and Portlet * environments, with the semantics adapting to the concrete environment. * - *

    NOTE: Method-level mappings are only allowed to narrow the mapping - * expressed at the class level (if any). In the Servlet case, an HTTP path needs to - * uniquely map onto one specific handler bean (not spread across multiple handler beans); - * the remaining mapping parameters and conditions are effectively assertions only. - * In the Portlet case, a portlet mode in combination with specific parameter conditions - * needs to uniquely map onto one specific handler bean, with all conditions evaluated - * for mapping purposes. It is strongly recommended to co-locate related handler methods - * into the same bean and therefore keep the mappings simple and intuitive. + *

    NOTE: The set of features supported for Servlets is a superset + * of the set of features supported for Portlets. The places where this applies + * are marked with the label "Servlet-only" in this source file. For Servlet + * environments there are some further distinctions depending on whether an + * application is configured with {@literal "@MVC 3.0"} or + * {@literal "@MVC 3.1"} support classes. The places where this applies are + * marked with {@literal "@MVC 3.1-only"} in this source file. For more + * details see the note on the new support classes added in Spring MVC 3.1 + * further below. * *

    Handler methods which are annotated with this annotation are allowed * to have very flexible signatures. They may have arguments of the following @@ -71,8 +72,8 @@ import java.lang.annotation.Target; *

  1. {@link java.io.OutputStream} / {@link java.io.Writer} for generating * the response's content. This will be the raw OutputStream/Writer as * exposed by the Servlet/Portlet API. - *
  2. {@link PathVariable @PathVariable} annotated parameters for access to - * URI template values (i.e. /hotels/{hotel}). Variable values will be + *
  3. {@link PathVariable @PathVariable} annotated parameters (Servlet-only) + * for access to URI template values (i.e. /hotels/{hotel}). Variable values will be * converted to the declared method argument type. By default, the URI template * will match against the regular expression {@code [^\.]*} (i.e. any character * other than period), but this can be changed by specifying another regular @@ -90,30 +91,43 @@ import java.lang.annotation.Target; * {@link org.springframework.util.MultiValueMap MultiValueMap<String, String>}, or * {@link org.springframework.http.HttpHeaders HttpHeaders} method parameter to * gain access to all request headers. - *
  4. {@link RequestBody @RequestBody} annotated parameters for access to - * the Servlet request HTTP contents. The request stream will be - * converted to the declared method argument type using + *
  5. {@link RequestBody @RequestBody} annotated parameters (Servlet-only) + * for access to the Servlet request HTTP contents. The request stream will be + * converted to the declared method argument type using * {@linkplain org.springframework.http.converter.HttpMessageConverter message - * converters}. Such parameters may optionally be annotated with {@code @Valid}. - *
  6. {@link RequestPart @RequestPart} annotated parameters for access to the content + * converters}. Such parameters may optionally be annotated with {@code @Valid} + * but do not support access to validation results through a + * {@link org.springframework.validation.Errors} / + * {@link org.springframework.validation.BindingResult} argument. + * Instead a {@link org.springframework.web.servlet.mvc.method.annotation.MethodArgumentNotValidException} + * exception is raised. + *
  7. {@link RequestPart @RequestPart} annotated parameters + * (Servlet-only, {@literal @MVC 3.1-only}) + * for access to the content * of a part of "multipart/form-data" request. The request part stream will be - * converted to the declared method argument type using + * converted to the declared method argument type using * {@linkplain org.springframework.http.converter.HttpMessageConverter message - * converters}. Such parameters may optionally be annotated with {@code @Valid}. + * converters}. Such parameters may optionally be annotated with {@code @Valid} + * but do not support access to validation results through a + * {@link org.springframework.validation.Errors} / + * {@link org.springframework.validation.BindingResult} argument. + * Instead a {@link org.springframework.web.servlet.mvc.method.annotation.MethodArgumentNotValidException} + * exception is raised. *
  8. {@link org.springframework.http.HttpEntity HttpEntity<?>} parameters - * for access to the Servlet request HTTP headers and contents. The request stream will be - * converted to the entity body using + * (Servlet-only) for access to the Servlet request HTTP headers and contents. + * The request stream will be converted to the entity body using * {@linkplain org.springframework.http.converter.HttpMessageConverter message * converters}. *
  9. {@link java.util.Map} / {@link org.springframework.ui.Model} / * {@link org.springframework.ui.ModelMap} for enriching the implicit model * that will be exposed to the web view. - *
  10. {@link org.springframework.web.servlet.mvc.support.RedirectAttributes} - * to specify the exact set of attributes to use in case of a redirect - * and also to add flash attributes (attributes stored temporarily on the - * server-side to make them available to the request after the redirect). - * {@code RedirectAttributes} is used instead of the implicit model if the - * method returns a "redirect:" prefixed view name or {@code RedirectView}. + *
  11. {@link org.springframework.web.servlet.mvc.support.RedirectAttributes} + * (Servlet-only, {@literal @MVC 3.1-only}) to specify the exact set of attributes + * to use in case of a redirect and also to add flash attributes (attributes + * stored temporarily on the server-side to make them available to the request + * after the redirect). {@code RedirectAttributes} is used instead of the + * implicit model if the method returns a "redirect:" prefixed view name or + * {@code RedirectView}. *
  12. Command/form objects to bind parameters to: as bean properties or fields, * with customizable type conversion, depending on {@link InitBinder} methods * and/or the HandlerAdapter configuration - see the "webBindingInitializer" @@ -130,9 +144,10 @@ import java.lang.annotation.Target; * for marking form processing as complete (triggering the cleanup of session * attributes that have been indicated by the {@link SessionAttributes} annotation * at the handler type level). - *
  13. {@link org.springframework.web.util.UriComponentsBuilder} a builder for - * preparing a URL relative to the current request's host, port, scheme, context - * path, and the literal part of the servlet mapping. + *
  14. {@link org.springframework.web.util.UriComponentsBuilder} + * (Servlet-only, {@literal @MVC 3.1-only}) + * for preparing a URL relative to the current request's host, port, scheme, + * context path, and the literal part of the servlet mapping. * * *

    The following return types are supported for handler methods: @@ -160,15 +175,15 @@ import java.lang.annotation.Target; * The handler method may also programmatically enrich the model by * declaring a {@link org.springframework.ui.ModelMap} argument * (see above). - *

  15. {@link ResponseBody @ResponseBody} annotated methods for access to - * the Servlet response HTTP contents. The return value will be converted - * to the response stream using + *
  16. {@link ResponseBody @ResponseBody} annotated methods (Servlet-only) + * for access to the Servlet response HTTP contents. The return value will + * be converted to the response stream using * {@linkplain org.springframework.http.converter.HttpMessageConverter message * converters}. *
  17. A {@link org.springframework.http.HttpEntity HttpEntity<?>} or * {@link org.springframework.http.ResponseEntity ResponseEntity<?>} object - * to access to the Servlet response HTTP headers and contents. The entity body will - * be converted to the response stream using + * (Servlet-only) to access to the Servlet response HTTP headers and contents. + * The entity body will be converted to the response stream using * {@linkplain org.springframework.http.converter.HttpMessageConverter message * converters}. *
  18. void if the method handles the response itself (by @@ -187,16 +202,24 @@ import java.lang.annotation.Target; * {@link ModelAttribute} annotated reference data accessor methods. * * - *

    NOTE: @RequestMapping will only be processed if a - * corresponding HandlerMapping (for type level annotations) - * and/or HandlerAdapter (for method level annotations) is - * present in the dispatcher. This is the case by default in both - * DispatcherServlet and DispatcherPortlet. + *

    NOTE: @RequestMapping will only be processed if an + * an appropriate HandlerMapping-HandlerAdapter pair + * is configured. This is the case by default in both the + * DispatcherServlet and the DispatcherPortlet. * However, if you are defining custom HandlerMappings or - * HandlerAdapters, then you need to make sure that a - * corresponding custom RequestMappingHandlerMethodMapping - * and/or RequestMappingHandlerMethodAdapter is defined as well - * - provided that you intend to use @RequestMapping. + * HandlerAdapters, then you need to add + * DefaultAnnotationHandlerMapping and + * AnnotationMethodHandlerAdapter to your configuration.. + * + *

    NOTE: Spring 3.1 introduced a new set of support classes for + * @RequestMapping methods in Servlet environments called + * RequestMappingHandlerMapping and + * RequestMappingHandlerAdapter. They are recommended for use and + * even required to take advantage of new features in Spring MVC 3.1 (search + * {@literal "@MVC 3.1-only"} in this source file) and going forward. + * The new support classes are enabled by default from the MVC namespace and + * with use of the MVC Java config (@EnableWebMvc) but must be + * configured explicitly if using neither. * *

    NOTE: When using controller interfaces (e.g. for AOP proxying), * make sure to consistently put all your mapping annotations - such as @@ -234,20 +257,6 @@ public @interface RequestMapping { *

    Supported at the type level as well as at the method level! * When used at the type level, all method-level mappings inherit * this primary mapping, narrowing it for a specific handler method. - *

    In case of Servlet-based handler methods, the method names are - * taken into account for narrowing if no path was specified explicitly, - * according to the specified - * {@link org.springframework.web.servlet.mvc.multiaction.MethodNameResolver} - * (by default an - * {@link org.springframework.web.servlet.mvc.multiaction.InternalPathMethodNameResolver}). - * Note that this only applies in case of ambiguous annotation mappings - * that do not specify a path mapping explicitly. In other words, - * the method name is only used for narrowing among a set of matching - * methods; it does not constitute a primary path mapping itself. - *

    If you have a single default method (without explicit path mapping), - * then all requests without a more specific mapped method found will - * be dispatched to it. If you have multiple such default methods, then - * the method name will be taken into account for choosing between them. */ String[] value() default {}; @@ -309,7 +318,7 @@ public @interface RequestMapping { * and against PortletRequest properties in a Portlet 2.0 environment. * @see org.springframework.http.MediaType */ - String[] headers() default {}; + String[] headers() default {}; /** * The consumable media types of the mapped request, narrowing the primary mapping. diff --git a/spring-web/src/main/java/org/springframework/web/context/ContextLoader.java b/spring-web/src/main/java/org/springframework/web/context/ContextLoader.java index 1f32562deb9..79c3d8744ca 100644 --- a/spring-web/src/main/java/org/springframework/web/context/ContextLoader.java +++ b/spring-web/src/main/java/org/springframework/web/context/ContextLoader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -23,11 +23,11 @@ import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; + import javax.servlet.ServletContext; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.access.BeanFactoryLocator; import org.springframework.beans.factory.access.BeanFactoryReference; @@ -44,6 +44,7 @@ import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; +import org.springframework.web.context.support.WebApplicationContextUtils; /** * Performs the actual initialization work for the root application context. @@ -463,11 +464,18 @@ public class ContextLoader { * @see ApplicationContextInitializer#initialize(ConfigurableApplicationContext) */ protected void customizeContext(ServletContext servletContext, ConfigurableWebApplicationContext applicationContext) { + List>> initializerClasses = + determineContextInitializerClasses(servletContext); + + if (initializerClasses.size() == 0) { + // no ApplicationContextInitializers have been declared -> nothing to do + return; + } + ArrayList> initializerInstances = new ArrayList>(); - for (Class> initializerClass : - determineContextInitializerClasses(servletContext)) { + for (Class> initializerClass : initializerClasses) { Class contextClass = applicationContext.getClass(); Class initializerContextClass = GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class); @@ -480,6 +488,13 @@ public class ContextLoader { Collections.sort(initializerInstances, new AnnotationAwareOrderComparator()); + // eagerly attempt to initialize servlet property sources in case initializers + // below depend on accessing context-params via the Environment API. Note that + // depending on application context implementation, this initialization will be + // attempted again during context refresh. + WebApplicationContextUtils.initServletPropertySources( + applicationContext.getEnvironment().getPropertySources(), servletContext); + for (ApplicationContextInitializer initializer : initializerInstances) { initializer.initialize(applicationContext); } diff --git a/spring-web/src/main/java/org/springframework/web/context/support/ServletContextResource.java b/spring-web/src/main/java/org/springframework/web/context/support/ServletContextResource.java index 99a028b2dc2..af73e49a436 100644 --- a/spring-web/src/main/java/org/springframework/web/context/support/ServletContextResource.java +++ b/spring-web/src/main/java/org/springframework/web/context/support/ServletContextResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -107,6 +107,28 @@ public class ServletContextResource extends AbstractFileResolvingResource implem } } + /** + * This implementation delegates to ServletContext.getResourceAsStream, + * which returns null in case of a non-readable resource (e.g. a directory). + * @see javax.servlet.ServletContext#getResourceAsStream(String) + */ + @Override + public boolean isReadable() { + InputStream is = this.servletContext.getResourceAsStream(this.path); + if (is != null) { + try { + is.close(); + } + catch (IOException ex) { + // ignore + } + return true; + } + else { + return false; + } + } + /** * This implementation delegates to ServletContext.getResourceAsStream, * but throws a FileNotFoundException if no resource found. diff --git a/spring-web/src/main/java/org/springframework/web/context/support/WebApplicationContextUtils.java b/spring-web/src/main/java/org/springframework/web/context/support/WebApplicationContextUtils.java index eb607d1c318..9b9426d112f 100644 --- a/spring-web/src/main/java/org/springframework/web/context/support/WebApplicationContextUtils.java +++ b/spring-web/src/main/java/org/springframework/web/context/support/WebApplicationContextUtils.java @@ -16,6 +16,9 @@ package org.springframework.web.context.support; +import static org.springframework.web.context.support.StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME; +import static org.springframework.web.context.support.StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME; + import java.io.Serializable; import java.util.Collections; import java.util.Enumeration; @@ -32,6 +35,7 @@ import javax.servlet.http.HttpSession; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.PropertySource.StubPropertySource; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.web.context.ConfigurableWebApplicationContext; @@ -247,13 +251,15 @@ public abstract class WebApplicationContextUtils { public static void initServletPropertySources( MutablePropertySources propertySources, ServletContext servletContext, ServletConfig servletConfig) { Assert.notNull(propertySources, "propertySources must not be null"); - if(servletContext != null && propertySources.contains(StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME)) { - propertySources.replace(StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME, - new ServletContextPropertySource(StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME, servletContext)); + if(servletContext != null && + propertySources.contains(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME) && + propertySources.get(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME) instanceof StubPropertySource) { + propertySources.replace(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME, new ServletContextPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME, servletContext)); } - if(servletConfig != null && propertySources.contains(StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME)) { - propertySources.replace(StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME, - new ServletConfigPropertySource(StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME, servletConfig)); + if(servletConfig != null && + propertySources.contains(SERVLET_CONFIG_PROPERTY_SOURCE_NAME) && + propertySources.get(SERVLET_CONFIG_PROPERTY_SOURCE_NAME) instanceof StubPropertySource) { + propertySources.replace(SERVLET_CONFIG_PROPERTY_SOURCE_NAME, new ServletConfigPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME, servletConfig)); } } diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolver.java b/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolver.java index c659d699d29..0fb76c9bfb4 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolver.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolver.java @@ -179,17 +179,10 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod } private void assertIsMultipartRequest(HttpServletRequest request) { - if (!isMultipartRequest(request)) { - throw new MultipartException("The current request is not a multipart request."); - } - } - - private boolean isMultipartRequest(HttpServletRequest request) { - if (!"post".equals(request.getMethod().toLowerCase())) { - return false; - } String contentType = request.getContentType(); - return (contentType != null && contentType.toLowerCase().startsWith("multipart/")); + if (contentType == null || !contentType.toLowerCase().startsWith("multipart/")) { + throw new MultipartException("The current request is not a multipart request"); + } } private boolean isMultipartFileCollection(MethodParameter parameter) { diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/SessionAttributesHandler.java b/spring-web/src/main/java/org/springframework/web/method/annotation/SessionAttributesHandler.java index fd7b4e42cf6..5e6080db13e 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/SessionAttributesHandler.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/SessionAttributesHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -31,16 +31,16 @@ import org.springframework.web.bind.support.SessionStatus; import org.springframework.web.context.request.WebRequest; /** - * Manages controller-specific session attributes declared via - * {@link SessionAttributes @SessionAttributes}. Actual storage is - * performed via {@link SessionAttributeStore}. - * - *

    When a controller annotated with {@code @SessionAttributes} adds - * attributes to its model, those attributes are checked against names and - * types specified via {@code @SessionAttributes}. Matching model attributes - * are saved in the HTTP session and remain there until the controller calls + * Manages controller-specific session attributes declared via + * {@link SessionAttributes @SessionAttributes}. Actual storage is + * delegated to a {@link SessionAttributeStore} instance. + * + *

    When a controller annotated with {@code @SessionAttributes} adds + * attributes to its model, those attributes are checked against names and + * types specified via {@code @SessionAttributes}. Matching model attributes + * are saved in the HTTP session and remain there until the controller calls * {@link SessionStatus#setComplete()}. - * + * * @author Rossen Stoyanchev * @since 3.1 */ @@ -50,51 +50,53 @@ public class SessionAttributesHandler { private final Set> attributeTypes = new HashSet>(); - private final Set resolvedAttributeNames = Collections.synchronizedSet(new HashSet(4)); + private final Set knownAttributeNames = Collections.synchronizedSet(new HashSet(4)); private final SessionAttributeStore sessionAttributeStore; /** - * Creates a new instance for a controller type. Session attribute names/types - * are extracted from a type-level {@code @SessionAttributes} if found. + * Create a new instance for a controller type. Session attribute names and + * types are extracted from the {@code @SessionAttributes} annotation, if + * present, on the given type. * @param handlerType the controller type * @param sessionAttributeStore used for session access */ public SessionAttributesHandler(Class handlerType, SessionAttributeStore sessionAttributeStore) { Assert.notNull(sessionAttributeStore, "SessionAttributeStore may not be null."); this.sessionAttributeStore = sessionAttributeStore; - + SessionAttributes annotation = AnnotationUtils.findAnnotation(handlerType, SessionAttributes.class); if (annotation != null) { - this.attributeNames.addAll(Arrays.asList(annotation.value())); + this.attributeNames.addAll(Arrays.asList(annotation.value())); this.attributeTypes.addAll(Arrays.>asList(annotation.types())); - } + } + + this.knownAttributeNames.addAll(this.attributeNames); } /** - * Whether the controller represented by this instance has declared session - * attribute names or types of interest via {@link SessionAttributes}. + * Whether the controller represented by this instance has declared any + * session attributes through an {@link SessionAttributes} annotation. */ public boolean hasSessionAttributes() { - return ((this.attributeNames.size() > 0) || (this.attributeTypes.size() > 0)); + return ((this.attributeNames.size() > 0) || (this.attributeTypes.size() > 0)); } - + /** - * Whether the attribute name and/or type match those specified in the - * controller's {@code @SessionAttributes} annotation. - * + * Whether the attribute name or type match the names and types specified + * via {@code @SessionAttributes} in underlying controller. + * *

    Attributes successfully resolved through this method are "remembered" - * and used in {@link #retrieveAttributes(WebRequest)} and - * {@link #cleanupAttributes(WebRequest)}. In other words, retrieval and - * cleanup only affect attributes previously resolved through here. - * - * @param attributeName the attribute name to check; must not be null - * @param attributeType the type for the attribute; or {@code null} + * and subsequently used in {@link #retrieveAttributes(WebRequest)} and + * {@link #cleanupAttributes(WebRequest)}. + * + * @param attributeName the attribute name to check, never {@code null} + * @param attributeType the type for the attribute, possibly {@code null} */ public boolean isHandlerSessionAttribute(String attributeName, Class attributeType) { Assert.notNull(attributeName, "Attribute name must not be null"); if (this.attributeNames.contains(attributeName) || this.attributeTypes.contains(attributeType)) { - this.resolvedAttributeNames.add(attributeName); + this.knownAttributeNames.add(attributeName); return true; } else { @@ -103,8 +105,8 @@ public class SessionAttributesHandler { } /** - * Stores a subset of the given attributes in the session. Attributes not - * declared as session attributes via {@code @SessionAttributes} are ignored. + * Store a subset of the given attributes in the session. Attributes not + * declared as session attributes via {@code @SessionAttributes} are ignored. * @param request the current request * @param attributes candidate attributes for session storage */ @@ -112,23 +114,23 @@ public class SessionAttributesHandler { for (String name : attributes.keySet()) { Object value = attributes.get(name); Class attrType = (value != null) ? value.getClass() : null; - + if (isHandlerSessionAttribute(name, attrType)) { this.sessionAttributeStore.storeAttribute(request, name, value); } } } - + /** - * Retrieve "known" attributes from the session -- i.e. attributes listed - * in {@code @SessionAttributes} and previously stored in the in the model - * at least once. + * Retrieve "known" attributes from the session, i.e. attributes listed + * by name in {@code @SessionAttributes} or attributes previously stored + * in the model that matched by type. * @param request the current request - * @return a map with handler session attributes; possibly empty. + * @return a map with handler session attributes, possibly empty */ public Map retrieveAttributes(WebRequest request) { Map attributes = new HashMap(); - for (String name : this.resolvedAttributeNames) { + for (String name : this.knownAttributeNames) { Object value = this.sessionAttributeStore.retrieveAttribute(request, name); if (value != null) { attributes.put(name, value); @@ -138,13 +140,13 @@ public class SessionAttributesHandler { } /** - * Cleans "known" attributes from the session - i.e. attributes listed - * in {@code @SessionAttributes} and previously stored in the in the model - * at least once. + * Remove "known" attributes from the session, i.e. attributes listed + * by name in {@code @SessionAttributes} or attributes previously stored + * in the model that matched by type. * @param request the current request */ public void cleanupAttributes(WebRequest request) { - for (String attributeName : this.resolvedAttributeNames) { + for (String attributeName : this.knownAttributeNames) { this.sessionAttributeStore.cleanupAttribute(request, attributeName); } } @@ -158,5 +160,5 @@ public class SessionAttributesHandler { Object retrieveAttribute(WebRequest request, String attributeName) { return this.sessionAttributeStore.retrieveAttribute(request, attributeName); } - + } \ No newline at end of file diff --git a/spring-web/src/main/java/org/springframework/web/util/UriComponents.java b/spring-web/src/main/java/org/springframework/web/util/UriComponents.java index a5b4ec74b9c..54cbcef437e 100644 --- a/spring-web/src/main/java/org/springframework/web/util/UriComponents.java +++ b/spring-web/src/main/java/org/springframework/web/util/UriComponents.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -286,7 +286,7 @@ public final class UriComponents { if (source == null) { return null; } - + Assert.hasLength(encoding, "'encoding' must not be empty"); byte[] bytes = encodeBytes(source.getBytes(encoding), type); @@ -406,7 +406,7 @@ public final class UriComponents { private UriComponents expandInternal(UriTemplateVariables uriVariables) { Assert.state(!encoded, "Cannot expand an already encoded UriComponents object"); - + String expandedScheme = expandUriComponent(this.scheme, uriVariables); String expandedUserInfo = expandUriComponent(this.userInfo, uriVariables); String expandedHost = expandUriComponent(this.host, uriVariables); @@ -458,6 +458,16 @@ public final class UriComponents { return variableValue != null ? variableValue.toString() : ""; } + /** + * Normalize the path removing sequences like "path/..". + * @see StringUtils#cleanPath(String) + */ + public UriComponents normalize() { + String normalizedPath = StringUtils.cleanPath(getPath()); + return new UriComponents(scheme, userInfo, host, this.port, new FullPathComponent(normalizedPath), + queryParams, fragment, encoded, false); + } + // other functionality /** @@ -930,7 +940,7 @@ public final class UriComponents { } } - + /** * Represents an empty path. @@ -992,6 +1002,9 @@ public final class UriComponents { } public Object getValue(String name) { + if (!this.uriVariables.containsKey(name)) { + throw new IllegalArgumentException("Map has no value for '" + name + "'"); + } return this.uriVariables.get(name); } } @@ -1000,6 +1013,7 @@ public final class UriComponents { * URI template variables backed by a variable argument array. */ private static class VarArgsTemplateVariables implements UriTemplateVariables { + private final Iterator valueIterator; public VarArgsTemplateVariables(Object... uriVariableValues) { @@ -1008,7 +1022,7 @@ public final class UriComponents { public Object getValue(String name) { if (!valueIterator.hasNext()) { - throw new IllegalArgumentException("Not enough variable values available to expand [" + name + "]"); + throw new IllegalArgumentException("Not enough variable values available to expand '" + name + "'"); } return valueIterator.next(); } diff --git a/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java b/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java index f4ed1f252e1..c95d620256e 100644 --- a/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java +++ b/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -18,7 +18,7 @@ package org.springframework.web.util; import java.net.URI; import java.util.ArrayList; -import java.util.Collections; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.regex.Matcher; @@ -80,7 +80,7 @@ public class UriComponentsBuilder { "^" + HTTP_PATTERN + "(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN + ")?" + ")?" + PATH_PATTERN + "(\\?" + LAST_PATTERN + ")?"); - + private String scheme; private String userInfo; @@ -223,10 +223,10 @@ public class UriComponentsBuilder { } /** - * Builds a {@code UriComponents} instance and replaces URI template variables - * with the values from a map. This is a shortcut method, which combines - * calls to {@link #build()} and then {@link UriComponents#expand(Map)}. - * + * Builds a {@code UriComponents} instance and replaces URI template variables + * with the values from a map. This is a shortcut method, which combines + * calls to {@link #build()} and then {@link UriComponents#expand(Map)}. + * * @param uriVariables the map of URI variables * @return the URI components with expanded values */ @@ -235,17 +235,17 @@ public class UriComponentsBuilder { } /** - * Builds a {@code UriComponents} instance and replaces URI template variables - * with the values from an array. This is a shortcut method, which combines - * calls to {@link #build()} and then {@link UriComponents#expand(Object...)}. - * + * Builds a {@code UriComponents} instance and replaces URI template variables + * with the values from an array. This is a shortcut method, which combines + * calls to {@link #build()} and then {@link UriComponents#expand(Object...)}. + * * @param uriVariableValues URI variable values * @return the URI components with expanded values */ public UriComponents buildAndExpand(Object... uriVariableValues) { return build(false).expand(uriVariableValues); } - + // URI components methods /** @@ -347,8 +347,8 @@ public class UriComponentsBuilder { } /** - * Sets the path of this builder overriding all existing path and path segment values. - * + * Sets the path of this builder overriding all existing path and path segment values. + * * @param path the URI path; a {@code null} value results in an empty path. * @return this UriComponentsBuilder */ @@ -394,7 +394,7 @@ public class UriComponentsBuilder { /** * Sets the query of this builder overriding all existing query parameters. - * + * * @param query the query string; a {@code null} value removes all query parameters. * @return this UriComponentsBuilder */ @@ -430,7 +430,7 @@ public class UriComponentsBuilder { /** * Sets the query parameter values overriding all existing query values for the same parameter. * If no values are given, the query parameter is removed. - * + * * @param name the query parameter name * @param values the query parameter values * @return this UriComponentsBuilder @@ -443,7 +443,7 @@ public class UriComponentsBuilder { } return this; } - + /** * Sets the URI fragment. The given fragment may contain URI template variables, and may also be {@code null} to clear * the fragment of this builder. @@ -509,7 +509,17 @@ public class UriComponentsBuilder { private final List pathSegments = new ArrayList(); private PathSegmentComponentBuilder(String... pathSegments) { - Collections.addAll(this.pathSegments, pathSegments); + this.pathSegments.addAll(removeEmptyPathSegments(pathSegments)); + } + + private Collection removeEmptyPathSegments(String... pathSegments) { + List result = new ArrayList(); + for (String segment : pathSegments) { + if (StringUtils.hasText(segment)) { + result.add(segment); + } + } + return result; } public UriComponents.PathComponent build() { @@ -523,7 +533,7 @@ public class UriComponentsBuilder { } public PathComponentBuilder appendPathSegments(String... pathSegments) { - Collections.addAll(this.pathSegments, pathSegments); + this.pathSegments.addAll(removeEmptyPathSegments(pathSegments)); return this; } } diff --git a/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpRequestTests.java b/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpRequestTests.java index 30fd57b6b79..1a2ec75e574 100644 --- a/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpRequestTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpRequestTests.java @@ -1,11 +1,11 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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 + * 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, @@ -17,6 +17,7 @@ package org.springframework.http.server; import java.net.URI; +import java.nio.charset.Charset; import java.util.List; import org.junit.Before; @@ -24,6 +25,7 @@ import org.junit.Test; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.util.FileCopyUtils; @@ -67,6 +69,8 @@ public class ServletServerHttpRequestTests { mockRequest.addHeader(headerName, headerValue1); String headerValue2 = "value2"; mockRequest.addHeader(headerName, headerValue2); + mockRequest.setContentType("text/plain"); + mockRequest.setCharacterEncoding("UTF-8"); HttpHeaders headers = request.getHeaders(); assertNotNull("No HttpHeaders returned", headers); @@ -75,6 +79,8 @@ public class ServletServerHttpRequestTests { assertEquals("Invalid header values returned", 2, headerValues.size()); assertTrue("Invalid header values returned", headerValues.contains(headerValue1)); assertTrue("Invalid header values returned", headerValues.contains(headerValue2)); + assertEquals("Invalid Content-Type", new MediaType("text", "plain", Charset.forName("UTF-8")), + headers.getContentType()); } @Test diff --git a/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpResponseTests.java b/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpResponseTests.java index bad9ccdb840..fd9392f108d 100644 --- a/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpResponseTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpResponseTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 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. @@ -16,16 +16,19 @@ package org.springframework.http.server; +import java.nio.charset.Charset; import java.util.List; -import static org.junit.Assert.*; import org.junit.Before; import org.junit.Test; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.util.FileCopyUtils; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.util.FileCopyUtils; + +import static org.junit.Assert.*; /** * @author Arjen Poutsma @@ -56,12 +59,16 @@ public class ServletServerHttpResponseTests { headers.add(headerName, headerValue1); String headerValue2 = "value2"; headers.add(headerName, headerValue2); + headers.setContentType(new MediaType("text", "plain", Charset.forName("UTF-8"))); response.close(); assertTrue("Header not set", mockResponse.getHeaderNames().contains(headerName)); List headerValues = mockResponse.getHeaders(headerName); assertTrue("Header not set", headerValues.contains(headerValue1)); assertTrue("Header not set", headerValues.contains(headerValue2)); + assertEquals("Invalid Content-Type", "text/plain;charset=UTF-8", mockResponse.getHeader("Content-Type")); + assertEquals("Invalid Content-Type", "text/plain;charset=UTF-8", mockResponse.getContentType()); + assertEquals("Invalid Content-Type", "UTF-8", mockResponse.getCharacterEncoding()); } @Test diff --git a/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolverTests.java b/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolverTests.java index 38af0e20a12..fe4099c6811 100644 --- a/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -45,7 +45,6 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.ServletWebRequest; -import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver; import org.springframework.web.multipart.MultipartException; import org.springframework.web.multipart.MultipartFile; @@ -183,11 +182,27 @@ public class RequestParamMethodArgumentResolverTests { } @Test(expected = MultipartException.class) - public void notMultipartRequest() throws Exception { + public void isMultipartRequest() throws Exception { resolver.resolveArgument(paramMultiPartFile, null, webRequest, null); fail("Expected exception: request is not a multipart request"); } + // SPR-9079 + + @Test + public void isMultipartRequestHttpPut() throws Exception { + MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest(); + MultipartFile expected = new MockMultipartFile("multipartFileList", "Hello World".getBytes()); + request.addFile(expected); + request.setMethod("PUT"); + webRequest = new ServletWebRequest(request); + + Object actual = resolver.resolveArgument(paramMultipartFileList, null, webRequest, null); + + assertTrue(actual instanceof List); + assertEquals(expected, ((List) actual).get(0)); + } + @Test(expected = IllegalArgumentException.class) public void missingMultipartFile() throws Exception { request.setMethod("POST"); diff --git a/spring-web/src/test/java/org/springframework/web/method/annotation/SessionAttributesHandlerTests.java b/spring-web/src/test/java/org/springframework/web/method/annotation/SessionAttributesHandlerTests.java index 06575004b95..8007e5143bc 100644 --- a/spring-web/src/test/java/org/springframework/web/method/annotation/SessionAttributesHandlerTests.java +++ b/spring-web/src/test/java/org/springframework/web/method/annotation/SessionAttributesHandlerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -24,7 +24,6 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.util.HashSet; -import java.util.Map; import org.junit.Before; import org.junit.Test; @@ -39,17 +38,17 @@ import org.springframework.web.context.request.ServletWebRequest; /** * Test fixture with {@link SessionAttributesHandler}. - * + * * @author Rossen Stoyanchev */ public class SessionAttributesHandlerTests { private Class handlerType = SessionAttributeHandler.class; - + private SessionAttributesHandler sessionAttributesHandler; - + private SessionAttributeStore sessionAttributeStore; - + private NativeWebRequest request; @Before @@ -58,7 +57,7 @@ public class SessionAttributesHandlerTests { this.sessionAttributesHandler = new SessionAttributesHandler(handlerType, sessionAttributeStore); this.request = new ServletWebRequest(new MockHttpServletRequest()); } - + @Test public void isSessionAttribute() throws Exception { assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr1", null)); @@ -72,14 +71,18 @@ public class SessionAttributesHandlerTests { sessionAttributeStore.storeAttribute(request, "attr1", "value1"); sessionAttributeStore.storeAttribute(request, "attr2", "value2"); sessionAttributeStore.storeAttribute(request, "attr3", new TestBean()); + sessionAttributeStore.storeAttribute(request, "attr4", new TestBean()); - // Resolve successfully handler session attributes once - assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr1", null)); - assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr3", TestBean.class)); + assertEquals("Named attributes (attr1, attr2) should be 'known' right away", + new HashSet(asList("attr1", "attr2")), + sessionAttributesHandler.retrieveAttributes(request).keySet()); - Map attributes = sessionAttributesHandler.retrieveAttributes(request); + // Resolve 'attr3' by type + sessionAttributesHandler.isHandlerSessionAttribute("attr3", TestBean.class); - assertEquals(new HashSet(asList("attr1", "attr3")), attributes.keySet()); + assertEquals("Named attributes (attr1, attr2) and resolved attribute (att3) should be 'known'", + new HashSet(asList("attr1", "attr2", "attr3")), + sessionAttributesHandler.retrieveAttributes(request).keySet()); } @Test @@ -88,14 +91,16 @@ public class SessionAttributesHandlerTests { sessionAttributeStore.storeAttribute(request, "attr2", "value2"); sessionAttributeStore.storeAttribute(request, "attr3", new TestBean()); - // Resolve successfully handler session attributes once - assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr1", null)); - assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr3", TestBean.class)); - sessionAttributesHandler.cleanupAttributes(request); - + assertNull(sessionAttributeStore.retrieveAttribute(request, "attr1")); - assertNotNull(sessionAttributeStore.retrieveAttribute(request, "attr2")); + assertNull(sessionAttributeStore.retrieveAttribute(request, "attr2")); + assertNotNull(sessionAttributeStore.retrieveAttribute(request, "attr3")); + + // Resolve 'attr3' by type + sessionAttributesHandler.isHandlerSessionAttribute("attr3", TestBean.class); + sessionAttributesHandler.cleanupAttributes(request); + assertNull(sessionAttributeStore.retrieveAttribute(request, "attr3")); } @@ -105,19 +110,14 @@ public class SessionAttributesHandlerTests { model.put("attr1", "value1"); model.put("attr2", "value2"); model.put("attr3", new TestBean()); - - // Resolve successfully handler session attributes once - assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr1", null)); - assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr2", null)); - assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr3", TestBean.class)); - + sessionAttributesHandler.storeAttributes(request, model); - + assertEquals("value1", sessionAttributeStore.retrieveAttribute(request, "attr1")); assertEquals("value2", sessionAttributeStore.retrieveAttribute(request, "attr2")); assertTrue(sessionAttributeStore.retrieveAttribute(request, "attr3") instanceof TestBean); } - + @SessionAttributes(value = { "attr1", "attr2" }, types = { TestBean.class }) private static class SessionAttributeHandler { } diff --git a/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java b/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java index 303bcd2d157..2d9323ca8d6 100644 --- a/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -45,7 +45,7 @@ public class UriComponentsBuilderTests { URI expected = new URI("http://example.com/foo?bar#baz"); assertEquals("Invalid result URI", expected, result.toUri()); } - + @Test public void fromPath() throws URISyntaxException { UriComponents result = UriComponentsBuilder.fromPath("foo").queryParam("bar").fragment("baz").build(); @@ -176,6 +176,15 @@ public class UriComponentsBuilderTests { assertEquals(Arrays.asList("foo"), result.getPathSegments()); } + @Test + public void pathSegmentsSomeEmpty() { + UriComponentsBuilder builder = UriComponentsBuilder.newInstance().pathSegment("", "foo", "", "bar"); + UriComponents result = builder.build(); + + assertEquals("/foo/bar", result.getPath()); + assertEquals(Arrays.asList("foo", "bar"), result.getPathSegments()); + } + @Test public void replacePath() { UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://www.ietf.org/rfc/rfc2396.txt"); @@ -183,26 +192,26 @@ public class UriComponentsBuilderTests { UriComponents result = builder.build(); assertEquals("http://www.ietf.org/rfc/rfc3986.txt", result.toUriString()); - + builder = UriComponentsBuilder.fromUriString("http://www.ietf.org/rfc/rfc2396.txt"); builder.replacePath(null); result = builder.build(); assertEquals("http://www.ietf.org", result.toUriString()); } - + @Test public void replaceQuery() { UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://example.com/foo?foo=bar&baz=qux"); builder.replaceQuery("baz=42"); UriComponents result = builder.build(); - + assertEquals("http://example.com/foo?baz=42", result.toUriString()); builder = UriComponentsBuilder.fromUriString("http://example.com/foo?foo=bar&baz=qux"); builder.replaceQuery(null); result = builder.build(); - + assertEquals("http://example.com/foo", result.toUriString()); } @@ -234,13 +243,13 @@ public class UriComponentsBuilderTests { UriComponentsBuilder builder = UriComponentsBuilder.newInstance().queryParam("baz", "qux", 42); builder.replaceQueryParam("baz", "xuq", 24); UriComponents result = builder.build(); - + assertEquals("baz=xuq&baz=24", result.getQuery()); builder = UriComponentsBuilder.newInstance().queryParam("baz", "qux", 42); builder.replaceQueryParam("baz"); result = builder.build(); - + assertNull("Query param should have been deleted", result.getQuery()); } diff --git a/spring-web/src/test/java/org/springframework/web/util/UriComponentsTests.java b/spring-web/src/test/java/org/springframework/web/util/UriComponentsTests.java index 00049b00c8b..234a21c0904 100644 --- a/spring-web/src/test/java/org/springframework/web/util/UriComponentsTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/UriComponentsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -69,4 +69,10 @@ public class UriComponentsTests { UriComponentsBuilder.fromPath("/fo%2o").build(true); } + @Test + public void normalize() { + UriComponents uriComponents = UriComponentsBuilder.fromUriString("http://example.com/foo/../bar").build(); + assertEquals("http://example.com/bar", uriComponents.normalize().toString()); + } + } diff --git a/spring-web/src/test/java/org/springframework/web/util/UriTemplateTests.java b/spring-web/src/test/java/org/springframework/web/util/UriTemplateTests.java index f0099328cd6..ba2d7a796d2 100644 --- a/spring-web/src/test/java/org/springframework/web/util/UriTemplateTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/UriTemplateTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -89,6 +89,15 @@ public class UriTemplateTests { assertEquals("Invalid expanded template", new URI("http://example.com/hotel%20list/Z%C3%BCrich"), result); } + @Test(expected = IllegalArgumentException.class) + public void expandMapUnboundVariables() throws Exception { + Map uriVariables = new HashMap(2); + uriVariables.put("booking", "42"); + uriVariables.put("bar", "1"); + UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}"); + template.expand(uriVariables); + } + @Test public void expandEncoded() throws Exception { UriTemplate template = new UriTemplate("http://example.com/hotel list/{hotel}"); diff --git a/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/PortletContextResource.java b/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/PortletContextResource.java index 599c9e3e54e..86f5794b28d 100644 --- a/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/PortletContextResource.java +++ b/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/PortletContextResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -107,6 +107,28 @@ public class PortletContextResource extends AbstractFileResolvingResource implem } } + /** + * This implementation delegates to PortletContext.getResourceAsStream, + * which returns null in case of a non-readable resource (e.g. a directory). + * @see javax.portlet.PortletContext#getResourceAsStream(String) + */ + @Override + public boolean isReadable() { + InputStream is = this.portletContext.getResourceAsStream(this.path); + if (is != null) { + try { + is.close(); + } + catch (IOException ex) { + // ignore + } + return true; + } + else { + return false; + } + } + /** * This implementation delegates to PortletContext.getResourceAsStream, * but throws a FileNotFoundException if not found. diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java index 7bac3dfb1ba..d6eddaf7d7e 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java @@ -840,14 +840,14 @@ public class DispatcherServlet extends FrameworkServlet { request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); + + FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response); + if (inputFlashMap != null) { + request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap)); + } request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager); - Map flashMap = this.flashMapManager.getFlashMapForRequest(request); - if (flashMap != null) { - request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, flashMap); - } - try { doDispatch(request, response); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/FlashMap.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/FlashMap.java index cb6450ca89b..08b7f736a4a 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/FlashMap.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/FlashMap.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -32,12 +32,11 @@ import org.springframework.util.StringUtils; *

    A FlashMap can be set up with a request path and request parameters to * help identify the target request. Without this information, a FlashMap is * made available to the next request, which may or may not be the intended - * recipient. On a redirect, the target URL is known and for example - * {@code org.springframework.web.servlet.view.RedirectView} has the - * opportunity to automatically update the current FlashMap with target - * URL information. + * recipient. On a redirect, the target URL is known and a FlashMap can be + * updated with that information. This is done automatically when the + * {@code org.springframework.web.servlet.view.RedirectView} is used. * - *

    Annotated controllers will usually not use this type directly. + *

    Note: annotated controllers will usually not use FlashMap directly. * See {@code org.springframework.web.servlet.mvc.support.RedirectAttributes} * for an overview of using flash attributes in annotated controllers. * @@ -58,25 +57,6 @@ public final class FlashMap extends HashMap implements Comparabl private int timeToLive; - private final int createdBy; - - /** - * Create a new instance with an id uniquely identifying the creator of - * this FlashMap. - * @param createdBy identifies the FlashMapManager instance that created - * and will manage this FlashMap instance (e.g. via a hashCode) - */ - public FlashMap(int createdBy) { - this.createdBy = createdBy; - } - - /** - * Create a new instance. - */ - public FlashMap() { - this.createdBy = 0; - } - /** * Provide a URL path to help identify the target request for this FlashMap. * The path may be absolute (e.g. /application/resource) or relative to the @@ -96,7 +76,6 @@ public final class FlashMap extends HashMap implements Comparabl /** * Provide request parameters identifying the request for this FlashMap. - * Null or empty keys and values are skipped. * @param params a Map with the names and values of expected parameters. */ public FlashMap addTargetRequestParams(MultiValueMap params) { @@ -112,8 +91,8 @@ public final class FlashMap extends HashMap implements Comparabl /** * Provide a request parameter identifying the request for this FlashMap. - * @param name the expected parameter name, skipped if {@code null} - * @param value the expected parameter value, skipped if {@code null} + * @param name the expected parameter name, skipped if empty or {@code null} + * @param value the expected value, skipped if empty or {@code null} */ public FlashMap addTargetRequestParam(String name, String value) { if (StringUtils.hasText(name) && StringUtils.hasText(value)) { @@ -151,13 +130,6 @@ public final class FlashMap extends HashMap implements Comparabl } } - /** - * Whether the given id matches the id of the creator of this FlashMap. - */ - public boolean isCreatedBy(int createdBy) { - return this.createdBy == createdBy; - } - /** * Compare two FlashMaps and prefer the one that specifies a target URL * path or has more target URL parameters. Before comparing FlashMap diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/FlashMapManager.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/FlashMapManager.java index 8440bd636c5..078713194b9 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/FlashMapManager.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/FlashMapManager.java @@ -16,44 +16,43 @@ package org.springframework.web.servlet; -import java.util.Map; - import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * A strategy interface for retrieving and saving FlashMap instances. * See {@link FlashMap} for a general overview of flash attributes. - * + * * @author Rossen Stoyanchev * @since 3.1 - * + * * @see FlashMap */ public interface FlashMapManager { /** - * Get a Map with flash attributes saved by a previous request. - * See {@link FlashMap} for details on how FlashMap instances - * identifies the target requests they're saved for. - * If found, the Map is removed from the underlying storage. + * Find a FlashMap saved by a previous request that matches to the current + * request, remove it from underlying storage, and also remove other + * expired FlashMap instances. + *

    This method is invoked in the beginning of every request in contrast + * to {@link #saveOutputFlashMap}, which is invoked only when there are + * flash attributes to be saved - i.e. before a redirect. * @param request the current request - * @return a read-only Map with flash attributes or {@code null} + * @param response the current response + * @return a FlashMap matching the current request or {@code null} */ - Map getFlashMapForRequest(HttpServletRequest request); + FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response); /** - * Save the given FlashMap, in some underlying storage, mark the beginning - * of its expiration period, and remove other expired FlashMap instances. - * The method has no impact if the FlashMap is empty and there are no - * expired FlashMap instances to be removed. + * Save the given FlashMap, in some underlying storage and set the start + * of its expiration period. *

    Note: Invoke this method prior to a redirect in order - * to allow saving the FlashMap in the HTTP session or perhaps in a response + * to allow saving the FlashMap in the HTTP session or in a response * cookie before the response is committed. * @param flashMap the FlashMap to save * @param request the current request * @param response the current response */ - void save(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response); + void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java index 1fdaa0e22dc..03c8480bba0 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java @@ -134,7 +134,7 @@ import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolv * @author Rossen Stoyanchev * @since 3.1 */ -public abstract class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware { +public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware { private ServletContext servletContext; @@ -419,9 +419,9 @@ public abstract class WebMvcConfigurationSupport implements ApplicationContextAw String className = "org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"; clazz = ClassUtils.forName(className, WebMvcConfigurationSupport.class.getClassLoader()); } catch (ClassNotFoundException e) { - throw new BeanInitializationException("Could not find default validator"); + throw new BeanInitializationException("Could not find default validator", e); } catch (LinkageError e) { - throw new BeanInitializationException("Could not find default validator"); + throw new BeanInitializationException("Could not find default validator", e); } validator = (Validator) BeanUtils.instantiate(clazz); } @@ -474,7 +474,7 @@ public abstract class WebMvcConfigurationSupport implements ApplicationContextAw * providing a list of resolvers. */ @Bean - public HandlerExceptionResolver handlerExceptionResolver() throws Exception { + public HandlerExceptionResolver handlerExceptionResolver() { List exceptionResolvers = new ArrayList(); configureHandlerExceptionResolvers(exceptionResolvers); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartMethodArgumentResolver.java index 6b1c1125c0a..f517cc03501 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartMethodArgumentResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartMethodArgumentResolver.java @@ -110,9 +110,7 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM NativeWebRequest request, WebDataBinderFactory binderFactory) throws Exception { HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class); - if (!isMultipartRequest(servletRequest)) { - throw new MultipartException("The current request is not a multipart request"); - } + assertIsMultipartRequest(servletRequest); MultipartHttpServletRequest multipartRequest = WebUtils.getNativeRequest(servletRequest, MultipartHttpServletRequest.class); @@ -166,12 +164,11 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM return arg; } - private boolean isMultipartRequest(HttpServletRequest request) { - if (!"post".equals(request.getMethod().toLowerCase())) { - return false; - } + private static void assertIsMultipartRequest(HttpServletRequest request) { String contentType = request.getContentType(); - return (contentType != null && contentType.toLowerCase().startsWith("multipart/")); + if (contentType == null || !contentType.toLowerCase().startsWith("multipart/")) { + throw new MultipartException("The current request is not a multipart request"); + } } private String getPartName(MethodParameter parameter) { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractFlashMapManager.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractFlashMapManager.java index 0cfc3b2037d..e745cfb47b8 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractFlashMapManager.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractFlashMapManager.java @@ -19,7 +19,6 @@ package org.springframework.web.servlet.support; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import javax.servlet.http.HttpServletRequest; @@ -50,6 +49,8 @@ public abstract class AbstractFlashMapManager implements FlashMapManager { private UrlPathHelper urlPathHelper = new UrlPathHelper(); + private static final Object writeLock = new Object(); + /** * Set the amount of time in seconds after a {@link FlashMap} is saved * (at request completion) and before it expires. @@ -81,34 +82,30 @@ public abstract class AbstractFlashMapManager implements FlashMapManager { return this.urlPathHelper; } - /** - * {@inheritDoc} - *

    Does not cause an HTTP session to be created. - */ - public final Map getFlashMapForRequest(HttpServletRequest request) { - List flashMaps = retrieveFlashMaps(request); - if (CollectionUtils.isEmpty(flashMaps)) { + public final FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response) { + List allMaps = retrieveFlashMaps(request); + if (CollectionUtils.isEmpty(allMaps)) { return null; } if (logger.isDebugEnabled()) { - logger.debug("Retrieved FlashMap(s): " + flashMaps); + logger.debug("Retrieved FlashMap(s): " + allMaps); } - List result = new ArrayList(); - for (FlashMap flashMap : flashMaps) { - if (isFlashMapForRequest(flashMap, request)) { - result.add(flashMap); - } + List mapsToRemove = getExpiredFlashMaps(allMaps); + FlashMap match = getMatchingFlashMap(allMaps, request); + if (match != null) { + mapsToRemove.add(match); } - if (!result.isEmpty()) { - Collections.sort(result); + if (!mapsToRemove.isEmpty()) { if (logger.isDebugEnabled()) { - logger.debug("Found matching FlashMap(s): " + result); + logger.debug("Removing FlashMap(s): " + allMaps); + } + synchronized (writeLock) { + allMaps = retrieveFlashMaps(request); + allMaps.removeAll(mapsToRemove); + updateFlashMaps(allMaps, request, response); } - FlashMap match = result.remove(0); - flashMaps.remove(match); - return Collections.unmodifiableMap(match); } - return null; + return match; } /** @@ -118,10 +115,44 @@ public abstract class AbstractFlashMapManager implements FlashMapManager { */ protected abstract List retrieveFlashMaps(HttpServletRequest request); + /** + * Return a list of expired FlashMap instances contained in the given list. + */ + private List getExpiredFlashMaps(List allMaps) { + List result = new ArrayList(); + for (FlashMap map : allMaps) { + if (map.isExpired()) { + result.add(map); + } + } + return result; + } + + /** + * Return a FlashMap contained in the given list that matches the request. + * @return a matching FlashMap or {@code null} + */ + private FlashMap getMatchingFlashMap(List allMaps, HttpServletRequest request) { + List result = new ArrayList(); + for (FlashMap flashMap : allMaps) { + if (isFlashMapForRequest(flashMap, request)) { + result.add(flashMap); + } + } + if (!result.isEmpty()) { + Collections.sort(result); + if (logger.isDebugEnabled()) { + logger.debug("Found matching FlashMap(s): " + result); + } + return result.get(0); + } + return null; + } + /** * Whether the given FlashMap matches the current request. - * The default implementation uses the target request path and query params - * saved in the FlashMap. + * The default implementation uses the target request path and query + * parameters saved in the FlashMap. */ protected boolean isFlashMapForRequest(FlashMap flashMap, HttpServletRequest request) { if (flashMap.getTargetRequestPath() != null) { @@ -142,37 +173,21 @@ public abstract class AbstractFlashMapManager implements FlashMapManager { return true; } - /** - * {@inheritDoc} - *

    The FlashMap, if not empty, is saved to the HTTP session. - */ - public final void save(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response) { - Assert.notNull(flashMap, "FlashMap must not be null"); - - List flashMaps = retrieveFlashMaps(request); - if (flashMap.isEmpty() && (flashMaps == null)) { + public final void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response) { + if (CollectionUtils.isEmpty(flashMap)) { return; } - synchronized (this) { - boolean update = false; - flashMaps = retrieveFlashMaps(request); - if (!CollectionUtils.isEmpty(flashMaps)) { - update = removeExpired(flashMaps); - } - if (!flashMap.isEmpty()) { - String path = decodeAndNormalizePath(flashMap.getTargetRequestPath(), request); - flashMap.setTargetRequestPath(path); - flashMap.startExpirationPeriod(this.flashMapTimeout); - if (logger.isDebugEnabled()) { - logger.debug("Saving FlashMap=" + flashMap); - } - flashMaps = (flashMaps == null) ? new CopyOnWriteArrayList() : flashMaps; - flashMaps.add(flashMap); - update = true; - } - if (update) { - updateFlashMaps(flashMaps, request, response); - } + String path = decodeAndNormalizePath(flashMap.getTargetRequestPath(), request); + flashMap.setTargetRequestPath(path); + flashMap.startExpirationPeriod(this.flashMapTimeout); + if (logger.isDebugEnabled()) { + logger.debug("Saving FlashMap=" + flashMap); + } + synchronized (writeLock) { + List allMaps = retrieveFlashMaps(request); + allMaps = (allMaps == null) ? new CopyOnWriteArrayList() : allMaps; + allMaps.add(flashMap); + updateFlashMaps(allMaps, request, response); } } @@ -197,25 +212,4 @@ public abstract class AbstractFlashMapManager implements FlashMapManager { protected abstract void updateFlashMaps(List flashMaps, HttpServletRequest request, HttpServletResponse response); - /** - * Remove expired FlashMap instances from the given List. - */ - protected boolean removeExpired(List flashMaps) { - List expired = new ArrayList(); - for (FlashMap flashMap : flashMaps) { - if (flashMap.isExpired()) { - if (logger.isTraceEnabled()) { - logger.trace("Removing expired FlashMap: " + flashMap); - } - expired.add(flashMap); - } - } - if (expired.isEmpty()) { - return false; - } - else { - return flashMaps.removeAll(expired); - } - } - } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/ServletUriComponentsBuilder.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/ServletUriComponentsBuilder.java index 818653ec7d5..8aa58643900 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/ServletUriComponentsBuilder.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/ServletUriComponentsBuilder.java @@ -23,13 +23,11 @@ import org.springframework.util.StringUtils; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; -import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UrlPathHelper; /** - * A builder for {@link UriComponents} that offers static factory methods to - * extract information from an {@code HttpServletRequest}. + * A UriComponentsBuilder that extracts information from an HttpServletRequest. * * @author Rossen Stoyanchev * @since 3.1 @@ -50,24 +48,25 @@ public class ServletUriComponentsBuilder extends UriComponentsBuilder { } /** - * Return a builder initialized with the host, port, scheme, and the - * context path of the given request. + * Prepare a builder from the host, port, scheme, and context path of + * an HttpServletRequest. */ public static ServletUriComponentsBuilder fromContextPath(HttpServletRequest request) { ServletUriComponentsBuilder builder = fromRequest(request); - builder.replacePath(new UrlPathHelper().getContextPath(request)); + builder.replacePath(request.getContextPath()); builder.replaceQuery(null); return builder; } /** - * Return a builder initialized with the host, port, scheme, context path, - * and the servlet mapping of the given request. - * - *

    For example if the servlet is mapped by name, i.e. {@code "/main/*"}, - * then the resulting path will be {@code /appContext/main}. If the servlet - * path is not mapped by name, i.e. {@code "/"} or {@code "*.html"}, then - * the resulting path will contain the context path only. + * Prepare a builder from the host, port, scheme, context path, and + * servlet mapping of an HttpServletRequest. The results may vary depending + * on the type of servlet mapping used. + * + *

    If the servlet is mapped by name, e.g. {@code "/main/*"}, the path + * will end with "/main". If the servlet is mapped otherwise, e.g. + * {@code "/"} or {@code "*.do"}, the result will be the same as + * if calling {@link #fromContextPath(HttpServletRequest)}. */ public static ServletUriComponentsBuilder fromServletMapping(HttpServletRequest request) { ServletUriComponentsBuilder builder = fromContextPath(request); @@ -78,8 +77,19 @@ public class ServletUriComponentsBuilder extends UriComponentsBuilder { } /** - * Return a builder initialized with all available information in the given - * request including scheme, host, port, path, and query string. + * Prepare a builder from the host, port, scheme, and path of + * an HttpSevletRequest. + */ + public static ServletUriComponentsBuilder fromRequestUri(HttpServletRequest request) { + ServletUriComponentsBuilder builder = fromRequest(request); + builder.replacePath(request.getRequestURI()); + builder.replaceQuery(null); + return builder; + } + + /** + * Prepare a builder by copying the scheme, host, port, path, and + * query string of an HttpServletRequest. */ public static ServletUriComponentsBuilder fromRequest(HttpServletRequest request) { String scheme = request.getScheme(); @@ -91,30 +101,38 @@ public class ServletUriComponentsBuilder extends UriComponentsBuilder { if ((scheme.equals("http") && port != 80) || (scheme.equals("https") && port != 443)) { builder.port(port); } - builder.path(new UrlPathHelper().getRequestUri(request)); + builder.path(request.getRequestURI()); builder.query(request.getQueryString()); return builder; } - + /** - * Equivalent to {@link #fromContextPath(HttpServletRequest)} except the - * request is obtained via {@link RequestContextHolder}. + * Same as {@link #fromContextPath(HttpServletRequest)} except the + * request is obtained through {@link RequestContextHolder}. */ public static ServletUriComponentsBuilder fromCurrentContextPath() { return fromContextPath(getCurrentRequest()); } /** - * Equivalent to {@link #fromServletMapping(HttpServletRequest)} except the - * request is obtained via {@link RequestContextHolder}. + * Same as {@link #fromServletMapping(HttpServletRequest)} except the + * request is obtained through {@link RequestContextHolder}. */ public static ServletUriComponentsBuilder fromCurrentServletMapping() { return fromServletMapping(getCurrentRequest()); } /** - * Equivalent to {@link #fromRequest(HttpServletRequest)} except the - * request is obtained via {@link RequestContextHolder}. + * Same as {@link #fromRequestUri(HttpServletRequest)} except the + * request is obtained through {@link RequestContextHolder}. + */ + public static ServletUriComponentsBuilder fromCurrentRequestUri() { + return fromRequestUri(getCurrentRequest()); + } + + /** + * Same as {@link #fromRequest(HttpServletRequest)} except the + * request is obtained through {@link RequestContextHolder}. */ public static ServletUriComponentsBuilder fromCurrentRequest() { return fromRequest(getCurrentRequest()); @@ -128,5 +146,5 @@ public class ServletUriComponentsBuilder extends UriComponentsBuilder { Assert.state(servletRequest != null, "Could not find current HttpServletRequest"); return servletRequest; } - + } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/SessionFlashMapManager.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/SessionFlashMapManager.java index ef6183e952f..527f89d6335 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/SessionFlashMapManager.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/SessionFlashMapManager.java @@ -25,7 +25,7 @@ import javax.servlet.http.HttpSession; import org.springframework.web.servlet.FlashMap; /** - * Stores {@link FlashMap} instances in the HTTP session. + * Store and retrieve {@link FlashMap} instances to and from the HTTP session. * * @author Rossen Stoyanchev * @since 3.1.1 @@ -35,9 +35,10 @@ public class SessionFlashMapManager extends AbstractFlashMapManager{ private static final String FLASH_MAPS_SESSION_ATTRIBUTE = SessionFlashMapManager.class.getName() + ".FLASH_MAPS"; /** - * Retrieve saved FlashMap instances from the HTTP session. - * @param request the current request - * @return a List with FlashMap instances or {@code null} + * Retrieve saved FlashMap instances from the HTTP Session. + *

    Does not cause an HTTP session to be created but may update it if a + * FlashMap matching the current request is found or there are expired + * FlashMap to be removed. */ @SuppressWarnings("unchecked") protected List retrieveFlashMaps(HttpServletRequest request) { @@ -46,7 +47,7 @@ public class SessionFlashMapManager extends AbstractFlashMapManager{ } /** - * Save the given FlashMap instances in the HTTP session. + * Save the given FlashMap instance, if not empty, in the HTTP session. */ protected void updateFlashMaps(List flashMaps, HttpServletRequest request, HttpServletResponse response) { request.getSession().setAttribute(FLASH_MAPS_SESSION_ATTRIBUTE, flashMaps); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/RedirectView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/RedirectView.java index 0f931d776bb..43f15df2e9d 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/RedirectView.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/RedirectView.java @@ -276,7 +276,7 @@ public class RedirectView extends AbstractUrlBasedView implements SmartView { } FlashMapManager flashMapManager = RequestContextUtils.getFlashMapManager(request); - flashMapManager.save(flashMap, request, response); + flashMapManager.saveOutputFlashMap(flashMap, request, response); sendRedirect(request, response, targetUrl.toString(), this.http10Compatible); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/jasperreports/AbstractJasperReportsView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/jasperreports/AbstractJasperReportsView.java index 28b73bb5a2b..865b9f38556 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/jasperreports/AbstractJasperReportsView.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/jasperreports/AbstractJasperReportsView.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2012 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. @@ -485,38 +485,38 @@ public abstract class AbstractJasperReportsView extends AbstractUrlBasedView { */ protected final JasperReport loadReport(Resource resource) { try { - String fileName = resource.getFilename(); - if (fileName.endsWith(".jasper")) { - // Load pre-compiled report. - if (logger.isInfoEnabled()) { - logger.info("Loading pre-compiled Jasper Report from " + resource); + String filename = resource.getFilename(); + if (filename != null) { + if (filename.endsWith(".jasper")) { + // Load pre-compiled report. + if (logger.isInfoEnabled()) { + logger.info("Loading pre-compiled Jasper Report from " + resource); + } + InputStream is = resource.getInputStream(); + try { + return (JasperReport) JRLoader.loadObject(is); + } + finally { + is.close(); + } } - InputStream is = resource.getInputStream(); - try { - return (JasperReport) JRLoader.loadObject(is); - } - finally { - is.close(); + else if (filename.endsWith(".jrxml")) { + // Compile report on-the-fly. + if (logger.isInfoEnabled()) { + logger.info("Compiling Jasper Report loaded from " + resource); + } + InputStream is = resource.getInputStream(); + try { + JasperDesign design = JRXmlLoader.load(is); + return JasperCompileManager.compileReport(design); + } + finally { + is.close(); + } } } - else if (fileName.endsWith(".jrxml")) { - // Compile report on-the-fly. - if (logger.isInfoEnabled()) { - logger.info("Compiling Jasper Report loaded from " + resource); - } - InputStream is = resource.getInputStream(); - try { - JasperDesign design = JRXmlLoader.load(is); - return JasperCompileManager.compileReport(design); - } - finally { - is.close(); - } - } - else { - throw new IllegalArgumentException( - "Report filename [" + fileName + "] must end in either .jasper or .jrxml"); - } + throw new IllegalArgumentException( + "Report filename [" + filename + "] must end in either .jasper or .jrxml"); } catch (IOException ex) { throw new ApplicationContextException( diff --git a/spring-webmvc/src/test/java/org/springframework/web/context/ContextLoaderTests.java b/spring-webmvc/src/test/java/org/springframework/web/context/ContextLoaderTests.java index e228f24f5eb..836e17a5ff5 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/context/ContextLoaderTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/context/ContextLoaderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -17,6 +17,7 @@ package org.springframework.web.context; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -116,7 +117,7 @@ public final class ContextLoaderTests { } @Test - public void testContextLoaderListenerWithRegisteredContextConfigurer() { + public void testContextLoaderListenerWithRegisteredContextInitializer() { MockServletContext sc = new MockServletContext(""); sc.addInitParameter(ContextLoader.CONFIG_LOCATION_PARAM, "org/springframework/web/context/WEB-INF/ContextLoaderTests-acc-context.xml"); @@ -132,7 +133,20 @@ public final class ContextLoaderTests { } @Test - public void testContextLoaderListenerWithUnkownContextConfigurer() { + public void testRegisteredContextInitializerCanAccessServletContextParamsViaEnvironment() { + MockServletContext sc = new MockServletContext(""); + // config file doesn't matter. just a placeholder + sc.addInitParameter(ContextLoader.CONFIG_LOCATION_PARAM, + "/org/springframework/web/context/WEB-INF/empty-context.xml"); + + sc.addInitParameter("someProperty", "someValue"); + sc.addInitParameter(ContextLoader.CONTEXT_INITIALIZER_CLASSES_PARAM, EnvApplicationContextInitializer.class.getName()); + ContextLoaderListener listener = new ContextLoaderListener(); + listener.contextInitialized(new ServletContextEvent(sc)); + } + + @Test + public void testContextLoaderListenerWithUnkownContextInitializer() { MockServletContext sc = new MockServletContext(""); // config file doesn't matter. just a placeholder sc.addInitParameter(ContextLoader.CONFIG_LOCATION_PARAM, @@ -324,6 +338,15 @@ public final class ContextLoaderTests { } } + private static class EnvApplicationContextInitializer implements ApplicationContextInitializer { + public void initialize(ConfigurableWebApplicationContext applicationContext) { + // test that ApplicationContextInitializers can access ServletContext properties + // via the environment (SPR-8991) + String value = applicationContext.getEnvironment().getRequiredProperty("someProperty"); + assertThat(value, is("someValue")); + } + } + private static interface UnknownApplicationContext extends ConfigurableApplicationContext { void unheardOf(); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartMethodArgumentResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartMethodArgumentResolverTests.java index afbaf311d3c..94b18935fa2 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartMethodArgumentResolverTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartMethodArgumentResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -227,12 +227,22 @@ public class RequestPartMethodArgumentResolverTests { } @Test(expected=MultipartException.class) - public void notMultipartRequest() throws Exception { + public void isMultipartRequest() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); resolver.resolveArgument(paramMultipartFile, new ModelAndViewContainer(), new ServletWebRequest(request), null); fail("Expected exception"); } + // SPR-9079 + + @Test + public void isMultipartRequestPut() throws Exception { + this.multipartRequest.setMethod("PUT"); + Object actual = resolver.resolveArgument(paramMultipartFile, null, webRequest, null); + assertNotNull(actual); + assertSame(multipartFile1, actual); + } + private void testResolveArgument(SimpleBean argValue, MethodParameter parameter) throws IOException, Exception { MediaType contentType = MediaType.TEXT_PLAIN; diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/AbstractFlashMapManagerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/AbstractFlashMapManagerTests.java index ccef7060b9b..f4b80f6b1ad 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/AbstractFlashMapManagerTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/AbstractFlashMapManagerTests.java @@ -26,7 +26,6 @@ import static org.junit.Assert.assertTrue; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import javax.servlet.http.HttpServletRequest; @@ -60,7 +59,7 @@ public class AbstractFlashMapManagerTests { } @Test - public void getFlashMapForRequestByPath() { + public void retrieveAndUpdateMatchByPath() { FlashMap flashMap = new FlashMap(); flashMap.put("key", "value"); flashMap.setTargetRequestPath("/path"); @@ -68,16 +67,15 @@ public class AbstractFlashMapManagerTests { this.flashMapManager.setFlashMaps(flashMap); this.request.setRequestURI("/path"); - Map inputFlashMap = this.flashMapManager.getFlashMapForRequest(this.request); + FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(this.request, this.response); assertEquals(flashMap, inputFlashMap); - assertEquals("Input FlashMap should have been removed", 0, this.flashMapManager.getFlashMaps().size()); } // SPR-8779 @Test - public void getFlashMapForRequestByOriginatingPath() { + public void retrieveAndUpdateMatchByOriginatingPath() { FlashMap flashMap = new FlashMap(); flashMap.put("key", "value"); flashMap.setTargetRequestPath("/accounts"); @@ -86,14 +84,14 @@ public class AbstractFlashMapManagerTests { this.request.setAttribute(WebUtils.FORWARD_REQUEST_URI_ATTRIBUTE, "/accounts"); this.request.setRequestURI("/mvc/accounts"); - Map inputFlashMap = this.flashMapManager.getFlashMapForRequest(this.request); + FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(this.request, this.response); assertEquals(flashMap, inputFlashMap); assertEquals("Input FlashMap should have been removed", 0, this.flashMapManager.getFlashMaps().size()); } @Test - public void getFlashMapForRequestByPathWithTrailingSlash() { + public void retrieveAndUpdateMatchWithTrailingSlash() { FlashMap flashMap = new FlashMap(); flashMap.put("key", "value"); flashMap.setTargetRequestPath("/path"); @@ -101,14 +99,14 @@ public class AbstractFlashMapManagerTests { this.flashMapManager.setFlashMaps(flashMap); this.request.setRequestURI("/path/"); - Map inputFlashMap = this.flashMapManager.getFlashMapForRequest(this.request); + FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(this.request, this.response); assertEquals(flashMap, inputFlashMap); assertEquals("Input FlashMap should have been removed", 0, this.flashMapManager.getFlashMaps().size()); } @Test - public void getFlashMapForRequestWithParams() { + public void retrieveAndUpdateMatchByParams() { FlashMap flashMap = new FlashMap(); flashMap.put("key", "value"); flashMap.addTargetRequestParam("number", "one"); @@ -116,19 +114,19 @@ public class AbstractFlashMapManagerTests { this.flashMapManager.setFlashMaps(flashMap); this.request.setParameter("number", (String) null); - Map inputFlashMap = this.flashMapManager.getFlashMapForRequest(this.request); + FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(this.request, this.response); assertNull(inputFlashMap); assertEquals("FlashMap should not have been removed", 1, this.flashMapManager.getFlashMaps().size()); this.request.setParameter("number", "two"); - inputFlashMap = this.flashMapManager.getFlashMapForRequest(this.request); + inputFlashMap = this.flashMapManager.retrieveAndUpdate(this.request, this.response); assertNull(inputFlashMap); assertEquals("FlashMap should not have been removed", 1, this.flashMapManager.getFlashMaps().size()); this.request.setParameter("number", "one"); - inputFlashMap = this.flashMapManager.getFlashMapForRequest(this.request); + inputFlashMap = this.flashMapManager.retrieveAndUpdate(this.request, this.response); assertEquals(flashMap, inputFlashMap); assertEquals("Input FlashMap should have been removed", 0, this.flashMapManager.getFlashMaps().size()); @@ -137,7 +135,7 @@ public class AbstractFlashMapManagerTests { // SPR-8798 @Test - public void getFlashMapForRequestWithMultiValueParam() { + public void retrieveAndUpdateMatchWithMultiValueParam() { FlashMap flashMap = new FlashMap(); flashMap.put("name", "value"); flashMap.addTargetRequestParam("id", "1"); @@ -146,20 +144,20 @@ public class AbstractFlashMapManagerTests { this.flashMapManager.setFlashMaps(flashMap); this.request.setParameter("id", "1"); - Map inputFlashMap = this.flashMapManager.getFlashMapForRequest(this.request); + FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(this.request, this.response); assertNull(inputFlashMap); assertEquals("FlashMap should not have been removed", 1, this.flashMapManager.getFlashMaps().size()); this.request.addParameter("id", "2"); - inputFlashMap = this.flashMapManager.getFlashMapForRequest(this.request); + inputFlashMap = this.flashMapManager.retrieveAndUpdate(this.request, this.response); assertEquals(flashMap, inputFlashMap); assertEquals("Input FlashMap should have been removed", 0, this.flashMapManager.getFlashMaps().size()); } @Test - public void getFlashMapForRequestSortOrder() { + public void retrieveAndUpdateSortMultipleMatches() { FlashMap emptyFlashMap = new FlashMap(); FlashMap flashMapOne = new FlashMap(); @@ -174,28 +172,44 @@ public class AbstractFlashMapManagerTests { this.flashMapManager.setFlashMaps(emptyFlashMap, flashMapOne, flashMapTwo); this.request.setRequestURI("/one/two"); - Map inputFlashMap = this.flashMapManager.getFlashMapForRequest(this.request); + FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(this.request, this.response); assertEquals(flashMapTwo, inputFlashMap); + assertEquals("Input FlashMap should have been removed", 2, this.flashMapManager.getFlashMaps().size()); } @Test - public void saveFlashMapEmpty() throws InterruptedException { + public void retrieveAndUpdateRemoveExpired() throws InterruptedException { + List flashMaps = new ArrayList(); + for (int i=0; i < 5; i++) { + FlashMap expiredFlashMap = new FlashMap(); + expiredFlashMap.startExpirationPeriod(-1); + flashMaps.add(expiredFlashMap); + } + this.flashMapManager.setFlashMaps(flashMaps); + this.flashMapManager.retrieveAndUpdate(this.request, this.response); + + assertEquals("Expired instances should be removed even if the saved FlashMap is empty", + 0, this.flashMapManager.getFlashMaps().size()); + } + + @Test + public void saveOutputFlashMapEmpty() throws InterruptedException { FlashMap flashMap = new FlashMap(); - this.flashMapManager.save(flashMap, this.request, this.response); + this.flashMapManager.saveOutputFlashMap(flashMap, this.request, this.response); List allMaps = this.flashMapManager.getFlashMaps(); assertNull(allMaps); } @Test - public void saveFlashMap() throws InterruptedException { + public void saveOutputFlashMap() throws InterruptedException { FlashMap flashMap = new FlashMap(); flashMap.put("name", "value"); this.flashMapManager.setFlashMapTimeout(-1); // expire immediately so we can check expiration started - this.flashMapManager.save(flashMap, this.request, this.response); + this.flashMapManager.saveOutputFlashMap(flashMap, this.request, this.response); List allMaps = this.flashMapManager.getFlashMaps(); assertNotNull(allMaps); @@ -204,67 +218,52 @@ public class AbstractFlashMapManagerTests { } @Test - public void saveFlashMapDecodeTargetPath() throws InterruptedException { + public void saveOutputFlashMapDecodeTargetPath() throws InterruptedException { FlashMap flashMap = new FlashMap(); flashMap.put("key", "value"); flashMap.setTargetRequestPath("/once%20upon%20a%20time"); - this.flashMapManager.save(flashMap, this.request, this.response); + this.flashMapManager.saveOutputFlashMap(flashMap, this.request, this.response); assertEquals("/once upon a time", flashMap.getTargetRequestPath()); } @Test - public void saveFlashMapNormalizeTargetPath() throws InterruptedException { + public void saveOutputFlashMapNormalizeTargetPath() throws InterruptedException { FlashMap flashMap = new FlashMap(); flashMap.put("key", "value"); flashMap.setTargetRequestPath("."); this.request.setRequestURI("/once/upon/a/time"); - this.flashMapManager.save(flashMap, this.request, this.response); + this.flashMapManager.saveOutputFlashMap(flashMap, this.request, this.response); assertEquals("/once/upon/a", flashMap.getTargetRequestPath()); flashMap.setTargetRequestPath("./"); this.request.setRequestURI("/once/upon/a/time"); - this.flashMapManager.save(flashMap, this.request, this.response); + this.flashMapManager.saveOutputFlashMap(flashMap, this.request, this.response); assertEquals("/once/upon/a/", flashMap.getTargetRequestPath()); flashMap.setTargetRequestPath(".."); this.request.setRequestURI("/once/upon/a/time"); - this.flashMapManager.save(flashMap, this.request, this.response); + this.flashMapManager.saveOutputFlashMap(flashMap, this.request, this.response); assertEquals("/once/upon", flashMap.getTargetRequestPath()); flashMap.setTargetRequestPath("../"); this.request.setRequestURI("/once/upon/a/time"); - this.flashMapManager.save(flashMap, this.request, this.response); + this.flashMapManager.saveOutputFlashMap(flashMap, this.request, this.response); assertEquals("/once/upon/", flashMap.getTargetRequestPath()); flashMap.setTargetRequestPath("../../only"); this.request.setRequestURI("/once/upon/a/time"); - this.flashMapManager.save(flashMap, this.request, this.response); + this.flashMapManager.saveOutputFlashMap(flashMap, this.request, this.response); assertEquals("/once/only", flashMap.getTargetRequestPath()); } - @Test - public void saveFlashMapAndRemoveExpired() throws InterruptedException { - List flashMaps = new ArrayList(); - for (int i=0; i < 5; i++) { - FlashMap flashMap = new FlashMap(); - flashMap.startExpirationPeriod(-1); - flashMaps.add(flashMap); - } - this.flashMapManager.setFlashMaps(flashMaps); - this.flashMapManager.save(new FlashMap(), request, response); - - assertEquals("Expired instances should be removed even if the saved FlashMap is empty", - 0, this.flashMapManager.getFlashMaps().size()); - } - private static class TestFlashMapManager extends AbstractFlashMapManager { diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/ServletUriComponentsBuilderTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/ServletUriComponentsBuilderTests.java index cd1798c242b..8b11249e882 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/ServletUriComponentsBuilderTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/ServletUriComponentsBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -44,7 +44,6 @@ public class ServletUriComponentsBuilderTests { public void fromRequest() { request.setRequestURI("/mvc-showcase/data/param"); request.setQueryString("foo=123"); - String result = ServletUriComponentsBuilder.fromRequest(request).build().toUriString(); assertEquals("http://localhost/mvc-showcase/data/param?foo=123", result); @@ -52,11 +51,10 @@ public class ServletUriComponentsBuilderTests { @Test public void fromRequestEncodedPath() { - request.setRequestURI("/mvc-showcase/data/foo%20bar;jsessionid=123"); - + request.setRequestURI("/mvc-showcase/data/foo%20bar"); String result = ServletUriComponentsBuilder.fromRequest(request).build().toUriString(); - assertEquals("http://localhost/mvc-showcase/data/foo bar", result); + assertEquals("http://localhost/mvc-showcase/data/foo%20bar", result); } @Test @@ -71,17 +69,24 @@ public class ServletUriComponentsBuilderTests { public void fromRequestAtypicalHttpsPort() { request.setScheme("https"); request.setServerPort(9043); - String result = ServletUriComponentsBuilder.fromRequest(request).build().toUriString(); assertEquals("https://localhost:9043", result); } + @Test + public void fromRequestUri() { + request.setRequestURI("/mvc-showcase/data/param"); + request.setQueryString("foo=123"); + String result = ServletUriComponentsBuilder.fromRequestUri(request).build().toUriString(); + + assertEquals("http://localhost/mvc-showcase/data/param", result); + } + @Test public void fromContextPath() { request.setRequestURI("/mvc-showcase/data/param"); request.setQueryString("foo=123"); - String result = ServletUriComponentsBuilder.fromContextPath(request).build().toUriString(); assertEquals("http://localhost/mvc-showcase", result); @@ -92,7 +97,6 @@ public class ServletUriComponentsBuilderTests { request.setRequestURI("/mvc-showcase/app/simple"); request.setServletPath("/app"); request.setQueryString("foo=123"); - String result = ServletUriComponentsBuilder.fromServletMapping(request).build().toUriString(); assertEquals("http://localhost/mvc-showcase/app", result); @@ -103,7 +107,6 @@ public class ServletUriComponentsBuilderTests { request.setRequestURI("/mvc-showcase/data/param"); request.setQueryString("foo=123"); RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(this.request)); - try { String result = ServletUriComponentsBuilder.fromCurrentRequest().build().toUriString(); diff --git a/src/dist/changelog.txt b/src/dist/changelog.txt index a5ddc80ba75..704787cc041 100644 --- a/src/dist/changelog.txt +++ b/src/dist/changelog.txt @@ -3,16 +3,23 @@ SPRING FRAMEWORK CHANGELOG http://www.springsource.org -Changes in version 3.1.1 (2012-02-06) +Changes in version 3.1.1 (2012-02-13) ------------------------------------- -* official support for Hibernate 4.0 GA (as released in the meantime) +* official support for Hibernate 4.0 GA as well as Hibernate 4.1 * JBossNativeJdbcExtractor is compatible with JBoss AS 7 as well +* restored JBossLoadTimeWeaver compatibility with JBoss AS 5.1 +* Provider injection works with generically typed collections of beans as well +* @ActiveProfiles mechanism in test context framework works with @ImportResource as well * context:property-placeholder's "file-encoding" attribute value is being applied correctly +* clarified Resource's "getFilename" method to return null if resource type does not have a filename +* optimized converter lookup in GenericConversionService to avoid contention in JDK proxy check * DataBinder correctly handles ParseException from Formatter for String->String case * CacheNamespaceHandler actually parses cache:annotation-driven's "key-generator" attribute * officially deprecated TopLinkJpaDialect in favor of EclipseLink and Spring's EclipseLinkJpaDialect * fixed LocalContainerEntityManagerFactoryBean's "packagesToScan" to avoid additional provider scan +* LocalContainerEntityManagerFactoryBean's "persistenceUnitName" applies to "packagesToScan" as well +* DefaultPersistenceUnitManager uses containing jar as persistence unit root URL for default unit * added protected "isPersistenceUnitOverrideAllowed()" method to DefaultPersistenceUnitManager * Hibernate synchronization properly unbinds Session even in case of afterCompletion exception * Hibernate exception translation covers NonUniqueObjectException to DuplicateKeyException case @@ -20,16 +27,25 @@ Changes in version 3.1.1 (2012-02-06) * Hibernate 4 LocalSessionFactoryBean does not insist on a "dataSource" reference being set * added "entityInterceptor" property to Hibernate 4 LocalSessionFactoryBean * added "getConfiguration" accessor to Hibernate 4 LocalSessionFactoryBean +* added "durability" and "description" properties to JobDetailFactoryBean * corrected fix for QuartzJobBean to work with Quartz 2.0/2.1 * JMS CachingConnectionFactory never caches consumers for temporary queues and topics * JMS SimpleMessageListenerContainer silently falls back to lazy registration of consumers -* fix regresion in UriUtils -* allow adding flash attributes in methods with a ModelAndView return value +* Servlet/PortletContextResource's "isReadable()" implementation returns false for directories * preserve quotes in MediaType parameters -* make flash attributes available in the model of ParameterizableViewController and UrlFilenameViewController -* add property to RedirectView to disable expanding URI variables in redirect URL -* fix request mapping bug involving direct vs pattern path matches with HTTP methods -* revise the FlashMapManager contract and implemenation to address a flaw in its design +* added "normalize()" method to UriComponents +* remove empty path segments from input to UriComponentsBuilder +* added "fromRequestUri(request)" and "fromCurrentRequestUri()" methods to ServletUriComponentsBuilder +* allow adding flash attributes in methods with a ModelAndView return value +* make flash attributes available in the model of Parameterizable/UrlFilenameViewController +* revised the FlashMapManager contract and implementation to address a flaw in its design +* improved @SessionAttributes handling to provide better support for clustered sessions +* added property to RedirectView to disable expanding URI variables in redirect URL +* fixed request mapping bug involving direct vs pattern path matches with HTTP methods +* removed check for HTTP "POST" when resolving multipart request controller method arguments +* updated @RequestMapping and reference docs wrt differences between @MVC 3.1 and @MVC 2.5-3.0 +* ServletServerHttpRequest/Response fall back on the Content-Type and encoding of the request + Changes in version 3.1 GA (2011-12-12) -------------------------------------- diff --git a/src/reference/docbook/beans-dependencies.xml b/src/reference/docbook/beans-dependencies.xml index 425ec366eb5..c8562213667 100644 --- a/src/reference/docbook/beans-dependencies.xml +++ b/src/reference/docbook/beans-dependencies.xml @@ -1438,7 +1438,7 @@ package fiona.apple; // Spring-API imports import org.springframework.beans.BeansException; -import org.springframework.context.Applicationcontext; +import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; public class CommandManager implements ApplicationContextAware { diff --git a/src/reference/docbook/beans-extension-points.xml b/src/reference/docbook/beans-extension-points.xml index 5b0688fd399..e45525b23bc 100644 --- a/src/reference/docbook/beans-extension-points.xml +++ b/src/reference/docbook/beans-extension-points.xml @@ -94,7 +94,7 @@ registration is through ApplicationContext auto-detection (as described above), it is also possible to register them programmatically - against an ApplicationContext using the + against a ConfigurableBeanFactory using the addBeanPostProcessor method. This can be useful when needing to evaluate conditional logic before registration, or even for copying bean post processors across contexts in a hierarchy. Note diff --git a/src/reference/docbook/mvc.xml b/src/reference/docbook/mvc.xml index 452eea7b386..38ee9fe58cc 100644 --- a/src/reference/docbook/mvc.xml +++ b/src/reference/docbook/mvc.xml @@ -859,6 +859,69 @@ public class ClinicController { mechanisms see . +

    + New Support Classes for <classname>@RequestMapping</classname> methods in Spring MVC 3.1 + + Spring 3.1 introduced a new set of support classes for + @RequestMapping methods called + RequestMappingHandlerMapping and + RequestMappingHandlerAdapter respectively. + They are recommended for use and even required to take advantage of + new features in Spring MVC 3.1 and going forward. The new support + classes are enabled by default by the MVC namespace and MVC Java + config (@EnableWebMvc) but must be configured + explicitly if using neither. This section describes a few + important differences between the old and the new support classes. + + + Prior to Spring 3.1, type and method-level request mappings were + examined in two separate stages -- a controller was selected first + by the DefaultAnnotationHandlerMapping and the + actual method to invoke was narrowed down second by + the AnnotationMethodHandlerAdapter. + + With the new support classes in Spring 3.1, the + RequestMappingHandlerMapping is the only place + where a decision is made about which method should process the request. + Think of controller methods as a collection of unique endpoints + with mappings for each method derived from type and method-level + @RequestMapping information. + + This enables some new possibilities. For once a + HandlerInterceptor or a + HandlerExceptionResolver can now expect the + Object-based handler to be a HandlerMethod, + which allows them to examine the exact method, its parameters and + associated annotations. The processing for a URL no longer needs to + be split across different controllers. + + + There are also several things no longer possible: + + Select a controller first with a + SimpleUrlHandlerMapping or + BeanNameUrlHandlerMapping and then narrow + the method based on @RequestMapping + annotations. + Rely on method names as a fall-back mechanism to + disambiguate between two @RequestMapping methods + that don't have an explicit path mapping URL path but otherwise + match equally, e.g. by HTTP method. In the new support classes + @RequestMapping methods have to be mapped + uniquely. + Have a single default method (without an explicit + path mapping) with which requests are processed if no other + controller method matches more concretely. In the new support + classes if a matching method is not found a 404 error + is raised. + + + The above features are still supported with the existing support + classes. However to take advantage of new Spring MVC 3.1 features + you'll need to use the new support classes. + +
    +
    URI Template Patterns @@ -1106,6 +1169,17 @@ public class RelativePathUriTemplateController { BindingResult arguments. This is described in the next section. + Spring 3.1 introduced a new set of support classes for + @RequestMapping methods called + RequestMappingHandlerMapping and + RequestMappingHandlerAdapter respectively. + They are recommended for use and even required to take advantage + of new features in Spring MVC 3.1 and going forward. + The new support classes are enabled by default from the MVC namespace and + with use of the MVC Java config (@EnableWebMvc) but must be + configured explicitly if using neither. + +
    Supported method argument types @@ -1503,16 +1577,18 @@ public void handle(@RequestBody String body, Writer writer) throws IOException { <bean id="castorMarshaller" class="org.springframework.oxm.castor.CastorMarshaller"/> An @RequestBody method parameter can be - annotated with @Valid, in which case it will + annotated with @Valid, in which case it will be validated using the configured Validator instance. When using the MVC namespace a JSR-303 validator is configured automatically assuming a JSR-303 implementation is - available on the classpath. If validation fails a - RequestBodyNotValidException is raised. The - exception is handled by the - DefaultHandlerExceptionResolver and results in - a 400 error sent back to the client along with a - message containing the validation errors. + available on the classpath. + Unlike @ModelAttribute parameters, for which + a BindingResult can be used to examine the errors, + @RequestBody validation errors always result in a + MethodArgumentNotValidException being raised. + The exception is handled in the + DefaultHandlerExceptionResolver, which sends + a 400 error back to the client. Also see for diff --git a/src/reference/docbook/new-in-3.1.xml b/src/reference/docbook/new-in-3.1.xml index 27c9024b074..3f0ff34b07a 100644 --- a/src/reference/docbook/new-in-3.1.xml +++ b/src/reference/docbook/new-in-3.1.xml @@ -391,6 +391,10 @@ Java-based configuration via @EnableWebMvc. The existing classes will continue to be available but use of the new classes is recommended going forward. + + See for additional + details and a list of features not available with the new support classes. +
    @@ -483,27 +487,27 @@
    <classname>UriComponentsBuilder</classname> and <classname>UriComponents</classname> - A new UriComponents class has been added, - which is an immutable container of URI components providing + A new UriComponents class has been added, + which is an immutable container of URI components providing access to all contained URI components. - A nenw UriComponentsBuilder class is also + A nenw UriComponentsBuilder class is also provided to help create UriComponents instances. Together the two classes give fine-grained control over all aspects of preparing a URI including construction, expansion from URI template variables, and encoding. - - In most cases the new classes can be used as a more flexible - alternative to the existing UriTemplate + + In most cases the new classes can be used as a more flexible + alternative to the existing UriTemplate especially since UriTemplate relies on those same classes internally. - + A ServletUriComponentsBuilder sub-class - provides static factory methods to copy information from + provides static factory methods to copy information from a Servlet request. See . - +
    - +
    diff --git a/src/reference/docbook/remoting.xml b/src/reference/docbook/remoting.xml index 18c6aed7c0b..d7e537c4f31 100644 --- a/src/reference/docbook/remoting.xml +++ b/src/reference/docbook/remoting.xml @@ -1580,7 +1580,7 @@ String body = response.getBody();
    - ByteArrayMessageConverter + ByteArrayHttpMessageConverter An HttpMessageConverter implementation that can read and write byte arrays from the HTTP