diff --git a/gradle.properties b/gradle.properties index af8519c3aff3..2edf644e9429 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=3.2.4.RELEASE +version=3.2.5.RELEASE diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/Advised.java b/spring-aop/src/main/java/org/springframework/aop/framework/Advised.java index 3c822cf2da51..232e2ebc3988 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/Advised.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/Advised.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -52,13 +52,13 @@ public interface Advised extends TargetClassAware { * Return the interfaces proxied by the AOP proxy. Will not * include the target class, which may also be proxied. */ - Class[] getProxiedInterfaces(); + Class[] getProxiedInterfaces(); /** * Determine whether the given interface is proxied. * @param intf the interface to check */ - boolean isInterfaceProxied(Class intf); + boolean isInterfaceProxied(Class intf); /** diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/AdvisedSupport.java b/spring-aop/src/main/java/org/springframework/aop/framework/AdvisedSupport.java index e5f89177b603..2d6f4b7eb0ee 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/AdvisedSupport.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/AdvisedSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -158,7 +158,7 @@ public TargetSource getTargetSource() { * @see #setTargetSource * @see #setTarget */ - public void setTargetClass(Class targetClass) { + public void setTargetClass(Class targetClass) { this.targetSource = EmptyTargetSource.forClass(targetClass); } @@ -194,7 +194,7 @@ public AdvisorChainFactory getAdvisorChainFactory() { /** * Set the interfaces to be proxied. */ - public void setInterfaces(Class[] interfaces) { + public void setInterfaces(Class... interfaces) { Assert.notNull(interfaces, "Interfaces must not be null"); this.interfaces.clear(); for (Class ifc : interfaces) { @@ -206,7 +206,7 @@ public void setInterfaces(Class[] interfaces) { * Add a new proxied interface. * @param intf the additional interface to proxy */ - public void addInterface(Class intf) { + public void addInterface(Class intf) { Assert.notNull(intf, "Interface must not be null"); if (!intf.isInterface()) { throw new IllegalArgumentException("[" + intf.getName() + "] is not an interface"); @@ -224,15 +224,15 @@ public void addInterface(Class intf) { * @return {@code true} if the interface was removed; {@code false} * if the interface was not found and hence could not be removed */ - public boolean removeInterface(Class intf) { + public boolean removeInterface(Class intf) { return this.interfaces.remove(intf); } - public Class[] getProxiedInterfaces() { + public Class[] getProxiedInterfaces() { return this.interfaces.toArray(new Class[this.interfaces.size()]); } - public boolean isInterfaceProxied(Class intf) { + public boolean isInterfaceProxied(Class intf) { for (Class proxyIntf : this.interfaces) { if (intf.isAssignableFrom(proxyIntf)) { return true; diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/ProxyFactory.java b/spring-aop/src/main/java/org/springframework/aop/framework/ProxyFactory.java index 67e89eeefd26..51f8d01f53c3 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/ProxyFactory.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/ProxyFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -57,7 +57,7 @@ public ProxyFactory(Object target) { *

No target, only interfaces. Must add interceptors. * @param proxyInterfaces the interfaces that the proxy should implement */ - public ProxyFactory(Class[] proxyInterfaces) { + public ProxyFactory(Class... proxyInterfaces) { setInterfaces(proxyInterfaces); } @@ -69,7 +69,7 @@ public ProxyFactory(Class[] proxyInterfaces) { * @param proxyInterface the interface that the proxy should implement * @param interceptor the interceptor that the proxy should invoke */ - public ProxyFactory(Class proxyInterface, Interceptor interceptor) { + public ProxyFactory(Class proxyInterface, Interceptor interceptor) { addInterface(proxyInterface); addAdvice(interceptor); } @@ -80,7 +80,7 @@ public ProxyFactory(Class proxyInterface, Interceptor interceptor) { * @param proxyInterface the interface that the proxy should implement * @param targetSource the TargetSource that the proxy should invoke */ - public ProxyFactory(Class proxyInterface, TargetSource targetSource) { + public ProxyFactory(Class proxyInterface, TargetSource targetSource) { addInterface(proxyInterface); setTargetSource(targetSource); } diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/BeanFactoryAdvisorRetrievalHelper.java b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/BeanFactoryAdvisorRetrievalHelper.java index e417f92df027..c484520b89be 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/BeanFactoryAdvisorRetrievalHelper.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/BeanFactoryAdvisorRetrievalHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -81,24 +81,32 @@ public List findAdvisorBeans() { List advisors = new LinkedList(); for (String name : advisorNames) { - if (isEligibleBean(name) && !this.beanFactory.isCurrentlyInCreation(name)) { - try { - advisors.add(this.beanFactory.getBean(name, Advisor.class)); + if (isEligibleBean(name)) { + if (this.beanFactory.isCurrentlyInCreation(name)) { + if (logger.isDebugEnabled()) { + logger.debug("Skipping currently created advisor '" + name + "'"); + } } - catch (BeanCreationException ex) { - Throwable rootCause = ex.getMostSpecificCause(); - if (rootCause instanceof BeanCurrentlyInCreationException) { - BeanCreationException bce = (BeanCreationException) rootCause; - if (this.beanFactory.isCurrentlyInCreation(bce.getBeanName())) { - if (logger.isDebugEnabled()) { - logger.debug("Ignoring currently created advisor '" + name + "': " + ex.getMessage()); + else { + try { + advisors.add(this.beanFactory.getBean(name, Advisor.class)); + } + catch (BeanCreationException ex) { + Throwable rootCause = ex.getMostSpecificCause(); + if (rootCause instanceof BeanCurrentlyInCreationException) { + BeanCreationException bce = (BeanCreationException) rootCause; + if (this.beanFactory.isCurrentlyInCreation(bce.getBeanName())) { + if (logger.isDebugEnabled()) { + logger.debug("Skipping advisor '" + name + + "' with dependency on currently created bean: " + ex.getMessage()); + } + // Ignore: indicates a reference back to the bean we're trying to advise. + // We want to find advisors other than the currently created bean itself. + continue; } - // Ignore: indicates a reference back to the bean we're trying to advise. - // We want to find advisors other than the currently created bean itself. - continue; } + throw ex; } - throw ex; } } } diff --git a/spring-aop/src/main/java/org/springframework/aop/scope/ScopedProxyUtils.java b/spring-aop/src/main/java/org/springframework/aop/scope/ScopedProxyUtils.java index fdd0253d6ed0..0a3e5ba893b7 100644 --- a/spring-aop/src/main/java/org/springframework/aop/scope/ScopedProxyUtils.java +++ b/spring-aop/src/main/java/org/springframework/aop/scope/ScopedProxyUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,7 +54,7 @@ public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder defini // Create a scoped proxy definition for the original bean name, // "hiding" the target bean in an internal target definition. RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class); - proxyDefinition.setOriginatingBeanDefinition(definition.getBeanDefinition()); + proxyDefinition.setOriginatingBeanDefinition(targetDefinition); proxyDefinition.setSource(definition.getSource()); proxyDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); diff --git a/spring-aspects/src/main/java/org/springframework/mock/staticmock/AbstractMethodMockingControl.aj b/spring-aspects/src/main/java/org/springframework/mock/staticmock/AbstractMethodMockingControl.aj index 972a32485939..e8fae8d097f8 100644 --- a/spring-aspects/src/main/java/org/springframework/mock/staticmock/AbstractMethodMockingControl.aj +++ b/spring-aspects/src/main/java/org/springframework/mock/staticmock/AbstractMethodMockingControl.aj @@ -97,36 +97,34 @@ public abstract aspect AbstractMethodMockingControl percflow(mockStaticsTestMeth public void verify() { if (verified != calls.size()) { - throw new IllegalStateException("Expected " + calls.size() - + " calls, received " + verified); + throw new IllegalStateException("Expected " + calls.size() + " calls, received " + verified); } } /** - * Validate the call and provide the expected return value - * @param lastSig - * @param args - * @return + * Validate the call and provide the expected return value. */ public Object respond(String lastSig, Object[] args) { Call call = nextCall(); CallResponse responseType = call.responseType; if (responseType == CallResponse.return_) { return call.returnValue(lastSig, args); - } else if(responseType == CallResponse.throw_) { - return (RuntimeException)call.throwException(lastSig, args); - } else if(responseType == CallResponse.nothing) { + } + else if (responseType == CallResponse.throw_) { + return call.throwException(lastSig, args); + } + else if (responseType == CallResponse.nothing) { // do nothing } throw new IllegalStateException("Behavior of " + call + " not specified"); } private Call nextCall() { - if (verified > calls.size() - 1) { - throw new IllegalStateException("Expected " + calls.size() - + " calls, received " + verified); + verified++; + if (verified > calls.size()) { + throw new IllegalStateException("Expected " + calls.size() + " calls, received " + verified); } - return calls.get(verified++); + return calls.get(verified); } public void expectCall(String lastSig, Object lastArgs[]) { @@ -171,7 +169,8 @@ public abstract aspect AbstractMethodMockingControl percflow(mockStaticsTestMeth expectations.expectCall(thisJoinPointStaticPart.toLongString(), thisJoinPoint.getArgs()); // Return value doesn't matter return null; - } else { + } + else { return expectations.respond(thisJoinPointStaticPart.toLongString(), thisJoinPoint.getArgs()); } } diff --git a/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java b/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java index 7c8cd51cece1..f83f6acb4eb7 100644 --- a/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java +++ b/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java @@ -101,15 +101,14 @@ public static void acceptClassLoader(ClassLoader classLoader) { /** * Clear the introspection cache for the given ClassLoader, removing the - * introspection results for all classes underneath that ClassLoader, - * and deregistering the ClassLoader (and any of its children) from the - * acceptance list. + * introspection results for all classes underneath that ClassLoader, and + * removing the ClassLoader (and its children) from the acceptance list. * @param classLoader the ClassLoader to clear the cache for */ public static void clearClassLoader(ClassLoader classLoader) { synchronized (classCache) { for (Iterator it = classCache.keySet().iterator(); it.hasNext();) { - Class beanClass = it.next(); + Class beanClass = it.next(); if (isUnderneathClassLoader(beanClass.getClassLoader(), classLoader)) { it.remove(); } @@ -127,13 +126,11 @@ public static void clearClassLoader(ClassLoader classLoader) { /** * Create CachedIntrospectionResults for the given bean class. - *

We don't want to use synchronization here. Object references are atomic, - * so we can live with doing the occasional unnecessary lookup at startup only. * @param beanClass the bean class to analyze * @return the corresponding CachedIntrospectionResults * @throws BeansException in case of introspection failure */ - static CachedIntrospectionResults forClass(Class beanClass) throws BeansException { + static CachedIntrospectionResults forClass(Class beanClass) throws BeansException { CachedIntrospectionResults results; Object value; synchronized (classCache) { @@ -225,7 +222,7 @@ private static boolean isUnderneathClassLoader(ClassLoader candidate, ClassLoade * @param beanClass the bean class to analyze * @throws BeansException in case of introspection failure */ - private CachedIntrospectionResults(Class beanClass) throws BeansException { + private CachedIntrospectionResults(Class beanClass) throws BeansException { try { if (logger.isTraceEnabled()) { logger.trace("Getting BeanInfo for class [" + beanClass.getName() + "]"); @@ -248,7 +245,7 @@ private CachedIntrospectionResults(Class beanClass) throws BeansException { // garbage collection on class loader shutdown - we cache it here anyway, // in a GC-friendly manner. In contrast to CachedIntrospectionResults, // Introspector does not use WeakReferences as values of its WeakHashMap! - Class classToFlush = beanClass; + Class classToFlush = beanClass; do { Introspector.flushFromCaches(classToFlush); classToFlush = classToFlush.getSuperclass(); @@ -286,7 +283,7 @@ BeanInfo getBeanInfo() { return this.beanInfo; } - Class getBeanClass() { + Class getBeanClass() { return this.beanInfo.getBeanDescriptor().getBeanClass(); } @@ -314,7 +311,7 @@ PropertyDescriptor[] getPropertyDescriptors() { return pds; } - private PropertyDescriptor buildGenericTypeAwarePropertyDescriptor(Class beanClass, PropertyDescriptor pd) { + private PropertyDescriptor buildGenericTypeAwarePropertyDescriptor(Class beanClass, PropertyDescriptor pd) { try { return new GenericTypeAwarePropertyDescriptor(beanClass, pd.getName(), pd.getReadMethod(), pd.getWriteMethod(), pd.getPropertyEditorClass()); 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 6606741ee4e0..971f009ac668 100644 --- a/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfo.java +++ b/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfo.java @@ -17,7 +17,6 @@ package org.springframework.beans; import java.awt.Image; - import java.beans.BeanDescriptor; import java.beans.BeanInfo; import java.beans.EventSetDescriptor; @@ -26,10 +25,8 @@ import java.beans.Introspector; import java.beans.MethodDescriptor; import java.beans.PropertyDescriptor; - import java.lang.reflect.Method; import java.lang.reflect.Modifier; - import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -84,8 +81,8 @@ class ExtendedBeanInfo implements BeanInfo { /** * Wrap the given {@link BeanInfo} instance; copy all its existing property descriptors - * locally, wrapping each in a custom {@link SimpleIndexedPropertyDescriptor indexed} or - * {@link SimpleNonIndexedPropertyDescriptor non-indexed} {@code PropertyDescriptor} + * locally, wrapping each in a custom {@link SimpleIndexedPropertyDescriptor indexed} + * or {@link SimplePropertyDescriptor non-indexed} {@code PropertyDescriptor} * variant that bypasses default JDK weak/soft reference management; then search * through its method descriptors to find any non-void returning write methods and * update or create the corresponding {@link PropertyDescriptor} for each one found. @@ -96,15 +93,16 @@ class ExtendedBeanInfo implements BeanInfo { */ public ExtendedBeanInfo(BeanInfo delegate) throws IntrospectionException { this.delegate = delegate; - for (PropertyDescriptor pd : delegate.getPropertyDescriptors()) { this.propertyDescriptors.add(pd instanceof IndexedPropertyDescriptor ? new SimpleIndexedPropertyDescriptor((IndexedPropertyDescriptor) pd) : - new SimpleNonIndexedPropertyDescriptor(pd)); + new SimplePropertyDescriptor(pd)); } - - for (Method method : findCandidateWriteMethods(delegate.getMethodDescriptors())) { - handleCandidateWriteMethod(method); + MethodDescriptor[] methodDescriptors = delegate.getMethodDescriptors(); + if (methodDescriptors != null) { + for (Method method : findCandidateWriteMethods(methodDescriptors)) { + handleCandidateWriteMethod(method); + } } } @@ -132,58 +130,44 @@ public static boolean isCandidateWriteMethod(Method method) { String methodName = method.getName(); Class[] parameterTypes = method.getParameterTypes(); int nParams = parameterTypes.length; - if (methodName.length() > 3 && methodName.startsWith("set") && - Modifier.isPublic(method.getModifiers()) && - ( - !void.class.isAssignableFrom(method.getReturnType()) || - Modifier.isStatic(method.getModifiers()) - ) && - (nParams == 1 || (nParams == 2 && parameterTypes[0].equals(int.class)))) { - return true; - } - return false; + return methodName.length() > 3 && methodName.startsWith("set") && Modifier.isPublic(method.getModifiers()) && + (!void.class.isAssignableFrom(method.getReturnType()) || Modifier.isStatic(method.getModifiers())) && + (nParams == 1 || (nParams == 2 && parameterTypes[0].equals(int.class))); } private void handleCandidateWriteMethod(Method method) throws IntrospectionException { int nParams = method.getParameterTypes().length; String propertyName = propertyNameFor(method); Class propertyType = method.getParameterTypes()[nParams-1]; - PropertyDescriptor existingPD = findExistingPropertyDescriptor(propertyName, propertyType); + PropertyDescriptor existingPd = findExistingPropertyDescriptor(propertyName, propertyType); if (nParams == 1) { - if (existingPD == null) { - this.propertyDescriptors.add( - new SimpleNonIndexedPropertyDescriptor(propertyName, null, method)); + if (existingPd == null) { + this.propertyDescriptors.add(new SimplePropertyDescriptor(propertyName, null, method)); } else { - existingPD.setWriteMethod(method); + existingPd.setWriteMethod(method); } } else if (nParams == 2) { - if (existingPD == null) { + if (existingPd == null) { this.propertyDescriptors.add( - new SimpleIndexedPropertyDescriptor( - propertyName, null, null, null, method)); + new SimpleIndexedPropertyDescriptor(propertyName, null, null, null, method)); } - else if (existingPD instanceof IndexedPropertyDescriptor) { - ((IndexedPropertyDescriptor)existingPD).setIndexedWriteMethod(method); + else if (existingPd instanceof IndexedPropertyDescriptor) { + ((IndexedPropertyDescriptor) existingPd).setIndexedWriteMethod(method); } else { - this.propertyDescriptors.remove(existingPD); - this.propertyDescriptors.add( - new SimpleIndexedPropertyDescriptor( - propertyName, existingPD.getReadMethod(), - existingPD.getWriteMethod(), null, method)); + this.propertyDescriptors.remove(existingPd); + this.propertyDescriptors.add(new SimpleIndexedPropertyDescriptor( + propertyName, existingPd.getReadMethod(), existingPd.getWriteMethod(), null, method)); } } else { - throw new IllegalArgumentException( - "write method must have exactly 1 or 2 parameters: " + method); + throw new IllegalArgumentException("Write method must have exactly 1 or 2 parameters: " + method); } } - private PropertyDescriptor findExistingPropertyDescriptor( - String propertyName, Class propertyType) { - + private PropertyDescriptor findExistingPropertyDescriptor(String propertyName, Class propertyType) { for (PropertyDescriptor pd : this.propertyDescriptors) { final Class candidateType; final String candidateName = pd.getName(); @@ -191,16 +175,14 @@ private PropertyDescriptor findExistingPropertyDescriptor( IndexedPropertyDescriptor ipd = (IndexedPropertyDescriptor) pd; candidateType = ipd.getIndexedPropertyType(); if (candidateName.equals(propertyName) && - (candidateType.equals(propertyType) || - candidateType.equals(propertyType.getComponentType()))) { + (candidateType.equals(propertyType) || candidateType.equals(propertyType.getComponentType()))) { return pd; } } else { candidateType = pd.getPropertyType(); if (candidateName.equals(propertyName) && - (candidateType.equals(propertyType) || - propertyType.equals(candidateType.getComponentType()))) { + (candidateType.equals(propertyType) || propertyType.equals(candidateType.getComponentType()))) { return pd; } } @@ -209,8 +191,7 @@ private PropertyDescriptor findExistingPropertyDescriptor( } private String propertyNameFor(Method method) { - return Introspector.decapitalize( - method.getName().substring(3, method.getName().length())); + return Introspector.decapitalize(method.getName().substring(3, method.getName().length())); } @@ -221,8 +202,7 @@ private String propertyNameFor(Method method) { * @see #ExtendedBeanInfo(BeanInfo) */ public PropertyDescriptor[] getPropertyDescriptors() { - return this.propertyDescriptors.toArray( - new PropertyDescriptor[this.propertyDescriptors.size()]); + return this.propertyDescriptors.toArray(new PropertyDescriptor[this.propertyDescriptors.size()]); } public BeanInfo[] getAdditionalBeanInfo() { @@ -255,31 +235,28 @@ public MethodDescriptor[] getMethodDescriptors() { } -class SimpleNonIndexedPropertyDescriptor extends PropertyDescriptor { +class SimplePropertyDescriptor extends PropertyDescriptor { private Method readMethod; + private Method writeMethod; - private Class propertyType; - private Class propertyEditorClass; + private Class propertyType; - public SimpleNonIndexedPropertyDescriptor(PropertyDescriptor original) - throws IntrospectionException { + private Class propertyEditorClass; + public SimplePropertyDescriptor(PropertyDescriptor original) throws IntrospectionException { this(original.getName(), original.getReadMethod(), original.getWriteMethod()); copyNonMethodProperties(original, this); } - public SimpleNonIndexedPropertyDescriptor(String propertyName, - Method readMethod, Method writeMethod) throws IntrospectionException { - + public SimplePropertyDescriptor(String propertyName, Method readMethod, Method writeMethod) throws IntrospectionException { super(propertyName, null, null); - this.setReadMethod(readMethod); - this.setWriteMethod(writeMethod); + this.readMethod = readMethod; + this.writeMethod = writeMethod; this.propertyType = findPropertyType(readMethod, writeMethod); } - @Override public Method getReadMethod() { return this.readMethod; @@ -305,7 +282,8 @@ public Class getPropertyType() { if (this.propertyType == null) { try { this.propertyType = findPropertyType(this.readMethod, this.writeMethod); - } catch (IntrospectionException ex) { + } + catch (IntrospectionException ex) { // ignore, as does PropertyDescriptor#getPropertyType } } @@ -322,7 +300,6 @@ public void setPropertyEditorClass(Class propertyEditorClass) { this.propertyEditorClass = propertyEditorClass; } - @Override public boolean equals(Object obj) { return PropertyDescriptorUtils.equals(this, obj); @@ -331,8 +308,7 @@ public boolean equals(Object obj) { @Override public String toString() { return String.format("%s[name=%s, propertyType=%s, readMethod=%s, writeMethod=%s]", - this.getClass().getSimpleName(), this.getName(), this.getPropertyType(), - this.readMethod, this.writeMethod); + getClass().getSimpleName(), getName(), getPropertyType(), this.readMethod, this.writeMethod); } } @@ -340,40 +316,37 @@ public String toString() { class SimpleIndexedPropertyDescriptor extends IndexedPropertyDescriptor { private Method readMethod; + private Method writeMethod; + private Class propertyType; - private Class propertyEditorClass; private Method indexedReadMethod; + private Method indexedWriteMethod; - private Class indexedPropertyType; + private Class indexedPropertyType; - public SimpleIndexedPropertyDescriptor(IndexedPropertyDescriptor original) - throws IntrospectionException { + private Class propertyEditorClass; + public SimpleIndexedPropertyDescriptor(IndexedPropertyDescriptor original) throws IntrospectionException { this(original.getName(), original.getReadMethod(), original.getWriteMethod(), original.getIndexedReadMethod(), original.getIndexedWriteMethod()); copyNonMethodProperties(original, this); } - public SimpleIndexedPropertyDescriptor(String propertyName, - Method readMethod, Method writeMethod, - Method indexedReadMethod, Method indexedWriteMethod) - throws IntrospectionException { + public SimpleIndexedPropertyDescriptor(String propertyName, Method readMethod, Method writeMethod, + Method indexedReadMethod, Method indexedWriteMethod) throws IntrospectionException { super(propertyName, null, null, null, null); - this.setReadMethod(readMethod); - this.setWriteMethod(writeMethod); + this.readMethod = readMethod; + this.writeMethod = writeMethod; this.propertyType = findPropertyType(readMethod, writeMethod); - - this.setIndexedReadMethod(indexedReadMethod); - this.setIndexedWriteMethod(indexedWriteMethod); - this.indexedPropertyType = findIndexedPropertyType( - this.getName(), this.propertyType, indexedReadMethod, indexedWriteMethod); + this.indexedReadMethod = indexedReadMethod; + this.indexedWriteMethod = indexedWriteMethod; + this.indexedPropertyType = findIndexedPropertyType(propertyName, this.propertyType, indexedReadMethod, indexedWriteMethod); } - @Override public Method getReadMethod() { return this.readMethod; @@ -399,7 +372,8 @@ public Class getPropertyType() { if (this.propertyType == null) { try { this.propertyType = findPropertyType(this.readMethod, this.writeMethod); - } catch (IntrospectionException ex) { + } + catch (IntrospectionException ex) { // ignore, as does IndexedPropertyDescriptor#getPropertyType } } @@ -431,9 +405,9 @@ public Class getIndexedPropertyType() { if (this.indexedPropertyType == null) { try { this.indexedPropertyType = findIndexedPropertyType( - this.getName(), this.getPropertyType(), - this.indexedReadMethod, this.indexedWriteMethod); - } catch (IntrospectionException ex) { + getName(), getPropertyType(), this.indexedReadMethod, this.indexedWriteMethod); + } + catch (IntrospectionException ex) { // ignore, as does IndexedPropertyDescriptor#getIndexedPropertyType } } @@ -450,26 +424,22 @@ public void setPropertyEditorClass(Class propertyEditorClass) { this.propertyEditorClass = propertyEditorClass; } - /* - * @see java.beans.IndexedPropertyDescriptor#equals(java.lang.Object) + * See java.beans.IndexedPropertyDescriptor#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } - if (obj != null && obj instanceof IndexedPropertyDescriptor) { IndexedPropertyDescriptor other = (IndexedPropertyDescriptor) obj; if (!compareMethods(getIndexedReadMethod(), other.getIndexedReadMethod())) { return false; } - if (!compareMethods(getIndexedWriteMethod(), other.getIndexedWriteMethod())) { return false; } - if (getIndexedPropertyType() != other.getIndexedPropertyType()) { return false; } @@ -482,9 +452,8 @@ public boolean equals(Object obj) { public String toString() { return String.format("%s[name=%s, propertyType=%s, indexedPropertyType=%s, " + "readMethod=%s, writeMethod=%s, indexedReadMethod=%s, indexedWriteMethod=%s]", - this.getClass().getSimpleName(), this.getName(), this.getPropertyType(), - this.getIndexedPropertyType(), this.readMethod, this.writeMethod, - this.indexedReadMethod, this.indexedWriteMethod); + getClass().getSimpleName(), getName(), getPropertyType(), getIndexedPropertyType(), + this.readMethod, this.writeMethod, this.indexedReadMethod, this.indexedWriteMethod); } } @@ -492,7 +461,7 @@ public String toString() { class PropertyDescriptorUtils { /* - * see java.beans.FeatureDescriptor#FeatureDescriptor(FeatureDescriptor) + * See java.beans.FeatureDescriptor#FeatureDescriptor(FeatureDescriptor) */ public static void copyNonMethodProperties(PropertyDescriptor source, PropertyDescriptor target) throws IntrospectionException { @@ -520,11 +489,8 @@ public static void copyNonMethodProperties(PropertyDescriptor source, PropertyDe /* * See PropertyDescriptor#findPropertyType */ - public static Class findPropertyType(Method readMethod, Method writeMethod) - throws IntrospectionException { - + public static Class findPropertyType(Method readMethod, Method writeMethod) throws IntrospectionException { Class propertyType = null; - if (readMethod != null) { Class[] params = readMethod.getParameterTypes(); if (params.length != 0) { @@ -554,8 +520,7 @@ public static Class findPropertyType(Method readMethod, Method writeMethod) * See IndexedPropertyDescriptor#findIndexedPropertyType */ public static Class findIndexedPropertyType(String name, Class propertyType, - Method indexedReadMethod, Method indexedWriteMethod) - throws IntrospectionException { + Method indexedReadMethod, Method indexedWriteMethod) throws IntrospectionException { Class indexedPropertyType = null; @@ -605,7 +570,6 @@ public static Class findIndexedPropertyType(String name, Class propertyTyp * return {@code true} if they are objects are equivalent, i.e. both are {@code * PropertyDescriptor}s whose read method, write method, property types, property * editor and flags are equivalent. - * * @see PropertyDescriptor#equals(Object) */ public static boolean equals(PropertyDescriptor pd1, Object obj) { @@ -633,13 +597,12 @@ public static boolean equals(PropertyDescriptor pd1, Object obj) { } /* - * see PropertyDescriptor#compareMethods + * See PropertyDescriptor#compareMethods */ public static boolean compareMethods(Method a, Method b) { if ((a == null) != (b == null)) { return false; } - if (a != null && b != null) { if (!a.equals(b)) { return false; @@ -653,7 +616,6 @@ public static boolean compareMethods(Method a, Method b) { /** * Sorts PropertyDescriptor instances alpha-numerically to emulate the behavior of * {@link java.beans.BeanInfo#getPropertyDescriptors()}. - * * @see ExtendedBeanInfo#propertyDescriptors */ class PropertyDescriptorComparator implements Comparator { diff --git a/spring-beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java b/spring-beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java index 0f0c5931627d..096bae5a6177 100644 --- a/spring-beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java +++ b/spring-beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,32 +31,32 @@ import org.springframework.util.StringUtils; /** - * Extension of the standard JavaBeans PropertyDescriptor class, - * overriding {@code getPropertyType()} such that a generically - * declared type will be resolved against the containing bean class. + * Extension of the standard JavaBeans {@link PropertyDescriptor} class, + * overriding {@code getPropertyType()} such that a generically declared + * type variable will be resolved against the containing bean class. * * @author Juergen Hoeller * @since 2.5.2 */ class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor { - private final Class beanClass; + private final Class beanClass; private final Method readMethod; private final Method writeMethod; - private final Class propertyEditorClass; + private final Class propertyEditorClass; private volatile Set ambiguousWriteMethods; - private Class propertyType; + private Class propertyType; private MethodParameter writeMethodParameter; - public GenericTypeAwarePropertyDescriptor(Class beanClass, String propertyName, - Method readMethod, Method writeMethod, Class propertyEditorClass) + public GenericTypeAwarePropertyDescriptor(Class beanClass, String propertyName, + Method readMethod, Method writeMethod, Class propertyEditorClass) throws IntrospectionException { super(propertyName, null, null); @@ -69,8 +69,11 @@ public GenericTypeAwarePropertyDescriptor(Class beanClass, String propertyName, // Fallback: Original JavaBeans introspection might not have found matching setter // method due to lack of bridge method resolution, in case of the getter using a // covariant return type whereas the setter is defined for the concrete property type. - writeMethodToUse = ClassUtils.getMethodIfAvailable(this.beanClass, - "set" + StringUtils.capitalize(getName()), readMethodToUse.getReturnType()); + Method candidate = ClassUtils.getMethodIfAvailable( + this.beanClass, "set" + StringUtils.capitalize(getName()), (Class[]) null); + if (candidate != null && candidate.getParameterTypes().length == 1) { + writeMethodToUse = candidate; + } } this.readMethod = readMethodToUse; this.writeMethod = writeMethodToUse; @@ -118,12 +121,12 @@ public Method getWriteMethodForActualAccess() { } @Override - public Class getPropertyEditorClass() { + public Class getPropertyEditorClass() { return this.propertyEditorClass; } @Override - public synchronized Class getPropertyType() { + public synchronized Class getPropertyType() { if (this.propertyType == null) { if (this.readMethod != null) { this.propertyType = GenericTypeResolver.resolveReturnType(this.readMethod, this.beanClass); diff --git a/spring-beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java b/spring-beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java index 24bc57e9e325..1c59360b6a8c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java +++ b/spring-beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java @@ -187,6 +187,9 @@ public T convertIfNecessary(String propertyName, Object oldValue, Object new // Try to apply some standard type conversion rules if appropriate. if (convertedValue != null) { + if (Object.class.equals(requiredType)) { + return (T) convertedValue; + } if (requiredType.isArray()) { // Array required -> apply appropriate conversion of elements. if (convertedValue instanceof String && Enum.class.isAssignableFrom(requiredType.getComponentType())) { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactoryUtils.java b/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactoryUtils.java index 657bb1ebd1dc..6952087521db 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactoryUtils.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactoryUtils.java @@ -139,7 +139,7 @@ public static String[] beanNamesIncludingAncestors(ListableBeanFactory lbf) { * @param type the type that beans must match * @return the array of matching bean names, or an empty array if none */ - public static String[] beanNamesForTypeIncludingAncestors(ListableBeanFactory lbf, Class type) { + public static String[] beanNamesForTypeIncludingAncestors(ListableBeanFactory lbf, Class type) { Assert.notNull(lbf, "ListableBeanFactory must not be null"); String[] result = lbf.getBeanNamesForType(type); if (lbf instanceof HierarchicalBeanFactory) { @@ -181,7 +181,7 @@ public static String[] beanNamesForTypeIncludingAncestors(ListableBeanFactory lb * @return the array of matching bean names, or an empty array if none */ public static String[] beanNamesForTypeIncludingAncestors( - ListableBeanFactory lbf, Class type, boolean includeNonSingletons, boolean allowEagerInit) { + ListableBeanFactory lbf, Class type, boolean includeNonSingletons, boolean allowEagerInit) { Assert.notNull(lbf, "ListableBeanFactory must not be null"); String[] result = lbf.getBeanNamesForType(type, includeNonSingletons, allowEagerInit); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java index 6775dd78edcd..ecf5044f04d8 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -59,6 +59,7 @@ import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; /** * {@link org.springframework.beans.factory.config.BeanPostProcessor} implementation @@ -121,8 +122,8 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean private final Map, Constructor[]> candidateConstructorsCache = new ConcurrentHashMap, Constructor[]>(64); - private final Map, InjectionMetadata> injectionMetadataCache = - new ConcurrentHashMap, InjectionMetadata>(64); + private final Map injectionMetadataCache = + new ConcurrentHashMap(64); /** @@ -214,7 +215,7 @@ public void setBeanFactory(BeanFactory beanFactory) throws BeansException { public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class beanType, String beanName) { if (beanType != null) { - InjectionMetadata metadata = findAutowiringMetadata(beanType); + InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType); metadata.checkConfigMembers(beanDefinition); } } @@ -280,7 +281,7 @@ else if (candidate.getParameterTypes().length == 0) { public PropertyValues postProcessPropertyValues( PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException { - InjectionMetadata metadata = findAutowiringMetadata(bean.getClass()); + InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass()); try { metadata.inject(bean, beanName, pvs); } @@ -298,7 +299,7 @@ public PropertyValues postProcessPropertyValues( */ public void processInjection(Object bean) throws BeansException { Class clazz = bean.getClass(); - InjectionMetadata metadata = findAutowiringMetadata(clazz); + InjectionMetadata metadata = findAutowiringMetadata(clazz.getName(), clazz); try { metadata.inject(bean, null, null); } @@ -308,15 +309,17 @@ public void processInjection(Object bean) throws BeansException { } - private InjectionMetadata findAutowiringMetadata(Class clazz) { + private InjectionMetadata findAutowiringMetadata(String beanName, Class clazz) { // Quick check on the concurrent map first, with minimal locking. - InjectionMetadata metadata = this.injectionMetadataCache.get(clazz); + // Fall back to class name as cache key, for backwards compatibility with custom callers. + String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName()); + InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey); if (metadata == null) { synchronized (this.injectionMetadataCache) { - metadata = this.injectionMetadataCache.get(clazz); + metadata = this.injectionMetadataCache.get(cacheKey); if (metadata == null) { metadata = buildAutowiringMetadata(clazz); - this.injectionMetadataCache.put(clazz, metadata); + this.injectionMetadataCache.put(cacheKey, metadata); } } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InjectionMetadata.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InjectionMetadata.java index fe3e3b15efd7..26a0d0b7f749 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InjectionMetadata.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InjectionMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,14 +48,14 @@ public class InjectionMetadata { private final Log logger = LogFactory.getLog(InjectionMetadata.class); - private final Class targetClass; + private final Class targetClass; private final Collection injectedElements; private volatile Set checkedElements; - public InjectionMetadata(Class targetClass, Collection elements) { + public InjectionMetadata(Class targetClass, Collection elements) { this.targetClass = targetClass; this.injectedElements = elements; } @@ -110,7 +110,7 @@ public final Member getMember() { return this.member; } - protected final Class getResourceType() { + protected final Class getResourceType() { if (this.isField) { return ((Field) this.member).getType(); } @@ -122,16 +122,16 @@ else if (this.pd != null) { } } - protected final void checkResourceType(Class resourceType) { + protected final void checkResourceType(Class resourceType) { if (this.isField) { - Class fieldType = ((Field) this.member).getType(); + Class fieldType = ((Field) this.member).getType(); if (!(resourceType.isAssignableFrom(fieldType) || fieldType.isAssignableFrom(resourceType))) { throw new IllegalStateException("Specified field type [" + fieldType + "] is incompatible with resource type [" + resourceType.getName() + "]"); } } else { - Class paramType = + Class paramType = (this.pd != null ? this.pd.getPropertyType() : ((Method) this.member).getParameterTypes()[0]); if (!(resourceType.isAssignableFrom(paramType) || paramType.isAssignableFrom(resourceType))) { throw new IllegalStateException("Specified parameter type [" + paramType + diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java index 657ee22666b0..76c36efbba96 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -233,8 +233,9 @@ protected boolean checkQualifier( } if (qualifier == null) { Annotation targetAnnotation = null; - if (bd.getResolvedFactoryMethod() != null) { - targetAnnotation = AnnotationUtils.getAnnotation(bd.getResolvedFactoryMethod(), type); + Method resolvedFactoryMethod = bd.getResolvedFactoryMethod(); + if (resolvedFactoryMethod != null) { + targetAnnotation = AnnotationUtils.getAnnotation(resolvedFactoryMethod, type); } if (targetAnnotation == null) { // look for matching annotation on the target class diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/ConstructorArgumentValues.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/ConstructorArgumentValues.java index 1e59e7d134c7..5bf2fffcdf98 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/ConstructorArgumentValues.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/ConstructorArgumentValues.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -146,7 +146,7 @@ public boolean hasIndexedArgumentValue(int index) { * untyped values only) * @return the ValueHolder for the argument, or {@code null} if none set */ - public ValueHolder getIndexedArgumentValue(int index, Class requiredType) { + public ValueHolder getIndexedArgumentValue(int index, Class requiredType) { return getIndexedArgumentValue(index, requiredType, null); } @@ -159,7 +159,7 @@ public ValueHolder getIndexedArgumentValue(int index, Class requiredType) { * unnamed values only) * @return the ValueHolder for the argument, or {@code null} if none set */ - public ValueHolder getIndexedArgumentValue(int index, Class requiredType, String requiredName) { + public ValueHolder getIndexedArgumentValue(int index, Class requiredType, String requiredName) { Assert.isTrue(index >= 0, "Index must not be negative"); ValueHolder valueHolder = this.indexedArgumentValues.get(index); if (valueHolder != null && @@ -247,7 +247,7 @@ private void addOrMergeGenericArgumentValue(ValueHolder newValue) { * @param requiredType the type to match * @return the ValueHolder for the argument, or {@code null} if none set */ - public ValueHolder getGenericArgumentValue(Class requiredType) { + public ValueHolder getGenericArgumentValue(Class requiredType) { return getGenericArgumentValue(requiredType, null, null); } @@ -257,7 +257,7 @@ public ValueHolder getGenericArgumentValue(Class requiredType) { * @param requiredName the name to match * @return the ValueHolder for the argument, or {@code null} if none set */ - public ValueHolder getGenericArgumentValue(Class requiredType, String requiredName) { + public ValueHolder getGenericArgumentValue(Class requiredType, String requiredName) { return getGenericArgumentValue(requiredType, requiredName, null); } @@ -273,7 +273,7 @@ public ValueHolder getGenericArgumentValue(Class requiredType, String requiredNa * in the current resolution process and should therefore not be returned again * @return the ValueHolder for the argument, or {@code null} if none found */ - public ValueHolder getGenericArgumentValue(Class requiredType, String requiredName, Set usedValueHolders) { + public ValueHolder getGenericArgumentValue(Class requiredType, String requiredName, Set usedValueHolders) { for (ValueHolder valueHolder : this.genericArgumentValues) { if (usedValueHolders != null && usedValueHolders.contains(valueHolder)) { continue; @@ -309,10 +309,10 @@ public List getGenericArgumentValues() { * Look for an argument value that either corresponds to the given index * in the constructor argument list or generically matches by type. * @param index the index in the constructor argument list - * @param requiredType the type to match + * @param requiredType the parameter type to match * @return the ValueHolder for the argument, or {@code null} if none set */ - public ValueHolder getArgumentValue(int index, Class requiredType) { + public ValueHolder getArgumentValue(int index, Class requiredType) { return getArgumentValue(index, requiredType, null, null); } @@ -320,11 +320,11 @@ public ValueHolder getArgumentValue(int index, Class requiredType) { * Look for an argument value that either corresponds to the given index * in the constructor argument list or generically matches by type. * @param index the index in the constructor argument list - * @param requiredType the type to match - * @param requiredName the name to match + * @param requiredType the parameter type to match + * @param requiredName the parameter name to match * @return the ValueHolder for the argument, or {@code null} if none set */ - public ValueHolder getArgumentValue(int index, Class requiredType, String requiredName) { + public ValueHolder getArgumentValue(int index, Class requiredType, String requiredName) { return getArgumentValue(index, requiredType, requiredName, null); } @@ -332,15 +332,17 @@ public ValueHolder getArgumentValue(int index, Class requiredType, String requir * Look for an argument value that either corresponds to the given index * in the constructor argument list or generically matches by type. * @param index the index in the constructor argument list - * @param requiredType the type to match (can be {@code null} to find - * an untyped argument value) + * @param requiredType the parameter type to match (can be {@code null} + * to find an untyped argument value) + * @param requiredName the parameter name to match (can be {@code null} + * to find an unnamed argument value) * @param usedValueHolders a Set of ValueHolder objects that have already * been used in the current resolution process and should therefore not * be returned again (allowing to return the next generic argument match * in case of multiple generic argument values of the same type) * @return the ValueHolder for the argument, or {@code null} if none set */ - public ValueHolder getArgumentValue(int index, Class requiredType, String requiredName, Set usedValueHolders) { + public ValueHolder getArgumentValue(int index, Class requiredType, String requiredName, Set usedValueHolders) { Assert.isTrue(index >= 0, "Index must not be negative"); ValueHolder valueHolder = getIndexedArgumentValue(index, requiredType, requiredName); if (valueHolder == null) { 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 2693f8246c50..35bdfd904b23 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-2012 the original author or authors. + * Copyright 2002-2013 the original author 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 DependencyDescriptor implements Serializable { private transient Field field; - private Class declaringClass; + private Class declaringClass; private String methodName; diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/TypedStringValue.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/TypedStringValue.java index e4f27fec8b2e..40f7e3f24e9b 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/TypedStringValue.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/TypedStringValue.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -61,7 +61,7 @@ public TypedStringValue(String value) { * @param value the String value * @param targetType the type to convert to */ - public TypedStringValue(String value, Class targetType) { + public TypedStringValue(String value, Class targetType) { setValue(value); setTargetType(targetType); } @@ -101,7 +101,7 @@ public String getValue() { * for example in BeanFactoryPostProcessors. * @see PropertyPlaceholderConfigurer */ - public void setTargetType(Class targetType) { + public void setTargetType(Class targetType) { Assert.notNull(targetType, "'targetType' must not be null"); this.targetType = targetType; } @@ -109,7 +109,7 @@ public void setTargetType(Class targetType) { /** * Return the type to convert to. */ - public Class getTargetType() { + public Class getTargetType() { Object targetTypeValue = this.targetType; if (!(targetTypeValue instanceof Class)) { throw new IllegalStateException("Typed String value does not carry a resolved target type"); @@ -153,11 +153,11 @@ public boolean hasTargetType() { * @return the resolved type to convert to * @throws ClassNotFoundException if the type cannot be resolved */ - public Class resolveTargetType(ClassLoader classLoader) throws ClassNotFoundException { + public Class resolveTargetType(ClassLoader classLoader) throws ClassNotFoundException { if (this.targetType == null) { return null; } - Class resolvedClass = ClassUtils.forName(getTargetTypeName(), classLoader); + Class resolvedClass = ClassUtils.forName(getTargetTypeName(), classLoader); this.targetType = resolvedClass; return resolvedClass; } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index 600524d4da0f..74e0ce73ed06 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -63,7 +63,7 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.ConfigurableBeanFactory; -import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder; +import org.springframework.beans.factory.config.ConstructorArgumentValues; import org.springframework.beans.factory.config.DependencyDescriptor; import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor; import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor; @@ -569,7 +569,7 @@ else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { } @Override - protected Class predictBeanType(String beanName, RootBeanDefinition mbd, Class... typesToMatch) { + protected Class predictBeanType(String beanName, RootBeanDefinition mbd, Class... typesToMatch) { Class targetType = mbd.getTargetType(); if (targetType == null) { targetType = (mbd.getFactoryMethodName() != null ? getTypeForFactoryMethod(beanName, mbd, typesToMatch) : @@ -632,12 +632,6 @@ protected Class getTypeForFactoryMethod(String beanName, RootBeanDefinition m return null; } - List argumentValues = mbd.getConstructorArgumentValues().getGenericArgumentValues(); - Object[] args = new Object[argumentValues.size()]; - for (int i = 0; i < args.length; i++) { - args[i] = argumentValues.get(i).getValue(); - } - // If all factory methods have the same return type, return that type. // Can't clearly figure out exact method due to type converting / autowiring! int minNrOfArgs = mbd.getConstructorArgumentValues().getArgumentCount(); @@ -647,9 +641,45 @@ protected Class getTypeForFactoryMethod(String beanName, RootBeanDefinition m if (Modifier.isStatic(factoryMethod.getModifiers()) == isStatic && factoryMethod.getName().equals(mbd.getFactoryMethodName()) && factoryMethod.getParameterTypes().length >= minNrOfArgs) { - Class returnType = GenericTypeResolver.resolveReturnTypeForGenericMethod(factoryMethod, args); - if (returnType != null) { - returnTypes.add(returnType); + // No declared type variables to inspect, so just process the standard return type. + if (factoryMethod.getTypeParameters().length > 0) { + try { + // Fully resolve parameter names and argument values. + Class[] paramTypes = factoryMethod.getParameterTypes(); + String[] paramNames = null; + ParameterNameDiscoverer pnd = getParameterNameDiscoverer(); + if (pnd != null) { + paramNames = pnd.getParameterNames(factoryMethod); + } + ConstructorArgumentValues cav = mbd.getConstructorArgumentValues(); + Set usedValueHolders = + new HashSet(paramTypes.length); + Object[] args = new Object[paramTypes.length]; + for (int i = 0; i < args.length; i++) { + ConstructorArgumentValues.ValueHolder valueHolder = cav.getArgumentValue( + i, paramTypes[i], (paramNames != null ? paramNames[i] : null), usedValueHolders); + if (valueHolder == null) { + valueHolder = cav.getGenericArgumentValue(null, null, usedValueHolders); + } + if (valueHolder != null) { + args[i] = valueHolder.getValue(); + usedValueHolders.add(valueHolder); + } + } + Class returnType = AutowireUtils.resolveReturnTypeForFactoryMethod( + factoryMethod, args, getBeanClassLoader()); + if (returnType != null) { + returnTypes.add(returnType); + } + } + catch (Throwable ex) { + if (logger.isDebugEnabled()) { + logger.debug("Failed to resolve generic return type for factory method: " + ex); + } + } + } + else { + returnTypes.add(factoryMethod.getReturnType()); } } } 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 34ce6b59b121..aae92fd8fee5 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 @@ -275,77 +275,83 @@ protected T doGetBean( markBeanAsCreated(beanName); } - final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); - checkMergedBeanDefinition(mbd, beanName, args); - - // Guarantee initialization of beans that the current bean depends on. - String[] dependsOn = mbd.getDependsOn(); - if (dependsOn != null) { - for (String dependsOnBean : dependsOn) { - getBean(dependsOnBean); - registerDependentBean(dependsOnBean, beanName); - } - } - - // Create bean instance. - if (mbd.isSingleton()) { - sharedInstance = getSingleton(beanName, new ObjectFactory() { - public Object getObject() throws BeansException { - try { - return createBean(beanName, mbd, args); - } - catch (BeansException ex) { - // Explicitly remove instance from singleton cache: It might have been put there - // eagerly by the creation process, to allow for circular reference resolution. - // Also remove any beans that received a temporary reference to the bean. - destroySingleton(beanName); - throw ex; - } + try { + final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); + checkMergedBeanDefinition(mbd, beanName, args); + + // Guarantee initialization of beans that the current bean depends on. + String[] dependsOn = mbd.getDependsOn(); + if (dependsOn != null) { + for (String dependsOnBean : dependsOn) { + getBean(dependsOnBean); + registerDependentBean(dependsOnBean, beanName); } - }); - bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); - } - - else if (mbd.isPrototype()) { - // It's a prototype -> create a new instance. - Object prototypeInstance = null; - try { - beforePrototypeCreation(beanName); - prototypeInstance = createBean(beanName, mbd, args); - } - finally { - afterPrototypeCreation(beanName); } - bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); - } - else { - String scopeName = mbd.getScope(); - final Scope scope = this.scopes.get(scopeName); - if (scope == null) { - throw new IllegalStateException("No Scope registered for scope '" + scopeName + "'"); - } - try { - Object scopedInstance = scope.get(beanName, new ObjectFactory() { + // Create bean instance. + if (mbd.isSingleton()) { + sharedInstance = getSingleton(beanName, new ObjectFactory() { public Object getObject() throws BeansException { - beforePrototypeCreation(beanName); try { return createBean(beanName, mbd, args); } - finally { - afterPrototypeCreation(beanName); + catch (BeansException ex) { + // Explicitly remove instance from singleton cache: It might have been put there + // eagerly by the creation process, to allow for circular reference resolution. + // Also remove any beans that received a temporary reference to the bean. + destroySingleton(beanName); + throw ex; } } }); - bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); + bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); + } + + else if (mbd.isPrototype()) { + // It's a prototype -> create a new instance. + Object prototypeInstance = null; + try { + beforePrototypeCreation(beanName); + prototypeInstance = createBean(beanName, mbd, args); + } + finally { + afterPrototypeCreation(beanName); + } + bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); } - catch (IllegalStateException ex) { - throw new BeanCreationException(beanName, - "Scope '" + scopeName + "' is not active for the current thread; " + - "consider defining a scoped proxy for this bean if you intend to refer to it from a singleton", - ex); + + else { + String scopeName = mbd.getScope(); + final Scope scope = this.scopes.get(scopeName); + if (scope == null) { + throw new IllegalStateException("No Scope registered for scope '" + scopeName + "'"); + } + try { + Object scopedInstance = scope.get(beanName, new ObjectFactory() { + public Object getObject() throws BeansException { + beforePrototypeCreation(beanName); + try { + return createBean(beanName, mbd, args); + } + finally { + afterPrototypeCreation(beanName); + } + } + }); + bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); + } + catch (IllegalStateException ex) { + throw new BeanCreationException(beanName, + "Scope '" + scopeName + "' is not active for the current thread; " + + "consider defining a scoped proxy for this bean if you intend to refer to it from a singleton", + ex); + } } } + catch (BeansException ex) { + cleanupAfterBeanCreationFailure(beanName); + throw ex; + } } // Check if required type matches the type of the actual bean instance. @@ -1388,6 +1394,14 @@ protected void markBeanAsCreated(String beanName) { this.alreadyCreated.put(beanName, Boolean.TRUE); } + /** + * Perform appropriate cleanup of cached metadata after bean creation failed. + * @param beanName the name of the bean + */ + protected void cleanupAfterBeanCreationFailure(String beanName) { + this.alreadyCreated.remove(beanName); + } + /** * Determine whether the specified bean is eligible for having * its bean definition metadata cached. diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java index f8ffd5798b52..81358b094309 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,20 +23,27 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; import java.lang.reflect.Proxy; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; import java.util.Arrays; import java.util.Comparator; import java.util.Set; +import org.springframework.beans.BeanMetadataElement; import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.config.TypedStringValue; +import org.springframework.util.Assert; import org.springframework.util.ClassUtils; /** - * Utility class that contains various methods useful for - * the implementation of autowire-capable bean factories. + * Utility class that contains various methods useful for the implementation of + * autowire-capable bean factories. * * @author Juergen Hoeller * @author Mark Fisher + * @author Sam Brannen * @since 1.1.2 * @see AbstractAutowireCapableBeanFactory */ @@ -117,8 +124,8 @@ public static boolean isExcludedFromDependencyCheck(PropertyDescriptor pd) { public static boolean isSetterDefinedInInterface(PropertyDescriptor pd, Set interfaces) { Method setter = pd.getWriteMethod(); if (setter != null) { - Class targetClass = setter.getDeclaringClass(); - for (Class ifc : interfaces) { + Class targetClass = setter.getDeclaringClass(); + for (Class ifc : interfaces) { if (ifc.isAssignableFrom(targetClass) && ClassUtils.hasMethod(ifc, setter.getName(), setter.getParameterTypes())) { return true; @@ -135,7 +142,7 @@ public static boolean isSetterDefinedInInterface(PropertyDescriptor pd, Set requiredType) { if (autowiringValue instanceof ObjectFactory && !requiredType.isInstance(autowiringValue)) { ObjectFactory factory = (ObjectFactory) autowiringValue; if (autowiringValue instanceof Serializable && requiredType.isInterface()) { @@ -149,6 +156,124 @@ public static Object resolveAutowiringValue(Object autowiringValue, Class requir return autowiringValue; } + /** + * Determine the target type for the generic return type of the given + * generic factory method, where formal type variables are declared + * on the given method itself. + *

For example, given a factory method with the following signature, + * if {@code resolveReturnTypeForFactoryMethod()} is invoked with the reflected + * method for {@code creatProxy()} and an {@code Object[]} array containing + * {@code MyService.class}, {@code resolveReturnTypeForFactoryMethod()} will + * infer that the target return type is {@code MyService}. + *

{@code public static  T createProxy(Class clazz)}
+ *

Possible Return Values

+ *
    + *
  • the target return type, if it can be inferred
  • + *
  • the {@linkplain Method#getReturnType() standard return type}, if + * the given {@code method} does not declare any {@linkplain + * Method#getTypeParameters() formal type variables}
  • + *
  • the {@linkplain Method#getReturnType() standard return type}, if the + * target return type cannot be inferred (e.g., due to type erasure)
  • + *
  • {@code null}, if the length of the given arguments array is shorter + * than the length of the {@linkplain + * Method#getGenericParameterTypes() formal argument list} for the given + * method
  • + *
+ * @param method the method to introspect (never {@code null}) + * @param args the arguments that will be supplied to the method when it is + * invoked (never {@code null}) + * @param classLoader the ClassLoader to resolve class names against, if necessary + * (never {@code null}) + * @return the resolved target return type, the standard return type, or {@code null} + * @since 3.2.5 + */ + public static Class resolveReturnTypeForFactoryMethod(Method method, Object[] args, ClassLoader classLoader) { + Assert.notNull(method, "Method must not be null"); + Assert.notNull(args, "Argument array must not be null"); + Assert.notNull(classLoader, "ClassLoader must not be null"); + + TypeVariable[] declaredTypeVariables = method.getTypeParameters(); + Type genericReturnType = method.getGenericReturnType(); + Type[] methodParameterTypes = method.getGenericParameterTypes(); + Assert.isTrue(args.length == methodParameterTypes.length, "Argument array does not match parameter count"); + + // Ensure that the type variable (e.g., T) is declared directly on the method + // itself (e.g., via ), not on the enclosing class or interface. + boolean locallyDeclaredTypeVariableMatchesReturnType = false; + for (TypeVariable currentTypeVariable : declaredTypeVariables) { + if (currentTypeVariable.equals(genericReturnType)) { + locallyDeclaredTypeVariableMatchesReturnType = true; + break; + } + } + + if (locallyDeclaredTypeVariableMatchesReturnType) { + for (int i = 0; i < methodParameterTypes.length; i++) { + Type methodParameterType = methodParameterTypes[i]; + Object arg = args[i]; + if (methodParameterType.equals(genericReturnType)) { + if (arg instanceof TypedStringValue) { + TypedStringValue typedValue = ((TypedStringValue) arg); + if (typedValue.hasTargetType()) { + return typedValue.getTargetType(); + } + try { + return typedValue.resolveTargetType(classLoader); + } + catch (ClassNotFoundException ex) { + throw new IllegalStateException("Failed to resolve value type [" + + typedValue.getTargetTypeName() + "] for factory method argument", ex); + } + } + // Only consider argument type if it is a simple value... + if (arg != null && !(arg instanceof BeanMetadataElement)) { + return arg.getClass(); + } + return method.getReturnType(); + } + else if (methodParameterType instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) methodParameterType; + Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + for (Type typeArg : actualTypeArguments) { + if (typeArg.equals(genericReturnType)) { + if (arg instanceof Class) { + return (Class) arg; + } + else { + String className = null; + if (arg instanceof String) { + className = (String) arg; + } + else if (arg instanceof TypedStringValue) { + TypedStringValue typedValue = ((TypedStringValue) arg); + String targetTypeName = typedValue.getTargetTypeName(); + if (targetTypeName == null || Class.class.getName().equals(targetTypeName)) { + className = typedValue.getValue(); + } + } + if (className != null) { + try { + return ClassUtils.forName(className, classLoader); + } + catch (ClassNotFoundException ex) { + throw new IllegalStateException("Could not resolve class name [" + arg + + "] for factory method argument", ex); + } + } + // Consider adding logic to determine the class of the typeArg, if possible. + // For now, just fall back... + return method.getReturnType(); + } + } + } + } + } + } + + // Fall back... + return method.getReturnType(); + } + /** * Reflective InvocationHandler for lazy access to the current target object. diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReader.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReader.java index f5d1e7778123..bd8b8f51b506 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReader.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,7 +22,7 @@ /** * Simple interface for bean definition readers. - * Specifies load methods with Resource parameters. + * Specifies load methods with Resource and String location parameters. * *

Concrete bean definition readers can of course add additional * load and register methods for bean definitions, specific to @@ -45,23 +45,23 @@ public interface BeanDefinitionReader { */ BeanDefinitionRegistry getRegistry(); - /** - * Return the resource loader to use for resource locations. - * Can be checked for the ResourcePatternResolver interface and cast - * accordingly, for loading multiple resources for a given resource pattern. - *

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

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

There is also a {@code loadBeanDefinitions(String)} method available, - * for loading bean definitions from a resource location (or location pattern). - * This is a convenience to avoid explicit ResourceLoader handling. - * @see #loadBeanDefinitions(String) - * @see org.springframework.core.io.support.ResourcePatternResolver - */ + /** + * Return the resource loader to use for resource locations. + * Can be checked for the ResourcePatternResolver interface and cast + * accordingly, for loading multiple resources for a given resource pattern. + *

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

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

There is also a {@code loadBeanDefinitions(String)} method available, + * for loading bean definitions from a resource location (or location pattern). + * This is a convenience to avoid explicit ResourceLoader handling. + * @see #loadBeanDefinitions(String) + * @see org.springframework.core.io.support.ResourcePatternResolver + */ ResourceLoader getResourceLoader(); /** diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValueResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValueResolver.java index 4ee6d218fa61..96400677848f 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValueResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValueResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -128,7 +128,7 @@ else if (value instanceof BeanDefinition) { else if (value instanceof ManagedArray) { // May need to resolve contained runtime references. ManagedArray array = (ManagedArray) value; - Class elementType = array.resolvedElementType; + Class elementType = array.resolvedElementType; if (elementType == null) { String elementTypeName = array.getElementTypeName(); if (StringUtils.hasText(elementTypeName)) { @@ -271,7 +271,7 @@ private Object resolveInnerBean(Object argName, String innerBeanName, BeanDefini Object innerBean = this.beanFactory.createBean(actualInnerBeanName, mbd, null); this.beanFactory.registerContainedBean(actualInnerBeanName, this.beanName); if (innerBean instanceof FactoryBean) { - boolean synthetic = (mbd != null && mbd.isSynthetic()); + boolean synthetic = mbd.isSynthetic(); return this.beanFactory.getObjectFromFactoryBean((FactoryBean) innerBean, actualInnerBeanName, !synthetic); } else { @@ -335,7 +335,7 @@ private Object resolveReference(Object argName, RuntimeBeanReference ref) { /** * For each element in the managed array, resolve reference if necessary. */ - private Object resolveManagedArray(Object argName, List ml, Class elementType) { + private Object resolveManagedArray(Object argName, List ml, Class elementType) { Object resolved = Array.newInstance(elementType, ml.size()); for (int i = 0; i < ml.size(); i++) { Array.set(resolved, i, diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java index 384866ffdd78..f290d51b1869 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -151,7 +151,7 @@ public BeanWrapper autowireConstructor( // Take specified constructors, if any. Constructor[] candidates = chosenCtors; if (candidates == null) { - Class beanClass = mbd.getBeanClass(); + Class beanClass = mbd.getBeanClass(); try { candidates = (mbd.isNonPublicAccessAllowed() ? beanClass.getDeclaredConstructors() : beanClass.getConstructors()); @@ -169,7 +169,7 @@ public BeanWrapper autowireConstructor( for (int i = 0; i < candidates.length; i++) { Constructor candidate = candidates[i]; - Class[] paramTypes = candidate.getParameterTypes(); + Class[] paramTypes = candidate.getParameterTypes(); if (constructorToUse != null && argsToUse.length > paramTypes.length) { // Already found greedy constructor that can be satisfied -> @@ -341,7 +341,7 @@ public BeanWrapper instantiateUsingFactoryMethod(final String beanName, final Ro this.beanFactory.initBeanWrapper(bw); Object factoryBean; - Class factoryClass; + Class factoryClass; boolean isStatic; String factoryBeanName = mbd.getFactoryBeanName(); @@ -399,7 +399,7 @@ public BeanWrapper instantiateUsingFactoryMethod(final String beanName, final Ro factoryClass = ClassUtils.getUserClass(factoryClass); Method[] rawCandidates; - final Class factoryClazz = factoryClass; + final Class factoryClazz = factoryClass; if (System.getSecurityManager() != null) { rawCandidates = AccessController.doPrivileged(new PrivilegedAction() { public Method[] run() { @@ -445,7 +445,7 @@ public Method[] run() { for (int i = 0; i < candidates.length; i++) { Method candidate = candidates[i]; - Class[] paramTypes = candidate.getParameterTypes(); + Class[] paramTypes = candidate.getParameterTypes(); if (paramTypes.length >= minNrOfArgs) { ArgumentsHolder argsHolder; @@ -503,7 +503,15 @@ public Method[] run() { minTypeDiffWeight = typeDiffWeight; ambiguousFactoryMethods = null; } - else if (factoryMethodToUse != null && typeDiffWeight == minTypeDiffWeight) { + // Find out about ambiguity: In case of the same type difference weight + // for methods with the same number of parameters, collect such candidates + // and eventually raise an ambiguity exception. + // However, only perform that check in non-lenient constructor resolution mode, + // and explicitly ignore overridden methods (with the same parameter signature). + else if (factoryMethodToUse != null && typeDiffWeight == minTypeDiffWeight && + !mbd.isLenientConstructorResolution() && + paramTypes.length == factoryMethodToUse.getParameterTypes().length && + !Arrays.equals(paramTypes, factoryMethodToUse.getParameterTypes())) { if (ambiguousFactoryMethods == null) { ambiguousFactoryMethods = new LinkedHashSet(); ambiguousFactoryMethods.add(factoryMethodToUse); @@ -540,7 +548,7 @@ else if (void.class.equals(factoryMethodToUse.getReturnType())) { "Invalid factory method '" + mbd.getFactoryMethodName() + "': needs to have a non-void return type!"); } - else if (ambiguousFactoryMethods != null && !mbd.isLenientConstructorResolution()) { + else if (ambiguousFactoryMethods != null) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Ambiguous factory method matches found in bean '" + beanName + "' " + "(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities): " + @@ -644,7 +652,7 @@ private int resolveConstructorArguments( */ private ArgumentsHolder createArgumentArray( String beanName, RootBeanDefinition mbd, ConstructorArgumentValues resolvedValues, - BeanWrapper bw, Class[] paramTypes, String[] paramNames, Object methodOrCtor, + BeanWrapper bw, Class[] paramTypes, String[] paramNames, Object methodOrCtor, boolean autowiring) throws UnsatisfiedDependencyException { String methodType = (methodOrCtor instanceof Constructor ? "constructor" : "factory method"); @@ -750,7 +758,7 @@ private ArgumentsHolder createArgumentArray( private Object[] resolvePreparedArguments( String beanName, RootBeanDefinition mbd, BeanWrapper bw, Member methodOrCtor, Object[] argsToResolve) { - Class[] paramTypes = (methodOrCtor instanceof Method ? + Class[] paramTypes = (methodOrCtor instanceof Method ? ((Method) methodOrCtor).getParameterTypes() : ((Constructor) methodOrCtor).getParameterTypes()); TypeConverter converter = (this.beanFactory.getCustomTypeConverter() != null ? this.beanFactory.getCustomTypeConverter() : bw); @@ -822,7 +830,7 @@ public ArgumentsHolder(Object[] args) { this.preparedArguments = args; } - public int getTypeDifferenceWeight(Class[] paramTypes) { + public int getTypeDifferenceWeight(Class[] paramTypes) { // If valid arguments found, determine type difference weight. // Try type difference weight on both the converted arguments and // the raw arguments. If the raw weight is better, use it. @@ -832,7 +840,7 @@ public int getTypeDifferenceWeight(Class[] paramTypes) { return (rawTypeDiffWeight < typeDiffWeight ? rawTypeDiffWeight : typeDiffWeight); } - public int getAssignabilityWeight(Class[] paramTypes) { + public int getAssignabilityWeight(Class[] paramTypes) { for (int i = 0; i < paramTypes.length; i++) { if (!ClassUtils.isAssignableValue(paramTypes[i], this.arguments[i])) { return Integer.MAX_VALUE; diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultBeanDefinitionDocumentReader.java b/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultBeanDefinitionDocumentReader.java index a422d5b4fcd4..0be926af27ed 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultBeanDefinitionDocumentReader.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultBeanDefinitionDocumentReader.java @@ -133,7 +133,7 @@ protected void doRegisterBeanDefinitions(Element root) { // then ultimately reset this.delegate back to its original (parent) reference. // this behavior emulates a stack of delegates without actually necessitating one. BeanDefinitionParserDelegate parent = this.delegate; - this.delegate = createHelper(this.readerContext, root, parent); + this.delegate = createDelegate(this.readerContext, root, parent); preProcessXml(root); parseBeanDefinitions(root, this.delegate); @@ -142,14 +142,24 @@ protected void doRegisterBeanDefinitions(Element root) { this.delegate = parent; } - protected BeanDefinitionParserDelegate createHelper( + protected BeanDefinitionParserDelegate createDelegate( XmlReaderContext readerContext, Element root, BeanDefinitionParserDelegate parentDelegate) { - BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext, environment); - delegate.initDefaults(root, parentDelegate); + BeanDefinitionParserDelegate delegate = createHelper(readerContext, root, parentDelegate); + if (delegate == null) { + delegate = new BeanDefinitionParserDelegate(readerContext, this.environment); + delegate.initDefaults(root, parentDelegate); + } return delegate; } + @Deprecated + protected BeanDefinitionParserDelegate createHelper( + XmlReaderContext readerContext, Element root, BeanDefinitionParserDelegate parentDelegate) { + + return null; + } + /** * Return the descriptor for the XML resource that this parser works on. */ diff --git a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java index 9ec9af033b8d..17dcfd653455 100644 --- a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java @@ -63,6 +63,10 @@ import org.springframework.util.StopWatch; import org.springframework.util.StringUtils; +import static org.hamcrest.Matchers.*; + +import static org.junit.Assert.*; + /** * @author Rod Johnson @@ -1559,6 +1563,20 @@ public void cornerSpr10115() { assertEquals("val1", Spr10115Bean.prop1); } + @Test + public void testArrayToObject() throws Exception { + ArrayToObject foo = new ArrayToObject(); + BeanWrapperImpl bwi = new BeanWrapperImpl(); + bwi.setWrappedInstance(foo); + + Object[] array = new Object[] {"1","2"}; + bwi.setPropertyValue("object", array ); + assertThat(foo.getObject(), equalTo((Object) array)); + + array = new Object[] {"1"}; + bwi.setPropertyValue("object", array ); + assertThat(foo.getObject(), equalTo((Object) array)); +} static class Spr10115Bean { private static String prop1; @@ -1944,4 +1962,19 @@ public enum TestEnum { TEST_VALUE } + + static class ArrayToObject { + + private Object object; + + + public void setObject(Object object) { + this.object = object; + } + + public Object getObject() { + return object; + } + + } } diff --git a/spring-beans/src/test/java/org/springframework/beans/SimplePropertyDescriptorTests.java b/spring-beans/src/test/java/org/springframework/beans/SimplePropertyDescriptorTests.java index 8627083279ef..b5a9f14e4f2e 100644 --- a/spring-beans/src/test/java/org/springframework/beans/SimplePropertyDescriptorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/SimplePropertyDescriptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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 static org.junit.Assert.*; /** - * Unit tests for {@link SimpleNonIndexedPropertyDescriptor} and + * Unit tests for {@link SimplePropertyDescriptor} and * {@link SimpleIndexedPropertyDescriptor}. * * @author Chris Beams @@ -39,7 +39,7 @@ public class SimplePropertyDescriptorTests { @Test public void toStringOutput() throws IntrospectionException, SecurityException, NoSuchMethodException { { - Object pd = new SimpleNonIndexedPropertyDescriptor("foo", null, null); + Object pd = new SimplePropertyDescriptor("foo", null, null); assertThat(pd.toString(), containsString( "PropertyDescriptor[name=foo, propertyType=null, readMethod=null")); } @@ -49,7 +49,7 @@ class C { public Object setFoo(String foo) { return null; } } Method m = C.class.getMethod("setFoo", String.class); - Object pd = new SimpleNonIndexedPropertyDescriptor("foo", null, m); + Object pd = new SimplePropertyDescriptor("foo", null, m); assertThat(pd.toString(), allOf( containsString("PropertyDescriptor[name=foo"), containsString("propertyType=class java.lang.String"), @@ -76,10 +76,10 @@ class C { @Test public void nonIndexedEquality() throws IntrospectionException, SecurityException, NoSuchMethodException { - Object pd1 = new SimpleNonIndexedPropertyDescriptor("foo", null, null); + Object pd1 = new SimplePropertyDescriptor("foo", null, null); assertThat(pd1, equalTo(pd1)); - Object pd2 = new SimpleNonIndexedPropertyDescriptor("foo", null, null); + Object pd2 = new SimplePropertyDescriptor("foo", null, null); assertThat(pd1, equalTo(pd2)); assertThat(pd2, equalTo(pd1)); @@ -89,12 +89,12 @@ class C { public String getFoo() { return null; } } Method wm1 = C.class.getMethod("setFoo", String.class); - Object pd3 = new SimpleNonIndexedPropertyDescriptor("foo", null, wm1); + Object pd3 = new SimplePropertyDescriptor("foo", null, wm1); assertThat(pd1, not(equalTo(pd3))); assertThat(pd3, not(equalTo(pd1))); Method rm1 = C.class.getMethod("getFoo"); - Object pd4 = new SimpleNonIndexedPropertyDescriptor("foo", rm1, null); + Object pd4 = new SimplePropertyDescriptor("foo", rm1, null); assertThat(pd1, not(equalTo(pd4))); assertThat(pd4, not(equalTo(pd1))); @@ -147,4 +147,5 @@ class C { assertThat(pd1, not(equalTo(pd7))); assertThat(pd7, not(equalTo(pd1))); } + } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/AutowireUtilsTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/AutowireUtilsTests.java new file mode 100644 index 000000000000..56fcde6ec9cf --- /dev/null +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/AutowireUtilsTests.java @@ -0,0 +1,192 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.beans.factory.support; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Test; + +import org.springframework.util.ReflectionUtils; + +import static org.junit.Assert.*; + +/** + * @author Juergen Hoeller + * @author Sam Brannen + */ +public class AutowireUtilsTests { + + @Test + public void genericMethodReturnTypes() { + Method notParameterized = ReflectionUtils.findMethod(MyTypeWithMethods.class, "notParameterized", new Class[]{}); + assertEquals(String.class, + AutowireUtils.resolveReturnTypeForFactoryMethod(notParameterized, new Object[]{}, getClass().getClassLoader())); + + Method notParameterizedWithArguments = ReflectionUtils.findMethod(MyTypeWithMethods.class, "notParameterizedWithArguments", + new Class[] { Integer.class, Boolean.class }); + assertEquals(String.class, + AutowireUtils.resolveReturnTypeForFactoryMethod(notParameterizedWithArguments, new Object[] { 99, true }, getClass().getClassLoader())); + + Method createProxy = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createProxy", new Class[] { Object.class }); + assertEquals(String.class, + AutowireUtils.resolveReturnTypeForFactoryMethod(createProxy, new Object[] { "foo" }, getClass().getClassLoader())); + + Method createNamedProxyWithDifferentTypes = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createNamedProxy", + new Class[] { String.class, Object.class }); + assertEquals(Long.class, + AutowireUtils.resolveReturnTypeForFactoryMethod(createNamedProxyWithDifferentTypes, new Object[] { "enigma", 99L }, getClass().getClassLoader())); + + Method createNamedProxyWithDuplicateTypes = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createNamedProxy", + new Class[] { String.class, Object.class }); + assertEquals(String.class, + AutowireUtils.resolveReturnTypeForFactoryMethod(createNamedProxyWithDuplicateTypes, new Object[] { "enigma", "foo" }, getClass().getClassLoader())); + + Method createMock = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createMock", new Class[] { Class.class }); + assertEquals(Runnable.class, + AutowireUtils.resolveReturnTypeForFactoryMethod(createMock, new Object[] { Runnable.class }, getClass().getClassLoader())); + assertEquals(Runnable.class, + AutowireUtils.resolveReturnTypeForFactoryMethod(createMock, new Object[] { Runnable.class.getName() }, getClass().getClassLoader())); + + Method createNamedMock = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createNamedMock", new Class[] { String.class, + Class.class }); + assertEquals(Runnable.class, + AutowireUtils.resolveReturnTypeForFactoryMethod(createNamedMock, new Object[] { "foo", Runnable.class }, getClass().getClassLoader())); + + Method createVMock = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createVMock", + new Class[] { Object.class, Class.class }); + assertEquals(Runnable.class, + AutowireUtils.resolveReturnTypeForFactoryMethod(createVMock, new Object[] { "foo", Runnable.class }, getClass().getClassLoader())); + + // Ideally we would expect String.class instead of Object.class, but + // resolveReturnTypeForFactoryMethod() does not currently support this form of + // look-up. + Method extractValueFrom = ReflectionUtils.findMethod(MyTypeWithMethods.class, "extractValueFrom", + new Class[] { MyInterfaceType.class }); + assertEquals(Object.class, + AutowireUtils.resolveReturnTypeForFactoryMethod(extractValueFrom, new Object[] { new MySimpleInterfaceType() }, getClass().getClassLoader())); + + // Ideally we would expect Boolean.class instead of Object.class, but this + // information is not available at run-time due to type erasure. + Map map = new HashMap(); + map.put(0, false); + map.put(1, true); + Method extractMagicValue = ReflectionUtils.findMethod(MyTypeWithMethods.class, "extractMagicValue", new Class[] { Map.class }); + assertEquals(Object.class, AutowireUtils.resolveReturnTypeForFactoryMethod(extractMagicValue, new Object[] { map }, getClass().getClassLoader())); + } + + + public interface MyInterfaceType { + } + + public class MySimpleInterfaceType implements MyInterfaceType { + } + + public static class MyTypeWithMethods { + + public MyInterfaceType integer() { + return null; + } + + public MySimpleInterfaceType string() { + return null; + } + + public Object object() { + return null; + } + + @SuppressWarnings("rawtypes") + public MyInterfaceType raw() { + return null; + } + + public String notParameterized() { + return null; + } + + public String notParameterizedWithArguments(Integer x, Boolean b) { + return null; + } + + /** + * Simulates a factory method that wraps the supplied object in a proxy of the + * same type. + */ + public static T createProxy(T object) { + return null; + } + + /** + * Similar to {@link #createProxy(Object)} but adds an additional argument before + * the argument of type {@code T}. Note that they may potentially be of the same + * time when invoked! + */ + public static T createNamedProxy(String name, T object) { + return null; + } + + /** + * Simulates factory methods found in libraries such as Mockito and EasyMock. + */ + public static MOCK createMock(Class toMock) { + return null; + } + + /** + * Similar to {@link #createMock(Class)} but adds an additional method argument + * before the parameterized argument. + */ + public static T createNamedMock(String name, Class toMock) { + return null; + } + + /** + * Similar to {@link #createNamedMock(String, Class)} but adds an additional + * parameterized type. + */ + public static T createVMock(V name, Class toMock) { + return null; + } + + /** + * Extract some value of the type supported by the interface (i.e., by a concrete, + * non-generic implementation of the interface). + */ + public static T extractValueFrom(MyInterfaceType myInterfaceType) { + return null; + } + + /** + * Extract some magic value from the supplied map. + */ + public static V extractMagicValue(Map map) { + return null; + } + + public void readIntegerInputMessage(MyInterfaceType message) { + } + + public void readIntegerArrayInputMessage(MyInterfaceType[] message) { + } + + public void readGenericArrayInputMessage(T[] message) { + } + } + +} diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java index 2480f64fac22..f2b33f22dc87 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java @@ -16,13 +16,9 @@ package org.springframework.beans.factory.support; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; @@ -37,21 +33,23 @@ import org.junit.Test; import org.mockito.Mockito; + import org.springframework.beans.PropertyEditorRegistrar; import org.springframework.beans.PropertyEditorRegistry; import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.config.TypedStringValue; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.beans.propertyeditors.CustomNumberEditor; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.UrlResource; import org.springframework.tests.Assume; import org.springframework.tests.TestGroup; - import org.springframework.tests.sample.beans.GenericBean; import org.springframework.tests.sample.beans.GenericIntegerBean; import org.springframework.tests.sample.beans.GenericSetOfIntegerBean; import org.springframework.tests.sample.beans.TestBean; +import static org.junit.Assert.*; /** * @author Juergen Hoeller @@ -115,7 +113,7 @@ public void testGenericListPropertyWithInvalidElementType() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericIntegerBean.class); - List input = new ArrayList(); + List input = new ArrayList(); input.add(1); rbd.getPropertyValues().add("testBeanList", input); @@ -655,18 +653,17 @@ public void testSetBean() throws Exception { } /** - * Tests support for parameterized {@code factory-method} declarations such - * as Mockito {@code mock()} method which has the following signature. - * - *

{@code
+	 * Tests support for parameterized static {@code factory-method} declarations such as
+	 * Mockito's {@code mock()} method which has the following signature.
+	 * 
+	 * {@code
 	 * public static  T mock(Class classToMock)
-	 * }
- * - * See SPR-9493 - * @since 3.2 + * } + *
+ *

See SPR-9493 */ @Test - public void parameterizedFactoryMethod() { + public void parameterizedStaticFactoryMethod() { RootBeanDefinition rbd = new RootBeanDefinition(Mockito.class); rbd.setFactoryMethodName("mock"); rbd.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class); @@ -678,6 +675,100 @@ public void parameterizedFactoryMethod() { assertEquals(1, beans.size()); } + /** + * Tests support for parameterized instance {@code factory-method} declarations such + * as EasyMock's {@code IMocksControl.createMock()} method which has the following + * signature. + *

+	 * {@code
+	 * public  T createMock(Class toMock)
+	 * }
+	 * 
+ *

See SPR-10411 + */ + @Test + public void parameterizedInstanceFactoryMethod() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + + RootBeanDefinition rbd = new RootBeanDefinition(MocksControl.class); + bf.registerBeanDefinition("mocksControl", rbd); + + rbd = new RootBeanDefinition(); + rbd.setFactoryBeanName("mocksControl"); + rbd.setFactoryMethodName("createMock"); + rbd.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class); + bf.registerBeanDefinition("mock", rbd); + + Map beans = bf.getBeansOfType(Runnable.class); + assertEquals(1, beans.size()); + } + + @Test + public void parameterizedInstanceFactoryMethodWithNonResolvedClassName() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + + RootBeanDefinition rbd = new RootBeanDefinition(MocksControl.class); + bf.registerBeanDefinition("mocksControl", rbd); + + rbd = new RootBeanDefinition(); + rbd.setFactoryBeanName("mocksControl"); + rbd.setFactoryMethodName("createMock"); + rbd.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class.getName()); + bf.registerBeanDefinition("mock", rbd); + + Map beans = bf.getBeansOfType(Runnable.class); + assertEquals(1, beans.size()); + } + + @Test + public void parameterizedInstanceFactoryMethodWithWrappedClassName() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + + RootBeanDefinition rbd = new RootBeanDefinition(); + rbd.setBeanClassName(Mockito.class.getName()); + rbd.setFactoryMethodName("mock"); + // TypedStringValue used to be equivalent to an XML-defined argument String + rbd.getConstructorArgumentValues().addGenericArgumentValue(new TypedStringValue(Runnable.class.getName())); + bf.registerBeanDefinition("mock", rbd); + + Map beans = bf.getBeansOfType(Runnable.class); + assertEquals(1, beans.size()); + } + + @Test + public void parameterizedInstanceFactoryMethodWithInvalidClassName() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + + RootBeanDefinition rbd = new RootBeanDefinition(MocksControl.class); + bf.registerBeanDefinition("mocksControl", rbd); + + rbd = new RootBeanDefinition(); + rbd.setFactoryBeanName("mocksControl"); + rbd.setFactoryMethodName("createMock"); + rbd.getConstructorArgumentValues().addGenericArgumentValue("x"); + bf.registerBeanDefinition("mock", rbd); + + Map beans = bf.getBeansOfType(Runnable.class); + assertEquals(0, beans.size()); + } + + @Test + public void parameterizedInstanceFactoryMethodWithIndexedArgument() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + + RootBeanDefinition rbd = new RootBeanDefinition(MocksControl.class); + bf.registerBeanDefinition("mocksControl", rbd); + + rbd = new RootBeanDefinition(); + rbd.setFactoryBeanName("mocksControl"); + rbd.setFactoryMethodName("createMock"); + rbd.getConstructorArgumentValues().addIndexedArgumentValue(0, Runnable.class); + bf.registerBeanDefinition("mock", rbd); + + Map beans = bf.getBeansOfType(Runnable.class); + assertEquals(1, beans.size()); + } + @SuppressWarnings("serial") public static class NamedUrlList extends LinkedList { @@ -722,4 +813,22 @@ public void setUrlNames(Set urlNames) throws MalformedURLException { } } + + /** + * Pseudo-implementation of EasyMock's {@code MocksControl} class. + */ + public static class MocksControl { + + @SuppressWarnings("unchecked") + public T createMock(Class toMock) { + return (T) Proxy.newProxyInstance(BeanFactoryGenericsTests.class.getClassLoader(), new Class[] {toMock}, + new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + throw new UnsupportedOperationException("mocked!"); + } + }); + } + } + } diff --git a/spring-beans/src/test/java/org/springframework/tests/sample/beans/DerivedTestBean.java b/spring-beans/src/test/java/org/springframework/tests/sample/beans/DerivedTestBean.java index 91416208a2b1..d12db5b7adf1 100644 --- a/spring-beans/src/test/java/org/springframework/tests/sample/beans/DerivedTestBean.java +++ b/spring-beans/src/test/java/org/springframework/tests/sample/beans/DerivedTestBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -71,6 +71,11 @@ public void setSpouseRef(String name) { setSpouse(new TestBean(name)); } + @Override + public TestBean getSpouse() { + return (TestBean) super.getSpouse(); + } + public void initialize() { this.initialized = true; diff --git a/spring-beans/src/test/resources/org/springframework/beans/factory/xml/collections.xml b/spring-beans/src/test/resources/org/springframework/beans/factory/xml/collections.xml index 974ae6c3d9ca..73294dfbaede 100644 --- a/spring-beans/src/test/resources/org/springframework/beans/factory/xml/collections.xml +++ b/spring-beans/src/test/resources/org/springframework/beans/factory/xml/collections.xml @@ -35,11 +35,10 @@ - + Jenny 30 - diff --git a/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheFactoryBean.java b/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheFactoryBean.java index dcec4047eeec..02f7f38396a2 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheFactoryBean.java +++ b/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheFactoryBean.java @@ -319,7 +319,8 @@ public void afterPropertiesSet() throws CacheException, IOException { // Fetch cache region: If none with the given name exists, // create one on the fly. Ehcache rawCache; - if (this.cacheManager.cacheExists(this.cacheName)) { + boolean cacheExists = this.cacheManager.cacheExists(cacheName); + if (cacheExists) { if (logger.isDebugEnabled()) { logger.debug("Using existing EhCache cache region '" + this.cacheName + "'"); } @@ -330,7 +331,6 @@ public void afterPropertiesSet() throws CacheException, IOException { logger.debug("Creating new EhCache cache region '" + this.cacheName + "'"); } rawCache = createCache(); - this.cacheManager.addCache(rawCache); } if (this.cacheEventListeners != null) { @@ -348,7 +348,9 @@ public void afterPropertiesSet() throws CacheException, IOException { rawCache.setDisabled(true); } - // Decorate cache if necessary. + if (!cacheExists) { + this.cacheManager.addCache(rawCache); + } Ehcache decoratedCache = decorateCache(rawCache); if (decoratedCache != rawCache) { this.cacheManager.replaceCacheWithDecoratedCache(rawCache, decoratedCache); diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/commonj/WorkManagerTaskExecutor.java b/spring-context-support/src/main/java/org/springframework/scheduling/commonj/WorkManagerTaskExecutor.java index 4d1518dfa9a9..499575210955 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/commonj/WorkManagerTaskExecutor.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/commonj/WorkManagerTaskExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,10 +17,9 @@ package org.springframework.scheduling.commonj; import java.util.Collection; +import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; -import java.util.concurrent.Callable; - import javax.naming.NamingException; import commonj.work.Work; @@ -54,10 +53,8 @@ * server's JNDI environment, as defined in the server's management console. * *

Note: At the time of this writing, the CommonJ WorkManager facility - * is only supported on IBM WebSphere 6.0+ and BEA WebLogic 9.0+, + * is only supported on IBM WebSphere 6.1+ and BEA WebLogic 9.0+, * despite being such a crucial API for an application server. - * (There is a similar facility available on WebSphere 5.1 Enterprise, - * though, which we will discuss below.) * *

On JBoss and GlassFish, a similar facility is available through * the JCA WorkManager. See the @@ -80,8 +77,7 @@ public class WorkManagerTaskExecutor extends JndiLocatorSupport /** * Specify the CommonJ WorkManager to delegate to. - *

Alternatively, you can also specify the JNDI name - * of the target WorkManager. + *

Alternatively, you can also specify the JNDI name of the target WorkManager. * @see #setWorkManagerName */ public void setWorkManager(WorkManager workManager) { @@ -90,9 +86,8 @@ public void setWorkManager(WorkManager workManager) { /** * Set the JNDI name of the CommonJ WorkManager. - *

This can either be a fully qualified JNDI name, - * or the JNDI name relative to the current environment - * naming context if "resourceRef" is set to "true". + *

This can either be a fully qualified JNDI name, or the JNDI name relative + * to the current environment naming context if "resourceRef" is set to "true". * @see #setWorkManager * @see #setResourceRef */ @@ -170,27 +165,19 @@ public boolean prefersShortLivedTasks() { // Implementation of the CommonJ WorkManager interface //------------------------------------------------------------------------- - public WorkItem schedule(Work work) - throws WorkException, IllegalArgumentException { - + public WorkItem schedule(Work work) throws WorkException, IllegalArgumentException { return this.workManager.schedule(work); } - public WorkItem schedule(Work work, WorkListener workListener) - throws WorkException, IllegalArgumentException { - + public WorkItem schedule(Work work, WorkListener workListener) throws WorkException { return this.workManager.schedule(work, workListener); } - public boolean waitForAll(Collection workItems, long timeout) - throws InterruptedException, IllegalArgumentException { - + public boolean waitForAll(Collection workItems, long timeout) throws InterruptedException { return this.workManager.waitForAll(workItems, timeout); } - public Collection waitForAny(Collection workItems, long timeout) - throws InterruptedException, IllegalArgumentException { - + public Collection waitForAny(Collection workItems, long timeout) throws InterruptedException { return this.workManager.waitForAny(workItems, timeout); } diff --git a/spring-context/src/main/java/org/springframework/cache/Cache.java b/spring-context/src/main/java/org/springframework/cache/Cache.java index dbfa1cba84ba..8a9109afc92d 100644 --- a/spring-context/src/main/java/org/springframework/cache/Cache.java +++ b/spring-context/src/main/java/org/springframework/cache/Cache.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,11 +39,15 @@ public interface Cache { Object getNativeCache(); /** - * Return the value to which this cache maps the specified key. Returns - * {@code null} if the cache contains no mapping for this key. - * @param key key whose associated value is to be returned. + * Return the value to which this cache maps the specified key. + *

Returns {@code null} if the cache contains no mapping for this key; + * otherwise, the cached value (which may be {@code null} itself) will + * be returned in a {@link ValueWrapper}. + * @param key the key whose associated value is to be returned * @return the value to which this cache maps the specified key, - * or {@code null} if the cache contains no mapping for this key + * contained within a {@link ValueWrapper} which may also hold + * a cached {@code null} value. A straight {@code null} being + * returned means that the cache contains no mapping for this key. */ ValueWrapper get(Object key); diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java index 4576ab3bba16..9110189f738a 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java @@ -109,7 +109,7 @@ public void setEnvironment(ConfigurableEnvironment environment) { public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) { this.reader.setBeanNameGenerator(beanNameGenerator); this.scanner.setBeanNameGenerator(beanNameGenerator); - this.getBeanFactory().registerSingleton( + getBeanFactory().registerSingleton( AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, beanNameGenerator); } @@ -126,9 +126,9 @@ public void setScopeMetadataResolver(ScopeMetadataResolver scopeMetadataResolver /** * Register one or more annotated classes to be processed. - * Note that {@link #refresh()} must be called in order for the context to fully - * process the new class. - *

Calls to {@link #register} are idempotent; adding the same + * Note that {@link #refresh()} must be called in order for the context + * to fully process the new class. + *

Calls to {@code register} are idempotent; adding the same * annotated class more than once has no additional effect. * @param annotatedClasses one or more annotated classes, * e.g. {@link Configuration @Configuration} classes 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 f283bb6d2c0f..39560ef53116 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -230,7 +230,7 @@ private static BeanDefinitionHolder registerPostProcessor( return new BeanDefinitionHolder(definition, beanName); } - static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd) { + public static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd) { AnnotationMetadata metadata = abd.getMetadata(); if (metadata.isAnnotated(Primary.class.getName())) { abd.setPrimary(true); @@ -260,5 +260,4 @@ static BeanDefinitionHolder applyScopedProxyMode( return ScopedProxyCreator.createScopedProxy(definition, registry, proxyTargetClass); } - } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java index b3f9450f983d..944a5a5ed166 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java @@ -317,8 +317,8 @@ protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) /** * Determine whether the given new bean definition is compatible with * the given existing bean definition. - *

The default implementation simply considers them as compatible - * when the bean class name matches. + *

The default implementation considers them as compatible when the existing + * bean definition comes from the same source or from a non-scanning source. * @param newDefinition the new bean definition, originated from scanning * @param existingDefinition the existing bean definition, potentially an * explicitly defined one or a previously generated one from scanning 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 b775c6e64084..9280c65e1b6c 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 @@ -228,7 +228,7 @@ protected void registerDefaultFilters() { try { this.includeFilters.add(new AnnotationTypeFilter( ((Class) cl.loadClass("javax.annotation.ManagedBean")), false)); - logger.info("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning"); + logger.debug("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning"); } catch (ClassNotFoundException ex) { // JSR-250 1.1 API (as included in Java EE 6) not available - simply skip. @@ -236,7 +236,7 @@ protected void registerDefaultFilters() { try { this.includeFilters.add(new AnnotationTypeFilter( ((Class) cl.loadClass("javax.inject.Named")), false)); - logger.info("JSR-330 'javax.inject.Named' annotation found and supported for component scanning"); + logger.debug("JSR-330 'javax.inject.Named' annotation found and supported for component scanning"); } catch (ClassNotFoundException ex) { // JSR-330 API not available - simply skip. diff --git a/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java b/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java index 93a71336a0fb..cbb1ee2110eb 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -177,8 +177,8 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean private transient BeanFactory beanFactory; - private transient final Map, InjectionMetadata> injectionMetadataCache = - new ConcurrentHashMap, InjectionMetadata>(64); + private transient final Map injectionMetadataCache = + new ConcurrentHashMap(64); /** @@ -282,7 +282,7 @@ public void setBeanFactory(BeanFactory beanFactory) throws BeansException { public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class beanType, String beanName) { super.postProcessMergedBeanDefinition(beanDefinition, beanType, beanName); if (beanType != null) { - InjectionMetadata metadata = findResourceMetadata(beanType); + InjectionMetadata metadata = findResourceMetadata(beanName, beanType); metadata.checkConfigMembers(beanDefinition); } } @@ -298,7 +298,7 @@ public boolean postProcessAfterInstantiation(Object bean, String beanName) throw public PropertyValues postProcessPropertyValues( PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException { - InjectionMetadata metadata = findResourceMetadata(bean.getClass()); + InjectionMetadata metadata = findResourceMetadata(beanName, bean.getClass()); try { metadata.inject(bean, beanName, pvs); } @@ -309,12 +309,14 @@ public PropertyValues postProcessPropertyValues( } - private InjectionMetadata findResourceMetadata(final Class clazz) { + private InjectionMetadata findResourceMetadata(String beanName, final Class clazz) { // Quick check on the concurrent map first, with minimal locking. - InjectionMetadata metadata = this.injectionMetadataCache.get(clazz); + // Fall back to class name as cache key, for backwards compatibility with custom callers. + String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName()); + InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey); if (metadata == null) { synchronized (this.injectionMetadataCache) { - metadata = this.injectionMetadataCache.get(clazz); + metadata = this.injectionMetadataCache.get(cacheKey); if (metadata == null) { LinkedList elements = new LinkedList(); Class targetClass = clazz; @@ -388,7 +390,7 @@ else if (method.isAnnotationPresent(Resource.class)) { while (targetClass != null && targetClass != Object.class); metadata = new InjectionMetadata(clazz, elements); - this.injectionMetadataCache.put(clazz, metadata); + this.injectionMetadataCache.put(cacheKey, metadata); } } } @@ -472,11 +474,8 @@ protected abstract class LookupElement extends InjectionMetadata.InjectedElement public LookupElement(Member member, PropertyDescriptor pd) { super(member, pd); - initAnnotation((AnnotatedElement) member); } - protected abstract void initAnnotation(AnnotatedElement ae); - /** * Return the resource name for the lookup. */ @@ -511,14 +510,11 @@ public final DependencyDescriptor getDependencyDescriptor() { */ private class ResourceElement extends LookupElement { - protected boolean shareable = true; + protected final boolean shareable; public ResourceElement(Member member, PropertyDescriptor pd) { super(member, pd); - } - - @Override - protected void initAnnotation(AnnotatedElement ae) { + AnnotatedElement ae = (AnnotatedElement) member; Resource resource = ae.getAnnotation(Resource.class); String resourceName = resource.name(); Class resourceType = resource.type(); @@ -558,16 +554,13 @@ protected Object getResourceToInject(Object target, String requestingBeanName) { */ private class WebServiceRefElement extends LookupElement { - private Class elementType; + private final Class elementType; - private String wsdlLocation; + private final String wsdlLocation; public WebServiceRefElement(Member member, PropertyDescriptor pd) { super(member, pd); - } - - @Override - protected void initAnnotation(AnnotatedElement ae) { + AnnotatedElement ae = (AnnotatedElement) member; WebServiceRef resource = ae.getAnnotation(WebServiceRef.class); String resourceName = resource.name(); Class resourceType = resource.type(); @@ -647,14 +640,11 @@ protected Object getResourceToInject(Object target, String requestingBeanName) { */ private class EjbRefElement extends LookupElement { - private String beanName; + private final String beanName; public EjbRefElement(Member member, PropertyDescriptor pd) { super(member, pd); - } - - @Override - protected void initAnnotation(AnnotatedElement ae) { + AnnotatedElement ae = (AnnotatedElement) member; EJB resource = ae.getAnnotation(EJB.class); String resourceBeanName = resource.beanName(); String resourceName = resource.name(); diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScan.java b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScan.java index eaab654ec702..f90f4d98588b 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScan.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScan.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,7 +42,7 @@ * always registered, meaning that any attempt to disable them at the * {@code @ComponentScan} level would be ignored. * - *

See @{@link Configuration} Javadoc for usage examples. + *

See @{@link Configuration}'s javadoc for usage examples. * * @author Chris Beams * @since 3.1 @@ -139,24 +139,18 @@ @Retention(RetentionPolicy.RUNTIME) @Target({}) @interface Filter { + /** - * The type of filter to use. - *

Note that the filter types available are limited to those that may - * be expressed as a {@code Class} in the {@link #value()} attribute. This is - * in contrast to {@code }, which allows for - * expression-based (i.e., string-based) filters such as AspectJ pointcuts. - * These filter types are intentionally not supported here, and not available - * in the {@link FilterType} enum. - * @see FilterType + * The type of filter to use. Default is {@link FilterType#ANNOTATION}. */ FilterType type() default FilterType.ANNOTATION; /** * The class or classes to use as the filter. In the case of - * {@link FilterType#ANNOTATION}, the class will be the annotation itself. In the - * case of {@link FilterType#ASSIGNABLE_TYPE}, the class will be the type that - * detected components should be assignable to. And in the case of - * {@link FilterType#CUSTOM}, the class will be an implementation of + * {@link FilterType#ANNOTATION}, the class will be the annotation itself. + * In the case of {@link FilterType#ASSIGNABLE_TYPE}, the class will be the + * type that detected components should be assignable to. And in the case + * of {@link FilterType#CUSTOM}, the class will be an implementation of * {@link TypeFilter}. *

When multiple classes are specified, OR logic is applied, e.g. "include * types annotated with {@code @Foo} OR {@code @Bar}". 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 48af4ed5a0e0..7519495de093 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-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,6 +28,7 @@ import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.env.Environment; import org.springframework.core.io.ResourceLoader; +import org.springframework.core.type.filter.AbstractTypeHierarchyTraversingFilter; import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.core.type.filter.AssignableTypeFilter; import org.springframework.core.type.filter.TypeFilter; @@ -64,7 +65,7 @@ public ComponentScanAnnotationParser(ResourceLoader resourceLoader, Environment } - public Set parse(AnnotationAttributes componentScan, String declaringClass) { + public Set parse(AnnotationAttributes componentScan, final String declaringClass) { ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry, componentScan.getBoolean("useDefaultFilters")); @@ -120,6 +121,12 @@ public Set parse(AnnotationAttributes componentScan, Strin basePackages.add(ClassUtils.getPackageName(declaringClass)); } + scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) { + @Override + protected boolean matchClassName(String className) { + return declaringClass.equals(className); + } + }); return scanner.doScan(StringUtils.toStringArray(basePackages)); } 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 a89be5597701..644065e5dc23 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 @@ -165,7 +165,6 @@ public Map> getImportedResources() return this.importedResources; } - public void validate(ProblemReporter problemReporter) { // A configuration class may not be final (CGLIB limitation) if (getMetadata().isAnnotated(Configuration.class.getName())) { @@ -196,7 +195,6 @@ public void validate(ProblemReporter problemReporter) { } } - @Override public boolean equals(Object other) { return (this == other || (other instanceof ConfigurationClass && @@ -210,7 +208,7 @@ public int hashCode() { @Override public String toString() { - return String.format("[ConfigurationClass:beanName=%s,resource=%s]", this.beanName, this.resource); + return "ConfigurationClass:beanName=" + this.beanName + ",resource=" + this.resource; } 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 27ae171955cc..5250f5e34c64 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 @@ -156,7 +156,7 @@ private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) { ConfigurationClass configClass = beanMethod.getConfigurationClass(); MethodMetadata metadata = beanMethod.getMetadata(); - RootBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass); + ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass); beanDef.setResource(configClass.getResource()); beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource())); if (metadata.isStatic()) { @@ -188,9 +188,18 @@ private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) { // has this already been overridden (e.g. via XML)? if (this.registry.containsBeanDefinition(beanName)) { - BeanDefinition existingBeanDef = registry.getBeanDefinition(beanName); - // is the existing bean definition one that was created from a configuration class? - if (!(existingBeanDef instanceof ConfigurationClassBeanDefinition)) { + BeanDefinition existingBeanDef = this.registry.getBeanDefinition(beanName); + // Is the existing bean definition one that was created from a configuration class? + // -> allow the current bean method to override, since both are at second-pass level. + // However, if the bean method is an overloaded case on the same configuration class, + // preserve the existing bean definition. + if (existingBeanDef instanceof ConfigurationClassBeanDefinition) { + ConfigurationClassBeanDefinition ccbd = (ConfigurationClassBeanDefinition) existingBeanDef; + if (ccbd.getMetadata().getClassName().equals(beanMethod.getConfigurationClass().getMetadata().getClassName())) { + return; + } + } + else { // no -> then it's an external override, probably XML // overriding is legal, return immediately if (logger.isDebugEnabled()) { @@ -238,7 +247,7 @@ else if (configClass.getMetadata().isAnnotated(Lazy.class.getName())){ beanDef.setDestroyMethodName(destroyMethodName); } - // consider scoping + // Consider scoping ScopedProxyMode proxyMode = ScopedProxyMode.NO; AnnotationAttributes scope = attributesFor(metadata, Scope.class); if (scope != null) { @@ -249,7 +258,7 @@ else if (configClass.getMetadata().isAnnotated(Lazy.class.getName())){ } } - // replace the original bean definition with the target one, if necessary + // Replace the original bean definition with the target one, if necessary BeanDefinition beanDefToRegister = beanDef; if (proxyMode != ScopedProxyMode.NO) { BeanDefinitionHolder proxyDef = ScopedProxyCreator.createScopedProxy( @@ -259,7 +268,8 @@ else if (configClass.getMetadata().isAnnotated(Lazy.class.getName())){ } if (logger.isDebugEnabled()) { - logger.debug(String.format("Registering bean definition for @Bean method %s.%s()", configClass.getMetadata().getClassName(), beanName)); + logger.debug(String.format("Registering bean definition for @Bean method %s.%s()", + configClass.getMetadata().getClassName(), beanName)); } registry.registerBeanDefinition(beanName, beanDefToRegister); @@ -310,6 +320,7 @@ private static class ConfigurationClassBeanDefinition extends RootBeanDefinition public ConfigurationClassBeanDefinition(ConfigurationClass configClass) { this.annotationMetadata = configClass.getMetadata(); + setLenientConstructorResolution(false); } public ConfigurationClassBeanDefinition(RootBeanDefinition original, ConfigurationClass configClass) { 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 c1748314bc89..29a1ea0357f4 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 @@ -287,11 +287,11 @@ private void processMemberClasses(AnnotationMetadata metadata) throws IOExceptio private void processPropertySource(AnnotationAttributes propertySource) throws IOException { String name = propertySource.getString("name"); String[] locations = propertySource.getStringArray("value"); - int nLocations = locations.length; - if (nLocations == 0) { + int locationCount = locations.length; + if (locationCount == 0) { throw new IllegalArgumentException("At least one @PropertySource(value) location is required"); } - for (int i = 0; i < nLocations; i++) { + for (int i = 0; i < locationCount; i++) { locations[i] = this.environment.resolveRequiredPlaceholders(locations[i]); } ClassLoader classLoader = this.resourceLoader.getClassLoader(); @@ -301,13 +301,13 @@ private void processPropertySource(AnnotationAttributes propertySource) throws I } } else { - if (nLocations == 1) { + if (locationCount == 1) { this.propertySources.push(new ResourcePropertySource(name, locations[0], classLoader)); } else { CompositePropertySource ps = new CompositePropertySource(name); - for (String location : locations) { - ps.addPropertySource(new ResourcePropertySource(location, classLoader)); + for (int i = locations.length - 1; i >= 0; i--) { + ps.addPropertySource(new ResourcePropertySource(locations[i], classLoader)); } this.propertySources.push(ps); } 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 ddc7000b7f1d..6242ffef7561 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-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,6 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.BeanFactory; @@ -85,7 +84,7 @@ * @since 3.0 */ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor, - ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware, Ordered { + Ordered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware { private static final String IMPORT_AWARE_PROCESSOR_BEAN_NAME = ConfigurationClassPostProcessor.class.getName() + ".importAwareProcessor"; @@ -130,6 +129,10 @@ protected String buildDefaultBeanName(BeanDefinition definition) { }; + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE; + } + /** * Set the {@link SourceExtractor} to use for generated bean definitions * that correspond to {@link Bean} factory methods. @@ -368,21 +371,21 @@ public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFact } } - @Override - public int getOrder() { - return Ordered.HIGHEST_PRECEDENCE; - } - - private static class ImportAwareBeanPostProcessor implements PriorityOrdered, BeanFactoryAware, BeanPostProcessor { + private static class ImportAwareBeanPostProcessor implements BeanPostProcessor, PriorityOrdered, BeanFactoryAware { private BeanFactory beanFactory; - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE; + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) { this.beanFactory = beanFactory; } - public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + public Object postProcessBeforeInitialization(Object bean, String beanName) { if (bean instanceof ImportAware) { ImportRegistry importRegistry = this.beanFactory.getBean(IMPORT_REGISTRY_BEAN_NAME, ImportRegistry.class); String importingClass = importRegistry.getImportingClassFor(bean.getClass().getSuperclass().getName()); @@ -404,13 +407,9 @@ public Object postProcessBeforeInitialization(Object bean, String beanName) thro return bean; } - public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + public Object postProcessAfterInitialization(Object bean, String beanName) { return bean; } - - public int getOrder() { - return Ordered.HIGHEST_PRECEDENCE; - } } } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/FilterType.java b/spring-context/src/main/java/org/springframework/context/annotation/FilterType.java index 26bd302d8e8f..746424007dfe 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/FilterType.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/FilterType.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2013 the original author 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,9 +16,6 @@ package org.springframework.context.annotation; -import org.springframework.core.type.filter.AssignableTypeFilter; - - /** * Enumeration of the type filters that may be used in conjunction with * {@link ComponentScan @ComponentScan}. @@ -42,12 +39,12 @@ public enum FilterType { /** * Filter candidates assignable to a given type. - * @see AssignableTypeFilter + * @see org.springframework.core.type.filter.AssignableTypeFilter */ ASSIGNABLE_TYPE, /** Filter candidates using a given custom - * {@link org.springframework.core.type.filter.TypeFilter} implementation + * {@link org.springframework.core.type.filter.TypeFilter} implementation. */ CUSTOM diff --git a/spring-context/src/main/java/org/springframework/context/annotation/Profile.java b/spring-context/src/main/java/org/springframework/context/annotation/Profile.java index ab793599c017..4d9c603910ae 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/Profile.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/Profile.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,7 @@ package org.springframework.context.annotation; +import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -44,11 +45,11 @@ * *

If a {@code @Configuration} class is marked with {@code @Profile}, all of the * {@code @Bean} methods and {@link Import @Import} annotations associated with that class - * will be bypassed unless one or more the specified profiles are active. This is very + * will be bypassed unless one or more of the specified profiles are active. This is very * similar to the behavior in Spring XML: if the {@code profile} attribute of the * {@code beans} element is supplied e.g., {@code }, the * {@code beans} element will not be parsed unless profiles 'p1' and/or 'p2' have been - * activated. Likewise, if a {@code @Component} or {@code @Configuration} class is marked + * activated. Likewise, if a {@code @Component} or {@code @Configuration} class is marked * with {@code @Profile({"p1", "p2"})}, that class will not be registered/processed unless * profiles 'p1' and/or 'p2' have been activated. * @@ -73,10 +74,11 @@ */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) +@Documented public @interface Profile { /** - * The set of profiles for which this component should be registered. + * The set of profiles for which the annotated component should be registered. */ String[] value(); diff --git a/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java b/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java index 055b5e59db84..f14f164ce6f7 100644 --- a/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java +++ b/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,6 +28,7 @@ import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.core.OrderComparator; +import org.springframework.util.ObjectUtils; /** * Abstract implementation of the {@link ApplicationEventMulticaster} interface, @@ -128,7 +129,8 @@ protected Collection getApplicationListeners() { */ protected Collection getApplicationListeners(ApplicationEvent event) { Class eventType = event.getClass(); - Class sourceType = event.getSource().getClass(); + Object source = event.getSource(); + Class sourceType = (source != null ? source.getClass() : null); ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType); ListenerRetriever retriever = this.retrieverCache.get(cacheKey); if (retriever != null) { @@ -191,11 +193,11 @@ protected boolean supportsEvent( */ private static class ListenerCacheKey { - private final Class eventType; + private final Class eventType; - private final Class sourceType; + private final Class sourceType; - public ListenerCacheKey(Class eventType, Class sourceType) { + public ListenerCacheKey(Class eventType, Class sourceType) { this.eventType = eventType; this.sourceType = sourceType; } @@ -206,12 +208,13 @@ public boolean equals(Object other) { return true; } ListenerCacheKey otherKey = (ListenerCacheKey) other; - return (this.eventType.equals(otherKey.eventType) && this.sourceType.equals(otherKey.sourceType)); + return ObjectUtils.nullSafeEquals(this.eventType, otherKey.eventType) && + ObjectUtils.nullSafeEquals(this.sourceType, otherKey.sourceType); } @Override public int hashCode() { - return this.eventType.hashCode() * 29 + this.sourceType.hashCode(); + return ObjectUtils.nullSafeHashCode(this.eventType) * 29 + ObjectUtils.nullSafeHashCode(this.sourceType); } } diff --git a/spring-context/src/main/java/org/springframework/context/event/SourceFilteringListener.java b/spring-context/src/main/java/org/springframework/context/event/SourceFilteringListener.java index a9326c5b168a..2fef497de283 100644 --- a/spring-context/src/main/java/org/springframework/context/event/SourceFilteringListener.java +++ b/spring-context/src/main/java/org/springframework/context/event/SourceFilteringListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -74,7 +74,7 @@ public boolean supportsEventType(Class eventType) { } public boolean supportsSourceType(Class sourceType) { - return sourceType.isInstance(this.source); + return (sourceType != null && sourceType.isInstance(this.source)); } public int getOrder() { diff --git a/spring-context/src/main/java/org/springframework/context/i18n/LocaleContextHolder.java b/spring-context/src/main/java/org/springframework/context/i18n/LocaleContextHolder.java index 2ca6ea2f85ca..c1d716793a26 100644 --- a/spring-context/src/main/java/org/springframework/context/i18n/LocaleContextHolder.java +++ b/spring-context/src/main/java/org/springframework/context/i18n/LocaleContextHolder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -59,7 +59,8 @@ public static void resetLocaleContext() { /** * Associate the given LocaleContext with the current thread, * not exposing it as inheritable for child threads. - * @param localeContext the current LocaleContext + * @param localeContext the current LocaleContext, + * or {@code null} to reset the thread-bound context */ public static void setLocaleContext(LocaleContext localeContext) { setLocaleContext(localeContext, false); diff --git a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java index 6bfc380aa5db..bbc7841acbc5 100644 --- a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java @@ -217,7 +217,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader * Create a new AbstractApplicationContext with no parent. */ public AbstractApplicationContext() { - this(null); + this.resourcePatternResolver = getResourcePatternResolver(); } /** @@ -225,8 +225,8 @@ public AbstractApplicationContext() { * @param parent the parent context */ public AbstractApplicationContext(ApplicationContext parent) { - this.parent = parent; - this.resourcePatternResolver = getResourcePatternResolver(); + this(); + setParent(parent); } diff --git a/spring-context/src/main/java/org/springframework/context/support/GenericXmlApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/GenericXmlApplicationContext.java index e120f667fb59..eef4d80ba412 100644 --- a/spring-context/src/main/java/org/springframework/context/support/GenericXmlApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/GenericXmlApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,10 +45,9 @@ public class GenericXmlApplicationContext extends GenericApplicationContext { /** * Create a new GenericXmlApplicationContext that needs to be - * {@linkplain #load loaded} and then manually {@link #refresh refreshed}. + * {@link #load loaded} and then manually {@link #refresh refreshed}. */ public GenericXmlApplicationContext() { - reader.setEnvironment(this.getEnvironment()); } /** @@ -91,14 +90,13 @@ public void setValidating(boolean validating) { } /** - * {@inheritDoc} - *

Delegates the given environment to underlying {@link XmlBeanDefinitionReader}. - * Should be called before any call to {@link #load}. + * Delegates the given environment to underlying {@link XmlBeanDefinitionReader}. + * Should be called before any call to {@code #load}. */ @Override public void setEnvironment(ConfigurableEnvironment environment) { super.setEnvironment(environment); - this.reader.setEnvironment(this.getEnvironment()); + this.reader.setEnvironment(getEnvironment()); } /** diff --git a/spring-context/src/main/java/org/springframework/format/datetime/DateFormatter.java b/spring-context/src/main/java/org/springframework/format/datetime/DateFormatter.java index b5c660f0236e..b67ab82505c7 100644 --- a/spring-context/src/main/java/org/springframework/format/datetime/DateFormatter.java +++ b/spring-context/src/main/java/org/springframework/format/datetime/DateFormatter.java @@ -29,7 +29,6 @@ import org.springframework.format.Formatter; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat.ISO; -import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** @@ -45,6 +44,7 @@ public class DateFormatter implements Formatter { private static final Map ISO_PATTERNS; + static { Map formats = new HashMap(4); formats.put(ISO.DATE, "yyyy-MM-dd"); @@ -168,34 +168,36 @@ private DateFormat createDateFormat(Locale locale) { if (StringUtils.hasLength(this.pattern)) { return new SimpleDateFormat(this.pattern, locale); } - if (iso != null && iso != ISO.NONE) { - String pattern = ISO_PATTERNS.get(iso); - Assert.state(pattern != null, "Unsupported ISO format " + iso); + if (this.iso != null && this.iso != ISO.NONE) { + String pattern = ISO_PATTERNS.get(this.iso); + if (pattern == null) { + throw new IllegalStateException("Unsupported ISO format " + this.iso); + } SimpleDateFormat format = new SimpleDateFormat(pattern); format.setTimeZone(TimeZone.getTimeZone("UTC")); return format; } - if(StringUtils.hasLength(stylePattern)) { + if (StringUtils.hasLength(this.stylePattern)) { int dateStyle = getStylePatternForChar(0); int timeStyle = getStylePatternForChar(1); - if(dateStyle != -1 && timeStyle != -1) { + if (dateStyle != -1 && timeStyle != -1) { return DateFormat.getDateTimeInstance(dateStyle, timeStyle, locale); } - if(dateStyle != -1) { + if (dateStyle != -1) { return DateFormat.getDateInstance(dateStyle, locale); } - if(timeStyle != -1) { + if (timeStyle != -1) { return DateFormat.getTimeInstance(timeStyle, locale); } - throw new IllegalStateException("Unsupported style pattern '"+ stylePattern+ "'"); + throw new IllegalStateException("Unsupported style pattern '"+ this.stylePattern+ "'"); } return DateFormat.getDateInstance(this.style, locale); } private int getStylePatternForChar(int index) { - if(stylePattern != null && stylePattern.length() > index) { - switch (stylePattern.charAt(index)) { + if (this.stylePattern != null && this.stylePattern.length() > index) { + switch (this.stylePattern.charAt(index)) { case 'S': return DateFormat.SHORT; case 'M': return DateFormat.MEDIUM; case 'L': return DateFormat.LONG; @@ -203,7 +205,7 @@ private int getStylePatternForChar(int index) { case '-': return -1; } } - throw new IllegalStateException("Unsupported style pattern '"+ stylePattern+ "'"); + throw new IllegalStateException("Unsupported style pattern '" + this.stylePattern + "'"); } } diff --git a/spring-context/src/main/java/org/springframework/format/datetime/joda/JodaTimeContext.java b/spring-context/src/main/java/org/springframework/format/datetime/joda/JodaTimeContext.java index e2db64c6bff8..e4db4a2557ad 100644 --- a/spring-context/src/main/java/org/springframework/format/datetime/joda/JodaTimeContext.java +++ b/spring-context/src/main/java/org/springframework/format/datetime/joda/JodaTimeContext.java @@ -38,31 +38,31 @@ public class JodaTimeContext { /** - * Set the user's chronology. + * Set the user's chronology (calendar system). */ public void setChronology(Chronology chronology) { this.chronology = chronology; } /** - * The user's chronology (calendar system), if any. + * Return the user's chronology (calendar system), if any. */ public Chronology getChronology() { return this.chronology; } /** - * Set the user's timezone. + * Set the user's time zone. */ public void setTimeZone(DateTimeZone timeZone) { this.timeZone = timeZone; } /** - * The user's timezone, if any. + * Return the user's time zone, if any. */ public DateTimeZone getTimeZone() { - return timeZone; + return this.timeZone; } diff --git a/spring-context/src/main/java/org/springframework/jndi/JndiObjectFactoryBean.java b/spring-context/src/main/java/org/springframework/jndi/JndiObjectFactoryBean.java index 504a96ad8c64..a906dab947bb 100644 --- a/spring-context/src/main/java/org/springframework/jndi/JndiObjectFactoryBean.java +++ b/spring-context/src/main/java/org/springframework/jndi/JndiObjectFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2013 the original author 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,8 +25,14 @@ import org.aopalliance.intercept.MethodInvocation; import org.springframework.aop.framework.ProxyFactory; +import org.springframework.beans.SimpleTypeConverter; +import org.springframework.beans.TypeConverter; +import org.springframework.beans.TypeMismatchException; import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.util.ClassUtils; /** @@ -61,9 +67,10 @@ * @see #setCache * @see JndiObjectTargetSource */ -public class JndiObjectFactoryBean extends JndiObjectLocator implements FactoryBean, BeanClassLoaderAware { +public class JndiObjectFactoryBean extends JndiObjectLocator + implements FactoryBean, BeanFactoryAware, BeanClassLoaderAware { - private Class[] proxyInterfaces; + private Class[] proxyInterfaces; private boolean lookupOnStartup = true; @@ -73,6 +80,8 @@ public class JndiObjectFactoryBean extends JndiObjectLocator implements FactoryB private Object defaultObject; + private ConfigurableBeanFactory beanFactory; + private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); private Object jndiObject; @@ -87,8 +96,8 @@ public class JndiObjectFactoryBean extends JndiObjectLocator implements FactoryB * @see #setLookupOnStartup * @see #setCache */ - public void setProxyInterface(Class proxyInterface) { - this.proxyInterfaces = new Class[] {proxyInterface}; + public void setProxyInterface(Class proxyInterface) { + this.proxyInterfaces = new Class[] {proxyInterface}; } /** @@ -100,7 +109,7 @@ public void setProxyInterface(Class proxyInterface) { * @see #setLookupOnStartup * @see #setCache */ - public void setProxyInterfaces(Class[] proxyInterfaces) { + public void setProxyInterfaces(Class... proxyInterfaces) { this.proxyInterfaces = proxyInterfaces; } @@ -149,12 +158,26 @@ public void setExposeAccessContext(boolean exposeAccessContext) { * It is typically used for literal values in scenarios where the JNDI environment * might define specific config settings but those are not required to be present. *

Note: This is only supported for lookup on startup. + * If specified together with {@link #setExpectedType}, the specified value + * needs to be either of that type or convertible to it. * @see #setLookupOnStartup + * @see ConfigurableBeanFactory#getTypeConverter() + * @see SimpleTypeConverter */ public void setDefaultObject(Object defaultObject) { this.defaultObject = defaultObject; } + @Override + public void setBeanFactory(BeanFactory beanFactory) { + if (beanFactory instanceof ConfigurableBeanFactory) { + // Just optional - for getting a specifically configured TypeConverter if needed. + // We'll simply fall back to a SimpleTypeConverter if no specific one available. + this.beanFactory = (ConfigurableBeanFactory) beanFactory; + } + } + + @Override public void setBeanClassLoader(ClassLoader classLoader) { this.beanClassLoader = classLoader; } @@ -179,9 +202,16 @@ public void afterPropertiesSet() throws IllegalArgumentException, NamingExceptio else { if (this.defaultObject != null && getExpectedType() != null && !getExpectedType().isInstance(this.defaultObject)) { - throw new IllegalArgumentException("Default object [" + this.defaultObject + - "] of type [" + this.defaultObject.getClass().getName() + - "] is not of expected type [" + getExpectedType().getName() + "]"); + TypeConverter converter = (this.beanFactory != null ? + this.beanFactory.getTypeConverter() : new SimpleTypeConverter()); + try { + this.defaultObject = converter.convertIfNecessary(this.defaultObject, getExpectedType()); + } + catch (TypeMismatchException ex) { + throw new IllegalArgumentException("Default object [" + this.defaultObject + "] of type [" + + this.defaultObject.getClass().getName() + "] is not of expected type [" + + getExpectedType().getName() + "] and cannot be converted either", ex); + } } // Locate specified JNDI object. this.jndiObject = lookupWithFallback(); @@ -263,7 +293,7 @@ public boolean isSingleton() { * @return the merged interface as Class * @see java.lang.reflect.Proxy#getProxyClass */ - protected Class createCompositeInterface(Class[] interfaces) { + protected Class createCompositeInterface(Class[] interfaces) { return ClassUtils.createCompositeInterface(interfaces, this.beanClassLoader); } @@ -290,13 +320,13 @@ private static Object createJndiObjectProxy(JndiObjectFactoryBean jof) throws Na proxyFactory.setInterfaces(jof.proxyInterfaces); } else { - Class targetClass = targetSource.getTargetClass(); + Class targetClass = targetSource.getTargetClass(); if (targetClass == null) { throw new IllegalStateException( "Cannot deactivate 'lookupOnStartup' without specifying a 'proxyInterface' or 'expectedType'"); } - Class[] ifcs = ClassUtils.getAllInterfacesForClass(targetClass, jof.beanClassLoader); - for (Class ifc : ifcs) { + Class[] ifcs = ClassUtils.getAllInterfacesForClass(targetClass, jof.beanClassLoader); + for (Class ifc : ifcs) { if (Modifier.isPublic(ifc.getModifiers())) { proxyFactory.addInterface(ifc); } diff --git a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ExecutorConfigurationSupport.java b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ExecutorConfigurationSupport.java index 91764b332865..d13a2f616239 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ExecutorConfigurationSupport.java +++ b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ExecutorConfigurationSupport.java @@ -63,8 +63,8 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac /** - * Set the ThreadFactory to use for the ThreadPoolExecutor's thread pool. - * Default is the ThreadPoolExecutor's default thread factory. + * Set the ThreadFactory to use for the ExecutorService's thread pool. + * Default is the underlying ExecutorService's default thread factory. * @see java.util.concurrent.Executors#defaultThreadFactory() */ public void setThreadFactory(ThreadFactory threadFactory) { @@ -78,8 +78,8 @@ public void setThreadNamePrefix(String threadNamePrefix) { } /** - * Set the RejectedExecutionHandler to use for the ThreadPoolExecutor. - * Default is the ThreadPoolExecutor's default abort policy. + * Set the RejectedExecutionHandler to use for the ExecutorService. + * Default is the ExecutorService's default abort policy. * @see java.util.concurrent.ThreadPoolExecutor.AbortPolicy */ public void setRejectedExecutionHandler(RejectedExecutionHandler rejectedExecutionHandler) { @@ -181,7 +181,7 @@ public void destroy() { } /** - * Perform a shutdown on the ThreadPoolExecutor. + * Perform a shutdown on the underlying ExecutorService. * @see java.util.concurrent.ExecutorService#shutdown() * @see java.util.concurrent.ExecutorService#shutdownNow() * @see #awaitTerminationIfNecessary() diff --git a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ScheduledExecutorFactoryBean.java b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ScheduledExecutorFactoryBean.java index 40d48ad29554..41707680206d 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ScheduledExecutorFactoryBean.java +++ b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ScheduledExecutorFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,9 +37,10 @@ * *

Allows for registration of {@link ScheduledExecutorTask ScheduledExecutorTasks}, * automatically starting the {@link ScheduledExecutorService} on initialization and - * cancelling it on destruction of the context. In scenarios that just require static + * cancelling it on destruction of the context. In scenarios that only require static * registration of tasks at startup, there is no need to access the - * {@link ScheduledExecutorService} instance itself in application code. + * {@link ScheduledExecutorService} instance itself in application code at all; + * ScheduledExecutorFactoryBean is then just being used for lifecycle integration. * *

Note that {@link java.util.concurrent.ScheduledExecutorService} * uses a {@link Runnable} instance that is shared between repeated executions, @@ -92,7 +93,7 @@ public void setPoolSize(int poolSize) { * @see java.util.concurrent.ScheduledExecutorService#scheduleWithFixedDelay(java.lang.Runnable, long, long, java.util.concurrent.TimeUnit) * @see java.util.concurrent.ScheduledExecutorService#scheduleAtFixedRate(java.lang.Runnable, long, long, java.util.concurrent.TimeUnit) */ - public void setScheduledExecutorTasks(ScheduledExecutorTask[] scheduledExecutorTasks) { + public void setScheduledExecutorTasks(ScheduledExecutorTask... scheduledExecutorTasks) { this.scheduledExecutorTasks = scheduledExecutorTasks; } @@ -192,9 +193,9 @@ protected void registerTasks(ScheduledExecutorTask[] tasks, ScheduledExecutorSer * @return the actual Runnable to schedule (may be a decorator) */ protected Runnable getRunnableToSchedule(ScheduledExecutorTask task) { - return this.continueScheduledExecutionAfterException - ? new DelegatingErrorHandlingRunnable(task.getRunnable(), TaskUtils.LOG_AND_SUPPRESS_ERROR_HANDLER) - : new DelegatingErrorHandlingRunnable(task.getRunnable(), TaskUtils.LOG_AND_PROPAGATE_ERROR_HANDLER); + return (this.continueScheduledExecutionAfterException ? + new DelegatingErrorHandlingRunnable(task.getRunnable(), TaskUtils.LOG_AND_SUPPRESS_ERROR_HANDLER) : + new DelegatingErrorHandlingRunnable(task.getRunnable(), TaskUtils.LOG_AND_PROPAGATE_ERROR_HANDLER)); } diff --git a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolExecutorFactoryBean.java b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolExecutorFactoryBean.java index 3e04248ea868..3f912aa27f24 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolExecutorFactoryBean.java +++ b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolExecutorFactoryBean.java @@ -71,7 +71,6 @@ public class ThreadPoolExecutorFactoryBean extends ExecutorConfigurationSupport /** * Set the ThreadPoolExecutor's core pool size. * Default is 1. - *

This setting can be modified at runtime, for example through JMX. */ public void setCorePoolSize(int corePoolSize) { this.corePoolSize = corePoolSize; @@ -80,7 +79,6 @@ public void setCorePoolSize(int corePoolSize) { /** * Set the ThreadPoolExecutor's maximum pool size. * Default is {@code Integer.MAX_VALUE}. - *

This setting can be modified at runtime, for example through JMX. */ public void setMaxPoolSize(int maxPoolSize) { this.maxPoolSize = maxPoolSize; @@ -89,7 +87,6 @@ public void setMaxPoolSize(int maxPoolSize) { /** * Set the ThreadPoolExecutor's keep-alive seconds. * Default is 60. - *

This setting can be modified at runtime, for example through JMX. */ public void setKeepAliveSeconds(int keepAliveSeconds) { this.keepAliveSeconds = keepAliveSeconds; diff --git a/spring-context/src/main/java/org/springframework/scripting/support/ResourceScriptSource.java b/spring-context/src/main/java/org/springframework/scripting/support/ResourceScriptSource.java index c0de42493f40..9934edc0ea5b 100644 --- a/spring-context/src/main/java/org/springframework/scripting/support/ResourceScriptSource.java +++ b/spring-context/src/main/java/org/springframework/scripting/support/ResourceScriptSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,6 +23,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.core.io.Resource; import org.springframework.scripting.ScriptSource; import org.springframework.util.Assert; @@ -51,11 +52,12 @@ public class ResourceScriptSource implements ScriptSource { private final Resource resource; + private String encoding = "UTF-8"; + private long lastModified = -1; private final Object lastModifiedMonitor = new Object(); - private String encoding = "UTF-8"; /** * Create a new ResourceScriptSource for the given resource. @@ -74,15 +76,23 @@ public final Resource getResource() { return this.resource; } + /** + * Set the encoding used for reading the script resource. + *

The default value for regular Resources is "UTF-8". + * A {@code null} value implies the platform default. + */ + public void setEncoding(String encoding) { + this.encoding = encoding; + } + + public String getScriptAsString() throws IOException { synchronized (this.lastModifiedMonitor) { this.lastModified = retrieveLastModifiedTime(); } - InputStream stream = this.resource.getInputStream(); - Reader reader = (StringUtils.hasText(encoding) ? new InputStreamReader(stream, encoding) - : new InputStreamReader(stream)); - + Reader reader = (StringUtils.hasText(this.encoding) ? new InputStreamReader(stream, this.encoding) : + new InputStreamReader(stream)); return FileCopyUtils.copyToString(reader); } @@ -99,10 +109,11 @@ public boolean isModified() { protected long retrieveLastModifiedTime() { try { return getResource().lastModified(); - } catch (IOException ex) { + } + catch (IOException ex) { if (logger.isDebugEnabled()) { - logger.debug(getResource() + " could not be resolved in the file system - " - + "current timestamp not available for script modification check", ex); + logger.debug(getResource() + " could not be resolved in the file system - " + + "current timestamp not available for script modification check", ex); } return 0; } @@ -112,18 +123,9 @@ public String suggestedClassName() { return StringUtils.stripFilenameExtension(getResource().getFilename()); } - /** - * Sets the encoding used for reading the script resource. The default value is "UTF-8". - * A null value, implies the platform default. - * - * @param encoding charset encoding used for reading the script. - */ - public void setEncoding(String encoding) { - this.encoding = encoding; - } - @Override public String toString() { return this.resource.toString(); } + } diff --git a/spring-context/src/main/resources/org/springframework/context/config/spring-context-3.1.xsd b/spring-context/src/main/resources/org/springframework/context/config/spring-context-3.1.xsd index b2132e4e1c2e..34dca16031a3 100644 --- a/spring-context/src/main/resources/org/springframework/context/config/spring-context-3.1.xsd +++ b/spring-context/src/main/resources/org/springframework/context/config/spring-context-3.1.xsd @@ -1,13 +1,15 @@ + xmlns:xsd="http://www.w3.org/2001/XMLSchema" + xmlns:beans="http://www.springframework.org/schema/beans" + xmlns:tool="http://www.springframework.org/schema/tool" + targetNamespace="http://www.springframework.org/schema/context" + elementFormDefault="qualified" + attributeFormDefault="unqualified"> - - + + + type="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"/> @@ -158,14 +160,13 @@ ]]> - + - + @@ -273,9 +274,8 @@ ]]> - - + + @@ -288,9 +288,8 @@ ]]> - - + + @@ -304,9 +303,9 @@ - - - + + + @@ -342,8 +341,7 @@ ]]> - + @@ -355,9 +353,8 @@ ]]> - - + + @@ -396,23 +393,20 @@ - - - + - - + @@ -455,9 +448,9 @@ - - - + + + @@ -466,8 +459,7 @@ - - + @@ -503,8 +495,8 @@ "annotation" indicates an annotation to be present at the type level in target components; "assignable" indicates a class (or interface) that the target components are assignable to (extend/implement); - "aspectj" indicates an AspectJ type expression to be matched by the target components; - "regex" indicates a regex expression to be matched by the target components' class names; + "aspectj" indicates an AspectJ type pattern expression to be matched by the target components; + "regex" indicates a regex pattern to be matched by the target components' class names; "custom" indicates a custom implementation of the org.springframework.core.type.TypeFilter interface. Note: This attribute will not be inherited by child bean definitions. @@ -513,11 +505,11 @@ - - - - - + + + + + diff --git a/spring-context/src/main/resources/org/springframework/context/config/spring-context-3.2.xsd b/spring-context/src/main/resources/org/springframework/context/config/spring-context-3.2.xsd index f63ecc1aafdf..d650f0713566 100644 --- a/spring-context/src/main/resources/org/springframework/context/config/spring-context-3.2.xsd +++ b/spring-context/src/main/resources/org/springframework/context/config/spring-context-3.2.xsd @@ -1,13 +1,15 @@ + xmlns:xsd="http://www.w3.org/2001/XMLSchema" + xmlns:beans="http://www.springframework.org/schema/beans" + xmlns:tool="http://www.springframework.org/schema/tool" + targetNamespace="http://www.springframework.org/schema/context" + elementFormDefault="qualified" + attributeFormDefault="unqualified"> - - + + + type="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"/> @@ -158,14 +160,13 @@ ]]> - + - + @@ -273,9 +274,8 @@ ]]> - - + + @@ -288,9 +288,8 @@ ]]> - - + + @@ -304,9 +303,9 @@ - - - + + + @@ -342,8 +341,7 @@ ]]> - + @@ -355,9 +353,8 @@ ]]> - - + + @@ -396,23 +393,20 @@ - - - + - - + @@ -455,9 +448,9 @@ - - - + + + @@ -466,8 +459,7 @@ - - + @@ -503,8 +495,8 @@ "annotation" indicates an annotation to be present at the type level in target components; "assignable" indicates a class (or interface) that the target components are assignable to (extend/implement); - "aspectj" indicates an AspectJ type expression to be matched by the target components; - "regex" indicates a regex expression to be matched by the target components' class names; + "aspectj" indicates an AspectJ type pattern expression to be matched by the target components; + "regex" indicates a regex pattern to be matched by the target components' class names; "custom" indicates a custom implementation of the org.springframework.core.type.TypeFilter interface. Note: This attribute will not be inherited by child bean definitions. @@ -513,11 +505,11 @@ - - - - - + + + + + diff --git a/spring-context/src/test/java/org/springframework/beans/factory/xml/QualifierAnnotationTests-context.xml b/spring-context/src/test/java/org/springframework/beans/factory/xml/QualifierAnnotationTests-context.xml index bdc524aa8bac..2f7db2105691 100644 --- a/spring-context/src/test/java/org/springframework/beans/factory/xml/QualifierAnnotationTests-context.xml +++ b/spring-context/src/test/java/org/springframework/beans/factory/xml/QualifierAnnotationTests-context.xml @@ -7,11 +7,19 @@ + + + + larry + + + + - + diff --git a/spring-context/src/test/java/org/springframework/context/AbstractApplicationContextTests.java b/spring-context/src/test/java/org/springframework/context/AbstractApplicationContextTests.java index 6f407fb24e07..d92309186e29 100644 --- a/spring-context/src/test/java/org/springframework/context/AbstractApplicationContextTests.java +++ b/spring-context/src/test/java/org/springframework/context/AbstractApplicationContextTests.java @@ -16,8 +16,13 @@ package org.springframework.context; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.util.Locale; +import org.junit.Test; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.xml.AbstractListableBeanFactoryTests; import org.springframework.tests.sample.beans.LifecycleBean; @@ -129,11 +134,29 @@ public void testMessageSource() throws NoSuchMessageException { } public void testEvents() throws Exception { + doTestEvents(this.listener, this.parentListener, new MyEvent(this)); + } + + @Test + public void testEventsWithNoSource() throws Exception { + // See SPR-10945 Serialized events result in a null source + MyEvent event = new MyEvent(this); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + oos.writeObject(event); + oos.close(); + event = (MyEvent) new ObjectInputStream(new ByteArrayInputStream( + bos.toByteArray())).readObject(); + doTestEvents(this.listener, this.parentListener, event); + } + + protected void doTestEvents(TestListener listener, TestListener parentListener, + MyEvent event) { listener.zeroCounter(); parentListener.zeroCounter(); assertTrue("0 events before publication", listener.getEventCount() == 0); assertTrue("0 parent events before publication", parentListener.getEventCount() == 0); - this.applicationContext.publishEvent(new MyEvent(this)); + this.applicationContext.publishEvent(event); assertTrue("1 events after publication, not " + listener.getEventCount(), listener.getEventCount() == 1); assertTrue("1 parent events after publication", parentListener.getEventCount() == 1); } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/BeanMethodPolymorphismTests.java b/spring-context/src/test/java/org/springframework/context/annotation/BeanMethodPolymorphismTests.java index 1d573540f8a2..ebdc073b9b7b 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/BeanMethodPolymorphismTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/BeanMethodPolymorphismTests.java @@ -16,61 +16,83 @@ package org.springframework.context.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 java.lang.annotation.Inherited; +import java.util.List; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; + import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.beans.factory.support.RootBeanDefinition; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; /** * Tests regarding overloading and overriding of bean methods. * Related to SPR-6618. * - * Bean-annotated methods should be able to be overridden, just as any regular - * method. This is straightforward. - * - * Bean-annotated methods should be able to be overloaded, though supporting this - * is more subtle. Essentially, it must be unambiguous to the container which bean - * method to call. A simple way to think about this is that no one Configuration - * class may declare two bean methods with the same name. In the case of inheritance, - * the most specific subclass bean method will always be the one that is invoked. - * * @author Chris Beams + * @author Phillip Webb + * @author Juergen Hoeller */ public class BeanMethodPolymorphismTests { + @Rule + public ExpectedException thrown = ExpectedException.none(); + + + @Test + public void beanMethodDetectedOnSuperClass() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class); + ctx.getBean("testBean", TestBean.class); + } + + @Test + public void beanMethodOverriding() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(OverridingConfig.class); + ctx.setAllowBeanDefinitionOverriding(false); + ctx.refresh(); + assertFalse(ctx.getDefaultListableBeanFactory().containsSingleton("testBean")); + assertEquals("overridden", ctx.getBean("testBean", TestBean.class).toString()); + assertTrue(ctx.getDefaultListableBeanFactory().containsSingleton("testBean")); + } + @Test public void beanMethodOverloadingWithoutInheritance() { + @SuppressWarnings({ "hiding" }) @Configuration class Config { @Bean String aString() { return "na"; } @Bean String aString(Integer dependency) { return "na"; } } - try { - new AnnotationConfigApplicationContext(Config.class); - fail("expected bean method overloading exception"); - } catch (BeanDefinitionParsingException ex) { - assertTrue(ex.getMessage(), ex.getMessage().contains("2 overloaded @Bean methods named 'aString'")); - } + + this.thrown.expect(BeanDefinitionParsingException.class); + this.thrown.expectMessage("overloaded @Bean methods named 'aString'"); + new AnnotationConfigApplicationContext(Config.class); } @Test public void beanMethodOverloadingWithInheritance() { - AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SubConfig.class); + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(SubConfig.class); + ctx.setAllowBeanDefinitionOverriding(false); + ctx.refresh(); + assertFalse(ctx.getDefaultListableBeanFactory().containsSingleton("aString")); assertThat(ctx.getBean(String.class), equalTo("overloaded5")); + assertTrue(ctx.getDefaultListableBeanFactory().containsSingleton("aString")); } - static @Configuration class SuperConfig { - @Bean String aString() { return "super"; } - } - static @Configuration class SubConfig { - @Bean Integer anInt() { return 5; } - @Bean String aString(Integer dependency) { return "overloaded"+dependency; } + + // SPR-11025 + @Test + public void beanMethodOverloadingWithInheritanceAndList() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(SubConfigWithList.class); + ctx.setAllowBeanDefinitionOverriding(false); + ctx.refresh(); + assertFalse(ctx.getDefaultListableBeanFactory().containsSingleton("aString")); + assertThat(ctx.getBean(String.class), equalTo("overloaded5")); + assertTrue(ctx.getDefaultListableBeanFactory().containsSingleton("aString")); } /** @@ -83,24 +105,6 @@ public void beanMethodShadowing() { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ShadowConfig.class); assertThat(ctx.getBean(String.class), equalTo("shadow")); } - @Import(SubConfig.class) - static @Configuration class ShadowConfig { - @Bean String aString() { return "shadow"; } - } - - /** - * Tests that polymorphic Configuration classes need not explicitly redeclare the - * {@link Configuration} annotation. This respects the {@link Inherited} nature - * of the Configuration annotation, even though it's being detected via ASM. - */ - @Test - public void beanMethodsDetectedOnSuperClass() { - DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); - beanFactory.registerBeanDefinition("config", new RootBeanDefinition(Config.class)); - ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor(); - pp.postProcessBeanFactory(beanFactory); - beanFactory.getBean("testBean", TestBean.class); - } @Configuration @@ -117,4 +121,71 @@ public TestBean testBean() { static class Config extends BaseConfig { } + + @Configuration + static class OverridingConfig extends BaseConfig { + + @Bean @Lazy + @Override + public TestBean testBean() { + return new TestBean() { + @Override + public String toString() { + return "overridden"; + } + }; + } + } + + + @Configuration + static class SuperConfig { + + @Bean + String aString() { + return "super"; + } + } + + + @Configuration + static class SubConfig extends SuperConfig { + + @Bean + Integer anInt() { + return 5; + } + + @Bean @Lazy + String aString(Integer dependency) { + return "overloaded" + dependency; + } + } + + + @Configuration + static class SubConfigWithList extends SuperConfig { + + @Bean + Integer anInt() { + return 5; + } + + @Bean @Lazy + String aString(List dependency) { + return "overloaded" + dependency.get(0); + } + } + + + @Configuration + @Import(SubConfig.class) + static class ShadowConfig { + + @Bean + String aString() { + return "shadow"; + } + } + } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/PropertySourceAnnotationTests.java b/spring-context/src/test/java/org/springframework/context/annotation/PropertySourceAnnotationTests.java index aaf2a82c18f7..45673cfd654d 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/PropertySourceAnnotationTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/PropertySourceAnnotationTests.java @@ -130,6 +130,18 @@ public void withNameAndMultipleResourceLocations() { assertThat(ctx.getEnvironment().containsProperty("from.p2"), is(true)); } + /** + * SPR-10820 + */ + @Test + public void orderingWithAndWithoutNameAndMultipleResourceLocations() { + // p2 should 'win' as it was registered last + AnnotationConfigApplicationContext ctxWithName = new AnnotationConfigApplicationContext(ConfigWithNameAndMultipleResourceLocations.class); + AnnotationConfigApplicationContext ctxWithoutName = new AnnotationConfigApplicationContext(ConfigWithMultipleResourceLocations.class); + assertThat(ctxWithoutName.getEnvironment().getProperty("testbean.name"), equalTo("p2TestBean")); + assertThat(ctxWithName.getEnvironment().getProperty("testbean.name"), equalTo("p2TestBean")); + } + @Test(expected=IllegalArgumentException.class) public void withEmptyResourceLocations() { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); @@ -209,6 +221,15 @@ static class P2Config { static class ConfigWithNameAndMultipleResourceLocations { } + @Configuration + @PropertySource( + value = { + "classpath:org/springframework/context/annotation/p1.properties", + "classpath:org/springframework/context/annotation/p2.properties" + }) + static class ConfigWithMultipleResourceLocations { + } + @Configuration @PropertySource(value = {}) diff --git a/spring-context/src/test/java/org/springframework/jndi/JndiObjectFactoryBeanTests.java b/spring-context/src/test/java/org/springframework/jndi/JndiObjectFactoryBeanTests.java index d9d7e60ac6b5..5d14e715a376 100644 --- a/spring-context/src/test/java/org/springframework/jndi/JndiObjectFactoryBeanTests.java +++ b/spring-context/src/test/java/org/springframework/jndi/JndiObjectFactoryBeanTests.java @@ -20,6 +20,8 @@ import javax.naming.NamingException; import org.junit.Test; + +import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.tests.mock.jndi.ExpectedLookupTemplate; import org.springframework.tests.sample.beans.DerivedTestBean; import org.springframework.tests.sample.beans.ITestBean; @@ -142,8 +144,7 @@ public void testLookupWithExpectedTypeAndMatch() throws Exception { @Test public void testLookupWithExpectedTypeAndNoMatch() throws Exception { JndiObjectFactoryBean jof = new JndiObjectFactoryBean(); - Object o = new Object(); - jof.setJndiTemplate(new ExpectedLookupTemplate("foo", o)); + jof.setJndiTemplate(new ExpectedLookupTemplate("foo", new Object())); jof.setJndiName("foo"); jof.setExpectedType(String.class); try { @@ -151,15 +152,14 @@ public void testLookupWithExpectedTypeAndNoMatch() throws Exception { fail("Should have thrown NamingException"); } catch (NamingException ex) { - assertTrue(ex.getMessage().indexOf("java.lang.String") != -1); + assertTrue(ex.getMessage().contains("java.lang.String")); } } @Test public void testLookupWithDefaultObject() throws Exception { JndiObjectFactoryBean jof = new JndiObjectFactoryBean(); - String s = ""; - jof.setJndiTemplate(new ExpectedLookupTemplate("foo", s)); + jof.setJndiTemplate(new ExpectedLookupTemplate("foo", "")); jof.setJndiName("myFoo"); jof.setExpectedType(String.class); jof.setDefaultObject("myString"); @@ -170,8 +170,7 @@ public void testLookupWithDefaultObject() throws Exception { @Test public void testLookupWithDefaultObjectAndExpectedType() throws Exception { JndiObjectFactoryBean jof = new JndiObjectFactoryBean(); - String s = ""; - jof.setJndiTemplate(new ExpectedLookupTemplate("foo", s)); + jof.setJndiTemplate(new ExpectedLookupTemplate("foo", "")); jof.setJndiName("myFoo"); jof.setExpectedType(String.class); jof.setDefaultObject("myString"); @@ -179,14 +178,36 @@ public void testLookupWithDefaultObjectAndExpectedType() throws Exception { assertEquals("myString", jof.getObject()); } + @Test + public void testLookupWithDefaultObjectAndExpectedTypeConversion() throws Exception { + JndiObjectFactoryBean jof = new JndiObjectFactoryBean(); + jof.setJndiTemplate(new ExpectedLookupTemplate("foo", "")); + jof.setJndiName("myFoo"); + jof.setExpectedType(Integer.class); + jof.setDefaultObject("5"); + jof.afterPropertiesSet(); + assertEquals(new Integer(5), jof.getObject()); + } + + @Test + public void testLookupWithDefaultObjectAndExpectedTypeConversionViaBeanFactory() throws Exception { + JndiObjectFactoryBean jof = new JndiObjectFactoryBean(); + jof.setJndiTemplate(new ExpectedLookupTemplate("foo", "")); + jof.setJndiName("myFoo"); + jof.setExpectedType(Integer.class); + jof.setDefaultObject("5"); + jof.setBeanFactory(new DefaultListableBeanFactory()); + jof.afterPropertiesSet(); + assertEquals(new Integer(5), jof.getObject()); + } + @Test public void testLookupWithDefaultObjectAndExpectedTypeNoMatch() throws Exception { JndiObjectFactoryBean jof = new JndiObjectFactoryBean(); - String s = ""; - jof.setJndiTemplate(new ExpectedLookupTemplate("foo", s)); + jof.setJndiTemplate(new ExpectedLookupTemplate("foo", "")); jof.setJndiName("myFoo"); - jof.setExpectedType(String.class); - jof.setDefaultObject(Boolean.TRUE); + jof.setExpectedType(Boolean.class); + jof.setDefaultObject("5"); try { jof.afterPropertiesSet(); fail("Should have thrown IllegalArgumentException"); diff --git a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java index e5a3834528cd..b860aaafca8b 100644 --- a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java +++ b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java @@ -131,8 +131,10 @@ public static Class resolveReturnType(Method method, Class clazz) { * invoked, never {@code null} * @return the resolved target return type, the standard return type, or {@code null} * @since 3.2 - * @see #resolveReturnType + * @deprecated in favor of resolveReturnTypeForFactoryMethod in the internal + * AutowireUtils class in the beans module; we do not expect other use of it! */ + @Deprecated public static Class resolveReturnTypeForGenericMethod(Method method, Object[] args) { Assert.notNull(method, "Method must not be null"); Assert.notNull(args, "Argument array must not be null"); @@ -229,7 +231,7 @@ public static Class resolveReturnTypeArgument(Method method, Class generic * @return the resolved type of the argument, or {@code null} if not resolvable */ public static Class resolveTypeArgument(Class clazz, Class genericIfc) { - Class[] typeArgs = resolveTypeArguments(clazz, genericIfc); + Class[] typeArgs = resolveTypeArguments(clazz, genericIfc); if (typeArgs == null) { return null; } @@ -244,21 +246,25 @@ public static Class resolveTypeArgument(Class clazz, Class genericIfc) * Resolve the type arguments of the given generic interface against the given * target class which is assumed to implement the generic interface and possibly * declare concrete types for its type variables. + *

Note: In Spring 3.2, this method doesn't return {@code null} in all scenarios + * where it should. To be fixed in Spring 4.0; for client code, this just means it + * might see {@code null} in a few more cases then where it now sees an array with + * a single {@link Object} type. * @param clazz the target class to check against * @param genericIfc the generic interface or superclass to resolve the type argument from * @return the resolved type of each argument, with the array size matching the * number of actual type arguments, or {@code null} if not resolvable */ - public static Class[] resolveTypeArguments(Class clazz, Class genericIfc) { + public static Class[] resolveTypeArguments(Class clazz, Class genericIfc) { return doResolveTypeArguments(clazz, clazz, genericIfc); } - private static Class[] doResolveTypeArguments(Class ownerClass, Class classToIntrospect, Class genericIfc) { + private static Class[] doResolveTypeArguments(Class ownerClass, Class classToIntrospect, Class genericIfc) { while (classToIntrospect != null) { if (genericIfc.isInterface()) { Type[] ifcs = classToIntrospect.getGenericInterfaces(); for (Type ifc : ifcs) { - Class[] result = doResolveTypeArguments(ownerClass, ifc, genericIfc); + Class[] result = doResolveTypeArguments(ownerClass, ifc, genericIfc); if (result != null) { return result; } @@ -266,7 +272,7 @@ private static Class[] doResolveTypeArguments(Class ownerClass, Class clas } else { try { - Class[] result = doResolveTypeArguments(ownerClass, classToIntrospect.getGenericSuperclass(), genericIfc); + Class[] result = doResolveTypeArguments(ownerClass, classToIntrospect.getGenericSuperclass(), genericIfc); if (result != null) { return result; } @@ -281,13 +287,13 @@ private static Class[] doResolveTypeArguments(Class ownerClass, Class clas return null; } - private static Class[] doResolveTypeArguments(Class ownerClass, Type ifc, Class genericIfc) { + private static Class[] doResolveTypeArguments(Class ownerClass, Type ifc, Class genericIfc) { if (ifc instanceof ParameterizedType) { ParameterizedType paramIfc = (ParameterizedType) ifc; Type rawType = paramIfc.getRawType(); if (genericIfc.equals(rawType)) { Type[] typeArgs = paramIfc.getActualTypeArguments(); - Class[] result = new Class[typeArgs.length]; + Class[] result = new Class[typeArgs.length]; for (int i = 0; i < typeArgs.length; i++) { Type arg = typeArgs[i]; result[i] = extractClass(ownerClass, arg); @@ -305,7 +311,7 @@ else if (ifc != null && genericIfc.isAssignableFrom((Class) ifc)) { } /** - * Extract a class instance from given Type. + * Extract a Class from the given Type. */ private static Class extractClass(Class ownerClass, Type arg) { if (arg instanceof ParameterizedType) { @@ -322,9 +328,12 @@ else if (arg instanceof TypeVariable) { arg = getTypeVariableMap(ownerClass).get(tv); if (arg == null) { arg = extractBoundForTypeVariable(tv); + if (arg instanceof ParameterizedType) { + return extractClass(ownerClass, ((ParameterizedType) arg).getRawType()); + } } else { - arg = extractClass(ownerClass, arg); + return extractClass(ownerClass, arg); } } return (arg instanceof Class ? (Class) arg : Object.class); 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 28e9f90677d3..d2f497d81405 100644 --- a/spring-core/src/main/java/org/springframework/core/MethodParameter.java +++ b/spring-core/src/main/java/org/springframework/core/MethodParameter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -179,7 +179,7 @@ private AnnotatedElement getAnnotatedElement() { /** * Return the class that declares the underlying Method or Constructor. */ - public Class getDeclaringClass() { + public Class getDeclaringClass() { return getMember().getDeclaringClass(); } diff --git a/spring-core/src/main/java/org/springframework/core/env/EnvironmentCapable.java b/spring-core/src/main/java/org/springframework/core/env/EnvironmentCapable.java index e95528fa3539..7d6ca3875fca 100644 --- a/spring-core/src/main/java/org/springframework/core/env/EnvironmentCapable.java +++ b/spring-core/src/main/java/org/springframework/core/env/EnvironmentCapable.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,9 +16,8 @@ package org.springframework.core.env; - /** - * Interface indicating a component contains and makes available an {@link Environment} object. + * Interface indicating a component that contains and exposes an {@link Environment} reference. * *

All Spring application contexts are EnvironmentCapable, and the interface is used primarily * for performing {@code instanceof} checks in framework methods that accept BeanFactory @@ -29,20 +28,20 @@ * extends EnvironmentCapable, and thus exposes a {@link #getEnvironment()} method; however, * {@link org.springframework.context.ConfigurableApplicationContext ConfigurableApplicationContext} * redefines {@link org.springframework.context.ConfigurableApplicationContext#getEnvironment - * getEnvironment()} and narrows the signature to return a {@link ConfigurableEnvironment}. The effect - * is that an Environment object is 'read-only' until it accessed from a ConfigurableApplicationContext, - * at which point it too may be configured. + * getEnvironment()} and narrows the signature to return a {@link ConfigurableEnvironment}. + * The effect is that an Environment object is 'read-only' until it is being accessed from + * a ConfigurableApplicationContext, at which point it too may be configured. * * @author Chris Beams * @since 3.1 * @see Environment * @see ConfigurableEnvironment - * @see org.springframework.context.ConfigurableApplicationContext#getEnvironment + * @see org.springframework.context.ConfigurableApplicationContext#getEnvironment() */ public interface EnvironmentCapable { /** - * Return the Environment for this object + * Return the {@link Environment} associated with this component. */ Environment getEnvironment(); 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 0b89eeec7835..661ba304485f 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 @@ -28,7 +28,6 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.asm.AnnotationVisitor; import org.springframework.asm.SpringAsmInfo; import org.springframework.asm.Type; @@ -122,7 +121,13 @@ public void visit(String attributeName, Object attributeValue) { newValue = ObjectUtils.addObjectToArray((Object[]) existingValue, newValue); } else { - Object[] newArray = (Object[]) Array.newInstance(newValue.getClass(), 1); + Class arrayClass = newValue.getClass(); + if(Enum.class.isAssignableFrom(arrayClass)) { + while(arrayClass.getSuperclass() != null && !arrayClass.isEnum()) { + arrayClass = arrayClass.getSuperclass(); + } + } + Object[] newArray = (Object[]) Array.newInstance(arrayClass, 1); newArray[0] = newValue; newValue = newArray; } 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 467b0f7cb6ef..ed84ba02a639 100644 --- a/spring-core/src/main/java/org/springframework/util/ClassUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ClassUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,15 +17,12 @@ package org.springframework.util; import java.beans.Introspector; - import java.lang.reflect.Array; 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; @@ -578,7 +575,7 @@ public static boolean matchesTypeName(Class clazz, String typeName) { /** * Determine whether the given class has a public constructor with the given signature. *

Essentially translates {@code NoSuchMethodException} to "false". - * @param clazz the clazz to analyze + * @param clazz the clazz to analyze * @param paramTypes the parameter types of the method * @return whether the class has a corresponding constructor * @see Class#getMethod @@ -591,7 +588,7 @@ public static boolean hasConstructor(Class clazz, Class... paramTypes) { * Determine whether the given class has a public constructor with the given signature, * and return it if available (else return {@code null}). *

Essentially translates {@code NoSuchMethodException} to {@code null}. - * @param clazz the clazz to analyze + * @param clazz the clazz to analyze * @param paramTypes the parameter types of the method * @return the constructor, or {@code null} if not found * @see Class#getConstructor @@ -607,9 +604,9 @@ public static Constructor getConstructorIfAvailable(Class clazz, Class } /** - * Determine whether the given class has a method with the given signature. + * Determine whether the given class has a public method with the given signature. *

Essentially translates {@code NoSuchMethodException} to "false". - * @param clazz the clazz to analyze + * @param clazz the clazz to analyze * @param methodName the name of the method * @param paramTypes the parameter types of the method * @return whether the class has a corresponding method @@ -620,12 +617,15 @@ public static boolean hasMethod(Class clazz, String methodName, Class... p } /** - * Determine whether the given class has a method with the given signature, + * Determine whether the given class has a public method with the given signature, * and return it if available (else throws an {@code IllegalStateException}). + *

In case of any signature specified, only returns the method if there is a + * unique candidate, i.e. a single public method with the specified name. *

Essentially translates {@code NoSuchMethodException} to {@code IllegalStateException}. - * @param clazz the clazz to analyze + * @param clazz the clazz to analyze * @param methodName the name of the method * @param paramTypes the parameter types of the method + * (may be {@code null} to indicate any signature) * @return the method (never {@code null}) * @throws IllegalStateException if the method has not been found * @see Class#getMethod @@ -633,31 +633,69 @@ public static boolean hasMethod(Class clazz, String methodName, Class... p public static Method getMethod(Class clazz, String methodName, Class... paramTypes) { Assert.notNull(clazz, "Class must not be null"); Assert.notNull(methodName, "Method name must not be null"); - try { - return clazz.getMethod(methodName, paramTypes); + if (paramTypes != null) { + try { + return clazz.getMethod(methodName, paramTypes); + } + catch (NoSuchMethodException ex) { + throw new IllegalStateException("Expected method not found: " + ex); + } } - catch (NoSuchMethodException ex) { - throw new IllegalStateException("Expected method not found: " + ex); + else { + Set candidates = new HashSet(1); + Method[] methods = clazz.getMethods(); + for (Method method : methods) { + if (methodName.equals(method.getName())) { + candidates.add(method); + } + } + if (candidates.size() == 1) { + return candidates.iterator().next(); + } + else if (candidates.isEmpty()) { + throw new IllegalStateException("Expected method not found: " + clazz + "." + methodName); + } + else { + throw new IllegalStateException("No unique method found: " + clazz + "." + methodName); + } } } /** - * Determine whether the given class has a method with the given signature, + * Determine whether the given class has a public method with the given signature, * and return it if available (else return {@code null}). + *

In case of any signature specified, only returns the method if there is a + * unique candidate, i.e. a single public method with the specified name. *

Essentially translates {@code NoSuchMethodException} to {@code null}. - * @param clazz the clazz to analyze + * @param clazz the clazz to analyze * @param methodName the name of the method * @param paramTypes the parameter types of the method + * (may be {@code null} to indicate any signature) * @return the method, or {@code null} if not found * @see Class#getMethod */ public static Method getMethodIfAvailable(Class clazz, String methodName, Class... paramTypes) { Assert.notNull(clazz, "Class must not be null"); Assert.notNull(methodName, "Method name must not be null"); - try { - return clazz.getMethod(methodName, paramTypes); + if (paramTypes != null) { + try { + return clazz.getMethod(methodName, paramTypes); + } + catch (NoSuchMethodException ex) { + return null; + } } - catch (NoSuchMethodException ex) { + else { + Set candidates = new HashSet(1); + Method[] methods = clazz.getMethods(); + for (Method method : methods) { + if (methodName.equals(method.getName())) { + candidates.add(method); + } + } + if (candidates.size() == 1) { + return candidates.iterator().next(); + } return null; } } @@ -1025,7 +1063,7 @@ public static Class[] toClassArray(Collection> collection) { * @param instance the instance to analyze for interfaces * @return all interfaces that the given instance implements as array */ - public static Class[] getAllInterfaces(Object instance) { + public static Class[] getAllInterfaces(Object instance) { Assert.notNull(instance, "Instance must not be null"); return getAllInterfacesForClass(instance.getClass()); } diff --git a/spring-core/src/main/java/org/springframework/util/ConcurrentReferenceHashMap.java b/spring-core/src/main/java/org/springframework/util/ConcurrentReferenceHashMap.java index 3da70f602b62..916b322576da 100644 --- a/spring-core/src/main/java/org/springframework/util/ConcurrentReferenceHashMap.java +++ b/spring-core/src/main/java/org/springframework/util/ConcurrentReferenceHashMap.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,7 +48,7 @@ * references at any time, so it may appear that an unknown thread is silently removing * entries. * - *

If not explicitly specified this implementation will use + *

If not explicitly specified, this implementation will use * {@linkplain SoftReference soft entry references}. * * @param The key type @@ -56,8 +56,7 @@ * @author Phillip Webb * @since 3.2 */ -public class ConcurrentReferenceHashMap extends AbstractMap implements - ConcurrentMap { +public class ConcurrentReferenceHashMap extends AbstractMap implements ConcurrentMap { private static final int DEFAULT_INITIAL_CAPACITY = 16; @@ -82,6 +81,9 @@ public class ConcurrentReferenceHashMap extends AbstractMap implemen */ private final float loadFactor; + /** + * The reference type: SOFT or WEAK. + */ private final ReferenceType referenceType; /** @@ -99,8 +101,7 @@ public class ConcurrentReferenceHashMap extends AbstractMap implemen * Create a new {@code ConcurrentReferenceHashMap} instance. */ public ConcurrentReferenceHashMap() { - this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, - DEFAULT_REFERENCE_TYPE); + this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE); } /** @@ -108,8 +109,7 @@ public ConcurrentReferenceHashMap() { * @param initialCapacity the initial capacity of the map */ public ConcurrentReferenceHashMap(int initialCapacity) { - this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, - DEFAULT_REFERENCE_TYPE); + this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE); } /** @@ -119,45 +119,44 @@ public ConcurrentReferenceHashMap(int initialCapacity) { * exceeds this value resize will be attempted */ public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor) { - this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL, - DEFAULT_REFERENCE_TYPE); + this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE); } /** * Create a new {@code ConcurrentReferenceHashMap} instance. * @param initialCapacity the initial capacity of the map - * @param concurrencyLevel the expected number of threads that will concurrently write - * to the map + * @param concurrencyLevel the expected number of threads that will concurrently + * write to the map */ public ConcurrentReferenceHashMap(int initialCapacity, int concurrencyLevel) { - this(initialCapacity, DEFAULT_LOAD_FACTOR, concurrencyLevel, - DEFAULT_REFERENCE_TYPE); + this(initialCapacity, DEFAULT_LOAD_FACTOR, concurrencyLevel, DEFAULT_REFERENCE_TYPE); } /** * Create a new {@code ConcurrentReferenceHashMap} instance. * @param initialCapacity the initial capacity of the map - * @param loadFactor the load factor. When the average number of references per table - * exceeds this value resize will be attempted - * @param concurrencyLevel the expected number of threads that will concurrently write - * to the map + * @param loadFactor the load factor. When the average number of references per + * table exceeds this value, resize will be attempted. + * @param concurrencyLevel the expected number of threads that will concurrently + * write to the map */ - public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor, - int concurrencyLevel) { + public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) { this(initialCapacity, loadFactor, concurrencyLevel, DEFAULT_REFERENCE_TYPE); } /** * Create a new {@code ConcurrentReferenceHashMap} instance. * @param initialCapacity the initial capacity of the map - * @param loadFactor the load factor. When the average number of references per table - * exceeds this value resize will be attempted - * @param concurrencyLevel the expected number of threads that will concurrently write - * to the map + * @param loadFactor the load factor. When the average number of references per + * table exceeds this value, resize will be attempted. + * @param concurrencyLevel the expected number of threads that will concurrently + * write to the map * @param referenceType the reference type used for entries */ - public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor, - int concurrencyLevel, ReferenceType referenceType) { + @SuppressWarnings("unchecked") + public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor, int concurrencyLevel, + ReferenceType referenceType) { + Assert.isTrue(concurrencyLevel > 0, "ConcurrencyLevel must be positive"); Assert.isTrue(initialCapacity >= 0, "InitialCapacity must not be negative"); Assert.isTrue(loadFactor > 0f, "LoadFactor must be positive"); @@ -167,17 +166,12 @@ public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor, int size = 1 << this.shift; this.referenceType = referenceType; int roundedUpSegmentCapactity = (int) ((initialCapacity + size - 1L) / size); - this.segments = createSegmentsArray(size); + this.segments = (Segment[]) Array.newInstance(Segment.class, size); for (int i = 0; i < this.segments.length; i++) { this.segments[i] = new Segment(roundedUpSegmentCapactity); } } - @SuppressWarnings("unchecked") - private Segment[] createSegmentsArray(int size) { - return (Segment[]) Array.newInstance(Segment.class, size); - } - protected final float getLoadFactor() { return this.loadFactor; @@ -222,7 +216,7 @@ protected int getHash(Object o) { public V get(Object key) { Reference reference = getReference(key, Restructure.WHEN_NECESSARY); Entry entry = (reference == null ? null : reference.get()); - return (entry == null ? null : entry.getValue()); + return (entry != null ? entry.getValue() : null); } @Override @@ -388,7 +382,7 @@ public static enum ReferenceType { /** * Use {@link WeakReference}s. */ - WEAK; + WEAK } @@ -421,14 +415,12 @@ protected final class Segment extends ReentrantLock { */ private int resizeThreshold; - public Segment(int initialCapacity) { this.referenceManager = createReferenceManager(); this.initialSize = 1 << calculateShift(initialCapacity, MAXIMUM_SEGMENT_SIZE); setReferences(createReferenceArray(this.initialSize)); } - public Reference getReference(Object key, int hash, Restructure restructure) { if (restructure == Restructure.WHEN_NECESSARY) { restructureIfNecessary(false); @@ -452,17 +444,13 @@ public Reference getReference(Object key, int hash, Restructure restructur * @return the result of the operation */ public T doTask(final int hash, final Object key, final Task task) { - boolean resize = task.hasOption(TaskOption.RESIZE); - if (task.hasOption(TaskOption.RESTRUCTURE_BEFORE)) { restructureIfNecessary(resize); } - if (task.hasOption(TaskOption.SKIP_IF_EMPTY) && (this.count == 0)) { return task.execute(null, null, null); } - lock(); try { final int index = getIndex(hash, this.references); @@ -480,7 +468,8 @@ public void add(V value) { } }; return task.execute(reference, entry, entries); - } finally { + } + finally { unlock(); if (task.hasOption(TaskOption.RESTRUCTURE_AFTER)) { restructureIfNecessary(resize); @@ -569,8 +558,7 @@ private void restructureIfNecessary(boolean allowResize) { } } - private Reference findInChain(Reference reference, Object key, - int hash) { + private Reference findInChain(Reference reference, Object key, int hash) { while (reference != null) { if (reference.getHash() == hash) { Entry entry = reference.get(); @@ -752,6 +740,7 @@ protected T execute(Reference reference, Entry entry) { * Various options supported by a {@link Task}. */ private static enum TaskOption { + RESTRUCTURE_BEFORE, RESTRUCTURE_AFTER, SKIP_IF_EMPTY, RESIZE } @@ -783,8 +772,7 @@ public Iterator> iterator() { public boolean contains(Object o) { if (o != null && o instanceof Map.Entry) { Map.Entry entry = (java.util.Map.Entry) o; - Reference reference = ConcurrentReferenceHashMap.this.getReference( - entry.getKey(), Restructure.NEVER); + Reference reference = ConcurrentReferenceHashMap.this.getReference(entry.getKey(), Restructure.NEVER); Entry other = (reference == null ? null : reference.get()); if (other != null) { return ObjectUtils.nullSafeEquals(entry.getValue(), other.getValue()); @@ -797,8 +785,7 @@ public boolean contains(Object o) { public boolean remove(Object o) { if (o instanceof Map.Entry) { Map.Entry entry = (Map.Entry) o; - return ConcurrentReferenceHashMap.this.remove(entry.getKey(), - entry.getValue()); + return ConcurrentReferenceHashMap.this.remove(entry.getKey(), entry.getValue()); } return false; } @@ -897,6 +884,7 @@ public void remove() { * The types of restructuring that can be performed. */ protected static enum Restructure { + WHEN_NECESSARY, NEVER } @@ -916,8 +904,7 @@ protected class ReferenceManager { * @param next the next reference in the chain or {@code null} * @return a new {@link Reference} */ - public Reference createReference(Entry entry, int hash, - Reference next) { + public Reference createReference(Entry entry, int hash, Reference next) { if (ConcurrentReferenceHashMap.this.referenceType == ReferenceType.WEAK) { return new WeakEntryReference(entry, hash, next, this.queue); } @@ -941,15 +928,13 @@ public Reference pollForPurge() { /** * Internal {@link Reference} implementation for {@link SoftReference}s. */ - private static final class SoftEntryReference extends - SoftReference> implements Reference { + private static final class SoftEntryReference extends SoftReference> implements Reference { private final int hash; private final Reference nextReference; - public SoftEntryReference(Entry entry, int hash, Reference next, - ReferenceQueue> queue) { + public SoftEntryReference(Entry entry, int hash, Reference next, ReferenceQueue> queue) { super(entry, queue); this.hash = hash; this.nextReference = next; @@ -973,15 +958,13 @@ public void release() { /** * Internal {@link Reference} implementation for {@link WeakReference}s. */ - private static final class WeakEntryReference extends - WeakReference> implements Reference { + private static final class WeakEntryReference extends WeakReference> implements Reference { private final int hash; private final Reference nextReference; - public WeakEntryReference(Entry entry, int hash, Reference next, - ReferenceQueue> queue) { + public WeakEntryReference(Entry entry, int hash, Reference next, ReferenceQueue> queue) { super(entry, queue); this.hash = hash; this.nextReference = next; @@ -1000,4 +983,5 @@ public void release() { clear(); } } + } diff --git a/spring-core/src/main/java/org/springframework/util/MethodInvoker.java b/spring-core/src/main/java/org/springframework/util/MethodInvoker.java index 565a68c696dd..b1bd04ccb380 100644 --- a/spring-core/src/main/java/org/springframework/util/MethodInvoker.java +++ b/spring-core/src/main/java/org/springframework/util/MethodInvoker.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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 MethodInvoker { - private Class targetClass; + private Class targetClass; private Object targetObject; @@ -61,14 +61,14 @@ public class MethodInvoker { * @see #setTargetObject * @see #setTargetMethod */ - public void setTargetClass(Class targetClass) { + public void setTargetClass(Class targetClass) { this.targetClass = targetClass; } /** * Return the target class on which to call the target method. */ - public Class getTargetClass() { + public Class getTargetClass() { return this.targetClass; } @@ -158,7 +158,7 @@ public void prepare() throws ClassNotFoundException, NoSuchMethodException { this.targetMethod = methodName; } - Class targetClass = getTargetClass(); + Class targetClass = getTargetClass(); String targetMethod = getTargetMethod(); if (targetClass == null) { throw new IllegalArgumentException("Either 'targetClass' or 'targetObject' is required"); @@ -194,7 +194,7 @@ public void prepare() throws ClassNotFoundException, NoSuchMethodException { * @return the resolved Class * @throws ClassNotFoundException if the class name was invalid */ - protected Class resolveClassName(String className) throws ClassNotFoundException { + protected Class resolveClassName(String className) throws ClassNotFoundException { return ClassUtils.forName(className, ClassUtils.getDefaultClassLoader()); } @@ -287,19 +287,22 @@ public Object invoke() throws InvocationTargetException, IllegalAccessException * Therefore, with an arg of type Integer, a constructor (Integer) would be preferred to a * constructor (Number) which would in turn be preferred to a constructor (Object). * All argument weights get accumulated. + *

Note: This is the algorithm used by MethodInvoker itself and also the algorithm + * used for constructor and factory method selection in Spring's bean container (in case + * of lenient constructor resolution which is the default for regular bean definitions). * @param paramTypes the parameter types to match * @param args the arguments to match * @return the accumulated weight for all arguments */ - public static int getTypeDifferenceWeight(Class[] paramTypes, Object[] args) { + public static int getTypeDifferenceWeight(Class[] paramTypes, Object[] args) { int result = 0; for (int i = 0; i < paramTypes.length; i++) { if (!ClassUtils.isAssignableValue(paramTypes[i], args[i])) { return Integer.MAX_VALUE; } if (args[i] != null) { - Class paramType = paramTypes[i]; - Class superClass = args[i].getClass().getSuperclass(); + Class paramType = paramTypes[i]; + Class superClass = args[i].getClass().getSuperclass(); while (superClass != null) { if (paramType.equals(superClass)) { result = result + 2; diff --git a/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java b/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java index 87ba4211412e..f2455db1589e 100644 --- a/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -98,8 +98,8 @@ public static void setField(Field field, Object target, Object value) { } catch (IllegalAccessException ex) { handleReflectionException(ex); - throw new IllegalStateException("Unexpected reflection exception - " + ex.getClass().getName() + ": " - + ex.getMessage()); + throw new IllegalStateException( + "Unexpected reflection exception - " + ex.getClass().getName() + ": " + ex.getMessage()); } } @@ -153,8 +153,8 @@ public static Method findMethod(Class clazz, String name, Class... paramTy while (searchType != null) { Method[] methods = (searchType.isInterface() ? searchType.getMethods() : searchType.getDeclaredMethods()); for (Method method : methods) { - if (name.equals(method.getName()) - && (paramTypes == null || Arrays.equals(paramTypes, method.getParameterTypes()))) { + if (name.equals(method.getName()) && + (paramTypes == null || Arrays.equals(paramTypes, method.getParameterTypes()))) { return method; } } diff --git a/spring-core/src/main/java/org/springframework/util/xml/StaxEventXMLReader.java b/spring-core/src/main/java/org/springframework/util/xml/StaxEventXMLReader.java index 0d32a3fc08c5..fd43354a68b2 100644 --- a/spring-core/src/main/java/org/springframework/util/xml/StaxEventXMLReader.java +++ b/spring-core/src/main/java/org/springframework/util/xml/StaxEventXMLReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -51,12 +51,12 @@ * an {@code XMLEventReader}, and calls the corresponding methods on the SAX callback interfaces. * * @author Arjen Poutsma + * @since 3.0 * @see XMLEventReader * @see #setContentHandler(org.xml.sax.ContentHandler) * @see #setDTDHandler(org.xml.sax.DTDHandler) * @see #setEntityResolver(org.xml.sax.EntityResolver) * @see #setErrorHandler(org.xml.sax.ErrorHandler) - * @since 3.0 */ class StaxEventXMLReader extends AbstractStaxXMLReader { @@ -70,11 +70,11 @@ class StaxEventXMLReader extends AbstractStaxXMLReader { private String encoding; + /** * Constructs a new instance of the {@code StaxEventXmlReader} that reads from the given * {@code XMLEventReader}. The supplied event reader must be in {@code XMLStreamConstants.START_DOCUMENT} or * {@code XMLStreamConstants.START_ELEMENT} state. - * * @param reader the {@code XMLEventReader} to read from * @throws IllegalStateException if the reader is not at the start of a document or element */ @@ -89,17 +89,17 @@ class StaxEventXMLReader extends AbstractStaxXMLReader { catch (XMLStreamException ex) { throw new IllegalStateException("Could not read first element: " + ex.getMessage()); } - this.reader = reader; } + @Override protected void parseInternal() throws SAXException, XMLStreamException { boolean documentStarted = false; boolean documentEnded = false; int elementDepth = 0; - while (reader.hasNext() && elementDepth >= 0) { - XMLEvent event = reader.nextEvent(); + while (this.reader.hasNext() && elementDepth >= 0) { + XMLEvent event = this.reader.nextEvent(); if (!event.isStartDocument() && !event.isEndDocument() && !documentStarted) { handleStartDocument(event); documentStarted = true; @@ -165,36 +165,28 @@ private void handleStartDocument(final XMLEvent event) throws SAXException { this.encoding = startDocument.getCharacterEncodingScheme(); } } - if (getContentHandler() != null) { final Location location = event.getLocation(); getContentHandler().setDocumentLocator(new Locator2() { - public int getColumnNumber() { - return location != null ? location.getColumnNumber() : -1; + return (location != null ? location.getColumnNumber() : -1); } - public int getLineNumber() { - return location != null ? location.getLineNumber() : -1; + return (location != null ? location.getLineNumber() : -1); } - public String getPublicId() { - return location != null ? location.getPublicId() : null; + return (location != null ? location.getPublicId() : null); } - public String getSystemId() { - return location != null ? location.getSystemId() : null; + return (location != null ? location.getSystemId() : null); } - public String getXMLVersion() { return xmlVersion; } - public String getEncoding() { return encoding; } }); - getContentHandler().startDocument(); } } @@ -311,7 +303,6 @@ private void handleEntityReference(EntityReference reference) throws SAXExceptio private Attributes getAttributes(StartElement event) { AttributesImpl attributes = new AttributesImpl(); - for (Iterator i = event.getAttributes(); i.hasNext();) { Attribute attribute = (Attribute) i.next(); QName qName = attribute.getName(); @@ -323,8 +314,7 @@ private Attributes getAttributes(StartElement event) { if (type == null) { type = "CDATA"; } - attributes - .addAttribute(namespace, qName.getLocalPart(), toQualifiedName(qName), type, attribute.getValue()); + attributes.addAttribute(namespace, qName.getLocalPart(), toQualifiedName(qName), type, attribute.getValue()); } if (hasNamespacePrefixesFeature()) { for (Iterator i = event.getNamespaces(); i.hasNext();) { diff --git a/spring-core/src/main/java/org/springframework/util/xml/StaxStreamXMLReader.java b/spring-core/src/main/java/org/springframework/util/xml/StaxStreamXMLReader.java index bca48d1cdbe6..26394c56a678 100644 --- a/spring-core/src/main/java/org/springframework/util/xml/StaxStreamXMLReader.java +++ b/spring-core/src/main/java/org/springframework/util/xml/StaxStreamXMLReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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.util.StringUtils; /** - * SAX {@code XMLReader} that reads from a StAX {@code XMLStreamReader}. Reads from an + * SAX {@code XMLReader} that reads from a StAX {@code XMLStreamReader}. Reads from an * {@code XMLStreamReader}, and calls the corresponding methods on the SAX callback interfaces. * * @author Arjen Poutsma + * @since 3.0 * @see XMLStreamReader * @see #setContentHandler(org.xml.sax.ContentHandler) * @see #setDTDHandler(org.xml.sax.DTDHandler) * @see #setEntityResolver(org.xml.sax.EntityResolver) * @see #setErrorHandler(org.xml.sax.ErrorHandler) - * @since 3.0 */ class StaxStreamXMLReader extends AbstractStaxXMLReader { @@ -52,11 +52,11 @@ class StaxStreamXMLReader extends AbstractStaxXMLReader { private String encoding; + /** - * Constructs a new instance of the {@code StaxStreamXmlReader} that reads from the given - * {@code XMLStreamReader}. The supplied stream reader must be in {@code XMLStreamConstants.START_DOCUMENT} + * Construct a new instance of the {@code StaxStreamXmlReader} that reads from the given + * {@code XMLStreamReader}. The supplied stream reader must be in {@code XMLStreamConstants.START_DOCUMENT} * or {@code XMLStreamConstants.START_ELEMENT} state. - * * @param reader the {@code XMLEventReader} to read from * @throws IllegalStateException if the reader is not at the start of a document or element */ @@ -69,12 +69,13 @@ class StaxStreamXMLReader extends AbstractStaxXMLReader { this.reader = reader; } + @Override protected void parseInternal() throws SAXException, XMLStreamException { boolean documentStarted = false; boolean documentEnded = false; int elementDepth = 0; - int eventType = reader.getEventType(); + int eventType = this.reader.getEventType(); while (true) { if (eventType != XMLStreamConstants.START_DOCUMENT && eventType != XMLStreamConstants.END_DOCUMENT && !documentStarted) { @@ -118,8 +119,8 @@ protected void parseInternal() throws SAXException, XMLStreamException { handleEntityReference(); break; } - if (reader.hasNext() && elementDepth >= 0) { - eventType = reader.next(); + if (this.reader.hasNext() && elementDepth >= 0) { + eventType = this.reader.next(); } else { break; @@ -131,66 +132,58 @@ protected void parseInternal() throws SAXException, XMLStreamException { } private void handleStartDocument() throws SAXException { - if (XMLStreamConstants.START_DOCUMENT == reader.getEventType()) { - String xmlVersion = reader.getVersion(); + if (XMLStreamConstants.START_DOCUMENT == this.reader.getEventType()) { + String xmlVersion = this.reader.getVersion(); if (StringUtils.hasLength(xmlVersion)) { this.xmlVersion = xmlVersion; } - this.encoding = reader.getCharacterEncodingScheme(); + this.encoding = this.reader.getCharacterEncodingScheme(); } - if (getContentHandler() != null) { - final Location location = reader.getLocation(); - + final Location location = this.reader.getLocation(); getContentHandler().setDocumentLocator(new Locator2() { - public int getColumnNumber() { - return location != null ? location.getColumnNumber() : -1; + return (location != null ? location.getColumnNumber() : -1); } - public int getLineNumber() { - return location != null ? location.getLineNumber() : -1; + return (location != null ? location.getLineNumber() : -1); } - public String getPublicId() { - return location != null ? location.getPublicId() : null; + return (location != null ? location.getPublicId() : null); } - public String getSystemId() { - return location != null ? location.getSystemId() : null; + return (location != null ? location.getSystemId() : null); } - public String getXMLVersion() { return xmlVersion; } - public String getEncoding() { return encoding; } }); getContentHandler().startDocument(); - if (reader.standaloneSet()) { - setStandalone(reader.isStandalone()); + if (this.reader.standaloneSet()) { + setStandalone(this.reader.isStandalone()); } } } private void handleStartElement() throws SAXException { if (getContentHandler() != null) { - QName qName = reader.getName(); + QName qName = this.reader.getName(); if (hasNamespacesFeature()) { - for (int i = 0; i < reader.getNamespaceCount(); i++) { - startPrefixMapping(reader.getNamespacePrefix(i), reader.getNamespaceURI(i)); + for (int i = 0; i < this.reader.getNamespaceCount(); i++) { + startPrefixMapping(this.reader.getNamespacePrefix(i), this.reader.getNamespaceURI(i)); } - for (int i = 0; i < reader.getAttributeCount(); i++) { - String prefix = reader.getAttributePrefix(i); - String namespace = reader.getAttributeNamespace(i); + for (int i = 0; i < this.reader.getAttributeCount(); i++) { + String prefix = this.reader.getAttributePrefix(i); + String namespace = this.reader.getAttributeNamespace(i); if (StringUtils.hasLength(namespace)) { startPrefixMapping(prefix, namespace); } } - getContentHandler().startElement(qName.getNamespaceURI(), qName.getLocalPart(), toQualifiedName(qName), - getAttributes()); + getContentHandler().startElement(qName.getNamespaceURI(), qName.getLocalPart(), + toQualifiedName(qName), getAttributes()); } else { getContentHandler().startElement("", "", toQualifiedName(qName), getAttributes()); @@ -200,11 +193,11 @@ private void handleStartElement() throws SAXException { private void handleEndElement() throws SAXException { if (getContentHandler() != null) { - QName qName = reader.getName(); + QName qName = this.reader.getName(); if (hasNamespacesFeature()) { getContentHandler().endElement(qName.getNamespaceURI(), qName.getLocalPart(), toQualifiedName(qName)); - for (int i = 0; i < reader.getNamespaceCount(); i++) { - String prefix = reader.getNamespacePrefix(i); + for (int i = 0; i < this.reader.getNamespaceCount(); i++) { + String prefix = this.reader.getNamespacePrefix(i); if (prefix == null) { prefix = ""; } @@ -218,31 +211,33 @@ private void handleEndElement() throws SAXException { } private void handleCharacters() throws SAXException { - if (getContentHandler() != null && reader.isWhiteSpace()) { - getContentHandler() - .ignorableWhitespace(reader.getTextCharacters(), reader.getTextStart(), reader.getTextLength()); + if (getContentHandler() != null && this.reader.isWhiteSpace()) { + getContentHandler().ignorableWhitespace(this.reader.getTextCharacters(), + this.reader.getTextStart(), this.reader.getTextLength()); return; } - if (XMLStreamConstants.CDATA == reader.getEventType() && getLexicalHandler() != null) { + if (XMLStreamConstants.CDATA == this.reader.getEventType() && getLexicalHandler() != null) { getLexicalHandler().startCDATA(); } if (getContentHandler() != null) { - getContentHandler().characters(reader.getTextCharacters(), reader.getTextStart(), reader.getTextLength()); + getContentHandler().characters(this.reader.getTextCharacters(), + this.reader.getTextStart(), this.reader.getTextLength()); } - if (XMLStreamConstants.CDATA == reader.getEventType() && getLexicalHandler() != null) { + if (XMLStreamConstants.CDATA == this.reader.getEventType() && getLexicalHandler() != null) { getLexicalHandler().endCDATA(); } } private void handleComment() throws SAXException { if (getLexicalHandler() != null) { - getLexicalHandler().comment(reader.getTextCharacters(), reader.getTextStart(), reader.getTextLength()); + getLexicalHandler().comment(this.reader.getTextCharacters(), + this.reader.getTextStart(), this.reader.getTextLength()); } } private void handleDtd() throws SAXException { if (getLexicalHandler() != null) { - javax.xml.stream.Location location = reader.getLocation(); + javax.xml.stream.Location location = this.reader.getLocation(); getLexicalHandler().startDTD(null, location.getPublicId(), location.getSystemId()); } if (getLexicalHandler() != null) { @@ -252,10 +247,10 @@ private void handleDtd() throws SAXException { private void handleEntityReference() throws SAXException { if (getLexicalHandler() != null) { - getLexicalHandler().startEntity(reader.getLocalName()); + getLexicalHandler().startEntity(this.reader.getLocalName()); } if (getLexicalHandler() != null) { - getLexicalHandler().endEntity(reader.getLocalName()); + getLexicalHandler().endEntity(this.reader.getLocalName()); } } @@ -267,29 +262,28 @@ private void handleEndDocument() throws SAXException { private void handleProcessingInstruction() throws SAXException { if (getContentHandler() != null) { - getContentHandler().processingInstruction(reader.getPITarget(), reader.getPIData()); + getContentHandler().processingInstruction(this.reader.getPITarget(), this.reader.getPIData()); } } private Attributes getAttributes() { AttributesImpl attributes = new AttributesImpl(); - - for (int i = 0; i < reader.getAttributeCount(); i++) { - String namespace = reader.getAttributeNamespace(i); + for (int i = 0; i < this.reader.getAttributeCount(); i++) { + String namespace = this.reader.getAttributeNamespace(i); if (namespace == null || !hasNamespacesFeature()) { namespace = ""; } - String type = reader.getAttributeType(i); + String type = this.reader.getAttributeType(i); if (type == null) { type = "CDATA"; } - attributes.addAttribute(namespace, reader.getAttributeLocalName(i), - toQualifiedName(reader.getAttributeName(i)), type, reader.getAttributeValue(i)); + attributes.addAttribute(namespace, this.reader.getAttributeLocalName(i), + toQualifiedName(this.reader.getAttributeName(i)), type, this.reader.getAttributeValue(i)); } if (hasNamespacePrefixesFeature()) { - for (int i = 0; i < reader.getNamespaceCount(); i++) { - String prefix = reader.getNamespacePrefix(i); - String namespaceUri = reader.getNamespaceURI(i); + for (int i = 0; i < this.reader.getNamespaceCount(); i++) { + String prefix = this.reader.getNamespacePrefix(i); + String namespaceUri = this.reader.getNamespaceURI(i); String qName; if (StringUtils.hasLength(prefix)) { qName = "xmlns:" + prefix; diff --git a/spring-core/src/main/java/org/springframework/util/xml/StaxUtils.java b/spring-core/src/main/java/org/springframework/util/xml/StaxUtils.java index efafa259d43c..41fd3f63dc91 100644 --- a/spring-core/src/main/java/org/springframework/util/xml/StaxUtils.java +++ b/spring-core/src/main/java/org/springframework/util/xml/StaxUtils.java @@ -111,7 +111,16 @@ public static Source createStaxSource(XMLEventReader eventReader) throws XMLStre * 1.4 {@link StAXSource}; {@code false} otherwise. */ public static boolean isStaxSource(Source source) { - return (source instanceof StaxSource || (jaxp14Available && Jaxp14StaxHandler.isStaxSource(source))); + return ((source instanceof StaxSource) || (jaxp14Available && Jaxp14StaxHandler.isStaxSource(source))); + } + + /** + * Indicate whether the given class is a StAX Source class. + * @return {@code true} if {@code source} is a custom StAX source or JAXP + * 1.4 {@link StAXSource} class; {@code false} otherwise. + */ + public static boolean isStaxSourceClass(Class clazz) { + return (StaxSource.class.equals(clazz) || (jaxp14Available && Jaxp14StaxHandler.isStaxSourceClass(clazz))); } @@ -348,6 +357,10 @@ private static boolean isStaxSource(Source source) { return (source instanceof StAXSource); } + private static boolean isStaxSourceClass(Class clazz) { + return StAXSource.class.equals(clazz); + } + private static boolean isStaxResult(Result result) { return (result instanceof StAXResult); } diff --git a/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java b/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java index 5bdc32d9a2ca..ca335500027c 100644 --- a/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java +++ b/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,15 +19,15 @@ import java.lang.reflect.Method; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; - import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.junit.Test; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; - import static org.springframework.core.GenericTypeResolver.*; import static org.springframework.util.ReflectionUtils.*; @@ -65,92 +65,110 @@ public void nullIfNotResolvable() { @Test public void methodReturnTypes() { - assertEquals(Integer.class, resolveReturnTypeArgument(findMethod(MyTypeWithMethods.class, "integer"), MyInterfaceType.class)); - assertEquals(String.class, resolveReturnTypeArgument(findMethod(MyTypeWithMethods.class, "string"), MyInterfaceType.class)); + assertEquals(Integer.class, + resolveReturnTypeArgument(findMethod(MyTypeWithMethods.class, "integer"), MyInterfaceType.class)); + assertEquals(String.class, + resolveReturnTypeArgument(findMethod(MyTypeWithMethods.class, "string"), MyInterfaceType.class)); assertEquals(null, resolveReturnTypeArgument(findMethod(MyTypeWithMethods.class, "raw"), MyInterfaceType.class)); - assertEquals(null, resolveReturnTypeArgument(findMethod(MyTypeWithMethods.class, "object"), MyInterfaceType.class)); + assertEquals(null, + resolveReturnTypeArgument(findMethod(MyTypeWithMethods.class, "object"), MyInterfaceType.class)); } - /** - * @since 3.2 - */ @Test - public void genericMethodReturnTypes() { - - Method notParameterized = findMethod(MyTypeWithMethods.class, "notParameterized", new Class[] {}); - assertEquals(String.class, resolveReturnTypeForGenericMethod(notParameterized, new Object[] {})); - - Method notParameterizedWithArguments = findMethod(MyTypeWithMethods.class, "notParameterizedWithArguments", - new Class[] { Integer.class, Boolean.class }); - assertEquals(String.class, - resolveReturnTypeForGenericMethod(notParameterizedWithArguments, new Object[] { 99, true })); + public void testResolveType() { + Method intMessageMethod = findMethod(MyTypeWithMethods.class, "readIntegerInputMessage", MyInterfaceType.class); + MethodParameter intMessageMethodParam = new MethodParameter(intMessageMethod, 0); + assertEquals(MyInterfaceType.class, + resolveType(intMessageMethodParam.getGenericParameterType(), new HashMap())); + + Method intArrMessageMethod = findMethod(MyTypeWithMethods.class, "readIntegerArrayInputMessage", + MyInterfaceType[].class); + MethodParameter intArrMessageMethodParam = new MethodParameter(intArrMessageMethod, 0); + assertEquals(MyInterfaceType[].class, + resolveType(intArrMessageMethodParam.getGenericParameterType(), new HashMap())); + + Method genericArrMessageMethod = findMethod(MySimpleTypeWithMethods.class, "readGenericArrayInputMessage", + Object[].class); + MethodParameter genericArrMessageMethodParam = new MethodParameter(genericArrMessageMethod, 0); + Map varMap = getTypeVariableMap(MySimpleTypeWithMethods.class); + assertEquals(Integer[].class, resolveType(genericArrMessageMethodParam.getGenericParameterType(), varMap)); + } - Method createProxy = findMethod(MyTypeWithMethods.class, "createProxy", new Class[] { Object.class }); - assertEquals(String.class, resolveReturnTypeForGenericMethod(createProxy, new Object[] { "foo" })); + @Test + public void testBoundParameterizedType() { + assertEquals(B.class, resolveTypeArgument(TestImpl.class, ITest.class)); + } - Method createNamedProxyWithDifferentTypes = findMethod(MyTypeWithMethods.class, "createNamedProxy", - new Class[] { String.class, Object.class }); - // one argument to few - assertNull(resolveReturnTypeForGenericMethod(createNamedProxyWithDifferentTypes, new Object[] { "enigma" })); - assertEquals(Long.class, - resolveReturnTypeForGenericMethod(createNamedProxyWithDifferentTypes, new Object[] { "enigma", 99L })); + @Test + public void testGetTypeVariableMap() throws Exception { + Map map; + + map = GenericTypeResolver.getTypeVariableMap(MySimpleInterfaceType.class); + assertThat(map.toString(), equalTo("{T=class java.lang.String}")); + + map = GenericTypeResolver.getTypeVariableMap(MyCollectionInterfaceType.class); + assertThat(map.toString(), equalTo("{T=java.util.Collection}")); + + map = GenericTypeResolver.getTypeVariableMap(MyCollectionSuperclassType.class); + assertThat(map.toString(), equalTo("{T=java.util.Collection}")); + + map = GenericTypeResolver.getTypeVariableMap(MySimpleTypeWithMethods.class); + assertThat(map.toString(), equalTo("{T=class java.lang.Integer}")); + + map = GenericTypeResolver.getTypeVariableMap(TopLevelClass.class); + assertThat(map.toString(), equalTo("{}")); + + map = GenericTypeResolver.getTypeVariableMap(TypedTopLevelClass.class); + assertThat(map.toString(), equalTo("{T=class java.lang.Integer}")); + + map = GenericTypeResolver.getTypeVariableMap(TypedTopLevelClass.TypedNested.class); + assertThat(map.size(), equalTo(2)); + Type t = null; + Type x = null; + for (Map.Entry entry : map.entrySet()) { + if(entry.getKey().toString().equals("T")) { + t = entry.getValue(); + } + else { + x = entry.getValue(); + } + } + assertThat(t, equalTo((Type) Integer.class)); + assertThat(x, equalTo((Type) Long.class)); + } - Method createNamedProxyWithDuplicateTypes = findMethod(MyTypeWithMethods.class, "createNamedProxy", - new Class[] { String.class, Object.class }); - assertEquals(String.class, - resolveReturnTypeForGenericMethod(createNamedProxyWithDuplicateTypes, new Object[] { "enigma", "foo" })); - - Method createMock = findMethod(MyTypeWithMethods.class, "createMock", new Class[] { Class.class }); - assertEquals(Runnable.class, resolveReturnTypeForGenericMethod(createMock, new Object[] { Runnable.class })); - - Method createNamedMock = findMethod(MyTypeWithMethods.class, "createNamedMock", new Class[] { String.class, - Class.class }); - assertEquals(Runnable.class, - resolveReturnTypeForGenericMethod(createNamedMock, new Object[] { "foo", Runnable.class })); - - Method createVMock = findMethod(MyTypeWithMethods.class, "createVMock", - new Class[] { Object.class, Class.class }); - assertEquals(Runnable.class, - resolveReturnTypeForGenericMethod(createVMock, new Object[] { "foo", Runnable.class })); - - // Ideally we would expect String.class instead of Object.class, but - // resolveReturnTypeForGenericMethod() does not currently support this form of - // look-up. - Method extractValueFrom = findMethod(MyTypeWithMethods.class, "extractValueFrom", - new Class[] { MyInterfaceType.class }); - assertEquals(Object.class, - resolveReturnTypeForGenericMethod(extractValueFrom, new Object[] { new MySimpleInterfaceType() })); - - // Ideally we would expect Boolean.class instead of Object.class, but this - // information is not available at run-time due to type erasure. - Map map = new HashMap(); - map.put(0, false); - map.put(1, true); - Method extractMagicValue = findMethod(MyTypeWithMethods.class, "extractMagicValue", new Class[] { Map.class }); - assertEquals(Object.class, resolveReturnTypeForGenericMethod(extractMagicValue, new Object[] { map })); - } - - /** - * @since 3.2 - */ @Test - public void testResolveType() { - Method intMessageMethod = findMethod(MyTypeWithMethods.class, "readIntegerInputMessage", MyInterfaceType.class); - MethodParameter intMessageMethodParam = new MethodParameter(intMessageMethod, 0); - assertEquals(MyInterfaceType.class, - resolveType(intMessageMethodParam.getGenericParameterType(), new HashMap())); + public void getGenericsCannotBeResolved() throws Exception { + // SPR-11030 + Class[] resolved = GenericTypeResolver.resolveTypeArguments(List.class, Iterable.class); + // Note: to be changed to return null in Spring 4.0 + assertThat(resolved, equalTo(new Class[] {Object.class})); + } - Method intArrMessageMethod = findMethod(MyTypeWithMethods.class, "readIntegerArrayInputMessage", MyInterfaceType[].class); - MethodParameter intArrMessageMethodParam = new MethodParameter(intArrMessageMethod, 0); - assertEquals(MyInterfaceType[].class, - resolveType(intArrMessageMethodParam.getGenericParameterType(), new HashMap())); + @Test + public void getRawMapTypeCannotBeResolved() throws Exception { + // SPR-11052 + Class[] resolved = GenericTypeResolver.resolveTypeArguments(Map.class, Map.class); + assertNull(resolved); + } - Method genericArrMessageMethod = findMethod(MySimpleTypeWithMethods.class, "readGenericArrayInputMessage", Object[].class); - MethodParameter genericArrMessageMethodParam = new MethodParameter(genericArrMessageMethod, 0); - Map varMap = getTypeVariableMap(MySimpleTypeWithMethods.class); - assertEquals(Integer[].class, resolveType(genericArrMessageMethodParam.getGenericParameterType(), varMap)); + @Test + public void getGenericsOnArrayFromParamCannotBeResolved() throws Exception { + // SPR-11044 + MethodParameter methodParameter = MethodParameter.forMethodOrConstructor( + WithArrayBase.class.getDeclaredMethod("array", Object[].class), 0); + Class resolved = GenericTypeResolver.resolveParameterType(methodParameter, WithArray.class); + assertThat(resolved, equalTo((Class) Object[].class)); } + @Test + public void getGenericsOnArrayFromReturnCannotBeResolved() throws Exception { + // SPR-11044 + Class resolved = GenericTypeResolver.resolveReturnType( + WithArrayBase.class.getDeclaredMethod("array", Object[].class), + WithArray.class); + assertThat(resolved, equalTo((Class) Object[].class)); + } public interface MyInterfaceType { } @@ -171,26 +189,44 @@ public class MyCollectionSuperclassType extends MySuperclassType { - public MyInterfaceType integer() { return null; } - public MySimpleInterfaceType string() { return null; } - public Object object() { return null; } + + public MyInterfaceType integer() { + return null; + } + + public MySimpleInterfaceType string() { + return null; + } + + public Object object() { + return null; + } + @SuppressWarnings("rawtypes") - public MyInterfaceType raw() { return null; } - public String notParameterized() { return null; } - public String notParameterizedWithArguments(Integer x, Boolean b) { return null; } + public MyInterfaceType raw() { + return null; + } + + public String notParameterized() { + return null; + } + + public String notParameterizedWithArguments(Integer x, Boolean b) { + return null; + } /** - * Simulates a factory method that wraps the supplied object in a proxy - * of the same type. + * Simulates a factory method that wraps the supplied object in a proxy of the + * same type. */ public static T createProxy(T object) { return null; } /** - * Similar to {@link #createProxy(Object)} but adds an additional argument - * before the argument of type {@code T}. Note that they may potentially - * be of the same time when invoked! + * Similar to {@link #createProxy(Object)} but adds an additional argument before + * the argument of type {@code T}. Note that they may potentially be of the same + * time when invoked! */ public static T createNamedProxy(String name, T object) { return null; @@ -204,8 +240,8 @@ public static MOCK createMock(Class toMock) { } /** - * Similar to {@link #createMock(Class)} but adds an additional method - * argument before the parameterized argument. + * Similar to {@link #createMock(Class)} but adds an additional method argument + * before the parameterized argument. */ public static T createNamedMock(String name, Class toMock) { return null; @@ -220,8 +256,8 @@ public static T createVMock(V name, Class toMock) { } /** - * Extract some value of the type supported by the interface (i.e., by - * a concrete, non-generic implementation of the interface). + * Extract some value of the type supported by the interface (i.e., by a concrete, + * non-generic implementation of the interface). */ public static T extractValueFrom(MyInterfaceType myInterfaceType) { return null; @@ -250,4 +286,31 @@ public static class MySimpleTypeWithMethods extends MyTypeWithMethods { static class GenericClass { } + class A{} + + class B{} + + class ITest{} + + class TestImpl> extends ITest{ + } + + static class TopLevelClass { + class Nested { + } + } + + static class TypedTopLevelClass extends TopLevelClass { + class TypedNested extends Nested { + } + } + + static abstract class WithArrayBase { + + public abstract T[] array(T... args); + } + + static abstract class WithArray extends WithArrayBase { + } + } diff --git a/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java b/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java index c08cdeebb775..07aa882307a4 100644 --- a/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java +++ b/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,7 +49,7 @@ import org.springframework.util.StopWatch; import org.springframework.util.StringUtils; -import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; /** @@ -81,14 +81,14 @@ public void canConvertIllegalArgumentNullTargetType() { try { assertFalse(conversionService.canConvert(String.class, null)); fail("Should have failed"); - } catch (IllegalArgumentException e) { - + } + catch (IllegalArgumentException ex) { } try { assertFalse(conversionService.canConvert(TypeDescriptor.valueOf(String.class), null)); fail("Should have failed"); - } catch (IllegalArgumentException e) { - + } + catch (IllegalArgumentException ex) { } } @@ -151,8 +151,8 @@ public Object convert(Object source) { } }); fail("Should have failed"); - } catch (IllegalArgumentException e) { - + } + catch (IllegalArgumentException ex) { } } @@ -254,7 +254,8 @@ public void genericConverterDelegatingBackToConversionServiceConverterNotFound() try { conversionService.convert("3,4,5", Integer[].class); fail("should have failed"); - } catch (ConverterNotFoundException e) { + } + catch (ConverterNotFoundException ex) { } } @@ -389,7 +390,7 @@ public void testIgnoreCopyConstructor() { } @Test - public void testConvertUUID() throws Exception { + public void testConvertUUID() { GenericConversionService service = new DefaultConversionService(); UUID uuid = UUID.randomUUID(); String convertToString = service.convert(uuid, String.class); @@ -472,7 +473,7 @@ public void testPerformance3() throws Exception { public static Map map; @Test - public void emptyListToArray() throws Exception { + public void emptyListToArray() { conversionService.addConverter(new CollectionToArrayConverter(conversionService)); conversionService.addConverterFactory(new StringToNumberConverterFactory()); List list = new ArrayList(); @@ -483,7 +484,7 @@ public void emptyListToArray() throws Exception { } @Test - public void emptyListToObject() throws Exception { + public void emptyListToObject() { conversionService.addConverter(new CollectionToObjectConverter(conversionService)); conversionService.addConverterFactory(new StringToNumberConverterFactory()); List list = new ArrayList(); @@ -590,14 +591,14 @@ public void stringToCollectionCanConvert() throws Exception { public Collection stringToCollection; @Test - public void testConvertiblePairsInSet() throws Exception { + public void testConvertiblePairsInSet() { Set set = new HashSet(); set.add(new GenericConverter.ConvertiblePair(Number.class, String.class)); assert set.contains(new GenericConverter.ConvertiblePair(Number.class, String.class)); } @Test - public void testConvertiblePairEqualsAndHash() throws Exception { + public void testConvertiblePairEqualsAndHash() { GenericConverter.ConvertiblePair pair = new GenericConverter.ConvertiblePair(Number.class, String.class); GenericConverter.ConvertiblePair pairEqual = new GenericConverter.ConvertiblePair(Number.class, String.class); assertEquals(pair, pairEqual); @@ -605,7 +606,7 @@ public void testConvertiblePairEqualsAndHash() throws Exception { } @Test - public void testConvertiblePairDifferentEqualsAndHash() throws Exception { + public void testConvertiblePairDifferentEqualsAndHash() { GenericConverter.ConvertiblePair pair = new GenericConverter.ConvertiblePair(Number.class, String.class); GenericConverter.ConvertiblePair pairOpposite = new GenericConverter.ConvertiblePair(String.class, Number.class); assertFalse(pair.equals(pairOpposite)); @@ -613,7 +614,7 @@ public void testConvertiblePairDifferentEqualsAndHash() throws Exception { } @Test - public void convertPrimitiveArray() throws Exception { + public void convertPrimitiveArray() { GenericConversionService conversionService = new DefaultConversionService(); byte[] byteArray = new byte[] { 1, 2, 3 }; Byte[] converted = conversionService.convert(byteArray, Byte[].class); @@ -625,7 +626,8 @@ public void canConvertIllegalArgumentNullTargetTypeFromClass() { try { conversionService.canConvert(String.class, null); fail("Did not thow IllegalArgumentException"); - } catch(IllegalArgumentException e) { + } + catch (IllegalArgumentException ex) { } } @@ -634,13 +636,14 @@ public void canConvertIllegalArgumentNullTargetTypeFromTypeDescriptor() { try { conversionService.canConvert(TypeDescriptor.valueOf(String.class), null); fail("Did not thow IllegalArgumentException"); - } catch(IllegalArgumentException e) { + } + catch(IllegalArgumentException ex) { } } @Test @SuppressWarnings({ "rawtypes" }) - public void convertHashMapValuesToList() throws Exception { + public void convertHashMapValuesToList() { GenericConversionService conversionService = new DefaultConversionService(); Map hashMap = new LinkedHashMap(); hashMap.put("1", 1); @@ -650,7 +653,7 @@ public void convertHashMapValuesToList() throws Exception { } @Test - public void removeConvertible() throws Exception { + public void removeConvertible() { conversionService.addConverter(new ColorConverter()); assertTrue(conversionService.canConvert(String.class, Color.class)); conversionService.removeConvertible(String.class, Color.class); @@ -658,7 +661,7 @@ public void removeConvertible() throws Exception { } @Test - public void conditionalConverter() throws Exception { + public void conditionalConverter() { GenericConversionService conversionService = new GenericConversionService(); MyConditionalConverter converter = new MyConditionalConverter(); conversionService.addConverter(new ColorConverter()); @@ -668,7 +671,7 @@ public void conditionalConverter() throws Exception { } @Test - public void conditionalConverterFactory() throws Exception { + public void conditionalConverterFactory() { GenericConversionService conversionService = new GenericConversionService(); MyConditionalConverterFactory converter = new MyConditionalConverterFactory(); conversionService.addConverter(new ColorConverter()); @@ -679,32 +682,29 @@ public void conditionalConverterFactory() throws Exception { } @Test - public void shouldNotSuportNullConvertibleTypesFromNonConditionalGenericConverter() - throws Exception { + public void shouldNotSuportNullConvertibleTypesFromNonConditionalGenericConverter() { GenericConversionService conversionService = new GenericConversionService(); GenericConverter converter = new GenericConverter() { - @Override public Set getConvertibleTypes() { return null; } - @Override - public Object convert(Object source, TypeDescriptor sourceType, - TypeDescriptor targetType) { + public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { return null; } }; try { conversionService.addConverter(converter); fail("Did not throw"); - } catch (IllegalStateException e) { - assertEquals("Only conditional converters may return null convertible types", e.getMessage()); + } + catch (IllegalStateException ex) { + assertEquals("Only conditional converters may return null convertible types", ex.getMessage()); } } @Test - public void conditionalConversionForAllTypes() throws Exception { + public void conditionalConversionForAllTypes() { GenericConversionService conversionService = new GenericConversionService(); MyConditionalGenericConverter converter = new MyConditionalGenericConverter(); conversionService.addConverter(converter); @@ -717,7 +717,7 @@ public void conditionalConversionForAllTypes() throws Exception { } @Test - public void convertOptimizeArray() throws Exception { + public void convertOptimizeArray() { // SPR-9566 GenericConversionService conversionService = new DefaultConversionService(); byte[] byteArray = new byte[] { 1, 2, 3 }; @@ -726,7 +726,7 @@ public void convertOptimizeArray() throws Exception { } @Test - public void convertCannotOptimizeArray() throws Exception { + public void convertCannotOptimizeArray() { GenericConversionService conversionService = new GenericConversionService(); conversionService.addConverter(new Converter() { @Override @@ -766,6 +766,7 @@ public void convertNullAnnotatedStringToString() throws Exception { conversionService.convert(source, sourceType, targetType); } + @ExampleAnnotation public String annotatedString; @@ -773,8 +774,7 @@ public void convertNullAnnotatedStringToString() throws Exception { public static @interface ExampleAnnotation { } - private static class MyConditionalConverter implements Converter, - ConditionalConverter { + private static class MyConditionalConverter implements Converter, ConditionalConverter { private int matchAttempts = 0; @@ -850,23 +850,26 @@ public int getNestedMatchAttempts() { } interface MyEnumInterface { + String getCode(); } public static enum MyEnum implements MyEnumInterface { + A { @Override public String getCode() { return "1"; } - }; + } } - private static class MyEnumInterfaceToStringConverter - implements Converter { + private static class MyEnumInterfaceToStringConverter implements Converter { + @Override public String convert(T source) { return source.getCode(); } } + } diff --git a/spring-expression/src/main/java/org/springframework/expression/EvaluationContext.java b/spring-expression/src/main/java/org/springframework/expression/EvaluationContext.java index 27a83cab3701..9d509c6121bb 100644 --- a/spring-expression/src/main/java/org/springframework/expression/EvaluationContext.java +++ b/spring-expression/src/main/java/org/springframework/expression/EvaluationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2013 the original author 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,12 +19,12 @@ import java.util.List; /** - * Expressions are executed in an evaluation context. It is in this context that references - * are resolved when encountered during expression evaluation. + * Expressions are executed in an evaluation context. It is in this context that + * references are resolved when encountered during expression evaluation. * *

There is a default implementation of the EvaluationContext, - * {@link org.springframework.expression.spel.support.StandardEvaluationContext} - * that can be extended, rather than having to implement everything. + * {@link org.springframework.expression.spel.support.StandardEvaluationContext} that can + * be extended, rather than having to implement everything. * * @author Andy Clement * @author Juergen Hoeller @@ -33,49 +33,51 @@ public interface EvaluationContext { /** - * @return the default root context object against which unqualified properties/methods/etc - * should be resolved. This can be overridden when evaluating an expression. + * Return the default root context object against which unqualified + * properties/methods/etc should be resolved. This can be overridden + * when evaluating an expression. */ TypedValue getRootObject(); /** - * @return a list of resolvers that will be asked in turn to locate a constructor + * Return a list of resolvers that will be asked in turn to locate a constructor. */ List getConstructorResolvers(); /** - * @return a list of resolvers that will be asked in turn to locate a method + * Return a list of resolvers that will be asked in turn to locate a method. */ List getMethodResolvers(); /** - * @return a list of accessors that will be asked in turn to read/write a property + * Return a list of accessors that will be asked in turn to read/write a property. */ List getPropertyAccessors(); /** - * @return a type locator that can be used to find types, either by short or fully qualified name. + * Return a type locator that can be used to find types, either by short or + * fully qualified name. */ TypeLocator getTypeLocator(); /** - * @return a type converter that can convert (or coerce) a value from one type to another. + * Return a type converter that can convert (or coerce) a value from one type to another. */ TypeConverter getTypeConverter(); /** - * @return a type comparator for comparing pairs of objects for equality. + * Return a type comparator for comparing pairs of objects for equality. */ TypeComparator getTypeComparator(); /** - * @return an operator overloader that may support mathematical operations - * between more than the standard set of types + * Return an operator overloader that may support mathematical operations + * between more than the standard set of types. */ OperatorOverloader getOperatorOverloader(); /** - * @return a bean resolver that can look up beans by name + * Return a bean resolver that can look up beans by name. */ BeanResolver getBeanResolver(); diff --git a/spring-expression/src/main/java/org/springframework/expression/MethodExecutor.java b/spring-expression/src/main/java/org/springframework/expression/MethodExecutor.java index bd4dd74516cc..6b94e4fbac8b 100644 --- a/spring-expression/src/main/java/org/springframework/expression/MethodExecutor.java +++ b/spring-expression/src/main/java/org/springframework/expression/MethodExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2013 the original author 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,13 +17,15 @@ package org.springframework.expression; /** - * MethodExecutors are built by the resolvers and can be cached by the infrastructure to repeat an operation quickly - * without going back to the resolvers. For example, the particular method to run on an object may be discovered by the - * reflection method resolver - it will then build a MethodExecutor that executes that method and the MethodExecutor can - * be reused without needing to go back to the resolver to discover the method again. + * MethodExecutors are built by the resolvers and can be cached by the infrastructure to + * repeat an operation quickly without going back to the resolvers. For example, the + * particular method to run on an object may be discovered by the reflection method + * resolver - it will then build a MethodExecutor that executes that method and the + * MethodExecutor can be reused without needing to go back to the resolver to discover + * the method again. * - *

They can become stale, and in that case should throw an AccessException - this will cause the infrastructure to go - * back to the resolvers to ask for a new one. + *

They can become stale, and in that case should throw an AccessException: + * This will cause the infrastructure to go back to the resolvers to ask for a new one. * * @author Andy Clement * @since 3.0 @@ -34,10 +36,11 @@ public interface MethodExecutor { * Execute a command using the specified arguments, and using the specified expression state. * @param context the evaluation context in which the command is being executed * @param target the target object of the call - null for static methods - * @param arguments the arguments to the executor, should match (in terms of number and type) whatever the - * command will need to run + * @param arguments the arguments to the executor, should match (in terms of number + * and type) whatever the command will need to run * @return the value returned from execution - * @throws AccessException if there is a problem executing the command or the MethodExecutor is no longer valid + * @throws AccessException if there is a problem executing the command or the + * MethodExecutor is no longer valid */ TypedValue execute(EvaluationContext context, Object target, Object... arguments) throws AccessException; diff --git a/spring-expression/src/main/java/org/springframework/expression/MethodResolver.java b/spring-expression/src/main/java/org/springframework/expression/MethodResolver.java index 75cdc5eab8b7..e33dc8423fea 100644 --- a/spring-expression/src/main/java/org/springframework/expression/MethodResolver.java +++ b/spring-expression/src/main/java/org/springframework/expression/MethodResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,8 +21,9 @@ import org.springframework.core.convert.TypeDescriptor; /** - * A method resolver attempts locate a method and returns a command executor that can be used to invoke that method. - * The command executor will be cached but if it 'goes stale' the resolvers will be called again. + * A method resolver attempts locate a method and returns a command executor that can be + * used to invoke that method. The command executor will be cached but if it 'goes stale' + * the resolvers will be called again. * * @author Andy Clement * @since 3.0 @@ -30,9 +31,9 @@ public interface MethodResolver { /** - * Within the supplied context determine a suitable method on the supplied object that can handle the - * specified arguments. Return a MethodExecutor that can be used to invoke that method - * (or {@code null} if no method could be found). + * Within the supplied context determine a suitable method on the supplied object that + * can handle the specified arguments. Return a {@link MethodExecutor} that can be used + * to invoke that method, or {@code null} if no method could be found. * @param context the current evaluation context * @param targetObject the object upon which the method is being called * @param argumentTypes the arguments that the constructor must be able to handle diff --git a/spring-expression/src/main/java/org/springframework/expression/TypedValue.java b/spring-expression/src/main/java/org/springframework/expression/TypedValue.java index 67ecb99ad278..785f5a81f294 100644 --- a/spring-expression/src/main/java/org/springframework/expression/TypedValue.java +++ b/spring-expression/src/main/java/org/springframework/expression/TypedValue.java @@ -73,7 +73,7 @@ public TypeDescriptor getTypeDescriptor() { @Override public String toString() { StringBuilder str = new StringBuilder(); - str.append("TypedValue: '").append(this.value).append("' of [").append(getTypeDescriptor() + "]"); + str.append("TypedValue: '").append(this.value).append("' of [").append(getTypeDescriptor()).append("]"); return str.toString(); } diff --git a/spring-expression/src/main/java/org/springframework/expression/common/LiteralExpression.java b/spring-expression/src/main/java/org/springframework/expression/common/LiteralExpression.java index ae7713c50f36..8740322c7297 100644 --- a/spring-expression/src/main/java/org/springframework/expression/common/LiteralExpression.java +++ b/spring-expression/src/main/java/org/springframework/expression/common/LiteralExpression.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -102,7 +102,7 @@ public String getValue(EvaluationContext context, Object rootObject) throws Eval public T getValue(EvaluationContext context, Object rootObject, Class desiredResultType) throws EvaluationException { Object value = getValue(context, rootObject); - return ExpressionUtils.convert(null, value, desiredResultType); + return ExpressionUtils.convert(context, value, desiredResultType); } public Class getValueType(Object rootObject) throws EvaluationException { diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ExpressionState.java b/spring-expression/src/main/java/org/springframework/expression/spel/ExpressionState.java index 85c1727c5c04..bf4866660a49 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ExpressionState.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ExpressionState.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,14 +30,18 @@ import org.springframework.expression.TypeComparator; import org.springframework.expression.TypeConverter; import org.springframework.expression.TypedValue; +import org.springframework.util.Assert; /** - * An ExpressionState is for maintaining per-expression-evaluation state, any changes to it are not seen by other - * expressions but it gives a place to hold local variables and for component expressions in a compound expression to - * communicate state. This is in contrast to the EvaluationContext, which is shared amongst expression evaluations, and - * any changes to it will be seen by other expressions or any code that chooses to ask questions of the context. + * An ExpressionState is for maintaining per-expression-evaluation state, any changes to + * it are not seen by other expressions but it gives a place to hold local variables and + * for component expressions in a compound expression to communicate state. This is in + * contrast to the EvaluationContext, which is shared amongst expression evaluations, and + * any changes to it will be seen by other expressions or any code that chooses to ask + * questions of the context. * - *

It also acts as a place for to define common utility routines that the various Ast nodes might need. + *

It also acts as a place for to define common utility routines that the various AST + * nodes might need. * * @author Andy Clement * @since 3.0 @@ -46,35 +50,33 @@ public class ExpressionState { private final EvaluationContext relatedContext; - private Stack variableScopes; + private final TypedValue rootObject; - private Stack contextObjects; + private final SpelParserConfiguration configuration; - private final TypedValue rootObject; + private Stack variableScopes; - private SpelParserConfiguration configuration; + private Stack contextObjects; public ExpressionState(EvaluationContext context) { - this.relatedContext = context; - this.rootObject = context.getRootObject(); + this(context, context.getRootObject(), new SpelParserConfiguration(false, false)); } public ExpressionState(EvaluationContext context, SpelParserConfiguration configuration) { - this.relatedContext = context; - this.configuration = configuration; - this.rootObject = context.getRootObject(); + this(context, context.getRootObject(), configuration); } public ExpressionState(EvaluationContext context, TypedValue rootObject) { - this.relatedContext = context; - this.rootObject = rootObject; + this(context, rootObject, new SpelParserConfiguration(false, false)); } public ExpressionState(EvaluationContext context, TypedValue rootObject, SpelParserConfiguration configuration) { + Assert.notNull(context, "EvaluationContext must not be null"); + Assert.notNull(configuration, "SpelParserConfiguration must not be null"); this.relatedContext = context; - this.configuration = configuration; this.rootObject = rootObject; + this.configuration = configuration; } @@ -90,23 +92,22 @@ private void ensureVariableScopesInitialized() { * The active context object is what unqualified references to properties/etc are resolved against. */ public TypedValue getActiveContextObject() { - if (this.contextObjects==null || this.contextObjects.isEmpty()) { + if (this.contextObjects == null || this.contextObjects.isEmpty()) { return this.rootObject; } - return this.contextObjects.peek(); } public void pushActiveContextObject(TypedValue obj) { - if (this.contextObjects==null) { - this.contextObjects = new Stack(); + if (this.contextObjects == null) { + this.contextObjects = new Stack(); } this.contextObjects.push(obj); } public void popActiveContextObject() { - if (this.contextObjects==null) { - this.contextObjects = new Stack(); + if (this.contextObjects == null) { + this.contextObjects = new Stack(); } this.contextObjects.pop(); } @@ -138,7 +139,8 @@ public Class findType(String type) throws EvaluationException { } public Object convertValue(Object value, TypeDescriptor targetTypeDescriptor) throws EvaluationException { - return this.relatedContext.getTypeConverter().convertValue(value, TypeDescriptor.forObject(value), targetTypeDescriptor); + return this.relatedContext.getTypeConverter().convertValue(value, + TypeDescriptor.forObject(value), targetTypeDescriptor); } public TypeConverter getTypeConverter() { @@ -151,9 +153,8 @@ public Object convertValue(TypedValue value, TypeDescriptor targetTypeDescriptor } /* - * A new scope is entered when a function is invoked + * A new scope is entered when a function is invoked. */ - public void enterScope(Map argMap) { ensureVariableScopesInitialized(); this.variableScopes.push(new VariableScope(argMap)); @@ -192,8 +193,8 @@ public TypedValue operate(Operation op, Object left, Object right) throws Evalua return new TypedValue(returnValue); } else { - String leftType = (left==null?"null":left.getClass().getName()); - String rightType = (right==null?"null":right.getClass().getName()); + String leftType = (left == null ? "null" : left.getClass().getName()); + String rightType = (right == null? "null" : right.getClass().getName()); throw new SpelEvaluationException(SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES, op, leftType, rightType); } } @@ -210,16 +211,20 @@ public SpelParserConfiguration getConfiguration() { return this.configuration; } + /** - * A new scope is entered when a function is called and it is used to hold the parameters to the function call. If the names - * of the parameters clash with those in a higher level scope, those in the higher level scope will not be accessible whilst - * the function is executing. When the function returns the scope is exited. + * A new scope is entered when a function is called and it is used to hold the + * parameters to the function call. If the names of the parameters clash with + * those in a higher level scope, those in the higher level scope will not be + * accessible whilst the function is executing. When the function returns, + * the scope is exited. */ private static class VariableScope { private final Map vars = new HashMap(); - public VariableScope() { } + public VariableScope() { + } public VariableScope(Map arguments) { if (arguments != null) { diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java b/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java index 3f89bed5121c..535be656bcc7 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java @@ -66,7 +66,7 @@ public enum SpelMessage { INSTANCEOF_OPERATOR_NEEDS_CLASS_OPERAND(Kind.ERROR, 1028, "The operator 'instanceof' needs the right operand to be a class, not a ''{0}''"), // EXCEPTION_DURING_METHOD_INVOCATION(Kind.ERROR, 1029, "A problem occurred when trying to execute method ''{0}'' on object of type ''{1}'': ''{2}''"), // OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES(Kind.ERROR, 1030, "The operator ''{0}'' is not supported between objects of type ''{1}'' and ''{2}''"), // - PROBLEM_LOCATING_METHOD(Kind.ERROR, 1031, "Problem locating method {0} cannot on type {1}"), + PROBLEM_LOCATING_METHOD(Kind.ERROR, 1031, "Problem locating method {0} on type {1}"), SETVALUE_NOT_SUPPORTED( Kind.ERROR, 1032, "setValue(ExpressionState, Object) not supported for ''{0}''"), // MULTIPLE_POSSIBLE_METHODS(Kind.ERROR, 1033, "Method call of ''{0}'' is ambiguous, supported type conversions allow multiple variants to match"), // EXCEPTION_DURING_PROPERTY_WRITE(Kind.ERROR, 1034, "A problem occurred whilst attempting to set the property ''{0}'': {1}"), // diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java b/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java index ccadb93061b5..8c26f883b28e 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java @@ -49,8 +49,7 @@ public SpelParserConfiguration(boolean autoGrowNullReferences, boolean autoGrowC * @param autoGrowCollections if collections should automatically grow * @param maximumAutoGrowSize the maximum size that the collection can auto grow */ - public SpelParserConfiguration(boolean autoGrowNullReferences, - boolean autoGrowCollections, int maximumAutoGrowSize) { + public SpelParserConfiguration(boolean autoGrowNullReferences, boolean autoGrowCollections, int maximumAutoGrowSize) { this.autoGrowNullReferences = autoGrowNullReferences; this.autoGrowCollections = autoGrowCollections; this.maximumAutoGrowSize = maximumAutoGrowSize; diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java index 8a16a2dcb71c..8cd7a42704b6 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java @@ -32,6 +32,7 @@ import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.SpelEvaluationException; import org.springframework.expression.spel.SpelMessage; +import org.springframework.expression.spel.support.ReflectiveMethodResolver; /** * Expression language AST node that represents a method reference. @@ -62,116 +63,97 @@ public final String getName() { @Override protected ValueRef getValueRef(ExpressionState state) throws EvaluationException { - TypedValue currentContext = state.getActiveContextObject(); - Object[] arguments = new Object[getChildCount()]; - for (int i = 0; i < arguments.length; i++) { - // Make the root object the active context again for evaluating the parameter - // expressions - try { - state.pushActiveContextObject(state.getRootContextObject()); - arguments[i] = this.children[i].getValueInternal(state).getValue(); - } - finally { - state.popActiveContextObject(); - } - } - if (currentContext.getValue() == null) { - if (this.nullSafe) { - return ValueRef.NullValueRef.instance; - } - else { - throw new SpelEvaluationException(getStartPosition(), SpelMessage.METHOD_CALL_ON_NULL_OBJECT_NOT_ALLOWED, - FormatHelper.formatMethodForMessage(this.name, getTypes(arguments))); - } + Object[] arguments = getArguments(state); + if (state.getActiveContextObject().getValue() == null) { + throwIfNotNullSafe(getArgumentTypes(arguments)); + return ValueRef.NullValueRef.instance; } - return new MethodValueRef(state,state.getEvaluationContext(),state.getActiveContextObject().getValue(),arguments); + return new MethodValueRef(state); } @Override public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { - TypedValue currentContext = state.getActiveContextObject(); - Object[] arguments = new Object[getChildCount()]; - for (int i = 0; i < arguments.length; i++) { - // Make the root object the active context again for evaluating the parameter - // expressions - try { - state.pushActiveContextObject(state.getRootContextObject()); - arguments[i] = this.children[i].getValueInternal(state).getValue(); - } - finally { - state.popActiveContextObject(); - } - } - List argumentTypes = getTypes(arguments); - if (currentContext.getValue() == null) { - if (this.nullSafe) { - return TypedValue.NULL; - } - else { - throw new SpelEvaluationException(getStartPosition(), SpelMessage.METHOD_CALL_ON_NULL_OBJECT_NOT_ALLOWED, - FormatHelper.formatMethodForMessage(this.name, argumentTypes)); - } + EvaluationContext evaluationContext = state.getEvaluationContext(); + Object value = state.getActiveContextObject().getValue(); + TypeDescriptor targetType = state.getActiveContextObject().getTypeDescriptor(); + Object[] arguments = getArguments(state); + return getValueInternal(evaluationContext, value, targetType, arguments); + } + + private TypedValue getValueInternal(EvaluationContext evaluationContext, + Object value, TypeDescriptor targetType, Object[] arguments) { + + List argumentTypes = getArgumentTypes(arguments); + if (value == null) { + throwIfNotNullSafe(argumentTypes); + return TypedValue.NULL; } - MethodExecutor executorToUse = getCachedExecutor(argumentTypes); + MethodExecutor executorToUse = getCachedExecutor(evaluationContext, value, targetType, argumentTypes); if (executorToUse != null) { try { - return executorToUse.execute(state.getEvaluationContext(), - state.getActiveContextObject().getValue(), arguments); + return executorToUse.execute(evaluationContext, value, arguments); } catch (AccessException ae) { // Two reasons this can occur: // 1. the method invoked actually threw a real exception - // 2. the method invoked was not passed the arguments it expected and has become 'stale' + // 2. the method invoked was not passed the arguments it expected and + // has become 'stale' - // In the first case we should not retry, in the second case we should see if there is a - // better suited method. + // In the first case we should not retry, in the second case we should see + // if there is a better suited method. - // To determine which situation it is, the AccessException will contain a cause. - // If the cause is an InvocationTargetException, a user exception was thrown inside the method. - // Otherwise the method could not be invoked. - throwSimpleExceptionIfPossible(state, ae); + // To determine the situation, the AccessException will contain a cause. + // If the cause is an InvocationTargetException, a user exception was + // thrown inside the method. Otherwise the method could not be invoked. + throwSimpleExceptionIfPossible(value, ae); - // at this point we know it wasn't a user problem so worth a retry if a better candidate can be found + // At this point we know it wasn't a user problem so worth a retry if a + // better candidate can be found. this.cachedExecutor = null; } } // either there was no accessor or it no longer existed - executorToUse = findAccessorForMethod(this.name, argumentTypes, state); - this.cachedExecutor = new CachedMethodExecutor(executorToUse, argumentTypes); + executorToUse = findAccessorForMethod(this.name, argumentTypes, value, evaluationContext); + this.cachedExecutor = new CachedMethodExecutor( + executorToUse, (value instanceof Class ? (Class) value : null), targetType, argumentTypes); try { - return executorToUse.execute( - state.getEvaluationContext(), state.getActiveContextObject().getValue(), arguments); + return executorToUse.execute(evaluationContext, value, arguments); } - catch (AccessException ae) { + catch (AccessException ex) { // Same unwrapping exception handling as above in above catch block - throwSimpleExceptionIfPossible(state, ae); - throw new SpelEvaluationException( getStartPosition(), ae, SpelMessage.EXCEPTION_DURING_METHOD_INVOCATION, - this.name, state.getActiveContextObject().getValue().getClass().getName(), ae.getMessage()); + throwSimpleExceptionIfPossible(value, ex); + throw new SpelEvaluationException(getStartPosition(), ex, + SpelMessage.EXCEPTION_DURING_METHOD_INVOCATION, this.name, + value.getClass().getName(), ex.getMessage()); } } - /** - * Decode the AccessException, throwing a lightweight evaluation exception or, if the cause was a RuntimeException, - * throw the RuntimeException directly. - */ - private void throwSimpleExceptionIfPossible(ExpressionState state, AccessException ae) { - if (ae.getCause() instanceof InvocationTargetException) { - Throwable rootCause = ae.getCause().getCause(); - if (rootCause instanceof RuntimeException) { - throw (RuntimeException) rootCause; + private void throwIfNotNullSafe(List argumentTypes) { + if (!this.nullSafe) { + throw new SpelEvaluationException(getStartPosition(), + SpelMessage.METHOD_CALL_ON_NULL_OBJECT_NOT_ALLOWED, + FormatHelper.formatMethodForMessage(this.name, argumentTypes)); + } + } + + private Object[] getArguments(ExpressionState state) { + Object[] arguments = new Object[getChildCount()]; + for (int i = 0; i < arguments.length; i++) { + // Make the root object the active context again for evaluating the parameter expressions + try { + state.pushActiveContextObject(state.getRootContextObject()); + arguments[i] = this.children[i].getValueInternal(state).getValue(); } - else { - throw new ExpressionInvocationTargetException(getStartPosition(), - "A problem occurred when trying to execute method '" + this.name + - "' on object of type '" + state.getActiveContextObject().getValue().getClass().getName() + "'", - rootCause); + finally { + state.popActiveContextObject(); } } + return arguments; } - private List getTypes(Object... arguments) { + private List getArgumentTypes(Object... arguments) { List descriptors = new ArrayList(arguments.length); for (Object argument : arguments) { descriptors.add(TypeDescriptor.forObject(argument)); @@ -179,119 +161,101 @@ private List getTypes(Object... arguments) { return Collections.unmodifiableList(descriptors); } - @Override - public String toStringAST() { - StringBuilder sb = new StringBuilder(); - sb.append(this.name).append("("); - for (int i = 0; i < getChildCount(); i++) { - if (i > 0) { - sb.append(","); - } - sb.append(getChild(i).toStringAST()); - } - sb.append(")"); - return sb.toString(); - } + private MethodExecutor getCachedExecutor(EvaluationContext evaluationContext, Object value, + TypeDescriptor target, List argumentTypes) { - private MethodExecutor findAccessorForMethod(String name, List argumentTypes, ExpressionState state) - throws SpelEvaluationException { + List methodResolvers = evaluationContext.getMethodResolvers(); + if (methodResolvers == null || methodResolvers.size() != 1 || + !(methodResolvers.get(0) instanceof ReflectiveMethodResolver)) { + // Not a default ReflectiveMethodResolver - don't know whether caching is valid + return null; + } - return findAccessorForMethod(name, argumentTypes, - state.getActiveContextObject().getValue(), state.getEvaluationContext()); + CachedMethodExecutor executorToCheck = this.cachedExecutor; + if (executorToCheck != null && executorToCheck.isSuitable(value, target, argumentTypes)) { + return executorToCheck.get(); + } + this.cachedExecutor = null; + return null; } - private MethodExecutor findAccessorForMethod(String name, - List argumentTypes, Object contextObject, EvaluationContext eContext) - throws SpelEvaluationException { + private MethodExecutor findAccessorForMethod(String name, List argumentTypes, + Object targetObject, EvaluationContext evaluationContext) throws SpelEvaluationException { - List methodResolvers = eContext.getMethodResolvers(); + List methodResolvers = evaluationContext.getMethodResolvers(); if (methodResolvers != null) { for (MethodResolver methodResolver : methodResolvers) { try { - MethodExecutor methodExecutor = methodResolver.resolve(eContext, - contextObject, name, argumentTypes); + MethodExecutor methodExecutor = methodResolver.resolve( + evaluationContext, targetObject, name, argumentTypes); if (methodExecutor != null) { return methodExecutor; } } catch (AccessException ex) { - throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.PROBLEM_LOCATING_METHOD, name, - contextObject.getClass()); + throw new SpelEvaluationException(getStartPosition(), ex, + SpelMessage.PROBLEM_LOCATING_METHOD, name, targetObject.getClass()); } } } + throw new SpelEvaluationException(getStartPosition(), SpelMessage.METHOD_NOT_FOUND, FormatHelper.formatMethodForMessage(name, argumentTypes), - FormatHelper.formatClassNameForMessage(contextObject instanceof Class ? ((Class) contextObject) : contextObject.getClass())); + FormatHelper.formatClassNameForMessage( + targetObject instanceof Class ? ((Class) targetObject) : targetObject.getClass())); } - private MethodExecutor getCachedExecutor(List argumentTypes) { - if (this.cachedExecutor == null || !this.cachedExecutor.isSuitable(argumentTypes)) { - this.cachedExecutor = null; - return null; + /** + * Decode the AccessException, throwing a lightweight evaluation exception or, if the + * cause was a RuntimeException, throw the RuntimeException directly. + */ + private void throwSimpleExceptionIfPossible(Object value, AccessException ae) { + if (ae.getCause() instanceof InvocationTargetException) { + Throwable rootCause = ae.getCause().getCause(); + if (rootCause instanceof RuntimeException) { + throw (RuntimeException) rootCause; + } + throw new ExpressionInvocationTargetException(getStartPosition(), + "A problem occurred when trying to execute method '" + this.name + + "' on object of type [" + value.getClass().getName() + "]", rootCause); } - return this.cachedExecutor.get(); } + @Override + public String toStringAST() { + StringBuilder sb = new StringBuilder(); + sb.append(this.name).append("("); + for (int i = 0; i < getChildCount(); i++) { + if (i > 0) { + sb.append(","); + } + sb.append(getChild(i).toStringAST()); + } + sb.append(")"); + return sb.toString(); + } - private class MethodValueRef implements ValueRef { - private final ExpressionState state; + private class MethodValueRef implements ValueRef { private final EvaluationContext evaluationContext; - private final Object target; + private final Object value; - private final Object[] arguments; + private final TypeDescriptor targetType; - private final List argumentTypes; + private final Object[] arguments; - MethodValueRef(ExpressionState state, EvaluationContext evaluationContext, Object object, Object[] arguments) { - this.state = state; - this.evaluationContext = evaluationContext; - this.target = object; - this.arguments = arguments; - this.argumentTypes = getTypes(this.arguments); + public MethodValueRef(ExpressionState state) { + this.evaluationContext = state.getEvaluationContext(); + this.value = state.getActiveContextObject().getValue(); + this.targetType = state.getActiveContextObject().getTypeDescriptor(); + this.arguments = getArguments(state); } @Override public TypedValue getValue() { - MethodExecutor executorToUse = getCachedExecutor(this.argumentTypes); - if (executorToUse != null) { - try { - return executorToUse.execute(this.evaluationContext, this.target, this.arguments); - } - catch (AccessException ae) { - // Two reasons this can occur: - // 1. the method invoked actually threw a real exception - // 2. the method invoked was not passed the arguments it expected and has become 'stale' - - // In the first case we should not retry, in the second case we should see if there is a - // better suited method. - - // To determine which situation it is, the AccessException will contain a cause. - // If the cause is an InvocationTargetException, a user exception was thrown inside the method. - // Otherwise the method could not be invoked. - throwSimpleExceptionIfPossible(this.state, ae); - - // at this point we know it wasn't a user problem so worth a retry if a better candidate can be found - MethodReference.this.cachedExecutor = null; - } - } - - // either there was no accessor or it no longer existed - executorToUse = findAccessorForMethod(MethodReference.this.name, argumentTypes, this.target, this.evaluationContext); - MethodReference.this.cachedExecutor = new CachedMethodExecutor(executorToUse, this.argumentTypes); - try { - return executorToUse.execute(this.evaluationContext, this.target, this.arguments); - } - catch (AccessException ex) { - // Same unwrapping exception handling as above in above catch block - throwSimpleExceptionIfPossible(this.state, ex); - throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.EXCEPTION_DURING_METHOD_INVOCATION, - MethodReference.this.name, this.state.getActiveContextObject().getValue().getClass().getName(), - ex.getMessage()); - } + return getValueInternal(this.evaluationContext, this.value, this.targetType, this.arguments); } @Override @@ -310,15 +274,23 @@ private static class CachedMethodExecutor { private final MethodExecutor methodExecutor; + private final Class staticClass; + + private final TypeDescriptor target; + private final List argumentTypes; - public CachedMethodExecutor(MethodExecutor methodExecutor, List argumentTypes) { + public CachedMethodExecutor(MethodExecutor methodExecutor, Class staticClass, + TypeDescriptor target, List argumentTypes) { this.methodExecutor = methodExecutor; + this.staticClass = staticClass; + this.target = target; this.argumentTypes = argumentTypes; } - public boolean isSuitable(List argumentTypes) { - return (this.methodExecutor != null && this.argumentTypes.equals(argumentTypes)); + public boolean isSuitable(Object value, TypeDescriptor target, List argumentTypes) { + return ((this.staticClass == null || this.staticClass.equals(value)) && + this.target.equals(target) && this.argumentTypes.equals(argumentTypes)); } public MethodExecutor get() { diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java index 99e79ff5048c..e1b8c34a3fe4 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -67,47 +67,20 @@ public String getName() { } - static class AccessorLValue implements ValueRef { - private PropertyOrFieldReference ref; - private TypedValue contextObject; - private EvaluationContext eContext; - private boolean isAutoGrowNullReferences; - - public AccessorLValue( - PropertyOrFieldReference propertyOrFieldReference, - TypedValue activeContextObject, - EvaluationContext evaluationContext, boolean isAutoGrowNullReferences) { - this.ref = propertyOrFieldReference; - this.contextObject = activeContextObject; - this.eContext =evaluationContext; - this.isAutoGrowNullReferences = isAutoGrowNullReferences; - } - - public TypedValue getValue() { - return ref.getValueInternal(contextObject,eContext,isAutoGrowNullReferences); - } - - public void setValue(Object newValue) { - ref.writeProperty(contextObject,eContext, ref.name, newValue); - } - - public boolean isWritable() { - return true; - } - - } - @Override public ValueRef getValueRef(ExpressionState state) throws EvaluationException { - return new AccessorLValue(this,state.getActiveContextObject(),state.getEvaluationContext(),state.getConfiguration().isAutoGrowNullReferences()); + return new AccessorLValue(this, state.getActiveContextObject(), state.getEvaluationContext(), + state.getConfiguration().isAutoGrowNullReferences()); } @Override public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { - return getValueInternal(state.getActiveContextObject(), state.getEvaluationContext(), state.getConfiguration().isAutoGrowNullReferences()); + return getValueInternal(state.getActiveContextObject(), state.getEvaluationContext(), + state.getConfiguration().isAutoGrowNullReferences()); } - private TypedValue getValueInternal(TypedValue contextObject, EvaluationContext eContext, boolean isAutoGrowNullReferences) throws EvaluationException { + private TypedValue getValueInternal(TypedValue contextObject, EvaluationContext eContext, + boolean isAutoGrowNullReferences) throws EvaluationException { TypedValue result = readProperty(contextObject, eContext, this.name); @@ -139,7 +112,7 @@ private TypedValue getValueInternal(TypedValue contextObject, EvaluationContext try { if (isWritableProperty(this.name,contextObject,eContext)) { Map newMap = HashMap.class.newInstance(); - writeProperty(contextObject, eContext, name, newMap); + writeProperty(contextObject, eContext, this.name, newMap); result = readProperty(contextObject, eContext, this.name); } } @@ -158,7 +131,7 @@ private TypedValue getValueInternal(TypedValue contextObject, EvaluationContext try { if (isWritableProperty(this.name,contextObject,eContext)) { Object newObject = result.getTypeDescriptor().getType().newInstance(); - writeProperty(contextObject, eContext, name, newObject); + writeProperty(contextObject, eContext, this.name, newObject); result = readProperty(contextObject, eContext, this.name); } } @@ -192,14 +165,11 @@ public String toStringAST() { /** * Attempt to read the named property from the current context object. - * @param state the evaluation state - * @param name the name of the property * @return the value of the property * @throws SpelEvaluationException if any problem accessing the property or it cannot be found */ private TypedValue readProperty(TypedValue contextObject, EvaluationContext eContext, String name) throws EvaluationException { Object targetObject = contextObject.getValue(); - if (targetObject == null && this.nullSafe) { return TypedValue.NULL; } @@ -249,8 +219,7 @@ private TypedValue readProperty(TypedValue contextObject, EvaluationContext eCon } private void writeProperty(TypedValue contextObject, EvaluationContext eContext, String name, Object newValue) throws SpelEvaluationException { - - if (contextObject.getValue() == null && nullSafe) { + if (contextObject.getValue() == null && this.nullSafe) { return; } @@ -353,4 +322,39 @@ else if (clazz.isAssignableFrom(targetType)) { return resolvers; } + + private static class AccessorLValue implements ValueRef { + + private final PropertyOrFieldReference ref; + + private final TypedValue contextObject; + + private final EvaluationContext eContext; + + private final boolean autoGrowNullReferences; + + public AccessorLValue(PropertyOrFieldReference propertyOrFieldReference, TypedValue activeContextObject, + EvaluationContext evaluationContext, boolean autoGrowNullReferences) { + this.ref = propertyOrFieldReference; + this.contextObject = activeContextObject; + this.eContext = evaluationContext; + this.autoGrowNullReferences = autoGrowNullReferences; + } + + @Override + public TypedValue getValue() { + return this.ref.getValueInternal(this.contextObject, this.eContext, this.autoGrowNullReferences); + } + + @Override + public void setValue(Object newValue) { + this.ref.writeProperty(this.contextObject, this.eContext, this.ref.name, newValue); + } + + @Override + public boolean isWritable() { + return true; + } + } + } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java index 6c5b249114de..c1055e5fddca 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java @@ -29,6 +29,7 @@ import org.springframework.core.MethodParameter; import org.springframework.core.convert.Property; import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.style.ToStringCreator; import org.springframework.expression.AccessException; import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationException; @@ -71,7 +72,7 @@ public boolean canRead(EvaluationContext context, Object target, String name) th if (type.isArray() && name.equals("length")) { return true; } - CacheKey cacheKey = new CacheKey(type, name); + CacheKey cacheKey = new CacheKey(type, name, target instanceof Class); if (this.readerCache.containsKey(cacheKey)) { return true; } @@ -110,7 +111,7 @@ public TypedValue read(EvaluationContext context, Object target, String name) th return new TypedValue(Array.getLength(target)); } - CacheKey cacheKey = new CacheKey(type, name); + CacheKey cacheKey = new CacheKey(type, name, target instanceof Class); InvokerPair invoker = this.readerCache.get(cacheKey); if (invoker == null || invoker.member instanceof Method) { @@ -168,7 +169,7 @@ public boolean canWrite(EvaluationContext context, Object target, String name) t return false; } Class type = (target instanceof Class ? (Class) target : target.getClass()); - CacheKey cacheKey = new CacheKey(type, name); + CacheKey cacheKey = new CacheKey(type, name, target instanceof Class); if (this.writerCache.containsKey(cacheKey)) { return true; } @@ -209,7 +210,7 @@ public void write(EvaluationContext context, Object target, String name, Object throw new AccessException("Type conversion failure",evaluationException); } } - CacheKey cacheKey = new CacheKey(type, name); + CacheKey cacheKey = new CacheKey(type, name, target instanceof Class); Member cachedMember = this.writerCache.get(cacheKey); if (cachedMember == null || cachedMember instanceof Method) { @@ -266,7 +267,7 @@ private TypeDescriptor getTypeDescriptor(EvaluationContext context, Object targe if (type.isArray() && name.equals("length")) { return TypeDescriptor.valueOf(Integer.TYPE); } - CacheKey cacheKey = new CacheKey(type, name); + CacheKey cacheKey = new CacheKey(type, name, target instanceof Class); TypeDescriptor typeDescriptor = this.typeDescriptorCache.get(cacheKey); if (typeDescriptor == null) { // attempt to populate the cache entry @@ -313,42 +314,34 @@ private Field findField(String name, Class clazz, Object target) { * Find a getter method for the specified property. */ protected Method findGetterForProperty(String propertyName, Class clazz, boolean mustBeStatic) { - Method[] ms = getSortedClassMethods(clazz); - String propertyMethodSuffix = getPropertyMethodSuffix(propertyName); - - // Try "get*" method... - String getterName = "get" + propertyMethodSuffix; - for (Method method : ms) { - if (method.getName().equals(getterName) && method.getParameterTypes().length == 0 && - (!mustBeStatic || Modifier.isStatic(method.getModifiers()))) { - return method; - } - } - // Try "is*" method... - getterName = "is" + propertyMethodSuffix; - for (Method method : ms) { - if (method.getName().equals(getterName) && method.getParameterTypes().length == 0 && - (boolean.class.equals(method.getReturnType()) || Boolean.class.equals(method.getReturnType())) && - (!mustBeStatic || Modifier.isStatic(method.getModifiers()))) { - return method; - } - } - return null; + return findMethodForProperty(getPropertyMethodSuffixes(propertyName), + new String[] { "get", "is" }, clazz, mustBeStatic, 0); } /** * Find a setter method for the specified property. */ protected Method findSetterForProperty(String propertyName, Class clazz, boolean mustBeStatic) { + return findMethodForProperty(getPropertyMethodSuffixes(propertyName), + new String[] { "set" }, clazz, mustBeStatic, 1); + } + + private Method findMethodForProperty(String[] methodSuffixes, String[] prefixes, Class clazz, + boolean mustBeStatic, int numberOfParams) { Method[] methods = getSortedClassMethods(clazz); - String setterName = "set" + getPropertyMethodSuffix(propertyName); - for (Method method : methods) { - if (method.getName().equals(setterName) && method.getParameterTypes().length == 1 && - (!mustBeStatic || Modifier.isStatic(method.getModifiers()))) { - return method; + for (String methodSuffix : methodSuffixes) { + for (String prefix : prefixes) { + for (Method method : methods) { + if (method.getName().equals(prefix + methodSuffix) + && method.getParameterTypes().length == numberOfParams + && (!mustBeStatic || Modifier.isStatic(method.getModifiers()))) { + return method; + } + } } } return null; + } /** @@ -364,13 +357,29 @@ public int compare(Method o1, Method o2) { return methods; } + /** + * Return the method suffixes for a given property name. The default implementation + * uses JavaBean conventions with additional support for properties of the form 'xY' + * where the method 'getXY()' is used in preference to the JavaBean convention of + * 'getxY()'. + */ + protected String[] getPropertyMethodSuffixes(String propertyName) { + String suffix = getPropertyMethodSuffix(propertyName); + if (suffix.length() > 0 && Character.isUpperCase(suffix.charAt(0))) { + return new String[] { suffix }; + } + return new String[] { suffix, StringUtils.capitalize(suffix) }; + } + + /** + * Return the method suffix for a given property name. The default implementation + * uses JavaBean conventions. + */ protected String getPropertyMethodSuffix(String propertyName) { if (propertyName.length() > 1 && Character.isUpperCase(propertyName.charAt(1))) { return propertyName; } - else { - return StringUtils.capitalize(propertyName); - } + return StringUtils.capitalize(propertyName); } /** @@ -417,7 +426,7 @@ public PropertyAccessor createOptimalAccessor(EvaluationContext eContext, Object return this; } - CacheKey cacheKey = new CacheKey(type, name); + CacheKey cacheKey = new CacheKey(type, name, target instanceof Class); InvokerPair invocationTarget = this.readerCache.get(cacheKey); if (invocationTarget == null || invocationTarget.member instanceof Method) { @@ -476,9 +485,12 @@ private static class CacheKey { private final String name; - public CacheKey(Class clazz, String name) { + private boolean targetIsClass; + + public CacheKey(Class clazz, String name, boolean targetIsClass) { this.clazz = clazz; this.name = name; + this.targetIsClass = targetIsClass; } @Override @@ -490,13 +502,23 @@ public boolean equals(Object other) { return false; } CacheKey otherKey = (CacheKey) other; - return (this.clazz.equals(otherKey.clazz) && this.name.equals(otherKey.name)); + boolean rtn = true; + rtn &= this.clazz.equals(otherKey.clazz); + rtn &= this.name.equals(otherKey.name); + rtn &= this.targetIsClass == otherKey.targetIsClass; + return rtn; } @Override public int hashCode() { return this.clazz.hashCode() * 29 + this.name.hashCode(); } + + @Override + public String toString() { + return new ToStringCreator(this).append("clazz", this.clazz).append("name", + this.name).append("targetIsClass", this.targetIsClass).toString(); + } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardEvaluationContext.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardEvaluationContext.java index c4a4425bed51..b00113071715 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardEvaluationContext.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardEvaluationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -103,15 +103,14 @@ public boolean removeConstructorResolver(ConstructorResolver resolver) { return this.constructorResolvers.remove(resolver); } - public List getConstructorResolvers() { - ensureConstructorResolversInitialized(); - return this.constructorResolvers; - } - public void setConstructorResolvers(List constructorResolvers) { this.constructorResolvers = constructorResolvers; } + public List getConstructorResolvers() { + ensureConstructorResolversInitialized(); + return this.constructorResolvers; + } public void addMethodResolver(MethodResolver resolver) { ensureMethodResolversInitialized(); @@ -123,6 +122,10 @@ public boolean removeMethodResolver(MethodResolver methodResolver) { return this.methodResolvers.remove(methodResolver); } + public void setMethodResolvers(List methodResolvers) { + this.methodResolvers = methodResolvers; + } + public List getMethodResolvers() { ensureMethodResolversInitialized(); return this.methodResolvers; @@ -136,11 +139,6 @@ public BeanResolver getBeanResolver() { return this.beanResolver; } - public void setMethodResolvers(List methodResolvers) { - this.methodResolvers = methodResolvers; - } - - public void addPropertyAccessor(PropertyAccessor accessor) { ensurePropertyAccessorsInitialized(); this.propertyAccessors.add(this.propertyAccessors.size() - 1, accessor); @@ -150,15 +148,14 @@ public boolean removePropertyAccessor(PropertyAccessor accessor) { return this.propertyAccessors.remove(accessor); } - public List getPropertyAccessors() { - ensurePropertyAccessorsInitialized(); - return this.propertyAccessors; - } - public void setPropertyAccessors(List propertyAccessors) { this.propertyAccessors = propertyAccessors; } + public List getPropertyAccessors() { + ensurePropertyAccessorsInitialized(); + return this.propertyAccessors; + } public void setTypeLocator(TypeLocator typeLocator) { Assert.notNull(typeLocator, "TypeLocator must not be null"); @@ -221,19 +218,18 @@ public Object lookupVariable(String name) { /** * Register a {@code MethodFilter} which will be called during method resolution * for the specified type. - * *

The {@code MethodFilter} may remove methods and/or sort the methods which * will then be used by SpEL as the candidates to look through for a match. - * * @param type the type for which the filter should be called * @param filter a {@code MethodFilter}, or {@code null} to unregister a filter for the type * @throws IllegalStateException if the {@link ReflectiveMethodResolver} is not in use */ public void registerMethodFilter(Class type, MethodFilter filter) throws IllegalStateException { ensureMethodResolversInitialized(); - if (reflectiveMethodResolver != null) { - reflectiveMethodResolver.registerMethodFilter(type, filter); - } else { + if (this.reflectiveMethodResolver != null) { + this.reflectiveMethodResolver.registerMethodFilter(type, filter); + } + else { throw new IllegalStateException("Method filter cannot be set as the reflective method resolver is not in use"); } } @@ -261,7 +257,8 @@ private void ensureMethodResolversInitialized() { private synchronized void initializeMethodResolvers() { if (this.methodResolvers == null) { List defaultResolvers = new ArrayList(); - defaultResolvers.add(reflectiveMethodResolver = new ReflectiveMethodResolver()); + this.reflectiveMethodResolver = new ReflectiveMethodResolver(); + defaultResolvers.add(this.reflectiveMethodResolver); this.methodResolvers = defaultResolvers; } } diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/CachedMethodExecutorTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/CachedMethodExecutorTests.java index 75e76a5903ea..bf15a7cce6bf 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/CachedMethodExecutorTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/CachedMethodExecutorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,6 +18,7 @@ import org.junit.Before; import org.junit.Test; + import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.ast.MethodReference; @@ -45,31 +46,42 @@ public void setUp() throws Exception { @Test - public void testCachedExecution() throws Exception { - Expression expression = this.parser.parseExpression("echo(#something)"); - + public void testCachedExecutionForParameters() throws Exception { + Expression expression = this.parser.parseExpression("echo(#var)"); assertMethodExecution(expression, 42, "int: 42"); assertMethodExecution(expression, 42, "int: 42"); assertMethodExecution(expression, "Deep Thought", "String: Deep Thought"); assertMethodExecution(expression, 42, "int: 42"); } + @Test + public void testCachedExecutionForTarget() throws Exception { + Expression expression = this.parser.parseExpression("#var.echo(42)"); + assertMethodExecution(expression, new RootObject(), "int: 42"); + assertMethodExecution(expression, new RootObject(), "int: 42"); + assertMethodExecution(expression, new BaseObject(), "String: 42"); + assertMethodExecution(expression, new RootObject(), "int: 42"); + } + private void assertMethodExecution(Expression expression, Object var, String expected) { - this.context.setVariable("something", var); + this.context.setVariable("var", var); assertEquals(expected, expression.getValue(this.context)); } - public static class RootObject { + public static class BaseObject { public String echo(String value) { return "String: " + value; } + } + + + public static class RootObject extends BaseObject { public String echo(int value) { return "int: " + value; } - } } diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java index e6e28eedd3c4..28c7570b947c 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java @@ -16,14 +16,7 @@ package org.springframework.expression.spel; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - +import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; @@ -38,6 +31,8 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; + +import org.springframework.core.MethodParameter; import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.AccessException; import org.springframework.expression.BeanResolver; @@ -58,6 +53,9 @@ import org.springframework.expression.spel.support.StandardTypeLocator; import org.springframework.expression.spel.testresources.le.div.mod.reserved.Reserver; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + /** * Reproduction tests cornering various SpEL JIRA issues. * @@ -1753,6 +1751,92 @@ public void SPR_10328() throws Exception { exp.getValue(Arrays.asList("foo", "bar", "baz")); } + @Test + public void SPR_10452() throws Exception { + SpelParserConfiguration configuration = new SpelParserConfiguration(false, false); + ExpressionParser parser = new SpelExpressionParser(configuration); + + StandardEvaluationContext context = new StandardEvaluationContext(); + Expression spel = parser.parseExpression("#enumType.values()"); + + context.setVariable("enumType", ABC.class); + Object result = spel.getValue(context); + assertNotNull(result); + assertTrue(result.getClass().isArray()); + assertEquals(ABC.A, Array.get(result, 0)); + assertEquals(ABC.B, Array.get(result, 1)); + assertEquals(ABC.C, Array.get(result, 2)); + + context.setVariable("enumType", XYZ.class); + result = spel.getValue(context); + assertNotNull(result); + assertTrue(result.getClass().isArray()); + assertEquals(XYZ.X, Array.get(result, 0)); + assertEquals(XYZ.Y, Array.get(result, 1)); + assertEquals(XYZ.Z, Array.get(result, 2)); + } + + @Test + public void SPR_9495() throws Exception { + SpelParserConfiguration configuration = new SpelParserConfiguration(false, false); + ExpressionParser parser = new SpelExpressionParser(configuration); + + StandardEvaluationContext context = new StandardEvaluationContext(); + Expression spel = parser.parseExpression("#enumType.values()"); + + context.setVariable("enumType", ABC.class); + Object result = spel.getValue(context); + assertNotNull(result); + assertTrue(result.getClass().isArray()); + assertEquals(ABC.A, Array.get(result, 0)); + assertEquals(ABC.B, Array.get(result, 1)); + assertEquals(ABC.C, Array.get(result, 2)); + + context.addMethodResolver(new MethodResolver() { + @Override + public MethodExecutor resolve(EvaluationContext context, Object targetObject, String name, List argumentTypes) throws AccessException { + return new MethodExecutor() { + @Override + public TypedValue execute(EvaluationContext context, Object target, Object... arguments) throws AccessException { + try { + Method method = XYZ.class.getMethod("values"); + Object value = method.invoke(target, arguments); + return new TypedValue(value, new TypeDescriptor(new MethodParameter(method, -1)).narrow(value)); + } + catch (Exception ex) { + throw new AccessException(ex.getMessage(), ex); + } + } + }; + } + }); + result = spel.getValue(context); + assertNotNull(result); + assertTrue(result.getClass().isArray()); + assertEquals(XYZ.X, Array.get(result, 0)); + assertEquals(XYZ.Y, Array.get(result, 1)); + assertEquals(XYZ.Z, Array.get(result, 2)); + } + + @Test + public void SPR_10486() throws Exception { + SpelExpressionParser parser = new SpelExpressionParser(); + StandardEvaluationContext context = new StandardEvaluationContext(); + SPR10486 rootObject = new SPR10486(); + Expression classNameExpression = parser.parseExpression("class.name"); + Expression nameExpression = parser.parseExpression("name"); + assertThat(classNameExpression.getValue(context, rootObject), + equalTo((Object) SPR10486.class.getName())); + assertThat(nameExpression.getValue(context, rootObject), + equalTo((Object) "name")); + } + + + private static enum ABC {A, B, C} + + private static enum XYZ {X, Y, Z} + + public static class BooleanHolder { private Boolean simpleProperty = true; @@ -1814,4 +1898,20 @@ public static class StaticFinalImpl1 extends AbstractStaticFinal implements Stat public static class StaticFinalImpl2 extends AbstractStaticFinal { } + /** + * The Class TestObject. + */ + public static class SPR10486 { + + private String name = "name"; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + } } diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/support/ReflectionHelperTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/support/ReflectionHelperTests.java index c31a94a71b4a..68911b9bcef4 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/support/ReflectionHelperTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/support/ReflectionHelperTests.java @@ -335,6 +335,12 @@ public void testReflectivePropertyResolver() throws Exception { assertEquals("id",rpr.read(ctx,t,"Id").getValue()); assertTrue(rpr.canRead(ctx,t,"Id")); + // repro SPR-10994 + assertEquals("xyZ",rpr.read(ctx,t,"xyZ").getValue()); + assertTrue(rpr.canRead(ctx,t,"xyZ")); + assertEquals("xY",rpr.read(ctx,t,"xY").getValue()); + assertTrue(rpr.canRead(ctx,t,"xY")); + // SPR-10122, ReflectivePropertyAccessor JavaBean property names compliance tests - setters rpr.write(ctx, t, "pEBS","Test String"); assertEquals("Test String",rpr.read(ctx,t,"pEBS").getValue()); @@ -429,6 +435,8 @@ static class Tester { String id = "id"; String ID = "ID"; String pEBS = "pEBS"; + String xY = "xY"; + String xyZ = "xyZ"; public String getProperty() { return property; } public void setProperty(String value) { property = value; } @@ -445,6 +453,10 @@ static class Tester { public String getID() { return ID; } + public String getXY() { return xY; } + + public String getXyZ() { return xyZ; } + public String getpEBS() { return pEBS; } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/StatementCreatorUtils.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/StatementCreatorUtils.java index a1fbc4d5207b..62eb086861b7 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/StatementCreatorUtils.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/StatementCreatorUtils.java @@ -120,8 +120,7 @@ public static int javaTypeToSqlParameterType(Class javaType) { * @param inValue the value to set * @throws SQLException if thrown by PreparedStatement methods */ - public static void setParameterValue( - PreparedStatement ps, int paramIndex, SqlParameter param, Object inValue) + public static void setParameterValue(PreparedStatement ps, int paramIndex, SqlParameter param, Object inValue) throws SQLException { setParameterValueInternal(ps, paramIndex, param.getSqlType(), param.getTypeName(), param.getScale(), inValue); @@ -137,8 +136,7 @@ public static void setParameterValue( * @throws SQLException if thrown by PreparedStatement methods * @see SqlTypeValue */ - public static void setParameterValue( - PreparedStatement ps, int paramIndex, int sqlType, Object inValue) + public static void setParameterValue(PreparedStatement ps, int paramIndex, int sqlType, Object inValue) throws SQLException { setParameterValueInternal(ps, paramIndex, sqlType, null, null, inValue); @@ -156,9 +154,8 @@ public static void setParameterValue( * @throws SQLException if thrown by PreparedStatement methods * @see SqlTypeValue */ - public static void setParameterValue( - PreparedStatement ps, int paramIndex, int sqlType, String typeName, Object inValue) - throws SQLException { + public static void setParameterValue(PreparedStatement ps, int paramIndex, int sqlType, String typeName, + Object inValue) throws SQLException { setParameterValueInternal(ps, paramIndex, sqlType, typeName, null, inValue); } @@ -177,9 +174,8 @@ public static void setParameterValue( * @throws SQLException if thrown by PreparedStatement methods * @see SqlTypeValue */ - private static void setParameterValueInternal( - PreparedStatement ps, int paramIndex, int sqlType, String typeName, Integer scale, Object inValue) - throws SQLException { + private static void setParameterValueInternal(PreparedStatement ps, int paramIndex, int sqlType, + String typeName, Integer scale, Object inValue) throws SQLException { String typeNameToUse = typeName; int sqlTypeToUse = sqlType; @@ -190,8 +186,7 @@ private static void setParameterValueInternal( SqlParameterValue parameterValue = (SqlParameterValue) inValue; if (logger.isDebugEnabled()) { logger.debug("Overriding type info with runtime info from SqlParameterValue: column index " + paramIndex + - ", SQL type " + parameterValue.getSqlType() + - ", Type name " + parameterValue.getTypeName()); + ", SQL type " + parameterValue.getSqlType() + ", type name " + parameterValue.getTypeName()); } if (parameterValue.getSqlType() != SqlTypeValue.TYPE_UNKNOWN) { sqlTypeToUse = parameterValue.getSqlType(); @@ -221,9 +216,7 @@ private static void setParameterValueInternal( * Set the specified PreparedStatement parameter to null, * respecting database-specific peculiarities. */ - private static void setNull(PreparedStatement ps, int paramIndex, int sqlType, String typeName) - throws SQLException { - + private static void setNull(PreparedStatement ps, int paramIndex, int sqlType, String typeName) throws SQLException { if (sqlType == SqlTypeValue.TYPE_UNKNOWN) { boolean useSetObject = false; sqlType = Types.NULL; @@ -231,9 +224,10 @@ private static void setNull(PreparedStatement ps, int paramIndex, int sqlType, S sqlType = ps.getParameterMetaData().getParameterType(paramIndex); } catch (Throwable ex) { - logger.debug("JDBC 3.0 getParameterType call not supported", ex); - // JDBC driver not compliant with JDBC 3.0 - // -> proceed with database-specific checks + if (logger.isDebugEnabled()) { + logger.debug("JDBC 3.0 getParameterType call not supported - using fallback method instead: " + ex); + } + // JDBC driver not compliant with JDBC 3.0 -> proceed with database-specific checks try { DatabaseMetaData dbmd = ps.getConnection().getMetaData(); String databaseProductName = dbmd.getDatabaseProductName(); diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/CallMetaDataContext.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/CallMetaDataContext.java old mode 100644 new mode 100755 index dffae703619a..dacff255ecbe --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/CallMetaDataContext.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/CallMetaDataContext.java @@ -379,7 +379,7 @@ protected List reconcileParameters(List parameters) else { String returnNameToUse =(StringUtils.hasLength(meta.getParameterName()) ? parNameToUse : getFunctionReturnName()); - workParameters.add(new SqlOutParameter(returnNameToUse, meta.getSqlType())); + workParameters.add(this.metaDataProvider.createDefaultOutParameter(returnNameToUse, meta)); if (isFunction()) { setFunctionReturnName(returnNameToUse); outParameterNames.add(returnNameToUse); diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/HsqlTableMetaDataProvider.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/HsqlTableMetaDataProvider.java index cb6796a2d12e..3020a58ee008 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/HsqlTableMetaDataProvider.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/HsqlTableMetaDataProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,8 +20,8 @@ import java.sql.SQLException; /** - * The HSQL specific implementation of the {@link TableMetaDataProvider}. Suports a feature for - * retreiving generated keys without the JDBC 3.0 getGeneratedKeys support. + * The HSQL specific implementation of the {@link TableMetaDataProvider}. + * Supports a feature for retreiving generated keys without the JDBC 3.0 getGeneratedKeys support. * * @author Thomas Risberg * @since 2.5 @@ -32,15 +32,14 @@ public HsqlTableMetaDataProvider(DatabaseMetaData databaseMetaData) throws SQLEx super(databaseMetaData); } - @Override public boolean isGetGeneratedKeysSimulated() { return true; } - @Override public String getSimpleQueryForGetGeneratedKey(String tableName, String keyColumnName) { return "select max(identity()) from " + tableName; } + } 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 b28f05c62004..31f84b50e5e7 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-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,7 +32,8 @@ /** * Helper methods for named parameter parsing. - * Only intended for internal use within Spring's JDBC framework. + * + *

Only intended for internal use within Spring's JDBC framework. * * @author Thomas Risberg * @author Juergen Hoeller @@ -112,11 +113,13 @@ public static ParsedSql parseSqlStatement(final String sql) { while (j < statement.length && !('}' == statement[j])) { j++; if (':' == statement[j] || '{' == statement[j]) { - throw new InvalidDataAccessApiUsageException("Parameter name contains invalid character '" + statement[j] + "' at position " + i + " in statement " + sql); + throw new InvalidDataAccessApiUsageException("Parameter name contains invalid character '" + + statement[j] + "' at position " + i + " in statement: " + sql); } } if (j >= statement.length) { - throw new InvalidDataAccessApiUsageException("Non-terminated named parameter declaration at position " + i + " in statement " + sql); + throw new InvalidDataAccessApiUsageException( + "Non-terminated named parameter declaration at position " + i + " in statement: " + sql); } if (j - i > 3) { parameter = sql.substring(i + 2, j); @@ -165,8 +168,9 @@ public static ParsedSql parseSqlStatement(final String sql) { return parsedSql; } - private static int addNamedParameter(List parameterList, int totalParameterCount, int escapes, int i, int j, - String parameter) { + private static int addNamedParameter( + List parameterList, int totalParameterCount, int escapes, int i, int j, String parameter) { + parameterList.add(new ParameterHolder(parameter, i - escapes, j - escapes)); totalParameterCount++; return totalParameterCount; @@ -229,18 +233,18 @@ private static int skipCommentsAndQuotes(char[] statement, int position) { } /** - * Parse the SQL statement and locate any placeholders or named parameters. - * Named parameters are substituted for a JDBC placeholder and any select list - * is expanded to the required number of placeholders. Select lists may contain - * an array of objects and in that case the placeholders will be grouped and - * enclosed with parantheses. This allows for the use of "expression lists" in - * the SQL statement like:
- * select id, name, state from table where (name, age) in (('John', 35), ('Ann', 50)) - *

The parameter values passed in are used to determine the number of - * placeholder to be used for a select list. Select lists should be limited - * to 100 or fewer elements. A larger number of elements is not guaramteed to - * be supported by the database and is strictly vendor-dependent. - * @param parsedSql the parsed represenation of the SQL statement + * Parse the SQL statement and locate any placeholders or named parameters. Named + * parameters are substituted for a JDBC placeholder, and any select list is expanded + * to the required number of placeholders. Select lists may contain an array of + * objects, and in that case the placeholders will be grouped and enclosed with + * parentheses. This allows for the use of "expression lists" in the SQL statement + * like:

+ * {@code select id, name, state from table where (name, age) in (('John', 35), ('Ann', 50))} + *

The parameter values passed in are used to determine the number of placeholders to + * be used for a select list. Select lists should be limited to 100 or fewer elements. + * A larger number of elements is not guaranteed to be supported by the database and + * is strictly vendor-dependent. + * @param parsedSql the parsed representation of the SQL statement * @param paramSource the source for named parameters * @return the SQL statement with substituted parameters * @see #parseSqlStatement @@ -255,7 +259,7 @@ public static String substituteNamedParameters(ParsedSql parsedSql, SqlParameter int[] indexes = parsedSql.getParameterIndexes(i); int startIndex = indexes[0]; int endIndex = indexes[1]; - actualSql.append(originalSql.substring(lastIndex, startIndex)); + actualSql.append(originalSql, lastIndex, startIndex); if (paramSource != null && paramSource.hasValue(paramName)) { Object value = paramSource.getValue(paramName); if (value instanceof SqlParameterValue) { @@ -295,7 +299,7 @@ public static String substituteNamedParameters(ParsedSql parsedSql, SqlParameter } lastIndex = endIndex; } - actualSql.append(originalSql.substring(lastIndex, originalSql.length())); + actualSql.append(originalSql, lastIndex, originalSql.length()); return actualSql.toString(); } @@ -314,10 +318,10 @@ public static Object[] buildValueArray( Object[] paramArray = new Object[parsedSql.getTotalParameterCount()]; if (parsedSql.getNamedParameterCount() > 0 && parsedSql.getUnnamedParameterCount() > 0) { throw new InvalidDataAccessApiUsageException( - "You can't mix named and traditional ? placeholders. You have " + + "Not allowed to mix named and traditional ? placeholders. You have " + parsedSql.getNamedParameterCount() + " named parameter(s) and " + - parsedSql.getUnnamedParameterCount() + " traditonal placeholder(s) in [" + - parsedSql.getOriginalSql() + "]"); + parsedSql.getUnnamedParameterCount() + " traditional placeholder(s) in statement: " + + parsedSql.getOriginalSql()); } List paramNames = parsedSql.getParameterNames(); for (int i = 0; i < paramNames.size(); i++) { @@ -408,15 +412,12 @@ public static List buildSqlParameterList(ParsedSql parsedSql, SqlP List paramNames = parsedSql.getParameterNames(); List params = new LinkedList(); for (String paramName : paramNames) { - SqlParameter param = new SqlParameter( - paramName, - paramSource.getSqlType(paramName), - paramSource.getTypeName(paramName)); - params.add(param); + params.add(new SqlParameter(paramName, paramSource.getSqlType(paramName), paramSource.getTypeName(paramName))); } return params; } + //------------------------------------------------------------------------- // Convenience methods operating on a plain SQL String //------------------------------------------------------------------------- @@ -463,28 +464,32 @@ public static Object[] buildValueArray(String sql, Map paramMap) { return buildValueArray(parsedSql, new MapSqlParameterSource(paramMap), null); } + private static class ParameterHolder { - private String parameterName; - private int startIndex; - private int endIndex; + + private final String parameterName; + + private final int startIndex; + + private final int endIndex; public ParameterHolder(String parameterName, int startIndex, int endIndex) { - super(); this.parameterName = parameterName; this.startIndex = startIndex; this.endIndex = endIndex; } public String getParameterName() { - return parameterName; + return this.parameterName; } public int getStartIndex() { - return startIndex; + return this.startIndex; } public int getEndIndex() { - return endIndex; + return this.endIndex; } } + } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLStateSQLExceptionTranslator.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLStateSQLExceptionTranslator.java index 666b39ef8c1e..0fc68068b158 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLStateSQLExceptionTranslator.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLStateSQLExceptionTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -62,7 +62,6 @@ public class SQLStateSQLExceptionTranslator extends AbstractFallbackSQLException BAD_SQL_GRAMMAR_CODES.add("37"); // Syntax error dynamic SQL BAD_SQL_GRAMMAR_CODES.add("42"); // General SQL syntax error BAD_SQL_GRAMMAR_CODES.add("65"); // Oracle: unknown identifier - BAD_SQL_GRAMMAR_CODES.add("S0"); // MySQL uses this - from ODBC error codes? DATA_INTEGRITY_VIOLATION_CODES.add("01"); // Data truncation DATA_INTEGRITY_VIOLATION_CODES.add("02"); // No data found diff --git a/spring-jms/src/main/java/org/springframework/jms/connection/JmsResourceHolder.java b/spring-jms/src/main/java/org/springframework/jms/connection/JmsResourceHolder.java index 1438b7e77721..c215b689952b 100644 --- a/spring-jms/src/main/java/org/springframework/jms/connection/JmsResourceHolder.java +++ b/spring-jms/src/main/java/org/springframework/jms/connection/JmsResourceHolder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,7 @@ package org.springframework.jms.connection; +import java.lang.reflect.Method; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -30,8 +31,10 @@ import org.apache.commons.logging.LogFactory; import org.springframework.transaction.support.ResourceHolderSupport; +import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; +import org.springframework.util.ReflectionUtils; /** * JMS resource holder, wrapping a JMS Connection and a JMS Session. @@ -183,7 +186,26 @@ public void commitAll() throws JMSException { catch (TransactionInProgressException ex) { // Ignore -> can only happen in case of a JTA transaction. } - // Let IllegalStateException through: It might point out an unexpectedly closed session. + catch (javax.jms.IllegalStateException ex) { + if (this.connectionFactory != null) { + try { + Method getDataSourceMethod = this.connectionFactory.getClass().getMethod("getDataSource"); + Object ds = ReflectionUtils.invokeMethod(getDataSourceMethod, this.connectionFactory); + if (ds != null && TransactionSynchronizationManager.hasResource(ds)) { + // IllegalStateException from sharing the underlying JDBC Connection + // which typically gets committed first, e.g. with Oracle AQ --> ignore + return; + } + } + catch (Throwable ex2) { + if (logger.isDebugEnabled()) { + logger.debug("No working getDataSource method found on ConnectionFactory: " + ex2); + } + // No working getDataSource method - cannot perform DataSource transaction check + } + } + throw ex; + } } } diff --git a/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/support/AsyncRequestInterceptor.java b/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/support/AsyncRequestInterceptor.java new file mode 100644 index 000000000000..38879158544a --- /dev/null +++ b/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/support/AsyncRequestInterceptor.java @@ -0,0 +1,109 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.orm.hibernate4.support; + + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.hibernate.SessionFactory; +import org.springframework.orm.hibernate4.SessionFactoryUtils; +import org.springframework.orm.hibernate4.SessionHolder; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter; +import org.springframework.web.context.request.async.DeferredResult; +import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor; + +import java.util.concurrent.Callable; + +/** + * An interceptor with asynchronous web requests used in OpenSessionInViewFilter and + * OpenSessionInViewInterceptor. + * + * Ensures the following: + * 1) The session is bound/unbound when "callable processing" is started + * 2) The session is closed if an async request times out + * + * @author Rossen Stoyanchev + * @since 3.2.5 + */ +public class AsyncRequestInterceptor extends CallableProcessingInterceptorAdapter + implements DeferredResultProcessingInterceptor { + + private static Log logger = LogFactory.getLog(AsyncRequestInterceptor.class); + + private final SessionFactory sessionFactory; + + private final SessionHolder sessionHolder; + + private volatile boolean timeoutInProgress; + + public AsyncRequestInterceptor(SessionFactory sessionFactory, SessionHolder sessionHolder) { + this.sessionFactory = sessionFactory; + this.sessionHolder = sessionHolder; + } + + @Override + public void preProcess(NativeWebRequest request, Callable task) { + bindSession(); + } + + public void bindSession() { + this.timeoutInProgress = false; + TransactionSynchronizationManager.bindResource(this.sessionFactory, this.sessionHolder); + } + + @Override + public void postProcess(NativeWebRequest request, Callable task, Object concurrentResult) { + TransactionSynchronizationManager.unbindResource(this.sessionFactory); + } + + @Override + public Object handleTimeout(NativeWebRequest request, Callable task) { + this.timeoutInProgress = true; + return RESULT_NONE; // give other interceptors a chance to handle the timeout + } + + @Override + public void afterCompletion(NativeWebRequest request, Callable task) throws Exception { + closeAfterTimeout(); + } + + private void closeAfterTimeout() { + if (this.timeoutInProgress) { + logger.debug("Closing Hibernate Session after async request timeout"); + SessionFactoryUtils.closeSession(sessionHolder.getSession()); + } + } + + // Implementation of DeferredResultProcessingInterceptor methods + + public void beforeConcurrentHandling(NativeWebRequest request, DeferredResult deferredResult) { } + public void preProcess(NativeWebRequest request, DeferredResult deferredResult) { } + public void postProcess(NativeWebRequest request, DeferredResult deferredResult, Object result) { } + + @Override + public boolean handleTimeout(NativeWebRequest request, DeferredResult deferredResult) { + this.timeoutInProgress = true; + return true; // give other interceptors a chance to handle the timeout + } + + @Override + public void afterCompletion(NativeWebRequest request, DeferredResult deferredResult) { + closeAfterTimeout(); + } +} diff --git a/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewFilter.java b/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewFilter.java index 0045a5d3f873..6ab5668293c2 100644 --- a/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewFilter.java +++ b/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewFilter.java @@ -16,14 +16,6 @@ package org.springframework.orm.hibernate4.support; -import java.io.IOException; -import java.util.concurrent.Callable; - -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - import org.hibernate.FlushMode; import org.hibernate.HibernateException; import org.hibernate.Session; @@ -33,13 +25,17 @@ import org.springframework.orm.hibernate4.SessionHolder; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.context.request.NativeWebRequest; -import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter; import org.springframework.web.context.request.async.WebAsyncManager; import org.springframework.web.context.request.async.WebAsyncUtils; import org.springframework.web.context.support.WebApplicationContextUtils; import org.springframework.web.filter.OncePerRequestFilter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + /** * Servlet 2.3 Filter that binds a Hibernate Session to the thread for the entire * processing of the request. Intended for the "Open Session in View" pattern, @@ -143,8 +139,9 @@ protected void doFilterInternal( SessionHolder sessionHolder = new SessionHolder(session); TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder); - asyncManager.registerCallableInterceptor(key, - new SessionBindingCallableInterceptor(sessionFactory, sessionHolder)); + AsyncRequestInterceptor interceptor = new AsyncRequestInterceptor(sessionFactory, sessionHolder); + asyncManager.registerCallableInterceptor(key, interceptor); + asyncManager.registerDeferredResultInterceptor(key, interceptor); } } @@ -216,37 +213,8 @@ private boolean applySessionBindingInterceptor(WebAsyncManager asyncManager, Str if (asyncManager.getCallableInterceptor(key) == null) { return false; } - ((SessionBindingCallableInterceptor) asyncManager.getCallableInterceptor(key)).initializeThread(); + ((AsyncRequestInterceptor) asyncManager.getCallableInterceptor(key)).bindSession(); return true; } - - /** - * Bind and unbind the Hibernate {@code Session} to the current thread. - */ - private static class SessionBindingCallableInterceptor extends CallableProcessingInterceptorAdapter { - - private final SessionFactory sessionFactory; - - private final SessionHolder sessionHolder; - - public SessionBindingCallableInterceptor(SessionFactory sessionFactory, SessionHolder sessionHolder) { - this.sessionFactory = sessionFactory; - this.sessionHolder = sessionHolder; - } - - @Override - public void preProcess(NativeWebRequest request, Callable task) { - initializeThread(); - } - - @Override - public void postProcess(NativeWebRequest request, Callable task, Object concurrentResult) { - TransactionSynchronizationManager.unbindResource(this.sessionFactory); - } - - private void initializeThread() { - TransactionSynchronizationManager.bindResource(this.sessionFactory, this.sessionHolder); - } - } } diff --git a/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewInterceptor.java b/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewInterceptor.java index 8994255e755f..f3e4ade59392 100644 --- a/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewInterceptor.java +++ b/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewInterceptor.java @@ -33,9 +33,7 @@ import org.springframework.web.context.request.AsyncWebRequestInterceptor; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.WebRequest; -import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter; -import org.springframework.web.context.request.async.WebAsyncManager; -import org.springframework.web.context.request.async.WebAsyncUtils; +import org.springframework.web.context.request.async.*; /** * Spring web request interceptor that binds a Hibernate {@code Session} to the @@ -118,8 +116,10 @@ public void preHandle(WebRequest request) throws DataAccessException { SessionHolder sessionHolder = new SessionHolder(session); TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder); - asyncManager.registerCallableInterceptor(participateAttributeName, - new SessionBindingCallableInterceptor(sessionHolder)); + AsyncRequestInterceptor asyncRequestInterceptor = + new AsyncRequestInterceptor(getSessionFactory(), sessionHolder); + asyncManager.registerCallableInterceptor(participateAttributeName, asyncRequestInterceptor); + asyncManager.registerDeferredResultInterceptor(participateAttributeName, asyncRequestInterceptor); } } @@ -196,35 +196,8 @@ private boolean applySessionBindingInterceptor(WebAsyncManager asyncManager, Str if (asyncManager.getCallableInterceptor(key) == null) { return false; } - ((SessionBindingCallableInterceptor) asyncManager.getCallableInterceptor(key)).initializeThread(); + ((AsyncRequestInterceptor) asyncManager.getCallableInterceptor(key)).bindSession(); return true; } - - /** - * Bind and unbind the Hibernate {@code Session} to the current thread. - */ - private class SessionBindingCallableInterceptor extends CallableProcessingInterceptorAdapter { - - private final SessionHolder sessionHolder; - - public SessionBindingCallableInterceptor(SessionHolder sessionHolder) { - this.sessionHolder = sessionHolder; - } - - @Override - public void preProcess(NativeWebRequest request, Callable task) { - initializeThread(); - } - - @Override - public void postProcess(NativeWebRequest request, Callable task, Object concurrentResult) { - TransactionSynchronizationManager.unbindResource(getSessionFactory()); - } - - private void initializeThread() { - TransactionSynchronizationManager.bindResource(getSessionFactory(), this.sessionHolder); - } - } - } diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/AsyncRequestInterceptor.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/AsyncRequestInterceptor.java new file mode 100644 index 000000000000..4e161d87df7b --- /dev/null +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/AsyncRequestInterceptor.java @@ -0,0 +1,110 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.orm.hibernate3.support; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.hibernate.SessionFactory; +import org.springframework.orm.hibernate3.SessionFactoryUtils; +import org.springframework.orm.hibernate3.SessionHolder; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter; +import org.springframework.web.context.request.async.DeferredResult; +import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor; + +import java.util.concurrent.Callable; + +/** + * An interceptor with asynchronous web requests used in OpenSessionInViewFilter and + * OpenSessionInViewInterceptor. + * + * Ensures the following: + * 1) The session is bound/unbound when "callable processing" is started + * 2) The session is closed if an async request times out + * + * @author Rossen Stoyanchev + * @since 3.2.5 + */ +class AsyncRequestInterceptor extends CallableProcessingInterceptorAdapter + implements DeferredResultProcessingInterceptor { + + private static final Log logger = LogFactory.getLog(AsyncRequestInterceptor.class); + + private final SessionFactory sessionFactory; + + private final SessionHolder sessionHolder; + + private volatile boolean timeoutInProgress; + + + public AsyncRequestInterceptor(SessionFactory sessionFactory, SessionHolder sessionHolder) { + this.sessionFactory = sessionFactory; + this.sessionHolder = sessionHolder; + } + + @Override + public void preProcess(NativeWebRequest request, Callable task) { + bindSession(); + } + + public void bindSession() { + this.timeoutInProgress = false; + TransactionSynchronizationManager.bindResource(this.sessionFactory, this.sessionHolder); + } + + @Override + public void postProcess(NativeWebRequest request, Callable task, Object concurrentResult) { + TransactionSynchronizationManager.unbindResource(this.sessionFactory); + } + + @Override + public Object handleTimeout(NativeWebRequest request, Callable task) { + this.timeoutInProgress = true; + return RESULT_NONE; // give other interceptors a chance to handle the timeout + } + + @Override + public void afterCompletion(NativeWebRequest request, Callable task) throws Exception { + closeAfterTimeout(); + } + + private void closeAfterTimeout() { + if (this.timeoutInProgress) { + logger.debug("Closing Hibernate Session after async request timeout"); + SessionFactoryUtils.closeSession(sessionHolder.getSession()); + } + } + + // Implementation of DeferredResultProcessingInterceptor methods + + public void beforeConcurrentHandling(NativeWebRequest request, DeferredResult deferredResult) {} + public void preProcess(NativeWebRequest request, DeferredResult deferredResult) {} + public void postProcess(NativeWebRequest request, DeferredResult deferredResult, Object result) {} + + @Override + public boolean handleTimeout(NativeWebRequest request, DeferredResult deferredResult) { + this.timeoutInProgress = true; + return true; // give other interceptors a chance to handle the timeout + } + + @Override + public void afterCompletion(NativeWebRequest request, DeferredResult deferredResult) { + closeAfterTimeout(); + } + +} diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewFilter.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewFilter.java index 7aab7c89100a..ced44d6f5e27 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewFilter.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewFilter.java @@ -17,7 +17,6 @@ package org.springframework.orm.hibernate3.support; import java.io.IOException; -import java.util.concurrent.Callable; import javax.servlet.FilterChain; import javax.servlet.ServletException; @@ -33,10 +32,7 @@ import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.util.Assert; import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.context.request.NativeWebRequest; -import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter; -import org.springframework.web.context.request.async.WebAsyncManager; -import org.springframework.web.context.request.async.WebAsyncUtils; +import org.springframework.web.context.request.async.*; import org.springframework.web.context.support.WebApplicationContextUtils; import org.springframework.web.filter.OncePerRequestFilter; @@ -212,8 +208,9 @@ protected void doFilterInternal( SessionHolder sessionHolder = new SessionHolder(session); TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder); - asyncManager.registerCallableInterceptor(key, - new SessionBindingCallableInterceptor(sessionFactory, sessionHolder)); + AsyncRequestInterceptor interceptor = new AsyncRequestInterceptor(sessionFactory, sessionHolder); + asyncManager.registerCallableInterceptor(key, interceptor); + asyncManager.registerDeferredResultInterceptor(key, interceptor); } } } @@ -319,38 +316,8 @@ private boolean applySessionBindingInterceptor(WebAsyncManager asyncManager, Str if (asyncManager.getCallableInterceptor(key) == null) { return false; } - ((SessionBindingCallableInterceptor) asyncManager.getCallableInterceptor(key)).initializeThread(); + ((AsyncRequestInterceptor) asyncManager.getCallableInterceptor(key)).bindSession(); return true; } - - /** - * Bind and unbind the Hibernate {@code Session} to the current thread. - */ - private static class SessionBindingCallableInterceptor extends CallableProcessingInterceptorAdapter { - - private final SessionFactory sessionFactory; - - private final SessionHolder sessionHolder; - - public SessionBindingCallableInterceptor(SessionFactory sessionFactory, SessionHolder sessionHolder) { - this.sessionFactory = sessionFactory; - this.sessionHolder = sessionHolder; - } - - @Override - public void preProcess(NativeWebRequest request, Callable task) { - initializeThread(); - } - - @Override - public void postProcess(NativeWebRequest request, Callable task, Object concurrentResult) { - TransactionSynchronizationManager.unbindResource(this.sessionFactory); - } - - private void initializeThread() { - TransactionSynchronizationManager.bindResource(this.sessionFactory, this.sessionHolder); - } - } - } diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewInterceptor.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewInterceptor.java index 692db0ba20db..a713ea9740cd 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewInterceptor.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewInterceptor.java @@ -29,9 +29,7 @@ import org.springframework.web.context.request.AsyncWebRequestInterceptor; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.WebRequest; -import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter; -import org.springframework.web.context.request.async.WebAsyncManager; -import org.springframework.web.context.request.async.WebAsyncUtils; +import org.springframework.web.context.request.async.*; /** * Spring web request interceptor that binds a Hibernate {@code Session} to the @@ -172,8 +170,10 @@ public void preHandle(WebRequest request) throws DataAccessException { SessionHolder sessionHolder = new SessionHolder(session); TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder); - asyncManager.registerCallableInterceptor(participateAttributeName, - new SessionBindingCallableInterceptor(sessionHolder)); + AsyncRequestInterceptor asyncRequestInterceptor = + new AsyncRequestInterceptor(getSessionFactory(), sessionHolder); + asyncManager.registerCallableInterceptor(participateAttributeName, asyncRequestInterceptor); + asyncManager.registerDeferredResultInterceptor(participateAttributeName, asyncRequestInterceptor); } else { // deferred close mode @@ -268,34 +268,8 @@ private boolean applySessionBindingInterceptor(WebAsyncManager asyncManager, Str if (asyncManager.getCallableInterceptor(key) == null) { return false; } - ((SessionBindingCallableInterceptor) asyncManager.getCallableInterceptor(key)).initializeThread(); + ((AsyncRequestInterceptor) asyncManager.getCallableInterceptor(key)).bindSession(); return true; } - - /** - * Bind and unbind the Hibernate {@code Session} to the current thread. - */ - private class SessionBindingCallableInterceptor extends CallableProcessingInterceptorAdapter { - - private final SessionHolder sessionHolder; - - public SessionBindingCallableInterceptor(SessionHolder sessionHolder) { - this.sessionHolder = sessionHolder; - } - - @Override - public void preProcess(NativeWebRequest request, Callable task) { - initializeThread(); - } - - @Override - public void postProcess(NativeWebRequest request, Callable task, Object concurrentResult) { - TransactionSynchronizationManager.unbindResource(getSessionFactory()); - } - - private void initializeThread() { - TransactionSynchronizationManager.bindResource(getSessionFactory(), this.sessionHolder); - } - } } diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/support/AsyncRequestInterceptor.java b/spring-orm/src/main/java/org/springframework/orm/jpa/support/AsyncRequestInterceptor.java new file mode 100644 index 000000000000..046bd8f65014 --- /dev/null +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/support/AsyncRequestInterceptor.java @@ -0,0 +1,111 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.orm.jpa.support; + + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.orm.jpa.EntityManagerFactoryUtils; +import org.springframework.orm.jpa.EntityManagerHolder; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter; +import org.springframework.web.context.request.async.DeferredResult; +import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor; + +import javax.persistence.EntityManagerFactory; +import java.util.concurrent.Callable; + +/** + * An interceptor with asynchronous web requests used in OpenSessionInViewFilter and + * OpenSessionInViewInterceptor. + * + * Ensures the following: + * 1) The session is bound/unbound when "callable processing" is started + * 2) The session is closed if an async request times out + * + * @author Rossen Stoyanchev + * @since 3.2.5 + */ +public class AsyncRequestInterceptor extends CallableProcessingInterceptorAdapter + implements DeferredResultProcessingInterceptor { + + private static Log logger = LogFactory.getLog(AsyncRequestInterceptor.class); + + private final EntityManagerFactory emFactory; + + private final EntityManagerHolder emHolder; + + private volatile boolean timeoutInProgress; + + + public AsyncRequestInterceptor(EntityManagerFactory emFactory, EntityManagerHolder emHolder) { + this.emFactory = emFactory; + this.emHolder = emHolder; + } + + @Override + public void preProcess(NativeWebRequest request, Callable task) { + bindSession(); + } + + public void bindSession() { + this.timeoutInProgress = false; + TransactionSynchronizationManager.bindResource(this.emFactory, this.emHolder); + } + + @Override + public void postProcess(NativeWebRequest request, Callable task, Object concurrentResult) { + TransactionSynchronizationManager.unbindResource(this.emFactory); + } + + @Override + public Object handleTimeout(NativeWebRequest request, Callable task) { + this.timeoutInProgress = true; + return RESULT_NONE; // give other interceptors a chance to handle the timeout + } + + @Override + public void afterCompletion(NativeWebRequest request, Callable task) throws Exception { + closeAfterTimeout(); + } + + private void closeAfterTimeout() { + if (this.timeoutInProgress) { + logger.debug("Closing JPA EntityManager after async request timeout"); + EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager()); + } + } + + // Implementation of DeferredResultProcessingInterceptor methods + + public void beforeConcurrentHandling(NativeWebRequest request, DeferredResult deferredResult) { } + public void preProcess(NativeWebRequest request, DeferredResult deferredResult) { } + public void postProcess(NativeWebRequest request, DeferredResult deferredResult, Object result) { } + + @Override + public boolean handleTimeout(NativeWebRequest request, DeferredResult deferredResult) { + this.timeoutInProgress = true; + return true; // give other interceptors a chance to handle the timeout + } + + @Override + public void afterCompletion(NativeWebRequest request, DeferredResult deferredResult) { + closeAfterTimeout(); + } +} + diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewFilter.java b/spring-orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewFilter.java index 7de9bf342970..673a685a183f 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewFilter.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewFilter.java @@ -34,9 +34,7 @@ import org.springframework.util.StringUtils; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.request.NativeWebRequest; -import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter; -import org.springframework.web.context.request.async.WebAsyncManager; -import org.springframework.web.context.request.async.WebAsyncUtils; +import org.springframework.web.context.request.async.*; import org.springframework.web.context.support.WebApplicationContextUtils; import org.springframework.web.filter.OncePerRequestFilter; @@ -168,7 +166,9 @@ protected void doFilterInternal( EntityManagerHolder emHolder = new EntityManagerHolder(em); TransactionSynchronizationManager.bindResource(emf, emHolder); - asyncManager.registerCallableInterceptor(key, new EntityManagerBindingCallableInterceptor(emf, emHolder)); + AsyncRequestInterceptor interceptor = new AsyncRequestInterceptor(emf, emHolder); + asyncManager.registerCallableInterceptor(key, interceptor); + asyncManager.registerDeferredResultInterceptor(key, interceptor); } catch (PersistenceException ex) { throw new DataAccessResourceFailureException("Could not create JPA EntityManager", ex); @@ -244,38 +244,8 @@ private boolean applyEntityManagerBindingInterceptor(WebAsyncManager asyncManage if (asyncManager.getCallableInterceptor(key) == null) { return false; } - ((EntityManagerBindingCallableInterceptor) asyncManager.getCallableInterceptor(key)).initializeThread(); + ((AsyncRequestInterceptor) asyncManager.getCallableInterceptor(key)).bindSession(); return true; } - /** - * Bind and unbind the {@code EntityManager} to the current thread. - */ - private static class EntityManagerBindingCallableInterceptor extends CallableProcessingInterceptorAdapter { - - private final EntityManagerFactory emFactory; - - private final EntityManagerHolder emHolder; - - - public EntityManagerBindingCallableInterceptor(EntityManagerFactory emFactory, EntityManagerHolder emHolder) { - this.emFactory = emFactory; - this.emHolder = emHolder; - } - - @Override - public void preProcess(NativeWebRequest request, Callable task) { - initializeThread(); - } - - @Override - public void postProcess(NativeWebRequest request, Callable task, Object concurrentResult) { - TransactionSynchronizationManager.unbindResource(this.emFactory); - } - - private void initializeThread() { - TransactionSynchronizationManager.bindResource(this.emFactory, this.emHolder); - } - } - } diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewInterceptor.java b/spring-orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewInterceptor.java index 8c9cf595ca88..2ca81c61d394 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewInterceptor.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewInterceptor.java @@ -97,8 +97,9 @@ public void preHandle(WebRequest request) throws DataAccessException { EntityManagerHolder emHolder = new EntityManagerHolder(em); TransactionSynchronizationManager.bindResource(getEntityManagerFactory(), emHolder); - asyncManager.registerCallableInterceptor(participateAttributeName, - new EntityManagerBindingCallableInterceptor(emHolder)); + AsyncRequestInterceptor interceptor = new AsyncRequestInterceptor(getEntityManagerFactory(), emHolder); + asyncManager.registerCallableInterceptor(participateAttributeName, interceptor); + asyncManager.registerDeferredResultInterceptor(participateAttributeName, interceptor); } catch (PersistenceException ex) { throw new DataAccessResourceFailureException("Could not create JPA EntityManager", ex); @@ -155,36 +156,8 @@ private boolean applyCallableInterceptor(WebAsyncManager asyncManager, String ke if (asyncManager.getCallableInterceptor(key) == null) { return false; } - ((EntityManagerBindingCallableInterceptor) asyncManager.getCallableInterceptor(key)).initializeThread(); + ((AsyncRequestInterceptor) asyncManager.getCallableInterceptor(key)).bindSession(); return true; } - - /** - * Bind and unbind the Hibernate {@code Session} to the current thread. - */ - private class EntityManagerBindingCallableInterceptor extends CallableProcessingInterceptorAdapter { - - private final EntityManagerHolder emHolder; - - - public EntityManagerBindingCallableInterceptor(EntityManagerHolder emHolder) { - this.emHolder = emHolder; - } - - @Override - public void preProcess(NativeWebRequest request, Callable task) { - initializeThread(); - } - - @Override - public void postProcess(NativeWebRequest request, Callable task, Object concurrentResult) { - TransactionSynchronizationManager.unbindResource(getEntityManagerFactory()); - } - - private void initializeThread() { - TransactionSynchronizationManager.bindResource(getEntityManagerFactory(), this.emHolder); - } - } - } diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java b/spring-orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java index 39c437e8cdfb..fe1ab5bc767b 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java @@ -61,6 +61,7 @@ import org.springframework.orm.jpa.SharedEntityManagerCreator; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; /** * BeanPostProcessor that processes {@link javax.persistence.PersistenceUnit} @@ -181,8 +182,8 @@ public class PersistenceAnnotationBeanPostProcessor private transient ListableBeanFactory beanFactory; - private transient final Map, InjectionMetadata> injectionMetadataCache = - new ConcurrentHashMap, InjectionMetadata>(64); + private transient final Map injectionMetadataCache = + new ConcurrentHashMap(64); private final Map extendedEntityManagersToClose = new ConcurrentHashMap(16); @@ -319,7 +320,7 @@ public void setBeanFactory(BeanFactory beanFactory) { public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class beanType, String beanName) { if (beanType != null) { - InjectionMetadata metadata = findPersistenceMetadata(beanType); + InjectionMetadata metadata = findPersistenceMetadata(beanName, beanType); metadata.checkConfigMembers(beanDefinition); } } @@ -335,7 +336,7 @@ public boolean postProcessAfterInstantiation(Object bean, String beanName) throw public PropertyValues postProcessPropertyValues( PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException { - InjectionMetadata metadata = findPersistenceMetadata(bean.getClass()); + InjectionMetadata metadata = findPersistenceMetadata(beanName, bean.getClass()); try { metadata.inject(bean, beanName, pvs); } @@ -359,12 +360,14 @@ public void postProcessBeforeDestruction(Object bean, String beanName) throws Be } - private InjectionMetadata findPersistenceMetadata(final Class clazz) { + private InjectionMetadata findPersistenceMetadata(String beanName, final Class clazz) { // Quick check on the concurrent map first, with minimal locking. - InjectionMetadata metadata = this.injectionMetadataCache.get(clazz); + // Fall back to class name as cache key, for backwards compatibility with custom callers. + String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName()); + InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey); if (metadata == null) { synchronized (this.injectionMetadataCache) { - metadata = this.injectionMetadataCache.get(clazz); + metadata = this.injectionMetadataCache.get(cacheKey); if (metadata == null) { LinkedList elements = new LinkedList(); Class targetClass = clazz; @@ -402,7 +405,7 @@ private InjectionMetadata findPersistenceMetadata(final Class clazz) { while (targetClass != null && targetClass != Object.class); metadata = new InjectionMetadata(clazz, elements); - this.injectionMetadataCache.put(clazz, metadata); + this.injectionMetadataCache.put(cacheKey, metadata); } } } diff --git a/spring-oxm/src/main/java/org/springframework/oxm/castor/CastorMarshaller.java b/spring-oxm/src/main/java/org/springframework/oxm/castor/CastorMarshaller.java index 131b782c876a..f708c4ca9d2c 100644 --- a/spring-oxm/src/main/java/org/springframework/oxm/castor/CastorMarshaller.java +++ b/spring-oxm/src/main/java/org/springframework/oxm/castor/CastorMarshaller.java @@ -170,30 +170,32 @@ public void setMappingLocation(Resource mappingLocation) { } /** - * Set the locations of the Castor XML Mapping files. + * Set the locations of the Castor XML mapping files. */ public void setMappingLocations(Resource[] mappingLocations) { this.mappingLocations = mappingLocations; } /** - * Set the Castor target class. Alternative means of configuring {@code CastorMarshaller} for unmarshalling - * multiple classes include use of mapping files, and specifying packages with Castor descriptor classes. + * Set the Castor target class. + * @see #setTargetPackage + * @see #setMappingLocation */ public void setTargetClass(Class targetClass) { this.targetClasses = new Class[]{targetClass}; } /** - * Set the Castor target classes. Alternative means of configuring {@code CastorMarshaller} for unmarshalling - * multiple classes include use of mapping files, and specifying packages with Castor descriptor classes. + * Set the Castor target classes. + * @see #setTargetPackages + * @see #setMappingLocations */ public void setTargetClasses(Class[] targetClasses) { this.targetClasses = targetClasses; } /** - * Set the names of package with the Castor descriptor classes. + * Set the name of a package with the Castor descriptor classes. */ public void setTargetPackage(String targetPackage) { this.targetPackages = new String[] {targetPackage}; diff --git a/spring-oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java b/spring-oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java index cb2eeeb815e5..bbf775452ef5 100644 --- a/spring-oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java +++ b/spring-oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java @@ -784,6 +784,9 @@ else if (source instanceof StreamSource) { else if (streamSource.getReader() != null) { inputSource = new InputSource(streamSource.getReader()); } + else { + inputSource = new InputSource(streamSource.getSystemId()); + } } try { diff --git a/spring-oxm/src/main/java/org/springframework/oxm/jibx/JibxMarshaller.java b/spring-oxm/src/main/java/org/springframework/oxm/jibx/JibxMarshaller.java index 2ded691c8ba9..1faf73e06b87 100644 --- a/spring-oxm/src/main/java/org/springframework/oxm/jibx/JibxMarshaller.java +++ b/spring-oxm/src/main/java/org/springframework/oxm/jibx/JibxMarshaller.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -84,6 +84,7 @@ public class JibxMarshaller extends AbstractMarshaller implements InitializingBe private static final String DEFAULT_BINDING_NAME = "binding"; + private Class targetClass; private String targetPackage; @@ -106,13 +107,12 @@ public class JibxMarshaller extends AbstractMarshaller implements InitializingBe private IBindingFactory bindingFactory; - private TransformerFactory transformerFactory = TransformerFactory.newInstance(); + private final TransformerFactory transformerFactory = TransformerFactory.newInstance(); /** * Set the target class for this instance. Setting either this property or the * {@link #setTargetPackage(String) targetPackage} property is required. - * *

If this property is set, {@link #setTargetPackage(String) targetPackage} is ignored. */ public void setTargetClass(Class targetClass) { @@ -122,7 +122,6 @@ public void setTargetClass(Class targetClass) { /** * Set the target package for this instance. Setting either this property or the * {@link #setTargetClass(Class) targetClass} property is required. - * *

If {@link #setTargetClass(Class) targetClass} is set, this property is ignored. */ public void setTargetPackage(String targetPackage) { @@ -157,10 +156,9 @@ public void setStandalone(Boolean standalone) { } /** - * Sets the root element name for the DTD declaration written when marshalling. By default, this is - * {@code null} (i.e. no DTD declaration is written). If set to a value, the system ID or public ID also need to - * be set. - * + * Set the root element name for the DTD declaration written when marshalling. + * By default, this is {@code null} (i.e. no DTD declaration is written). + *

If set to a value, the system ID or public ID also need to be set. * @see #setDocTypeSystemId(String) * @see #setDocTypePublicId(String) */ @@ -169,10 +167,9 @@ public void setDocTypeRootElementName(String docTypeRootElementName) { } /** - * Sets the system Id for the DTD declaration written when marshalling. By default, this is - * {@code null}. Only used when the root element also has been set. Set either this property or - * {@code docTypePublicId}, not both. - * + * Set the system id for the DTD declaration written when marshalling. + * By default, this is {@code null}. Only used when the root element also has been set. + *

Set either this property or {@code docTypePublicId}, not both. * @see #setDocTypeRootElementName(String) */ public void setDocTypeSystemId(String docTypeSystemId) { @@ -180,10 +177,9 @@ public void setDocTypeSystemId(String docTypeSystemId) { } /** - * Sets the public Id for the DTD declaration written when marshalling. By default, this is - * {@code null}. Only used when the root element also has been set. Set either this property or - * {@code docTypeSystemId}, not both. - * + * Set the public id for the DTD declaration written when marshalling. + * By default, this is {@code null}. Only used when the root element also has been set. + *

Set either this property or {@code docTypeSystemId}, not both. * @see #setDocTypeRootElementName(String) */ public void setDocTypePublicId(String docTypePublicId) { @@ -191,15 +187,15 @@ public void setDocTypePublicId(String docTypePublicId) { } /** - * Sets the internal subset Id for the DTD declaration written when marshalling. By default, this is - * {@code null}. Only used when the root element also has been set. - * + * Set the internal subset Id for the DTD declaration written when marshalling. + * By default, this is {@code null}. Only used when the root element also has been set. * @see #setDocTypeRootElementName(String) */ public void setDocTypeInternalSubset(String docTypeInternalSubset) { this.docTypeInternalSubset = docTypeInternalSubset; } + public void afterPropertiesSet() throws JiBXException { if (this.targetClass != null) { if (StringUtils.hasLength(this.bindingName)) { @@ -214,16 +210,18 @@ public void afterPropertiesSet() throws JiBXException { } this.bindingFactory = BindingDirectory.getFactory(this.targetClass); } - } else if (this.targetPackage != null) { + } + else if (this.targetPackage != null) { if (!StringUtils.hasLength(bindingName)) { bindingName = DEFAULT_BINDING_NAME; } if (logger.isInfoEnabled()) { - logger.info("Configured for target package [" + targetPackage + "] using binding [" + bindingName + "]"); + logger.info("Configured for target package [" + this.targetPackage + "] using binding [" + this.bindingName + "]"); } - this.bindingFactory = BindingDirectory.getFactory(bindingName, targetPackage); - } else { - throw new IllegalArgumentException("either 'targetClass' or 'targetPackage' is required"); + this.bindingFactory = BindingDirectory.getFactory(this.bindingName, this.targetPackage); + } + else { + throw new IllegalArgumentException("Either 'targetClass' or 'targetPackage' is required"); } } @@ -244,7 +242,7 @@ public boolean supports(Class clazz) { } - // Supported Marshalling + // Supported marshalling @Override protected void marshalOutputStream(Object graph, OutputStream outputStream) @@ -271,8 +269,7 @@ protected void marshalWriter(Object graph, Writer writer) throws XmlMappingExcep } } - private void marshalDocument(IMarshallingContext marshallingContext, Object graph) throws IOException, - JiBXException { + private void marshalDocument(IMarshallingContext marshallingContext, Object graph) throws IOException, JiBXException { if (StringUtils.hasLength(docTypeRootElementName)) { IXMLWriter xmlWriter = marshallingContext.getXmlWriter(); xmlWriter.writeDocType(docTypeRootElementName, docTypeSystemId, docTypePublicId, docTypeInternalSubset); @@ -280,20 +277,8 @@ private void marshalDocument(IMarshallingContext marshallingContext, Object grap marshallingContext.marshalDocument(graph); } - @Override - protected void marshalXmlStreamWriter(Object graph, XMLStreamWriter streamWriter) throws XmlMappingException { - try { - MarshallingContext marshallingContext = (MarshallingContext) createMarshallingContext(); - IXMLWriter xmlWriter = new StAXWriter(marshallingContext.getNamespaces(), streamWriter); - marshallingContext.setXmlWriter(xmlWriter); - marshallingContext.marshalDocument(graph); - } - catch (JiBXException ex) { - throw convertJibxException(ex, false); - } - } - // Unsupported Marshalling + // Unsupported marshalling @Override protected void marshalDomNode(Object graph, Node node) throws XmlMappingException { @@ -307,6 +292,25 @@ protected void marshalDomNode(Object graph, Node node) throws XmlMappingExceptio } } + @Override + protected void marshalXmlEventWriter(Object graph, XMLEventWriter eventWriter) { + XMLStreamWriter streamWriter = StaxUtils.createEventStreamWriter(eventWriter); + marshalXmlStreamWriter(graph, streamWriter); + } + + @Override + protected void marshalXmlStreamWriter(Object graph, XMLStreamWriter streamWriter) throws XmlMappingException { + try { + MarshallingContext marshallingContext = (MarshallingContext) createMarshallingContext(); + IXMLWriter xmlWriter = new StAXWriter(marshallingContext.getNamespaces(), streamWriter); + marshallingContext.setXmlWriter(xmlWriter); + marshallingContext.marshalDocument(graph); + } + catch (JiBXException ex) { + throw convertJibxException(ex, false); + } + } + @Override protected void marshalSaxHandlers(Object graph, ContentHandler contentHandler, LexicalHandler lexicalHandler) throws XmlMappingException { @@ -336,31 +340,27 @@ private void transformAndMarshal(Object graph, Result result) throws IOException } - @Override - protected void marshalXmlEventWriter(Object graph, XMLEventWriter eventWriter) { - XMLStreamWriter streamWriter = StaxUtils.createEventStreamWriter(eventWriter); - marshalXmlStreamWriter(graph, streamWriter); - } - // Unmarshalling @Override - protected Object unmarshalInputStream(InputStream inputStream) throws XmlMappingException, IOException { + protected Object unmarshalXmlEventReader(XMLEventReader eventReader) { try { - IUnmarshallingContext unmarshallingContext = createUnmarshallingContext(); - return unmarshallingContext.unmarshalDocument(inputStream, encoding); + XMLStreamReader streamReader = StaxUtils.createEventStreamReader(eventReader); + return unmarshalXmlStreamReader(streamReader); } - catch (JiBXException ex) { - throw convertJibxException(ex, false); + catch (XMLStreamException ex) { + return new UnmarshallingFailureException("JiBX unmarshalling exception", ex); } } @Override - protected Object unmarshalReader(Reader reader) throws XmlMappingException, IOException { + protected Object unmarshalXmlStreamReader(XMLStreamReader streamReader) { try { - IUnmarshallingContext unmarshallingContext = createUnmarshallingContext(); - return unmarshallingContext.unmarshalDocument(reader); + UnmarshallingContext unmarshallingContext = (UnmarshallingContext) createUnmarshallingContext(); + IXMLReader xmlReader = new StAXReaderWrapper(streamReader, null, true); + unmarshallingContext.setDocument(xmlReader); + return unmarshallingContext.unmarshalElement(); } catch (JiBXException ex) { throw convertJibxException(ex, false); @@ -368,12 +368,10 @@ protected Object unmarshalReader(Reader reader) throws XmlMappingException, IOEx } @Override - protected Object unmarshalXmlStreamReader(XMLStreamReader streamReader) { + protected Object unmarshalInputStream(InputStream inputStream) throws XmlMappingException, IOException { try { - UnmarshallingContext unmarshallingContext = (UnmarshallingContext) createUnmarshallingContext(); - IXMLReader xmlReader = new StAXReaderWrapper(streamReader, null, true); - unmarshallingContext.setDocument(xmlReader); - return unmarshallingContext.unmarshalElement(); + IUnmarshallingContext unmarshallingContext = createUnmarshallingContext(); + return unmarshallingContext.unmarshalDocument(inputStream, encoding); } catch (JiBXException ex) { throw convertJibxException(ex, false); @@ -381,13 +379,13 @@ protected Object unmarshalXmlStreamReader(XMLStreamReader streamReader) { } @Override - protected Object unmarshalXmlEventReader(XMLEventReader eventReader) { + protected Object unmarshalReader(Reader reader) throws XmlMappingException, IOException { try { - XMLStreamReader streamReader = StaxUtils.createEventStreamReader(eventReader); - return unmarshalXmlStreamReader(streamReader); + IUnmarshallingContext unmarshallingContext = createUnmarshallingContext(); + return unmarshallingContext.unmarshalDocument(reader); } - catch (XMLStreamException ex) { - return new UnmarshallingFailureException("JiBX unmarshalling exception", ex); + catch (JiBXException ex) { + throw convertJibxException(ex, false); } } @@ -407,12 +405,13 @@ protected Object unmarshalDomNode(Node node) throws XmlMappingException { @Override protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource) throws XmlMappingException, IOException { + return transformAndUnmarshal(new SAXSource(xmlReader, inputSource)); } private Object transformAndUnmarshal(Source source) throws IOException { try { - Transformer transformer = transformerFactory.newTransformer(); + Transformer transformer = this.transformerFactory.newTransformer(); ByteArrayOutputStream os = new ByteArrayOutputStream(); transformer.transform(source, new StreamResult(os)); ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray()); diff --git a/spring-oxm/src/main/java/org/springframework/oxm/xstream/CatchAllConverter.java b/spring-oxm/src/main/java/org/springframework/oxm/xstream/CatchAllConverter.java new file mode 100644 index 000000000000..94bb1998dce3 --- /dev/null +++ b/spring-oxm/src/main/java/org/springframework/oxm/xstream/CatchAllConverter.java @@ -0,0 +1,64 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.oxm.xstream; + +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +/** + * XStream {@link Converter} that supports all classes, but throws exceptions for + * (un)marshalling. + *

Main purpose of this class is to + * {@linkplain com.thoughtworks.xstream.XStream#registerConverter(com.thoughtworks.xstream.converters.Converter, int) register} + * this converter as a catchall last converter with a + * {@linkplain com.thoughtworks.xstream.XStream#PRIORITY_NORMAL normal} + * or higher priority, in addition to converters that explicitly support the domain + * classes that should be supported. As a result, default XStream converters with lower + * priorities and possible security vulnerabilities do not get invoked. + *

For instance:

+ *
+ * XStreamMarshaller unmarshaller = new XStreamMarshaller();
+ * unmarshaller.getXStream().registerConverter(new MyDomainClassConverter(), XStream.PRIORITY_VERY_HIGH);
+ * unmarshaller.getXStream().registerConverter(new CatchAllConverter(), XStream.PRIORITY_NORMAL);
+ * MyDomainClass o = unmarshaller.unmarshal(source);
+ * 
(i.e. the Web), * as this can result in security vulnerabilities. If you do use the * {@code XStreamMarshaller} to unmarshal external XML, set the - * {@link #setConverters(ConverterMatcher[]) converters} and - * {@link #setSupportedClasses(Class[]) supportedClasses} properties or override the + * {@link #setSupportedClasses(Class[]) supportedClasses} and + * {@link #setConverters(ConverterMatcher[]) converters} properties (possibly using a + * {@link CatchAllConverter} as the last converter in the list) or override the * {@link #customizeXStream(XStream)} method to make sure it only accepts the classes * you want it to support. * diff --git a/spring-oxm/src/test/java/org/springframework/oxm/jaxb/Jaxb2UnmarshallerTests.java b/spring-oxm/src/test/java/org/springframework/oxm/jaxb/Jaxb2UnmarshallerTests.java index 04842a736666..a802392a3979 100644 --- a/spring-oxm/src/test/java/org/springframework/oxm/jaxb/Jaxb2UnmarshallerTests.java +++ b/spring-oxm/src/test/java/org/springframework/oxm/jaxb/Jaxb2UnmarshallerTests.java @@ -16,8 +16,9 @@ package org.springframework.oxm.jaxb; +import java.io.File; +import java.io.IOException; import java.io.StringReader; - import javax.activation.DataHandler; import javax.activation.FileDataSource; import javax.xml.bind.JAXBElement; @@ -26,7 +27,11 @@ import javax.xml.transform.Source; import javax.xml.transform.stream.StreamSource; +import static org.junit.Assert.*; import org.junit.Test; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.mock; + import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.oxm.AbstractUnmarshallerTests; @@ -36,9 +41,6 @@ import org.springframework.oxm.mime.MimeContainer; import org.springframework.util.xml.StaxUtils; -import static org.junit.Assert.*; -import static org.mockito.BDDMockito.*; - /** * @author Arjen Poutsma * @author Biju Kunjummen @@ -134,4 +136,13 @@ public void unmarshalAnXmlReferingToAWrappedXmlElementDecl() throws Exception { "test", airplane.getValue().getName()); } + @Test + public void unmarshalFile() throws IOException { + Resource resource = new ClassPathResource("jaxb2.xml", getClass()); + File file = resource.getFile(); + + Flights f = (Flights) unmarshaller.unmarshal(new StreamSource(file)); + testFlights(f); + } + } diff --git a/spring-oxm/src/test/resources/org/springframework/oxm/jaxb/jaxb2.xml b/spring-oxm/src/test/resources/org/springframework/oxm/jaxb/jaxb2.xml new file mode 100644 index 000000000000..4428a1dc7ab5 --- /dev/null +++ b/spring-oxm/src/test/resources/org/springframework/oxm/jaxb/jaxb2.xml @@ -0,0 +1,5 @@ + + + 42 + + \ No newline at end of file diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/MockMvc.java b/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/MockMvc.java index b18fc3554fa9..a5e15a60e095 100644 --- a/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/MockMvc.java +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/MockMvc.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.List; +import javax.servlet.Filter; import javax.servlet.ServletContext; import org.springframework.beans.Mergeable; @@ -54,7 +55,9 @@ public final class MockMvc { static String MVC_RESULT_ATTRIBUTE = MockMvc.class.getName().concat(".MVC_RESULT_ATTRIBUTE"); - private final MockFilterChain filterChain; + private final TestDispatcherServlet servlet; + + private final Filter[] filters; private final ServletContext servletContext; @@ -69,11 +72,15 @@ public final class MockMvc { * Private constructor, not for direct instantiation. * @see org.springframework.test.web.servlet.setup.MockMvcBuilders */ - MockMvc(MockFilterChain filterChain, ServletContext servletContext) { + MockMvc(TestDispatcherServlet servlet, Filter[] filters, ServletContext servletContext) { + + Assert.notNull(servlet, "DispatcherServlet is required"); + Assert.notNull(filters, "filters cannot be null"); + Assert.noNullElements(filters, "filters cannot contain null values"); Assert.notNull(servletContext, "A ServletContext is required"); - Assert.notNull(filterChain, "A MockFilterChain is required"); - this.filterChain = filterChain; + this.servlet = servlet; + this.filters = filters; this.servletContext = servletContext; } @@ -130,8 +137,8 @@ public ResultActions perform(RequestBuilder requestBuilder) throws Exception { final MvcResult mvcResult = new DefaultMvcResult(request, response); request.setAttribute(MVC_RESULT_ATTRIBUTE, mvcResult); - this.filterChain.reset(); - this.filterChain.doFilter(request, response); + MockFilterChain filterChain = new MockFilterChain(this.servlet, this.filters); + filterChain.doFilter(request, response); applyDefaultResultActions(mvcResult); diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/MockMvcBuilderSupport.java b/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/MockMvcBuilderSupport.java index d2e1054a636c..925c6b0e9e88 100644 --- a/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/MockMvcBuilderSupport.java +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/MockMvcBuilderSupport.java @@ -23,7 +23,6 @@ import javax.servlet.ServletException; import org.springframework.core.NestedRuntimeException; -import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockServletConfig; import org.springframework.web.context.WebApplicationContext; @@ -57,9 +56,7 @@ protected final MockMvc createMockMvc(Filter[] filters, MockServletConfig servle throw new MockMvcBuildException("Failed to initialize TestDispatcherServlet", ex); } - MockFilterChain filterChain = new MockFilterChain(dispatcherServlet, filters); - - MockMvc mockMvc = new MockMvc(filterChain, servletContext); + MockMvc mockMvc = new MockMvc(dispatcherServlet, filters, servletContext); mockMvc.setDefaultRequest(defaultRequestBuilder); mockMvc.setGlobalResultMatchers(globalResultMatchers); mockMvc.setGlobalResultHandlers(globalResultHandlers); diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java b/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java index dbd9ac1d8884..2f9c91d4a697 100644 --- a/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java @@ -581,9 +581,8 @@ public final MockHttpServletRequest buildRequest(ServletContext servletContext) for (Entry> entry : this.uriComponents.getQueryParams().entrySet()) { for (String value : entry.getValue()) { - request.addParameter( - UriUtils.decode(entry.getKey(), "UTF-8"), - UriUtils.decode(value, "UTF-8")); + value = (value != null) ? UriUtils.decode(value, "UTF-8") : null; + request.addParameter(UriUtils.decode(entry.getKey(), "UTF-8"), value); } } } diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilderTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilderTests.java index 9320acd338b8..e33e92260b7b 100644 --- a/spring-test-mvc/src/test/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilderTests.java +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilderTests.java @@ -228,6 +228,19 @@ public void requestParameterFromQueryWithEncoding() throws Exception { assertEquals("bar=baz", request.getParameter("foo")); } + // SPR-11043 + + @Test + public void requestParameterFromQueryNull() throws Exception { + this.builder = new MockHttpServletRequestBuilder(HttpMethod.GET, "/?foo"); + + MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); + Map parameterMap = request.getParameterMap(); + + assertArrayEquals(new String[]{null}, parameterMap.get("foo")); + assertEquals("foo", request.getQueryString()); + } + @Test public void acceptHeader() throws Exception { this.builder.accept(MediaType.TEXT_HTML, MediaType.APPLICATION_XML); diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java b/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java index a43897eb0b6e..f71e447b6c48 100644 --- a/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java @@ -237,11 +237,9 @@ private static void convertContextConfigToConfigAttributesAndAddToList(ContextCo * never {@code null} * @throws IllegalArgumentException if the supplied class is {@code null}; if * neither {@code @ContextConfiguration} nor {@code @ContextHierarchy} is - * present on the supplied class; if a given class in the class hierarchy + * present on the supplied class; or if a given class in the class hierarchy * declares both {@code @ContextConfiguration} and {@code @ContextHierarchy} as - * top-level annotations; or if individual {@code @ContextConfiguration} - * elements within a {@code @ContextHierarchy} declaration on a given class - * in the class hierarchy do not define unique context configuration. + * top-level annotations. * * @since 3.2.2 * @see #buildContextHierarchyMap(Class) @@ -287,17 +285,6 @@ else if (contextHierarchyDeclaredLocally) { convertContextConfigToConfigAttributesAndAddToList(contextConfiguration, declaringClass, configAttributesList); } - - // Check for uniqueness - Set configAttributesSet = new HashSet( - configAttributesList); - if (configAttributesSet.size() != configAttributesList.size()) { - String msg = String.format("The @ContextConfiguration elements configured via " - + "@ContextHierarchy in test class [%s] must define unique contexts to load.", - declaringClass.getName()); - logger.error(msg); - throw new IllegalStateException(msg); - } } else { // This should theoretically actually never happen... @@ -336,6 +323,9 @@ else if (contextHierarchyDeclaredLocally) { * (must not be {@code null}) * @return a map of context configuration attributes for the context hierarchy, * keyed by context hierarchy level name; never {@code null} + * @throws IllegalArgumentException if the lists of context configuration + * attributes for each level in the {@code @ContextHierarchy} do not define + * unique context configuration within the overall hierarchy. * * @since 3.2.2 * @see #resolveContextHierarchyAttributes(Class) @@ -363,6 +353,16 @@ static Map> buildContextHierarchyMa } } + // Check for uniqueness + Set> set = new HashSet>(map.values()); + if (set.size() != map.size()) { + String msg = String.format("The @ContextConfiguration elements configured via " + + "@ContextHierarchy in test class [%s] and its superclasses must " + + "define unique contexts per hierarchy level.", testClass.getName()); + logger.error(msg); + throw new IllegalStateException(msg); + } + return map; } diff --git a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsTests.java index ecedda4ddf9f..9553efc83435 100644 --- a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsTests.java @@ -225,29 +225,6 @@ public void resolveContextHierarchyAttributesForTestClassHierarchyWithMultiLevel assertThat(configAttributesListClassLevel3.get(2).getLocations()[0], equalTo("3-C.xml")); } - private void assertContextConfigEntriesAreNotUnique(Class testClass) { - try { - resolveContextHierarchyAttributes(testClass); - fail("Should throw an IllegalStateException"); - } - catch (IllegalStateException e) { - String msg = String.format( - "The @ContextConfiguration elements configured via @ContextHierarchy in test class [%s] must define unique contexts to load.", - testClass.getName()); - assertEquals(msg, e.getMessage()); - } - } - - @Test - public void resolveContextHierarchyAttributesForSingleTestClassWithMultiLevelContextHierarchyWithEmptyContextConfig() { - assertContextConfigEntriesAreNotUnique(SingleTestClassWithMultiLevelContextHierarchyWithEmptyContextConfig.class); - } - - @Test - public void resolveContextHierarchyAttributesForSingleTestClassWithMultiLevelContextHierarchyWithDuplicatedContextConfig() { - assertContextConfigEntriesAreNotUnique(SingleTestClassWithMultiLevelContextHierarchyWithDuplicatedContextConfig.class); - } - @Test public void buildContextHierarchyMapForTestClassHierarchyWithMultiLevelContextHierarchies() { Map> map = buildContextHierarchyMap(TestClass3WithMultiLevelContextHierarchy.class); @@ -335,6 +312,58 @@ public void buildContextHierarchyMapForTestClassHierarchyWithMultiLevelContextHi assertThat(level3Config.get(0).getLocations()[0], is("2-C.xml")); } + private void assertContextConfigEntriesAreNotUnique(Class testClass) { + try { + buildContextHierarchyMap(testClass); + fail("Should throw an IllegalStateException"); + } + catch (IllegalStateException e) { + String msg = String.format( + "The @ContextConfiguration elements configured via @ContextHierarchy in test class [%s] and its superclasses must define unique contexts per hierarchy level.", + testClass.getName()); + assertEquals(msg, e.getMessage()); + } + } + + @Test + public void buildContextHierarchyMapForSingleTestClassWithMultiLevelContextHierarchyWithEmptyContextConfig() { + assertContextConfigEntriesAreNotUnique(SingleTestClassWithMultiLevelContextHierarchyWithEmptyContextConfig.class); + } + + @Test + public void buildContextHierarchyMapForSingleTestClassWithMultiLevelContextHierarchyWithDuplicatedContextConfig() { + assertContextConfigEntriesAreNotUnique(SingleTestClassWithMultiLevelContextHierarchyWithDuplicatedContextConfig.class); + } + + /** + * Used to reproduce bug reported in https://jira.springsource.org/browse/SPR-10997 + */ + @Test + public void buildContextHierarchyMapForTestClassHierarchyWithMultiLevelContextHierarchiesAndOverriddenInitializers() { + Map> map = buildContextHierarchyMap(TestClass2WithMultiLevelContextHierarchyWithOverriddenInitializers.class); + + assertThat(map.size(), is(2)); + assertThat(map.keySet(), hasItems("alpha", "beta")); + + List alphaConfig = map.get("alpha"); + assertThat(alphaConfig.size(), is(2)); + assertThat(alphaConfig.get(0).getLocations().length, is(1)); + assertThat(alphaConfig.get(0).getLocations()[0], is("1-A.xml")); + assertThat(alphaConfig.get(0).getInitializers().length, is(0)); + assertThat(alphaConfig.get(1).getLocations().length, is(0)); + assertThat(alphaConfig.get(1).getInitializers().length, is(1)); + assertEquals(DummyApplicationContextInitializer.class, alphaConfig.get(1).getInitializers()[0]); + + List betaConfig = map.get("beta"); + assertThat(betaConfig.size(), is(2)); + assertThat(betaConfig.get(0).getLocations().length, is(1)); + assertThat(betaConfig.get(0).getLocations()[0], is("1-B.xml")); + assertThat(betaConfig.get(0).getInitializers().length, is(0)); + assertThat(betaConfig.get(1).getLocations().length, is(0)); + assertThat(betaConfig.get(1).getInitializers().length, is(1)); + assertEquals(DummyApplicationContextInitializer.class, betaConfig.get(1).getInitializers()[0]); + } + @Test(expected = IllegalStateException.class) public void resolveConfigAttributesWithConflictingLocations() { resolveContextConfigurationAttributes(ConflictingLocations.class); @@ -843,4 +872,40 @@ private static class SingleTestClassWithMultiLevelContextHierarchyWithEmptyConte private static class SingleTestClassWithMultiLevelContextHierarchyWithDuplicatedContextConfig { } + /** + * Used to reproduce bug reported in https://jira.springsource.org/browse/SPR-10997 + */ + @ContextHierarchy({// + // + @ContextConfiguration(name = "alpha", locations = "1-A.xml"),// + @ContextConfiguration(name = "beta", locations = "1-B.xml") // + }) + private static class TestClass1WithMultiLevelContextHierarchyWithUniqueContextConfig { + } + + /** + * Used to reproduce bug reported in https://jira.springsource.org/browse/SPR-10997 + */ + @ContextHierarchy({// + // + @ContextConfiguration(name = "alpha", initializers = DummyApplicationContextInitializer.class),// + @ContextConfiguration(name = "beta", initializers = DummyApplicationContextInitializer.class) // + }) + private static class TestClass2WithMultiLevelContextHierarchyWithOverriddenInitializers extends + TestClass1WithMultiLevelContextHierarchyWithUniqueContextConfig { + } + + /** + * Used to reproduce bug reported in https://jira.springsource.org/browse/SPR-10997 + */ + private static class DummyApplicationContextInitializer implements + ApplicationContextInitializer { + + @Override + public void initialize(ConfigurableApplicationContext applicationContext) { + /* no-op */ + } + + } + } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/AbstractTransactionalSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/AbstractTransactionalSpringRunnerTests.java index 5ada9b5c618b..1469cc354966 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/AbstractTransactionalSpringRunnerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/AbstractTransactionalSpringRunnerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,7 +16,7 @@ package org.springframework.test.context.junit4; -import org.springframework.jdbc.BadSqlGrammarException; +import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.simple.SimpleJdbcTemplate; import org.springframework.test.annotation.NotTransactional; import org.springframework.test.context.ContextConfiguration; @@ -54,7 +54,7 @@ protected static void createPersonTable(SimpleJdbcTemplate simpleJdbcTemplate) { try { simpleJdbcTemplate.update("CREATE TABLE person (name VARCHAR(20) NOT NULL, PRIMARY KEY(name))"); } - catch (BadSqlGrammarException bsge) { + catch (DataAccessException dae) { // ignore } } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/ConcreteTransactionalJUnit4SpringContextTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/ConcreteTransactionalJUnit4SpringContextTests.java index c88aed37d126..bd7ce8116e33 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/ConcreteTransactionalJUnit4SpringContextTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/ConcreteTransactionalJUnit4SpringContextTests.java @@ -16,31 +16,28 @@ package org.springframework.test.context.junit4; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.springframework.test.transaction.TransactionTestUtils.assertInTransaction; -import static org.springframework.test.transaction.TransactionTestUtils.inTransaction; - import javax.annotation.Resource; import javax.sql.DataSource; import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.springframework.tests.sample.beans.Employee; -import org.springframework.tests.sample.beans.Pet; + import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.jdbc.BadSqlGrammarException; +import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.simple.SimpleJdbcTemplate; import org.springframework.test.annotation.NotTransactional; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.transaction.AfterTransaction; import org.springframework.test.context.transaction.BeforeTransaction; import org.springframework.test.jdbc.SimpleJdbcTestUtils; +import org.springframework.tests.sample.beans.Employee; +import org.springframework.tests.sample.beans.Pet; + +import static org.junit.Assert.*; +import static org.springframework.test.transaction.TransactionTestUtils.*; /** * Combined integration test for {@link AbstractJUnit4SpringContextTests} and @@ -87,7 +84,7 @@ protected static void createPersonTable(final SimpleJdbcTemplate simpleJdbcTempl try { simpleJdbcTemplate.update("CREATE TABLE person (name VARCHAR(20) NOT NULL, PRIMARY KEY(name))"); } - catch (final BadSqlGrammarException bsge) { + catch (DataAccessException dae) { /* ignore */ } } diff --git a/spring-tx/src/main/java/org/springframework/jca/work/glassfish/GlassFishWorkManagerTaskExecutor.java b/spring-tx/src/main/java/org/springframework/jca/work/glassfish/GlassFishWorkManagerTaskExecutor.java index 431884514db0..a026b1114542 100644 --- a/spring-tx/src/main/java/org/springframework/jca/work/glassfish/GlassFishWorkManagerTaskExecutor.java +++ b/spring-tx/src/main/java/org/springframework/jca/work/glassfish/GlassFishWorkManagerTaskExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,12 +43,12 @@ public class GlassFishWorkManagerTaskExecutor extends WorkManagerTaskExecutor { public GlassFishWorkManagerTaskExecutor() { try { - Class wmf = getClass().getClassLoader().loadClass(WORK_MANAGER_FACTORY_CLASS); - this.getWorkManagerMethod = wmf.getMethod("getWorkManager", new Class[] {String.class}); + Class wmf = getClass().getClassLoader().loadClass(WORK_MANAGER_FACTORY_CLASS); + this.getWorkManagerMethod = wmf.getMethod("getWorkManager", String.class); } catch (Exception ex) { throw new IllegalStateException( - "Could not initialize GlassFishWorkManagerTaskExecutor because GlassFish API is not available: " + ex); + "Could not initialize GlassFishWorkManagerTaskExecutor because GlassFish API is not available", ex); } } diff --git a/spring-tx/src/main/java/org/springframework/jca/work/jboss/JBossWorkManagerTaskExecutor.java b/spring-tx/src/main/java/org/springframework/jca/work/jboss/JBossWorkManagerTaskExecutor.java index 5abb09231b88..cc605665018f 100644 --- a/spring-tx/src/main/java/org/springframework/jca/work/jboss/JBossWorkManagerTaskExecutor.java +++ b/spring-tx/src/main/java/org/springframework/jca/work/jboss/JBossWorkManagerTaskExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2013 the original author 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,10 @@ * {@link org.springframework.scheduling.commonj.WorkManagerTaskExecutor} * adapter for WebLogic and WebSphere. * + *

Note: This class does not work on JBoss 7 or higher. + * There is no known immediate replacement, since JBoss does not want + * its JCA WorkManager to be exposed anymore. + * * @author Juergen Hoeller * @since 2.5.2 * @see org.jboss.resource.work.JBossWorkManagerMBean diff --git a/spring-tx/src/main/java/org/springframework/jca/work/jboss/JBossWorkManagerUtils.java b/spring-tx/src/main/java/org/springframework/jca/work/jboss/JBossWorkManagerUtils.java index 7c2757b600ce..c2cddcd79883 100644 --- a/spring-tx/src/main/java/org/springframework/jca/work/jboss/JBossWorkManagerUtils.java +++ b/spring-tx/src/main/java/org/springframework/jca/work/jboss/JBossWorkManagerUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2013 the original author 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,7 +69,7 @@ public static WorkManager getWorkManager(String mbeanName) { } catch (Exception ex) { throw new IllegalStateException( - "Could not initialize JBossWorkManagerTaskExecutor because JBoss API is not available: " + ex); + "Could not initialize JBossWorkManagerTaskExecutor because JBoss API is not available", ex); } } diff --git a/spring-web/src/main/java/org/springframework/http/converter/ResourceHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/ResourceHttpMessageConverter.java index 81733712488c..386ca52e36e9 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/ResourceHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/ResourceHttpMessageConverter.java @@ -138,6 +138,9 @@ private static FileTypeMap loadFileTypeMapFromContextSupportModule() { } public static MediaType getMediaType(Resource resource) { + if(resource.getFilename() == null) { + return null; + } String mediaType = fileTypeMap.getContentType(resource.getFilename()); return (StringUtils.hasText(mediaType) ? MediaType.parseMediaType(mediaType) : null); } diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverter.java index daa0c5d40644..3ed2053332ff 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverter.java @@ -21,14 +21,6 @@ import java.nio.charset.Charset; import java.util.List; -import com.fasterxml.jackson.core.JsonEncoding; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; -import com.fasterxml.jackson.databind.JavaType; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; - import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; @@ -38,6 +30,14 @@ import org.springframework.http.converter.HttpMessageNotWritableException; import org.springframework.util.Assert; +import com.fasterxml.jackson.core.JsonEncoding; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; + /** * Implementation of {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverter} * that can read and write JSON using Jackson 2's {@link ObjectMapper}. @@ -199,7 +199,7 @@ protected void writeInternal(Object object, HttpOutputMessage outputMessage) try { if (this.jsonPrefix != null) { - jsonGenerator.writeRaw("{} && "); + jsonGenerator.writeRaw(this.jsonPrefix); } this.objectMapper.writeValue(jsonGenerator, object); } diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/MappingJacksonHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/json/MappingJacksonHttpMessageConverter.java index ea4ad64dc4b7..da63cd5ea486 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/json/MappingJacksonHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/json/MappingJacksonHttpMessageConverter.java @@ -28,7 +28,6 @@ import org.codehaus.jackson.map.SerializationConfig; import org.codehaus.jackson.map.type.TypeFactory; import org.codehaus.jackson.type.JavaType; - import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; @@ -196,7 +195,7 @@ protected void writeInternal(Object object, HttpOutputMessage outputMessage) try { if (this.jsonPrefix != null) { - jsonGenerator.writeRaw("{} && "); + jsonGenerator.writeRaw(this.jsonPrefix); } this.objectMapper.writeValue(jsonGenerator, object); } diff --git a/spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java index e94950326322..15b7d8e781f9 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2013 the original author 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,73 +19,147 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMResult; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.sax.SAXSource; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; +import org.w3c.dom.Document; import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.XMLReaderFactory; import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpInputMessage; +import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; +import org.springframework.http.converter.AbstractHttpMessageConverter; import org.springframework.http.converter.HttpMessageConversionException; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; +import org.springframework.util.StreamUtils; +import org.springframework.util.xml.StaxUtils; /** - * Implementation of {@link org.springframework.http.converter.HttpMessageConverter} that can read and write {@link - * Source} objects. + * Implementation of {@link org.springframework.http.converter.HttpMessageConverter} + * that can read and write {@link Source} objects. * * @author Arjen Poutsma * @since 3.0 */ -public class SourceHttpMessageConverter extends AbstractXmlHttpMessageConverter { +public class SourceHttpMessageConverter extends AbstractHttpMessageConverter { - @Override - public boolean supports(Class clazz) { - return DOMSource.class.equals(clazz) || SAXSource.class.equals(clazz) || StreamSource.class.equals(clazz) || - Source.class.equals(clazz); - } + private final TransformerFactory transformerFactory = TransformerFactory.newInstance(); - @Override - @SuppressWarnings("unchecked") - protected T readFromSource(Class clazz, HttpHeaders headers, Source source) throws IOException { - try { - if (DOMSource.class.equals(clazz)) { - DOMResult domResult = new DOMResult(); - transform(source, domResult); - return (T) new DOMSource(domResult.getNode()); - } - else if (SAXSource.class.equals(clazz)) { - ByteArrayInputStream bis = transformToByteArrayInputStream(source); - return (T) new SAXSource(new InputSource(bis)); - } - else if (StreamSource.class.equals(clazz) || Source.class.equals(clazz)) { - ByteArrayInputStream bis = transformToByteArrayInputStream(source); - return (T) new StreamSource(bis); - } - else { - throw new HttpMessageConversionException("Could not read class [" + clazz + - "]. Only DOMSource, SAXSource, and StreamSource are supported."); - } - } - catch (TransformerException ex) { - throw new HttpMessageNotReadableException("Could not transform from [" + source + "] to [" + clazz + "]", - ex); - } - } + private boolean processExternalEntities = false; + + /** + * Sets the {@link #setSupportedMediaTypes(java.util.List) supportedMediaTypes} + * to {@code text/xml} and {@code application/xml}, and {@code application/*-xml}. + */ + public SourceHttpMessageConverter() { + super(MediaType.APPLICATION_XML, MediaType.TEXT_XML, new MediaType("application", "*+xml")); + } + + + /** + * Indicates whether external XML entities are processed when converting + * to a Source. + *

Default is {@code false}, meaning that external entities are not resolved. + */ + public void setProcessExternalEntities(boolean processExternalEntities) { + this.processExternalEntities = processExternalEntities; + } - private ByteArrayInputStream transformToByteArrayInputStream(Source source) throws TransformerException { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - transform(source, new StreamResult(bos)); - return new ByteArrayInputStream(bos.toByteArray()); + @Override + public boolean supports(Class clazz) { + return DOMSource.class.equals(clazz) || SAXSource.class.equals(clazz) + || StreamSource.class.equals(clazz) || Source.class.equals(clazz); } + @Override + protected T readInternal(Class clazz, HttpInputMessage inputMessage) + throws IOException, HttpMessageNotReadableException { + + InputStream body = inputMessage.getBody(); + if (DOMSource.class.equals(clazz)) { + return (T) readDOMSource(body); + } + else if (StaxUtils.isStaxSourceClass(clazz)) { + return (T) readStAXSource(body); + } + else if (SAXSource.class.equals(clazz)) { + return (T) readSAXSource(body); + } + else if (StreamSource.class.equals(clazz) || Source.class.equals(clazz)) { + return (T) readStreamSource(body); + } + else { + throw new HttpMessageConversionException("Could not read class [" + clazz + + "]. Only DOMSource, SAXSource, and StreamSource are supported."); + } + } + + private DOMSource readDOMSource(InputStream body) throws IOException { + try { + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + documentBuilderFactory.setNamespaceAware(true); + documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", processExternalEntities); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + Document document = documentBuilder.parse(body); + return new DOMSource(document); + } + catch (ParserConfigurationException ex) { + throw new HttpMessageNotReadableException("Could not set feature: " + ex.getMessage(), ex); + } + catch (SAXException ex) { + throw new HttpMessageNotReadableException("Could not parse document: " + ex.getMessage(), ex); + } + } + + private SAXSource readSAXSource(InputStream body) throws IOException { + try { + XMLReader reader = XMLReaderFactory.createXMLReader(); + reader.setFeature("http://xml.org/sax/features/external-general-entities", processExternalEntities); + byte[] bytes = StreamUtils.copyToByteArray(body); + return new SAXSource(reader, new InputSource(new ByteArrayInputStream(bytes))); + } + catch (SAXException ex) { + throw new HttpMessageNotReadableException("Could not parse document: " + ex.getMessage(), ex); + } + } + + private Source readStAXSource(InputStream body) { + try { + XMLInputFactory inputFactory = XMLInputFactory.newFactory(); + inputFactory.setProperty("javax.xml.stream.isSupportingExternalEntities", processExternalEntities); + XMLStreamReader streamReader = inputFactory.createXMLStreamReader(body); + return StaxUtils.createStaxSource(streamReader); + } + catch (XMLStreamException ex) { + throw new HttpMessageNotReadableException("Could not parse document: " + ex.getMessage(), ex); + } + } + + private StreamSource readStreamSource(InputStream body) throws IOException { + byte[] bytes = StreamUtils.copyToByteArray(body); + return new StreamSource(new ByteArrayInputStream(bytes)); + } + @Override protected Long getContentLength(T t, MediaType contentType) { if (t instanceof DOMSource) { @@ -101,17 +175,24 @@ protected Long getContentLength(T t, MediaType contentType) { return null; } - @Override - protected void writeToResult(T t, HttpHeaders headers, Result result) throws IOException { + @Override + protected void writeInternal(T t, HttpOutputMessage outputMessage) + throws IOException, HttpMessageNotWritableException { try { + Result result = new StreamResult(outputMessage.getBody()); transform(t, result); } catch (TransformerException ex) { - throw new HttpMessageNotWritableException("Could not transform [" + t + "] to [" + result + "]", ex); + throw new HttpMessageNotWritableException("Could not transform [" + t + "] to output message", ex); } } - private static class CountingOutputStream extends OutputStream { + private void transform(Source source, Result result) throws TransformerException { + this.transformerFactory.newTransformer().transform(source, result); + } + + + private static class CountingOutputStream extends OutputStream { private long count = 0; diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResultProcessingInterceptorAdapter.java b/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResultProcessingInterceptorAdapter.java index 774761bdb656..9a7387486a1a 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResultProcessingInterceptorAdapter.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResultProcessingInterceptorAdapter.java @@ -30,7 +30,9 @@ public abstract class DeferredResultProcessingInterceptorAdapter implements Defe /** * This implementation is empty. */ - public void beforeConcurrentHandling(NativeWebRequest request, DeferredResult deferredResult) throws Exception { + @Override + public void beforeConcurrentHandling(NativeWebRequest request, DeferredResult deferredResult) + throws Exception { } /** @@ -47,7 +49,8 @@ public void postProcess(NativeWebRequest request, DeferredResult deferred } /** - * This implementation returns {@code true} by default. + * This implementation returns {@code true} by default allowing other interceptors + * to be given a chance to handle the timeout. */ public boolean handleTimeout(NativeWebRequest request, DeferredResult deferredResult) throws Exception { return true; diff --git a/spring-web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java b/spring-web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java index 8f672aae4197..5a0837a556d8 100644 --- a/spring-web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java +++ b/spring-web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,10 @@ package org.springframework.web.context.support; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; + import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.annotation.AnnotatedBeanDefinitionReader; @@ -23,7 +27,6 @@ import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; import org.springframework.context.annotation.ScopeMetadataResolver; import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import org.springframework.web.context.ContextLoader; @@ -64,7 +67,7 @@ * {@linkplain ContextLoader#CONTEXT_INITIALIZER_CLASSES_PARAM "contextInitializerClasses"} * context-param / init-param. In such cases, users should favor the {@link #refresh()} * and {@link #scan(String...)} methods over the {@link #setConfigLocation(String)} - * method, which is primarily for use by {@code ContextLoader} + * method, which is primarily for use by {@code ContextLoader}. * *

Note: In case of multiple {@code @Configuration} classes, later {@code @Bean} * definitions will override ones defined in earlier loaded files. This can be leveraged @@ -77,64 +80,59 @@ */ public class AnnotationConfigWebApplicationContext extends AbstractRefreshableWebApplicationContext { - private Class[] annotatedClasses; - - private String[] basePackages; - private BeanNameGenerator beanNameGenerator; private ScopeMetadataResolver scopeMetadataResolver; + private final Set> annotatedClasses = new LinkedHashSet>(); + + private final Set basePackages = new LinkedHashSet(); + + /** - * {@inheritDoc} - *

This implementation accepts delimited values in the form of fully-qualified - * class names, (typically of {@code Configuration} classes) or fully-qualified - * packages to scan for annotated classes. During {@link #loadBeanDefinitions}, these - * locations will be processed in their given order, first attempting to load each - * value as a class. If class loading fails (i.e. a {@code ClassNotFoundException} - * occurs), the value is assumed to be a package and scanning is attempted. - *

Note that this method exists primarily for compatibility with Spring's - * {@link org.springframework.web.context.ContextLoader} and that if this application - * context is being configured through an - * {@link org.springframework.context.ApplicationContextInitializer}, use of the - * {@link #register} and {@link #scan} methods are preferred. - * @see #register(Class...) - * @see #scan(String...) - * @see #setConfigLocations(String[]) - * @see #loadBeanDefinitions(DefaultListableBeanFactory) + * Set a custom {@link BeanNameGenerator} for use with {@link AnnotatedBeanDefinitionReader} + * and/or {@link ClassPathBeanDefinitionScanner}. + *

Default is {@link org.springframework.context.annotation.AnnotationBeanNameGenerator}. + * @see AnnotatedBeanDefinitionReader#setBeanNameGenerator + * @see ClassPathBeanDefinitionScanner#setBeanNameGenerator */ - @Override - public void setConfigLocation(String location) { - super.setConfigLocation(location); + public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) { + this.beanNameGenerator = beanNameGenerator; } /** - * {@inheritDoc} - *

This implementation accepts individual location values as fully-qualified class - * names (typically {@code @Configuration} classes) or fully-qualified packages to - * scan. During {@link #loadBeanDefinitions}, these locations will be processed in - * order, first attempting to load values as a class, and upon class loading failure - * the value is assumed to be a package to be scanned. - *

Note that this method exists primarily for compatibility with Spring's - * {@link org.springframework.web.context.ContextLoader} and that if this application - * context is being configured through an - * {@link org.springframework.context.ApplicationContextInitializer}, use of the - * {@link #register} and {@link #scan} methods are preferred. - * @see #scan(String...) - * @see #register(Class...) - * @see #setConfigLocation(String) - * @see #loadBeanDefinitions(DefaultListableBeanFactory) + * Return the custom {@link BeanNameGenerator} for use with {@link AnnotatedBeanDefinitionReader} + * and/or {@link ClassPathBeanDefinitionScanner}, if any. */ - @Override - public void setConfigLocations(String[] locations) { - super.setConfigLocations(locations); + protected BeanNameGenerator getBeanNameGenerator() { + return this.beanNameGenerator; } + /** + * Set a custom {@link ScopeMetadataResolver} for use with {@link AnnotatedBeanDefinitionReader} + * and/or {@link ClassPathBeanDefinitionScanner}. + *

Default is an {@link org.springframework.context.annotation.AnnotationScopeMetadataResolver}. + * @see AnnotatedBeanDefinitionReader#setScopeMetadataResolver + * @see ClassPathBeanDefinitionScanner#setScopeMetadataResolver + */ + public void setScopeMetadataResolver(ScopeMetadataResolver scopeMetadataResolver) { + this.scopeMetadataResolver = scopeMetadataResolver; + } + + /** + * Return the custom {@link ScopeMetadataResolver} for use with {@link AnnotatedBeanDefinitionReader} + * and/or {@link ClassPathBeanDefinitionScanner}, if any. + */ + protected ScopeMetadataResolver getScopeMetadataResolver() { + return this.scopeMetadataResolver; + } + + /** * Register one or more annotated classes to be processed. - * Note that {@link #refresh()} must be called in order for the context to fully - * process the new class. - *

Calls to {@link #register} are idempotent; adding the same + * Note that {@link #refresh()} must be called in order for the context + * to fully process the new class. + *

Calls to {@code register} are idempotent; adding the same * annotated class more than once has no additional effect. * @param annotatedClasses one or more annotated classes, * e.g. {@link org.springframework.context.annotation.Configuration @Configuration} classes @@ -145,7 +143,7 @@ public void setConfigLocations(String[] locations) { */ public void register(Class... annotatedClasses) { Assert.notEmpty(annotatedClasses, "At least one annotated class must be specified"); - this.annotatedClasses = annotatedClasses; + this.annotatedClasses.addAll(Arrays.asList(annotatedClasses)); } /** @@ -160,9 +158,10 @@ public void register(Class... annotatedClasses) { */ public void scan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); - this.basePackages = basePackages; + this.basePackages.addAll(Arrays.asList(basePackages)); } + /** * Register a {@link org.springframework.beans.factory.config.BeanDefinition} for * any classes specified by {@link #register(Class...)} and scan any packages @@ -205,20 +204,20 @@ protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) { scanner.setScopeMetadataResolver(scopeMetadataResolver); } - if (!ObjectUtils.isEmpty(this.annotatedClasses)) { + if (!this.annotatedClasses.isEmpty()) { if (logger.isInfoEnabled()) { logger.info("Registering annotated classes: [" + - StringUtils.arrayToCommaDelimitedString(this.annotatedClasses) + "]"); + StringUtils.collectionToCommaDelimitedString(this.annotatedClasses) + "]"); } - reader.register(this.annotatedClasses); + reader.register(this.annotatedClasses.toArray(new Class[this.annotatedClasses.size()])); } - if (!ObjectUtils.isEmpty(this.basePackages)) { + if (!this.basePackages.isEmpty()) { if (logger.isInfoEnabled()) { logger.info("Scanning base packages: [" + - StringUtils.arrayToCommaDelimitedString(this.basePackages) + "]"); + StringUtils.collectionToCommaDelimitedString(this.basePackages) + "]"); } - scanner.scan(this.basePackages); + scanner.scan(this.basePackages.toArray(new String[this.basePackages.size()])); } String[] configLocations = getConfigLocations(); @@ -250,37 +249,4 @@ protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) { } } - public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) { - this.beanNameGenerator = beanNameGenerator; - } - - /** - * Provide a custom {@link BeanNameGenerator} for use with {@link AnnotatedBeanDefinitionReader} - * and/or {@link ClassPathBeanDefinitionScanner}, if any. - *

Default is {@link org.springframework.context.annotation.AnnotationBeanNameGenerator}. - * @see AnnotatedBeanDefinitionReader#setBeanNameGenerator - * @see ClassPathBeanDefinitionScanner#setBeanNameGenerator - */ - protected BeanNameGenerator getBeanNameGenerator() { - return this.beanNameGenerator; - } - - /** - * Set the {@link ScopeMetadataResolver} to use for detected bean classes. - *

The default is an {@link org.springframework.context.annotation.AnnotationScopeMetadataResolver}. - */ - public void setScopeMetadataResolver(ScopeMetadataResolver scopeMetadataResolver) { - this.scopeMetadataResolver = scopeMetadataResolver; - } - - /** - * Provide a custom {@link ScopeMetadataResolver} for use with {@link AnnotatedBeanDefinitionReader} - * and/or {@link ClassPathBeanDefinitionScanner}, if any. - *

Default is {@link org.springframework.context.annotation.AnnotationScopeMetadataResolver}. - * @see AnnotatedBeanDefinitionReader#setScopeMetadataResolver - * @see ClassPathBeanDefinitionScanner#setScopeMetadataResolver - */ - protected ScopeMetadataResolver getScopeMetadataResolver() { - return this.scopeMetadataResolver; - } } diff --git a/spring-web/src/main/java/org/springframework/web/util/CookieGenerator.java b/spring-web/src/main/java/org/springframework/web/util/CookieGenerator.java index c9662a365bf5..c79a59e6a219 100644 --- a/spring-web/src/main/java/org/springframework/web/util/CookieGenerator.java +++ b/spring-web/src/main/java/org/springframework/web/util/CookieGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,13 +22,15 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.util.Assert; + /** * Helper class for cookie generation, carrying cookie descriptor settings * as bean properties and being able to add and remove cookie to/from a * given response. * *

Can serve as base class for components that generate specific cookies, - * like CookieLocaleResolcer and CookieThemeResolver. + * such as CookieLocaleResolver and CookieThemeResolver. * * @author Juergen Hoeller * @since 1.1.4 @@ -177,6 +179,7 @@ public boolean isCookieHttpOnly() { * @see #setCookieMaxAge */ public void addCookie(HttpServletResponse response, String cookieValue) { + Assert.notNull(response, "HttpServletResponse must not be null"); Cookie cookie = createCookie(cookieValue); Integer maxAge = getCookieMaxAge(); if (maxAge != null) { @@ -204,6 +207,7 @@ public void addCookie(HttpServletResponse response, String cookieValue) { * @see #setCookiePath */ public void removeCookie(HttpServletResponse response) { + Assert.notNull(response, "HttpServletResponse must not be null"); Cookie cookie = createCookie(""); cookie.setMaxAge(0); response.addCookie(cookie); diff --git a/spring-web/src/main/java/org/springframework/web/util/WebUtils.java b/spring-web/src/main/java/org/springframework/web/util/WebUtils.java index c7acf0950297..0008703fde65 100644 --- a/spring-web/src/main/java/org/springframework/web/util/WebUtils.java +++ b/spring-web/src/main/java/org/springframework/web/util/WebUtils.java @@ -22,7 +22,6 @@ import java.util.Map; import java.util.StringTokenizer; import java.util.TreeMap; - import javax.servlet.ServletContext; import javax.servlet.ServletRequest; import javax.servlet.ServletRequestWrapper; @@ -199,7 +198,6 @@ public static Boolean getDefaultHtmlEscape(ServletContext servletContext) { if (servletContext == null) { return null; } - Assert.notNull(servletContext, "ServletContext must not be null"); String param = servletContext.getInitParameter(HTML_ESCAPE_CONTEXT_PARAM); return (StringUtils.hasText(param)? Boolean.valueOf(param) : null); } diff --git a/spring-web/src/test/java/org/springframework/http/converter/ResourceHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/ResourceHttpMessageConverterTests.java index 8d8f38516bf7..f8c60a94b4b6 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/ResourceHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/ResourceHttpMessageConverterTests.java @@ -17,11 +17,11 @@ package org.springframework.http.converter; import java.io.IOException; +import java.util.Arrays; -import static org.junit.Assert.*; import org.junit.Before; import org.junit.Test; - +import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.http.MediaType; @@ -29,6 +29,8 @@ import org.springframework.http.MockHttpOutputMessage; import org.springframework.util.FileCopyUtils; +import static org.junit.Assert.*; + /** * @author Arjen Poutsma */ @@ -70,4 +72,15 @@ public void write() throws IOException { assertEquals("Invalid content-length", body.getFile().length(), outputMessage.getHeaders().getContentLength()); } + // SPR-10848 + + @Test + public void writeByteArrayNullMediaType() throws IOException { + MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); + byte[] byteArray = {1, 2, 3}; + Resource body = new ByteArrayResource(byteArray); + converter.write(body, null, outputMessage); + assertTrue(Arrays.equals(byteArray, outputMessage.getBodyAsBytes())); + } + } diff --git a/spring-web/src/test/java/org/springframework/http/converter/json/AbstractMappingJacksonHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/json/AbstractMappingJacksonHttpMessageConverterTests.java index da72170f5557..e36244bfb8f6 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/json/AbstractMappingJacksonHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/json/AbstractMappingJacksonHttpMessageConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,6 @@ package org.springframework.http.converter.json; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - import java.io.IOException; import java.nio.charset.Charset; import java.util.ArrayList; @@ -35,6 +31,8 @@ import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageNotReadableException; +import static org.junit.Assert.*; + /** * Base class for Jackson and Jackson 2 converter tests. * diff --git a/spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java index a755a7458f5b..03aad0272a8e 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,16 +22,17 @@ import java.util.ArrayList; import java.util.List; -import com.fasterxml.jackson.databind.JavaType; -import com.fasterxml.jackson.databind.ObjectMapper; -import static org.junit.Assert.*; import org.junit.Test; - import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.MediaType; import org.springframework.http.MockHttpInputMessage; import org.springframework.http.MockHttpOutputMessage; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; + +import static org.junit.Assert.*; + /** * Jackson 2.x converter tests. * @@ -112,6 +113,24 @@ public void prettyPrint() throws Exception { assertEquals("{" + NEWLINE_SYSTEM_PROPERTY + " \"name\" : \"Jason\"" + NEWLINE_SYSTEM_PROPERTY + "}", result); } + @Test + public void prefixJson() throws Exception { + MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); + getConverter().setPrefixJson(true); + getConverter().writeInternal("foo", outputMessage); + + assertEquals("{} && \"foo\"", outputMessage.getBodyAsString(Charset.forName("UTF-8"))); + } + + @Test + public void prefixJsonCustom() throws Exception { + MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); + getConverter().setJsonPrefix(")]}',"); + getConverter().writeInternal("foo", outputMessage); + + assertEquals(")]}',\"foo\"", outputMessage.getBodyAsString(Charset.forName("UTF-8"))); + } + public static class PrettyPrintBean { diff --git a/spring-web/src/test/java/org/springframework/http/converter/json/MappingJacksonHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/json/MappingJacksonHttpMessageConverterTests.java index 424bb885c8c9..08d20668b993 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/json/MappingJacksonHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/json/MappingJacksonHttpMessageConverterTests.java @@ -24,14 +24,14 @@ import org.codehaus.jackson.map.type.TypeFactory; import org.codehaus.jackson.type.JavaType; -import static org.junit.Assert.*; import org.junit.Test; - import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.MediaType; import org.springframework.http.MockHttpInputMessage; import org.springframework.http.MockHttpOutputMessage; +import static org.junit.Assert.*; + /** * Jackson 1.x converter tests. * @@ -109,6 +109,24 @@ public void prettyPrint() throws Exception { assertEquals("{" + NEWLINE_SYSTEM_PROPERTY + " \"name\" : \"Jason\"" + NEWLINE_SYSTEM_PROPERTY + "}", result); } + @Test + public void prefixJson() throws Exception { + MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); + getConverter().setPrefixJson(true); + getConverter().writeInternal("foo", outputMessage); + + assertEquals("{} && \"foo\"", outputMessage.getBodyAsString(Charset.forName("UTF-8"))); + } + + @Test + public void prefixJsonCustom() throws Exception { + MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); + getConverter().setJsonPrefix(")]}',"); + getConverter().writeInternal("foo", outputMessage); + + assertEquals(")]}',\"foo\"", outputMessage.getBodyAsString(Charset.forName("UTF-8"))); + } + public static class PrettyPrintBean { diff --git a/spring-web/src/test/java/org/springframework/http/converter/xml/SourceHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/xml/SourceHttpMessageConverterTests.java index c34e202330d1..8d47c221d1cd 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/xml/SourceHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/xml/SourceHttpMessageConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,21 +17,29 @@ package org.springframework.http.converter.xml; import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; +import static org.junit.Assert.assertNotEquals; +import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; import java.io.StringReader; import java.nio.charset.Charset; import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; import javax.xml.transform.Source; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.sax.SAXSource; +import javax.xml.transform.stax.StAXSource; import javax.xml.transform.stream.StreamSource; import org.junit.Before; import org.junit.Test; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; import org.springframework.http.MediaType; import org.springframework.http.MockHttpInputMessage; import org.springframework.http.MockHttpOutputMessage; @@ -39,17 +47,29 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.DefaultHandler; /** * @author Arjen Poutsma */ public class SourceHttpMessageConverterTests { + private static final String BODY = "Hello World"; + private SourceHttpMessageConverter converter; + private String bodyExternal; + @Before - public void setUp() { + public void setUp() throws IOException { converter = new SourceHttpMessageConverter(); + Resource external = new ClassPathResource("external.txt", getClass()); + + bodyExternal = "\n" + + " ]>&ext;"; } @Test @@ -67,39 +87,94 @@ public void canWrite() { @Test public void readDOMSource() throws Exception { - String body = "Hello World"; - MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(BODY.getBytes("UTF-8")); inputMessage.getHeaders().setContentType(new MediaType("application", "xml")); DOMSource result = (DOMSource) converter.read(DOMSource.class, inputMessage); Document document = (Document) result.getNode(); assertEquals("Invalid result", "root", document.getDocumentElement().getLocalName()); } + @Test + public void readDOMSourceExternal() throws Exception { + MockHttpInputMessage inputMessage = new MockHttpInputMessage(bodyExternal.getBytes("UTF-8")); + inputMessage.getHeaders().setContentType(new MediaType("application", "xml")); + DOMSource result = (DOMSource) converter.read(DOMSource.class, inputMessage); + Document document = (Document) result.getNode(); + assertEquals("Invalid result", "root", document.getDocumentElement().getLocalName()); + assertNotEquals("Invalid result", "Foo Bar", document.getDocumentElement().getTextContent()); + } + @Test public void readSAXSource() throws Exception { - String body = "Hello World"; - MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(BODY.getBytes("UTF-8")); inputMessage.getHeaders().setContentType(new MediaType("application", "xml")); SAXSource result = (SAXSource) converter.read(SAXSource.class, inputMessage); InputSource inputSource = result.getInputSource(); String s = FileCopyUtils.copyToString(new InputStreamReader(inputSource.getByteStream())); - assertXMLEqual("Invalid result", body, s); + assertXMLEqual("Invalid result", BODY, s); + } + + @Test + public void readSAXSourceExternal() throws Exception { + MockHttpInputMessage inputMessage = new MockHttpInputMessage(bodyExternal.getBytes("UTF-8")); + inputMessage.getHeaders().setContentType(new MediaType("application", "xml")); + SAXSource result = (SAXSource) converter.read(SAXSource.class, inputMessage); + InputSource inputSource = result.getInputSource(); + XMLReader reader = result.getXMLReader(); + reader.setContentHandler(new DefaultHandler() { + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + String s = new String(ch, start, length); + assertNotEquals("Invalid result", "Foo Bar", s); + } + }); + reader.parse(inputSource); } + @Test + public void readStAXSource() throws Exception { + MockHttpInputMessage inputMessage = new MockHttpInputMessage(BODY.getBytes("UTF-8")); + inputMessage.getHeaders().setContentType(new MediaType("application", "xml")); + StAXSource result = (StAXSource) converter.read(StAXSource.class, inputMessage); + XMLStreamReader streamReader = result.getXMLStreamReader(); + assertTrue(streamReader.hasNext()); + streamReader.nextTag(); + String s = streamReader.getLocalName(); + assertEquals("root", s); + s = streamReader.getElementText(); + assertEquals("Hello World", s); + streamReader.close(); + } + + @Test + public void readStAXSourceExternal() throws Exception { + MockHttpInputMessage inputMessage = new MockHttpInputMessage(bodyExternal.getBytes("UTF-8")); + inputMessage.getHeaders().setContentType(new MediaType("application", "xml")); + StAXSource result = (StAXSource) converter.read(StAXSource.class, inputMessage); + XMLStreamReader streamReader = result.getXMLStreamReader(); + assertTrue(streamReader.hasNext()); + streamReader.next(); + streamReader.next(); + String s = streamReader.getLocalName(); + assertEquals("root", s); + s = streamReader.getElementText(); + assertNotEquals("Foo Bar", s); + streamReader.close(); + } + + @Test public void readStreamSource() throws Exception { - String body = "Hello World"; - MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(BODY.getBytes("UTF-8")); inputMessage.getHeaders().setContentType(new MediaType("application", "xml")); StreamSource result = (StreamSource) converter.read(StreamSource.class, inputMessage); String s = FileCopyUtils.copyToString(new InputStreamReader(result.getInputStream())); - assertXMLEqual("Invalid result", body, s); + assertXMLEqual("Invalid result", BODY, s); } @Test public void readSource() throws Exception { - String body = "Hello World"; - MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(BODY.getBytes("UTF-8")); inputMessage.getHeaders().setContentType(new MediaType("application", "xml")); converter.read(Source.class, inputMessage); } diff --git a/spring-web/src/test/resources/org/springframework/http/converter/xml/external.txt b/spring-web/src/test/resources/org/springframework/http/converter/xml/external.txt new file mode 100644 index 000000000000..76c7ac2d0ce6 --- /dev/null +++ b/spring-web/src/test/resources/org/springframework/http/converter/xml/external.txt @@ -0,0 +1 @@ +Foo Bar diff --git a/spring-webmvc-portlet/src/test/java/org/springframework/web/portlet/context/AbstractXmlWebApplicationContextTests.java b/spring-webmvc-portlet/src/test/java/org/springframework/web/portlet/context/AbstractXmlWebApplicationContextTests.java index bec09daa5e28..21d6e905323d 100644 --- a/spring-webmvc-portlet/src/test/java/org/springframework/web/portlet/context/AbstractXmlWebApplicationContextTests.java +++ b/spring-webmvc-portlet/src/test/java/org/springframework/web/portlet/context/AbstractXmlWebApplicationContextTests.java @@ -45,19 +45,13 @@ public abstract class AbstractXmlWebApplicationContextTests extends AbstractAppl * @see org.springframework.context.AbstractApplicationContextTests#testEvents() */ @Override - public void testEvents() throws Exception { - TestListener listener = (TestListener) this.applicationContext.getBean("testListener"); - listener.zeroCounter(); - TestListener parentListener = (TestListener) this.applicationContext.getParent().getBean("parentListener"); - parentListener.zeroCounter(); - - parentListener.zeroCounter(); - assertTrue("0 events before publication", listener.getEventCount() == 0); - assertTrue("0 parent events before publication", parentListener.getEventCount() == 0); - this.applicationContext.publishEvent(new MyEvent(this)); - assertTrue("1 events after publication, not " + listener.getEventCount(), listener.getEventCount() == 1); - assertTrue("1 parent events after publication", parentListener.getEventCount() == 1); - } + protected void doTestEvents(TestListener listener, TestListener parentListener, + MyEvent event) { + TestListener listenerBean = (TestListener) this.applicationContext.getBean("testListener"); + TestListener parentListenerBean = (TestListener) this.applicationContext.getParent().getBean("parentListener"); + super.doTestEvents(listenerBean, parentListenerBean, event); + + }; @Override public void testCount() { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/LocaleResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/LocaleResolver.java index d580afe766e8..380bdfd39778 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/LocaleResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/LocaleResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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.web.servlet; import java.util.Locale; - import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -27,22 +26,22 @@ * request and response. * *

This interface allows for implementations based on request, session, - * cookies, etc. The default implementation is AcceptHeaderLocaleResolver, + * cookies, etc. The default implementation is + * {@link org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver}, * simply using the request's locale provided by the respective HTTP header. * - *

Use {@code RequestContext.getLocale()} to retrieve the current locale - * in controllers or views, independent of the actual resolution strategy. + *

Use {@link org.springframework.web.servlet.support.RequestContext#getLocale()} + * to retrieve the current locale in controllers or views, independent + * of the actual resolution strategy. * * @author Juergen Hoeller * @since 27.02.2003 - * @see org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver - * @see org.springframework.web.servlet.support.RequestContext#getLocale */ public interface LocaleResolver { /** * Resolve the current locale via the given request. - * Should return a default locale as fallback in any case. + * Can return a default locale as fallback in any case. * @param request the request to resolve the locale for * @return the current locale (never {@code null}) */ @@ -54,7 +53,7 @@ public interface LocaleResolver { * @param response the response to be used for locale modification * @param locale the new locale, or {@code null} to clear the locale * @throws UnsupportedOperationException if the LocaleResolver implementation - * does not support dynamic changing of the theme + * does not support dynamic changing of the locale */ void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/ThemeResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/ThemeResolver.java index 39c0cbd8b188..6da2e2afd34d 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/ThemeResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/ThemeResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,7 +25,8 @@ * request and response. * *

This interface allows for implementations based on session, - * cookies, etc. The default implementation is FixedThemeResolver, + * cookies, etc. The default implementation is + * {@link org.springframework.web.servlet.theme.FixedThemeResolver}, * simply using a configured default theme. * *

Note that this resolver is only responsible for determining the @@ -33,35 +34,34 @@ * gets looked up by DispatcherServlet via the respective ThemeSource, * i.e. the current WebApplicationContext. * - *

Use RequestContext.getTheme() to retrieve the current theme in - * controllers or views, independent of the actual resolution strategy. + *

Use {@link org.springframework.web.servlet.support.RequestContext#getTheme()} + * to retrieve the current theme in controllers or views, independent + * of the actual resolution strategy. * * @author Jean-Pierre Pawlak * @author Juergen Hoeller * @since 17.06.2003 - * @see org.springframework.web.servlet.theme.FixedThemeResolver * @see org.springframework.ui.context.Theme * @see org.springframework.ui.context.ThemeSource - * @see org.springframework.web.servlet.support.RequestContext#getTheme */ public interface ThemeResolver { - /** - * Resolve the current theme name via the given request. - * Should return a default theme as fallback in any case. - * @param request request to be used for resolution - * @return the current theme name - */ + /** + * Resolve the current theme name via the given request. + * Should return a default theme as fallback in any case. + * @param request request to be used for resolution + * @return the current theme name + */ String resolveThemeName(HttpServletRequest request); - /** - * Set the current theme name to the given one. - * @param request request to be used for theme name modification - * @param response response to be used for theme name modification - * @param themeName the new theme name + /** + * Set the current theme name to the given one. + * @param request request to be used for theme name modification + * @param response response to be used for theme name modification + * @param themeName the new theme name * @throws UnsupportedOperationException if the ThemeResolver implementation * does not support dynamic changing of the theme - */ + */ void setThemeName(HttpServletRequest request, HttpServletResponse response, String themeName); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/PatternsRequestCondition.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/PatternsRequestCondition.java index 3af593bd8b17..d68b231ba561 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/PatternsRequestCondition.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/PatternsRequestCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2013 the original author 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,7 +25,6 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Set; - import javax.servlet.http.HttpServletRequest; import org.springframework.util.AntPathMatcher; @@ -54,6 +53,7 @@ public final class PatternsRequestCondition extends AbstractRequestCondition fileExtensions = new ArrayList(); + /** * Creates a new instance with the given URL patterns. * Each pattern that is not empty and does not start with "/" is prepended with "/". @@ -66,7 +66,6 @@ public PatternsRequestCondition(String... patterns) { /** * Additional constructor with flags for using suffix pattern (.*) and * trailing slash matches. - * * @param patterns the URL patterns to use; if 0, the condition will match to every request. * @param urlPathHelper for determining the lookup path of a request * @param pathMatcher for path matching with patterns @@ -98,7 +97,6 @@ public PatternsRequestCondition(String[] patterns, UrlPathHelper urlPathHelper, /** * Private constructor accepting a collection of patterns. - * @param fileExtensionResolver */ private PatternsRequestCondition(Collection patterns, UrlPathHelper urlPathHelper, PathMatcher pathMatcher, boolean useSuffixPatternMatch, boolean useTrailingSlashMatch, @@ -119,8 +117,9 @@ private PatternsRequestCondition(Collection patterns, UrlPathHelper urlP } } + private static List asList(String... patterns) { - return patterns != null ? Arrays.asList(patterns) : Collections.emptyList(); + return (patterns != null ? Arrays.asList(patterns) : Collections.emptyList()); } private static Set prependLeadingSlash(Collection patterns) { @@ -188,7 +187,6 @@ else if (!other.patterns.isEmpty()) { * Checks if any of the patterns match the given request and returns an instance * that is guaranteed to contain matching patterns, sorted via * {@link PathMatcher#getPatternComparator(String)}. - * *

A matching pattern is obtained by making checks in the following order: *

    *
  • Direct match @@ -196,12 +194,10 @@ else if (!other.patterns.isEmpty()) { *
  • Pattern match *
  • Pattern match with "/" appended if the pattern doesn't already end in "/" *
- * * @param request the current request - * * @return the same instance if the condition contains no patterns; - * or a new condition with sorted matching patterns; - * or {@code null} if no patterns match. + * or a new condition with sorted matching patterns; + * or {@code null} if no patterns match. */ @Override public PatternsRequestCondition getMatchingCondition(HttpServletRequest request) { @@ -210,9 +206,8 @@ public PatternsRequestCondition getMatchingCondition(HttpServletRequest request) } String lookupPath = this.pathHelper.getLookupPathForRequest(request); - List matches = new ArrayList(); - for (String pattern : patterns) { + for (String pattern : this.patterns) { String match = getMatchingPattern(pattern, lookupPath); if (match != null) { matches.add(match); @@ -260,7 +255,6 @@ private String getMatchingPattern(String pattern, String lookupPath) { * {@link PathMatcher#getPatternComparator(String)}. If all compared * patterns match equally, but one instance has more patterns, it is * considered a closer match. - * *

It is assumed that both instances have been obtained via * {@link #getMatchingCondition(HttpServletRequest)} to ensure they * contain only patterns that match the request and are sorted with @@ -271,7 +265,7 @@ public int compareTo(PatternsRequestCondition other, HttpServletRequest request) String lookupPath = this.pathHelper.getLookupPathForRequest(request); Comparator patternComparator = this.pathMatcher.getPatternComparator(lookupPath); - Iterator iterator = patterns.iterator(); + Iterator iterator = this.patterns.iterator(); Iterator iteratorOther = other.patterns.iterator(); while (iterator.hasNext() && iteratorOther.hasNext()) { int result = patternComparator.compare(iterator.next(), iteratorOther.next()); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java index 07ad5dce9d77..62fdd9d95f6a 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java @@ -16,14 +16,9 @@ package org.springframework.web.servlet.mvc.method; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.Set; + import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -94,16 +89,28 @@ public int compare(RequestMappingInfo info1, RequestMappingInfo info2) { protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) { super.handleMatch(info, lookupPath, request); + String bestPattern; + Map uriVariables; + Map decodedUriVariables; + Set patterns = info.getPatternsCondition().getPatterns(); - String bestPattern = patterns.isEmpty() ? lookupPath : patterns.iterator().next(); - request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern); + if (patterns.isEmpty()) { + bestPattern = lookupPath; + uriVariables = Collections.emptyMap(); + decodedUriVariables = Collections.emptyMap(); + } + else { + bestPattern = patterns.iterator().next(); + uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath); + decodedUriVariables = getUrlPathHelper().decodePathVariables(request, uriVariables); + } - Map uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath); - Map decodedUriVariables = getUrlPathHelper().decodePathVariables(request, uriVariables); + request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern); request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, decodedUriVariables); if (isMatrixVariableContentAvailable()) { - request.setAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, extractMatrixVariables(request, uriVariables)); + Map> matrixVars = extractMatrixVariables(request, uriVariables); + request.setAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, matrixVars); } if (!info.getProducesCondition().getProducibleMediaTypes().isEmpty()) { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java index 14c74c607713..519ae72967d0 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java @@ -33,6 +33,7 @@ import org.springframework.core.GenericTypeResolver; import org.springframework.core.MethodParameter; import org.springframework.http.HttpInputMessage; +import org.springframework.http.InvalidMediaTypeException; import org.springframework.http.MediaType; import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; @@ -115,10 +116,17 @@ protected Object readWithMessageConverters(NativeWebRequest webRequest, protected Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter methodParam, Type targetType) throws IOException, HttpMediaTypeNotSupportedException { - MediaType contentType = inputMessage.getHeaders().getContentType(); - if (contentType == null) { - contentType = MediaType.APPLICATION_OCTET_STREAM; - } + MediaType contentType; + try { + contentType = inputMessage.getHeaders().getContentType(); + } + catch (InvalidMediaTypeException ex) { + throw new HttpMediaTypeNotSupportedException(ex.getMessage()); + } + + if (contentType == null) { + contentType = MediaType.APPLICATION_OCTET_STREAM; + } Class contextClass = methodParam.getDeclaringClass(); Map map = GenericTypeResolver.getTypeVariableMap(contextClass); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java index bb9ddd72b1fb..53335e5d5436 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java @@ -236,7 +236,9 @@ protected RequestCondition getCustomMethodCondition(Method method) { /** * Created a RequestMappingInfo from a RequestMapping annotation. */ - private RequestMappingInfo createRequestMappingInfo(RequestMapping annotation, RequestCondition customCondition) { + protected RequestMappingInfo createRequestMappingInfo(RequestMapping annotation, + RequestCondition customCondition) { + String[] patterns = resolveEmbeddedValuesInPatterns(annotation.value()); return new RequestMappingInfo( new PatternsRequestCondition(patterns, getUrlPathHelper(), getPathMatcher(), diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/JstlUtils.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/JstlUtils.java index f568f362137d..dab87183822b 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/JstlUtils.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/JstlUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -141,6 +141,6 @@ public Locale getLocale() { } return RequestContextUtils.getLocale(this.request); } - }; + } } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContext.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContext.java index cf33995355d2..f2edbfedc3ef 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContext.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContext.java @@ -20,14 +20,12 @@ import java.util.List; import java.util.Locale; import java.util.Map; - import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import javax.servlet.jsp.jstl.core.Config; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.context.MessageSource; import org.springframework.context.MessageSourceResolvable; import org.springframework.context.NoSuchMessageException; @@ -90,6 +88,7 @@ public class RequestContext { */ private static final String REQUEST_DATA_VALUE_PROCESSOR_BEAN_NAME = "requestDataValueProcessor"; + protected static final boolean jstlPresent = ClassUtils.isPresent("javax.servlet.jsp.jstl.core.Config", RequestContext.class.getClassLoader()); @@ -113,6 +112,7 @@ public class RequestContext { private Map errorsMap; + /** * Create a new RequestContext for the given request, using the request attributes for Errors retrieval.

This * only works with InternalResourceViews, as Errors instances are part of the model and not normally exposed as @@ -181,6 +181,7 @@ public RequestContext(HttpServletRequest request, HttpServletResponse response, protected RequestContext() { } + /** * Initialize this context with the given request, using the given model attributes for Errors retrieval. *

Delegates to {@code getFallbackLocale} and {@code getFallbackTheme} for determining the fallback @@ -214,7 +215,8 @@ protected void initContext(HttpServletRequest request, HttpServletResponse respo if (localeResolver != null) { // Try LocaleResolver (we're within a DispatcherServlet request). this.locale = localeResolver.resolveLocale(request); - } else { + } + else { // No LocaleResolver available -> try fallback. this.locale = getFallbackLocale(); } @@ -225,13 +227,10 @@ protected void initContext(HttpServletRequest request, HttpServletResponse respo this.urlPathHelper = new UrlPathHelper(); - try { + if (this.webApplicationContext.containsBean(REQUEST_DATA_VALUE_PROCESSOR_BEAN_NAME)) { this.requestDataValueProcessor = this.webApplicationContext.getBean( REQUEST_DATA_VALUE_PROCESSOR_BEAN_NAME, RequestDataValueProcessor.class); } - catch (NoSuchBeanDefinitionException ex) { - // Ignored - } } /** @@ -269,6 +268,7 @@ protected Theme getFallbackTheme() { return theme; } + /** * Return the underlying HttpServletRequest. Only intended for cooperating classes in this package. */ @@ -702,7 +702,8 @@ public Errors getErrors(String name, boolean htmlEscape) { if (htmlEscape && !(errors instanceof EscapedErrors)) { errors = new EscapedErrors(errors); put = true; - } else if (!htmlEscape && errors instanceof EscapedErrors) { + } + else if (!htmlEscape && errors instanceof EscapedErrors) { errors = ((EscapedErrors) errors).getSource(); put = true; } @@ -720,7 +721,8 @@ public Errors getErrors(String name, boolean htmlEscape) { protected Object getModelObject(String modelName) { if (this.model != null) { return this.model.get(modelName); - } else { + } + else { return this.request.getAttribute(modelName); } } @@ -746,9 +748,10 @@ public BindStatus getBindStatus(String path, boolean htmlEscape) throws IllegalS return new BindStatus(this, path, htmlEscape); } + /** - * Inner class that isolates the JSTL dependency. Just called to resolve the fallback locale if the JSTL API is - * present. + * Inner class that isolates the JSTL dependency. + * Just called to resolve the fallback locale if the JSTL API is present. */ private static class JstlLocaleResolver { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContextUtils.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContextUtils.java index f99283904668..e0b22a9c8754 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContextUtils.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContextUtils.java @@ -18,7 +18,6 @@ import java.util.Locale; import java.util.Map; - import javax.servlet.ServletContext; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; @@ -41,6 +40,7 @@ * Locale, ThemeResolver, Theme, and MultipartResolver. * * @author Juergen Hoeller + * @author Rossen Stoyanchev * @since 03.03.2003 * @see RequestContext * @see org.springframework.web.servlet.DispatcherServlet @@ -160,7 +160,7 @@ public static Theme getTheme(HttpServletRequest request) { * Return a read-only {@link Map} with "input" flash attributes saved on a * previous request. * @param request the current request - * @return a read-only Map, or {@code null} + * @return a read-only Map, or {@code null} if not found * @see FlashMap */ @SuppressWarnings("unchecked") @@ -170,8 +170,8 @@ public static Theme getTheme(HttpServletRequest request) { /** * Return the "output" FlashMap with attributes to save for a subsequent request. - * @param request current request - * @return a {@link FlashMap} instance, never {@code null} + * @param request the current request + * @return a {@link FlashMap} instance (never {@code null} within a DispatcherServlet request) * @see FlashMap */ public static FlashMap getOutputFlashMap(HttpServletRequest request) { @@ -182,6 +182,7 @@ public static FlashMap getOutputFlashMap(HttpServletRequest request) { * Return the FlashMapManager instance to save flash attributes with * before a redirect. * @param request the current request + * @return a {@link FlashMapManager} instance (never {@code null} within a DispatcherServlet request) */ public static FlashMapManager getFlashMapManager(HttpServletRequest request) { return (FlashMapManager) request.getAttribute(DispatcherServlet.FLASH_MAP_MANAGER_ATTRIBUTE); 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 0f96d4832ad5..83451c11f738 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,7 +33,6 @@ import org.springframework.beans.BeanUtils; import org.springframework.http.HttpStatus; -import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -250,15 +249,15 @@ protected boolean isContextRequired() { return false; } + /** * Convert model to request parameters and redirect to the given URL. * @see #appendQueryProperties * @see #sendRedirect */ @Override - protected void renderMergedOutputModel( - Map model, HttpServletRequest request, HttpServletResponse response) - throws IOException { + protected void renderMergedOutputModel(Map model, HttpServletRequest request, + HttpServletResponse response) throws IOException { String targetUrl = createTargetUrl(model, request); targetUrl = updateTargetUrl(targetUrl, model, request, response); @@ -268,11 +267,13 @@ protected void renderMergedOutputModel( UriComponents uriComponents = UriComponentsBuilder.fromUriString(targetUrl).build(); flashMap.setTargetRequestPath(uriComponents.getPath()); flashMap.addTargetRequestParams(uriComponents.getQueryParams()); + FlashMapManager flashMapManager = RequestContextUtils.getFlashMapManager(request); + if (flashMapManager == null) { + throw new IllegalStateException("FlashMapManager not found despite output FlashMap having been set"); + } + flashMapManager.saveOutputFlashMap(flashMap, request, response); } - FlashMapManager flashMapManager = RequestContextUtils.getFlashMapManager(request); - flashMapManager.saveOutputFlashMap(flashMap, request, response); - sendRedirect(request, response, targetUrl, this.http10Compatible); } @@ -304,7 +305,6 @@ protected final String createTargetUrl(Map model, HttpServletReq Map variables = getCurrentRequestUriVariables(request); targetUrl = replaceUriTemplateVariables(targetUrl.toString(), model, variables, enc); } - if (this.exposeModelAttributes) { appendQueryProperties(targetUrl, model, enc); } @@ -327,15 +327,17 @@ protected StringBuilder replaceUriTemplateVariables( throws UnsupportedEncodingException { StringBuilder result = new StringBuilder(); - Matcher m = URI_TEMPLATE_VARIABLE_PATTERN.matcher(targetUrl); + Matcher matcher = URI_TEMPLATE_VARIABLE_PATTERN.matcher(targetUrl); int endLastMatch = 0; - while (m.find()) { - String name = m.group(1); - Object value = model.containsKey(name) ? model.remove(name) : currentUriVariables.get(name); - Assert.notNull(value, "Model has no value for '" + name + "'"); - result.append(targetUrl.substring(endLastMatch, m.start())); + while (matcher.find()) { + String name = matcher.group(1); + Object value = (model.containsKey(name) ? model.remove(name) : currentUriVariables.get(name)); + if (value == null) { + throw new IllegalArgumentException("Model has no value for key '" + name + "'"); + } + result.append(targetUrl.substring(endLastMatch, matcher.start())); result.append(UriUtils.encodePathSegment(value.toString(), encodingScheme)); - endLastMatch = m.end(); + endLastMatch = matcher.end(); } result.append(targetUrl.substring(endLastMatch, targetUrl.length())); return result; @@ -344,7 +346,7 @@ protected StringBuilder replaceUriTemplateVariables( @SuppressWarnings("unchecked") private Map getCurrentRequestUriVariables(HttpServletRequest request) { Map uriVars = - (Map) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); + (Map) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); return (uriVars != null) ? uriVars : Collections. emptyMap(); } @@ -441,7 +443,6 @@ protected boolean isEligibleProperty(String key, Object value) { if (isEligibleValue(value)) { return true; } - if (value.getClass().isArray()) { int length = Array.getLength(value); if (length == 0) { @@ -455,7 +456,6 @@ protected boolean isEligibleProperty(String key, Object value) { } return true; } - if (value instanceof Collection) { Collection coll = (Collection) value; if (coll.isEmpty()) { @@ -468,7 +468,6 @@ protected boolean isEligibleProperty(String key, Object value) { } return true; } - return false; } @@ -504,7 +503,7 @@ protected String urlEncode(String input, String encodingScheme) throws Unsupport * @return the updated URL or the same as URL as the one passed in */ protected String updateTargetUrl(String targetUrl, Map model, - HttpServletRequest request, HttpServletResponse response) { + HttpServletRequest request, HttpServletResponse response) { RequestContext requestContext = null; if (getWebApplicationContext() != null) { @@ -516,14 +515,12 @@ protected String updateTargetUrl(String targetUrl, Map model, requestContext = new RequestContext(request, response, wac.getServletContext(), model); } } - if (requestContext != null) { RequestDataValueProcessor processor = requestContext.getRequestDataValueProcessor(); if (processor != null) { targetUrl = processor.processUrl(request, targetUrl); } } - return targetUrl; } @@ -535,12 +532,10 @@ protected String updateTargetUrl(String targetUrl, Map model, * @param http10Compatible whether to stay compatible with HTTP 1.0 clients * @throws IOException if thrown by response methods */ - protected void sendRedirect( - HttpServletRequest request, HttpServletResponse response, String targetUrl, boolean http10Compatible) - throws IOException { + protected void sendRedirect(HttpServletRequest request, HttpServletResponse response, + String targetUrl, boolean http10Compatible) throws IOException { String encodedRedirectURL = response.encodeRedirectURL(targetUrl); - if (http10Compatible) { if (this.statusCode != null) { response.setStatus(this.statusCode.value()); diff --git a/spring-webmvc/src/test/java/org/springframework/web/context/XmlWebApplicationContextTests.java b/spring-webmvc/src/test/java/org/springframework/web/context/XmlWebApplicationContextTests.java index 4dce1e9fac39..3d1d0b203eb2 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/context/XmlWebApplicationContextTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/context/XmlWebApplicationContextTests.java @@ -96,18 +96,11 @@ public void testEnvironmentMerge() { * @see org.springframework.context.AbstractApplicationContextTests#testEvents() */ @Override - public void testEvents() throws Exception { - TestListener listener = (TestListener) this.applicationContext.getBean("testListener"); - listener.zeroCounter(); - TestListener parentListener = (TestListener) this.applicationContext.getParent().getBean("parentListener"); - parentListener.zeroCounter(); - - parentListener.zeroCounter(); - assertTrue("0 events before publication", listener.getEventCount() == 0); - assertTrue("0 parent events before publication", parentListener.getEventCount() == 0); - this.applicationContext.publishEvent(new MyEvent(this)); - assertTrue("1 events after publication, not " + listener.getEventCount(), listener.getEventCount() == 1); - assertTrue("1 parent events after publication", parentListener.getEventCount() == 1); + protected void doTestEvents(TestListener listener, TestListener parentListener, + MyEvent event) { + TestListener listenerBean = (TestListener) this.applicationContext.getBean("testListener"); + TestListener parentListenerBean = (TestListener) this.applicationContext.getParent().getBean("parentListener"); + super.doTestEvents(listenerBean, parentListenerBean, event); } @Override diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorMockTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorMockTests.java index 5eb6d49f42ca..a4abfb010718 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorMockTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorMockTests.java @@ -187,6 +187,12 @@ public void resolveArgumentNoContentType() throws Exception { } } + @Test(expected = HttpMediaTypeNotSupportedException.class) + public void resolveArgumentInvalidContentType() throws Exception { + this.servletRequest.setContentType("bad"); + processor.resolveArgument(paramRequestBodyString, mavContainer, webRequest, null); + } + @Test public void resolveArgumentNotRequiredNoContent() throws Exception { servletRequest.setContent(null); diff --git a/src/dist/changelog.txt b/src/dist/changelog.txt index ca7d3c3d67de..e19c3bda6c73 100644 --- a/src/dist/changelog.txt +++ b/src/dist/changelog.txt @@ -3,6 +3,49 @@ SPRING FRAMEWORK CHANGELOG http://www.springsource.org +Changes in version 3.2.5 (2013-11-06) +------------------------------------- + +* fixed type prediction for generic factory method with conversion of method arguments (SPR-10411) +* fixed GenericTypeResolver issues relating to ParameterizedType (SPR-10819) +* allow indexed constructors to be used with @Autowire resolution (SPR-11019) +* refined JavaConfig overrides algorithm (SPR-10988, SPR-10992) +* fixed multiple calls to @Autowire setters when defining several beans of the same class (SPR-11027) +* provided more lenient fallback checks for setter injection (SPR-10995) +* allowed AnnotationConfigWebApplicationContext.register to be called multiple times (SPR-10852) +* fixed various SpEL issues (SPR-9495, SPR-10452, SPR-10928, SPR-10884, SPR-10953, SPR-10716, SPR-10486, SPR-11031) +* added XStream catch-all converter (SPR-10821) +* fixed issue with AOP Advisor being silently skipped during creation (SPR-10430) +* fixed use of configured prefix for Jackson message converters (SPR-10817) +* fixed SimpleJdbcCall function return issues (SPR-10606) +* protected against memory leak in AntPathMatcher (SPR-10803) +* fixed memory leak in AbstractBeanFactory (SPR-10896) +* fixed Ehcache RMI replication issue (SPR-10904) +* fixed ArrayStoreException with ASM reading of enum subclass (SPR-10914) +* prevented duplicate scan of @Import annotations (SPR-10918) +* fixed issues using non-shareable @Resource (SPR-10931) +* ensured malformed content type is translated to 415 status code (SPR-10982) +* fixed issues when converting a single element array to object (SPR-10996) +* allow override of @ContextConfiguration initializer when using @ContextHierarchy (SPR-10997) +* fixed issue with ordering of @PropertySource values (SPR-10820) +* fixed parsing issues with JNDI variables (SPR-11039) +* fixed MockHttpServletRequestBuilder handling parameter without value (SPR-11043) +* fixed ClasspathXmlApplicationContext inherit/merge of parent context environment (SPR-11068) +* fixed wrong translation of MS SQL error code (SPR-10902) +* fixed JMSTemplate issues when used with Oracle AQ (SPR-10829) +* fixed AbstractMethodMockingControl off-by-one error (SPR-10885) +* fixed potential NPE with JaxB2Marshaller (SPR-10828) +* fixed potential NPE with RestTemplate (SPR-10848) +* fixed potential NPE in AbstractApplicationEventMulticaster (SPR-10945) +* fixed potential NPE with RedirectView (SPR-10937) +* fixed NPE with ExtendedBeanInfo on IBM J9 VM (SPR-10862) +* improved subclassing support for RequestMappingHandlerMapping (SPR-10950) +* made AnnotationConfigUtils.processCommonDefinitionAnnotations public (SPR-11032) +* removed unnecessary char[] allocation with NamedParameterUtils (SPR-11042) +* refined logging output (SPR-10974, SPR-11017) +* minor documentation updates (SPR-10798, SPR-10850, SPR-10927) + + Changes in version 3.2.4 (2013-08-06) ------------------------------------- diff --git a/src/reference/docbook/expressions.xml b/src/reference/docbook/expressions.xml index 5dcafe7e1408..8da6717367c6 100644 --- a/src/reference/docbook/expressions.xml +++ b/src/reference/docbook/expressions.xml @@ -458,7 +458,7 @@ Boolean b = simple.booleanList.get(0); @Autowired public void configure(MovieFinder movieFinder, - @Value("#{ systemProperties['user.region'] }"} String defaultLocale) { + @Value("#{ systemProperties['user.region'] }") String defaultLocale) { this.movieFinder = movieFinder; this.defaultLocale = defaultLocale; } @@ -474,7 +474,7 @@ Boolean b = simple.booleanList.get(0); @Autowired public MovieRecommender(CustomerPreferenceDao customerPreferenceDao, - @Value("#{systemProperties['user.country']}"} String defaultLocale) { + @Value("#{systemProperties['user.country']}") String defaultLocale) { this.customerPreferenceDao = customerPreferenceDao; this.defaultLocale = defaultLocale; } @@ -980,7 +980,7 @@ StandardEvaluationContext context = new StandardEvaluationContext(tesla); String name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class); -System.out.println(name); // Mike Tesla +System.out.println(name); // Nikola Tesla tesla.setName(null); diff --git a/src/reference/docbook/jdbc.xml b/src/reference/docbook/jdbc.xml index 65b238e98559..6b3ae4601b34 100644 --- a/src/reference/docbook/jdbc.xml +++ b/src/reference/docbook/jdbc.xml @@ -2601,19 +2601,19 @@ clobReader.close();]]> that must be implemented. This interface is used as part of the declaration of an SqlOutParameter. - final TestItem - new TestItem(123L, "A test item", - new SimpleDateFormat("yyyy-M-d").parse("2010-12-31");); + final TestItem = new TestItem(123L, "A test item", + new SimpleDateFormat("yyyy-M-d").parse("2010-12-31")); declareParameter(new SqlOutParameter("item", OracleTypes.STRUCT, "ITEM_TYPE", new SqlReturnType() { public Object getTypeValue(CallableStatement cs, int colIndx, int sqlType, String typeName) throws SQLException { - STRUCT struct = (STRUCT)cs.getObject(colIndx); + STRUCT struct = (STRUCT) cs.getObject(colIndx); Object[] attr = struct.getAttributes(); TestItem item = new TestItem(); item.setId(((Number) attr[0]).longValue()); - item.setDescription((String)attr[1]); - item.setExpirationDate((java.util.Date)attr[2]); + item.setDescription((String) attr[1]); + item.setExpirationDate((java.util.Date) attr[2]); return item; } }));You use the SqlTypeValue to @@ -2626,8 +2626,8 @@ declareParameter(new SqlOutParameter("item", OracleTypes.STRUCT, "ITEM_TYPE", StructDescriptors, as shown in the following example, or ArrayDescriptors. - final TestItem - new TestItem(123L, "A test item", - new SimpleDateFormat("yyyy-M-d").parse("2010-12-31");); + final TestItem = new TestItem(123L, "A test item", + new SimpleDateFormat("yyyy-M-d").parse("2010-12-31")); SqlTypeValue value = new AbstractSqlTypeValue() { protected Object createTypeValue(Connection conn, int sqlType, String typeName) throws SQLException { diff --git a/src/reference/docbook/mvc.xml b/src/reference/docbook/mvc.xml index 1b8a2e0c283b..b4bd6eac521c 100644 --- a/src/reference/docbook/mvc.xml +++ b/src/reference/docbook/mvc.xml @@ -4267,7 +4267,7 @@ public class SimpleController { response for example when providing a REST API. You can prepare a ModelAndView and render error content through view resolution -- i.e. by configuring a - ContentNeogitatingViewResolver, + ContentNegotiatingViewResolver, MappingJacksonJsonView, and so on. However, you may prefer to use @ExceptionHandler methods instead. diff --git a/src/reference/docbook/oxm.xml b/src/reference/docbook/oxm.xml index 1a7cbac17c5f..608b847f26c0 100644 --- a/src/reference/docbook/oxm.xml +++ b/src/reference/docbook/oxm.xml @@ -766,6 +766,9 @@ public class Application { Additionally, you can register custom converters to make sure that only your supported classes can be unmarshalled. + You might want to add a CatchAllConverter as the last converter in the list, + in addition to converters that explicitly support the domain classes that should be supported. + As a result, default XStream converters with lower priorities and possible security vulnerabilities do not get invoked.