diff options
author | Emmanuel Bourg <ebourg@apache.org> | 2019-01-01 11:22:29 +0100 |
---|---|---|
committer | Emmanuel Bourg <ebourg@apache.org> | 2019-01-01 11:22:29 +0100 |
commit | d2188a36ffbb40643baa12f9a68494774552563f (patch) | |
tree | be7118bd87b5b0960603ccda439d474261fbd69d | |
parent | cb53abe54064010cd788530c7a882ecebb88afcd (diff) |
New upstream version 4.3.21
73 files changed, 1054 insertions, 717 deletions
diff --git a/build.gradle b/build.gradle index aa4bc568..bd1f4ec0 100644 --- a/build.gradle +++ b/build.gradle @@ -41,7 +41,7 @@ configure(allprojects) { project -> ext.fileuploadVersion = "1.3.3" ext.freemarkerVersion = "2.3.23" ext.groovyVersion = "2.4.15" - ext.gsonVersion = "2.8.2" + ext.gsonVersion = "2.8.5" ext.guavaVersion = "20.0" ext.hamcrestVersion = "1.3" ext.hibernate3Version = "3.6.10.Final" @@ -50,9 +50,9 @@ configure(allprojects) { project -> ext.hibval4Version = "4.3.2.Final" ext.hibval5Version = "5.2.5.Final" ext.hsqldbVersion = "2.3.4" - ext.httpasyncVersion = "4.1.3" - ext.httpclientVersion = "4.5.5" - ext.jackson2Version = "2.8.11.2" + ext.httpasyncVersion = "4.1.4" + ext.httpclientVersion = "4.5.6" + ext.jackson2Version = "2.8.11.3" ext.jasperreportsVersion = "6.2.1" // our tests fail with JR-internal NPEs against 6.2.2 and higher ext.javamailVersion = "1.5.6" ext.jettyVersion = "9.3.14.v20161028" // as of 9.3.15, Jetty has hard Servlet 3.1 requirement @@ -62,7 +62,7 @@ configure(allprojects) { project -> ext.jtaVersion = "1.2" ext.junitVersion = "4.12" ext.log4jVersion = "1.2.17" - ext.nettyVersion = "4.1.29.Final" + ext.nettyVersion = "4.1.31.Final" ext.okhttpVersion = "2.7.5" ext.okhttp3Version = "3.8.1" ext.openjpaVersion = "2.4.2" @@ -71,11 +71,11 @@ configure(allprojects) { project -> ext.romeVersion = "1.7.4" ext.slf4jVersion = "1.7.25" ext.snakeyamlVersion = "1.17" - ext.snifferVersion = "1.16" + ext.snifferVersion = "1.17" ext.testngVersion = "6.9.10" ext.tiles2Version = "2.2.2" - ext.tiles3Version = "3.0.7" - ext.tomcatVersion = "8.5.33" + ext.tiles3Version = "3.0.8" + ext.tomcatVersion = "8.5.35" ext.tyrusVersion = "1.3.5" // constrained by WebLogic 12.1.3 support ext.undertowVersion = "1.3.33.Final" ext.xmlunitVersion = "1.6" @@ -289,10 +289,9 @@ project("spring-build-src") { project("spring-core") { description = "Spring Core" - // As of Spring 4.0.3, spring-core includes asm 5.x and repackages cglib 3.2, inlining - // both into the spring-core jar. cglib 3.2 itself depends on asm 5.x and is therefore - // further transformed by the JarJar task to depend on org.springframework.asm; this - // avoids including two different copies of asm unnecessarily. + // spring-core includes asm and repackages cglib, inlining both into the spring-core jar. + // cglib itself depends on asm and is therefore further transformed by the JarJar task to + // depend on org.springframework.asm; this avoids including two different copies of asm. def cglibVersion = "3.2.6" def objenesisVersion = "2.6" diff --git a/gradle.properties b/gradle.properties index 4a28008c..08f289ef 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=4.3.20.RELEASE +version=4.3.21.RELEASE 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 1411f556..5c0942cf 100644 --- a/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfo.java +++ b/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfo.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 the original author 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,8 +43,10 @@ import org.springframework.util.ObjectUtils; * Decorator for a standard {@link BeanInfo} object, e.g. as created by * {@link Introspector#getBeanInfo(Class)}, designed to discover and register static * and/or non-void returning setter methods. For example: + * * <pre class="code"> * public class Bean { + * * private Foo foo; * * public Foo getFoo() { @@ -56,6 +58,7 @@ import org.springframework.util.ObjectUtils; * return this; * } * }</pre> + * * The standard JavaBeans {@code Introspector} will discover the {@code getFoo} read * method, but will bypass the {@code #setFoo(Foo)} write method, because its non-void * returning signature does not comply with the JavaBeans specification. @@ -68,6 +71,7 @@ import org.springframework.util.ObjectUtils; * indexed properties</a> are fully supported. * * @author Chris Beams + * @author Juergen Hoeller * @since 3.1 * @see #ExtendedBeanInfo(BeanInfo) * @see ExtendedBeanInfoFactory diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/ListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/ListableBeanFactory.java index e8be3322..1c0c70b4 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/ListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/ListableBeanFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -243,7 +243,9 @@ public interface ListableBeanFactory extends BeanFactory { /** * Find all names of beans whose {@code Class} has the supplied {@link Annotation} - * type, without creating any bean instances yet. + * type, without creating corresponding bean instances yet. + * <p>Note that this method considers objects created by FactoryBeans, which means + * that FactoryBeans will get initialized in order to determine their object type. * @param annotationType the type of annotation to look for * @return the names of all matching beans * @since 4.0 @@ -253,6 +255,8 @@ public interface ListableBeanFactory extends BeanFactory { /** * Find all beans whose {@code Class} has the supplied {@link Annotation} type, * returning a Map of bean names with corresponding bean instances. + * <p>Note that this method considers objects created by FactoryBeans, which means + * that FactoryBeans will get initialized in order to determine their object type. * @param annotationType the type of annotation to look for * @return a Map with the matching beans, containing the bean names as * keys and the corresponding bean instances as values @@ -267,7 +271,7 @@ public interface ListableBeanFactory extends BeanFactory { * found on the given class itself. * @param beanName the name of the bean to look for annotations on * @param annotationType the annotation class to look for - * @return the annotation of the given type if found, or {@code null} + * @return the annotation of the given type if found, or {@code null} otherwise * @throws NoSuchBeanDefinitionException if there is no bean with the given name * @since 3.0 */ diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AnnotationBeanWiringInfoResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AnnotationBeanWiringInfoResolver.java index 78fe8fe8..e8e20ab0 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AnnotationBeanWiringInfoResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AnnotationBeanWiringInfoResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2018 the original author 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,24 +44,23 @@ public class AnnotationBeanWiringInfoResolver implements BeanWiringInfoResolver } /** - * Build the BeanWiringInfo for the given Configurable annotation. + * Build the {@link BeanWiringInfo} for the given {@link Configurable} annotation. * @param beanInstance the bean instance * @param annotation the Configurable annotation found on the bean class * @return the resolved BeanWiringInfo */ protected BeanWiringInfo buildWiringInfo(Object beanInstance, Configurable annotation) { if (!Autowire.NO.equals(annotation.autowire())) { + // Autowiring by name or by type return new BeanWiringInfo(annotation.autowire().value(), annotation.dependencyCheck()); } + else if (!"".equals(annotation.value())) { + // Explicitly specified bean name for bean definition to take property values from + return new BeanWiringInfo(annotation.value(), false); + } else { - if (!"".equals(annotation.value())) { - // explicitly specified bean name - return new BeanWiringInfo(annotation.value(), false); - } - else { - // default bean name - return new BeanWiringInfo(getDefaultBeanName(beanInstance), true); - } + // Default bean name for bean definition to take property values from + return new BeanWiringInfo(getDefaultBeanName(beanInstance), true); } } 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 734aef5a..1e021cc9 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 @@ -509,12 +509,21 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp // Generics potentially only match on the target class, not on the proxy... RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); Class<?> targetType = mbd.getTargetType(); - if (targetType != null && targetType != ClassUtils.getUserClass(beanInstance) && - typeToMatch.isAssignableFrom(targetType)) { + if (targetType != null && targetType != ClassUtils.getUserClass(beanInstance)) { // Check raw class match as well, making sure it's exposed on the proxy. Class<?> classToMatch = typeToMatch.resolve(); - return (classToMatch == null || classToMatch.isInstance(beanInstance)); + if (classToMatch != null && !classToMatch.isInstance(beanInstance)) { + return false; + } + if (typeToMatch.isAssignableFrom(targetType)) { + return true; + } } + ResolvableType resolvableType = mbd.targetType; + if (resolvableType == null) { + resolvableType = mbd.factoryMethodReturnType; + } + return (resolvableType != null && typeToMatch.isAssignableFrom(resolvableType)); } } return false; @@ -1363,6 +1372,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp */ protected Class<?> resolveBeanClass(final RootBeanDefinition mbd, String beanName, final Class<?>... typesToMatch) throws CannotLoadBeanClassException { + try { if (mbd.hasBeanClass()) { return mbd.getBeanClass(); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index c3ea84a0..bb5ff2cf 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -1574,7 +1574,9 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto } } // Lenient fallback: dummy factory in case of original factory not found... - return new DefaultListableBeanFactory(); + DefaultListableBeanFactory dummyFactory = new DefaultListableBeanFactory(); + dummyFactory.serializationId = this.id; + return dummyFactory; } } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/DependsOn.java b/spring-context/src/main/java/org/springframework/context/annotation/DependsOn.java index 121cec3a..48f3dc05 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/DependsOn.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/DependsOn.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2018 the original author 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,12 @@ import java.lang.annotation.Target; * does not explicitly depend on another through properties or constructor arguments, * but rather depends on the side effects of another bean's initialization. * + * <p>A depends-on declaration can specify both an initialization-time dependency and, + * in the case of singleton beans only, a corresponding destruction-time dependency. + * Dependent beans that define a depends-on relationship with a given bean are destroyed + * first, prior to the given bean itself being destroyed. Thus, a depends-on declaration + * can also control shutdown order. + * * <p>May be used on any class directly or indirectly annotated with * {@link org.springframework.stereotype.Component} or on methods annotated * with {@link Bean}. diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationInterceptor.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationInterceptor.java index 42427f4c..473ba922 100644 --- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationInterceptor.java +++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationInterceptor.java @@ -171,8 +171,23 @@ public class MethodValidationInterceptor implements MethodInterceptor { private boolean isFactoryBeanMetadataMethod(Method method) { Class<?> clazz = method.getDeclaringClass(); - return ((clazz == FactoryBean.class || clazz == SmartFactoryBean.class) && - !method.getName().equals("getObject")); + + // Call from interface-based proxy handle, allowing for an efficient check? + if (clazz.isInterface()) { + return ((clazz == FactoryBean.class || clazz == SmartFactoryBean.class) && + !method.getName().equals("getObject")); + } + + // Call from CGLIB proxy handle, potentially implementing a FactoryBean method? + Class<?> factoryBeanType = null; + if (SmartFactoryBean.class.isAssignableFrom(clazz)) { + factoryBeanType = SmartFactoryBean.class; + } + else if (FactoryBean.class.isAssignableFrom(clazz)) { + factoryBeanType = FactoryBean.class; + } + return (factoryBeanType != null && !method.getName().equals("getObject") && + ClassUtils.hasMethod(factoryBeanType, method.getName(), method.getParameterTypes())); } /** diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java index dcf2f373..dd053d7b 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java @@ -544,6 +544,10 @@ public class ConfigurationClassPostProcessorTests { beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); assertEquals(1, beanNames.length); assertEquals("stringRepo", beanNames[0]); + + beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); + assertEquals(1, beanNames.length); + assertEquals("stringRepo", beanNames[0]); } @Test @@ -558,6 +562,78 @@ public class ConfigurationClassPostProcessorTests { beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); assertEquals(1, beanNames.length); assertEquals("stringRepo", beanNames[0]); + + beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); + assertEquals(1, beanNames.length); + assertEquals("stringRepo", beanNames[0]); + } + + @Test + public void genericsBasedInjectionWithEarlyGenericsMatchingAndRawFactoryMethod() { + beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(RawFactoryMethodRepositoryConfiguration.class)); + new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory); + + String[] beanNames = beanFactory.getBeanNamesForType(Repository.class); + assertTrue(ObjectUtils.containsElement(beanNames, "stringRepo")); + + beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); + assertEquals(0, beanNames.length); + + beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); + assertEquals(0, beanNames.length); + } + + @Test + public void genericsBasedInjectionWithLateGenericsMatchingAndRawFactoryMethod() { + beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(RawFactoryMethodRepositoryConfiguration.class)); + new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory); + beanFactory.preInstantiateSingletons(); + + String[] beanNames = beanFactory.getBeanNamesForType(Repository.class); + assertTrue(ObjectUtils.containsElement(beanNames, "stringRepo")); + + beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); + assertEquals(1, beanNames.length); + assertEquals("stringRepo", beanNames[0]); + + beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); + assertEquals(1, beanNames.length); + assertEquals("stringRepo", beanNames[0]); + } + + @Test + public void genericsBasedInjectionWithEarlyGenericsMatchingAndRawInstance() { + beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(RawInstanceRepositoryConfiguration.class)); + new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory); + + String[] beanNames = beanFactory.getBeanNamesForType(Repository.class); + assertTrue(ObjectUtils.containsElement(beanNames, "stringRepo")); + + beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); + assertEquals(1, beanNames.length); + assertEquals("stringRepo", beanNames[0]); + + beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); + assertEquals(1, beanNames.length); + assertEquals("stringRepo", beanNames[0]); + } + + @Test + public void genericsBasedInjectionWithLateGenericsMatchingAndRawInstance() { + beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(RawInstanceRepositoryConfiguration.class)); + new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory); + beanFactory.preInstantiateSingletons(); + + String[] beanNames = beanFactory.getBeanNamesForType(Repository.class); + assertTrue(ObjectUtils.containsElement(beanNames, "stringRepo")); + + beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); + assertEquals(1, beanNames.length); + assertEquals("stringRepo", beanNames[0]); + + beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); + assertEquals(1, beanNames.length); + assertEquals("stringRepo", beanNames[0]); } @Test @@ -577,6 +653,10 @@ public class ConfigurationClassPostProcessorTests { assertEquals(1, beanNames.length); assertEquals("stringRepo", beanNames[0]); + beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); + assertEquals(1, beanNames.length); + assertEquals("stringRepo", beanNames[0]); + assertTrue(AopUtils.isCglibProxy(beanFactory.getBean("stringRepo"))); } @@ -598,12 +678,16 @@ public class ConfigurationClassPostProcessorTests { assertEquals(1, beanNames.length); assertEquals("stringRepo", beanNames[0]); + beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); + assertEquals(1, beanNames.length); + assertEquals("stringRepo", beanNames[0]); + assertTrue(AopUtils.isCglibProxy(beanFactory.getBean("stringRepo"))); } @Test public void genericsBasedInjectionWithLateGenericsMatchingOnCglibProxyAndRawFactoryMethod() { - beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(RawRepositoryConfiguration.class)); + beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(RawFactoryMethodRepositoryConfiguration.class)); new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory); DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator(); autoProxyCreator.setProxyTargetClass(true); @@ -619,6 +703,35 @@ public class ConfigurationClassPostProcessorTests { assertEquals(1, beanNames.length); assertEquals("stringRepo", beanNames[0]); + beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); + assertEquals(1, beanNames.length); + assertEquals("stringRepo", beanNames[0]); + + assertTrue(AopUtils.isCglibProxy(beanFactory.getBean("stringRepo"))); + } + + @Test + public void genericsBasedInjectionWithLateGenericsMatchingOnCglibProxyAndRawInstance() { + beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(RawInstanceRepositoryConfiguration.class)); + new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory); + DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator(); + autoProxyCreator.setProxyTargetClass(true); + autoProxyCreator.setBeanFactory(beanFactory); + beanFactory.addBeanPostProcessor(autoProxyCreator); + beanFactory.registerSingleton("traceInterceptor", new DefaultPointcutAdvisor(new SimpleTraceInterceptor())); + beanFactory.preInstantiateSingletons(); + + String[] beanNames = beanFactory.getBeanNamesForType(Repository.class); + assertTrue(ObjectUtils.containsElement(beanNames, "stringRepo")); + + beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); + assertEquals(1, beanNames.length); + assertEquals("stringRepo", beanNames[0]); + + beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); + assertEquals(1, beanNames.length); + assertEquals("stringRepo", beanNames[0]); + assertTrue(AopUtils.isCglibProxy(beanFactory.getBean("stringRepo"))); } @@ -638,6 +751,10 @@ public class ConfigurationClassPostProcessorTests { assertEquals(1, beanNames.length); assertEquals("stringRepo", beanNames[0]); + beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(RepositoryInterface.class, String.class)); + assertEquals(1, beanNames.length); + assertEquals("stringRepo", beanNames[0]); + assertTrue(AopUtils.isJdkDynamicProxy(beanFactory.getBean("stringRepo"))); } @@ -658,12 +775,16 @@ public class ConfigurationClassPostProcessorTests { assertEquals(1, beanNames.length); assertEquals("stringRepo", beanNames[0]); + beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(RepositoryInterface.class, String.class)); + assertEquals(1, beanNames.length); + assertEquals("stringRepo", beanNames[0]); + assertTrue(AopUtils.isJdkDynamicProxy(beanFactory.getBean("stringRepo"))); } @Test public void genericsBasedInjectionWithLateGenericsMatchingOnJdkProxyAndRawFactoryMethod() { - beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(RawRepositoryConfiguration.class)); + beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(RawFactoryMethodRepositoryConfiguration.class)); new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory); DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator(); autoProxyCreator.setBeanFactory(beanFactory); @@ -678,6 +799,34 @@ public class ConfigurationClassPostProcessorTests { assertEquals(1, beanNames.length); assertEquals("stringRepo", beanNames[0]); + beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(RepositoryInterface.class, String.class)); + assertEquals(1, beanNames.length); + assertEquals("stringRepo", beanNames[0]); + + assertTrue(AopUtils.isJdkDynamicProxy(beanFactory.getBean("stringRepo"))); + } + + @Test + public void genericsBasedInjectionWithLateGenericsMatchingOnJdkProxyAndRawInstance() { + beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(RawInstanceRepositoryConfiguration.class)); + new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory); + DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator(); + autoProxyCreator.setBeanFactory(beanFactory); + beanFactory.addBeanPostProcessor(autoProxyCreator); + beanFactory.registerSingleton("traceInterceptor", new DefaultPointcutAdvisor(new SimpleTraceInterceptor())); + beanFactory.preInstantiateSingletons(); + + String[] beanNames = beanFactory.getBeanNamesForType(RepositoryInterface.class); + assertTrue(ObjectUtils.containsElement(beanNames, "stringRepo")); + + beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(RepositoryInterface.class, String.class)); + assertEquals(1, beanNames.length); + assertEquals("stringRepo", beanNames[0]); + + beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(RepositoryInterface.class, String.class)); + assertEquals(1, beanNames.length); + assertEquals("stringRepo", beanNames[0]); + assertTrue(AopUtils.isJdkDynamicProxy(beanFactory.getBean("stringRepo"))); } @@ -980,7 +1129,7 @@ public class ConfigurationClassPostProcessorTests { } @Configuration - public static class RawRepositoryConfiguration { + public static class RawFactoryMethodRepositoryConfiguration { @Bean public Repository stringRepo() { @@ -994,6 +1143,21 @@ public class ConfigurationClassPostProcessorTests { } @Configuration + public static class RawInstanceRepositoryConfiguration { + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Bean + public Repository<String> stringRepo() { + return new Repository() { + @Override + public String toString() { + return "Repository<String>"; + } + }; + } + } + + @Configuration public static class ScopedRepositoryConfiguration { @Bean diff --git a/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableAsyncTests.java b/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableAsyncTests.java index ed5b11fc..9eb5aefe 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableAsyncTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableAsyncTests.java @@ -161,7 +161,7 @@ public class EnableAsyncTests { Object bean = ctx.getBean(CustomAsyncBean.class); assertTrue(AopUtils.isAopProxy(bean)); boolean isAsyncAdvised = false; - for (Advisor advisor : ((Advised)bean).getAdvisors()) { + for (Advisor advisor : ((Advised) bean).getAdvisors()) { if (advisor instanceof AsyncAnnotationAdvisor) { isAsyncAdvised = true; break; @@ -365,7 +365,8 @@ public class EnableAsyncTests { @EnableAsync static class AsyncConfigWithMockito { - @Bean @Lazy + @Bean + @Lazy public AsyncBean asyncBean() { return Mockito.mock(AsyncBean.class); } diff --git a/spring-context/src/test/java/org/springframework/tests/mock/jndi/ExpectedLookupTemplate.java b/spring-context/src/test/java/org/springframework/tests/mock/jndi/ExpectedLookupTemplate.java index 8cc22aab..00c3d24c 100644 --- a/spring-context/src/test/java/org/springframework/tests/mock/jndi/ExpectedLookupTemplate.java +++ b/spring-context/src/test/java/org/springframework/tests/mock/jndi/ExpectedLookupTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2018 the original author 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,28 +23,29 @@ import javax.naming.NamingException; import org.springframework.jndi.JndiTemplate; /** - * Simple extension of the JndiTemplate class that always returns - * a given object. Very useful for testing. Effectively a mock object. + * Simple extension of the JndiTemplate class that always returns a given object. + * + * <p>Very useful for testing. Effectively a mock object. * * @author Rod Johnson * @author Juergen Hoeller */ public class ExpectedLookupTemplate extends JndiTemplate { - private final Map<String, Object> jndiObjects = new ConcurrentHashMap<String, Object>(); + private final Map<String, Object> jndiObjects = new ConcurrentHashMap<String, Object>(16); /** - * Construct a new JndiTemplate that will always return given objects - * for given names. To be populated through {@code addObject} calls. + * Construct a new JndiTemplate that will always return given objects for + * given names. To be populated through {@code addObject} calls. * @see #addObject(String, Object) */ public ExpectedLookupTemplate() { } /** - * Construct a new JndiTemplate that will always return the - * given object, but honour only requests for the given name. + * Construct a new JndiTemplate that will always return the given object, + * but honour only requests for the given name. * @param name the name the client is expected to look up * @param object the object that will be returned */ @@ -54,8 +55,7 @@ public class ExpectedLookupTemplate extends JndiTemplate { /** - * Add the given object to the list of JNDI objects that this - * template will expose. + * Add the given object to the list of JNDI objects that this template will expose. * @param name the name the client is expected to look up * @param object the object that will be returned */ @@ -63,11 +63,10 @@ public class ExpectedLookupTemplate extends JndiTemplate { this.jndiObjects.put(name, object); } - /** - * If the name is the expected name specified in the constructor, - * return the object provided in the constructor. If the name is - * unexpected, a respective NamingException gets thrown. + * If the name is the expected name specified in the constructor, return the + * object provided in the constructor. If the name is unexpected, a + * respective NamingException gets thrown. */ @Override public Object lookup(String name) throws NamingException { diff --git a/spring-context/src/test/java/org/springframework/tests/mock/jndi/SimpleNamingContextBuilder.java b/spring-context/src/test/java/org/springframework/tests/mock/jndi/SimpleNamingContextBuilder.java index 1ae3b78d..b4fe464e 100644 --- a/spring-context/src/test/java/org/springframework/tests/mock/jndi/SimpleNamingContextBuilder.java +++ b/spring-context/src/test/java/org/springframework/tests/mock/jndi/SimpleNamingContextBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import javax.naming.spi.NamingManager; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.util.Assert; import org.springframework.util.ClassUtils; /** @@ -35,13 +36,14 @@ import org.springframework.util.ClassUtils; * configure JNDI appropriately, so that {@code new InitialContext()} * will expose the required objects. Also usable for standalone applications, * e.g. for binding a JDBC DataSource to a well-known JNDI location, to be - * able to use traditional J2EE data access code outside of a J2EE container. + * able to use traditional Java EE data access code outside of a Java EE + * container. * * <p>There are various choices for DataSource implementations: * <ul> * <li>{@code SingleConnectionDataSource} (using the same Connection for all getConnection calls) * <li>{@code DriverManagerDataSource} (creating a new Connection on each getConnection call) - * <li>Apache's Jakarta Commons DBCP offers {@code org.apache.commons.dbcp.BasicDataSource} (a real pool) + * <li>Apache's Commons DBCP offers {@code org.apache.commons.dbcp.BasicDataSource} (a real pool) * </ul> * * <p>Typical usage in bootstrap code: @@ -98,7 +100,7 @@ public class SimpleNamingContextBuilder implements InitialContextFactoryBuilder /** * If no SimpleNamingContextBuilder is already configuring JNDI, - * create and activate one. Otherwise take the existing activate + * create and activate one. Otherwise take the existing activated * SimpleNamingContextBuilder, clear it and return it. * <p>This is mainly intended for test suites that want to * reinitialize JNDI bindings from scratch repeatedly. @@ -137,12 +139,10 @@ public class SimpleNamingContextBuilder implements InitialContextFactoryBuilder logger.info("Activating simple JNDI environment"); synchronized (initializationLock) { if (!initialized) { - if (NamingManager.hasInitialContextFactoryBuilder()) { - throw new IllegalStateException( + Assert.state(!NamingManager.hasInitialContextFactoryBuilder(), "Cannot activate SimpleNamingContextBuilder: there is already a JNDI provider registered. " + "Note that JNDI is a JVM-wide service, shared at the JVM system class loader level, " + "with no reset option. As a consequence, a JNDI provider must only be registered once per JVM."); - } NamingManager.setInitialContextFactoryBuilder(this); initialized = true; } diff --git a/spring-context/src/test/java/org/springframework/validation/beanvalidation/MethodValidationTests.java b/spring-context/src/test/java/org/springframework/validation/beanvalidation/MethodValidationTests.java index 63746bbb..dbd8b5cd 100644 --- a/spring-context/src/test/java/org/springframework/validation/beanvalidation/MethodValidationTests.java +++ b/spring-context/src/test/java/org/springframework/validation/beanvalidation/MethodValidationTests.java @@ -123,7 +123,16 @@ public class MethodValidationTests { @Test public void testLazyValidatorForMethodValidation() { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext( - LazyMethodValidationConfig.class, CustomValidatorBean.class, MyValidBean.class, MyValidFactoryBean.class); + LazyMethodValidationConfig.class, CustomValidatorBean.class, + MyValidBean.class, MyValidFactoryBean.class); + ctx.getBeansOfType(MyValidInterface.class).values().forEach(bean -> bean.myValidMethod("value", 5)); + } + + @Test + public void testLazyValidatorForMethodValidationWithProxyTargetClass() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext( + LazyMethodValidationConfigWithProxyTargetClass.class, CustomValidatorBean.class, + MyValidBean.class, MyValidFactoryBean.class); ctx.getBeansOfType(MyValidInterface.class).values().forEach(bean -> bean.myValidMethod("value", 5)); } @@ -223,4 +232,17 @@ public class MethodValidationTests { } } + + @Configuration + public static class LazyMethodValidationConfigWithProxyTargetClass { + + @Bean + public static MethodValidationPostProcessor methodValidationPostProcessor(@Lazy Validator validator) { + MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor(); + postProcessor.setValidator(validator); + postProcessor.setProxyTargetClass(true); + return postProcessor; + } + } + } diff --git a/spring-core/src/main/java/org/springframework/asm/package-info.java b/spring-core/src/main/java/org/springframework/asm/package-info.java index dc7bf516..69209083 100644 --- a/spring-core/src/main/java/org/springframework/asm/package-info.java +++ b/spring-core/src/main/java/org/springframework/asm/package-info.java @@ -1,7 +1,7 @@ /** * Spring's repackaging of - * <a href="http://asm.ow2.org">ASM</a> - * (for internal use only). + * <a href="http://asm.ow2.org">ASM 6.0</a> + * (with Spring-specific patches; for internal use only). * * <p>This repackaging technique avoids any potential conflicts with * dependencies on ASM at the application level or from third-party diff --git a/spring-core/src/main/java/org/springframework/cglib/package-info.java b/spring-core/src/main/java/org/springframework/cglib/package-info.java index 85d1758e..f82ef60a 100644 --- a/spring-core/src/main/java/org/springframework/cglib/package-info.java +++ b/spring-core/src/main/java/org/springframework/cglib/package-info.java @@ -1,7 +1,7 @@ /** * Spring's repackaging of - * <a href="http://cglib.sourceforge.net">CGLIB</a> - * (for internal use only). + * <a href="https://github.com/cglib/cglib">CGLIB 3.2</a> + * (with Spring naming strategy; for internal use only). * * <p>This repackaging technique avoids any potential conflicts with * dependencies on CGLIB at the application level or from third-party diff --git a/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java b/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java index 131bb0a1..69de70c4 100644 --- a/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java +++ b/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.core.io.support; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; @@ -170,7 +171,7 @@ import org.springframework.util.StringUtils; * @author Colin Sampaleanu * @author Marius Bogoevici * @author Costin Leau - * @author Phil Webb + * @author Phillip Webb * @since 1.0.2 * @see #CLASSPATH_ALL_URL_PREFIX * @see org.springframework.util.AntPathMatcher @@ -716,10 +717,16 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol try { rootDir = rootDirResource.getFile().getAbsoluteFile(); } - catch (IOException ex) { + catch (FileNotFoundException ex) { + if (logger.isInfoEnabled()) { + logger.info("Cannot search for matching files underneath " + rootDirResource + + " in the file system: " + ex.getMessage()); + } + return Collections.emptySet(); + } + catch (Exception ex) { if (logger.isWarnEnabled()) { - logger.warn("Cannot search for matching files underneath " + rootDirResource + - " because it does not correspond to a directory in the file system", ex); + logger.warn("Failed to resolve " + rootDirResource + " in the file system: " + ex); } return Collections.emptySet(); } diff --git a/spring-core/src/main/java/org/springframework/core/io/support/SpringFactoriesLoader.java b/spring-core/src/main/java/org/springframework/core/io/support/SpringFactoriesLoader.java index fa378ed0..0f3d891b 100644 --- a/spring-core/src/main/java/org/springframework/core/io/support/SpringFactoriesLoader.java +++ b/spring-core/src/main/java/org/springframework/core/io/support/SpringFactoriesLoader.java @@ -57,14 +57,14 @@ import org.springframework.util.StringUtils; */ public abstract class SpringFactoriesLoader { - private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class); - /** * The location to look for factories. * <p>Can be present in multiple JAR files. */ public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; + private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class); + /** * Load and instantiate the factory implementations of the given type from @@ -74,9 +74,9 @@ public abstract class SpringFactoriesLoader { * to obtain all registered factory names. * @param factoryClass the interface or abstract class representing the factory * @param classLoader the ClassLoader to use for loading (can be {@code null} to use the default) - * @see #loadFactoryNames * @throws IllegalArgumentException if any factory implementation class cannot * be loaded or if an error occurs while instantiating any factory + * @see #loadFactoryNames */ public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) { Assert.notNull(factoryClass, "'factoryClass' must not be null"); @@ -103,8 +103,8 @@ public abstract class SpringFactoriesLoader { * @param factoryClass the interface or abstract class representing the factory * @param classLoader the ClassLoader to use for loading resources; can be * {@code null} to use the default - * @see #loadFactories * @throws IllegalArgumentException if an error occurs while loading factory names + * @see #loadFactories */ public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); @@ -115,14 +115,16 @@ public abstract class SpringFactoriesLoader { while (urls.hasMoreElements()) { URL url = urls.nextElement(); Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url)); - String factoryClassNames = properties.getProperty(factoryClassName); - result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames))); + String propertyValue = properties.getProperty(factoryClassName); + for (String factoryName : StringUtils.commaDelimitedListToStringArray(propertyValue)) { + result.add(factoryName.trim()); + } } return result; } catch (IOException ex) { - throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + - "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); + throw new IllegalArgumentException("Unable to load factories from location [" + + FACTORIES_RESOURCE_LOCATION + "]", ex); } } diff --git a/spring-core/src/main/java/org/springframework/objenesis/package-info.java b/spring-core/src/main/java/org/springframework/objenesis/package-info.java index 2c5158a7..71cf5123 100644 --- a/spring-core/src/main/java/org/springframework/objenesis/package-info.java +++ b/spring-core/src/main/java/org/springframework/objenesis/package-info.java @@ -1,7 +1,7 @@ /** * Spring's repackaging of - * <a href="http://objenesis.org">Objenesis 2.1</a> - * (for internal use only). + * <a href="http://objenesis.org">Objenesis 2.6</a> + * (with SpringObjenesis entry point; for internal use only). * * <p>This repackaging technique avoids any potential conflicts with * dependencies on different Objenesis versions at the application diff --git a/spring-core/src/main/java/org/springframework/util/FastByteArrayOutputStream.java b/spring-core/src/main/java/org/springframework/util/FastByteArrayOutputStream.java index 886f92c0..81d761ac 100644 --- a/spring-core/src/main/java/org/springframework/util/FastByteArrayOutputStream.java +++ b/spring-core/src/main/java/org/springframework/util/FastByteArrayOutputStream.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -367,7 +367,7 @@ public class FastByteArrayOutputStream extends OutputStream { else { if (this.nextIndexInCurrentBuffer < this.currentBufferLength) { this.totalBytesRead++; - return this.currentBuffer[this.nextIndexInCurrentBuffer++]; + return this.currentBuffer[this.nextIndexInCurrentBuffer++] & 0xFF; } else { if (this.buffersIterator.hasNext()) { @@ -487,7 +487,7 @@ public class FastByteArrayOutputStream extends OutputStream { /** * Update the message digest with the remaining bytes in this stream. - * @param messageDigest The message digest to update + * @param messageDigest the message digest to update */ @Override public void updateMessageDigest(MessageDigest messageDigest) { @@ -497,7 +497,7 @@ public class FastByteArrayOutputStream extends OutputStream { /** * Update the message digest with the next len bytes in this stream. * Avoids creating new byte arrays and use internal buffers for performance. - * @param messageDigest The message digest to update + * @param messageDigest the message digest to update * @param len how many bytes to read from this stream and use to update the message digest */ @Override diff --git a/spring-core/src/main/java/org/springframework/util/MultiValueMap.java b/spring-core/src/main/java/org/springframework/util/MultiValueMap.java index a526af78..408273db 100644 --- a/spring-core/src/main/java/org/springframework/util/MultiValueMap.java +++ b/spring-core/src/main/java/org/springframework/util/MultiValueMap.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2018 the original author 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,7 +30,7 @@ public interface MultiValueMap<K, V> extends Map<K, List<V>> { /** * Return the first value for the given key. * @param key the key - * @return the first value for the specified key, or {@code null} + * @return the first value for the specified key, or {@code null} if none */ V getFirst(K key); @@ -55,7 +55,7 @@ public interface MultiValueMap<K, V> extends Map<K, List<V>> { void setAll(Map<K, V> values); /** - * Returns the first values contained in this {@code MultiValueMap}. + * Return a {@code Map} with the first values contained in this {@code MultiValueMap}. * @return a single value representation of this map */ Map<K, V> toSingleValueMap(); diff --git a/spring-core/src/main/java/org/springframework/util/ObjectUtils.java b/spring-core/src/main/java/org/springframework/util/ObjectUtils.java index 10db9f54..05a46229 100644 --- a/spring-core/src/main/java/org/springframework/util/ObjectUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ObjectUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -170,7 +170,7 @@ public abstract class ObjectUtils { /** * Check whether the given array of enum constants contains a constant with the given name, * ignoring case when determining a match. - * @param enumValues the enum values to check, typically the product of a call to MyEnum.values() + * @param enumValues the enum values to check, typically obtained via {@code MyEnum.values()} * @param constant the constant name to find (must not be null or empty string) * @return whether the constant has been found in the given array */ @@ -180,15 +180,14 @@ public abstract class ObjectUtils { /** * Check whether the given array of enum constants contains a constant with the given name. - * @param enumValues the enum values to check, typically the product of a call to MyEnum.values() + * @param enumValues the enum values to check, typically obtained via {@code MyEnum.values()} * @param constant the constant name to find (must not be null or empty string) * @param caseSensitive whether case is significant in determining a match * @return whether the constant has been found in the given array */ public static boolean containsConstant(Enum<?>[] enumValues, String constant, boolean caseSensitive) { for (Enum<?> candidate : enumValues) { - if (caseSensitive ? - candidate.toString().equals(constant) : + if (caseSensitive ? candidate.toString().equals(constant) : candidate.toString().equalsIgnoreCase(constant)) { return true; } @@ -199,7 +198,7 @@ public abstract class ObjectUtils { /** * Case insensitive alternative to {@link Enum#valueOf(Class, String)}. * @param <E> the concrete Enum type - * @param enumValues the array of all Enum constants in question, usually per Enum.values() + * @param enumValues the array of all Enum constants in question, usually per {@code Enum.values()} * @param constant the constant to get the enum value of * @throws IllegalArgumentException if the given constant is not found in the given array * of enum values. Use {@link #containsConstant(Enum[], String)} as a guard to avoid this exception. @@ -210,9 +209,8 @@ public abstract class ObjectUtils { return candidate; } } - throw new IllegalArgumentException( - String.format("constant [%s] does not exist in enum type %s", - constant, enumValues.getClass().getComponentType().getName())); + throw new IllegalArgumentException("Constant [" + constant + "] does not exist in enum type " + + enumValues.getClass().getComponentType().getName()); } /** 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 a43d016b..4d02886a 100644 --- a/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java @@ -25,7 +25,6 @@ import java.lang.reflect.UndeclaredThrowableException; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; -import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -635,7 +634,7 @@ public abstract class ReflectionUtils { for (Method ifcMethod : ifc.getMethods()) { if (!Modifier.isAbstract(ifcMethod.getModifiers())) { if (result == null) { - result = new LinkedList<Method>(); + result = new ArrayList<Method>(); } result.add(ifcMethod); } @@ -802,19 +801,8 @@ public abstract class ReflectionUtils { /** - * Pre-built FieldFilter that matches all non-static, non-final fields. - */ - public static final FieldFilter COPYABLE_FIELDS = new FieldFilter() { - - @Override - public boolean matches(Field field) { - return !(Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers())); - } - }; - - - /** * Pre-built MethodFilter that matches all non-bridge methods. + * @since 3.0 */ public static final MethodFilter NON_BRIDGED_METHODS = new MethodFilter() { @@ -828,6 +816,7 @@ public abstract class ReflectionUtils { /** * Pre-built MethodFilter that matches all non-bridge methods * which are not declared on {@code java.lang.Object}. + * @since 3.0.5 */ public static final MethodFilter USER_DECLARED_METHODS = new MethodFilter() { @@ -837,4 +826,16 @@ public abstract class ReflectionUtils { } }; + + /** + * Pre-built FieldFilter that matches all non-static, non-final fields. + */ + public static final FieldFilter COPYABLE_FIELDS = new FieldFilter() { + + @Override + public boolean matches(Field field) { + return !(Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers())); + } + }; + } diff --git a/spring-core/src/test/java/org/springframework/core/io/support/SpringFactoriesLoaderTests.java b/spring-core/src/test/java/org/springframework/core/io/support/SpringFactoriesLoaderTests.java index 4de87d2c..56939b9c 100644 --- a/spring-core/src/test/java/org/springframework/core/io/support/SpringFactoriesLoaderTests.java +++ b/spring-core/src/test/java/org/springframework/core/io/support/SpringFactoriesLoaderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 the original author 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,8 +33,7 @@ public class SpringFactoriesLoaderTests { @Test public void loadFactoriesInCorrectOrder() { - List<DummyFactory> factories = SpringFactoriesLoader - .loadFactories(DummyFactory.class, null); + List<DummyFactory> factories = SpringFactoriesLoader.loadFactories(DummyFactory.class, null); assertEquals(2, factories.size()); assertTrue(factories.get(0) instanceof MyDummyFactory1); assertTrue(factories.get(1) instanceof MyDummyFactory2); @@ -46,9 +45,9 @@ public class SpringFactoriesLoaderTests { } @Test - public void loadPackagePrivateFactory() throws Exception { - List<DummyPackagePrivateFactory> factories = SpringFactoriesLoader - .loadFactories(DummyPackagePrivateFactory.class, null); + public void loadPackagePrivateFactory() { + List<DummyPackagePrivateFactory> factories = + SpringFactoriesLoader.loadFactories(DummyPackagePrivateFactory.class, null); assertEquals(1, factories.size()); assertTrue((factories.get(0).getClass().getModifiers() & Modifier.PUBLIC) == 0); } diff --git a/spring-core/src/test/java/org/springframework/util/FastByteArrayOutputStreamTests.java b/spring-core/src/test/java/org/springframework/util/FastByteArrayOutputStreamTests.java index 15217c87..69bd0466 100644 --- a/spring-core/src/test/java/org/springframework/util/FastByteArrayOutputStreamTests.java +++ b/spring-core/src/test/java/org/springframework/util/FastByteArrayOutputStreamTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 the original author 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,39 +16,34 @@ package org.springframework.util; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; -import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; /** - * Test suite for {@link FastByteArrayOutputStream} + * Test suite for {@link FastByteArrayOutputStream}. + * * @author Craig Andrews */ public class FastByteArrayOutputStreamTests { private static final int INITIAL_CAPACITY = 256; - private FastByteArrayOutputStream os; - - private byte[] helloBytes; + private final FastByteArrayOutputStream os = new FastByteArrayOutputStream(INITIAL_CAPACITY);; - - @Before - public void setUp() throws Exception { - this.os = new FastByteArrayOutputStream(INITIAL_CAPACITY); - this.helloBytes = "Hello World".getBytes("UTF-8"); - } + private final byte[] helloBytes = "Hello World".getBytes(StandardCharsets.UTF_8);; @Test public void size() throws Exception { this.os.write(this.helloBytes); - assertEquals(this.os.size(), this.helloBytes.length); + assertEquals(this.helloBytes.length, this.os.size()); } @Test @@ -124,17 +119,26 @@ public class FastByteArrayOutputStreamTests { @Test public void getInputStreamAvailable() throws Exception { this.os.write(this.helloBytes); - assertEquals(this.os.getInputStream().available(), this.helloBytes.length); + assertEquals(this.helloBytes.length, this.os.getInputStream().available()); } @Test public void getInputStreamRead() throws Exception { this.os.write(this.helloBytes); InputStream inputStream = this.os.getInputStream(); - assertEquals(inputStream.read(), this.helloBytes[0]); - assertEquals(inputStream.read(), this.helloBytes[1]); - assertEquals(inputStream.read(), this.helloBytes[2]); - assertEquals(inputStream.read(), this.helloBytes[3]); + assertEquals(this.helloBytes[0], inputStream.read()); + assertEquals(this.helloBytes[1], inputStream.read()); + assertEquals(this.helloBytes[2], inputStream.read()); + assertEquals(this.helloBytes[3], inputStream.read()); + } + + @Test + public void getInputStreamReadBytePromotion() throws Exception { + byte[] bytes = new byte[] { -1 }; + this.os.write(bytes); + InputStream inputStream = this.os.getInputStream(); + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + assertEquals(bais.read(), inputStream.read()); } @Test @@ -166,9 +170,9 @@ public class FastByteArrayOutputStreamTests { public void getInputStreamSkip() throws Exception { this.os.write(this.helloBytes); InputStream inputStream = this.os.getInputStream(); - assertEquals(inputStream.read(), this.helloBytes[0]); - assertEquals(inputStream.skip(1), 1); - assertEquals(inputStream.read(), this.helloBytes[2]); + assertEquals(this.helloBytes[0], inputStream.read()); + assertEquals(1, inputStream.skip(1)); + assertEquals(this.helloBytes[2], inputStream.read()); assertEquals(this.helloBytes.length - 3, inputStream.available()); } @@ -176,7 +180,7 @@ public class FastByteArrayOutputStreamTests { public void getInputStreamSkipAll() throws Exception { this.os.write(this.helloBytes); InputStream inputStream = this.os.getInputStream(); - assertEquals(inputStream.skip(1000), this.helloBytes.length); + assertEquals(this.helloBytes.length, inputStream.skip(1000)); assertEquals(0, inputStream.available()); } @@ -187,8 +191,7 @@ public class FastByteArrayOutputStreamTests { InputStream inputStream = this.os.getInputStream(); DigestUtils.appendMd5DigestAsHex(inputStream, builder); builder.append("\""); - String actual = builder.toString(); - assertEquals("\"0b10a8db164e0754105b7a99be72e3fe5\"", actual); + assertEquals("\"0b10a8db164e0754105b7a99be72e3fe5\"", builder.toString()); } @Test @@ -201,8 +204,7 @@ public class FastByteArrayOutputStreamTests { InputStream inputStream = this.os.getInputStream(); DigestUtils.appendMd5DigestAsHex(inputStream, builder); builder.append("\""); - String actual = builder.toString(); - assertEquals("\"06225ca1e4533354c516e74512065331d\"", actual); + assertEquals("\"06225ca1e4533354c516e74512065331d\"", builder.toString()); } diff --git a/spring-core/src/test/java/org/springframework/util/ObjectUtilsTests.java b/spring-core/src/test/java/org/springframework/util/ObjectUtilsTests.java index d11ea1f7..c783657c 100644 --- a/spring-core/src/test/java/org/springframework/util/ObjectUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/util/ObjectUtilsTests.java @@ -45,6 +45,7 @@ public class ObjectUtilsTests { @Rule public final ExpectedException exception = ExpectedException.none(); + @Test public void isCheckedException() { assertTrue(ObjectUtils.isCheckedException(new Exception())); @@ -101,8 +102,8 @@ public class ObjectUtilsTests { assertTrue(isEmpty(new Object[0])); assertTrue(isEmpty(new Integer[0])); - assertFalse(isEmpty(new int[] { 42 })); - assertFalse(isEmpty(new Integer[] { new Integer(42) })); + assertFalse(isEmpty(new int[] {42})); + assertFalse(isEmpty(new Integer[] {42})); } @Test @@ -271,7 +272,7 @@ public class ObjectUtilsTests { @Test @Deprecated public void hashCodeWithLong() { - long lng = 883l; + long lng = 883L; int expected = (new Long(lng)).hashCode(); assertEquals(expected, ObjectUtils.hashCode(lng)); } @@ -489,12 +490,12 @@ public class ObjectUtilsTests { @Test public void nullSafeHashCodeWithLongArray() { - long lng = 7993l; + long lng = 7993L; int expected = 31 * 7 + (int) (lng ^ (lng >>> 32)); - lng = 84320l; + lng = 84320L; expected = 31 * expected + (int) (lng ^ (lng >>> 32)); - long[] array = {7993l, 84320l}; + long[] array = {7993L, 84320L}; int actual = ObjectUtils.nullSafeHashCode(array); assertEquals(expected, actual); @@ -715,7 +716,7 @@ public class ObjectUtilsTests { @Test public void nullSafeToStringWithLongArray() { - long[] array = {434l, 23423l}; + long[] array = {434L, 23423L}; assertEquals("{434, 23423}", ObjectUtils.nullSafeToString(array)); } @@ -737,7 +738,7 @@ public class ObjectUtilsTests { @Test public void nullSafeToStringWithObjectArray() { - Object[] array = {"Han", new Long(43)}; + Object[] array = {"Han", Long.valueOf(43)}; assertEquals("{Han, 43}", ObjectUtils.nullSafeToString(array)); } @@ -807,7 +808,8 @@ public class ObjectUtilsTests { assertThat(ObjectUtils.caseInsensitiveValueOf(Tropes.values(), "BAR"), is(Tropes.BAR)); exception.expect(IllegalArgumentException.class); - exception.expectMessage(is("constant [bogus] does not exist in enum type org.springframework.util.ObjectUtilsTests$Tropes")); + exception.expectMessage( + is("Constant [bogus] does not exist in enum type org.springframework.util.ObjectUtilsTests$Tropes")); ObjectUtils.caseInsensitiveValueOf(Tropes.values(), "bogus"); } @@ -818,6 +820,6 @@ public class ObjectUtilsTests { } - enum Tropes { FOO, BAR, baz } + enum Tropes {FOO, BAR, baz} } diff --git a/spring-core/src/test/resources/META-INF/spring.factories b/spring-core/src/test/resources/META-INF/spring.factories index ab64ffe1..3c2c4e9d 100644 --- a/spring-core/src/test/resources/META-INF/spring.factories +++ b/spring-core/src/test/resources/META-INF/spring.factories @@ -1,5 +1,5 @@ -org.springframework.core.io.support.DummyFactory=\ -org.springframework.core.io.support.MyDummyFactory2,\ +org.springframework.core.io.support.DummyFactory =\ +org.springframework.core.io.support.MyDummyFactory2, \ org.springframework.core.io.support.MyDummyFactory1 java.lang.String=\ 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 78a7e91f..8dfa206c 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 @@ -194,7 +194,7 @@ public class StandardEvaluationContext implements EvaluationContext { @Override public TypeConverter getTypeConverter() { if (this.typeConverter == null) { - this.typeConverter = new StandardTypeConverter(); + this.typeConverter = new StandardTypeConverter(); } return this.typeConverter; } @@ -224,7 +224,7 @@ public class StandardEvaluationContext implements EvaluationContext { this.variables.put(name, value); } - public void setVariables(Map<String,Object> variables) { + public void setVariables(Map<String, Object> variables) { this.variables.putAll(variables); } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/BatchUpdateUtils.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/BatchUpdateUtils.java index d18099a6..d1546a9d 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/BatchUpdateUtils.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/BatchUpdateUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author 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,24 +25,29 @@ import java.util.List; * Mainly for internal use within the framework. * * @author Thomas Risberg + * @author Juergen Hoeller * @since 3.0 */ public abstract class BatchUpdateUtils { public static int[] executeBatchUpdate( - String sql, final List<Object[]> batchValues, final int[] columnTypes, JdbcOperations jdbcOperations) { + String sql, final List<Object[]> batchArgs, final int[] columnTypes, JdbcOperations jdbcOperations) { + + if (batchArgs.isEmpty()) { + return new int[0]; + } return jdbcOperations.batchUpdate( sql, new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement ps, int i) throws SQLException { - Object[] values = batchValues.get(i); + Object[] values = batchArgs.get(i); setStatementParameters(values, ps, columnTypes); } @Override public int getBatchSize() { - return batchValues.size(); + return batchArgs.size(); } }); } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java index acd1c034..64aa2a82 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -914,6 +914,7 @@ public interface JdbcOperations { * @param pss ParameterizedPreparedStatementSetter to use * @return an array containing for each batch another array containing the numbers of rows affected * by each update in the batch + * @since 3.1 */ <T> int[][] batchUpdate(String sql, Collection<T> batchArgs, int batchSize, ParameterizedPreparedStatementSetter<T> pss) throws DataAccessException; diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java index 6a2bc943..6f97a196 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1018,11 +1018,7 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { public int[][] doInPreparedStatement(PreparedStatement ps) throws SQLException { List<int[]> rowsAffected = new ArrayList<int[]>(); try { - boolean batchSupported = true; - if (!JdbcUtils.supportsBatchUpdates(ps.getConnection())) { - batchSupported = false; - logger.warn("JDBC Driver does not support Batch updates; resorting to single statement execution"); - } + boolean batchSupported = JdbcUtils.supportsBatchUpdates(ps.getConnection()); int n = 0; for (T obj : batchArgs) { pss.setValues(ps, obj); diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterBatchUpdateUtils.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterBatchUpdateUtils.java index cffc7a85..4430e05b 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterBatchUpdateUtils.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterBatchUpdateUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author 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,15 +28,16 @@ import org.springframework.jdbc.core.JdbcOperations; * Mainly for internal use within the framework. * * @author Thomas Risberg + * @author Juergen Hoeller * @since 3.0 */ public class NamedParameterBatchUpdateUtils extends BatchUpdateUtils { - public static int[] executeBatchUpdateWithNamedParameters(final ParsedSql parsedSql, - final SqlParameterSource[] batchArgs, JdbcOperations jdbcOperations) { + public static int[] executeBatchUpdateWithNamedParameters( + final ParsedSql parsedSql, final SqlParameterSource[] batchArgs, JdbcOperations jdbcOperations) { - if (batchArgs.length <= 0) { - return new int[] {0}; + if (batchArgs.length == 0) { + return new int[0]; } String sqlToUse = NamedParameterUtils.substituteNamedParameters(parsedSql, batchArgs[0]); 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 41a526fb..43d49c87 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 @@ -99,25 +99,25 @@ public abstract class NamedParameterUtils { char c = statement[i]; if (c == ':' || c == '&') { int j = i + 1; - if (j < statement.length && statement[j] == ':' && c == ':') { + if (c == ':' && j < statement.length && statement[j] == ':') { // Postgres-style "::" casting operator should be skipped i = i + 2; continue; } String parameter = null; - if (j < statement.length && c == ':' && statement[j] == '{') { + if (c == ':' && j < statement.length && statement[j] == '{') { // :{x} style parameter - while (j < statement.length && statement[j] != '}') { + while (statement[j] != '}') { j++; + if (j >= statement.length) { + throw new InvalidDataAccessApiUsageException("Non-terminated named parameter declaration " + + "at position " + i + " in statement: " + sql); + } if (statement[j] == ':' || statement[j] == '{') { 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); - } if (j - i > 2) { parameter = sql.substring(i + 2, j); namedParameterCount = addNewNamedParameter(namedParameters, namedParameterCount, parameter); @@ -190,7 +190,7 @@ public abstract class NamedParameterUtils { } /** - * Skip over comments and quoted names present in an SQL statement + * Skip over comments and quoted names present in an SQL statement. * @param statement character array containing SQL statement * @param position current position of statement * @return next position to process after any comments or quotes are skipped diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/object/RdbmsOperation.java b/spring-jdbc/src/main/java/org/springframework/jdbc/object/RdbmsOperation.java index b6ad500c..e285f1b4 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/object/RdbmsOperation.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/object/RdbmsOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,7 +41,7 @@ import org.springframework.util.Assert; * arguments. Subclasses should be JavaBeans, allowing easy configuration. * * <p>This class and subclasses throw runtime exceptions, defined in the - * <codeorg.springframework.dao package</code> (and as thrown by the + * {@code org.springframework.dao} package (and as thrown by the * {@code org.springframework.jdbc.core} package, which the classes * in this package use under the hood to perform raw JDBC operations). * @@ -71,7 +71,7 @@ public abstract class RdbmsOperation implements InitializingBean { private boolean returnGeneratedKeys = false; - private String[] generatedKeysColumnNames = null; + private String[] generatedKeysColumnNames; private String sql; @@ -282,7 +282,7 @@ public abstract class RdbmsOperation implements InitializingBean { * Add one or more declared parameters. Used for configuring this operation * when used in a bean factory. Each parameter will specify SQL type and (optionally) * the parameter's name. - * @param parameters Array containing the declared {@link SqlParameter} objects + * @param parameters an array containing the declared {@link SqlParameter} objects * @see #declaredParameters */ public void setParameters(SqlParameter... parameters) { diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/object/SqlCall.java b/spring-jdbc/src/main/java/org/springframework/jdbc/object/SqlCall.java index ee70d608..eb0d58ff 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/object/SqlCall.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/object/SqlCall.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2018 the original author 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,31 +39,30 @@ import org.springframework.jdbc.core.SqlParameter; public abstract class SqlCall extends RdbmsOperation { /** - * Object enabling us to create CallableStatementCreators - * efficiently, based on this class's declared parameters. - */ - private CallableStatementCreatorFactory callableStatementFactory; - - /** * Flag used to indicate that this call is for a function and to * use the {? = call get_invoice_count(?)} syntax. */ private boolean function = false; /** - * Flag used to indicate that the sql for this call should be used exactly as it is - * defined. No need to add the escape syntax and parameter place holders. + * Flag used to indicate that the sql for this call should be used exactly as + * it is defined. No need to add the escape syntax and parameter place holders. */ private boolean sqlReadyForUse = false; /** * Call string as defined in java.sql.CallableStatement. - * String of form {call add_invoice(?, ?, ?)} - * or {? = call get_invoice_count(?)} if isFunction is set to true - * Updated after each parameter is added. + * String of form {call add_invoice(?, ?, ?)} or {? = call get_invoice_count(?)} + * if isFunction is set to true. Updated after each parameter is added. */ private String callString; + /** + * Object enabling us to create CallableStatementCreators + * efficiently, based on this class's declared parameters. + */ + private CallableStatementCreatorFactory callableStatementFactory; + /** * Constructor to allow use as a JavaBean. @@ -79,8 +78,8 @@ public abstract class SqlCall extends RdbmsOperation { /** * Create a new SqlCall object with SQL, but without parameters. * Must add parameters or settle with none. - * @param ds DataSource to obtain connections from - * @param sql SQL to execute + * @param ds the DataSource to obtain connections from + * @param sql the SQL to execute */ public SqlCall(DataSource ds, String sql) { setDataSource(ds); @@ -99,7 +98,7 @@ public abstract class SqlCall extends RdbmsOperation { * Return whether this call is for a function. */ public boolean isFunction() { - return function; + return this.function; } /** @@ -113,7 +112,7 @@ public abstract class SqlCall extends RdbmsOperation { * Return whether the SQL can be used as is. */ public boolean isSqlReadyForUse() { - return sqlReadyForUse; + return this.sqlReadyForUse; } diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateTests.java index 4e8b2cea..73080c16 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateTests.java @@ -28,6 +28,7 @@ import java.sql.Statement; import java.sql.Types; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -152,76 +153,46 @@ public class JdbcTemplateTests { @Test public void testStringsWithStaticSql() throws Exception { - doTestStrings(null, null, null, null, new JdbcTemplateCallback() { - @Override - public void doInJdbcTemplate(JdbcTemplate template, String sql, RowCallbackHandler rch) { - template.query(sql, rch); - } - }); + doTestStrings(null, null, null, null, (template, sql, rch) -> template.query(sql, rch)); } @Test public void testStringsWithStaticSqlAndFetchSizeAndMaxRows() throws Exception { - doTestStrings(10, 20, 30, null, new JdbcTemplateCallback() { - @Override - public void doInJdbcTemplate(JdbcTemplate template, String sql, RowCallbackHandler rch) { - template.query(sql, rch); - } - }); + doTestStrings(10, 20, 30, null, (template, sql, rch) -> template.query(sql, rch)); } @Test public void testStringsWithEmptyPreparedStatementSetter() throws Exception { - doTestStrings(null, null, null, null, new JdbcTemplateCallback() { - @Override - public void doInJdbcTemplate(JdbcTemplate template, String sql, RowCallbackHandler rch) { - template.query(sql, (PreparedStatementSetter) null, rch); - } - }); + doTestStrings(null, null, null, null, (template, sql, rch) -> + template.query(sql, (PreparedStatementSetter) null, rch)); } @Test public void testStringsWithPreparedStatementSetter() throws Exception { final Integer argument = 99; - doTestStrings(null, null, null, argument, new JdbcTemplateCallback() { - @Override - public void doInJdbcTemplate(JdbcTemplate template, String sql, RowCallbackHandler rch) { - template.query(sql, new PreparedStatementSetter() { - @Override - public void setValues(PreparedStatement ps) throws SQLException { - ps.setObject(1, argument); - } - }, rch); - } - }); + doTestStrings(null, null, null, argument, (template, sql, rch) -> template.query(sql, ps -> { + ps.setObject(1, argument); + }, rch)); } @Test public void testStringsWithEmptyPreparedStatementArgs() throws Exception { - doTestStrings(null, null, null, null, new JdbcTemplateCallback() { - @Override - public void doInJdbcTemplate(JdbcTemplate template, String sql, RowCallbackHandler rch) { - template.query(sql, (Object[]) null, rch); - } - }); + doTestStrings(null, null, null, null, + (template, sql, rch) -> template.query(sql, (Object[]) null, rch)); } @Test public void testStringsWithPreparedStatementArgs() throws Exception { final Integer argument = 99; - doTestStrings(null, null, null, argument, new JdbcTemplateCallback() { - @Override - public void doInJdbcTemplate(JdbcTemplate template, String sql, RowCallbackHandler rch) { - template.query(sql, new Object[] { argument }, rch); - } - }); + doTestStrings(null, null, null, argument, + (template, sql, rch) -> template.query(sql, new Object[] {argument}, rch)); } private void doTestStrings(Integer fetchSize, Integer maxRows, Integer queryTimeout, Object argument, JdbcTemplateCallback jdbcTemplateCallback) throws Exception { String sql = "SELECT FORENAME FROM CUSTMR"; - String[] results = { "rod", "gary", " portia" }; + String[] results = {"rod", "gary", " portia"}; class StringHandler implements RowCallbackHandler { private List<String> list = new LinkedList<>(); @@ -357,11 +328,8 @@ public class JdbcTemplateTests { this.thrown.expect(sameInstance(runtimeException)); try { - this.template.query(sql, new RowCallbackHandler() { - @Override - public void processRow(ResultSet rs) { - throw runtimeException; - } + this.template.query(sql, (RowCallbackHandler) rs -> { + throw runtimeException; }); } finally { @@ -398,10 +366,10 @@ public class JdbcTemplateTests { given(this.preparedStatement.executeUpdate()).willReturn(rowsAffected); int actualRowsAffected = this.template.update(sql, - new Object[] {4, new SqlParameterValue(Types.NUMERIC, 2, new Float(1.4142))}); + 4, new SqlParameterValue(Types.NUMERIC, 2, Float.valueOf(1.4142f))); assertTrue("Actual rows affected is correct", actualRowsAffected == rowsAffected); verify(this.preparedStatement).setObject(1, 4); - verify(this.preparedStatement).setObject(2, new Float(1.4142), Types.NUMERIC, 2); + verify(this.preparedStatement).setObject(2, Float.valueOf(1.4142f), Types.NUMERIC, 2); verify(this.preparedStatement).close(); verify(this.connection).close(); } @@ -463,8 +431,7 @@ public class JdbcTemplateTests { public void testBatchUpdateWithBatchFailure() throws Exception { final String[] sql = {"A", "B", "C", "D"}; given(this.statement.executeBatch()).willThrow( - new BatchUpdateException(new int[] { 1, Statement.EXECUTE_FAILED, 1, - Statement.EXECUTE_FAILED })); + new BatchUpdateException(new int[] {1, Statement.EXECUTE_FAILED, 1, Statement.EXECUTE_FAILED})); mockDatabaseMetaData(true); given(this.connection.createStatement()).willReturn(this.statement); @@ -525,17 +492,15 @@ public class JdbcTemplateTests { @Test public void testBatchUpdateWithPreparedStatement() throws Exception { final String sql = "UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = ?"; - final int[] ids = new int[] { 100, 200 }; - final int[] rowsAffected = new int[] { 1, 2 }; + final int[] ids = new int[] {100, 200}; + final int[] rowsAffected = new int[] {1, 2}; given(this.preparedStatement.executeBatch()).willReturn(rowsAffected); mockDatabaseMetaData(true); - BatchPreparedStatementSetter setter = - new BatchPreparedStatementSetter() { + BatchPreparedStatementSetter setter = new BatchPreparedStatementSetter() { @Override - public void setValues(PreparedStatement ps, int i) - throws SQLException { + public void setValues(PreparedStatement ps, int i) throws SQLException { ps.setInt(1, ids[i]); } @Override @@ -561,8 +526,8 @@ public class JdbcTemplateTests { @Test public void testInterruptibleBatchUpdate() throws Exception { final String sql = "UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = ?"; - final int[] ids = new int[] { 100, 200 }; - final int[] rowsAffected = new int[] { 1, 2 }; + final int[] ids = new int[] {100, 200}; + final int[] rowsAffected = new int[] {1, 2}; given(this.preparedStatement.executeBatch()).willReturn(rowsAffected); mockDatabaseMetaData(true); @@ -602,8 +567,8 @@ public class JdbcTemplateTests { @Test public void testInterruptibleBatchUpdateWithBaseClass() throws Exception { final String sql = "UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = ?"; - final int[] ids = new int[] { 100, 200 }; - final int[] rowsAffected = new int[] { 1, 2 }; + final int[] ids = new int[] {100, 200}; + final int[] rowsAffected = new int[] {1, 2}; given(this.preparedStatement.executeBatch()).willReturn(rowsAffected); mockDatabaseMetaData(true); @@ -639,8 +604,8 @@ public class JdbcTemplateTests { @Test public void testInterruptibleBatchUpdateWithBaseClassAndNoBatchSupport() throws Exception { final String sql = "UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = ?"; - final int[] ids = new int[] { 100, 200 }; - final int[] rowsAffected = new int[] { 1, 2 }; + final int[] ids = new int[] {100, 200}; + final int[] rowsAffected = new int[] {1, 2}; given(this.preparedStatement.executeUpdate()).willReturn(rowsAffected[0], rowsAffected[1]); mockDatabaseMetaData(false); @@ -676,8 +641,8 @@ public class JdbcTemplateTests { @Test public void testBatchUpdateWithPreparedStatementAndNoBatchSupport() throws Exception { final String sql = "UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = ?"; - final int[] ids = new int[] { 100, 200 }; - final int[] rowsAffected = new int[] { 1, 2 }; + final int[] ids = new int[] {100, 200}; + final int[] rowsAffected = new int[] {1, 2}; given(this.preparedStatement.executeUpdate()).willReturn(rowsAffected[0], rowsAffected[1]); @@ -707,7 +672,7 @@ public class JdbcTemplateTests { @Test public void testBatchUpdateFails() throws Exception { final String sql = "UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = ?"; - final int[] ids = new int[] { 100, 200 }; + final int[] ids = new int[] {100, 200}; SQLException sqlException = new SQLException(); given(this.preparedStatement.executeBatch()).willThrow(sqlException); @@ -739,20 +704,27 @@ public class JdbcTemplateTests { } @Test + public void testBatchUpdateWithEmptyList() throws Exception { + final String sql = "UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = ?"; + JdbcTemplate template = new JdbcTemplate(this.dataSource, false); + + int[] actualRowsAffected = template.batchUpdate(sql, Collections.emptyList()); + assertTrue("executed 0 updates", actualRowsAffected.length == 0); + } + + @Test public void testBatchUpdateWithListOfObjectArrays() throws Exception { final String sql = "UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = ?"; - final List<Object[]> ids = new ArrayList<>(); + final List<Object[]> ids = new ArrayList<>(2); ids.add(new Object[] {100}); ids.add(new Object[] {200}); - final int[] rowsAffected = new int[] { 1, 2 }; + final int[] rowsAffected = new int[] {1, 2}; given(this.preparedStatement.executeBatch()).willReturn(rowsAffected); mockDatabaseMetaData(true); - JdbcTemplate template = new JdbcTemplate(this.dataSource, false); int[] actualRowsAffected = template.batchUpdate(sql, ids); - assertTrue("executed 2 updates", actualRowsAffected.length == 2); assertEquals(rowsAffected[0], actualRowsAffected[0]); assertEquals(rowsAffected[1], actualRowsAffected[1]); @@ -767,18 +739,17 @@ public class JdbcTemplateTests { @Test public void testBatchUpdateWithListOfObjectArraysPlusTypeInfo() throws Exception { final String sql = "UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = ?"; - final List<Object[]> ids = new ArrayList<>(); + final List<Object[]> ids = new ArrayList<>(2); ids.add(new Object[] {100}); ids.add(new Object[] {200}); final int[] sqlTypes = new int[] {Types.NUMERIC}; - final int[] rowsAffected = new int[] { 1, 2 }; + final int[] rowsAffected = new int[] {1, 2}; given(this.preparedStatement.executeBatch()).willReturn(rowsAffected); mockDatabaseMetaData(true); - this.template = new JdbcTemplate(this.dataSource, false); - int[] actualRowsAffected = this.template.batchUpdate(sql, ids, sqlTypes); + int[] actualRowsAffected = this.template.batchUpdate(sql, ids, sqlTypes); assertTrue("executed 2 updates", actualRowsAffected.length == 2); assertEquals(rowsAffected[0], actualRowsAffected[0]); assertEquals(rowsAffected[1], actualRowsAffected[1]); @@ -793,23 +764,17 @@ public class JdbcTemplateTests { public void testBatchUpdateWithCollectionOfObjects() throws Exception { final String sql = "UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = ?"; final List<Integer> ids = Arrays.asList(100, 200, 300); - final int[] rowsAffected1 = new int[] { 1, 2 }; - final int[] rowsAffected2 = new int[] { 3 }; + final int[] rowsAffected1 = new int[] {1, 2}; + final int[] rowsAffected2 = new int[] {3}; given(this.preparedStatement.executeBatch()).willReturn(rowsAffected1, rowsAffected2); mockDatabaseMetaData(true); - ParameterizedPreparedStatementSetter<Integer> setter = new ParameterizedPreparedStatementSetter<Integer>() { - @Override - public void setValues(PreparedStatement ps, Integer argument) throws SQLException { - ps.setInt(1, argument.intValue()); - } - }; - + ParameterizedPreparedStatementSetter<Integer> setter = (ps, argument) -> ps.setInt(1, argument.intValue()); JdbcTemplate template = new JdbcTemplate(this.dataSource, false); int[][] actualRowsAffected = template.batchUpdate(sql, ids, 2, setter); - assertTrue("executed 2 updates", actualRowsAffected[0].length == 2); + assertEquals("executed 2 updates", 2, actualRowsAffected[0].length); assertEquals(rowsAffected1[0], actualRowsAffected[0][0]); assertEquals(rowsAffected1[1], actualRowsAffected[0][1]); assertEquals(rowsAffected2[0], actualRowsAffected[1][0]); @@ -823,19 +788,20 @@ public class JdbcTemplateTests { } @Test - public void testCouldntGetConnectionForOperationOrExceptionTranslator() throws SQLException { + public void testCouldNotGetConnectionForOperationOrExceptionTranslator() throws SQLException { SQLException sqlException = new SQLException("foo", "07xxx"); this.dataSource = mock(DataSource.class); given(this.dataSource.getConnection()).willThrow(sqlException); JdbcTemplate template = new JdbcTemplate(this.dataSource, false); RowCountCallbackHandler rcch = new RowCountCallbackHandler(); + this.thrown.expect(CannotGetJdbcConnectionException.class); this.thrown.expect(exceptionCause(sameInstance(sqlException))); template.query("SELECT ID, FORENAME FROM CUSTMR WHERE ID < 3", rcch); } @Test - public void testCouldntGetConnectionForOperationWithLazyExceptionTranslator() throws SQLException { + public void testCouldNotGetConnectionForOperationWithLazyExceptionTranslator() throws SQLException { SQLException sqlException = new SQLException("foo", "07xxx"); this.dataSource = mock(DataSource.class); given(this.dataSource.getConnection()).willThrow(sqlException); @@ -843,30 +809,31 @@ public class JdbcTemplateTests { this.template.setDataSource(this.dataSource); this.template.afterPropertiesSet(); RowCountCallbackHandler rcch = new RowCountCallbackHandler(); + this.thrown.expect(CannotGetJdbcConnectionException.class); this.thrown.expect(exceptionCause(sameInstance(sqlException))); this.template.query("SELECT ID, FORENAME FROM CUSTMR WHERE ID < 3", rcch); } @Test - public void testCouldntGetConnectionInOperationWithExceptionTranslatorInitializedViaBeanProperty() + public void testCouldNotGetConnectionInOperationWithExceptionTranslatorInitializedViaBeanProperty() throws SQLException { - doTestCouldntGetConnectionInOperationWithExceptionTranslatorInitialized(true); + doTestCouldNotGetConnectionInOperationWithExceptionTranslatorInitialized(true); } @Test - public void testCouldntGetConnectionInOperationWithExceptionTranslatorInitializedInAfterPropertiesSet() + public void testCouldNotGetConnectionInOperationWithExceptionTranslatorInitializedInAfterPropertiesSet() throws SQLException { - doTestCouldntGetConnectionInOperationWithExceptionTranslatorInitialized(false); + doTestCouldNotGetConnectionInOperationWithExceptionTranslatorInitialized(false); } /** * If beanProperty is true, initialize via exception translator bean property; * if false, use afterPropertiesSet(). */ - private void doTestCouldntGetConnectionInOperationWithExceptionTranslatorInitialized(boolean beanProperty) + private void doTestCouldNotGetConnectionInOperationWithExceptionTranslatorInitialized(boolean beanProperty) throws SQLException { SQLException sqlException = new SQLException("foo", "07xxx"); @@ -897,14 +864,9 @@ public class JdbcTemplateTests { given(this.preparedStatement.executeUpdate()).willReturn(expectedRowsUpdated); - PreparedStatementSetter pss = new PreparedStatementSetter() { - @Override - public void setValues(PreparedStatement ps) throws SQLException { - ps.setString(1, name); - } - }; + PreparedStatementSetter pss = ps -> ps.setString(1, name); int actualRowsUpdated = new JdbcTemplate(this.dataSource).update(sql, pss); - assertTrue("updated correct # of rows", actualRowsUpdated == expectedRowsUpdated); + assertEquals("updated correct # of rows", actualRowsUpdated, expectedRowsUpdated); verify(this.preparedStatement).setString(1, name); verify(this.preparedStatement).close(); verify(this.connection).close(); @@ -917,12 +879,7 @@ public class JdbcTemplateTests { SQLException sqlException = new SQLException(); given(this.preparedStatement.executeUpdate()).willThrow(sqlException); - PreparedStatementSetter pss = new PreparedStatementSetter() { - @Override - public void setValues(PreparedStatement ps) throws SQLException { - ps.setString(1, name); - } - }; + PreparedStatementSetter pss = ps -> ps.setString(1, name); this.thrown.expect(DataAccessException.class); this.thrown.expect(exceptionCause(sameInstance(sqlException))); try { @@ -936,7 +893,7 @@ public class JdbcTemplateTests { } @Test - public void testCouldntClose() throws Exception { + public void testCouldNotClose() throws Exception { SQLException sqlException = new SQLException("bar"); given(this.connection.createStatement()).willReturn(this.statement); given(this.resultSet.next()).willReturn(false); @@ -966,11 +923,8 @@ public class JdbcTemplateTests { this.thrown.expect(SQLWarningException.class); this.thrown.expect(exceptionCause(sameInstance(warnings))); try { - t.query(sql, new RowCallbackHandler() { - @Override - public void processRow(ResultSet rs) throws SQLException { - rs.getByte(1); - } + t.query(sql, rs -> { + rs.getByte(1); }); } finally { @@ -992,11 +946,8 @@ public class JdbcTemplateTests { // Too long: truncation this.template.setIgnoreWarnings(true); - this.template.query(sql, new RowCallbackHandler() { - @Override - public void processRow(ResultSet rs) throws java.sql.SQLException { - rs.getByte(1); - } + this.template.query(sql, rs -> { + rs.getByte(1); }); verify(this.resultSet).close(); @@ -1016,11 +967,8 @@ public class JdbcTemplateTests { this.thrown.expect(BadSqlGrammarException.class); this.thrown.expect(exceptionCause(sameInstance(sqlException))); try { - this.template.query(sql, new RowCallbackHandler() { - @Override - public void processRow(ResultSet rs) throws SQLException { - throw sqlException; - } + this.template.query(sql, (RowCallbackHandler) rs -> { + throw sqlException; }); fail("Should have thrown BadSqlGrammarException"); } @@ -1047,11 +995,8 @@ public class JdbcTemplateTests { this.thrown.expect(BadSqlGrammarException.class); this.thrown.expect(exceptionCause(sameInstance(sqlException))); try { - template.query(sql, new RowCallbackHandler() { - @Override - public void processRow(ResultSet rs) throws SQLException { - throw sqlException; - } + template.query(sql, (RowCallbackHandler) rs -> { + throw sqlException; }); } finally { @@ -1084,11 +1029,8 @@ public class JdbcTemplateTests { this.thrown.expect(BadSqlGrammarException.class); this.thrown.expect(exceptionCause(sameInstance(sqlException))); try { - template.query(sql, new RowCallbackHandler() { - @Override - public void processRow(ResultSet rs) throws SQLException { - throw sqlException; - } + template.query(sql, (RowCallbackHandler) rs -> { + throw sqlException; }); } finally { @@ -1204,34 +1146,22 @@ public class JdbcTemplateTests { given(this.connection.createStatement()).willReturn(this.statement); try { - this.template.query("my query", new ResultSetExtractor<Object>() { - @Override - public Object extractData(ResultSet rs) { - throw new InvalidDataAccessApiUsageException(""); - } + this.template.query("my query", (ResultSetExtractor<Object>) rs -> { + throw new InvalidDataAccessApiUsageException(""); }); fail("Should have thrown InvalidDataAccessApiUsageException"); } - catch (InvalidDataAccessApiUsageException idaauex) { + catch (InvalidDataAccessApiUsageException ex) { // ok } try { - this.template.query(new PreparedStatementCreator() { - @Override - public PreparedStatement createPreparedStatement(Connection con) - throws SQLException { - return con.prepareStatement("my query"); - } - }, new ResultSetExtractor<Object>() { - @Override - public Object extractData(ResultSet rs2) { - throw new InvalidDataAccessApiUsageException(""); - } + this.template.query(con -> con.prepareStatement("my query"), (ResultSetExtractor<Object>) rs2 -> { + throw new InvalidDataAccessApiUsageException(""); }); fail("Should have thrown InvalidDataAccessApiUsageException"); } - catch (InvalidDataAccessApiUsageException idaauex) { + catch (InvalidDataAccessApiUsageException ex) { // ok } @@ -1247,24 +1177,13 @@ public class JdbcTemplateTests { given(this.callableStatement.execute()).willReturn(true); given(this.callableStatement.getUpdateCount()).willReturn(-1); - List<SqlParameter> params = new ArrayList<>(); - params.add(new SqlReturnResultSet("", new RowCallbackHandler() { - @Override - public void processRow(ResultSet rs) { - throw new InvalidDataAccessApiUsageException(""); - } - - })); + SqlParameter param = new SqlReturnResultSet("", (RowCallbackHandler) rs -> { + throw new InvalidDataAccessApiUsageException(""); + }); this.thrown.expect(InvalidDataAccessApiUsageException.class); try { - this.template.call(new CallableStatementCreator() { - @Override - public CallableStatement createCallableStatement(Connection conn) - throws SQLException { - return conn.prepareCall("my query"); - } - }, params); + this.template.call(conn -> conn.prepareCall("my query"), Collections.singletonList(param)); } finally { verify(this.resultSet).close(); @@ -1275,7 +1194,6 @@ public class JdbcTemplateTests { @Test public void testCaseInsensitiveResultsMap() throws Exception { - given(this.callableStatement.execute()).willReturn(false); given(this.callableStatement.getUpdateCount()).willReturn(-1); given(this.callableStatement.getObject(1)).willReturn("X"); @@ -1287,16 +1205,8 @@ public class JdbcTemplateTests { assertTrue("now it should have been set to case insensitive", this.template.isResultsMapCaseInsensitive()); - List<SqlParameter> params = new ArrayList<>(); - params.add(new SqlOutParameter("a", 12)); - - Map<String, Object> out = this.template.call(new CallableStatementCreator() { - @Override - public CallableStatement createCallableStatement(Connection conn) - throws SQLException { - return conn.prepareCall("my query"); - } - }, params); + Map<String, Object> out = this.template.call( + conn -> conn.prepareCall("my query"), Collections.singletonList(new SqlOutParameter("a", 12))); assertThat(out, instanceOf(LinkedCaseInsensitiveMap.class)); assertNotNull("we should have gotten the result with upper case", out.get("A")); diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplateTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplateTests.java index 56a3bbf4..0b17406c 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplateTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplateTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,14 +34,10 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.springframework.dao.DataAccessException; import org.springframework.jdbc.Customer; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.PreparedStatementCallback; -import org.springframework.jdbc.core.ResultSetExtractor; -import org.springframework.jdbc.core.RowCallbackHandler; -import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.SqlParameterValue; import static org.junit.Assert.*; @@ -55,32 +51,40 @@ import static org.mockito.BDDMockito.*; public class NamedParameterJdbcTemplateTests { private static final String SELECT_NAMED_PARAMETERS = - "select id, forename from custmr where id = :id and country = :country"; + "select id, forename from custmr where id = :id and country = :country"; private static final String SELECT_NAMED_PARAMETERS_PARSED = - "select id, forename from custmr where id = ? and country = ?"; + "select id, forename from custmr where id = ? and country = ?"; private static final String SELECT_NO_PARAMETERS = "select id, forename from custmr"; private static final String UPDATE_NAMED_PARAMETERS = - "update seat_status set booking_id = null where performance_id = :perfId and price_band_id = :priceId"; + "update seat_status set booking_id = null where performance_id = :perfId and price_band_id = :priceId"; private static final String UPDATE_NAMED_PARAMETERS_PARSED = - "update seat_status set booking_id = null where performance_id = ? and price_band_id = ?"; + "update seat_status set booking_id = null where performance_id = ? and price_band_id = ?"; private static final String[] COLUMN_NAMES = new String[] {"id", "forename"}; + @Rule public ExpectedException thrown = ExpectedException.none(); private Connection connection; + private DataSource dataSource; + private PreparedStatement preparedStatement; + private ResultSet resultSet; + private DatabaseMetaData databaseMetaData; - private Map<String, Object> params = new HashMap<String, Object>(); + + private Map<String, Object> params = new HashMap<>(); + private NamedParameterJdbcTemplate namedParameterTemplate; + @Before - public void setUp() throws Exception { + public void setup() throws Exception { connection = mock(Connection.class); dataSource = mock(DataSource.class); preparedStatement = mock(PreparedStatement.class); @@ -95,14 +99,15 @@ public class NamedParameterJdbcTemplateTests { given(databaseMetaData.supportsBatchUpdates()).willReturn(true); } + @Test - public void testNullDataSourceProvidedToCtor() throws Exception { + public void testNullDataSourceProvidedToCtor() { thrown.expect(IllegalArgumentException.class); new NamedParameterJdbcTemplate((DataSource) null); } @Test - public void testNullJdbcTemplateProvidedToCtor() throws Exception { + public void testNullJdbcTemplateProvidedToCtor() { thrown.expect(IllegalArgumentException.class); new NamedParameterJdbcTemplate((JdbcOperations) null); } @@ -114,14 +119,10 @@ public class NamedParameterJdbcTemplateTests { params.put("perfId", 1); params.put("priceId", 1); Object result = namedParameterTemplate.execute(UPDATE_NAMED_PARAMETERS, params, - new PreparedStatementCallback<Object>() { - @Override - public Object doInPreparedStatement(PreparedStatement ps) - throws SQLException { - assertEquals(preparedStatement, ps); - ps.executeUpdate(); - return "result"; - } + (PreparedStatementCallback<Object>) ps -> { + assertEquals(preparedStatement, ps); + ps.executeUpdate(); + return "result"; }); assertEquals("result", result); @@ -139,14 +140,10 @@ public class NamedParameterJdbcTemplateTests { params.put("perfId", new SqlParameterValue(Types.DECIMAL, 1)); params.put("priceId", new SqlParameterValue(Types.INTEGER, 1)); Object result = namedParameterTemplate.execute(UPDATE_NAMED_PARAMETERS, params, - new PreparedStatementCallback<Object>() { - @Override - public Object doInPreparedStatement(PreparedStatement ps) - throws SQLException { - assertEquals(preparedStatement, ps); - ps.executeUpdate(); - return "result"; - } + (PreparedStatementCallback<Object>) ps -> { + assertEquals(preparedStatement, ps); + ps.executeUpdate(); + return "result"; }); assertEquals("result", result); @@ -162,14 +159,10 @@ public class NamedParameterJdbcTemplateTests { given(preparedStatement.executeUpdate()).willReturn(1); Object result = namedParameterTemplate.execute(SELECT_NO_PARAMETERS, - new PreparedStatementCallback<Object>() { - @Override - public Object doInPreparedStatement(PreparedStatement ps) - throws SQLException { - assertEquals(preparedStatement, ps); - ps.executeQuery(); - return "result"; - } + (PreparedStatementCallback<Object>) ps -> { + assertEquals(preparedStatement, ps); + ps.executeQuery(); + return "result"; }); assertEquals("result", result); @@ -187,16 +180,12 @@ public class NamedParameterJdbcTemplateTests { params.put("id", new SqlParameterValue(Types.DECIMAL, 1)); params.put("country", "UK"); Customer cust = namedParameterTemplate.query(SELECT_NAMED_PARAMETERS, params, - new ResultSetExtractor<Customer>() { - @Override - public Customer extractData(ResultSet rs) throws SQLException, - DataAccessException { - rs.next(); - Customer cust = new Customer(); - cust.setId(rs.getInt(COLUMN_NAMES[0])); - cust.setForename(rs.getString(COLUMN_NAMES[1])); - return cust; - } + rs -> { + rs.next(); + Customer cust1 = new Customer(); + cust1.setId(rs.getInt(COLUMN_NAMES[0])); + cust1.setForename(rs.getString(COLUMN_NAMES[1])); + return cust1; }); assertTrue("Customer id was assigned correctly", cust.getId() == 1); @@ -215,16 +204,12 @@ public class NamedParameterJdbcTemplateTests { given(resultSet.getString("forename")).willReturn("rod"); Customer cust = namedParameterTemplate.query(SELECT_NO_PARAMETERS, - new ResultSetExtractor<Customer>() { - @Override - public Customer extractData(ResultSet rs) throws SQLException, - DataAccessException { - rs.next(); - Customer cust = new Customer(); - cust.setId(rs.getInt(COLUMN_NAMES[0])); - cust.setForename(rs.getString(COLUMN_NAMES[1])); - return cust; - } + rs -> { + rs.next(); + Customer cust1 = new Customer(); + cust1.setId(rs.getInt(COLUMN_NAMES[0])); + cust1.setForename(rs.getString(COLUMN_NAMES[1])); + return cust1; }); assertTrue("Customer id was assigned correctly", cust.getId() == 1); @@ -242,15 +227,12 @@ public class NamedParameterJdbcTemplateTests { params.put("id", new SqlParameterValue(Types.DECIMAL, 1)); params.put("country", "UK"); - final List<Customer> customers = new LinkedList<Customer>(); - namedParameterTemplate.query(SELECT_NAMED_PARAMETERS, params, new RowCallbackHandler() { - @Override - public void processRow(ResultSet rs) throws SQLException { - Customer cust = new Customer(); - cust.setId(rs.getInt(COLUMN_NAMES[0])); - cust.setForename(rs.getString(COLUMN_NAMES[1])); - customers.add(cust); - } + final List<Customer> customers = new LinkedList<>(); + namedParameterTemplate.query(SELECT_NAMED_PARAMETERS, params, rs -> { + Customer cust = new Customer(); + cust.setId(rs.getInt(COLUMN_NAMES[0])); + cust.setForename(rs.getString(COLUMN_NAMES[1])); + customers.add(cust); }); assertEquals(1, customers.size()); @@ -269,15 +251,12 @@ public class NamedParameterJdbcTemplateTests { given(resultSet.getInt("id")).willReturn(1); given(resultSet.getString("forename")).willReturn("rod"); - final List<Customer> customers = new LinkedList<Customer>(); - namedParameterTemplate.query(SELECT_NO_PARAMETERS, new RowCallbackHandler() { - @Override - public void processRow(ResultSet rs) throws SQLException { - Customer cust = new Customer(); - cust.setId(rs.getInt(COLUMN_NAMES[0])); - cust.setForename(rs.getString(COLUMN_NAMES[1])); - customers.add(cust); - } + final List<Customer> customers = new LinkedList<>(); + namedParameterTemplate.query(SELECT_NO_PARAMETERS, rs -> { + Customer cust = new Customer(); + cust.setId(rs.getInt(COLUMN_NAMES[0])); + cust.setForename(rs.getString(COLUMN_NAMES[1])); + customers.add(cust); }); assertEquals(1, customers.size()); @@ -297,14 +276,11 @@ public class NamedParameterJdbcTemplateTests { params.put("id", new SqlParameterValue(Types.DECIMAL, 1)); params.put("country", "UK"); List<Customer> customers = namedParameterTemplate.query(SELECT_NAMED_PARAMETERS, params, - new RowMapper<Customer>() { - @Override - public Customer mapRow(ResultSet rs, int rownum) throws SQLException { - Customer cust = new Customer(); - cust.setId(rs.getInt(COLUMN_NAMES[0])); - cust.setForename(rs.getString(COLUMN_NAMES[1])); - return cust; - } + (rs, rownum) -> { + Customer cust = new Customer(); + cust.setId(rs.getInt(COLUMN_NAMES[0])); + cust.setForename(rs.getString(COLUMN_NAMES[1])); + return cust; }); assertEquals(1, customers.size()); assertTrue("Customer id was assigned correctly", customers.get(0).getId() == 1); @@ -323,14 +299,11 @@ public class NamedParameterJdbcTemplateTests { given(resultSet.getString("forename")).willReturn("rod"); List<Customer> customers = namedParameterTemplate.query(SELECT_NO_PARAMETERS, - new RowMapper<Customer>() { - @Override - public Customer mapRow(ResultSet rs, int rownum) throws SQLException { - Customer cust = new Customer(); - cust.setId(rs.getInt(COLUMN_NAMES[0])); - cust.setForename(rs.getString(COLUMN_NAMES[1])); - return cust; - } + (rs, rownum) -> { + Customer cust = new Customer(); + cust.setId(rs.getInt(COLUMN_NAMES[0])); + cust.setForename(rs.getString(COLUMN_NAMES[1])); + return cust; }); assertEquals(1, customers.size()); assertTrue("Customer id was assigned correctly", customers.get(0).getId() == 1); @@ -349,14 +322,11 @@ public class NamedParameterJdbcTemplateTests { params.put("id", new SqlParameterValue(Types.DECIMAL, 1)); params.put("country", "UK"); Customer cust = namedParameterTemplate.queryForObject(SELECT_NAMED_PARAMETERS, params, - new RowMapper<Customer>() { - @Override - public Customer mapRow(ResultSet rs, int rownum) throws SQLException { - Customer cust = new Customer(); - cust.setId(rs.getInt(COLUMN_NAMES[0])); - cust.setForename(rs.getString(COLUMN_NAMES[1])); - return cust; - } + (rs, rownum) -> { + Customer cust1 = new Customer(); + cust1.setId(rs.getInt(COLUMN_NAMES[0])); + cust1.setForename(rs.getString(COLUMN_NAMES[1])); + return cust1; }); assertTrue("Customer id was assigned correctly", cust.getId() == 1); assertTrue("Customer forename was assigned correctly", cust.getForename().equals("rod")); @@ -405,15 +375,14 @@ public class NamedParameterJdbcTemplateTests { final Map<String, Integer>[] ids = new Map[2]; ids[0] = Collections.singletonMap("id", 100); ids[1] = Collections.singletonMap("id", 200); - final int[] rowsAffected = new int[] { 1, 2 }; + final int[] rowsAffected = new int[] {1, 2}; given(preparedStatement.executeBatch()).willReturn(rowsAffected); given(connection.getMetaData()).willReturn(databaseMetaData); + namedParameterTemplate = new NamedParameterJdbcTemplate(new JdbcTemplate(dataSource, false)); - JdbcTemplate template = new JdbcTemplate(dataSource, false); - namedParameterTemplate = new NamedParameterJdbcTemplate(template); - int[] actualRowsAffected = namedParameterTemplate.batchUpdate("UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = :id", ids); - + int[] actualRowsAffected = namedParameterTemplate.batchUpdate( + "UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = :id", ids); assertTrue("executed 2 updates", actualRowsAffected.length == 2); assertEquals(rowsAffected[0], actualRowsAffected[0]); assertEquals(rowsAffected[1], actualRowsAffected[1]); @@ -426,19 +395,29 @@ public class NamedParameterJdbcTemplateTests { } @Test + public void testBatchUpdateWithEmptyMap() throws Exception { + @SuppressWarnings("unchecked") + final Map<String, Integer>[] ids = new Map[0]; + namedParameterTemplate = new NamedParameterJdbcTemplate(new JdbcTemplate(dataSource, false)); + + int[] actualRowsAffected = namedParameterTemplate.batchUpdate( + "UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = :id", ids); + assertTrue("executed 0 updates", actualRowsAffected.length == 0); + } + + @Test public void testBatchUpdateWithSqlParameterSource() throws Exception { SqlParameterSource[] ids = new SqlParameterSource[2]; ids[0] = new MapSqlParameterSource("id", 100); ids[1] = new MapSqlParameterSource("id", 200); - final int[] rowsAffected = new int[] { 1, 2 }; + final int[] rowsAffected = new int[] {1, 2}; given(preparedStatement.executeBatch()).willReturn(rowsAffected); given(connection.getMetaData()).willReturn(databaseMetaData); + namedParameterTemplate = new NamedParameterJdbcTemplate(new JdbcTemplate(dataSource, false)); - JdbcTemplate template = new JdbcTemplate(dataSource, false); - namedParameterTemplate = new NamedParameterJdbcTemplate(template); - int[] actualRowsAffected = namedParameterTemplate.batchUpdate("UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = :id", ids); - + int[] actualRowsAffected = namedParameterTemplate.batchUpdate( + "UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = :id", ids); assertTrue("executed 2 updates", actualRowsAffected.length == 2); assertEquals(rowsAffected[0], actualRowsAffected[0]); assertEquals(rowsAffected[1], actualRowsAffected[1]); @@ -455,15 +434,14 @@ public class NamedParameterJdbcTemplateTests { SqlParameterSource[] ids = new SqlParameterSource[2]; ids[0] = new MapSqlParameterSource().addValue("id", 100, Types.NUMERIC); ids[1] = new MapSqlParameterSource().addValue("id", 200, Types.NUMERIC); - final int[] rowsAffected = new int[] { 1, 2 }; + final int[] rowsAffected = new int[] {1, 2}; given(preparedStatement.executeBatch()).willReturn(rowsAffected); given(connection.getMetaData()).willReturn(databaseMetaData); + namedParameterTemplate = new NamedParameterJdbcTemplate(new JdbcTemplate(dataSource, false)); - JdbcTemplate template = new JdbcTemplate(dataSource, false); - namedParameterTemplate = new NamedParameterJdbcTemplate(template); - int[] actualRowsAffected = namedParameterTemplate.batchUpdate("UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = :id", ids); - + int[] actualRowsAffected = namedParameterTemplate.batchUpdate( + "UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = :id", ids); assertTrue("executed 2 updates", actualRowsAffected.length == 2); assertEquals(rowsAffected[0], actualRowsAffected[0]); assertEquals(rowsAffected[1], actualRowsAffected[1]); diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterUtilsTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterUtilsTests.java index 949634f4..3cf4eba6 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterUtilsTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterUtilsTests.java @@ -52,11 +52,11 @@ public class NamedParameterUtilsTests { assertEquals(2, psql2.getTotalParameterCount()); assertEquals(1, psql2.getNamedParameterCount()); - String sql3 = "xxx &a+:b" + '\t' + ":c%10 yyyy ? zzzzz"; + String sql3 = "xxx &ä+:ö" + '\t' + ":ü%10 yyyy ? zzzzz"; ParsedSql psql3 = NamedParameterUtils.parseSqlStatement(sql3); - assertEquals("a", psql3.getParameterNames().get(0)); - assertEquals("b", psql3.getParameterNames().get(1)); - assertEquals("c", psql3.getParameterNames().get(2)); + assertEquals("ä", psql3.getParameterNames().get(0)); + assertEquals("ö", psql3.getParameterNames().get(1)); + assertEquals("ü", psql3.getParameterNames().get(2)); } @Test @@ -117,13 +117,13 @@ public class NamedParameterUtilsTests { } @Test(expected = InvalidDataAccessApiUsageException.class) - public void buildValueArrayWithMissingParameterValue() throws Exception { + public void buildValueArrayWithMissingParameterValue() { String sql = "select count(0) from foo where id = :id"; NamedParameterUtils.buildValueArray(sql, Collections.<String, Object>emptyMap()); } @Test - public void substituteNamedParametersWithStringContainingQuotes() throws Exception { + public void substituteNamedParametersWithStringContainingQuotes() { String expectedSql = "select 'first name' from artists where id = ? and quote = 'exsqueeze me?'"; String sql = "select 'first name' from artists where id = :id and quote = 'exsqueeze me?'"; String newSql = NamedParameterUtils.substituteNamedParameters(sql, new MapSqlParameterSource()); @@ -131,7 +131,7 @@ public class NamedParameterUtilsTests { } @Test - public void testParseSqlStatementWithStringContainingQuotes() throws Exception { + public void testParseSqlStatementWithStringContainingQuotes() { String expectedSql = "select 'first name' from artists where id = ? and quote = 'exsqueeze me?'"; String sql = "select 'first name' from artists where id = :id and quote = 'exsqueeze me?'"; ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(sql); @@ -173,7 +173,7 @@ public class NamedParameterUtilsTests { } @Test // SPR-4612 - public void parseSqlStatementWithPostgresCasting() throws Exception { + public void parseSqlStatementWithPostgresCasting() { String expectedSql = "select 'first name' from artists where id = ? and birth_date=?::timestamp"; String sql = "select 'first name' from artists where id = :id and birth_date=:birthDate::timestamp"; ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(sql); @@ -181,7 +181,7 @@ public class NamedParameterUtilsTests { } @Test // SPR-13582 - public void parseSqlStatementWithPostgresContainedOperator() throws Exception { + public void parseSqlStatementWithPostgresContainedOperator() { String expectedSql = "select 'first name' from artists where info->'stat'->'albums' = ?? ? and '[\"1\",\"2\",\"3\"]'::jsonb ?? '4'"; String sql = "select 'first name' from artists where info->'stat'->'albums' = ?? :album and '[\"1\",\"2\",\"3\"]'::jsonb ?? '4'"; ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(sql); @@ -190,7 +190,7 @@ public class NamedParameterUtilsTests { } @Test // SPR-15382 - public void parseSqlStatementWithPostgresAnyArrayStringsExistsOperator() throws Exception { + public void parseSqlStatementWithPostgresAnyArrayStringsExistsOperator() { String expectedSql = "select '[\"3\", \"11\"]'::jsonb ?| '{1,3,11,12,17}'::text[]"; String sql = "select '[\"3\", \"11\"]'::jsonb ?| '{1,3,11,12,17}'::text[]"; @@ -200,7 +200,7 @@ public class NamedParameterUtilsTests { } @Test // SPR-15382 - public void parseSqlStatementWithPostgresAllArrayStringsExistsOperator() throws Exception { + public void parseSqlStatementWithPostgresAllArrayStringsExistsOperator() { String expectedSql = "select '[\"3\", \"11\"]'::jsonb ?& '{1,3,11,12,17}'::text[] AND ? = 'Back in Black'"; String sql = "select '[\"3\", \"11\"]'::jsonb ?& '{1,3,11,12,17}'::text[] AND :album = 'Back in Black'"; @@ -210,7 +210,7 @@ public class NamedParameterUtilsTests { } @Test // SPR-7476 - public void parseSqlStatementWithEscapedColon() throws Exception { + public void parseSqlStatementWithEscapedColon() { String expectedSql = "select '0\\:0' as a, foo from bar where baz < DATE(? 23:59:59) and baz = ?"; String sql = "select '0\\:0' as a, foo from bar where baz < DATE(:p1 23\\:59\\:59) and baz = :p2"; @@ -223,7 +223,7 @@ public class NamedParameterUtilsTests { } @Test // SPR-7476 - public void parseSqlStatementWithBracketDelimitedParameterNames() throws Exception { + public void parseSqlStatementWithBracketDelimitedParameterNames() { String expectedSql = "select foo from bar where baz = b??z"; String sql = "select foo from bar where baz = b:{p1}:{p2}z"; @@ -236,7 +236,7 @@ public class NamedParameterUtilsTests { } @Test // SPR-7476 - public void parseSqlStatementWithEmptyBracketsOrBracketsInQuotes() throws Exception { + public void parseSqlStatementWithEmptyBracketsOrBracketsInQuotes() { String expectedSql = "select foo from bar where baz = b:{}z"; String sql = "select foo from bar where baz = b:{}z"; ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(sql); @@ -257,7 +257,7 @@ public class NamedParameterUtilsTests { public void parseSqlStatementWithSingleLetterInBrackets() { String expectedSql = "select foo from bar where baz = b?z"; String sql = "select foo from bar where baz = b:{p}z"; - + ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(sql); assertEquals(1, parsedSql.getParameterNames().size()); assertEquals("p", parsedSql.getParameterNames().get(0)); @@ -273,14 +273,14 @@ public class NamedParameterUtilsTests { } @Test // SPR-2544 - public void substituteNamedParametersWithLogicalAnd() throws Exception { + public void substituteNamedParametersWithLogicalAnd() { String expectedSql = "xxx & yyyy"; String newSql = NamedParameterUtils.substituteNamedParameters(expectedSql, new MapSqlParameterSource()); assertEquals(expectedSql, newSql); } @Test // SPR-3173 - public void variableAssignmentOperator() throws Exception { + public void variableAssignmentOperator() { String expectedSql = "x := 1"; String newSql = NamedParameterUtils.substituteNamedParameters(expectedSql, new MapSqlParameterSource()); assertEquals(expectedSql, newSql); diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethod.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethod.java index 0d0a3415..ce00d3ad 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethod.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethod.java @@ -118,7 +118,7 @@ public class InvocableHandlerMethod extends HandlerMethod { } /** - * Get the method argument values for the current request. + * Get the method argument values for the current message. */ private Object[] getMethodArgumentValues(Message<?> message, Object... providedArgs) throws Exception { MethodParameter[] parameters = getMethodParameters(); diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/broker/DefaultSubscriptionRegistry.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/broker/DefaultSubscriptionRegistry.java index 49673a5b..9691ecea 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/broker/DefaultSubscriptionRegistry.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/broker/DefaultSubscriptionRegistry.java @@ -65,7 +65,7 @@ public class DefaultSubscriptionRegistry extends AbstractSubscriptionRegistry { public static final int DEFAULT_CACHE_LIMIT = 1024; /** Static evaluation context to reuse */ - private static EvaluationContext messageEvalContext = + private static final EvaluationContext messageEvalContext = SimpleEvaluationContext.forPropertyAccessors(new SimpMessageHeaderPropertyAccessor()).build(); @@ -128,7 +128,7 @@ public class DefaultSubscriptionRegistry extends AbstractSubscriptionRegistry { * @since 4.2 */ public void setSelectorHeaderName(String selectorHeaderName) { - this.selectorHeaderName = StringUtils.hasText(selectorHeaderName) ? selectorHeaderName : null; + this.selectorHeaderName = (StringUtils.hasText(selectorHeaderName) ? selectorHeaderName : null); } /** diff --git a/spring-orm-hibernate4/src/test/java/org/springframework/validation/hibernatevalidator5/MethodValidationTests.java b/spring-orm-hibernate4/src/test/java/org/springframework/validation/hibernatevalidator5/MethodValidationTests.java index 4a7454b4..0dc53b93 100644 --- a/spring-orm-hibernate4/src/test/java/org/springframework/validation/hibernatevalidator5/MethodValidationTests.java +++ b/spring-orm-hibernate4/src/test/java/org/springframework/validation/hibernatevalidator5/MethodValidationTests.java @@ -127,7 +127,16 @@ public class MethodValidationTests { @Test public void testLazyValidatorForMethodValidation() { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext( - LazyMethodValidationConfig.class, CustomValidatorBean.class, MyValidBean.class, MyValidFactoryBean.class); + LazyMethodValidationConfig.class, CustomValidatorBean.class, + MyValidBean.class, MyValidFactoryBean.class); + ctx.getBeansOfType(MyValidInterface.class).values().forEach(bean -> bean.myValidMethod("value", 5)); + } + + @Test + public void testLazyValidatorForMethodValidationWithProxyTargetClass() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext( + LazyMethodValidationConfigWithProxyTargetClass.class, CustomValidatorBean.class, + MyValidBean.class, MyValidFactoryBean.class); ctx.getBeansOfType(MyValidInterface.class).values().forEach(bean -> bean.myValidMethod("value", 5)); } @@ -227,4 +236,17 @@ public class MethodValidationTests { } } + + @Configuration + public static class LazyMethodValidationConfigWithProxyTargetClass { + + @Bean + public static MethodValidationPostProcessor methodValidationPostProcessor(@Lazy Validator validator) { + MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor(); + postProcessor.setValidator(validator); + postProcessor.setProxyTargetClass(true); + return postProcessor; + } + } + } diff --git a/spring-test/src/main/java/org/springframework/mock/jndi/ExpectedLookupTemplate.java b/spring-test/src/main/java/org/springframework/mock/jndi/ExpectedLookupTemplate.java index 7bffe4de..0a6b40fa 100644 --- a/spring-test/src/main/java/org/springframework/mock/jndi/ExpectedLookupTemplate.java +++ b/spring-test/src/main/java/org/springframework/mock/jndi/ExpectedLookupTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -53,6 +53,7 @@ public class ExpectedLookupTemplate extends JndiTemplate { addObject(name, object); } + /** * Add the given object to the list of JNDI objects that this template will expose. * @param name the name the client is expected to look up diff --git a/spring-test/src/main/java/org/springframework/mock/jndi/SimpleNamingContextBuilder.java b/spring-test/src/main/java/org/springframework/mock/jndi/SimpleNamingContextBuilder.java index d12a968f..fc759e05 100644 --- a/spring-test/src/main/java/org/springframework/mock/jndi/SimpleNamingContextBuilder.java +++ b/spring-test/src/main/java/org/springframework/mock/jndi/SimpleNamingContextBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import javax.naming.spi.NamingManager; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.util.Assert; import org.springframework.util.ClassUtils; /** @@ -35,7 +36,8 @@ import org.springframework.util.ClassUtils; * configure JNDI appropriately, so that {@code new InitialContext()} * will expose the required objects. Also usable for standalone applications, * e.g. for binding a JDBC DataSource to a well-known JNDI location, to be - * able to use traditional J2EE data access code outside of a J2EE container. + * able to use traditional Java EE data access code outside of a Java EE + * container. * * <p>There are various choices for DataSource implementations: * <ul> @@ -98,7 +100,7 @@ public class SimpleNamingContextBuilder implements InitialContextFactoryBuilder /** * If no SimpleNamingContextBuilder is already configuring JNDI, - * create and activate one. Otherwise take the existing activate + * create and activate one. Otherwise take the existing activated * SimpleNamingContextBuilder, clear it and return it. * <p>This is mainly intended for test suites that want to * reinitialize JNDI bindings from scratch repeatedly. @@ -137,12 +139,10 @@ public class SimpleNamingContextBuilder implements InitialContextFactoryBuilder logger.info("Activating simple JNDI environment"); synchronized (initializationLock) { if (!initialized) { - if (NamingManager.hasInitialContextFactoryBuilder()) { - throw new IllegalStateException( + Assert.state(!NamingManager.hasInitialContextFactoryBuilder(), "Cannot activate SimpleNamingContextBuilder: there is already a JNDI provider registered. " + "Note that JNDI is a JVM-wide service, shared at the JVM system class loader level, " + "with no reset option. As a consequence, a JNDI provider must only be registered once per JVM."); - } NamingManager.setInitialContextFactoryBuilder(this); initialized = true; } diff --git a/spring-tx/src/main/java/org/springframework/transaction/annotation/Transactional.java b/spring-tx/src/main/java/org/springframework/transaction/annotation/Transactional.java index eeccff5c..68f7df3f 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/annotation/Transactional.java +++ b/spring-tx/src/main/java/org/springframework/transaction/annotation/Transactional.java @@ -27,7 +27,12 @@ import org.springframework.core.annotation.AliasFor; import org.springframework.transaction.TransactionDefinition; /** - * Describes transaction attributes on a method or class. + * Describes a transaction attribute on an individual method or on a class. + * + * <p>At the class level, this annotation applies as a default to all methods of + * the declaring class and its subclasses. Note that it does not apply to ancestor + * classes up the class hierarchy; methods need to be locally redeclared in order + * to participate in a subclass-level annotation. * * <p>This annotation type is generally directly comparable to Spring's * {@link org.springframework.transaction.interceptor.RuleBasedTransactionAttribute} diff --git a/spring-web/src/main/java/org/springframework/http/HttpStatus.java b/spring-web/src/main/java/org/springframework/http/HttpStatus.java index ac65c21c..0d4e2467 100644 --- a/spring-web/src/main/java/org/springframework/http/HttpStatus.java +++ b/spring-web/src/main/java/org/springframework/http/HttpStatus.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -420,57 +420,61 @@ public enum HttpStatus { } /** + * Return the HTTP status series of this status code. + * @see HttpStatus.Series + */ + public Series series() { + return Series.valueOf(this); + } + + /** * Whether this status code is in the HTTP series * {@link org.springframework.http.HttpStatus.Series#INFORMATIONAL}. * This is a shortcut for checking the value of {@link #series()}. + * @see #series() */ public boolean is1xxInformational() { - return Series.INFORMATIONAL.equals(series()); + return (series() == Series.INFORMATIONAL); } /** * Whether this status code is in the HTTP series * {@link org.springframework.http.HttpStatus.Series#SUCCESSFUL}. * This is a shortcut for checking the value of {@link #series()}. + * @see #series() */ public boolean is2xxSuccessful() { - return Series.SUCCESSFUL.equals(series()); + return (series() == Series.SUCCESSFUL); } /** * Whether this status code is in the HTTP series * {@link org.springframework.http.HttpStatus.Series#REDIRECTION}. * This is a shortcut for checking the value of {@link #series()}. + * @see #series() */ public boolean is3xxRedirection() { - return Series.REDIRECTION.equals(series()); + return (series() == Series.REDIRECTION); } - /** * Whether this status code is in the HTTP series * {@link org.springframework.http.HttpStatus.Series#CLIENT_ERROR}. * This is a shortcut for checking the value of {@link #series()}. + * @see #series() */ public boolean is4xxClientError() { - return Series.CLIENT_ERROR.equals(series()); + return (series() == Series.CLIENT_ERROR); } /** * Whether this status code is in the HTTP series * {@link org.springframework.http.HttpStatus.Series#SERVER_ERROR}. * This is a shortcut for checking the value of {@link #series()}. + * @see #series() */ public boolean is5xxServerError() { - return Series.SERVER_ERROR.equals(series()); - } - - /** - * Returns the HTTP status series of this status code. - * @see HttpStatus.Series - */ - public Series series() { - return Series.valueOf(this); + return (series() == Series.SERVER_ERROR); } /** @@ -523,18 +527,30 @@ public enum HttpStatus { return this.value; } - public static Series valueOf(int status) { - int seriesCode = status / 100; + /** + * Return the enum constant of this type with the corresponding series. + * @param status a standard HTTP status enum value + * @return the enum constant of this type with the corresponding series + * @throws IllegalArgumentException if this enum has no corresponding constant + */ + public static Series valueOf(HttpStatus status) { + return valueOf(status.value); + } + + /** + * Return the enum constant of this type with the corresponding series. + * @param statusCode the HTTP status code (potentially non-standard) + * @return the enum constant of this type with the corresponding series + * @throws IllegalArgumentException if this enum has no corresponding constant + */ + public static Series valueOf(int statusCode) { + int seriesCode = statusCode / 100; for (Series series : values()) { if (series.value == seriesCode) { return series; } } - throw new IllegalArgumentException("No matching constant for [" + status + "]"); - } - - public static Series valueOf(HttpStatus status) { - return valueOf(status.value); + throw new IllegalArgumentException("No matching constant for [" + statusCode + "]"); } } diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestPart.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestPart.java index 8f27d5c8..191053cb 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestPart.java +++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestPart.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 the original author 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,23 +33,24 @@ import org.springframework.web.multipart.MultipartResolver; * Annotation that can be used to associate the part of a "multipart/form-data" request * with a method argument. * - * <p>Supported method argument types include {@link MultipartFile} - * in conjunction with Spring's {@link MultipartResolver} abstraction, - * {@code javax.servlet.http.Part} in conjunction with Servlet 3.0 multipart requests, - * or otherwise for any other method argument, the content of the part is passed through an - * {@link HttpMessageConverter} taking into consideration the 'Content-Type' header - * of the request part. This is analogous to what @{@link RequestBody} does to resolve - * an argument based on the content of a non-multipart regular request. + * <p>Supported method argument types include {@link MultipartFile} in conjunction with + * Spring's {@link MultipartResolver} abstraction, {@code javax.servlet.http.Part} in + * conjunction with Servlet 3.0 multipart requests, or otherwise for any other method + * argument, the content of the part is passed through an {@link HttpMessageConverter} + * taking into consideration the 'Content-Type' header of the request part. This is + * analogous to what @{@link RequestBody} does to resolve an argument based on the + * content of a non-multipart regular request. * - * <p>Note that @{@link RequestParam} annotation can also be used to associate the - * part of a "multipart/form-data" request with a method argument supporting the same - * method argument types. The main difference is that when the method argument is not a - * String, @{@link RequestParam} relies on type conversion via a registered - * {@link Converter} or {@link PropertyEditor} while @{@link RequestPart} relies - * on {@link HttpMessageConverter}s taking into consideration the 'Content-Type' header - * of the request part. @{@link RequestParam} is likely to be used with name-value form - * fields while @{@link RequestPart} is likely to be used with parts containing more - * complex content (e.g. JSON, XML). + * <p>Note that @{@link RequestParam} annotation can also be used to associate the part + * of a "multipart/form-data" request with a method argument supporting the same method + * argument types. The main difference is that when the method argument is not a String + * or raw {@code MultipartFile} / {@code Part}, {@code @RequestParam} relies on type + * conversion via a registered {@link Converter} or {@link PropertyEditor} while + * {@link RequestPart} relies on {@link HttpMessageConverter HttpMessageConverters} + * taking into consideration the 'Content-Type' header of the request part. + * {@link RequestParam} is likely to be used with name-value form fields while + * {@link RequestPart} is likely to be used with parts containing more complex content + * e.g. JSON, XML). * * @author Rossen Stoyanchev * @author Arjen Poutsma diff --git a/spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java b/spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java index 467a4767..9c84f776 100644 --- a/spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java +++ b/spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java @@ -43,7 +43,11 @@ import org.springframework.util.FileCopyUtils; public class DefaultResponseErrorHandler implements ResponseErrorHandler { /** - * Delegates to {@link #hasError(HttpStatus)} with the response status code. + * Delegates to {@link #hasError(HttpStatus)} (for a standard status enum value) or + * {@link #hasError(int)} (for an unknown status code) with the response status code. + * @see ClientHttpResponse#getRawStatusCode() + * @see #hasError(HttpStatus) + * @see #hasError(int) */ @Override public boolean hasError(ClientHttpResponse response) throws IOException { @@ -53,7 +57,7 @@ public class DefaultResponseErrorHandler implements ResponseErrorHandler { return hasError(statusCode); } } - return false; + return hasError(rawStatusCode); } /** @@ -62,13 +66,31 @@ public class DefaultResponseErrorHandler implements ResponseErrorHandler { * {@link HttpStatus.Series#CLIENT_ERROR CLIENT_ERROR} or * {@link HttpStatus.Series#SERVER_ERROR SERVER_ERROR}. * Can be overridden in subclasses. - * @param statusCode the HTTP status code - * @return {@code true} if the response has an error; {@code false} otherwise - * @see #getHttpStatusCode(ClientHttpResponse) + * @param statusCode the HTTP status code as enum value + * @return {@code true} if the response indicates an error; {@code false} otherwise + * @see HttpStatus#is4xxClientError() + * @see HttpStatus#is5xxServerError() */ protected boolean hasError(HttpStatus statusCode) { - return (statusCode.series() == HttpStatus.Series.CLIENT_ERROR || - statusCode.series() == HttpStatus.Series.SERVER_ERROR); + return (statusCode.is4xxClientError() || statusCode.is5xxServerError()); + } + + /** + * Template method called from {@link #hasError(ClientHttpResponse)}. + * <p>The default implementation checks if the given status code is + * {@code HttpStatus.Series#CLIENT_ERROR CLIENT_ERROR} or + * {@code HttpStatus.Series#SERVER_ERROR SERVER_ERROR}. + * Can be overridden in subclasses. + * @param unknownStatusCode the HTTP status code as raw value + * @return {@code true} if the response indicates an error; {@code false} otherwise + * @since 4.3.21 + * @see HttpStatus.Series#CLIENT_ERROR + * @see HttpStatus.Series#SERVER_ERROR + */ + protected boolean hasError(int unknownStatusCode) { + int seriesCode = unknownStatusCode / 100; + return (seriesCode == HttpStatus.Series.CLIENT_ERROR.value() || + seriesCode == HttpStatus.Series.SERVER_ERROR.value()); } /** @@ -93,7 +115,6 @@ public class DefaultResponseErrorHandler implements ResponseErrorHandler { } } - /** * Determine the HTTP status of the given response. * <p>Note: Only called from {@link #handleError}, not from {@link #hasError}. diff --git a/spring-web/src/main/java/org/springframework/web/client/ResponseErrorHandler.java b/spring-web/src/main/java/org/springframework/web/client/ResponseErrorHandler.java index 2ad6db45..c8056239 100644 --- a/spring-web/src/main/java/org/springframework/web/client/ResponseErrorHandler.java +++ b/spring-web/src/main/java/org/springframework/web/client/ResponseErrorHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,7 +34,7 @@ public interface ResponseErrorHandler { * <p>Implementations will typically inspect the * {@link ClientHttpResponse#getStatusCode() HttpStatus} of the response. * @param response the response to inspect - * @return {@code true} if the response has an error; {@code false} otherwise + * @return {@code true} if the response indicates an error; {@code false} otherwise * @throws IOException in case of I/O errors */ boolean hasError(ClientHttpResponse response) throws IOException; diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/MapMethodProcessor.java b/spring-web/src/main/java/org/springframework/web/method/annotation/MapMethodProcessor.java index 07effd2c..30f41dfc 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/MapMethodProcessor.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/MapMethodProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -56,7 +56,7 @@ public class MapMethodProcessor implements HandlerMethodArgumentResolver, Handle } @Override - @SuppressWarnings({ "unchecked", "rawtypes" }) + @SuppressWarnings({"unchecked", "rawtypes"}) public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMapMethodArgumentResolver.java b/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMapMethodArgumentResolver.java index 39fce258..2471d034 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMapMethodArgumentResolver.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMapMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 the original author 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,7 +37,7 @@ import org.springframework.web.method.support.ModelAndViewContainer; * * <p>The created {@link Map} contains all request parameter name/value pairs. * If the method parameter type is {@link MultiValueMap} instead, the created - * map contains all request parameters and all there values for cases where + * map contains all request parameters and all their values for cases where * request parameters have multiple values. * * @author Arjen Poutsma @@ -50,22 +50,16 @@ public class RequestParamMapMethodArgumentResolver implements HandlerMethodArgum @Override public boolean supportsParameter(MethodParameter parameter) { RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class); - if (requestParam != null) { - if (Map.class.isAssignableFrom(parameter.getParameterType())) { - return !StringUtils.hasText(requestParam.name()); - } - } - return false; + return (requestParam != null && Map.class.isAssignableFrom(parameter.getParameterType()) && + !StringUtils.hasText(requestParam.name())); } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { - Class<?> paramType = parameter.getParameterType(); - Map<String, String[]> parameterMap = webRequest.getParameterMap(); - if (MultiValueMap.class.isAssignableFrom(paramType)) { + if (MultiValueMap.class.isAssignableFrom(parameter.getParameterType())) { MultiValueMap<String, String> result = new LinkedMultiValueMap<String, String>(parameterMap.size()); for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) { for (String value : entry.getValue()) { @@ -84,4 +78,5 @@ public class RequestParamMapMethodArgumentResolver implements HandlerMethodArgum return result; } } + } diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolver.java b/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolver.java index 50335548..55c3ba26 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolver.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -80,6 +80,7 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod /** + * Create a new {@link RequestParamMethodArgumentResolver} instance. * @param useDefaultResolution in default resolution mode a method argument * that is a simple type, as defined in {@link BeanUtils#isSimpleProperty}, * is treated as a request parameter even if it isn't annotated, the @@ -90,6 +91,7 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod } /** + * Create a new {@link RequestParamMethodArgumentResolver} instance. * @param beanFactory a bean factory used for resolving ${...} placeholder * and #{...} SpEL expressions in default values, or {@code null} if default * values are not expected to contain expressions @@ -108,15 +110,11 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod * Supports the following: * <ul> * <li>@RequestParam-annotated method arguments. - * This excludes {@link Map} params where the annotation doesn't - * specify a name. See {@link RequestParamMapMethodArgumentResolver} - * instead for such params. - * <li>Arguments of type {@link MultipartFile} - * unless annotated with @{@link RequestPart}. - * <li>Arguments of type {@code javax.servlet.http.Part} - * unless annotated with @{@link RequestPart}. - * <li>In default resolution mode, simple type arguments - * even if not with @{@link RequestParam}. + * This excludes {@link Map} params where the annotation does not specify a name. + * See {@link RequestParamMapMethodArgumentResolver} instead for such params. + * <li>Arguments of type {@link MultipartFile} unless annotated with @{@link RequestPart}. + * <li>Arguments of type {@code Part} unless annotated with @{@link RequestPart}. + * <li>In default resolution mode, simple type arguments even if not with @{@link RequestParam}. * </ul> */ @Override diff --git a/spring-web/src/test/java/org/springframework/web/client/DefaultResponseErrorHandlerTests.java b/spring-web/src/test/java/org/springframework/web/client/DefaultResponseErrorHandlerTests.java index 1c5e911a..4684a2d7 100644 --- a/spring-web/src/test/java/org/springframework/web/client/DefaultResponseErrorHandlerTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/DefaultResponseErrorHandlerTests.java @@ -103,8 +103,20 @@ public class DefaultResponseErrorHandlerTests { handler.handleError(response); } + @Test // SPR-16108 + public void hasErrorForUnknownStatusCode() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.TEXT_PLAIN); + + given(response.getRawStatusCode()).willReturn(999); + given(response.getStatusText()).willReturn("Custom status code"); + given(response.getHeaders()).willReturn(headers); + + assertFalse(handler.hasError(response)); + } + @Test(expected = UnknownHttpStatusCodeException.class) // SPR-9406 - public void unknownStatusCode() throws Exception { + public void handleErrorUnknownStatusCode() throws Exception { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.TEXT_PLAIN); @@ -115,23 +127,59 @@ public class DefaultResponseErrorHandlerTests { handler.handleError(response); } - @Test // SPR-16108 - public void hasErrorForUnknownStatusCode() throws Exception { + @Test // SPR-17461 + public void hasErrorForCustomClientError() throws Exception { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.TEXT_PLAIN); - given(response.getRawStatusCode()).willReturn(999); + given(response.getRawStatusCode()).willReturn(499); given(response.getStatusText()).willReturn("Custom status code"); given(response.getHeaders()).willReturn(headers); - assertFalse(handler.hasError(response)); + assertTrue(handler.hasError(response)); + } + + @Test(expected = UnknownHttpStatusCodeException.class) + public void handleErrorForCustomClientError() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.TEXT_PLAIN); + + given(response.getStatusCode()).willThrow(new IllegalArgumentException("No matching constant for 499")); + given(response.getStatusText()).willReturn("Custom status code"); + given(response.getHeaders()).willReturn(headers); + + handler.handleError(response); + } + + @Test // SPR-17461 + public void hasErrorForCustomServerError() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.TEXT_PLAIN); + + given(response.getRawStatusCode()).willReturn(599); + given(response.getStatusText()).willReturn("Custom status code"); + given(response.getHeaders()).willReturn(headers); + + assertTrue(handler.hasError(response)); + } + + @Test(expected = UnknownHttpStatusCodeException.class) + public void handleErrorForCustomServerError() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.TEXT_PLAIN); + + given(response.getStatusCode()).willThrow(new IllegalArgumentException("No matching constant for 599")); + given(response.getStatusText()).willReturn("Custom status code"); + given(response.getHeaders()).willReturn(headers); + + handler.handleError(response); } @Test // SPR-16604 public void bodyAvailableAfterHasErrorForUnknownStatusCode() throws Exception { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.TEXT_PLAIN); - TestByteArrayInputStream body = new TestByteArrayInputStream("Hello World".getBytes("UTF-8")); + TestByteArrayInputStream body = new TestByteArrayInputStream("Hello World".getBytes(UTF8)); given(response.getRawStatusCode()).willReturn(999); given(response.getStatusText()).willReturn("Custom status code"); diff --git a/spring-web/src/test/java/org/springframework/web/method/annotation/RequestHeaderMapMethodArgumentResolverTests.java b/spring-web/src/test/java/org/springframework/web/method/annotation/RequestHeaderMapMethodArgumentResolverTests.java index ef2a3f01..8b8bddd3 100644 --- a/spring-web/src/test/java/org/springframework/web/method/annotation/RequestHeaderMapMethodArgumentResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/method/annotation/RequestHeaderMapMethodArgumentResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -104,7 +104,7 @@ public class RequestHeaderMapMethodArgumentResolverTests { request.addHeader(name, value1); request.addHeader(name, value2); - MultiValueMap<String, String> expected = new LinkedMultiValueMap<String, String>(1); + MultiValueMap<String, String> expected = new LinkedMultiValueMap<>(1); expected.add(name, value1); expected.add(name, value2); @@ -135,9 +135,8 @@ public class RequestHeaderMapMethodArgumentResolverTests { public void params(@RequestHeader Map<?, ?> param1, - @RequestHeader MultiValueMap<?, ?> param2, - @RequestHeader HttpHeaders param3, - Map<?,?> unsupported) { + @RequestHeader MultiValueMap<?, ?> param2, @RequestHeader HttpHeaders param3, + Map<?, ?> unsupported) { } } diff --git a/spring-web/src/test/java/org/springframework/web/method/annotation/RequestHeaderMethodArgumentResolverTests.java b/spring-web/src/test/java/org/springframework/web/method/annotation/RequestHeaderMethodArgumentResolverTests.java index a453abb0..db2e766c 100644 --- a/spring-web/src/test/java/org/springframework/web/method/annotation/RequestHeaderMethodArgumentResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/method/annotation/RequestHeaderMethodArgumentResolverTests.java @@ -44,7 +44,7 @@ import org.springframework.web.context.support.GenericWebApplicationContext; import static org.junit.Assert.*; /** - * Test fixture with {@link org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver}. + * Test fixture with {@link RequestHeaderMethodArgumentResolver}. * * @author Arjen Poutsma * @author Rossen Stoyanchev @@ -70,7 +70,7 @@ public class RequestHeaderMethodArgumentResolverTests { @Before @SuppressWarnings("resource") - public void setUp() throws Exception { + public void setup() throws Exception { GenericWebApplicationContext context = new GenericWebApplicationContext(); context.refresh(); resolver = new RequestHeaderMethodArgumentResolver(context.getBeanFactory()); @@ -94,7 +94,7 @@ public class RequestHeaderMethodArgumentResolverTests { } @After - public void teardown() { + public void reset() { RequestContextHolder.resetRequestAttributes(); } diff --git a/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMapMethodArgumentResolverTests.java b/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMapMethodArgumentResolverTests.java index cae73f46..757dbe95 100644 --- a/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMapMethodArgumentResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMapMethodArgumentResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 the original author 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,10 +59,10 @@ public class RequestParamMapMethodArgumentResolverTests { @Before - public void setUp() throws Exception { + public void setup() throws Exception { resolver = new RequestParamMapMethodArgumentResolver(); - Method method = getClass().getMethod("params", Map.class, MultiValueMap.class, Map.class, Map.class); + Method method = getClass().getMethod("handle", Map.class, MultiValueMap.class, Map.class, Map.class); paramMap = new SynthesizingMethodParameter(method, 0); paramMultiValueMap = new SynthesizingMethodParameter(method, 1); paramNamedMap = new SynthesizingMethodParameter(method, 2); @@ -99,9 +99,9 @@ public class RequestParamMapMethodArgumentResolverTests { String name = "foo"; String value1 = "bar"; String value2 = "baz"; - request.addParameter(name, new String[]{value1, value2}); + request.addParameter(name, value1, value2); - MultiValueMap<String, String> expected = new LinkedMultiValueMap<String, String>(1); + MultiValueMap<String, String> expected = new LinkedMultiValueMap<>(1); expected.add(name, value1); expected.add(name, value2); @@ -112,10 +112,11 @@ public class RequestParamMapMethodArgumentResolverTests { } - public void params(@RequestParam Map<?, ?> param1, - @RequestParam MultiValueMap<?, ?> param2, - @RequestParam("name") Map<?, ?> param3, - Map<?, ?> param4) { + public void handle( + @RequestParam Map<?, ?> param1, + @RequestParam MultiValueMap<?, ?> param2, + @RequestParam("name") Map<?, ?> param3, + Map<?, ?> param4) { } } diff --git a/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolverTests.java b/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolverTests.java index 881e1d28..9d88a246 100644 --- a/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -94,7 +94,7 @@ public class RequestParamMethodArgumentResolverTests { @Before - public void setUp() throws Exception { + public void setup() throws Exception { resolver = new RequestParamMethodArgumentResolver(null, true); ParameterNameDiscoverer paramNameDiscoverer = new LocalVariableTableParameterNameDiscoverer(); Method method = ReflectionUtils.findMethod(getClass(), "handle", (Class<?>[]) null); @@ -369,7 +369,7 @@ public class RequestParamMethodArgumentResolverTests { WebDataBinderFactory binderFactory = mock(WebDataBinderFactory.class); given(binderFactory.createBinder(webRequest, null, "stringNotAnnot")).willReturn(binder); - this.request.addParameter("stringNotAnnot", ""); + request.addParameter("stringNotAnnot", ""); Object arg = resolver.resolveArgument(paramStringNotAnnot, null, webRequest, binderFactory); assertNull(arg); @@ -383,7 +383,7 @@ public class RequestParamMethodArgumentResolverTests { WebDataBinderFactory binderFactory = mock(WebDataBinderFactory.class); given(binderFactory.createBinder(webRequest, null, "name")).willReturn(binder); - this.request.addParameter("name", ""); + request.addParameter("name", ""); Object arg = resolver.resolveArgument(paramNotRequired, null, webRequest, binderFactory); assertNull(arg); @@ -406,21 +406,21 @@ public class RequestParamMethodArgumentResolverTests { @Test // SPR-10180 public void resolveEmptyValueToDefault() throws Exception { - this.request.addParameter("name", ""); + request.addParameter("name", ""); Object result = resolver.resolveArgument(paramNamedDefaultValueString, null, webRequest, null); assertEquals("bar", result); } @Test public void resolveEmptyValueWithoutDefault() throws Exception { - this.request.addParameter("stringNotAnnot", ""); + request.addParameter("stringNotAnnot", ""); Object result = resolver.resolveArgument(paramStringNotAnnot, null, webRequest, null); assertEquals("", result); } @Test public void resolveEmptyValueRequiredWithoutDefault() throws Exception { - this.request.addParameter("name", ""); + request.addParameter("name", ""); Object result = resolver.resolveArgument(paramRequired, null, webRequest, null); assertEquals("", result); } @@ -435,7 +435,7 @@ public class RequestParamMethodArgumentResolverTests { Object result = resolver.resolveArgument(paramOptional, null, webRequest, binderFactory); assertEquals(Optional.empty(), result); - this.request.addParameter("name", "123"); + request.addParameter("name", "123"); result = resolver.resolveArgument(paramOptional, null, webRequest, binderFactory); assertEquals(Optional.class, result.getClass()); assertEquals(123, ((Optional) result).get()); @@ -466,7 +466,7 @@ public class RequestParamMethodArgumentResolverTests { Object result = resolver.resolveArgument(paramOptionalArray, null, webRequest, binderFactory); assertEquals(Optional.empty(), result); - this.request.addParameter("name", "123", "456"); + request.addParameter("name", "123", "456"); result = resolver.resolveArgument(paramOptionalArray, null, webRequest, binderFactory); assertEquals(Optional.class, result.getClass()); assertArrayEquals(new Integer[] {123, 456}, (Integer[]) ((Optional) result).get()); @@ -497,7 +497,7 @@ public class RequestParamMethodArgumentResolverTests { Object result = resolver.resolveArgument(paramOptionalList, null, webRequest, binderFactory); assertEquals(Optional.empty(), result); - this.request.addParameter("name", "123", "456"); + request.addParameter("name", "123", "456"); result = resolver.resolveArgument(paramOptionalList, null, webRequest, binderFactory); assertEquals(Optional.class, result.getClass()); assertEquals(Arrays.asList("123", "456"), ((Optional) result).get()); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerMapping.java index 117bff35..da03f81f 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2018 the original author 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,6 +54,13 @@ import javax.servlet.http.HttpServletRequest; public interface HandlerMapping { /** + * Name of the {@link HttpServletRequest} attribute that contains the mapped + * handler for the best matching pattern. + * @since 4.3.21 + */ + String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler"; + + /** * Name of the {@link HttpServletRequest} attribute that contains the path * within the handler mapping, in case of a pattern match, or the full * relevant URI (typically within the DispatcherServlet's mapping) else. diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerExceptionResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerExceptionResolver.java index b401b525..98c33b9d 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerExceptionResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerExceptionResolver.java @@ -132,8 +132,8 @@ public abstract class AbstractHandlerExceptionResolver implements HandlerExcepti ModelAndView result = doResolveException(request, response, handler, ex); if (result != null) { // Print warn message when warn logger is not enabled... - if (logger.isWarnEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) { - logger.warn("Resolved [" + ex + "]" + (result.isEmpty() ? "" : " to " + result)); + if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) { + logger.debug("Resolved [" + ex + "]" + (result.isEmpty() ? "" : " to " + result)); } // warnLogger with full stack trace (requires explicit config) logException(ex, request); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java index f471b36a..3893b2a0 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java @@ -367,6 +367,7 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap request.getRequestURL() + "': {" + m1 + ", " + m2 + "}"); } } + request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod); handleMatch(bestMatch.mapping, lookupPath, request); return bestMatch.handlerMethod; } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java index 16a120cb..48571d6b 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java @@ -407,6 +407,7 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping i @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { exposePathWithinMapping(this.bestMatchingPattern, this.pathWithinMapping, request); + request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, handler); request.setAttribute(INTROSPECT_TYPE_LEVEL_MAPPING, supportsTypeLevelMappings()); return true; } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolver.java index 80ee3852..cca63932 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolver.java @@ -72,8 +72,7 @@ public class ResponseStatusExceptionResolver extends AbstractHandlerExceptionRes } } else if (ex.getCause() instanceof Exception) { - ex = (Exception) ex.getCause(); - return doResolveException(request, response, handler, ex); + return doResolveException(request, response, handler, (Exception) ex.getCause()); } return null; } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartMethodArgumentResolver.java index bddd9190..941e47bd 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartMethodArgumentResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,7 +41,7 @@ import org.springframework.web.multipart.support.RequestPartServletServerHttpReq /** * Resolves the following method arguments: * <ul> - * <li>Annotated with {@code @RequestPart} + * <li>Annotated with @{@link RequestPart} * <li>Of type {@link MultipartFile} in conjunction with Spring's {@link MultipartResolver} abstraction * <li>Of type {@code javax.servlet.http.Part} in conjunction with Servlet 3.0 multipart requests * </ul> @@ -85,11 +85,13 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM /** - * Supports the following: + * Whether the given {@linkplain MethodParameter method parameter} is a multi-part + * supported. Supports the following: * <ul> * <li>annotated with {@code @RequestPart} * <li>of type {@link MultipartFile} unless annotated with {@code @RequestParam} - * <li>of type {@code javax.servlet.http.Part} unless annotated with {@code @RequestParam} + * <li>of type {@code javax.servlet.http.Part} unless annotated with + * {@code @RequestParam} * </ul> */ @Override diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java index a92c33a6..22a14efa 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java @@ -97,6 +97,7 @@ public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionRes */ public DefaultHandlerExceptionResolver() { setOrder(Ordered.LOWEST_PRECEDENCE); + setWarnLogCategory(getClass().getName()); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java index 531167c1..d1dee3c9 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java @@ -641,22 +641,25 @@ public class ResourceHttpRequestHandler extends WebContentGenerator */ protected boolean isInvalidPath(String path) { if (path.contains("WEB-INF") || path.contains("META-INF")) { - logger.trace("Path contains \"WEB-INF\" or \"META-INF\"."); + if (logger.isTraceEnabled()) { + logger.trace("Path with \"WEB-INF\" or \"META-INF\": [" + path + "]"); + } return true; } if (path.contains(":/")) { String relativePath = (path.charAt(0) == '/' ? path.substring(1) : path); if (ResourceUtils.isUrl(relativePath) || relativePath.startsWith("url:")) { - logger.trace("Path represents URL or has \"url:\" prefix."); + if (logger.isTraceEnabled()) { + logger.trace("Path represents URL or has \"url:\" prefix: [" + path + "]"); + } return true; } } - if (path.contains("..")) { - path = StringUtils.cleanPath(path); - if (path.contains("../")) { - logger.trace("Path contains \"../\" after call to StringUtils#cleanPath."); - return true; + if (path.contains("..") && StringUtils.cleanPath(path).contains("../")) { + if (logger.isTraceEnabled()) { + logger.trace("Path contains \"../\" after call to StringUtils#cleanPath: [" + path + "]"); } + return true; } return false; } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceUrlEncodingFilter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceUrlEncodingFilter.java index d6ca1c33..64dfed2f 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceUrlEncodingFilter.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceUrlEncodingFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 the original author 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,6 +22,7 @@ import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; @@ -34,8 +35,7 @@ import org.springframework.web.util.UrlPathHelper; /** * A filter that wraps the {@link HttpServletResponse} and overrides its * {@link HttpServletResponse#encodeURL(String) encodeURL} method in order to - * translate internal resource request URLs into public URL paths for external - * use. + * translate internal resource request URLs into public URL paths for external use. * * @author Jeremy Grelle * @author Rossen Stoyanchev @@ -50,67 +50,52 @@ public class ResourceUrlEncodingFilter extends GenericFilterBean { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) - throws IOException, ServletException { + throws ServletException, IOException { + if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) { - throw new ServletException("ResourceUrlEncodingFilter just supports HTTP requests"); + throw new ServletException("ResourceUrlEncodingFilter only supports HTTP requests"); } - HttpServletRequest httpRequest = (HttpServletRequest) request; - HttpServletResponse httpResponse = (HttpServletResponse) response; - filterChain.doFilter(httpRequest, new ResourceUrlEncodingResponseWrapper(httpRequest, httpResponse)); + ResourceUrlEncodingRequestWrapper wrappedRequest = + new ResourceUrlEncodingRequestWrapper((HttpServletRequest) request); + ResourceUrlEncodingResponseWrapper wrappedResponse = + new ResourceUrlEncodingResponseWrapper(wrappedRequest, (HttpServletResponse) response); + filterChain.doFilter(wrappedRequest, wrappedResponse); } - private static class ResourceUrlEncodingResponseWrapper extends HttpServletResponseWrapper { + private static class ResourceUrlEncodingRequestWrapper extends HttpServletRequestWrapper { - private final HttpServletRequest request; + private ResourceUrlProvider resourceUrlProvider; - /* Cache the index and prefix of the path within the DispatcherServlet mapping */ private Integer indexLookupPath; private String prefixLookupPath; - public ResourceUrlEncodingResponseWrapper(HttpServletRequest request, HttpServletResponse wrapped) { - super(wrapped); - this.request = request; + ResourceUrlEncodingRequestWrapper(HttpServletRequest request) { + super(request); } @Override - public String encodeURL(String url) { - ResourceUrlProvider resourceUrlProvider = getResourceUrlProvider(); - if (resourceUrlProvider == null) { - logger.debug("Request attribute exposing ResourceUrlProvider not found"); - return super.encodeURL(url); - } - - initLookupPath(resourceUrlProvider); - if (url.startsWith(this.prefixLookupPath)) { - int suffixIndex = getQueryParamsIndex(url); - String suffix = url.substring(suffixIndex); - String lookupPath = url.substring(this.indexLookupPath, suffixIndex); - lookupPath = resourceUrlProvider.getForLookupPath(lookupPath); - if (lookupPath != null) { - return super.encodeURL(this.prefixLookupPath + lookupPath + suffix); + public void setAttribute(String name, Object value) { + super.setAttribute(name, value); + if (ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR.equals(name)) { + if (value instanceof ResourceUrlProvider) { + initLookupPath((ResourceUrlProvider) value); } } - return super.encodeURL(url); - } - - private ResourceUrlProvider getResourceUrlProvider() { - return (ResourceUrlProvider) this.request.getAttribute( - ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR); } private void initLookupPath(ResourceUrlProvider urlProvider) { + this.resourceUrlProvider = urlProvider; if (this.indexLookupPath == null) { - UrlPathHelper pathHelper = urlProvider.getUrlPathHelper(); - String requestUri = pathHelper.getRequestUri(this.request); - String lookupPath = pathHelper.getLookupPathForRequest(this.request); + UrlPathHelper pathHelper = this.resourceUrlProvider.getUrlPathHelper(); + String requestUri = pathHelper.getRequestUri(this); + String lookupPath = pathHelper.getLookupPathForRequest(this); this.indexLookupPath = requestUri.lastIndexOf(lookupPath); this.prefixLookupPath = requestUri.substring(0, this.indexLookupPath); - if ("/".equals(lookupPath) && !"/".equals(requestUri)) { - String contextPath = pathHelper.getContextPath(this.request); + String contextPath = pathHelper.getContextPath(this); if (requestUri.equals(contextPath)) { this.indexLookupPath = requestUri.length(); this.prefixLookupPath = requestUri; @@ -119,10 +104,48 @@ public class ResourceUrlEncodingFilter extends GenericFilterBean { } } + public String resolveUrlPath(String url) { + if (this.resourceUrlProvider == null) { + logger.trace("ResourceUrlProvider not available via request attribute " + + "ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR"); + return null; + } + if (this.indexLookupPath != null && url.startsWith(this.prefixLookupPath)) { + int suffixIndex = getQueryParamsIndex(url); + String suffix = url.substring(suffixIndex); + String lookupPath = url.substring(this.indexLookupPath, suffixIndex); + lookupPath = this.resourceUrlProvider.getForLookupPath(lookupPath); + if (lookupPath != null) { + return this.prefixLookupPath + lookupPath + suffix; + } + } + return null; + } + private int getQueryParamsIndex(String url) { int index = url.indexOf('?'); return (index > 0 ? index : url.length()); } } + + private static class ResourceUrlEncodingResponseWrapper extends HttpServletResponseWrapper { + + private final ResourceUrlEncodingRequestWrapper request; + + ResourceUrlEncodingResponseWrapper(ResourceUrlEncodingRequestWrapper request, HttpServletResponse wrapped) { + super(wrapped); + this.request = request; + } + + @Override + public String encodeURL(String url) { + String urlPath = this.request.resolveUrlPath(url); + if (urlPath != null) { + return super.encodeURL(urlPath); + } + return super.encodeURL(url); + } + } + } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/HandlerMethodMappingTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/HandlerMethodMappingTests.java index 5a181069..6676f2de 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/HandlerMethodMappingTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/HandlerMethodMappingTests.java @@ -38,6 +38,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.context.support.StaticWebApplicationContext; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.util.UrlPathHelper; @@ -79,8 +80,10 @@ public class HandlerMethodMappingTests { String key = "foo"; this.mapping.registerMapping(key, this.handler, this.method1); - HandlerMethod result = this.mapping.getHandlerInternal(new MockHttpServletRequest("GET", key)); + MockHttpServletRequest request = new MockHttpServletRequest("GET", key); + HandlerMethod result = this.mapping.getHandlerInternal(request); assertEquals(method1, result.getMethod()); + assertEquals(result, request.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE)); } @Test @@ -88,8 +91,10 @@ public class HandlerMethodMappingTests { this.mapping.registerMapping("/fo*", this.handler, this.method1); this.mapping.registerMapping("/f*", this.handler, this.method2); - HandlerMethod result = this.mapping.getHandlerInternal(new MockHttpServletRequest("GET", "/foo")); + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); + HandlerMethod result = this.mapping.getHandlerInternal(request); assertEquals(method1, result.getMethod()); + assertEquals(result, request.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE)); } @Test(expected = IllegalStateException.class) diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/SimpleUrlHandlerMappingTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/SimpleUrlHandlerMappingTests.java index dd6b09bd..2550405d 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/SimpleUrlHandlerMappingTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/SimpleUrlHandlerMappingTests.java @@ -41,17 +41,18 @@ import static org.junit.Assert.*; public class SimpleUrlHandlerMappingTests { @Test - public void handlerBeanNotFound() throws Exception { + @SuppressWarnings("resource") + public void handlerBeanNotFound() { MockServletContext sc = new MockServletContext(""); XmlWebApplicationContext root = new XmlWebApplicationContext(); root.setServletContext(sc); - root.setConfigLocations(new String[] {"/org/springframework/web/servlet/handler/map1.xml"}); + root.setConfigLocations("/org/springframework/web/servlet/handler/map1.xml"); root.refresh(); XmlWebApplicationContext wac = new XmlWebApplicationContext(); wac.setParent(root); wac.setServletContext(sc); wac.setNamespace("map2err"); - wac.setConfigLocations(new String[] {"/org/springframework/web/servlet/handler/map2err.xml"}); + wac.setConfigLocations("/org/springframework/web/servlet/handler/map2err.xml"); try { wac.refresh(); fail("Should have thrown NoSuchBeanDefinitionException"); @@ -93,7 +94,7 @@ public class SimpleUrlHandlerMappingTests { MockServletContext sc = new MockServletContext(""); XmlWebApplicationContext wac = new XmlWebApplicationContext(); wac.setServletContext(sc); - wac.setConfigLocations(new String[] {"/org/springframework/web/servlet/handler/map2.xml"}); + wac.setConfigLocations("/org/springframework/web/servlet/handler/map2.xml"); wac.refresh(); Object bean = wac.getBean("mainController"); Object otherBean = wac.getBean("otherController"); @@ -104,11 +105,13 @@ public class SimpleUrlHandlerMappingTests { HandlerExecutionChain hec = getHandler(hm, req); assertTrue("Handler is correct bean", hec != null && hec.getHandler() == bean); assertEquals("/welcome.html", req.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)); + assertEquals(bean, req.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE)); req = new MockHttpServletRequest("GET", "/welcome.x"); hec = getHandler(hm, req); assertTrue("Handler is correct bean", hec != null && hec.getHandler() == otherBean); assertEquals("welcome.x", req.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)); + assertEquals(otherBean, req.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE)); req = new MockHttpServletRequest("GET", "/welcome/"); hec = getHandler(hm, req); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceUrlEncodingFilterTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceUrlEncodingFilterTests.java index d7716c7d..7722f763 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceUrlEncodingFilterTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceUrlEncodingFilterTests.java @@ -38,7 +38,7 @@ public class ResourceUrlEncodingFilterTests { private ResourceUrlEncodingFilter filter; - private ResourceUrlProvider resourceUrlProvider; + private ResourceUrlProvider urlProvider; @Before public void createFilter() throws Exception { @@ -49,16 +49,16 @@ public class ResourceUrlEncodingFilterTests { List<ResourceResolver> resolvers = Arrays.asList(versionResolver, pathResolver); this.filter = new ResourceUrlEncodingFilter(); - this.resourceUrlProvider = createResourceUrlProvider(resolvers); + this.urlProvider = createResourceUrlProvider(resolvers); } @Test public void encodeURL() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/"); - request.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, this.resourceUrlProvider); MockHttpServletResponse response = new MockHttpServletResponse(); this.filter.doFilter(request, response, (req, res) -> { + req.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, this.urlProvider); String result = ((HttpServletResponse) res).encodeURL("/resources/bar.css"); assertEquals("/resources/bar-11e16cf79faee7ac698c805cf28248d2.css", result); }); @@ -68,10 +68,26 @@ public class ResourceUrlEncodingFilterTests { public void encodeURLWithContext() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/context/foo"); request.setContextPath("/context"); - request.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, this.resourceUrlProvider); MockHttpServletResponse response = new MockHttpServletResponse(); this.filter.doFilter(request, response, (req, res) -> { + req.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, this.urlProvider); + String result = ((HttpServletResponse) res).encodeURL("/context/resources/bar.css"); + assertEquals("/context/resources/bar-11e16cf79faee7ac698c805cf28248d2.css", result); + }); + } + + + @Test + public void encodeUrlWithContextAndForwardedRequest() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/context/foo"); + request.setContextPath("/context"); + MockHttpServletResponse response = new MockHttpServletResponse(); + + this.filter.doFilter(request, response, (req, res) -> { + req.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, this.urlProvider); + request.setRequestURI("/forwarded"); + request.setContextPath("/"); String result = ((HttpServletResponse) res).encodeURL("/context/resources/bar.css"); assertEquals("/context/resources/bar-11e16cf79faee7ac698c805cf28248d2.css", result); }); @@ -82,10 +98,10 @@ public class ResourceUrlEncodingFilterTests { public void encodeContextPathUrlWithoutSuffix() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/context"); request.setContextPath("/context"); - request.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, this.resourceUrlProvider); MockHttpServletResponse response = new MockHttpServletResponse(); this.filter.doFilter(request, response, (req, res) -> { + req.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, this.urlProvider); String result = ((HttpServletResponse) res).encodeURL("/context/resources/bar.css"); assertEquals("/context/resources/bar-11e16cf79faee7ac698c805cf28248d2.css", result); }); @@ -95,10 +111,10 @@ public class ResourceUrlEncodingFilterTests { public void encodeContextPathUrlWithSuffix() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/context/"); request.setContextPath("/context"); - request.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, this.resourceUrlProvider); MockHttpServletResponse response = new MockHttpServletResponse(); this.filter.doFilter(request, response, (req, res) -> { + req.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, this.urlProvider); String result = ((HttpServletResponse) res).encodeURL("/context/resources/bar.css"); assertEquals("/context/resources/bar-11e16cf79faee7ac698c805cf28248d2.css", result); }); @@ -109,10 +125,10 @@ public class ResourceUrlEncodingFilterTests { public void encodeEmptyURLWithContext() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/context/foo"); request.setContextPath("/context"); - request.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, this.resourceUrlProvider); MockHttpServletResponse response = new MockHttpServletResponse(); this.filter.doFilter(request, response, (req, res) -> { + req.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, this.urlProvider); String result = ((HttpServletResponse) res).encodeURL("?foo=1"); assertEquals("?foo=1", result); }); @@ -123,10 +139,10 @@ public class ResourceUrlEncodingFilterTests { public void encodeURLWithRequestParams() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); request.setContextPath("/"); - request.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, this.resourceUrlProvider); MockHttpServletResponse response = new MockHttpServletResponse(); this.filter.doFilter(request, response, (req, res) -> { + req.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, this.urlProvider); String result = ((HttpServletResponse) res).encodeURL("/resources/bar.css?foo=bar&url=http://example.org"); assertEquals("/resources/bar-11e16cf79faee7ac698c805cf28248d2.css?foo=bar&url=http://example.org", result); }); @@ -138,10 +154,10 @@ public class ResourceUrlEncodingFilterTests { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/context-path/index"); request.setContextPath("/context-path"); request.setServletPath(""); - request.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, this.resourceUrlProvider); MockHttpServletResponse response = new MockHttpServletResponse(); this.filter.doFilter(request, response, (req, res) -> { + req.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, this.urlProvider); String result = ((HttpServletResponse) res).encodeURL("index?key=value"); assertEquals("index?key=value", result); }); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceUrlProviderJavaConfigTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceUrlProviderJavaConfigTests.java index 9ad2f6aa..fbdc0f5c 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceUrlProviderJavaConfigTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceUrlProviderJavaConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 the original author 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,14 @@ package org.springframework.web.servlet.resource; +import java.io.IOException; +import java.util.Arrays; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -23,6 +31,7 @@ import javax.servlet.http.HttpServletResponse; import org.junit.Before; import org.junit.Test; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Configuration; import org.springframework.mock.web.test.MockFilterChain; import org.springframework.mock.web.test.MockHttpServletRequest; @@ -55,8 +64,6 @@ public class ResourceUrlProviderJavaConfigTests { @Before @SuppressWarnings("resource") public void setup() throws Exception { - this.filterChain = new MockFilterChain(this.servlet, new ResourceUrlEncodingFilter()); - AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); context.setServletContext(new MockServletContext()); context.register(WebConfig.class); @@ -66,8 +73,9 @@ public class ResourceUrlProviderJavaConfigTests { this.request.setContextPath("/myapp"); this.response = new MockHttpServletResponse(); - Object urlProvider = context.getBean(ResourceUrlProvider.class); - this.request.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, urlProvider); + this.filterChain = new MockFilterChain(this.servlet, + new ResourceUrlEncodingFilter(), + new ResourceUrlProviderExposingFilter(context)); } @Test @@ -117,6 +125,34 @@ public class ResourceUrlProviderJavaConfigTests { } @SuppressWarnings("serial") + private static class ResourceUrlProviderExposingFilter implements Filter { + + private final ApplicationContext context; + + public ResourceUrlProviderExposingFilter(ApplicationContext context) { + this.context = context; + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + Object urlProvider = context.getBean(ResourceUrlProvider.class); + request.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, urlProvider); + chain.doFilter(request, response); + } + + @Override + public void destroy() { + + } + } + + @SuppressWarnings("serial") private static class TestServlet extends HttpServlet { private HttpServletResponse wrappedResponse; diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/messaging/StompSubProtocolHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/messaging/StompSubProtocolHandler.java index 879ac2d4..73bcf842 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/messaging/StompSubProtocolHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/messaging/StompSubProtocolHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -435,7 +435,7 @@ public class StompSubProtocolHandler implements SubProtocolHandler, ApplicationE else if (StompCommand.CONNECTED.equals(command)) { this.stats.incrementConnectedCount(); accessor = afterStompSessionConnected(message, accessor, session); - if (this.eventPublisher != null && StompCommand.CONNECTED.equals(command)) { + if (this.eventPublisher != null) { try { SimpAttributes simpAttributes = new SimpAttributes(session.getId(), session.getAttributes()); SimpAttributesContextHolder.setAttributes(simpAttributes); diff --git a/src/asciidoc/core-beans.adoc b/src/asciidoc/core-beans.adoc index c44b20a5..df41f1a3 100644 --- a/src/asciidoc/core-beans.adoc +++ b/src/asciidoc/core-beans.adoc @@ -545,21 +545,21 @@ XML-based configuration metadata, you can use the `<alias/>` element to accompli <alias name="fromName" alias="toName"/> ---- -In this case, a bean in the same container which is named `fromName`, may also, +In this case, a bean (in the same container) named `fromName` may also, after the use of this alias definition, be referred to as `toName`. -For example, the configuration metadata for subsystem A may refer to a DataSource via -the name `subsystemA-dataSource`. The configuration metadata for subsystem B may refer to -a DataSource via the name `subsystemB-dataSource`. When composing the main application -that uses both these subsystems the main application refers to the DataSource via the -name `myApp-dataSource`. To have all three names refer to the same object you add to the -MyApp configuration metadata the following aliases definitions: +For example, the configuration metadata for subsystem A may refer to a DataSource by the +name of `subsystemA-dataSource`. The configuration metadata for subsystem B may refer to +a DataSource by the name of `subsystemB-dataSource`. When composing the main application +that uses both these subsystems, the main application refers to the DataSource by the +name of `myApp-dataSource`. To have all three names refer to the same object, you can +add the following alias definitions to the configuration metadata: [source,xml,indent=0] [subs="verbatim,quotes"] ---- - <alias name="subsystemA-dataSource" alias="subsystemB-dataSource"/> - <alias name="subsystemA-dataSource" alias="myApp-dataSource" /> + <alias name="myApp-dataSource" alias="subsystemA-dataSource"/> + <alias name="myApp-dataSource" alias="subsystemB-dataSource"/> ---- Now each component and the main application can refer to the dataSource through a name @@ -1911,11 +1911,11 @@ delimiters: [NOTE] ==== -The `depends-on` attribute in the bean definition can specify both an initialization -time dependency and, in the case of <<beans-factory-scopes-singleton,singleton>> beans -only, a corresponding destroy time dependency. Dependent beans that define a -`depends-on` relationship with a given bean are destroyed first, prior to the given bean -itself being destroyed. Thus `depends-on` can also control shutdown order. +The `depends-on` attribute in the bean definition can specify both an initialization-time +dependency and, in the case of <<beans-factory-scopes-singleton,singleton>> beans only, +a corresponding destruction-time dependency. Dependent beans that define a `depends-on` +relationship with a given bean are destroyed first, prior to the given bean itself being +destroyed. Thus `depends-on` can also control shutdown order. ==== @@ -6762,7 +6762,7 @@ annotation accepts a String array for this purpose. @Configuration public class AppConfig { - @Bean(name = { "dataSource", "subsystemA-dataSource", "subsystemB-dataSource" }) + @Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"}) public DataSource dataSource() { // instantiate, configure and return DataSource bean... } diff --git a/src/asciidoc/data-access.adoc b/src/asciidoc/data-access.adoc index df35df15..95d30375 100644 --- a/src/asciidoc/data-access.adoc +++ b/src/asciidoc/data-access.adoc @@ -1090,6 +1090,12 @@ following class definition: } ---- +Used at the class level as above, the annotation indicates a default for all methods +of the declaring class (as well as its subclasses). Alternatively, each method can +get annotated individually. Note that a class-level annotation does not apply to +ancestor classes up the class hierarchy; in such a scenario, methods need to be +locally redeclared in order to participate in a subclass-level annotation. + When the above POJO is defined as a bean in a Spring IoC container, the bean instance can be made transactional by adding merely __one__ line of XML configuration: @@ -1115,6 +1121,7 @@ can be made transactional by adding merely __one__ line of XML configuration: <!-- enable the configuration of transactional behavior based on annotations --> __<tx:annotation-driven transaction-manager="txManager"/>__<!-- a PlatformTransactionManager is still required --> + <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- (this dependency is defined somewhere else) --> <property name="dataSource" ref="dataSource"/> diff --git a/src/asciidoc/web-mvc.adoc b/src/asciidoc/web-mvc.adoc index 96327923..52dac141 100644 --- a/src/asciidoc/web-mvc.adoc +++ b/src/asciidoc/web-mvc.adoc @@ -452,8 +452,8 @@ initialization parameters ( `init-param` elements) to the Servlet declaration in | Parameter| Explanation | `contextClass` -| Class that implements `WebApplicationContext`, which instantiates the context used by - this Servlet. By default, the `XmlWebApplicationContext` is used. +| Class that implements `ConfigurableWebApplicationContext`, to be instantiated and + locally configured by this Servlet. By default, `XmlWebApplicationContext` is used. | `contextConfigLocation` | String that is passed to the context instance (specified by `contextClass`) to |