summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEmmanuel Bourg <ebourg@apache.org>2016-08-03 19:55:01 +0200
committerEmmanuel Bourg <ebourg@apache.org>2016-08-03 19:55:01 +0200
commit75a721d1019da2a2fa86e24ff439df4a224e5b19 (patch)
tree2c44c00ce2c8641cccad177177e5682e187a17ea
parent9eaca6a06af3cbceb3754de19d477be770614265 (diff)
Imported Upstream version 4.3.2
-rw-r--r--CODE_OF_CONDUCT.adoc44
-rw-r--r--CONTRIBUTING.md4
-rw-r--r--README.md4
-rw-r--r--build.gradle123
-rw-r--r--gradle.properties2
-rw-r--r--spring-aop/src/main/java/org/aopalliance/aop/Advice.java28
-rw-r--r--spring-aop/src/main/java/org/aopalliance/aop/AspectException.java48
-rw-r--r--spring-aop/src/main/java/org/aopalliance/intercept/ConstructorInterceptor.java59
-rw-r--r--spring-aop/src/main/java/org/aopalliance/intercept/ConstructorInvocation.java41
-rw-r--r--spring-aop/src/main/java/org/aopalliance/intercept/Interceptor.java69
-rw-r--r--spring-aop/src/main/java/org/aopalliance/intercept/Invocation.java37
-rw-r--r--spring-aop/src/main/java/org/aopalliance/intercept/Joinpoint.java65
-rw-r--r--spring-aop/src/main/java/org/aopalliance/intercept/MethodInterceptor.java56
-rw-r--r--spring-aop/src/main/java/org/aopalliance/intercept/MethodInvocation.java41
-rw-r--r--spring-aop/src/main/java/org/springframework/aop/aspectj/AbstractAspectJAdvice.java41
-rw-r--r--spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAfterAdvice.java5
-rw-r--r--spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAfterReturningAdvice.java5
-rw-r--r--spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAfterThrowingAdvice.java5
-rw-r--r--spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAroundAdvice.java4
-rw-r--r--spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJMethodBeforeAdvice.java4
-rw-r--r--spring-aop/src/main/java/org/springframework/aop/aspectj/DeclareParentsAdvisor.java2
-rw-r--r--spring-aop/src/main/java/org/springframework/aop/aspectj/SimpleAspectInstanceFactory.java8
-rw-r--r--spring-aop/src/main/java/org/springframework/aop/aspectj/SingletonAspectInstanceFactory.java7
-rw-r--r--spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectMetadata.java25
-rw-r--r--spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectInstanceFactory.java6
-rw-r--r--spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/InstantiationModelAwarePointcutAdvisorImpl.java27
-rw-r--r--spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/LazySingletonAspectInstanceFactoryDecorator.java16
-rw-r--r--spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/MetadataAwareAspectInstanceFactory.java9
-rw-r--r--spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/PrototypeAspectInstanceFactory.java5
-rw-r--r--spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/ReflectiveAspectJAdvisorFactory.java4
-rw-r--r--spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/SimpleMetadataAwareAspectInstanceFactory.java7
-rw-r--r--spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/SingletonMetadataAwareAspectInstanceFactory.java12
-rw-r--r--spring-aop/src/main/java/org/springframework/aop/config/AopConfigUtils.java4
-rw-r--r--spring-aop/src/main/java/org/springframework/aop/framework/AdvisedSupport.java25
-rw-r--r--spring-aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java40
-rw-r--r--spring-aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java13
-rw-r--r--spring-aop/src/main/java/org/springframework/aop/framework/ProxyProcessorSupport.java5
-rw-r--r--spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java4
-rw-r--r--spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AutoProxyUtils.java2
-rw-r--r--spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java2
-rw-r--r--spring-aop/src/main/java/org/springframework/aop/support/AopUtils.java35
-rw-r--r--spring-aop/src/main/resources/META-INF/spring.schemas3
-rw-r--r--spring-aop/src/main/resources/org/springframework/aop/config/spring-aop-4.3.xsd409
-rw-r--r--spring-aop/src/test/java/org/springframework/aop/aspectj/AspectJAdviceParameterNameDiscovererTests.java3
-rw-r--r--spring-aop/src/test/java/org/springframework/aop/aspectj/MethodInvocationProceedingJoinPointTests.java3
-rw-r--r--spring-aop/src/test/java/org/springframework/aop/aspectj/TrickyAspectJPointcutExpressionTests.java35
-rw-r--r--spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactoryTests.java7
-rw-r--r--spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AspectProxyFactoryTests.java27
-rw-r--r--spring-aop/src/test/java/org/springframework/aop/framework/AopProxyUtilsTests.java15
-rw-r--r--spring-aop/src/test/java/org/springframework/aop/framework/ProxyFactoryTests.java54
-rw-r--r--spring-aop/src/test/java/org/springframework/aop/interceptor/DebugInterceptorTests.java3
-rw-r--r--spring-aop/src/test/java/org/springframework/aop/interceptor/SimpleTraceInterceptorTests.java3
-rw-r--r--spring-aop/src/test/java/org/springframework/aop/target/ThreadLocalTargetSourceTests.java3
-rw-r--r--spring-aspects/src/main/java/org/springframework/cache/aspectj/AbstractCacheAspect.aj15
-rw-r--r--spring-aspects/src/main/java/org/springframework/cache/aspectj/AnyThrow.java35
-rw-r--r--spring-aspects/src/main/java/org/springframework/cache/aspectj/JCacheCacheAspect.aj12
-rw-r--r--spring-aspects/src/main/java/org/springframework/mock/staticmock/AbstractMethodMockingControl.aj2
-rw-r--r--spring-aspects/src/main/java/org/springframework/mock/staticmock/AnnotationDrivenStaticEntityMockingControl.aj2
-rw-r--r--spring-aspects/src/main/java/org/springframework/mock/staticmock/MockStaticEntityMethods.java2
-rw-r--r--spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AnnotationAsyncExecutionAspect.aj6
-rw-r--r--spring-aspects/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java30
-rw-r--r--spring-aspects/src/test/java/org/springframework/cache/config/CacheableService.java11
-rw-r--r--spring-aspects/src/test/java/org/springframework/cache/config/DefaultCacheableService.java35
-rw-r--r--spring-aspects/src/test/java/org/springframework/scheduling/aspectj/AnnotationAsyncExecutionAspectTests.java3
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java10
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/AbstractPropertyAccessor.java2
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/BeanInfoFactory.java2
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/BeanInstantiationException.java59
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/BeanUtils.java39
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java13
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java20
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/NullValueInNestedPathException.java15
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/PropertyAccessor.java14
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java16
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java9
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/BeanCreationException.java8
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java4
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/BeanInitializationException.java6
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/BeanNotOfRequiredTypeException.java6
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/InjectionPoint.java167
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/NoUniqueBeanDefinitionException.java15
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/ObjectProvider.java61
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/UnsatisfiedDependencyException.java46
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/access/SingletonBeanFactoryLocator.java6
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/annotation/Autowired.java4
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java210
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/annotation/BeanFactoryAnnotationUtils.java17
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java21
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java23
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/config/AbstractFactoryBean.java4
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/config/AutowireCapableBeanFactory.java12
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java9
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/config/ConstructorArgumentValues.java12
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java123
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/config/DestructionAwareBeanPostProcessor.java22
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/config/EmbeddedValueResolver.java59
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.java2
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/config/PlaceholderConfigurerSupport.java14
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/config/PropertiesFactoryBean.java4
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurer.java9
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/config/Scope.java2
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinitionReader.java2
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java8
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java4
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java139
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java286
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java4
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java57
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java4
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleInstantiationStrategy.java10
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java27
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultBeanDefinitionDocumentReader.java6
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/propertyeditors/CustomCollectionEditor.java6
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/propertyeditors/CustomMapEditor.java6
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/propertyeditors/FileEditor.java10
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/propertyeditors/InputStreamEditor.java8
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/propertyeditors/PathEditor.java116
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/propertyeditors/ReaderEditor.java8
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/propertyeditors/StringArrayPropertyEditor.java4
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/propertyeditors/URIEditor.java9
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/propertyeditors/URLEditor.java4
-rw-r--r--spring-beans/src/main/java/org/springframework/beans/support/ResourceEditorRegistrar.java20
-rw-r--r--spring-beans/src/main/resources/META-INF/spring.schemas9
-rw-r--r--spring-beans/src/main/resources/org/springframework/beans/factory/xml/spring-beans-4.3.xsd1201
-rw-r--r--spring-beans/src/main/resources/org/springframework/beans/factory/xml/spring-tool-4.3.xsd115
-rw-r--r--spring-beans/src/main/resources/org/springframework/beans/factory/xml/spring-util-4.3.xsd221
-rw-r--r--spring-beans/src/test/java/org/springframework/beans/AbstractPropertyAccessorTests.java34
-rw-r--r--spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java27
-rw-r--r--spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java22
-rw-r--r--spring-beans/src/test/java/org/springframework/beans/factory/Spr5475Tests.java32
-rw-r--r--spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java426
-rw-r--r--spring-beans/src/test/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurerTests.java73
-rw-r--r--spring-beans/src/test/java/org/springframework/beans/factory/config/ServiceLocatorFactoryBeanTests.java18
-rw-r--r--spring-beans/src/test/java/org/springframework/beans/factory/support/security/CallbacksSecurityTests.java33
-rw-r--r--spring-beans/src/test/java/org/springframework/beans/factory/xml/DuplicateBeanIdTests.java3
-rw-r--r--spring-beans/src/test/java/org/springframework/beans/factory/xml/XmlBeanCollectionTests.java32
-rw-r--r--spring-beans/src/test/java/org/springframework/beans/propertyeditors/CustomEditorTests.java3
-rw-r--r--spring-beans/src/test/java/org/springframework/beans/propertyeditors/FileEditorTests.java19
-rw-r--r--spring-beans/src/test/java/org/springframework/beans/propertyeditors/InputStreamEditorTests.java5
-rw-r--r--spring-beans/src/test/java/org/springframework/beans/propertyeditors/PathEditorTests.java80
-rw-r--r--spring-beans/src/test/java/org/springframework/beans/propertyeditors/ReaderEditorTests.java4
-rw-r--r--spring-beans/src/test/java/org/springframework/beans/propertyeditors/URIEditorTests.java21
-rw-r--r--spring-beans/src/test/java/org/springframework/beans/propertyeditors/URLEditorTests.java16
-rw-r--r--spring-beans/src/test/java/org/springframework/tests/sample/beans/HasMap.java23
-rw-r--r--spring-beans/src/test/resources/org/springframework/beans/factory/xml/collections.xml17
-rw-r--r--spring-beans/src/test/resources/org/springframework/beans/factory/xml/import.xml2
-rw-r--r--spring-context-support/src/main/java/org/springframework/cache/caffeine/CaffeineCache.java166
-rw-r--r--spring-context-support/src/main/java/org/springframework/cache/caffeine/CaffeineCacheManager.java230
-rw-r--r--spring-context-support/src/main/java/org/springframework/cache/caffeine/package-info.java6
-rw-r--r--spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheCache.java46
-rw-r--r--spring-context-support/src/main/java/org/springframework/cache/ehcache/package-info.java2
-rw-r--r--spring-context-support/src/main/java/org/springframework/cache/guava/GuavaCache.java19
-rw-r--r--spring-context-support/src/main/java/org/springframework/cache/jcache/JCacheCache.java41
-rw-r--r--spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractFallbackJCacheOperationSource.java10
-rw-r--r--spring-context-support/src/main/java/org/springframework/cache/transaction/TransactionAwareCacheDecorator.java9
-rw-r--r--spring-context-support/src/main/java/org/springframework/cache/transaction/package-info.java2
-rw-r--r--spring-context-support/src/main/java/org/springframework/scheduling/commonj/WorkManagerTaskExecutor.java21
-rw-r--r--spring-context-support/src/main/java/org/springframework/ui/freemarker/FreeMarkerConfigurationFactory.java14
-rw-r--r--spring-context-support/src/main/java/org/springframework/ui/velocity/VelocityEngineFactory.java4
-rw-r--r--spring-context-support/src/main/java/org/springframework/ui/velocity/VelocityEngineFactoryBean.java2
-rw-r--r--spring-context-support/src/main/java/org/springframework/ui/velocity/VelocityEngineUtils.java2
-rw-r--r--spring-context-support/src/test/java/org/springframework/cache/caffeine/CaffeineCacheManagerTests.java207
-rw-r--r--spring-context-support/src/test/java/org/springframework/cache/caffeine/CaffeineCacheTests.java70
-rw-r--r--spring-context-support/src/test/java/org/springframework/cache/ehcache/EhCacheCacheTests.java3
-rw-r--r--spring-context-support/src/test/java/org/springframework/cache/guava/GuavaCacheTests.java3
-rw-r--r--spring-context/src/main/java/org/springframework/cache/Cache.java45
-rw-r--r--spring-context/src/main/java/org/springframework/cache/annotation/CacheEvict.java21
-rw-r--r--spring-context/src/main/java/org/springframework/cache/annotation/CachePut.java29
-rw-r--r--spring-context/src/main/java/org/springframework/cache/annotation/Cacheable.java47
-rw-r--r--spring-context/src/main/java/org/springframework/cache/annotation/Caching.java5
-rw-r--r--spring-context/src/main/java/org/springframework/cache/annotation/CachingConfigurationSelector.java8
-rw-r--r--spring-context/src/main/java/org/springframework/cache/annotation/EnableCaching.java6
-rw-r--r--spring-context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.java135
-rw-r--r--spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCache.java119
-rw-r--r--spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCacheManager.java67
-rw-r--r--spring-context/src/main/java/org/springframework/cache/config/AnnotationDrivenCacheBeanDefinitionParser.java9
-rw-r--r--spring-context/src/main/java/org/springframework/cache/config/CacheAdviceParser.java60
-rw-r--r--spring-context/src/main/java/org/springframework/cache/interceptor/AbstractFallbackCacheOperationSource.java43
-rw-r--r--spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java216
-rw-r--r--spring-context/src/main/java/org/springframework/cache/interceptor/CacheEvictOperation.java61
-rw-r--r--spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperation.java194
-rw-r--r--spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationExpressionEvaluator.java (renamed from spring-context/src/main/java/org/springframework/cache/interceptor/ExpressionEvaluator.java)30
-rw-r--r--spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationInvoker.java5
-rw-r--r--spring-context/src/main/java/org/springframework/cache/interceptor/CachePutOperation.java50
-rw-r--r--spring-context/src/main/java/org/springframework/cache/interceptor/CacheableOperation.java65
-rw-r--r--spring-context/src/main/java/org/springframework/cache/interceptor/NamedCacheResolver.java4
-rw-r--r--spring-context/src/main/java/org/springframework/cache/interceptor/SimpleKey.java4
-rw-r--r--spring-context/src/main/java/org/springframework/cache/interceptor/VariableNotAvailableException.java4
-rw-r--r--spring-context/src/main/java/org/springframework/cache/support/AbstractCacheManager.java10
-rw-r--r--spring-context/src/main/java/org/springframework/cache/support/NoOpCacheManager.java13
-rw-r--r--spring-context/src/main/java/org/springframework/cache/support/package-info.java2
-rw-r--r--spring-context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java16
-rw-r--r--spring-context/src/main/java/org/springframework/context/EmbeddedValueResolverAware.java6
-rw-r--r--spring-context/src/main/java/org/springframework/context/access/ContextSingletonBeanFactoryLocator.java4
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java12
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/AnnotationScopeMetadataResolver.java7
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/AspectJAutoProxyRegistrar.java10
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/AutoProxyRegistrar.java4
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/BeanAnnotationHelper.java13
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java2
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java22
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/ComponentScan.java11
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java44
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/ComponentScans.java44
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java8
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java2
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java6
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java81
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/EnableAspectJAutoProxy.java9
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/ImportBeanDefinitionRegistrar.java2
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/ImportSelector.java2
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/PropertySource.java25
-rw-r--r--spring-context/src/main/java/org/springframework/context/config/LoadTimeWeaverBeanDefinitionParser.java26
-rw-r--r--spring-context/src/main/java/org/springframework/context/config/PropertyPlaceholderBeanDefinitionParser.java6
-rw-r--r--spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java21
-rw-r--r--spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java7
-rw-r--r--spring-context/src/main/java/org/springframework/context/event/EventExpressionEvaluator.java19
-rw-r--r--spring-context/src/main/java/org/springframework/context/event/EventListenerMethodProcessor.java7
-rw-r--r--spring-context/src/main/java/org/springframework/context/expression/AnnotatedElementKey.java16
-rw-r--r--spring-context/src/main/java/org/springframework/context/expression/CachedExpressionEvaluator.java40
-rw-r--r--spring-context/src/main/java/org/springframework/context/expression/MethodBasedEvaluationContext.java3
-rw-r--r--spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java28
-rw-r--r--spring-context/src/main/java/org/springframework/context/support/AbstractResourceBasedMessageSource.java208
-rw-r--r--spring-context/src/main/java/org/springframework/context/support/ApplicationContextAwareProcessor.java25
-rw-r--r--spring-context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java2
-rw-r--r--spring-context/src/main/java/org/springframework/context/support/PostProcessorRegistrationDelegate.java5
-rw-r--r--spring-context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java9
-rw-r--r--spring-context/src/main/java/org/springframework/context/support/ReloadableResourceBundleMessageSource.java138
-rw-r--r--spring-context/src/main/java/org/springframework/context/support/ResourceBundleMessageSource.java148
-rw-r--r--spring-context/src/main/java/org/springframework/format/annotation/DateTimeFormat.java6
-rw-r--r--spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeFormatterFactory.java9
-rw-r--r--spring-context/src/main/java/org/springframework/format/datetime/standard/InstantFormatter.java18
-rw-r--r--spring-context/src/main/java/org/springframework/instrument/classloading/ReflectiveLoadTimeWeaver.java7
-rw-r--r--spring-context/src/main/java/org/springframework/instrument/classloading/glassfish/GlassFishLoadTimeWeaver.java5
-rw-r--r--spring-context/src/main/java/org/springframework/instrument/classloading/tomcat/TomcatLoadTimeWeaver.java9
-rw-r--r--spring-context/src/main/java/org/springframework/instrument/classloading/weblogic/WebLogicLoadTimeWeaver.java7
-rw-r--r--spring-context/src/main/java/org/springframework/instrument/classloading/websphere/WebSphereLoadTimeWeaver.java6
-rw-r--r--spring-context/src/main/java/org/springframework/jmx/access/MBeanClientInterceptor.java33
-rw-r--r--spring-context/src/main/java/org/springframework/jmx/export/MBeanExporter.java2
-rw-r--r--spring-context/src/main/java/org/springframework/jmx/export/annotation/AnnotationJmxAttributeSource.java24
-rw-r--r--spring-context/src/main/java/org/springframework/jmx/export/annotation/ManagedNotifications.java4
-rw-r--r--spring-context/src/main/java/org/springframework/jmx/export/assembler/MethodNameBasedMBeanInfoAssembler.java4
-rw-r--r--spring-context/src/main/java/org/springframework/jmx/export/naming/IdentityNamingStrategy.java2
-rw-r--r--spring-context/src/main/java/org/springframework/jmx/export/naming/KeyNamingStrategy.java2
-rw-r--r--spring-context/src/main/java/org/springframework/jmx/support/ConnectorServerFactoryBean.java2
-rw-r--r--spring-context/src/main/java/org/springframework/jndi/JndiLocatorDelegate.java30
-rw-r--r--spring-context/src/main/java/org/springframework/jndi/JndiObjectFactoryBean.java2
-rw-r--r--spring-context/src/main/java/org/springframework/jndi/JndiPropertySource.java11
-rw-r--r--spring-context/src/main/java/org/springframework/scheduling/annotation/AnnotationAsyncExecutionInterceptor.java6
-rw-r--r--spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncResult.java18
-rw-r--r--spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java28
-rw-r--r--spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java129
-rw-r--r--spring-context/src/main/java/org/springframework/scheduling/annotation/Schedules.java5
-rw-r--r--spring-context/src/main/java/org/springframework/scheduling/concurrent/ConcurrentTaskExecutor.java17
-rw-r--r--spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.java47
-rw-r--r--spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTask.java49
-rw-r--r--spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTaskRegistrar.java161
-rw-r--r--spring-context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java26
-rw-r--r--spring-context/src/main/java/org/springframework/scripting/bsh/BshScriptUtils.java3
-rw-r--r--spring-context/src/main/java/org/springframework/scripting/config/ScriptingDefaultsParser.java6
-rw-r--r--spring-context/src/main/java/org/springframework/scripting/groovy/GroovyScriptFactory.java2
-rw-r--r--spring-context/src/main/java/org/springframework/scripting/support/ScriptFactoryPostProcessor.java2
-rw-r--r--spring-context/src/main/java/org/springframework/scripting/support/StandardScriptFactory.java4
-rw-r--r--spring-context/src/main/java/org/springframework/validation/beanvalidation/LocalValidatorFactoryBean.java42
-rw-r--r--spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java73
-rw-r--r--spring-context/src/main/resources/META-INF/spring.schemas15
-rw-r--r--spring-context/src/main/resources/org/springframework/cache/config/spring-cache-4.3.xsd317
-rw-r--r--spring-context/src/main/resources/org/springframework/context/config/spring-context-4.3.xsd547
-rw-r--r--spring-context/src/main/resources/org/springframework/ejb/config/spring-jee-4.3.xsd267
-rw-r--r--spring-context/src/main/resources/org/springframework/scheduling/config/spring-task-4.3.xsd309
-rw-r--r--spring-context/src/main/resources/org/springframework/scripting/config/spring-lang-4.3.xsd240
-rw-r--r--spring-context/src/test/java/org/springframework/aop/aspectj/AspectAndAdvicePrecedenceTests.java8
-rw-r--r--spring-context/src/test/java/org/springframework/aop/aspectj/_TestTypes.java2
-rw-r--r--spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/AnnotationBindingTests.java9
-rw-r--r--spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/AspectJAutoProxyCreatorTests.java8
-rw-r--r--spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/spr3064/SPR3064Tests.java10
-rw-r--r--spring-context/src/test/java/org/springframework/aop/framework/AbstractAopProxyTests.java3
-rw-r--r--spring-context/src/test/java/org/springframework/aop/framework/ClassWithComplexConstructor.java6
-rw-r--r--spring-context/src/test/java/org/springframework/aop/framework/JdkDynamicProxyTests.java4
-rw-r--r--spring-context/src/test/java/org/springframework/aop/framework/autoproxy/AdvisorAutoProxyCreatorTests.java36
-rw-r--r--spring-context/src/test/java/org/springframework/aop/framework/autoproxy/AutoProxyCreatorTests.java21
-rw-r--r--spring-context/src/test/java/org/springframework/aop/framework/autoproxy/PackageVisibleMethod.java24
-rw-r--r--spring-context/src/test/java/org/springframework/beans/factory/support/QualifierAnnotationAutowireContextTests.java67
-rw-r--r--spring-context/src/test/java/org/springframework/beans/factory/xml/XmlBeanFactoryTests.java15
-rw-r--r--spring-context/src/test/java/org/springframework/cache/AbstractCacheTests.java (renamed from spring-context-support/src/test/java/org/springframework/cache/AbstractCacheTests.java)108
-rw-r--r--spring-context/src/test/java/org/springframework/cache/CacheReproTests.java50
-rw-r--r--spring-context/src/test/java/org/springframework/cache/NoOpCacheManagerTests.java57
-rw-r--r--spring-context/src/test/java/org/springframework/cache/annotation/AnnotationCacheOperationSourceTests.java44
-rw-r--r--spring-context/src/test/java/org/springframework/cache/concurrent/ConcurrentCacheTests.java115
-rw-r--r--spring-context/src/test/java/org/springframework/cache/concurrent/ConcurrentMapCacheManagerTests.java18
-rw-r--r--spring-context/src/test/java/org/springframework/cache/concurrent/ConcurrentMapCacheTests.java111
-rw-r--r--spring-context/src/test/java/org/springframework/cache/config/AbstractCacheAnnotationTests.java97
-rw-r--r--spring-context/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java29
-rw-r--r--spring-context/src/test/java/org/springframework/cache/config/CacheAdviceParserTests.java5
-rw-r--r--spring-context/src/test/java/org/springframework/cache/config/CacheableService.java10
-rw-r--r--spring-context/src/test/java/org/springframework/cache/config/DefaultCacheableService.java32
-rw-r--r--spring-context/src/test/java/org/springframework/cache/config/EnableCachingIntegrationTests.java98
-rw-r--r--spring-context/src/test/java/org/springframework/cache/config/ExpressionCachingIntegrationTests.java2
-rw-r--r--spring-context/src/test/java/org/springframework/cache/interceptor/CacheSyncFailureTests.java158
-rw-r--r--spring-context/src/test/java/org/springframework/cache/interceptor/ExpressionEvaluatorTests.java40
-rw-r--r--spring-context/src/test/java/org/springframework/context/annotation/AbstractCircularImportDetectionTests.java19
-rw-r--r--spring-context/src/test/java/org/springframework/context/annotation/AsmCircularImportDetectionTests.java10
-rw-r--r--spring-context/src/test/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessorTests.java5
-rw-r--r--spring-context/src/test/java/org/springframework/context/annotation/ComponentScanAndImportAnnotationInteractionTests.java14
-rw-r--r--spring-context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationIntegrationTests.java86
-rw-r--r--spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java10
-rw-r--r--spring-context/src/test/java/org/springframework/context/annotation/EnableAspectJAutoProxyTests.java36
-rw-r--r--spring-context/src/test/java/org/springframework/context/annotation/PropertySourceAnnotationTests.java65
-rw-r--r--spring-context/src/test/java/org/springframework/context/annotation/Spr12278Tests.java115
-rw-r--r--spring-context/src/test/java/org/springframework/context/annotation/configuration/AutowiredConfigurationTests.java159
-rw-r--r--spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassAspectIntegrationTests.java6
-rw-r--r--spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java50
-rw-r--r--spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassWithPlaceholderConfigurerBeanTests.java74
-rw-r--r--spring-context/src/test/java/org/springframework/context/annotation/configuration/ImportResourceTests.java52
-rw-r--r--spring-context/src/test/java/org/springframework/context/annotation/configuration/ScopingTests.java16
-rw-r--r--spring-context/src/test/java/org/springframework/context/annotation/spr10546/Spr10546Tests.java2
-rw-r--r--spring-context/src/test/java/org/springframework/context/config/ContextNamespaceHandlerTests.java24
-rw-r--r--spring-context/src/test/java/org/springframework/context/event/AbstractApplicationEventListenerTests.java6
-rw-r--r--spring-context/src/test/java/org/springframework/context/event/AnnotationDrivenEventListenerTests.java100
-rw-r--r--spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java6
-rw-r--r--spring-context/src/test/java/org/springframework/context/event/ApplicationListenerMethodAdapterTests.java24
-rw-r--r--spring-context/src/test/java/org/springframework/context/event/EventPublicationInterceptorTests.java12
-rw-r--r--spring-context/src/test/java/org/springframework/context/event/test/AbstractIdentifiable.java8
-rw-r--r--spring-context/src/test/java/org/springframework/context/event/test/EventCollector.java6
-rw-r--r--spring-context/src/test/java/org/springframework/context/event/test/IdentifiableApplicationEvent.java8
-rw-r--r--spring-context/src/test/java/org/springframework/context/expression/FactoryBeanAccessTests.java130
-rw-r--r--spring-context/src/test/java/org/springframework/context/expression/MapAccessorTests.java22
-rw-r--r--spring-context/src/test/java/org/springframework/context/expression/MethodBasedEvaluationContextTests.java44
-rw-r--r--spring-context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.java153
-rw-r--r--spring-context/src/test/java/org/springframework/context/support/SerializableBeanFactoryMemoryLeakTests.java6
-rw-r--r--spring-context/src/test/java/org/springframework/ejb/access/LocalStatelessSessionProxyFactoryBeanTests.java8
-rw-r--r--spring-context/src/test/java/org/springframework/ejb/access/SimpleRemoteStatelessSessionProxyFactoryBeanTests.java11
-rw-r--r--spring-context/src/test/java/org/springframework/format/datetime/DateFormattingTests.java10
-rw-r--r--spring-context/src/test/java/org/springframework/format/datetime/joda/JodaTimeFormattingTests.java10
-rw-r--r--spring-context/src/test/java/org/springframework/format/datetime/standard/DateTimeFormattingTests.java12
-rw-r--r--spring-context/src/test/java/org/springframework/format/support/FormattingConversionServiceTests.java4
-rw-r--r--spring-context/src/test/java/org/springframework/jmx/access/MBeanClientInterceptorTests.java12
-rw-r--r--spring-context/src/test/java/org/springframework/jmx/access/RemoteMBeanClientInterceptorTests.java3
-rw-r--r--spring-context/src/test/java/org/springframework/jmx/export/NotificationListenerTests.java9
-rw-r--r--spring-context/src/test/java/org/springframework/jmx/export/annotation/AnnotationTestBean.java2
-rw-r--r--spring-context/src/test/java/org/springframework/jmx/export/annotation/AnotherAnnotationTestBeanImpl.java2
-rw-r--r--spring-context/src/test/java/org/springframework/jmx/export/annotation/EnableMBeanExportConfigurationTests.java243
-rw-r--r--spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodNameBasedMBeanInfoAssemblerMappedTests.java2
-rw-r--r--spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodNameBasedMBeanInfoAssemblerTests.java2
-rw-r--r--spring-context/src/test/java/org/springframework/jmx/support/ConnectorServerFactoryBeanTests.java15
-rw-r--r--spring-context/src/test/java/org/springframework/jmx/support/MBeanServerConnectionFactoryBeanTests.java9
-rw-r--r--spring-context/src/test/java/org/springframework/jndi/JndiPropertySourceTests.java36
-rw-r--r--spring-context/src/test/java/org/springframework/remoting/rmi/RmiSupportTests.java3
-rw-r--r--spring-context/src/test/java/org/springframework/scheduling/annotation/EnableAsyncTests.java91
-rw-r--r--spring-context/src/test/java/org/springframework/scheduling/annotation/EnableSchedulingTests.java244
-rw-r--r--spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java189
-rw-r--r--spring-context/src/test/java/org/springframework/scheduling/config/LazyScheduledTasksBeanDefinitionParserTests.java6
-rw-r--r--spring-context/src/test/java/org/springframework/scheduling/support/CronSequenceGeneratorTests.java47
-rw-r--r--spring-context/src/test/java/org/springframework/scripting/groovy/GroovyScriptFactoryTests.java11
-rw-r--r--spring-context/src/test/java/org/springframework/scripting/jruby/AdvisedJRubyScriptFactoryTests.java6
-rw-r--r--spring-context/src/test/java/org/springframework/tests/sample/beans/Employee.java9
-rw-r--r--spring-context/src/test/java/org/springframework/validation/DataBinderTests.java47
-rw-r--r--spring-context/src/test/java/org/springframework/validation/beanvalidation/SpringValidatorAdapterTests.java225
-rw-r--r--spring-context/src/test/resources/org/springframework/aop/framework/autoproxy/AdvisorAutoProxyCreatorTests-common-interceptors.xml43
-rw-r--r--spring-context/src/test/resources/org/springframework/beans/factory/xml/XmlBeanFactoryTests-constructorArg.xml8
-rw-r--r--spring-context/src/test/resources/org/springframework/cache/config/cache-advice.xml3
-rw-r--r--spring-context/src/test/resources/org/springframework/context/config/contextNamespaceHandlerTests-location.xml6
-rw-r--r--spring-context/src/test/resources/org/springframework/context/config/test-bar.properties4
-rw-r--r--spring-context/src/test/resources/org/springframework/scripting/groovy/jruby-with-xsd-proxy-target-class.xml17
-rw-r--r--spring-core/src/main/java/org/springframework/asm/ClassReader.java3
-rw-r--r--spring-core/src/main/java/org/springframework/asm/ClassWriter.java47
-rw-r--r--spring-core/src/main/java/org/springframework/asm/Frame.java2
-rw-r--r--spring-core/src/main/java/org/springframework/asm/Handle.java60
-rw-r--r--spring-core/src/main/java/org/springframework/core/CollectionFactory.java6
-rw-r--r--spring-core/src/main/java/org/springframework/core/ConfigurableObjectInputStream.java12
-rw-r--r--spring-core/src/main/java/org/springframework/core/DecoratingClassLoader.java33
-rw-r--r--spring-core/src/main/java/org/springframework/core/DecoratingProxy.java47
-rw-r--r--spring-core/src/main/java/org/springframework/core/MethodClassKey.java85
-rw-r--r--spring-core/src/main/java/org/springframework/core/MethodIntrospector.java15
-rw-r--r--spring-core/src/main/java/org/springframework/core/MethodParameter.java78
-rw-r--r--spring-core/src/main/java/org/springframework/core/OverridingClassLoader.java37
-rw-r--r--spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java7
-rw-r--r--spring-core/src/main/java/org/springframework/core/SpringProperties.java3
-rw-r--r--spring-core/src/main/java/org/springframework/core/annotation/AbstractAliasAwareAnnotationAttributeExtractor.java17
-rw-r--r--spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java1135
-rw-r--r--spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributeExtractor.java5
-rw-r--r--spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java100
-rw-r--r--spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java20
-rw-r--r--spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java293
-rw-r--r--spring-core/src/main/java/org/springframework/core/annotation/DefaultAnnotationAttributeExtractor.java7
-rw-r--r--spring-core/src/main/java/org/springframework/core/annotation/MapAnnotationAttributeExtractor.java36
-rw-r--r--spring-core/src/main/java/org/springframework/core/annotation/SynthesizedAnnotationInvocationHandler.java31
-rw-r--r--spring-core/src/main/java/org/springframework/core/annotation/SynthesizingMethodParameter.java57
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java6
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/converter/ConverterRegistry.java13
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/AbstractConditionalEnumConverter.java50
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/ConversionUtils.java15
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java8
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/EnumToIntegerConverter.java40
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/EnumToStringConverter.java21
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java17
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/IntegerToEnumConverterFactory.java52
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java7
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/StringToEnumConverterFactory.java13
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java3
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java17
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/ConfigurablePropertyResolver.java20
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/PropertyResolver.java24
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/PropertySourcesPropertyResolver.java67
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/SystemEnvironmentPropertySource.java58
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java4
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java37
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/FileSystemResource.java5
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/PathResource.java6
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/ProtocolResolver.java42
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/Resource.java23
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/UrlResource.java7
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/support/DefaultPropertySourceFactory.java39
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java132
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/support/PropertySourceFactory.java41
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/support/ResourcePatternUtils.java6
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/support/ResourceRegion.java77
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/support/SpringFactoriesLoader.java10
-rw-r--r--spring-core/src/main/java/org/springframework/core/serializer/support/SerializationDelegate.java78
-rw-r--r--spring-core/src/main/java/org/springframework/core/style/ToStringCreator.java14
-rw-r--r--spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java23
-rw-r--r--spring-core/src/main/java/org/springframework/core/task/TaskDecorator.java45
-rw-r--r--spring-core/src/main/java/org/springframework/core/task/support/TaskExecutorAdapter.java50
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/classreading/AbstractRecursiveAnnotationVisitor.java4
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java51
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java5
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationReadingVisitorUtils.java43
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java8
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/classreading/RecursiveAnnotationArrayVisitor.java2
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/classreading/RecursiveAnnotationAttributesVisitor.java51
-rw-r--r--spring-core/src/main/java/org/springframework/lang/UsesSunMisc.java36
-rw-r--r--spring-core/src/main/java/org/springframework/util/AntPathMatcher.java65
-rw-r--r--spring-core/src/main/java/org/springframework/util/AutoPopulatingList.java15
-rw-r--r--spring-core/src/main/java/org/springframework/util/Base64Utils.java8
-rw-r--r--spring-core/src/main/java/org/springframework/util/ClassUtils.java11
-rw-r--r--spring-core/src/main/java/org/springframework/util/DigestUtils.java26
-rw-r--r--spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java10
-rw-r--r--spring-core/src/main/java/org/springframework/util/MimeType.java157
-rw-r--r--spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java5
-rw-r--r--spring-core/src/main/java/org/springframework/util/NumberUtils.java48
-rw-r--r--spring-core/src/main/java/org/springframework/util/ResizableByteArrayOutputStream.java2
-rw-r--r--spring-core/src/main/java/org/springframework/util/ResourceUtils.java4
-rw-r--r--spring-core/src/main/java/org/springframework/util/StopWatch.java4
-rw-r--r--spring-core/src/main/java/org/springframework/util/StreamUtils.java59
-rw-r--r--spring-core/src/main/java/org/springframework/util/UpdateMessageDigestInputStream.java4
-rw-r--r--spring-core/src/main/java/org/springframework/util/backoff/BackOff.java2
-rw-r--r--spring-core/src/main/java/org/springframework/util/comparator/CompoundComparator.java2
-rw-r--r--spring-core/src/main/java/org/springframework/util/comparator/InstanceComparator.java2
-rw-r--r--spring-core/src/main/java/org/springframework/util/concurrent/CompletableToListenableFutureAdapter.java5
-rw-r--r--spring-core/src/main/java/org/springframework/util/concurrent/FailureCallback.java10
-rw-r--r--spring-core/src/main/java/org/springframework/util/concurrent/FutureAdapter.java9
-rw-r--r--spring-core/src/main/java/org/springframework/util/concurrent/ListenableFuture.java23
-rw-r--r--spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureAdapter.java8
-rw-r--r--spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallback.java4
-rw-r--r--spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallbackRegistry.java38
-rw-r--r--spring-core/src/main/java/org/springframework/util/concurrent/SuccessCallback.java8
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/XmlValidationModeDetector.java2
-rw-r--r--spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java2
-rw-r--r--spring-core/src/test/java/org/springframework/core/SerializableTypeWrapperTests.java52
-rw-r--r--spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java247
-rw-r--r--spring-core/src/test/java/org/springframework/core/annotation/AnnotationAttributesTests.java366
-rw-r--r--spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java307
-rw-r--r--spring-core/src/test/java/org/springframework/core/annotation/ComposedRepeatableAnnotationsTests.java386
-rw-r--r--spring-core/src/test/java/org/springframework/core/annotation/MapAnnotationAttributeExtractorTests.java36
-rw-r--r--spring-core/src/test/java/org/springframework/core/annotation/MultipleComposedAnnotationsOnSingleAnnotatedElementTests.java368
-rw-r--r--spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java574
-rw-r--r--spring-core/src/test/java/org/springframework/core/convert/support/CollectionToCollectionConverterTests.java48
-rw-r--r--spring-core/src/test/java/org/springframework/core/convert/support/DefaultConversionServiceTests.java65
-rw-r--r--spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java121
-rw-r--r--spring-core/src/test/java/org/springframework/core/convert/support/MapToMapConverterTests.java66
-rw-r--r--spring-core/src/test/java/org/springframework/core/convert/support/StreamConverterTests.java21
-rw-r--r--spring-core/src/test/java/org/springframework/core/env/DummyEnvironment.java9
-rw-r--r--spring-core/src/test/java/org/springframework/core/env/PropertySourceTests.java20
-rw-r--r--spring-core/src/test/java/org/springframework/core/env/PropertySourcesPropertyResolverTests.java23
-rw-r--r--spring-core/src/test/java/org/springframework/core/env/SystemEnvironmentPropertySourceTests.java55
-rw-r--r--spring-core/src/test/java/org/springframework/core/io/support/DummyPackagePrivateFactory.java26
-rw-r--r--spring-core/src/test/java/org/springframework/core/io/support/ResourceRegionTests.java47
-rw-r--r--spring-core/src/test/java/org/springframework/core/io/support/SpringFactoriesLoaderTests.java56
-rw-r--r--spring-core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java24
-rw-r--r--spring-core/src/test/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitorMemberClassTests.java3
-rw-r--r--spring-core/src/test/java/org/springframework/tests/Assume.java58
-rw-r--r--spring-core/src/test/java/org/springframework/tests/JavaVersion.java95
-rw-r--r--spring-core/src/test/java/org/springframework/tests/TestGroup.java11
-rw-r--r--spring-core/src/test/java/org/springframework/tests/TestGroupTests.java8
-rw-r--r--spring-core/src/test/java/org/springframework/util/AntPathMatcherTests.java55
-rw-r--r--spring-core/src/test/java/org/springframework/util/AutoPopulatingListTests.java4
-rw-r--r--spring-core/src/test/java/org/springframework/util/DigestUtilsTests.java32
-rw-r--r--spring-core/src/test/java/org/springframework/util/LinkedCaseInsensitiveMapTests.java22
-rw-r--r--spring-core/src/test/java/org/springframework/util/MimeTypeTests.java15
-rw-r--r--spring-core/src/test/java/org/springframework/util/NumberUtilsTests.java19
-rw-r--r--spring-core/src/test/java/org/springframework/util/ResizableByteArrayOutputStreamTests.java2
-rw-r--r--spring-core/src/test/java/org/springframework/util/StreamUtilsTests.java10
-rw-r--r--spring-core/src/test/java/org/springframework/util/xml/AbstractStaxHandlerTestCase.java2
-rw-r--r--spring-core/src/test/java/org/springframework/util/xml/AbstractStaxXMLReaderTestCase.java8
-rw-r--r--spring-core/src/test/resources/META-INF/spring.factories9
-rw-r--r--spring-core/src/test/resources/org/springframework/core/io/support/springFactoriesLoaderTests.properties2
-rw-r--r--spring-expression/src/main/java/org/springframework/expression/BeanResolver.java9
-rw-r--r--spring-expression/src/main/java/org/springframework/expression/spel/CodeFlow.java147
-rw-r--r--spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java4
-rw-r--r--spring-expression/src/main/java/org/springframework/expression/spel/ast/BeanReference.java12
-rw-r--r--spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java4
-rw-r--r--spring-expression/src/main/java/org/springframework/expression/spel/ast/InlineList.java12
-rw-r--r--spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorInstanceof.java22
-rw-r--r--spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java13
-rw-r--r--spring-expression/src/main/java/org/springframework/expression/spel/standard/TokenKind.java4
-rw-r--r--spring-expression/src/main/java/org/springframework/expression/spel/standard/Tokenizer.java12
-rw-r--r--spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java53
-rw-r--r--spring-expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java10
-rw-r--r--spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java112
-rw-r--r--spring-expression/src/test/java/org/springframework/expression/spel/ExpressionLanguageScenarioTests.java17
-rw-r--r--spring-expression/src/test/java/org/springframework/expression/spel/ExpressionStateTests.java12
-rw-r--r--spring-expression/src/test/java/org/springframework/expression/spel/IndexingTests.java20
-rw-r--r--spring-expression/src/test/java/org/springframework/expression/spel/OperatorOverloaderTests.java45
-rw-r--r--spring-expression/src/test/java/org/springframework/expression/spel/ParsingTests.java3
-rw-r--r--spring-expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java36
-rw-r--r--spring-expression/src/test/java/org/springframework/expression/spel/ScenariosForSpringSecurity.java9
-rw-r--r--spring-expression/src/test/java/org/springframework/expression/spel/SelectionAndProjectionTests.java3
-rw-r--r--spring-expression/src/test/java/org/springframework/expression/spel/SetValueTests.java31
-rw-r--r--spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java71
-rw-r--r--spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationPerformanceTests.java267
-rw-r--r--spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java33
-rw-r--r--spring-expression/src/test/java/org/springframework/expression/spel/StandardTypeLocatorTests.java3
-rw-r--r--spring-expression/src/test/java/org/springframework/expression/spel/VariableAndFunctionTests.java7
-rw-r--r--spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelParserTests.java61
-rw-r--r--spring-instrument-tomcat/src/main/java/org/springframework/instrument/classloading/tomcat/TomcatInstrumentableClassLoader.java4
-rw-r--r--spring-jdbc/src/main/java/org/springframework/jdbc/config/DatabasePopulatorConfigUtils.java20
-rw-r--r--spring-jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java41
-rw-r--r--spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java34
-rw-r--r--spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/TableMetaDataContext.java6
-rw-r--r--spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcInsert.java3
-rw-r--r--spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcInsert.java5
-rw-r--r--spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcInsertOperations.java3
-rw-r--r--spring-jdbc/src/main/java/org/springframework/jdbc/datasource/AbstractDriverBasedDataSource.java52
-rw-r--r--spring-jdbc/src/main/java/org/springframework/jdbc/datasource/ConnectionHandle.java4
-rw-r--r--spring-jdbc/src/main/java/org/springframework/jdbc/datasource/ConnectionHolder.java6
-rw-r--r--spring-jdbc/src/main/java/org/springframework/jdbc/datasource/UserCredentialsDataSourceAdapter.java39
-rw-r--r--spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/CompositeDatabasePopulator.java29
-rw-r--r--spring-jdbc/src/main/java/org/springframework/jdbc/object/GenericSqlQuery.java54
-rw-r--r--spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodes.java4
-rw-r--r--spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/DefaultLobHandler.java1
-rw-r--r--spring-jdbc/src/main/resources/META-INF/spring.schemas3
-rw-r--r--spring-jdbc/src/main/resources/org/springframework/jdbc/config/spring-jdbc-4.3.xsd223
-rw-r--r--spring-jdbc/src/main/resources/org/springframework/jdbc/support/sql-error-codes.xml7
-rw-r--r--spring-jdbc/src/test/java/org/springframework/jdbc/config/JdbcNamespaceIntegrationTests.java13
-rw-r--r--spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateQueryTests.java3
-rw-r--r--spring-jdbc/src/test/java/org/springframework/jdbc/datasource/DataSourceJtaTransactionTests.java9
-rw-r--r--spring-jdbc/src/test/java/org/springframework/jdbc/datasource/DataSourceTransactionManagerTests.java4
-rw-r--r--spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/CompositeDatabasePopulatorTests.java92
-rw-r--r--spring-jdbc/src/test/java/org/springframework/jdbc/datasource/lookup/BeanFactoryDataSourceLookupTests.java5
-rw-r--r--spring-jdbc/src/test/java/org/springframework/jdbc/object/GenericSqlQueryTests.java19
-rw-r--r--spring-jdbc/src/test/java/org/springframework/jdbc/object/StoredProcedureTests.java4
-rw-r--r--spring-jdbc/src/test/java/org/springframework/jdbc/support/rowset/ResultSetWrappingRowSetTests.java3
-rw-r--r--spring-jdbc/src/test/resources/org/springframework/jdbc/config/jdbc-config-custom-separator.xml13
-rw-r--r--spring-jdbc/src/test/resources/org/springframework/jdbc/config/jdbc-initialize-custom-separator.xml15
-rw-r--r--spring-jdbc/src/test/resources/org/springframework/jdbc/object/GenericSqlQueryTests-context.xml26
-rw-r--r--spring-jms/src/main/java/org/springframework/jms/UncategorizedJmsException.java4
-rw-r--r--spring-jms/src/main/java/org/springframework/jms/annotation/EnableJms.java2
-rw-r--r--spring-jms/src/main/java/org/springframework/jms/annotation/JmsListener.java8
-rw-r--r--spring-jms/src/main/java/org/springframework/jms/annotation/JmsListenerAnnotationBeanPostProcessor.java17
-rw-r--r--spring-jms/src/main/java/org/springframework/jms/annotation/JmsListeners.java5
-rw-r--r--spring-jms/src/main/java/org/springframework/jms/config/JcaListenerContainerParser.java4
-rw-r--r--spring-jms/src/main/java/org/springframework/jms/config/MethodJmsListenerEndpoint.java44
-rw-r--r--spring-jms/src/main/java/org/springframework/jms/connection/CachingConnectionFactory.java22
-rw-r--r--spring-jms/src/main/java/org/springframework/jms/connection/SessionProxy.java2
-rw-r--r--spring-jms/src/main/java/org/springframework/jms/connection/TransactionAwareConnectionFactoryProxy.java4
-rw-r--r--spring-jms/src/main/java/org/springframework/jms/core/JmsTemplate.java45
-rw-r--r--spring-jms/src/main/java/org/springframework/jms/listener/AbstractPollingMessageListenerContainer.java10
-rw-r--r--spring-jms/src/main/java/org/springframework/jms/listener/DefaultMessageListenerContainer.java14
-rw-r--r--spring-jms/src/main/java/org/springframework/jms/listener/adapter/AbstractAdaptableMessageListener.java106
-rw-r--r--spring-jms/src/main/java/org/springframework/jms/listener/adapter/MessagingMessageListenerAdapter.java16
-rw-r--r--spring-jms/src/main/java/org/springframework/jms/listener/endpoint/JmsMessageEndpointFactory.java4
-rw-r--r--spring-jms/src/main/java/org/springframework/jms/support/JmsAccessor.java2
-rw-r--r--spring-jms/src/main/java/org/springframework/jms/support/converter/MappingJackson2MessageConverter.java165
-rw-r--r--spring-jms/src/main/java/org/springframework/jms/support/converter/MessagingMessageConverter.java33
-rw-r--r--spring-jms/src/main/java/org/springframework/jms/support/converter/SmartMessageConverter.java51
-rw-r--r--spring-jms/src/main/java/org/springframework/jms/support/destination/JmsDestinationAccessor.java41
-rw-r--r--spring-jms/src/main/resources/META-INF/spring.schemas3
-rw-r--r--spring-jms/src/main/resources/org/springframework/jms/config/spring-jms-4.3.xsd638
-rw-r--r--spring-jms/src/test/java/org/springframework/jms/annotation/EnableJmsTests.java64
-rw-r--r--spring-jms/src/test/java/org/springframework/jms/annotation/JmsListenerAnnotationBeanPostProcessorTests.java6
-rw-r--r--spring-jms/src/test/java/org/springframework/jms/config/JmsListenerContainerFactoryIntegrationTests.java19
-rw-r--r--spring-jms/src/test/java/org/springframework/jms/config/MethodJmsListenerEndpointTests.java44
-rw-r--r--spring-jms/src/test/java/org/springframework/jms/listener/adapter/MessageListenerAdapterTests.java12
-rw-r--r--spring-jms/src/test/java/org/springframework/jms/listener/adapter/MessagingMessageListenerAdapterTests.java92
-rw-r--r--spring-jms/src/test/java/org/springframework/jms/support/SimpleMessageConverterTests.java30
-rw-r--r--spring-jms/src/test/java/org/springframework/jms/support/converter/MappingJackson2MessageConverterTests.java134
-rw-r--r--spring-jms/src/test/java/org/springframework/jms/support/converter/MessagingMessageConverterTests.java46
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/converter/MappingJackson2MessageConverter.java54
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/converter/MarshallingMessageConverter.java2
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/converter/StringMessageConverter.java4
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/handler/HandlerMethod.java71
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/handler/HandlerMethodSelector.java2
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/SendTo.java9
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/AbstractNamedValueMethodArgumentResolver.java55
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/DefaultMessageHandlerMethodFactory.java2
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MessageMethodArgumentResolver.java98
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/AbstractExceptionHandlerMethodResolver.java33
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/HandlerMethodArgumentResolverComposite.java14
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethod.java15
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/SendToUser.java7
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SendToMethodReturnValueHandler.java69
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandler.java32
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SubscriptionMethodReturnValueHandler.java65
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/simp/broker/DefaultSubscriptionRegistry.java38
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/simp/broker/SimpleBrokerMessageHandler.java50
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/simp/config/AbstractMessageBrokerConfiguration.java32
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/simp/config/MessageBrokerRegistry.java38
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/simp/config/TaskExecutorRegistration.java2
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/DefaultStompSession.java28
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandler.java2
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompHeaderAccessor.java2
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompSession.java15
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/simp/user/DefaultUserDestinationResolver.java31
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/simp/user/MultiServerUserRegistry.java331
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/simp/user/UserRegistryMessageHandler.java37
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/simp/user/UserSessionRegistryAdapter.java39
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/support/MessageHeaderAccessor.java21
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/tcp/TcpConnection.java4
-rw-r--r--spring-messaging/src/main/java/org/springframework/messaging/tcp/reactor/AbstractPromiseToListenableFutureAdapter.java7
-rw-r--r--spring-messaging/src/test/java/org/springframework/messaging/converter/AbstractMessageConverterTests.java136
-rw-r--r--spring-messaging/src/test/java/org/springframework/messaging/converter/MessageConverterTests.java5
-rw-r--r--spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/AnnotationExceptionHandlerMethodResolverTests.java18
-rw-r--r--spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/HeaderMethodArgumentResolverTests.java44
-rw-r--r--spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/MessageMethodArgumentResolverTests.java92
-rw-r--r--spring-messaging/src/test/java/org/springframework/messaging/handler/invocation/MethodMessageHandlerTests.java5
-rw-r--r--spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SendToMethodReturnValueHandlerTests.java379
-rw-r--r--spring-messaging/src/test/java/org/springframework/messaging/simp/config/MessageBrokerConfigurationTests.java29
-rw-r--r--spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/DefaultStompSessionTests.java73
-rw-r--r--spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/Reactor2TcpStompClientTests.java22
-rw-r--r--spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandlerIntegrationTests.java49
-rw-r--r--spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompCodecTests.java3
-rw-r--r--spring-messaging/src/test/java/org/springframework/messaging/simp/user/DefaultUserDestinationResolverTests.java39
-rw-r--r--spring-messaging/src/test/java/org/springframework/messaging/simp/user/MultiServerUserRegistryTests.java147
-rw-r--r--spring-messaging/src/test/java/org/springframework/messaging/simp/user/TestSimpSession.java17
-rw-r--r--spring-messaging/src/test/java/org/springframework/messaging/simp/user/TestSimpSubscription.java27
-rw-r--r--spring-messaging/src/test/java/org/springframework/messaging/simp/user/TestSimpUser.java15
-rw-r--r--spring-messaging/src/test/java/org/springframework/messaging/support/MessageHeaderAccessorTests.java24
-rw-r--r--spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/HibernateExceptionTranslator.java16
-rw-r--r--spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/HibernateTemplate.java82
-rw-r--r--spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/HibernateTransactionManager.java24
-rw-r--r--spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBean.java102
-rw-r--r--spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBuilder.java144
-rw-r--r--spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/SessionFactoryUtils.java117
-rw-r--r--spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/SpringFlushSynchronization.java11
-rw-r--r--spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/SpringJtaSessionContext.java3
-rw-r--r--spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/SpringSessionContext.java5
-rw-r--r--spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/SpringSessionSynchronization.java22
-rw-r--r--spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/support/OpenSessionInViewFilter.java3
-rw-r--r--spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/support/OpenSessionInViewInterceptor.java3
-rw-r--r--spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/support/OpenSessionInterceptor.java3
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/AbstractSessionFactoryBean.java2
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/FilterDefinitionFactoryBean.java2
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateAccessor.java2
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateCallback.java2
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateExceptionTranslator.java2
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateJdbcException.java2
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateObjectRetrievalFailureException.java2
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateOperations.java2
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateOptimisticLockingFailureException.java2
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateQueryException.java2
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateSystemException.java2
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateTemplate.java3
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateTransactionManager.java4
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/LocalDataSourceConnectionProvider.java2
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/LocalJtaDataSourceConnectionProvider.java2
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/LocalRegionFactoryProxy.java2
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/LocalSessionFactoryBean.java12
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/LocalTransactionManagerLookup.java2
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/SessionFactoryUtils.java3
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/SessionHolder.java2
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/SpringSessionContext.java2
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/SpringSessionSynchronization.java2
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/SpringTransactionFactory.java2
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/TransactionAwareDataSourceConnectionProvider.java2
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/TypeDefinitionBean.java2
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/annotation/AnnotationSessionFactoryBean.java6
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/support/AbstractLobType.java7
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/support/AsyncRequestInterceptor.java10
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/support/BlobByteArrayType.java2
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/support/BlobSerializableType.java2
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/support/BlobStringType.java2
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/support/ClobStringType.java2
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/support/HibernateDaoSupport.java28
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/support/IdTransferringMergeEventListener.java4
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewFilter.java24
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewInterceptor.java31
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInterceptor.java8
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/hibernate3/support/ScopedBeanInterceptor.java2
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java99
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java4
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/jpa/ExtendedEntityManagerCreator.java7
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/jpa/JpaSystemException.java3
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/jpa/JpaVendorAdapter.java2
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.java17
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/jpa/SharedEntityManagerCreator.java12
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/ClassFileTransformerAdapter.java57
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/DefaultPersistenceUnitManager.java34
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitReader.java89
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java5
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaDialect.java27
-rw-r--r--spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaVendorAdapter.java30
-rw-r--r--spring-orm/src/main/java/overview.html2
-rw-r--r--spring-orm/src/test/java/org/springframework/orm/hibernate3/HibernateInterceptorTests.java2
-rw-r--r--spring-orm/src/test/java/org/springframework/orm/hibernate3/HibernateJtaTransactionTests.java21
-rw-r--r--spring-orm/src/test/java/org/springframework/orm/hibernate3/HibernateTemplateTests.java4
-rw-r--r--spring-orm/src/test/java/org/springframework/orm/hibernate3/HibernateTransactionManagerTests.java4
-rw-r--r--spring-orm/src/test/java/org/springframework/orm/hibernate3/LocalSessionFactoryBeanTests.java2
-rw-r--r--spring-orm/src/test/java/org/springframework/orm/hibernate3/support/HibernateDaoSupportTests.java2
-rw-r--r--spring-orm/src/test/java/org/springframework/orm/hibernate3/support/LobTypeTests.java2
-rw-r--r--spring-orm/src/test/java/org/springframework/orm/hibernate3/support/OpenSessionInViewTests.java2
-rw-r--r--spring-orm/src/test/java/org/springframework/orm/hibernate3/support/ScopedBeanInterceptorTests.java2
-rw-r--r--spring-orm/src/test/java/org/springframework/orm/jpa/persistenceunit/PersistenceXmlParsingTests.java15
-rw-r--r--spring-orm/src/test/resources/org/springframework/orm/jpa/hibernate/hibernate-manager.xml13
-rw-r--r--spring-oxm/oxm.gradle34
-rw-r--r--spring-oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java4
-rw-r--r--spring-oxm/src/main/java/org/springframework/oxm/jibx/JibxMarshaller.java4
-rw-r--r--spring-oxm/src/main/java/org/springframework/oxm/xstream/XStreamMarshaller.java12
-rw-r--r--spring-oxm/src/main/resources/META-INF/spring.schemas3
-rw-r--r--spring-oxm/src/main/resources/org/springframework/oxm/config/spring-oxm-4.3.xsd136
-rw-r--r--spring-oxm/src/test/java/org/springframework/oxm/config/OxmNamespaceHandlerTests.java10
-rw-r--r--spring-oxm/src/test/java/org/springframework/oxm/jibx/Flights.java3
-rw-r--r--spring-oxm/src/test/java/org/springframework/oxm/jibx/JibxMarshallerTests.java12
-rw-r--r--spring-oxm/src/test/java/org/springframework/oxm/jibx/JibxUnmarshallerTests.java11
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/MockHttpServletRequest.java4
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java4
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/MockServletContext.java11
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/portlet/ServletWrappingPortletContext.java5
-rw-r--r--spring-test/src/main/java/org/springframework/test/annotation/Commit.java4
-rw-r--r--spring-test/src/main/java/org/springframework/test/annotation/ProfileValueSourceConfiguration.java10
-rw-r--r--spring-test/src/main/java/org/springframework/test/annotation/ProfileValueUtils.java48
-rw-r--r--spring-test/src/main/java/org/springframework/test/annotation/Rollback.java2
-rw-r--r--spring-test/src/main/java/org/springframework/test/annotation/TestAnnotationUtils.java5
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/ActiveProfiles.java4
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java71
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/BootstrapWith.java6
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java6
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/ContextConfigurationAttributes.java30
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/ContextCustomizer.java49
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/ContextCustomizerFactory.java52
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/ContextHierarchy.java6
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java64
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/TestContextBootstrapper.java15
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/TestContextManager.java76
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/TestExecutionListener.java2
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/TestExecutionListeners.java4
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/TestPropertySource.java4
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/cache/ContextCache.java24
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/cache/ContextCacheUtils.java54
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/cache/DefaultContextCache.java93
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java4
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java9
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/junit4/AbstractJUnit4SpringContextTests.java10
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/junit4/AbstractTransactionalJUnit4SpringContextTests.java9
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.java27
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/junit4/SpringRunner.java52
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/junit4/package-info.java3
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/junit4/rules/SpringClassRule.java14
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/junit4/rules/SpringMethodRule.java14
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/junit4/statements/ProfileValueChecker.java24
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunAfterTestClassCallbacks.java8
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunAfterTestMethodCallbacks.java8
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java24
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/support/AbstractDelegatingSmartContextLoader.java17
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java7
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java132
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/support/AnnotationConfigContextLoaderUtils.java6
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/support/ContextLoaderUtils.java18
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceUtils.java127
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/testng/AbstractTransactionalTestNGSpringContextTests.java3
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/transaction/AfterTransaction.java14
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/transaction/BeforeTransaction.java14
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/transaction/TestContextTransactionUtils.java41
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/transaction/TransactionConfiguration.java7
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java166
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/web/AbstractGenericWebContextLoader.java9
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/web/ServletTestExecutionListener.java52
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/web/WebAppConfiguration.java3
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/web/WebMergedContextConfiguration.java44
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/web/WebTestContextBootstrapper.java22
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/web/socket/MockServerContainer.java135
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/web/socket/MockServerContainerContextCustomizer.java52
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/web/socket/MockServerContainerContextCustomizerFactory.java73
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/web/socket/package-info.java4
-rw-r--r--spring-test/src/main/java/org/springframework/test/jdbc/JdbcTestUtils.java7
-rw-r--r--spring-test/src/main/java/org/springframework/test/util/AopTestUtils.java3
-rw-r--r--spring-test/src/main/java/org/springframework/test/util/AssertionErrors.java14
-rw-r--r--spring-test/src/main/java/org/springframework/test/util/JsonPathExpectationsHelper.java39
-rw-r--r--spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java43
-rw-r--r--spring-test/src/main/java/org/springframework/test/util/ReflectionTestUtils.java71
-rw-r--r--spring-test/src/main/java/org/springframework/test/web/client/AbstractRequestExpectationManager.java189
-rw-r--r--spring-test/src/main/java/org/springframework/test/web/client/DefaultRequestExpectation.java146
-rw-r--r--spring-test/src/main/java/org/springframework/test/web/client/ExpectedCount.java118
-rw-r--r--spring-test/src/main/java/org/springframework/test/web/client/MockMvcClientHttpRequestFactory.java8
-rw-r--r--spring-test/src/main/java/org/springframework/test/web/client/MockRestServiceServer.java308
-rw-r--r--spring-test/src/main/java/org/springframework/test/web/client/RequestExpectation.java42
-rw-r--r--spring-test/src/main/java/org/springframework/test/web/client/RequestExpectationManager.java63
-rw-r--r--spring-test/src/main/java/org/springframework/test/web/client/RequestMatcher.java3
-rw-r--r--spring-test/src/main/java/org/springframework/test/web/client/RequestMatcherClientHttpRequest.java81
-rw-r--r--spring-test/src/main/java/org/springframework/test/web/client/SimpleRequestExpectationManager.java82
-rw-r--r--spring-test/src/main/java/org/springframework/test/web/client/UnorderedRequestExpectationManager.java58
-rw-r--r--spring-test/src/main/java/org/springframework/test/web/client/match/ContentRequestMatchers.java43
-rw-r--r--spring-test/src/main/java/org/springframework/test/web/client/match/MockRestRequestMatchers.java2
-rw-r--r--spring-test/src/main/java/org/springframework/test/web/client/response/DefaultResponseCreator.java17
-rw-r--r--spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnection.java3
-rw-r--r--spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilder.java2
-rw-r--r--spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnection.java109
-rw-r--r--spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionBuilderSupport.java48
-rw-r--r--spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilder.java30
-rw-r--r--spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilder.java2
-rw-r--r--spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/WebConnectionHtmlUnitDriver.java8
-rw-r--r--spring-test/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java119
-rw-r--r--spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMvcRequestBuilders.java69
-rw-r--r--spring-test/src/main/java/org/springframework/test/web/servlet/result/HandlerResultMatchers.java91
-rw-r--r--spring-test/src/main/java/org/springframework/test/web/servlet/result/HeaderResultMatchers.java88
-rw-r--r--spring-test/src/main/java/org/springframework/test/web/servlet/result/JsonPathResultMatchers.java62
-rw-r--r--spring-test/src/main/java/org/springframework/test/web/servlet/result/MockMvcResultMatchers.java2
-rw-r--r--spring-test/src/main/java/org/springframework/test/web/servlet/result/StatusResultMatchers.java9
-rw-r--r--spring-test/src/main/java/org/springframework/test/web/servlet/setup/AbstractMockMvcBuilder.java4
-rw-r--r--spring-test/src/main/resources/META-INF/spring.factories5
-rw-r--r--spring-test/src/test/java/org/springframework/mock/web/MockFilterChainTests.java6
-rw-r--r--spring-test/src/test/java/org/springframework/test/annotation/ProfileValueUtilsTests.java28
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/BootstrapUtilsTests.java24
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/MergedContextConfigurationTests.java34
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/TestExecutionListenersTests.java13
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/cache/ClassLevelDirtiesContextTestNGTests.java36
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/cache/ClassLevelDirtiesContextTests.java17
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheTests.java3
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheUtilsTests.java89
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/cache/LruContextCacheTests.java177
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/cache/SpringRunnerContextCacheTests.java3
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ActiveProfilesInterfaceTests.java65
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ActiveProfilesTestInterface.java27
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/BootstrapWithInterfaceTests.java (renamed from spring-core/src/test/java/org/springframework/tests/BuildTests.java)25
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/BootstrapWithTestInterface.java47
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ContextConfigurationInterfaceTests.java45
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ContextConfigurationTestInterface.java39
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ContextHierarchyInterfaceTests.java58
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ContextHierarchyTestInterface.java31
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/DirtiesContextInterfaceTests.java95
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/DirtiesContextTestInterface.java27
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/SqlConfigInterfaceTests.java46
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/SqlConfigTestInterface.java32
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/TestPropertySourceInterfaceTests.java57
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/TestPropertySourceTestInterface.java27
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/WebAppConfigurationInterfaceTests.java (renamed from spring-core/src/test/java/org/springframework/tests/JavaVersionTests.java)31
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/WebAppConfigurationTestInterface.java37
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/env/InlinedPropertiesOverridePropertiesFilesTestPropertySourceTests.java69
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/SingleTestClassWithTwoLevelContextHierarchyTests.java6
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/jdbc/ComposedAnnotationSqlScriptsTests.java72
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/jdbc/MetaAnnotationSqlScriptsTests.java4
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/jdbc/PrimaryDataSourceTests.java89
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/AbstractTransactionalSpringRunnerTests.java4
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/BeforeAndAfterTransactionAnnotationTests.java11
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/ClassLevelDisabledSpringRunnerTests.java4
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/ClassLevelTransactionalSpringRunnerTests.java4
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/ContextCustomizerSpringRunnerTests.java66
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/CustomDefaultContextLoaderClassSpringRunnerTests.java4
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackFalseRollbackAnnotationTransactionalTests.java4
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackTrueRollbackAnnotationTransactionalTests.java4
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/EnabledAndIgnoredSpringRunnerTests.java6
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/ExpectedExceptionSpringRunnerTests.java6
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/FailingBeforeAndAfterMethodsJUnitTests.java10
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/HardCodedProfileValueSourceSpringRunnerTests.java4
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/MethodLevelTransactionalSpringRunnerTests.java4
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/OptionalContextConfigurationSpringRunnerTests.java58
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests.java6
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/RepeatedSpringRunnerTests.java6
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit47ClassRunnerRuleTests.java8
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerAppCtxTests.java6
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4TestSuite.java6
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/StandardJUnit4FeaturesSpringRunnerTests.java6
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/TimedSpringRunnerTests.java6
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/TimedTransactionalSpringRunnerTests.java4
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/BarConfig.java34
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/FooConfig.java34
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/InitializerConfiguredViaMetaAnnotationTests.java92
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/support/BootstrapTestUtilsContextInitializerTests.java66
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/support/BootstrapTestUtilsMergedConfigTests.java33
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsContextHierarchyTests.java10
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/support/DelegatingSmartContextLoaderTests.java13
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/support/TestPropertySourceUtilsTests.java145
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/testng/AnnotationConfigTransactionalTestNGSpringContextTests.java30
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/testng/ConcreteTransactionalTestNGSpringContextTests.java30
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/testng/web/ServletTestExecutionListenerTestNGIntegrationTests.java6
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/testng/web/TestNGSpringContextWebTests.java24
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/transaction/PrimaryTransactionManagerTests.java122
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/transaction/TransactionalTestExecutionListenerTests.java166
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/web/ServletTestExecutionListenerTests.java110
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/web/WebAppConfigurationBootstrapWithTests.java78
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/web/socket/WebSocketServletServerContainerFactoryBeanTests.java67
-rw-r--r--spring-test/src/test/java/org/springframework/test/util/MetaAnnotationUtilsTests.java119
-rw-r--r--spring-test/src/test/java/org/springframework/test/util/ReflectionTestUtilsTests.java105
-rw-r--r--spring-test/src/test/java/org/springframework/test/util/subpackage/LegacyEntity.java34
-rw-r--r--spring-test/src/test/java/org/springframework/test/util/subpackage/LegacyEntityException.java32
-rw-r--r--spring-test/src/test/java/org/springframework/test/util/subpackage/PersistentEntity.java7
-rw-r--r--spring-test/src/test/java/org/springframework/test/util/subpackage/Person.java84
-rw-r--r--spring-test/src/test/java/org/springframework/test/util/subpackage/PersonEntity.java96
-rw-r--r--spring-test/src/test/java/org/springframework/test/web/client/DefaultRequestExpectationTests.java99
-rw-r--r--spring-test/src/test/java/org/springframework/test/web/client/MockClientHttpRequestFactoryTests.java102
-rw-r--r--spring-test/src/test/java/org/springframework/test/web/client/MockRestServiceServerTests.java110
-rw-r--r--spring-test/src/test/java/org/springframework/test/web/client/SimpleRequestExpectationManagerTests.java173
-rw-r--r--spring-test/src/test/java/org/springframework/test/web/client/UnorderedRequestExpectationManagerTests.java133
-rw-r--r--spring-test/src/test/java/org/springframework/test/web/client/match/ContentRequestMatchersTests.java36
-rw-r--r--spring-test/src/test/java/org/springframework/test/web/client/match/MockRestRequestMatchersTests.java3
-rw-r--r--spring-test/src/test/java/org/springframework/test/web/client/samples/SampleAsyncTests.java39
-rw-r--r--spring-test/src/test/java/org/springframework/test/web/client/samples/SampleTests.java38
-rw-r--r--spring-test/src/test/java/org/springframework/test/web/client/samples/matchers/HeaderRequestMatchersIntegrationTests.java1
-rw-r--r--spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnectionTests.java3
-rw-r--r--spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilderTests.java32
-rw-r--r--spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcConnectionBuilderSupportTests.java85
-rw-r--r--spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilderTests.java97
-rw-r--r--spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionTests.java3
-rw-r--r--spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilderTests.java28
-rw-r--r--spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilderTests.java43
-rw-r--r--spring-test/src/test/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilderTests.java41
-rw-r--r--spring-test/src/test/java/org/springframework/test/web/servlet/result/JsonPathResultMatchersTests.java44
-rw-r--r--spring-test/src/test/java/org/springframework/test/web/servlet/result/StatusResultMatchersTests.java6
-rw-r--r--spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/AsyncControllerJavaConfigTests.java8
-rw-r--r--spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/PersonController.java15
-rw-r--r--spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/AsyncTests.java70
-rw-r--r--spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/ViewResolutionTests.java11
-rw-r--r--spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/ContentAssertionTests.java10
-rw-r--r--spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/HandlerAssertionTests.java74
-rw-r--r--spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/HeaderAssertionTests.java142
-rw-r--r--spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/XpathAssertionTests.java2
-rw-r--r--spring-test/src/test/resources/log4j.properties3
-rw-r--r--spring-tx/src/main/java/org/springframework/jca/endpoint/AbstractMessageEndpointFactory.java2
-rw-r--r--spring-tx/src/main/java/org/springframework/jca/endpoint/GenericMessageEndpointManager.java2
-rw-r--r--spring-tx/src/main/java/org/springframework/jca/work/WorkManagerTaskExecutor.java21
-rw-r--r--spring-tx/src/main/java/org/springframework/transaction/event/ApplicationListenerMethodTransactionalAdapter.java15
-rw-r--r--spring-tx/src/main/java/org/springframework/transaction/interceptor/AbstractFallbackTransactionAttributeSource.java50
-rw-r--r--spring-tx/src/main/java/org/springframework/transaction/interceptor/DefaultTransactionAttribute.java2
-rw-r--r--spring-tx/src/main/java/org/springframework/transaction/interceptor/RuleBasedTransactionAttribute.java2
-rw-r--r--spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java34
-rw-r--r--spring-tx/src/main/java/org/springframework/transaction/jta/JtaTransactionManager.java16
-rw-r--r--spring-tx/src/main/java/org/springframework/transaction/jta/JtaTransactionObject.java4
-rw-r--r--spring-tx/src/main/java/org/springframework/transaction/support/DefaultTransactionDefinition.java2
-rw-r--r--spring-tx/src/main/resources/META-INF/spring.schemas3
-rw-r--r--spring-tx/src/main/resources/org/springframework/transaction/config/spring-tx-4.3.xsd247
-rw-r--r--spring-tx/src/test/java/org/springframework/dao/annotation/PersistenceExceptionTranslationPostProcessorTests.java5
-rw-r--r--spring-tx/src/test/java/org/springframework/transaction/annotation/AnnotationTransactionAttributeSourceTests.java94
-rw-r--r--spring-tx/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementTests.java1
-rw-r--r--spring-tx/src/test/java/org/springframework/transaction/config/AnnotationDrivenTests.java12
-rw-r--r--spring-tx/src/test/java/org/springframework/transaction/config/NoSynch.java31
-rw-r--r--spring-tx/src/test/java/org/springframework/transaction/config/NoSynchTransactionManager.java32
-rw-r--r--spring-tx/src/test/java/org/springframework/transaction/config/SynchTransactionManager.java29
-rw-r--r--spring-tx/src/test/java/org/springframework/transaction/config/TransactionManagerConfiguration.java4
-rw-r--r--spring-tx/src/test/java/org/springframework/transaction/event/ApplicationListenerMethodTransactionalAdapterTests.java20
-rw-r--r--spring-web/src/main/java/org/springframework/http/CacheControl.java45
-rw-r--r--spring-web/src/main/java/org/springframework/http/HttpHeaders.java238
-rw-r--r--spring-web/src/main/java/org/springframework/http/HttpMethod.java2
-rw-r--r--spring-web/src/main/java/org/springframework/http/HttpRange.java49
-rw-r--r--spring-web/src/main/java/org/springframework/http/HttpStatus.java38
-rw-r--r--spring-web/src/main/java/org/springframework/http/MediaType.java87
-rw-r--r--spring-web/src/main/java/org/springframework/http/RequestEntity.java74
-rw-r--r--spring-web/src/main/java/org/springframework/http/ResponseEntity.java102
-rw-r--r--spring-web/src/main/java/org/springframework/http/client/AsyncClientHttpRequestExecution.java47
-rw-r--r--spring-web/src/main/java/org/springframework/http/client/AsyncClientHttpRequestInterceptor.java71
-rw-r--r--spring-web/src/main/java/org/springframework/http/client/ClientHttpRequestExecution.java13
-rw-r--r--spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactory.java21
-rw-r--r--spring-web/src/main/java/org/springframework/http/client/InterceptingAsyncClientHttpRequest.java117
-rw-r--r--spring-web/src/main/java/org/springframework/http/client/InterceptingAsyncClientHttpRequestFactory.java59
-rw-r--r--spring-web/src/main/java/org/springframework/http/client/Netty4ClientHttpResponse.java2
-rw-r--r--spring-web/src/main/java/org/springframework/http/client/OkHttp3AsyncClientHttpRequest.java102
-rw-r--r--spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpRequest.java71
-rw-r--r--spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpRequestFactory.java155
-rw-r--r--spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpResponse.java82
-rw-r--r--spring-web/src/main/java/org/springframework/http/client/OkHttpAsyncClientHttpRequest.java102
-rw-r--r--spring-web/src/main/java/org/springframework/http/client/OkHttpClientHttpRequest.java89
-rw-r--r--spring-web/src/main/java/org/springframework/http/client/OkHttpClientHttpRequestFactory.java48
-rw-r--r--spring-web/src/main/java/org/springframework/http/client/OkHttpClientHttpResponse.java3
-rw-r--r--spring-web/src/main/java/org/springframework/http/client/SimpleBufferingAsyncClientHttpRequest.java6
-rw-r--r--spring-web/src/main/java/org/springframework/http/client/SimpleBufferingClientHttpRequest.java9
-rw-r--r--spring-web/src/main/java/org/springframework/http/client/SimpleClientHttpResponse.java17
-rw-r--r--spring-web/src/main/java/org/springframework/http/client/SimpleStreamingAsyncClientHttpRequest.java4
-rw-r--r--spring-web/src/main/java/org/springframework/http/client/SimpleStreamingClientHttpRequest.java4
-rw-r--r--spring-web/src/main/java/org/springframework/http/client/support/BasicAuthorizationInterceptor.java66
-rw-r--r--spring-web/src/main/java/org/springframework/http/client/support/InterceptingAsyncHttpAccessor.java68
-rw-r--r--spring-web/src/main/java/org/springframework/http/converter/AbstractHttpMessageConverter.java41
-rw-r--r--spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java78
-rw-r--r--spring-web/src/main/java/org/springframework/http/converter/ObjectToStringHttpMessageConverter.java2
-rw-r--r--spring-web/src/main/java/org/springframework/http/converter/ResourceHttpMessageConverter.java42
-rw-r--r--spring-web/src/main/java/org/springframework/http/converter/ResourceRegionHttpMessageConverter.java190
-rw-r--r--spring-web/src/main/java/org/springframework/http/converter/StringHttpMessageConverter.java14
-rw-r--r--spring-web/src/main/java/org/springframework/http/converter/feed/AbstractWireFeedHttpMessageConverter.java2
-rw-r--r--spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java126
-rw-r--r--spring-web/src/main/java/org/springframework/http/converter/json/GsonHttpMessageConverter.java7
-rw-r--r--spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java94
-rw-r--r--spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperFactoryBean.java27
-rw-r--r--spring-web/src/main/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverter.java5
-rw-r--r--spring-web/src/main/java/org/springframework/http/converter/json/SpringHandlerInstantiator.java47
-rw-r--r--spring-web/src/main/java/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverter.java36
-rw-r--r--spring-web/src/main/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverter.java6
-rw-r--r--spring-web/src/main/java/org/springframework/http/converter/xml/MappingJackson2XmlHttpMessageConverter.java8
-rw-r--r--spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java17
-rw-r--r--spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java2
-rw-r--r--spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java7
-rw-r--r--spring-web/src/main/java/org/springframework/remoting/httpinvoker/HttpComponentsHttpInvokerRequestExecutor.java22
-rw-r--r--spring-web/src/main/java/org/springframework/remoting/httpinvoker/HttpInvokerServiceExporter.java28
-rw-r--r--spring-web/src/main/java/org/springframework/remoting/jaxws/SimpleJaxWsServiceExporter.java6
-rw-r--r--spring-web/src/main/java/org/springframework/web/HttpSessionRequiredException.java25
-rw-r--r--spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManager.java16
-rw-r--r--spring-web/src/main/java/org/springframework/web/accept/HeaderContentNegotiationStrategy.java15
-rw-r--r--spring-web/src/main/java/org/springframework/web/accept/PathExtensionContentNegotiationStrategy.java72
-rw-r--r--spring-web/src/main/java/org/springframework/web/accept/ServletPathExtensionContentNegotiationStrategy.java26
-rw-r--r--spring-web/src/main/java/org/springframework/web/bind/WebDataBinder.java48
-rw-r--r--spring-web/src/main/java/org/springframework/web/bind/annotation/CrossOrigin.java10
-rw-r--r--spring-web/src/main/java/org/springframework/web/bind/annotation/DeleteMapping.java90
-rw-r--r--spring-web/src/main/java/org/springframework/web/bind/annotation/GetMapping.java88
-rw-r--r--spring-web/src/main/java/org/springframework/web/bind/annotation/ModelAttribute.java24
-rw-r--r--spring-web/src/main/java/org/springframework/web/bind/annotation/PatchMapping.java90
-rw-r--r--spring-web/src/main/java/org/springframework/web/bind/annotation/PostMapping.java90
-rw-r--r--spring-web/src/main/java/org/springframework/web/bind/annotation/PutMapping.java90
-rw-r--r--spring-web/src/main/java/org/springframework/web/bind/annotation/RequestAttribute.java66
-rw-r--r--spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java16
-rw-r--r--spring-web/src/main/java/org/springframework/web/bind/annotation/ResponseStatus.java6
-rw-r--r--spring-web/src/main/java/org/springframework/web/bind/annotation/RestController.java14
-rw-r--r--spring-web/src/main/java/org/springframework/web/bind/annotation/RestControllerAdvice.java103
-rw-r--r--spring-web/src/main/java/org/springframework/web/bind/annotation/SessionAttribute.java74
-rw-r--r--spring-web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvocationException.java2
-rw-r--r--spring-web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java2
-rw-r--r--spring-web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodResolver.java2
-rw-r--r--spring-web/src/main/java/org/springframework/web/bind/support/WebRequestDataBinder.java7
-rw-r--r--spring-web/src/main/java/org/springframework/web/client/AsyncRestTemplate.java173
-rw-r--r--spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java2
-rw-r--r--spring-web/src/main/java/org/springframework/web/client/HttpMessageConverterExtractor.java2
-rw-r--r--spring-web/src/main/java/org/springframework/web/client/HttpStatusCodeException.java79
-rw-r--r--spring-web/src/main/java/org/springframework/web/client/RestClientResponseException.java109
-rw-r--r--spring-web/src/main/java/org/springframework/web/client/RestTemplate.java65
-rw-r--r--spring-web/src/main/java/org/springframework/web/client/UnknownHttpStatusCodeException.java69
-rw-r--r--spring-web/src/main/java/org/springframework/web/context/annotation/ApplicationScope.java64
-rw-r--r--spring-web/src/main/java/org/springframework/web/context/annotation/RequestScope.java64
-rw-r--r--spring-web/src/main/java/org/springframework/web/context/annotation/SessionScope.java64
-rw-r--r--spring-web/src/main/java/org/springframework/web/context/annotation/package-info.java4
-rw-r--r--spring-web/src/main/java/org/springframework/web/context/request/FacesRequestAttributes.java14
-rw-r--r--spring-web/src/main/java/org/springframework/web/context/request/ServletRequestAttributes.java52
-rw-r--r--spring-web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java123
-rw-r--r--spring-web/src/main/java/org/springframework/web/context/request/WebRequest.java40
-rw-r--r--spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResultProcessingInterceptor.java2
-rw-r--r--spring-web/src/main/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequest.java2
-rw-r--r--spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncManager.java3
-rw-r--r--spring-web/src/main/java/org/springframework/web/context/support/ServletContextResource.java4
-rw-r--r--spring-web/src/main/java/org/springframework/web/cors/CorsConfiguration.java3
-rw-r--r--spring-web/src/main/java/org/springframework/web/filter/AbstractRequestLoggingFilter.java25
-rw-r--r--spring-web/src/main/java/org/springframework/web/filter/CharacterEncodingFilter.java92
-rw-r--r--spring-web/src/main/java/org/springframework/web/filter/ForwardedHeaderFilter.java225
-rw-r--r--spring-web/src/main/java/org/springframework/web/filter/ShallowEtagHeaderFilter.java44
-rw-r--r--spring-web/src/main/java/org/springframework/web/method/ControllerAdviceBean.java7
-rw-r--r--spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java47
-rw-r--r--spring-web/src/main/java/org/springframework/web/method/HandlerMethodSelector.java2
-rw-r--r--spring-web/src/main/java/org/springframework/web/method/annotation/AbstractCookieValueMethodArgumentResolver.java4
-rw-r--r--spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java78
-rw-r--r--spring-web/src/main/java/org/springframework/web/method/annotation/ExceptionHandlerMethodResolver.java20
-rw-r--r--spring-web/src/main/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessor.java67
-rw-r--r--spring-web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java12
-rw-r--r--spring-web/src/main/java/org/springframework/web/method/annotation/RequestHeaderMethodArgumentResolver.java4
-rw-r--r--spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolver.java142
-rw-r--r--spring-web/src/main/java/org/springframework/web/method/annotation/SessionAttributesHandler.java11
-rw-r--r--spring-web/src/main/java/org/springframework/web/method/support/HandlerMethodArgumentResolverComposite.java22
-rw-r--r--spring-web/src/main/java/org/springframework/web/method/support/ModelAndViewContainer.java47
-rw-r--r--spring-web/src/main/java/org/springframework/web/multipart/commons/CommonsFileUploadSupport.java2
-rw-r--r--spring-web/src/main/java/org/springframework/web/multipart/support/MultipartFilter.java6
-rw-r--r--spring-web/src/main/java/org/springframework/web/multipart/support/MultipartResolutionDelegate.java199
-rw-r--r--spring-web/src/main/java/org/springframework/web/multipart/support/RequestPartServletServerHttpRequest.java30
-rw-r--r--spring-web/src/main/java/org/springframework/web/util/AbstractUriTemplateHandler.java138
-rw-r--r--spring-web/src/main/java/org/springframework/web/util/ContentCachingResponseWrapper.java2
-rw-r--r--spring-web/src/main/java/org/springframework/web/util/DefaultUriTemplateHandler.java148
-rw-r--r--spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java21
-rw-r--r--spring-web/src/main/java/org/springframework/web/util/HtmlUtils.java9
-rw-r--r--spring-web/src/main/java/org/springframework/web/util/UriTemplate.java2
-rw-r--r--spring-web/src/main/java/org/springframework/web/util/UriTemplateHandler.java20
-rw-r--r--spring-web/src/main/java/org/springframework/web/util/UriUtils.java27
-rw-r--r--spring-web/src/main/java/org/springframework/web/util/UrlPathHelper.java10
-rw-r--r--spring-web/src/main/java/org/springframework/web/util/WebUtils.java14
-rw-r--r--spring-web/src/test/java/org/springframework/http/CacheControlTests.java15
-rw-r--r--spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java74
-rw-r--r--spring-web/src/test/java/org/springframework/http/HttpRangeTests.java47
-rw-r--r--spring-web/src/test/java/org/springframework/http/HttpStatusTests.java3
-rw-r--r--spring-web/src/test/java/org/springframework/http/MediaTypeTests.java4
-rw-r--r--spring-web/src/test/java/org/springframework/http/RequestEntityTests.java15
-rw-r--r--spring-web/src/test/java/org/springframework/http/ResponseEntityTests.java20
-rw-r--r--spring-web/src/test/java/org/springframework/http/client/BufferedSimpleHttpRequestFactoryTests.java15
-rw-r--r--spring-web/src/test/java/org/springframework/http/client/OkHttp3AsyncClientHttpRequestFactoryTests.java40
-rw-r--r--spring-web/src/test/java/org/springframework/http/client/OkHttp3ClientHttpRequestFactoryTests.java40
-rw-r--r--spring-web/src/test/java/org/springframework/http/client/OkHttpAsyncClientHttpRequestFactoryTests.java2
-rw-r--r--spring-web/src/test/java/org/springframework/http/client/SimpleClientHttpResponseTests.java128
-rw-r--r--spring-web/src/test/java/org/springframework/http/client/support/BasicAuthorizationInterceptorTests.java78
-rw-r--r--spring-web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java2
-rw-r--r--spring-web/src/test/java/org/springframework/http/converter/ResourceHttpMessageConverterTests.java67
-rw-r--r--spring-web/src/test/java/org/springframework/http/converter/ResourceRegionHttpMessageConverterTests.java142
-rw-r--r--spring-web/src/test/java/org/springframework/http/converter/StringHttpMessageConverterTests.java87
-rw-r--r--spring-web/src/test/java/org/springframework/http/converter/feed/AtomFeedHttpMessageConverterTests.java16
-rw-r--r--spring-web/src/test/java/org/springframework/http/converter/feed/RssChannelHttpMessageConverterTests.java17
-rw-r--r--spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilderTests.java57
-rw-r--r--spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperFactoryBeanTests.java4
-rw-r--r--spring-web/src/test/java/org/springframework/http/converter/json/SpringHandlerInstantiatorTests.java12
-rw-r--r--spring-web/src/test/java/org/springframework/http/server/ServletServerHttpResponseTests.java13
-rw-r--r--spring-web/src/test/java/org/springframework/mock/web/test/MockHttpServletRequest.java4
-rw-r--r--spring-web/src/test/java/org/springframework/mock/web/test/MockHttpServletResponse.java4
-rw-r--r--spring-web/src/test/java/org/springframework/mock/web/test/MockServletContext.java11
-rw-r--r--spring-web/src/test/java/org/springframework/remoting/jaxws/JaxWsSupportTests.java6
-rw-r--r--spring-web/src/test/java/org/springframework/web/accept/ContentNegotiationManagerFactoryBeanTests.java18
-rw-r--r--spring-web/src/test/java/org/springframework/web/accept/HeaderContentNegotiationStrategyTests.java32
-rw-r--r--spring-web/src/test/java/org/springframework/web/bind/support/WebRequestDataBinderTests.java34
-rw-r--r--spring-web/src/test/java/org/springframework/web/client/AsyncRestTemplateIntegrationTests.java188
-rw-r--r--spring-web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java70
-rw-r--r--spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java37
-rw-r--r--spring-web/src/test/java/org/springframework/web/context/request/ServletRequestAttributesTests.java16
-rw-r--r--spring-web/src/test/java/org/springframework/web/context/request/ServletWebRequestHttpMethodsTests.java47
-rw-r--r--spring-web/src/test/java/org/springframework/web/context/request/SessionScopeTests.java10
-rw-r--r--spring-web/src/test/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequestTests.java2
-rw-r--r--spring-web/src/test/java/org/springframework/web/context/request/async/WebAsyncManagerTests.java16
-rw-r--r--spring-web/src/test/java/org/springframework/web/context/support/Spr8510Tests.java15
-rw-r--r--spring-web/src/test/java/org/springframework/web/cors/CorsConfigurationTests.java6
-rw-r--r--spring-web/src/test/java/org/springframework/web/cors/DefaultCorsProcessorTests.java4
-rw-r--r--spring-web/src/test/java/org/springframework/web/filter/CharacterEncodingFilterTests.java24
-rw-r--r--spring-web/src/test/java/org/springframework/web/filter/ForwardedHeaderFilterTests.java241
-rw-r--r--spring-web/src/test/java/org/springframework/web/filter/ShallowEtagHeaderFilterTests.java43
-rw-r--r--spring-web/src/test/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessorTests.java198
-rw-r--r--spring-web/src/test/java/org/springframework/web/method/annotation/ModelFactoryTests.java180
-rw-r--r--spring-web/src/test/java/org/springframework/web/method/annotation/RequestHeaderMethodArgumentResolverTests.java121
-rw-r--r--spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolverTests.java105
-rw-r--r--spring-web/src/test/java/org/springframework/web/util/DefaultUriTemplateHandlerTests.java115
-rw-r--r--spring-web/src/test/java/org/springframework/web/util/Log4jWebConfigurerTests.java6
-rw-r--r--spring-web/src/test/java/org/springframework/web/util/UriTemplateTests.java74
-rw-r--r--spring-web/src/test/java/org/springframework/web/util/UriUtilsTests.java21
-rw-r--r--spring-web/src/test/resources/org/springframework/http/converter/byterangeresource.txt1
-rw-r--r--spring-web/src/test/resources/org/springframework/web/util/HtmlCharacterEntityReferences.dtd10
-rw-r--r--spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/PortletContextResource.java2
-rw-r--r--spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/PortletRequestAttributes.java73
-rw-r--r--spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/PortletRequestHandledEvent.java2
-rw-r--r--spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/handler/SimplePortletPostProcessor.java38
-rw-r--r--spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/mvc/PortletWrappingController.java11
-rw-r--r--spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/AnnotationMethodHandlerAdapter.java14
-rw-r--r--spring-webmvc-portlet/src/test/java/org/springframework/mock/web/portlet/ServletWrappingPortletContext.java7
-rw-r--r--spring-webmvc-portlet/src/test/java/org/springframework/web/portlet/context/PortletRequestAttributesTests.java31
-rw-r--r--spring-webmvc-portlet/src/test/java/org/springframework/web/portlet/context/XmlPortletApplicationContextTests.java2
-rw-r--r--spring-webmvc-portlet/src/test/java/org/springframework/web/portlet/mvc/PortletWrappingControllerTests.java6
-rw-r--r--spring-webmvc-portlet/src/test/java/org/springframework/web/portlet/util/PortletUtilsTests.java5
-rw-r--r--spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/AbstractSpringPreparerFactory.java5
-rw-r--r--spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/SimpleSpringPreparerFactory.java5
-rw-r--r--spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/SpringBeanPreparerFactory.java5
-rw-r--r--spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/SpringLocaleResolver.java3
-rw-r--r--spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/SpringWildcardServletTilesApplicationContext.java3
-rw-r--r--spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/TilesConfigurer.java3
-rw-r--r--spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/TilesView.java5
-rw-r--r--spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/TilesViewResolver.java3
-rw-r--r--spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/package-info.java5
-rw-r--r--spring-webmvc-tiles2/src/test/resources/jasperreports.properties1
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java23
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java11
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerInterceptor.java8
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/LocaleResolver.java10
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/ModelAndView.java41
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java9
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceUtils.java19
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java24
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/config/ViewResolversBeanDefinitionParser.java22
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceChainRegistration.java11
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistry.java10
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ViewResolverRegistry.java30
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java107
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurer.java12
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurerAdapter.java8
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurerComposite.java11
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerExceptionResolver.java13
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java28
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java13
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java27
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/handler/BeanNameUrlHandlerMapping.java8
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerInterceptorAdapter.java7
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMappingIntrospector.java187
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/handler/MatchableHandlerMapping.java42
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/handler/RequestMatchResult.java67
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/handler/SimpleMappingExceptionResolver.java5
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/handler/SimpleServletPostProcessor.java10
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/AcceptHeaderLocaleResolver.java81
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/CookieLocaleResolver.java60
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/LocaleChangeInterceptor.java43
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/AbstractController.java29
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ParameterizableViewController.java6
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ServletForwardingController.java7
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ServletWrappingController.java25
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/WebContentInterceptor.java3
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java25
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationMappingUtils.java8
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/AbstractMediaTypeExpression.java20
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/AbstractRequestCondition.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/ConsumesRequestCondition.java38
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/HeadersRequestCondition.java9
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/ProducesRequestCondition.java40
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/RequestCondition.java35
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/RequestMethodsRequestCondition.java78
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfo.java89
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java330
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java53
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/CompletionStageReturnValueHandler.java5
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/DeferredResultAdapter.java36
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/DeferredResultMethodReturnValueHandler.java125
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java15
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessor.java48
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/JsonViewResponseBodyAdvice.java4
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ListenableFutureReturnValueHandler.java5
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MatrixVariableMapMethodArgumentResolver.java36
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MatrixVariableMethodArgumentResolver.java6
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandler.java3
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java23
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/PathVariableMethodArgumentResolver.java4
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestAttributeMethodArgumentResolver.java59
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java91
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java45
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartMethodArgumentResolver.java159
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessor.java20
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterAdapter.java39
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java81
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandler.java12
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletCookieValueMethodArgumentResolver.java4
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java23
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/SessionAttributeMethodArgumentResolver.java59
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/SseEmitter.java6
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/StreamingResponseBodyReturnValueHandler.java12
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/AbstractUrlMethodNameResolver.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/InternalPathMethodNameResolver.java6
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/MethodNameResolver.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/MultiActionController.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/NoSuchRequestHandlingMethodException.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/ParameterMethodNameResolver.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/PropertiesMethodNameResolver.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/AbstractControllerUrlHandlerMapping.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/AnnotationControllerTypePredicate.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/ControllerBeanNameHandlerMapping.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/ControllerClassNameHandlerMapping.java7
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/ControllerTypePredicate.java6
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java12
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/resource/CssLinkResourceTransformer.java21
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java456
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceTransformer.java6
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceUrlEncodingFilter.java8
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceUrlProvider.java26
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/resource/WebJarsResourceResolver.java24
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractAnnotationConfigDispatcherServletInitializer.java5
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContext.java17
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContextUtils.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/support/ServletUriComponentsBuilder.java18
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/support/WebContentGenerator.java109
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/CheckboxTag.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/FormTag.java6
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/DefaultRequestToViewNameTranslator.java26
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/InternalResourceViewResolver.java13
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/RedirectView.java62
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/UrlBasedViewResolver.java28
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerView.java10
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerViewResolver.java18
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupView.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupViewResolver.java18
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/jasperreports/JasperReportsMultiFormatView.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/AbstractJackson2View.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJackson2JsonView.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateView.java62
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateViewResolver.java18
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/AbstractSpringPreparerFactory.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/SimpleSpringPreparerFactory.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/SpringBeanPreparerFactory.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/package-info.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityConfig.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityConfigurer.java5
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityLayoutView.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityLayoutViewResolver.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityToolboxView.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityView.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityViewResolver.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/xml/MappingJackson2XmlView.java4
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/xslt/XsltView.java11
-rw-r--r--spring-webmvc/src/main/resources/META-INF/spring-form.tld5
-rw-r--r--spring-webmvc/src/main/resources/META-INF/spring.schemas3
-rw-r--r--spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-4.3.xsd1374
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/DispatcherServletTests.java3
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParserTests.java63
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java27
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/InterceptorRegistryTests.java2
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistryTests.java7
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ViewResolutionIntegrationTests.java4
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ViewResolverRegistryTests.java4
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java76
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java141
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/handler/HandlerMappingIntrospectorTests.java188
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/handler/MappedInterceptorTests.java9
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/handler/PathMatchingUrlHandlerMappingTests.java2
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/AcceptHeaderLocaleResolverTests.java78
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/CookieLocaleResolverTests.java54
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/ParameterizableViewControllerTests.java13
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolverTests.java8
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java17
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/UriTemplateServletAnnotationControllerTests.java21
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/condition/ProducesRequestConditionTests.java14
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/condition/RequestMethodsRequestConditionTests.java118
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMappingTests.java313
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoTests.java253
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/AbstractRequestAttributesArgumentResolverTests.java182
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/CrossOriginTests.java90
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/DeferredResultReturnValueHandlerTests.java197
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolverTests.java75
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorMockTests.java228
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorTests.java53
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MatrixVariablesMapMethodArgumentResolverTests.java6
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilderTests.java35
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestAttributeMethodArgumentResolverTests.java45
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterIntegrationTests.java69
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMappingTests.java88
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartMethodArgumentResolverTests.java192
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorMockTests.java93
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java210
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandlerTests.java63
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java178
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethodTests.java9
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/SessionAttributeMethodArgumentResolverTests.java45
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/StreamingResponseBodyReturnValueHandlerTests.java40
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolverTests.java1
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java207
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceUrlProviderTests.java65
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/resource/VersionResourceResolverTests.java8
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/resource/WebJarsResourceResolverTests.java26
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/support/FlashMapManagerTests.java2
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/support/WebContentGeneratorTests.java132
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/OptionTagTests.java6
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/PasswordInputTagTests.java3
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/view/RedirectViewTests.java203
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/view/freemarker/FreeMarkerViewTests.java4
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/view/jasperreports/JasperReportsMultiFormatViewTests.java10
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/view/json/MappingJackson2JsonViewTests.java33
-rw-r--r--spring-webmvc/src/test/java/org/springframework/web/servlet/view/script/ScriptTemplateViewTests.java58
-rw-r--r--spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-resources.xml1
-rw-r--r--spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-controllers-minimal.xml7
-rw-r--r--spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-controllers.xml7
-rw-r--r--spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-resolution-content-negotiation.xml37
-rw-r--r--spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-resolution-custom-order.xml8
-rw-r--r--spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-resolution.xml17
-rw-r--r--spring-websocket/src/main/java/org/springframework/web/socket/WebSocketHttpHeaders.java2
-rw-r--r--spring-websocket/src/main/java/org/springframework/web/socket/adapter/jetty/JettyWebSocketSession.java21
-rw-r--r--spring-websocket/src/main/java/org/springframework/web/socket/client/standard/StandardWebSocketClient.java4
-rw-r--r--spring-websocket/src/main/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParser.java32
-rw-r--r--spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/EnableWebSocketMessageBroker.java4
-rw-r--r--spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebMvcStompEndpointRegistry.java19
-rw-r--r--spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebMvcStompWebSocketEndpointRegistration.java2
-rw-r--r--spring-websocket/src/main/java/org/springframework/web/socket/handler/ConcurrentWebSocketSessionDecorator.java70
-rw-r--r--spring-websocket/src/main/java/org/springframework/web/socket/messaging/DefaultSimpUserRegistry.java134
-rw-r--r--spring-websocket/src/main/java/org/springframework/web/socket/messaging/SessionConnectEvent.java2
-rw-r--r--spring-websocket/src/main/java/org/springframework/web/socket/messaging/WebSocketAnnotationMethodMessageHandler.java26
-rw-r--r--spring-websocket/src/main/java/org/springframework/web/socket/server/standard/AbstractStandardUpgradeStrategy.java18
-rw-r--r--spring-websocket/src/main/java/org/springframework/web/socket/server/standard/AbstractTyrusRequestUpgradeStrategy.java8
-rw-r--r--spring-websocket/src/main/java/org/springframework/web/socket/server/standard/ServletServerContainerFactoryBean.java13
-rw-r--r--spring-websocket/src/main/java/org/springframework/web/socket/server/standard/UndertowRequestUpgradeStrategy.java369
-rw-r--r--spring-websocket/src/main/java/org/springframework/web/socket/server/standard/WebLogicRequestUpgradeStrategy.java3
-rw-r--r--spring-websocket/src/main/java/org/springframework/web/socket/server/support/AbstractHandshakeHandler.java18
-rw-r--r--spring-websocket/src/main/java/org/springframework/web/socket/sockjs/client/AbstractXhrTransport.java9
-rw-r--r--spring-websocket/src/main/java/org/springframework/web/socket/sockjs/client/UndertowXhrTransport.java8
-rw-r--r--spring-websocket/src/main/java/org/springframework/web/socket/sockjs/frame/Jackson2SockJsMessageCodec.java4
-rw-r--r--spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/AbstractHttpSockJsSession.java16
-rw-r--r--spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/PollingSockJsSession.java1
-rw-r--r--spring-websocket/src/main/resources/META-INF/spring.schemas3
-rw-r--r--spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket-4.0.xsd2
-rw-r--r--spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket-4.1.xsd2
-rw-r--r--spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket-4.2.xsd2
-rw-r--r--spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket-4.3.xsd931
-rw-r--r--spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket.gifbin0 -> 1025 bytes
-rw-r--r--spring-websocket/src/test/java/org/springframework/web/socket/AbstractWebSocketIntegrationTests.java13
-rw-r--r--spring-websocket/src/test/java/org/springframework/web/socket/ContextLoaderTestUtils.java3
-rw-r--r--spring-websocket/src/test/java/org/springframework/web/socket/WebSocketHandshakeTests.java (renamed from spring-websocket/src/test/java/org/springframework/web/socket/WebSocketIntegrationTests.java)13
-rw-r--r--spring-websocket/src/test/java/org/springframework/web/socket/adapter/jetty/JettyWebSocketSessionTests.java20
-rw-r--r--spring-websocket/src/test/java/org/springframework/web/socket/adapter/standard/StandardWebSocketSessionTests.java22
-rw-r--r--spring-websocket/src/test/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParserTests.java22
-rw-r--r--spring-websocket/src/test/java/org/springframework/web/socket/config/annotation/WebSocketConfigurationTests.java4
-rw-r--r--spring-websocket/src/test/java/org/springframework/web/socket/handler/ConcurrentWebSocketSessionDecoratorTests.java82
-rw-r--r--spring-websocket/src/test/java/org/springframework/web/socket/sockjs/client/RestTemplateXhrTransportTests.java2
-rw-r--r--spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/handler/HttpSendingTransportHandlerTests.java4
-rw-r--r--spring-websocket/src/test/resources/org/springframework/web/socket/config/websocket-config-broker-customchannels.xml4
-rw-r--r--src/asciidoc/appendix.adoc4
-rw-r--r--src/asciidoc/appx-spring-form-tld.adoc5
-rw-r--r--src/asciidoc/core-aop.adoc62
-rw-r--r--src/asciidoc/core-beans.adoc247
-rw-r--r--src/asciidoc/core-expressions.adoc25
-rw-r--r--src/asciidoc/core-resources.adoc2
-rw-r--r--src/asciidoc/core-validation.adoc4
-rw-r--r--src/asciidoc/data-access.adoc31
-rw-r--r--src/asciidoc/index-docinfo.xml2
-rw-r--r--src/asciidoc/integration.adoc88
-rw-r--r--src/asciidoc/overview.adoc4
-rw-r--r--src/asciidoc/testing.adoc930
-rw-r--r--src/asciidoc/web-cors.adoc42
-rw-r--r--src/asciidoc/web-mvc.adoc494
-rw-r--r--src/asciidoc/web-portlet.adoc10
-rw-r--r--src/asciidoc/web-view.adoc27
-rw-r--r--src/asciidoc/web-websocket.adoc76
-rw-r--r--src/asciidoc/web.adoc2
-rw-r--r--src/asciidoc/whats-new.adoc128
-rw-r--r--src/eclipse/org.eclipse.jdt.ui.prefs2
-rw-r--r--src/test/java/org/springframework/context/annotation/scope/ClassPathBeanDefinitionScannerScopeIntegrationTests.java89
-rw-r--r--src/test/java/org/springframework/scheduling/annotation/ScheduledAndTransactionalAnnotationIntegrationTests.java2
-rw-r--r--src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementIntegrationTests.java3
1399 files changed, 47351 insertions, 13633 deletions
diff --git a/CODE_OF_CONDUCT.adoc b/CODE_OF_CONDUCT.adoc
new file mode 100644
index 00000000..f013d6f3
--- /dev/null
+++ b/CODE_OF_CONDUCT.adoc
@@ -0,0 +1,44 @@
+= Contributor Code of Conduct
+
+As contributors and maintainers of this project, and in the interest of fostering an open
+and welcoming community, we pledge to respect all people who contribute through reporting
+issues, posting feature requests, updating documentation, submitting pull requests or
+patches, and other activities.
+
+We are committed to making participation in this project a harassment-free experience for
+everyone, regardless of level of experience, gender, gender identity and expression,
+sexual orientation, disability, personal appearance, body size, race, ethnicity, age,
+religion, or nationality.
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery
+* Personal attacks
+* Trolling or insulting/derogatory comments
+* Public or private harassment
+* Publishing other's private information, such as physical or electronic addresses,
+ without explicit permission
+* Other unethical or unprofessional conduct
+
+Project maintainers have the right and responsibility to remove, edit, or reject comments,
+commits, code, wiki edits, issues, and other contributions that are not aligned to this
+Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors
+that they deem inappropriate, threatening, offensive, or harmful.
+
+By adopting this Code of Conduct, project maintainers commit themselves to fairly and
+consistently applying these principles to every aspect of managing this project. Project
+maintainers who do not follow or enforce the Code of Conduct may be permanently removed
+from the project team.
+
+This Code of Conduct applies both within project spaces and in public spaces when an
+individual is representing the project or its community.
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by
+contacting a project maintainer at spring-code-of-conduct@pivotal.io . All complaints will
+be reviewed and investigated and will result in a response that is deemed necessary and
+appropriate to the circumstances. Maintainers are obligated to maintain confidentiality
+with regard to the reporter of an incident.
+
+This Code of Conduct is adapted from the
+http://contributor-covenant.org[Contributor Covenant], version 1.3.0, available at
+http://contributor-covenant.org/version/1/3/0/[contributor-covenant.org/version/1/3/0/]
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f87099b8..cdde5c72 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -6,6 +6,10 @@ to expect from the Spring team when evaluating your submission._
_Please refer back to this document as a checklist before issuing any pull
request; this will save time for everyone!_
+## Code of Conduct
+This project adheres to the Contributor Covenant [code of conduct](CODE_OF_CONDUCT.adoc).
+By participating, you are expected to uphold this code. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
+
## Take Your First Steps
### Understand the basics
diff --git a/README.md b/README.md
index ffd8432a..852ecf38 100644
--- a/README.md
+++ b/README.md
@@ -10,6 +10,10 @@ The framework also serves as the foundation for [Spring Integration][], [Spring
and the rest of the Spring [family of projects][]. Browse the repositories under
the [Spring organization][] on GitHub for a full list.
+## Code of Conduct
+This project adheres to the Contributor Covenant [code of conduct](CODE_OF_CONDUCT.adoc).
+By participating, you are expected to uphold this code. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
+
## Downloading Artifacts
See [downloading Spring artifacts][] for Maven repository information. Unable to
use Maven or other transitive dependency management tools?
diff --git a/build.gradle b/build.gradle
index 84067b7a..e9f47cf2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -10,6 +10,10 @@ buildscript {
}
}
+plugins {
+ id "org.sonarqube" version "1.1"
+}
+
ext {
linkHomepage = 'https://projects.spring.io/spring-framework'
linkCi = 'https://build.spring.io/browse/SPR'
@@ -28,47 +32,49 @@ configure(allprojects) { project ->
version = qualifyVersionIfNecessary(version)
ext.aspectjVersion = "1.8.9"
+ ext.caffeineVersion = "2.3.1"
ext.eclipselinkVersion = "2.4.2"
ext.ehcacheVersion = "2.10.2"
ext.ehcachejcacheVersion = "1.0.1"
- ext.ehcache3Version = "3.0.2"
+ ext.ehcache3Version = "3.1.1"
ext.ejbVersion = "3.0"
ext.fileuploadVersion = "1.3.2"
ext.freemarkerVersion = "2.3.23"
ext.groovyVersion = "2.4.7"
- ext.gsonVersion = "2.6.2"
+ ext.gsonVersion = "2.7"
ext.guavaVersion = "19.0"
ext.hamcrestVersion = "1.3"
ext.hibernate3Version = "3.6.10.Final"
ext.hibernate4Version = "4.3.11.Final"
- ext.hibernate5Version = "5.0.9.Final"
+ ext.hibernate5Version = "5.2.1.Final"
ext.hibval4Version = "4.3.2.Final"
ext.hibval5Version = "5.2.4.Final"
ext.hsqldbVersion = "2.3.4"
- ext.httpasyncVersion = "4.1.1"
+ ext.httpasyncVersion = "4.1.2"
ext.httpclientVersion = "4.5.2"
- ext.jackson2Version = "2.6.7"
- ext.jasperreportsVersion = "6.2.1"
+ ext.jackson2Version = "2.8.1"
+ ext.jasperreportsVersion = "6.2.1" // our tests fail with JR-internal NPEs against 6.2.2 and higher
ext.javamailVersion = "1.5.5"
- ext.jettyVersion = "9.3.9.v20160517"
+ ext.jettyVersion = "9.3.11.v20160721"
ext.jodaVersion = "2.9.4"
ext.jrubyVersion = "1.7.25" // JRuby 9000 only supported through JSR-223 (StandardScriptFactory)
ext.jtaVersion = "1.2"
ext.junitVersion = "4.12"
ext.log4jVersion = "1.2.17"
- ext.nettyVersion = "4.0.37.Final"
+ ext.nettyVersion = "4.1.4.Final"
ext.okhttpVersion = "2.7.5"
+ ext.okhttp3Version = "3.4.1"
ext.openjpaVersion = "2.4.1"
- ext.poiVersion = "3.13"
+ ext.poiVersion = "3.14"
ext.reactorVersion = "2.0.8.RELEASE"
- ext.romeVersion = "1.5.1"
+ ext.romeVersion = "1.6.0"
ext.slf4jVersion = "1.7.21"
ext.snakeyamlVersion = "1.17"
- ext.snifferVersion = "1.14"
+ ext.snifferVersion = "1.15"
ext.testngVersion = "6.9.10"
ext.tiles2Version = "2.2.2"
ext.tiles3Version = "3.0.5"
- ext.tomcatVersion = "8.0.36"
+ ext.tomcatVersion = "8.5.4"
ext.tyrusVersion = "1.3.5" // constrained by WebLogic 12.1.3 support
ext.undertowVersion = "1.3.23.Final"
ext.xmlunitVersion = "1.6"
@@ -190,13 +196,13 @@ configure(allprojects) { project ->
"http://portals.apache.org/pluto/portlet-2.0-apidocs/",
"http://tiles.apache.org/tiles-request/apidocs/",
"http://tiles.apache.org/framework/apidocs/",
- "http://aopalliance.sourceforge.net/doc/",
"http://www.eclipse.org/aspectj/doc/released/aspectj5rt-api/",
- "http://ehcache.org/apidocs/",
- "http://quartz-scheduler.org/api/2.2.0/",
- "http://fasterxml.github.com/jackson-core/javadoc/2.3.0/",
- "http://fasterxml.github.com/jackson-databind/javadoc/2.3.0/",
- "http://fasterxml.github.io/jackson-dataformat-xml/javadoc/2.3.0/",
+ "http://ehcache.org/apidocs/${ehcacheVersion}",
+ "http://ehcache.org/apidocs/${ehcache3Version}",
+ "http://quartz-scheduler.org/api/2.2.1/",
+ "http://fasterxml.github.io/jackson-core/javadoc/2.7/",
+ "http://fasterxml.github.io/jackson-databind/javadoc/2.7/",
+ "http://fasterxml.github.io/jackson-dataformat-xml/javadoc/2.7/",
"http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/"
] as String[]
}
@@ -210,11 +216,11 @@ configure(subprojects - project(":spring-build-src")) { subproject ->
}
dependencies {
- jacoco("org.jacoco:org.jacoco.agent:0.7.1.201405082137:runtime")
+ jacoco("org.jacoco:org.jacoco.agent:0.7.5.201505241946:runtime")
}
gradle.taskGraph.whenReady {taskGraph ->
- if (taskGraph.hasTask(':sonarRunner')) {
+ if (taskGraph.hasTask(':sonarqube')) {
test.jvmArgs "-javaagent:${configurations.jacoco.asPath}=destfile=${buildDir}/jacoco.exec,includes=org.springframework.*"
}
}
@@ -280,12 +286,12 @@ project("spring-build-src") {
project("spring-core") {
description = "Spring Core"
- // As of Spring 4.0.3, spring-core includes asm 5.0 and repackages cglib 3.2, inlining
- // both into the spring-core jar. cglib 3.2 itself depends on asm 5.0 and is therefore
+ // 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.
def cglibVersion = "3.2.4"
- def objenesisVersion = "2.2"
+ def objenesisVersion = "2.4"
configurations {
jarjar
@@ -345,7 +351,7 @@ project("spring-core") {
compile("commons-logging:commons-logging:1.2")
optional("commons-codec:commons-codec:1.10")
optional("org.aspectj:aspectjweaver:${aspectjVersion}")
- optional("net.sf.jopt-simple:jopt-simple:4.9")
+ optional("net.sf.jopt-simple:jopt-simple:5.0.2")
optional("log4j:log4j:${log4jVersion}")
testCompile("org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}")
testCompile("xmlunit:xmlunit:${xmlunitVersion}")
@@ -416,7 +422,6 @@ project("spring-aop") {
compile(project(":spring-core"))
compile(files(project(":spring-core").cglibRepackJar))
compile(files(project(":spring-core").objenesisRepackJar))
- compile("aopalliance:aopalliance:1.0")
optional("org.aspectj:aspectjweaver:${aspectjVersion}")
optional("commons-pool:commons-pool:1.6")
optional("org.apache.commons:commons-pool2:2.4.2")
@@ -482,7 +487,7 @@ project("spring-context") {
optional("org.beanshell:bsh:2.0b4")
optional("org.jruby:jruby:${jrubyVersion}")
testCompile("javax.inject:javax.inject-tck:1")
- testCompile("org.javamoney:moneta:1.0")
+ testCompile("org.javamoney:moneta:1.1")
testCompile("commons-dbcp:commons-dbcp:1.4")
testCompile("org.apache.commons:commons-pool2:2.4.2")
testCompile("org.slf4j:slf4j-api:${slf4jVersion}")
@@ -521,7 +526,6 @@ project("spring-messaging") {
}
testCompile("org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}")
testCompile("org.apache.tomcat.embed:tomcat-embed-websocket:${tomcatVersion}")
- testCompile("org.apache.tomcat.embed:tomcat-embed-logging-juli:${tomcatVersion}")
testCompile("io.netty:netty-all:${nettyVersion}")
testCompile("commons-dbcp:commons-dbcp:1.4")
testCompile("log4j:log4j:${log4jVersion}")
@@ -538,13 +542,13 @@ project("spring-tx") {
compile(project(":spring-core"))
optional(project(":spring-aop"))
optional(project(":spring-context")) // for JCA, @EnableTransactionManagement
- optional("aopalliance:aopalliance:1.0")
optional("javax.transaction:javax.transaction-api:${jtaVersion}")
optional("javax.resource:connector-api:1.5")
optional("javax.ejb:ejb-api:${ejbVersion}")
optional("com.ibm.websphere:uow:6.0.2.17")
testCompile("org.aspectj:aspectjweaver:${aspectjVersion}")
testCompile("org.eclipse.persistence:javax.persistence:2.0.0")
+ testCompile("org.codehaus.groovy:groovy-all:${groovyVersion}")
}
}
@@ -552,13 +556,6 @@ project("spring-oxm") {
description = "Spring Object/XML Marshalling"
apply from: "oxm.gradle"
- compileTestJava {
- // necessary to avoid java.lang.VerifyError on jibx compilation
- // see http://jira.codehaus.org/browse/JIBX-465
- sourceCompatibility = 1.6
- targetCompatibility = 1.6
- }
-
dependencies {
compile(project(":spring-beans"))
compile(project(":spring-core"))
@@ -580,11 +577,9 @@ project("spring-oxm") {
testCompile("org.codehaus.jettison:jettison:1.3.7") {
exclude group: 'stax', module: 'stax-api'
}
- if (compileTestJava.enabled) {
- testCompile(files(genCastor.classesDir).builtBy(genCastor))
- testCompile(files(genJaxb.classesDir).builtBy(genJaxb))
- testCompile(files(genXmlbeans.classesDir).builtBy(genXmlbeans))
- }
+ testCompile(files(genCastor.classesDir).builtBy(genCastor))
+ testCompile(files(genJaxb.classesDir).builtBy(genJaxb))
+ testCompile(files(genXmlbeans.classesDir).builtBy(genXmlbeans))
}
}
@@ -600,7 +595,6 @@ project("spring-jms") {
compile(project(":spring-tx"))
provided("javax.jms:jms-api:1.1-rev-1")
optional(project(":spring-oxm"))
- optional("aopalliance:aopalliance:1.0")
optional("javax.transaction:javax.transaction-api:${jtaVersion}")
optional("javax.resource:connector-api:1.5")
optional("com.fasterxml.jackson.core:jackson-databind:${jackson2Version}")
@@ -636,6 +630,7 @@ project("spring-context-support") {
optional("javax.mail:javax.mail-api:${javamailVersion}")
optional("javax.cache:cache-api:1.0.0")
optional("com.google.guava:guava:${guavaVersion}")
+ optional("com.github.ben-manes.caffeine:caffeine:${caffeineVersion}")
optional("net.sf.ehcache:ehcache:${ehcacheVersion}")
optional("org.quartz-scheduler:quartz:2.2.3")
optional("org.codehaus.fabric3.api:commonj:1.1.0")
@@ -692,7 +687,6 @@ project("spring-web") {
optional("javax.el:javax.el-api:2.2.5")
optional("javax.faces:javax.faces-api:2.2")
optional("javax.validation:validation-api:1.0.0.GA")
- optional("aopalliance:aopalliance:1.0")
optional("org.codehaus.groovy:groovy-all:${groovyVersion}")
optional("com.caucho:hessian:4.0.38")
optional("commons-fileupload:commons-fileupload:${fileuploadVersion}")
@@ -700,6 +694,7 @@ project("spring-web") {
optional("org.apache.httpcomponents:httpasyncclient:${httpasyncVersion}")
optional("io.netty:netty-all:${nettyVersion}")
optional("com.squareup.okhttp:okhttp:${okhttpVersion}")
+ optional("com.squareup.okhttp3:okhttp:${okhttp3Version}")
optional("com.fasterxml.jackson.core:jackson-databind:${jackson2Version}")
optional("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:${jackson2Version}")
optional("com.google.code.gson:gson:${gsonVersion}")
@@ -712,7 +707,7 @@ project("spring-web") {
}
optional("log4j:log4j:${log4jVersion}")
optional("com.google.protobuf:protobuf-java:2.6.1")
- optional("com.googlecode.protobuf-java-format:protobuf-java-format:1.2")
+ optional("com.googlecode.protobuf-java-format:protobuf-java-format:1.4")
optional("javax.mail:javax.mail-api:${javamailVersion}")
testCompile(project(":spring-context-support")) // for JafMediaTypeFactory
testCompile("xmlunit:xmlunit:${xmlunitVersion}")
@@ -721,8 +716,8 @@ project("spring-web") {
exclude group: "org.apache.taglibs", module: "taglibs-standard-spec"
}
testCompile("com.fasterxml.jackson.datatype:jackson-datatype-joda:${jackson2Version}")
- testCompile("com.fasterxml.jackson.datatype:jackson-datatype-jdk7:${jackson2Version}")
testCompile("com.fasterxml.jackson.datatype:jackson-datatype-jdk8:${jackson2Version}")
+ testCompile("com.fasterxml.jackson.module:jackson-module-kotlin:${jackson2Version}")
testRuntime("com.sun.mail:javax.mail:${javamailVersion}")
}
}
@@ -738,7 +733,6 @@ project("spring-orm") {
optional(project(":spring-aop"))
optional(project(":spring-context"))
optional(project(":spring-web"))
- optional("aopalliance:aopalliance:1.0")
optional("org.eclipse.persistence:javax.persistence:2.0.5")
optional("org.eclipse.persistence:org.eclipse.persistence.core:${eclipselinkVersion}")
optional("org.eclipse.persistence:org.eclipse.persistence.jpa:${eclipselinkVersion}") {
@@ -779,7 +773,6 @@ project("spring-orm-hibernate4") {
optional("org.hibernate:hibernate-core:${hibernate4Version}")
optional("org.hibernate:hibernate-entitymanager:${hibernate4Version}")
optional("javax.servlet:javax.servlet-api:3.0.1")
- optional("aopalliance:aopalliance:1.0")
testCompile("javax.validation:validation-api:1.1.0.GA")
testCompile("org.hibernate:hibernate-validator:${hibval5Version}")
testCompile("javax.el:javax.el-api:2.2.5")
@@ -796,10 +789,8 @@ project("spring-orm-hibernate5") {
provided(project(":spring-tx"))
optional(project(":spring-web"))
optional("org.hibernate:hibernate-core:${hibernate5Version}")
- optional("org.hibernate:hibernate-entitymanager:${hibernate5Version}")
optional("javax.servlet:javax.servlet-api:3.0.1")
optional("javax.transaction:javax.transaction-api:${jtaVersion}")
- optional("aopalliance:aopalliance:1.0")
}
}
@@ -807,13 +798,14 @@ project("spring-webmvc") {
description = "Spring Web MVC"
dependencies {
+ compile(project(":spring-aop"))
compile(project(":spring-beans"))
compile(project(":spring-context"))
compile(project(":spring-core"))
compile(files(project(":spring-core").objenesisRepackJar))
compile(project(":spring-expression"))
compile(project(":spring-web"))
- provided("javax.servlet:javax.servlet-api:3.0.1")
+ provided("javax.servlet:javax.servlet-api:3.1.0")
optional(project(":spring-context-support")) // for Velocity support
optional(project(":spring-oxm")) // for MarshallingView
optional("javax.servlet.jsp:javax.servlet.jsp-api:2.2.1")
@@ -856,7 +848,6 @@ project("spring-webmvc") {
exclude group: "org.springframework", module: "spring-web"
}
optional('org.webjars:webjars-locator:0.32')
- testCompile(project(":spring-aop"))
testCompile("xmlunit:xmlunit:${xmlunitVersion}")
testCompile("dom4j:dom4j:1.6.1") {
exclude group: "xml-apis", module: "xml-apis"
@@ -879,10 +870,10 @@ project("spring-webmvc") {
testCompile("commons-io:commons-io:1.3")
testCompile("joda-time:joda-time:${jodaVersion}")
testCompile("org.slf4j:slf4j-jcl:${slf4jVersion}")
- testCompile("org.jruby:jruby:${jrubyVersion}")
- testCompile("org.python:jython-standalone:2.5.3")
testCompile("org.mozilla:rhino:1.7.7.1")
- testCompile("org.webjars:underscorejs:1.8.3")
+ testRuntime("org.jruby:jruby:${jrubyVersion}")
+ testRuntime("org.python:jython-standalone:2.5.3")
+ testRuntime("org.webjars:underscorejs:1.8.3")
}
}
@@ -971,7 +962,6 @@ project("spring-websocket") {
optional("com.fasterxml.jackson.core:jackson-databind:${jackson2Version}")
testCompile("org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}")
testCompile("org.apache.tomcat.embed:tomcat-embed-websocket:${tomcatVersion}")
- testCompile("org.apache.tomcat.embed:tomcat-embed-logging-juli:${tomcatVersion}")
testCompile("io.projectreactor:reactor-net:${reactorVersion}")
testCompile("io.netty:netty-all:${nettyVersion}")
testCompile("log4j:log4j:${log4jVersion}")
@@ -992,6 +982,7 @@ project("spring-test") {
optional(project(":spring-web"))
optional(project(":spring-webmvc"))
optional(project(":spring-webmvc-portlet"))
+ optional(project(":spring-websocket"))
optional("junit:junit:${junitVersion}")
optional("org.testng:testng:${testngVersion}")
optional("javax.inject:javax.inject:1")
@@ -1003,14 +994,16 @@ project("spring-test") {
}
optional("javax.portlet:portlet-api:2.0")
optional("javax.el:javax.el-api:2.2.5")
+ optional("javax.websocket:javax.websocket-api:1.0")
optional("org.aspectj:aspectjweaver:${aspectjVersion}")
optional("org.codehaus.groovy:groovy-all:${groovyVersion}")
optional("org.hamcrest:hamcrest-core:${hamcrestVersion}")
optional("xmlunit:xmlunit:${xmlunitVersion}")
- optional("net.sourceforge.htmlunit:htmlunit:2.19")
- optional("org.seleniumhq.selenium:selenium-htmlunit-driver:2.48.2")
- optional("org.skyscreamer:jsonassert:1.2.3")
- optional("com.jayway.jsonpath:json-path:2.1.0")
+ optional("net.sourceforge.htmlunit:htmlunit:2.22")
+ optional("org.seleniumhq.selenium:htmlunit-driver:2.21")
+ optional("org.seleniumhq.selenium:selenium-java:2.53.1")
+ optional("org.skyscreamer:jsonassert:1.3.0")
+ optional("com.jayway.jsonpath:json-path:2.2.0")
testCompile(project(":spring-context-support"))
testCompile(project(":spring-oxm"))
testCompile("javax.mail:javax.mail-api:${javamailVersion}")
@@ -1127,10 +1120,8 @@ project("spring-framework-bom") {
}
}
-apply plugin: 'sonar-runner'
-
-sonarRunner {
- sonarProperties {
+sonarqube {
+ properties {
property "sonar.projectName", "Spring Framework"
property "sonar.profile", "Spring Framework"
property "sonar.jacoco.reportPath", "${buildDir.name}/jacoco.exec"
@@ -1367,7 +1358,7 @@ configure(rootProject) {
task wrapper(type: Wrapper) {
description = "Generates gradlew[.bat] scripts"
- gradleVersion = "2.5"
+ gradleVersion = "2.13"
doLast() {
def gradleOpts = "-XX:MaxMetaspaceSize=1024m -Xmx1024m"
@@ -1384,14 +1375,14 @@ configure(rootProject) {
}
configure([project(':spring-build-src'), project(':spring-framework-bom')]) {
- sonarRunner {
+ sonarqube {
skipProject = true
}
}
configure(project(':spring-core')) {
- sonarRunner {
- sonarProperties {
+ sonarqube {
+ properties {
property "sonar.exclusions",
"src/main/java/org/springframework/cglib/**/*,src/main/java/org/springframework/asm/**/*"
}
diff --git a/gradle.properties b/gradle.properties
index 4233e4cb..296f99dc 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1 +1 @@
-version=4.2.7.RELEASE
+version=4.3.2.RELEASE
diff --git a/spring-aop/src/main/java/org/aopalliance/aop/Advice.java b/spring-aop/src/main/java/org/aopalliance/aop/Advice.java
new file mode 100644
index 00000000..6dcbc4af
--- /dev/null
+++ b/spring-aop/src/main/java/org/aopalliance/aop/Advice.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.aopalliance.aop;
+
+/**
+ * Tag interface for Advice. Implementations can be any type
+ * of advice, such as Interceptors.
+ *
+ * @author Rod Johnson
+ * @version $Id: Advice.java,v 1.1 2004/03/19 17:02:16 johnsonr Exp $
+ */
+public interface Advice {
+
+}
diff --git a/spring-aop/src/main/java/org/aopalliance/aop/AspectException.java b/spring-aop/src/main/java/org/aopalliance/aop/AspectException.java
new file mode 100644
index 00000000..c634d51a
--- /dev/null
+++ b/spring-aop/src/main/java/org/aopalliance/aop/AspectException.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.aopalliance.aop;
+
+/**
+ * Superclass for all AOP infrastructure exceptions.
+ * Unchecked, as such exceptions are fatal and end user
+ * code shouldn't be forced to catch them.
+ *
+ * @author Rod Johnson
+ * @author Bob Lee
+ * @author Juergen Hoeller
+ */
+@SuppressWarnings("serial")
+public class AspectException extends RuntimeException {
+
+ /**
+ * Constructor for AspectException.
+ * @param message the exception message
+ */
+ public AspectException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructor for AspectException.
+ * @param message the exception message
+ * @param cause the root cause, if any
+ */
+ public AspectException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/spring-aop/src/main/java/org/aopalliance/intercept/ConstructorInterceptor.java b/spring-aop/src/main/java/org/aopalliance/intercept/ConstructorInterceptor.java
new file mode 100644
index 00000000..7e0ac706
--- /dev/null
+++ b/spring-aop/src/main/java/org/aopalliance/intercept/ConstructorInterceptor.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.aopalliance.intercept;
+
+/**
+ * Intercepts the construction of a new object.
+ *
+ * <p>The user should implement the {@link
+ * #construct(ConstructorInvocation)} method to modify the original
+ * behavior. E.g. the following class implements a singleton
+ * interceptor (allows only one unique instance for the intercepted
+ * class):
+ *
+ * <pre class=code>
+ * class DebuggingInterceptor implements ConstructorInterceptor {
+ * Object instance=null;
+ *
+ * Object construct(ConstructorInvocation i) throws Throwable {
+ * if(instance==null) {
+ * return instance=i.proceed();
+ * } else {
+ * throw new Exception("singleton does not allow multiple instance");
+ * }
+ * }
+ * }
+ * </pre>
+ *
+ * @author Rod Johnson
+ */
+public interface ConstructorInterceptor extends Interceptor {
+
+ /**
+ * Implement this method to perform extra treatments before and
+ * after the construction of a new object. Polite implementations
+ * would certainly like to invoke {@link Joinpoint#proceed()}.
+ * @param invocation the construction joinpoint
+ * @return the newly created object, which is also the result of
+ * the call to {@link Joinpoint#proceed()}; might be replaced by
+ * the interceptor
+ * @throws Throwable if the interceptors or the target object
+ * throws an exception
+ */
+ Object construct(ConstructorInvocation invocation) throws Throwable;
+
+}
diff --git a/spring-aop/src/main/java/org/aopalliance/intercept/ConstructorInvocation.java b/spring-aop/src/main/java/org/aopalliance/intercept/ConstructorInvocation.java
new file mode 100644
index 00000000..a453ac32
--- /dev/null
+++ b/spring-aop/src/main/java/org/aopalliance/intercept/ConstructorInvocation.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.aopalliance.intercept;
+
+import java.lang.reflect.Constructor;
+
+/**
+ * Description of an invocation to a constuctor, given to an
+ * interceptor upon constructor-call.
+ *
+ * <p>A constructor invocation is a joinpoint and can be intercepted
+ * by a constructor interceptor.
+ *
+ * @author Rod Johnson
+ * @see ConstructorInterceptor
+ */
+public interface ConstructorInvocation extends Invocation {
+
+ /**
+ * Get the constructor being called.
+ * <p>This method is a friendly implementation of the
+ * {@link Joinpoint#getStaticPart()} method (same result).
+ * @return the constructor being called
+ */
+ Constructor<?> getConstructor();
+
+}
diff --git a/spring-aop/src/main/java/org/aopalliance/intercept/Interceptor.java b/spring-aop/src/main/java/org/aopalliance/intercept/Interceptor.java
new file mode 100644
index 00000000..eef409a7
--- /dev/null
+++ b/spring-aop/src/main/java/org/aopalliance/intercept/Interceptor.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.aopalliance.intercept;
+
+import org.aopalliance.aop.Advice;
+
+/**
+ * This interface represents a generic interceptor.
+ *
+ * <p>A generic interceptor can intercept runtime events that occur
+ * within a base program. Those events are materialized by (reified
+ * in) joinpoints. Runtime joinpoints can be invocations, field
+ * access, exceptions...
+ *
+ * <p>This interface is not used directly. Use the sub-interfaces
+ * to intercept specific events. For instance, the following class
+ * implements some specific interceptors in order to implement a
+ * debugger:
+ *
+ * <pre class=code>
+ * class DebuggingInterceptor implements MethodInterceptor,
+ * ConstructorInterceptor, FieldInterceptor {
+ *
+ * Object invoke(MethodInvocation i) throws Throwable {
+ * debug(i.getMethod(), i.getThis(), i.getArgs());
+ * return i.proceed();
+ * }
+ *
+ * Object construct(ConstructorInvocation i) throws Throwable {
+ * debug(i.getConstructor(), i.getThis(), i.getArgs());
+ * return i.proceed();
+ * }
+ *
+ * Object get(FieldAccess fa) throws Throwable {
+ * debug(fa.getField(), fa.getThis(), null);
+ * return fa.proceed();
+ * }
+ *
+ * Object set(FieldAccess fa) throws Throwable {
+ * debug(fa.getField(), fa.getThis(), fa.getValueToSet());
+ * return fa.proceed();
+ * }
+ *
+ * void debug(AccessibleObject ao, Object this, Object value) {
+ * ...
+ * }
+ * }
+ * </pre>
+ *
+ * @author Rod Johnson
+ * @see Joinpoint
+ */
+public interface Interceptor extends Advice {
+
+}
diff --git a/spring-aop/src/main/java/org/aopalliance/intercept/Invocation.java b/spring-aop/src/main/java/org/aopalliance/intercept/Invocation.java
new file mode 100644
index 00000000..e785afb4
--- /dev/null
+++ b/spring-aop/src/main/java/org/aopalliance/intercept/Invocation.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.aopalliance.intercept;
+
+/**
+ * This interface represents an invocation in the program.
+ *
+ * <p>An invocation is a joinpoint and can be intercepted by an
+ * interceptor.
+ *
+ * @author Rod Johnson
+ */
+public interface Invocation extends Joinpoint {
+
+ /**
+ * Get the arguments as an array object.
+ * It is possible to change element values within this
+ * array to change the arguments.
+ * @return the argument of the invocation
+ */
+ Object[] getArguments();
+
+}
diff --git a/spring-aop/src/main/java/org/aopalliance/intercept/Joinpoint.java b/spring-aop/src/main/java/org/aopalliance/intercept/Joinpoint.java
new file mode 100644
index 00000000..9328f333
--- /dev/null
+++ b/spring-aop/src/main/java/org/aopalliance/intercept/Joinpoint.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.aopalliance.intercept;
+
+import java.lang.reflect.AccessibleObject;
+
+/**
+ * This interface represents a generic runtime joinpoint (in the AOP
+ * terminology).
+ *
+ * <p>A runtime joinpoint is an <i>event</i> that occurs on a static
+ * joinpoint (i.e. a location in a the program). For instance, an
+ * invocation is the runtime joinpoint on a method (static joinpoint).
+ * The static part of a given joinpoint can be generically retrieved
+ * using the {@link #getStaticPart()} method.
+ *
+ * <p>In the context of an interception framework, a runtime joinpoint
+ * is then the reification of an access to an accessible object (a
+ * method, a constructor, a field), i.e. the static part of the
+ * joinpoint. It is passed to the interceptors that are installed on
+ * the static joinpoint.
+ *
+ * @author Rod Johnson
+ * @see Interceptor
+ */
+public interface Joinpoint {
+
+ /**
+ * Proceed to the next interceptor in the chain.
+ * <p>The implementation and the semantics of this method depends
+ * on the actual joinpoint type (see the children interfaces).
+ * @return see the children interfaces' proceed definition
+ * @throws Throwable if the joinpoint throws an exception
+ */
+ Object proceed() throws Throwable;
+
+ /**
+ * Return the object that holds the current joinpoint's static part.
+ * <p>For instance, the target object for an invocation.
+ * @return the object (can be null if the accessible object is static)
+ */
+ Object getThis();
+
+ /**
+ * Return the static part of this joinpoint.
+ * <p>The static part is an accessible object on which a chain of
+ * interceptors are installed.
+ */
+ AccessibleObject getStaticPart();
+
+}
diff --git a/spring-aop/src/main/java/org/aopalliance/intercept/MethodInterceptor.java b/spring-aop/src/main/java/org/aopalliance/intercept/MethodInterceptor.java
new file mode 100644
index 00000000..c08fd744
--- /dev/null
+++ b/spring-aop/src/main/java/org/aopalliance/intercept/MethodInterceptor.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.aopalliance.intercept;
+
+/**
+ * Intercepts calls on an interface on its way to the target. These
+ * are nested "on top" of the target.
+ *
+ * <p>The user should implement the {@link #invoke(MethodInvocation)}
+ * method to modify the original behavior. E.g. the following class
+ * implements a tracing interceptor (traces all the calls on the
+ * intercepted method(s)):
+ *
+ * <pre class=code>
+ * class TracingInterceptor implements MethodInterceptor {
+ * Object invoke(MethodInvocation i) throws Throwable {
+ * System.out.println("method "+i.getMethod()+" is called on "+
+ * i.getThis()+" with args "+i.getArguments());
+ * Object ret=i.proceed();
+ * System.out.println("method "+i.getMethod()+" returns "+ret);
+ * return ret;
+ * }
+ * }
+ * </pre>
+ *
+ * @author Rod Johnson
+ */
+public interface MethodInterceptor extends Interceptor {
+
+ /**
+ * Implement this method to perform extra treatments before and
+ * after the invocation. Polite implementations would certainly
+ * like to invoke {@link Joinpoint#proceed()}.
+ * @param invocation the method invocation joinpoint
+ * @return the result of the call to {@link Joinpoint#proceed()};
+ * might be intercepted by the interceptor
+ * @throws Throwable if the interceptors or the target object
+ * throws an exception
+ */
+ Object invoke(MethodInvocation invocation) throws Throwable;
+
+}
diff --git a/spring-aop/src/main/java/org/aopalliance/intercept/MethodInvocation.java b/spring-aop/src/main/java/org/aopalliance/intercept/MethodInvocation.java
new file mode 100644
index 00000000..6314824d
--- /dev/null
+++ b/spring-aop/src/main/java/org/aopalliance/intercept/MethodInvocation.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.aopalliance.intercept;
+
+import java.lang.reflect.Method;
+
+/**
+ * Description of an invocation to a method, given to an interceptor
+ * upon method-call.
+ *
+ * <p>A method invocation is a joinpoint and can be intercepted by a
+ * method interceptor.
+ *
+ * @author Rod Johnson
+ * @see MethodInterceptor
+ */
+public interface MethodInvocation extends Invocation {
+
+ /**
+ * Get the method being called.
+ * <p>This method is a frienly implementation of the
+ * {@link Joinpoint#getStaticPart()} method (same result).
+ * @return the method being called
+ */
+ Method getMethod();
+
+}
diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/AbstractAspectJAdvice.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/AbstractAspectJAdvice.java
index 71bd31d9..c7575168 100644
--- a/spring-aop/src/main/java/org/springframework/aop/aspectj/AbstractAspectJAdvice.java
+++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/AbstractAspectJAdvice.java
@@ -16,6 +16,9 @@
package org.springframework.aop.aspectj;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
@@ -55,7 +58,8 @@ import org.springframework.util.StringUtils;
* @author Ramnivas Laddad
* @since 2.0
*/
-public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedenceInformation {
+@SuppressWarnings("serial")
+public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedenceInformation, Serializable {
/**
* Key used in ReflectiveMethodInvocation userAtributes map for the current joinpoint.
@@ -86,10 +90,13 @@ public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedence
}
- protected final Method aspectJAdviceMethod;
+ private final Class<?> declaringClass;
- /** The total number of arguments we have to populate on advice dispatch */
- private final int adviceInvocationArgumentCount;
+ private final String methodName;
+
+ private final Class<?>[] parameterTypes;
+
+ protected transient Method aspectJAdviceMethod;
private final AspectJExpressionPointcut pointcut;
@@ -154,8 +161,10 @@ public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedence
Method aspectJAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aspectInstanceFactory) {
Assert.notNull(aspectJAdviceMethod, "Advice method must not be null");
+ this.declaringClass = aspectJAdviceMethod.getDeclaringClass();
+ this.methodName = aspectJAdviceMethod.getName();
+ this.parameterTypes = aspectJAdviceMethod.getParameterTypes();
this.aspectJAdviceMethod = aspectJAdviceMethod;
- this.adviceInvocationArgumentCount = this.aspectJAdviceMethod.getParameterTypes().length;
this.pointcut = pointcut;
this.aspectInstanceFactory = aspectInstanceFactory;
}
@@ -359,11 +368,11 @@ public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedence
*/
public synchronized final void calculateArgumentBindings() {
// The simple case... nothing to bind.
- if (this.argumentsIntrospected || this.adviceInvocationArgumentCount == 0) {
+ if (this.argumentsIntrospected || this.parameterTypes.length == 0) {
return;
}
- int numUnboundArgs = this.adviceInvocationArgumentCount;
+ int numUnboundArgs = this.parameterTypes.length;
Class<?>[] parameterTypes = this.aspectJAdviceMethod.getParameterTypes();
if (maybeBindJoinPoint(parameterTypes[0]) || maybeBindProceedingJoinPoint(parameterTypes[0])) {
numUnboundArgs--;
@@ -462,7 +471,7 @@ public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedence
}
// So we match in number...
- int argumentIndexOffset = this.adviceInvocationArgumentCount - numArgumentsLeftToBind;
+ int argumentIndexOffset = this.parameterTypes.length - numArgumentsLeftToBind;
for (int i = argumentIndexOffset; i < this.argumentNames.length; i++) {
this.argumentBindings.put(this.argumentNames[i], i);
}
@@ -543,7 +552,7 @@ public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedence
calculateArgumentBindings();
// AMC start
- Object[] adviceInvocationArgs = new Object[this.adviceInvocationArgumentCount];
+ Object[] adviceInvocationArgs = new Object[this.parameterTypes.length];
int numBound = 0;
if (this.joinPointArgumentIndex != -1) {
@@ -580,8 +589,8 @@ public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedence
}
}
- if (numBound != this.adviceInvocationArgumentCount) {
- throw new IllegalStateException("Required to bind " + this.adviceInvocationArgumentCount +
+ if (numBound != this.parameterTypes.length) {
+ throw new IllegalStateException("Required to bind " + this.parameterTypes.length +
" arguments, but only bound " + numBound + " (JoinPointMatch " +
(jpMatch == null ? "was NOT" : "WAS") + " bound in invocation)");
}
@@ -664,6 +673,16 @@ public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedence
"aspect name '" + this.aspectName + "'";
}
+ private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException {
+ inputStream.defaultReadObject();
+ try {
+ this.aspectJAdviceMethod = this.declaringClass.getMethod(this.methodName, this.parameterTypes);
+ }
+ catch (NoSuchMethodException ex) {
+ throw new IllegalStateException("Failed to find advice method on deserialization", ex);
+ }
+ }
+
/**
* MethodMatcher that excludes the specified advice method.
diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAfterAdvice.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAfterAdvice.java
index a1900b89..3de3c25a 100644
--- a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAfterAdvice.java
+++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAfterAdvice.java
@@ -16,6 +16,7 @@
package org.springframework.aop.aspectj;
+import java.io.Serializable;
import java.lang.reflect.Method;
import org.aopalliance.intercept.MethodInterceptor;
@@ -29,7 +30,9 @@ import org.springframework.aop.AfterAdvice;
* @author Rod Johnson
* @since 2.0
*/
-public class AspectJAfterAdvice extends AbstractAspectJAdvice implements MethodInterceptor, AfterAdvice {
+@SuppressWarnings("serial")
+public class AspectJAfterAdvice extends AbstractAspectJAdvice
+ implements MethodInterceptor, AfterAdvice, Serializable {
public AspectJAfterAdvice(
Method aspectJBeforeAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) {
diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAfterReturningAdvice.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAfterReturningAdvice.java
index 3a852bdb..92f9cb4a 100644
--- a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAfterReturningAdvice.java
+++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAfterReturningAdvice.java
@@ -16,6 +16,7 @@
package org.springframework.aop.aspectj;
+import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
@@ -32,7 +33,9 @@ import org.springframework.util.TypeUtils;
* @author Ramnivas Laddad
* @since 2.0
*/
-public class AspectJAfterReturningAdvice extends AbstractAspectJAdvice implements AfterReturningAdvice, AfterAdvice {
+@SuppressWarnings("serial")
+public class AspectJAfterReturningAdvice extends AbstractAspectJAdvice
+ implements AfterReturningAdvice, AfterAdvice, Serializable {
public AspectJAfterReturningAdvice(
Method aspectJBeforeAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) {
diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAfterThrowingAdvice.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAfterThrowingAdvice.java
index be74c7e4..fcf89a12 100644
--- a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAfterThrowingAdvice.java
+++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAfterThrowingAdvice.java
@@ -16,6 +16,7 @@
package org.springframework.aop.aspectj;
+import java.io.Serializable;
import java.lang.reflect.Method;
import org.aopalliance.intercept.MethodInterceptor;
@@ -29,7 +30,9 @@ import org.springframework.aop.AfterAdvice;
* @author Rod Johnson
* @since 2.0
*/
-public class AspectJAfterThrowingAdvice extends AbstractAspectJAdvice implements MethodInterceptor, AfterAdvice {
+@SuppressWarnings("serial")
+public class AspectJAfterThrowingAdvice extends AbstractAspectJAdvice
+ implements MethodInterceptor, AfterAdvice, Serializable {
public AspectJAfterThrowingAdvice(
Method aspectJBeforeAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) {
diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAroundAdvice.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAroundAdvice.java
index 3efefd27..66f3b03b 100644
--- a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAroundAdvice.java
+++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAroundAdvice.java
@@ -16,6 +16,7 @@
package org.springframework.aop.aspectj;
+import java.io.Serializable;
import java.lang.reflect.Method;
import org.aopalliance.intercept.MethodInterceptor;
@@ -33,7 +34,8 @@ import org.springframework.aop.ProxyMethodInvocation;
* @author Juergen Hoeller
* @since 2.0
*/
-public class AspectJAroundAdvice extends AbstractAspectJAdvice implements MethodInterceptor {
+@SuppressWarnings("serial")
+public class AspectJAroundAdvice extends AbstractAspectJAdvice implements MethodInterceptor, Serializable {
public AspectJAroundAdvice(
Method aspectJAroundAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) {
diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJMethodBeforeAdvice.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJMethodBeforeAdvice.java
index 65a5dce1..b1834566 100644
--- a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJMethodBeforeAdvice.java
+++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJMethodBeforeAdvice.java
@@ -16,6 +16,7 @@
package org.springframework.aop.aspectj;
+import java.io.Serializable;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
@@ -27,7 +28,8 @@ import org.springframework.aop.MethodBeforeAdvice;
* @author Adrian Colyer
* @since 2.0
*/
-public class AspectJMethodBeforeAdvice extends AbstractAspectJAdvice implements MethodBeforeAdvice {
+@SuppressWarnings("serial")
+public class AspectJMethodBeforeAdvice extends AbstractAspectJAdvice implements MethodBeforeAdvice, Serializable {
public AspectJMethodBeforeAdvice(
Method aspectJBeforeAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) {
diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/DeclareParentsAdvisor.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/DeclareParentsAdvisor.java
index 3272b922..47e46155 100644
--- a/spring-aop/src/main/java/org/springframework/aop/aspectj/DeclareParentsAdvisor.java
+++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/DeclareParentsAdvisor.java
@@ -65,7 +65,7 @@ public class DeclareParentsAdvisor implements IntroductionAdvisor {
/**
* Private constructor to share common code between impl-based delegate and reference-based delegate
- * (cannot use method such as init() to share common code, due the the use of final fields)
+ * (cannot use method such as init() to share common code, due the use of final fields)
* @param interfaceType static field defining the introduction
* @param typePattern type pattern the introduction is restricted to
* @param implementationClass implementation class
diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/SimpleAspectInstanceFactory.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/SimpleAspectInstanceFactory.java
index f1275dae..5d413bd6 100644
--- a/spring-aop/src/main/java/org/springframework/aop/aspectj/SimpleAspectInstanceFactory.java
+++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/SimpleAspectInstanceFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -55,10 +55,12 @@ public class SimpleAspectInstanceFactory implements AspectInstanceFactory {
return this.aspectClass.newInstance();
}
catch (InstantiationException ex) {
- throw new AopConfigException("Unable to instantiate aspect class [" + this.aspectClass.getName() + "]", ex);
+ throw new AopConfigException(
+ "Unable to instantiate aspect class: " + this.aspectClass.getName(), ex);
}
catch (IllegalAccessException ex) {
- throw new AopConfigException("Cannot access element class [" + this.aspectClass.getName() + "]", ex);
+ throw new AopConfigException(
+ "Could not access aspect constructor: " + this.aspectClass.getName(), ex);
}
}
diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/SingletonAspectInstanceFactory.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/SingletonAspectInstanceFactory.java
index 1e8f723e..d311f620 100644
--- a/spring-aop/src/main/java/org/springframework/aop/aspectj/SingletonAspectInstanceFactory.java
+++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/SingletonAspectInstanceFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
package org.springframework.aop.aspectj;
+import java.io.Serializable;
+
import org.springframework.core.Ordered;
import org.springframework.util.Assert;
@@ -29,7 +31,8 @@ import org.springframework.util.Assert;
* @since 2.0
* @see SimpleAspectInstanceFactory
*/
-public class SingletonAspectInstanceFactory implements AspectInstanceFactory {
+@SuppressWarnings("serial")
+public class SingletonAspectInstanceFactory implements AspectInstanceFactory, Serializable {
private final Object aspectInstance;
diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectMetadata.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectMetadata.java
index d20e5222..dd8823ec 100644
--- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectMetadata.java
+++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectMetadata.java
@@ -16,6 +16,10 @@
package org.springframework.aop.aspectj.annotation;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.AjType;
import org.aspectj.lang.reflect.AjTypeSystem;
@@ -40,7 +44,8 @@ import org.springframework.aop.support.ComposablePointcut;
* @since 2.0
* @see org.springframework.aop.aspectj.AspectJExpressionPointcut
*/
-public class AspectMetadata {
+@SuppressWarnings("serial")
+public class AspectMetadata implements Serializable {
/**
* The name of this aspect as defined to Spring (the bean name) -
@@ -50,9 +55,16 @@ public class AspectMetadata {
private final String aspectName;
/**
+ * The aspect class, stored separately for re-resolution of the
+ * corresponding AjType on deserialization.
+ */
+ private final Class<?> aspectClass;
+
+ /**
* AspectJ reflection information (AspectJ 5 / Java 5 specific).
+ * Re-resolved on deserialization since it isn't serializable itself.
*/
- private final AjType<?> ajType;
+ private transient AjType<?> ajType;
/**
* Spring AOP pointcut corresponding to the per clause of the
@@ -86,6 +98,7 @@ public class AspectMetadata {
if (ajType.getDeclarePrecedence().length > 0) {
throw new IllegalArgumentException("DeclarePrecendence not presently supported in Spring AOP");
}
+ this.aspectClass = ajType.getJavaClass();
this.ajType = ajType;
switch (this.ajType.getPerClause().getKind()) {
@@ -132,7 +145,7 @@ public class AspectMetadata {
* Return the aspect class.
*/
public Class<?> getAspectClass() {
- return this.ajType.getJavaClass();
+ return this.aspectClass;
}
/**
@@ -173,4 +186,10 @@ public class AspectMetadata {
return (isPerThisOrPerTarget() || isPerTypeWithin());
}
+
+ private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException {
+ inputStream.defaultReadObject();
+ this.ajType = AjTypeSystem.getAjType(this.aspectClass);
+ }
+
}
diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectInstanceFactory.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectInstanceFactory.java
index 713fb44f..f68fea56 100644
--- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectInstanceFactory.java
+++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectInstanceFactory.java
@@ -16,6 +16,8 @@
package org.springframework.aop.aspectj.annotation;
+import java.io.Serializable;
+
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.Ordered;
@@ -38,7 +40,8 @@ import org.springframework.util.ClassUtils;
* @see org.springframework.beans.factory.BeanFactory
* @see LazySingletonAspectInstanceFactoryDecorator
*/
-public class BeanFactoryAspectInstanceFactory implements MetadataAwareAspectInstanceFactory {
+@SuppressWarnings("serial")
+public class BeanFactoryAspectInstanceFactory implements MetadataAwareAspectInstanceFactory, Serializable {
private final BeanFactory beanFactory;
@@ -92,6 +95,7 @@ public class BeanFactoryAspectInstanceFactory implements MetadataAwareAspectInst
return this.aspectMetadata;
}
+ @Override
public Object getAspectCreationMutex() {
return (this.beanFactory instanceof ConfigurableBeanFactory ?
((ConfigurableBeanFactory) this.beanFactory).getSingletonMutex() : this);
diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/InstantiationModelAwarePointcutAdvisorImpl.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/InstantiationModelAwarePointcutAdvisorImpl.java
index 750a1eb2..27d3a9ba 100644
--- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/InstantiationModelAwarePointcutAdvisorImpl.java
+++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/InstantiationModelAwarePointcutAdvisorImpl.java
@@ -16,6 +16,9 @@
package org.springframework.aop.aspectj.annotation;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
import java.lang.reflect.Method;
import org.aopalliance.aop.Advice;
@@ -37,12 +40,19 @@ import org.springframework.aop.support.Pointcuts;
* @author Juergen Hoeller
* @since 2.0
*/
+@SuppressWarnings("serial")
class InstantiationModelAwarePointcutAdvisorImpl
- implements InstantiationModelAwarePointcutAdvisor, AspectJPrecedenceInformation {
+ implements InstantiationModelAwarePointcutAdvisor, AspectJPrecedenceInformation, Serializable {
private final AspectJExpressionPointcut declaredPointcut;
- private final Method aspectJAdviceMethod;
+ private final Class<?> declaringClass;
+
+ private final String methodName;
+
+ private final Class<?>[] parameterTypes;
+
+ private transient Method aspectJAdviceMethod;
private final AspectJAdvisorFactory aspectJAdvisorFactory;
@@ -68,6 +78,9 @@ class InstantiationModelAwarePointcutAdvisorImpl
MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {
this.declaredPointcut = declaredPointcut;
+ this.declaringClass = aspectJAdviceMethod.getDeclaringClass();
+ this.methodName = aspectJAdviceMethod.getName();
+ this.parameterTypes = aspectJAdviceMethod.getParameterTypes();
this.aspectJAdviceMethod = aspectJAdviceMethod;
this.aspectJAdvisorFactory = aspectJAdvisorFactory;
this.aspectInstanceFactory = aspectInstanceFactory;
@@ -227,6 +240,16 @@ class InstantiationModelAwarePointcutAdvisorImpl
}
+ private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException {
+ inputStream.defaultReadObject();
+ try {
+ this.aspectJAdviceMethod = this.declaringClass.getMethod(this.methodName, this.parameterTypes);
+ }
+ catch (NoSuchMethodException ex) {
+ throw new IllegalStateException("Failed to find advice method on deserialization", ex);
+ }
+ }
+
/**
* Pointcut implementation that changes its behaviour when the advice is instantiated.
diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/LazySingletonAspectInstanceFactoryDecorator.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/LazySingletonAspectInstanceFactoryDecorator.java
index 78df5465..3e76fb47 100644
--- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/LazySingletonAspectInstanceFactoryDecorator.java
+++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/LazySingletonAspectInstanceFactoryDecorator.java
@@ -16,6 +16,8 @@
package org.springframework.aop.aspectj.annotation;
+import java.io.Serializable;
+
import org.springframework.util.Assert;
/**
@@ -25,7 +27,8 @@ import org.springframework.util.Assert;
* @author Juergen Hoeller
* @since 2.0
*/
-public class LazySingletonAspectInstanceFactoryDecorator implements MetadataAwareAspectInstanceFactory {
+@SuppressWarnings("serial")
+public class LazySingletonAspectInstanceFactoryDecorator implements MetadataAwareAspectInstanceFactory, Serializable {
private final MetadataAwareAspectInstanceFactory maaif;
@@ -45,11 +48,7 @@ public class LazySingletonAspectInstanceFactoryDecorator implements MetadataAwar
@Override
public Object getAspectInstance() {
if (this.materialized == null) {
- Object mutex = this;
- if (this.maaif instanceof BeanFactoryAspectInstanceFactory) {
- mutex = ((BeanFactoryAspectInstanceFactory) this.maaif).getAspectCreationMutex();
- }
- synchronized (mutex) {
+ synchronized (this.maaif.getAspectCreationMutex()) {
if (this.materialized == null) {
this.materialized = this.maaif.getAspectInstance();
}
@@ -73,6 +72,11 @@ public class LazySingletonAspectInstanceFactoryDecorator implements MetadataAwar
}
@Override
+ public Object getAspectCreationMutex() {
+ return this.maaif.getAspectCreationMutex();
+ }
+
+ @Override
public int getOrder() {
return this.maaif.getOrder();
}
diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/MetadataAwareAspectInstanceFactory.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/MetadataAwareAspectInstanceFactory.java
index cb9a77d4..a001efbc 100644
--- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/MetadataAwareAspectInstanceFactory.java
+++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/MetadataAwareAspectInstanceFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2007 the original author or authors.
+ * Copyright 2002-2016 the original author 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,4 +39,11 @@ public interface MetadataAwareAspectInstanceFactory extends AspectInstanceFactor
*/
AspectMetadata getAspectMetadata();
+ /**
+ * Return the best possible creation mutex for this factory.
+ * @return the mutex object (never {@code null})
+ * @since 4.3
+ */
+ Object getAspectCreationMutex();
+
}
diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/PrototypeAspectInstanceFactory.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/PrototypeAspectInstanceFactory.java
index fc2978a9..e1e17155 100644
--- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/PrototypeAspectInstanceFactory.java
+++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/PrototypeAspectInstanceFactory.java
@@ -16,6 +16,8 @@
package org.springframework.aop.aspectj.annotation;
+import java.io.Serializable;
+
import org.springframework.beans.factory.BeanFactory;
/**
@@ -32,7 +34,8 @@ import org.springframework.beans.factory.BeanFactory;
* @see org.springframework.beans.factory.BeanFactory
* @see LazySingletonAspectInstanceFactoryDecorator
*/
-public class PrototypeAspectInstanceFactory extends BeanFactoryAspectInstanceFactory {
+@SuppressWarnings("serial")
+public class PrototypeAspectInstanceFactory extends BeanFactoryAspectInstanceFactory implements Serializable {
/**
* Create a PrototypeAspectInstanceFactory. AspectJ will be called to
diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/ReflectiveAspectJAdvisorFactory.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/ReflectiveAspectJAdvisorFactory.java
index 1a354930..ce139f71 100644
--- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/ReflectiveAspectJAdvisorFactory.java
+++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/ReflectiveAspectJAdvisorFactory.java
@@ -16,6 +16,7 @@
package org.springframework.aop.aspectj.annotation;
+import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@@ -65,7 +66,8 @@ import org.springframework.util.comparator.InstanceComparator;
* @author Phillip Webb
* @since 2.0
*/
-public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFactory {
+@SuppressWarnings("serial")
+public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFactory implements Serializable {
private static final Comparator<Method> METHOD_COMPARATOR;
diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/SimpleMetadataAwareAspectInstanceFactory.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/SimpleMetadataAwareAspectInstanceFactory.java
index 5b8113e7..2aa2431a 100644
--- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/SimpleMetadataAwareAspectInstanceFactory.java
+++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/SimpleMetadataAwareAspectInstanceFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -51,6 +51,11 @@ public class SimpleMetadataAwareAspectInstanceFactory extends SimpleAspectInstan
}
@Override
+ public Object getAspectCreationMutex() {
+ return this;
+ }
+
+ @Override
protected int getOrderForAspectClass(Class<?> aspectClass) {
return OrderUtils.getOrder(aspectClass, Ordered.LOWEST_PRECEDENCE);
}
diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/SingletonMetadataAwareAspectInstanceFactory.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/SingletonMetadataAwareAspectInstanceFactory.java
index 0f83e6c2..15edfbbd 100644
--- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/SingletonMetadataAwareAspectInstanceFactory.java
+++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/SingletonMetadataAwareAspectInstanceFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
package org.springframework.aop.aspectj.annotation;
+import java.io.Serializable;
+
import org.springframework.aop.aspectj.SingletonAspectInstanceFactory;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.OrderUtils;
@@ -30,8 +32,9 @@ import org.springframework.core.annotation.OrderUtils;
* @since 2.0
* @see SimpleMetadataAwareAspectInstanceFactory
*/
+@SuppressWarnings("serial")
public class SingletonMetadataAwareAspectInstanceFactory extends SingletonAspectInstanceFactory
- implements MetadataAwareAspectInstanceFactory {
+ implements MetadataAwareAspectInstanceFactory, Serializable {
private final AspectMetadata metadata;
@@ -53,6 +56,11 @@ public class SingletonMetadataAwareAspectInstanceFactory extends SingletonAspect
}
@Override
+ public Object getAspectCreationMutex() {
+ return this;
+ }
+
+ @Override
protected int getOrderForAspectClass(Class<?> aspectClass) {
return OrderUtils.getOrder(aspectClass, Ordered.LOWEST_PRECEDENCE);
}
diff --git a/spring-aop/src/main/java/org/springframework/aop/config/AopConfigUtils.java b/spring-aop/src/main/java/org/springframework/aop/config/AopConfigUtils.java
index d3f199c5..c2271500 100644
--- a/spring-aop/src/main/java/org/springframework/aop/config/AopConfigUtils.java
+++ b/spring-aop/src/main/java/org/springframework/aop/config/AopConfigUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -97,7 +97,7 @@ public abstract class AopConfigUtils {
}
}
- static void forceAutoProxyCreatorToExposeProxy(BeanDefinitionRegistry registry) {
+ public static void forceAutoProxyCreatorToExposeProxy(BeanDefinitionRegistry registry) {
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
definition.getPropertyValues().add("exposeProxy", Boolean.TRUE);
diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/AdvisedSupport.java b/spring-aop/src/main/java/org/springframework/aop/framework/AdvisedSupport.java
index 9b3b3074..40940374 100644
--- a/spring-aop/src/main/java/org/springframework/aop/framework/AdvisedSupport.java
+++ b/spring-aop/src/main/java/org/springframework/aop/framework/AdvisedSupport.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -586,7 +586,7 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
* Simple wrapper class around a Method. Used as the key when
* caching methods, for efficient equals and hashCode comparisons.
*/
- private static class MethodCacheKey {
+ private static final class MethodCacheKey implements Comparable<MethodCacheKey> {
private final Method method;
@@ -599,17 +599,28 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
@Override
public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
- MethodCacheKey otherKey = (MethodCacheKey) other;
- return (this.method == otherKey.method);
+ return (this == other || (other instanceof MethodCacheKey &&
+ this.method == ((MethodCacheKey) other).method));
}
@Override
public int hashCode() {
return this.hashCode;
}
+
+ @Override
+ public String toString() {
+ return this.method.toString();
+ }
+
+ @Override
+ public int compareTo(MethodCacheKey other) {
+ int result = this.method.getName().compareTo(other.method.getName());
+ if (result == 0) {
+ result = this.method.toString().compareTo(other.method.toString());
+ }
+ return result;
+ }
}
}
diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java b/spring-aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java
index 4e063d2b..15535a5e 100644
--- a/spring-aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java
+++ b/spring-aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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 org.springframework.aop.TargetClassAware;
import org.springframework.aop.TargetSource;
import org.springframework.aop.support.AopUtils;
import org.springframework.aop.target.SingletonTargetSource;
+import org.springframework.core.DecoratingProxy;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
@@ -78,11 +79,29 @@ public abstract class AopProxyUtils {
* <p>This will always add the {@link Advised} interface unless the AdvisedSupport's
* {@link AdvisedSupport#setOpaque "opaque"} flag is on. Always adds the
* {@link org.springframework.aop.SpringProxy} marker interface.
+ * @param advised the proxy config
* @return the complete set of interfaces to proxy
+ * @see SpringProxy
* @see Advised
- * @see org.springframework.aop.SpringProxy
*/
public static Class<?>[] completeProxiedInterfaces(AdvisedSupport advised) {
+ return completeProxiedInterfaces(advised, false);
+ }
+
+ /**
+ * Determine the complete set of interfaces to proxy for the given AOP configuration.
+ * <p>This will always add the {@link Advised} interface unless the AdvisedSupport's
+ * {@link AdvisedSupport#setOpaque "opaque"} flag is on. Always adds the
+ * {@link org.springframework.aop.SpringProxy} marker interface.
+ * @param advised the proxy config
+ * @param decoratingProxy whether to expose the {@link DecoratingProxy} interface
+ * @return the complete set of interfaces to proxy
+ * @since 4.3
+ * @see SpringProxy
+ * @see Advised
+ * @see DecoratingProxy
+ */
+ static Class<?>[] completeProxiedInterfaces(AdvisedSupport advised, boolean decoratingProxy) {
Class<?>[] specifiedInterfaces = advised.getProxiedInterfaces();
if (specifiedInterfaces.length == 0) {
// No user-specified interfaces: check whether target class is an interface.
@@ -99,6 +118,7 @@ public abstract class AopProxyUtils {
}
boolean addSpringProxy = !advised.isInterfaceProxied(SpringProxy.class);
boolean addAdvised = !advised.isOpaque() && !advised.isInterfaceProxied(Advised.class);
+ boolean addDecoratingProxy = (decoratingProxy && !advised.isInterfaceProxied(DecoratingProxy.class));
int nonUserIfcCount = 0;
if (addSpringProxy) {
nonUserIfcCount++;
@@ -106,13 +126,22 @@ public abstract class AopProxyUtils {
if (addAdvised) {
nonUserIfcCount++;
}
+ if (addDecoratingProxy) {
+ nonUserIfcCount++;
+ }
Class<?>[] proxiedInterfaces = new Class<?>[specifiedInterfaces.length + nonUserIfcCount];
System.arraycopy(specifiedInterfaces, 0, proxiedInterfaces, 0, specifiedInterfaces.length);
+ int index = specifiedInterfaces.length;
if (addSpringProxy) {
- proxiedInterfaces[specifiedInterfaces.length] = SpringProxy.class;
+ proxiedInterfaces[index] = SpringProxy.class;
+ index++;
}
if (addAdvised) {
- proxiedInterfaces[proxiedInterfaces.length - 1] = Advised.class;
+ proxiedInterfaces[index] = Advised.class;
+ index++;
+ }
+ if (addDecoratingProxy) {
+ proxiedInterfaces[index] = DecoratingProxy.class;
}
return proxiedInterfaces;
}
@@ -134,6 +163,9 @@ public abstract class AopProxyUtils {
if (proxy instanceof Advised) {
nonUserIfcCount++;
}
+ if (proxy instanceof DecoratingProxy) {
+ nonUserIfcCount++;
+ }
Class<?>[] userInterfaces = new Class<?>[proxyInterfaces.length - nonUserIfcCount];
System.arraycopy(proxyInterfaces, 0, userInterfaces, 0, userInterfaces.length);
Assert.notEmpty(userInterfaces, "JDK proxy must implement one or more interfaces");
diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java b/spring-aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java
index e98c3085..1f90b0b5 100644
--- a/spring-aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java
+++ b/spring-aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +30,7 @@ import org.springframework.aop.AopInvocationException;
import org.springframework.aop.RawTargetAccess;
import org.springframework.aop.TargetSource;
import org.springframework.aop.support.AopUtils;
+import org.springframework.core.DecoratingProxy;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -116,7 +117,7 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
if (logger.isDebugEnabled()) {
logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
}
- Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
+ Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
@@ -164,11 +165,15 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
// The target does not implement the equals(Object) method itself.
return equals(args[0]);
}
- if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
+ else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
// The target does not implement the hashCode() method itself.
return hashCode();
}
- if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
+ else if (method.getDeclaringClass() == DecoratingProxy.class) {
+ // There is only getDecoratedClass() declared -> dispatch to proxy config.
+ return AopProxyUtils.ultimateTargetClass(this.advised);
+ }
+ else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
method.getDeclaringClass().isAssignableFrom(Advised.class)) {
// Service invocations on ProxyConfig with the proxy config...
return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/ProxyProcessorSupport.java b/spring-aop/src/main/java/org/springframework/aop/framework/ProxyProcessorSupport.java
index af1cf603..a94c53b1 100644
--- a/spring-aop/src/main/java/org/springframework/aop/framework/ProxyProcessorSupport.java
+++ b/spring-aop/src/main/java/org/springframework/aop/framework/ProxyProcessorSupport.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -139,7 +139,8 @@ public class ProxyProcessorSupport extends ProxyConfig implements Ordered, BeanC
* @return whether the given interface is an internal language interface
*/
protected boolean isInternalLanguageInterface(Class<?> ifc) {
- return ifc.getName().equals("groovy.lang.GroovyObject");
+ return (ifc.getName().equals("groovy.lang.GroovyObject") ||
+ ifc.getName().endsWith(".cglib.proxy.Factory"));
}
}
diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java
index eb476b4e..72934308 100644
--- a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java
+++ b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.Advisor;
+import org.springframework.aop.Pointcut;
import org.springframework.aop.TargetSource;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.aop.framework.ProxyFactory;
@@ -370,6 +371,7 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
*/
protected boolean isInfrastructureClass(Class<?> beanClass) {
boolean retVal = Advice.class.isAssignableFrom(beanClass) ||
+ Pointcut.class.isAssignableFrom(beanClass) ||
Advisor.class.isAssignableFrom(beanClass) ||
AopInfrastructureBean.class.isAssignableFrom(beanClass);
if (retVal && logger.isTraceEnabled()) {
diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AutoProxyUtils.java b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AutoProxyUtils.java
index a771146f..af39672c 100644
--- a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AutoProxyUtils.java
+++ b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AutoProxyUtils.java
@@ -35,7 +35,7 @@ public abstract class AutoProxyUtils {
* to be proxied with its target class (in case of it getting proxied in the first
* place). The value is {@code Boolean.TRUE} or {@code Boolean.FALSE}.
* <p>Proxy factories can set this attribute if they built a target class proxy
- * for a specific bean, and want to enforce that that bean can always be cast
+ * for a specific bean, and want to enforce that bean can always be cast
* to its target class (even if AOP advices get applied through auto-proxying).
* @see #shouldProxyTargetClass
*/
diff --git a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java
index b1cdaa0f..28917436 100644
--- a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java
+++ b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java
@@ -235,7 +235,7 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
if (logger.isInfoEnabled()) {
logger.info("More than one TaskExecutor bean found within the context, and none is named " +
"'taskExecutor'. Mark one of them as primary or name it 'taskExecutor' (possibly " +
- "as an alias) in order to use it for async processing.");
+ "as an alias) in order to use it for async processing: " + ex.getBeanNamesFound());
}
}
}
diff --git a/spring-aop/src/main/java/org/springframework/aop/support/AopUtils.java b/spring-aop/src/main/java/org/springframework/aop/support/AopUtils.java
index 1ed373ca..4c8273ec 100644
--- a/spring-aop/src/main/java/org/springframework/aop/support/AopUtils.java
+++ b/spring-aop/src/main/java/org/springframework/aop/support/AopUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.aop.support;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.LinkedHashSet;
import java.util.LinkedList;
@@ -34,6 +35,7 @@ import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.SpringProxy;
import org.springframework.aop.TargetClassAware;
import org.springframework.core.BridgeMethodResolver;
+import org.springframework.core.MethodIntrospector;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
@@ -113,6 +115,30 @@ public abstract class AopUtils {
}
/**
+ * Select an invocable method on the target type: either the given method itself
+ * if actually exposed on the target type, or otherwise a corresponding method
+ * on one of the target type's interfaces or on the target type itself.
+ * @param method the method to check
+ * @param targetType the target type to search methods on (typically an AOP proxy)
+ * @return a corresponding invocable method on the target type
+ * @throws IllegalStateException if the given method is not invocable on the given
+ * target type (typically due to a proxy mismatch)
+ * @since 4.3
+ * @see MethodIntrospector#selectInvocableMethod(Method, Class)
+ */
+ public static Method selectInvocableMethod(Method method, Class<?> targetType) {
+ Method methodToUse = MethodIntrospector.selectInvocableMethod(method, targetType);
+ if (Modifier.isPrivate(methodToUse.getModifiers()) && !Modifier.isStatic(methodToUse.getModifiers()) &&
+ SpringProxy.class.isAssignableFrom(targetType)) {
+ throw new IllegalStateException(String.format(
+ "Need to invoke method '%s' found on proxy for target class '%s' but cannot " +
+ "be delegated to target bean. Switch its visibility to package or protected.",
+ method.getName(), method.getDeclaringClass().getSimpleName()));
+ }
+ return methodToUse;
+ }
+
+ /**
* Determine whether the given method is an "equals" method.
* @see java.lang.Object#equals
*/
@@ -196,6 +222,11 @@ public abstract class AopUtils {
}
MethodMatcher methodMatcher = pc.getMethodMatcher();
+ if (methodMatcher == MethodMatcher.TRUE) {
+ // No need to iterate the methods if we're matching any method anyway...
+ return true;
+ }
+
IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
@@ -204,7 +235,7 @@ public abstract class AopUtils {
Set<Class<?>> classes = new LinkedHashSet<Class<?>>(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
classes.add(targetClass);
for (Class<?> clazz : classes) {
- Method[] methods = clazz.getMethods();
+ Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
for (Method method : methods) {
if ((introductionAwareMethodMatcher != null &&
introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) ||
diff --git a/spring-aop/src/main/resources/META-INF/spring.schemas b/spring-aop/src/main/resources/META-INF/spring.schemas
index 6f3dae37..fda92512 100644
--- a/spring-aop/src/main/resources/META-INF/spring.schemas
+++ b/spring-aop/src/main/resources/META-INF/spring.schemas
@@ -6,4 +6,5 @@ http\://www.springframework.org/schema/aop/spring-aop-3.2.xsd=org/springframewor
http\://www.springframework.org/schema/aop/spring-aop-4.0.xsd=org/springframework/aop/config/spring-aop-4.0.xsd
http\://www.springframework.org/schema/aop/spring-aop-4.1.xsd=org/springframework/aop/config/spring-aop-4.1.xsd
http\://www.springframework.org/schema/aop/spring-aop-4.2.xsd=org/springframework/aop/config/spring-aop-4.2.xsd
-http\://www.springframework.org/schema/aop/spring-aop.xsd=org/springframework/aop/config/spring-aop-4.2.xsd
+http\://www.springframework.org/schema/aop/spring-aop-4.3.xsd=org/springframework/aop/config/spring-aop-4.3.xsd
+http\://www.springframework.org/schema/aop/spring-aop.xsd=org/springframework/aop/config/spring-aop-4.3.xsd
diff --git a/spring-aop/src/main/resources/org/springframework/aop/config/spring-aop-4.3.xsd b/spring-aop/src/main/resources/org/springframework/aop/config/spring-aop-4.3.xsd
new file mode 100644
index 00000000..4dd43ca6
--- /dev/null
+++ b/spring-aop/src/main/resources/org/springframework/aop/config/spring-aop-4.3.xsd
@@ -0,0 +1,409 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+
+<xsd:schema xmlns="http://www.springframework.org/schema/aop"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:tool="http://www.springframework.org/schema/tool"
+ targetNamespace="http://www.springframework.org/schema/aop"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified">
+
+ <xsd:import namespace="http://www.springframework.org/schema/beans" schemaLocation="http://www.springframework.org/schema/beans/spring-beans-4.3.xsd"/>
+ <xsd:import namespace="http://www.springframework.org/schema/tool" schemaLocation="http://www.springframework.org/schema/tool/spring-tool-4.3.xsd"/>
+
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Defines the configuration elements for the Spring Framework's AOP support.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+
+ <xsd:element name="config">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A section (compartmentalization) of AOP-specific configuration (including
+ aspects, pointcuts, etc).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="pointcut" type="pointcutType" minOccurs="0" maxOccurs="unbounded">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A named pointcut definition.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="advisor" type="advisorType" minOccurs="0" maxOccurs="unbounded">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.aop.Advisor"><![CDATA[
+ A named advisor definition.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="aspect" type="aspectType" minOccurs="0" maxOccurs="unbounded">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A named aspect definition.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="proxy-target-class" type="xsd:boolean" default="false">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Are class-based (CGLIB) proxies to be created? By default, standard
+ Java interface-based proxies are created.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="expose-proxy" type="xsd:boolean" default="false">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Indicate that the proxy should be exposed by the AOP framework as a
+ ThreadLocal for retrieval via the AopContext class. Off by default,
+ i.e. no guarantees that AopContext access will work.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="aspectj-autoproxy">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator"><![CDATA[
+ Enables the use of the @AspectJ style of Spring AOP.
+
+ See org.springframework.context.annotation.EnableAspectJAutoProxy Javadoc
+ for information on code-based alternatives to this XML element.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="include" type="includeType" minOccurs="0" maxOccurs="unbounded">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Indicates that only @AspectJ beans with names matched by the (regex)
+ pattern will be considered as defining aspects to use for Spring autoproxying.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="proxy-target-class" type="xsd:boolean" default="false">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Are class-based (CGLIB) proxies to be created? By default, standard
+ Java interface-based proxies are created.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="expose-proxy" type="xsd:boolean" default="false">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Indicate that the proxy should be exposed by the AOP framework as a
+ ThreadLocal for retrieval via the AopContext class. Off by default,
+ i.e. no guarantees that AopContext access will work.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="scoped-proxy">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.aop.scope.ScopedProxyFactoryBean"><![CDATA[
+ Marks a bean definition as being a scoped proxy.
+
+ A bean marked as such will be exposed via a proxy, with the 'real'
+ bean instance being retrieved from some other source (such as a
+ HttpSession) as and when required.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="proxy-target-class" type="xsd:boolean" default="true">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Are class-based (CGLIB) proxies to be created? This is the default; in order to
+ switch to standard Java interface-based proxies, turn this flag to "false".
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:complexType name="aspectType">
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="pointcut" type="pointcutType">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A named pointcut definition.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="declare-parents" type="declareParentsType">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Allows this aspect to introduce additional interfaces that the advised
+ object will transparently implement.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="before" type="basicAdviceType">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A before advice definition.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="after" type="basicAdviceType">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ An after advice definition.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="after-returning" type="afterReturningAdviceType">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ An after-returning advice definition.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="after-throwing" type="afterThrowingAdviceType">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ An after-throwing advice definition.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="around" type="basicAdviceType">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ An around advice definition.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:choice>
+ <xsd:attribute name="id" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The unique identifier for an aspect.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="ref" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The name of the (backing) bean that encapsulates the aspect.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="order" type="xsd:token">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.core.Ordered"><![CDATA[
+ Controls the ordering of the execution of this aspect when multiple
+ advice executes at a specific joinpoint.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+
+ <xsd:complexType name="includeType">
+ <xsd:attribute name="name" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:java.util.regex.Pattern"><![CDATA[
+ The regular expression defining which beans are to be included in the
+ list of @AspectJ beans; beans with names matched by the pattern will
+ be included.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+
+ <xsd:complexType name="pointcutType">
+ <xsd:annotation>
+ <xsd:appinfo>
+ <tool:annotation>
+ <tool:exports type="org.springframework.aop.Pointcut"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:attribute name="id" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The unique identifier for a pointcut.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="expression" use="required" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The pointcut expression.
+
+ For example : 'execution(* com.xyz.myapp.service.*.*(..))'
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+
+ <xsd:complexType name="declareParentsType">
+ <xsd:attribute name="types-matching" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.aop.aspectj.TypePatternClassFilter"><![CDATA[
+ The AspectJ type expression that defines what types (classes) the
+ introduction is restricted to.
+
+ An example would be 'org.springframework.beans.ITestBean+'.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="implement-interface" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation source="java:java.lang.Class"><![CDATA[
+ The fully qualified name of the interface that will be introduced.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="default-impl" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:java.lang.Class"><![CDATA[
+ The fully qualified name of the class that will be instantiated to serve
+ as the default implementation of the introduced interface.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="delegate-ref" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A reference to the bean that will serve
+ as the default implementation of the introduced interface.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref"/>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+
+ <xsd:complexType name="basicAdviceType">
+ <xsd:attribute name="pointcut" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The associated pointcut expression.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="pointcut-ref" type="pointcutRefType">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The name of an associated pointcut definition.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="org.springframework.aop.Pointcut"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="method" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The name of the method that defines the logic of the advice.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="arg-names" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The comma-delimited list of advice method argument (parameter) names
+ that will be matched from pointcut parameters.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+
+ <xsd:complexType name="afterReturningAdviceType">
+ <xsd:complexContent>
+ <xsd:extension base="basicAdviceType">
+ <xsd:attribute name="returning" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The name of the method parameter to which the return value must
+ be passed.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="afterThrowingAdviceType">
+ <xsd:complexContent>
+ <xsd:extension base="basicAdviceType">
+ <xsd:attribute name="throwing" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The name of the method parameter to which the thrown exception must
+ be passed.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="advisorType">
+ <xsd:annotation>
+ <xsd:appinfo>
+ <tool:annotation>
+ <tool:exports type="org.springframework.aop.Advisor"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:attribute name="id" type="xsd:string"/>
+ <xsd:attribute name="advice-ref" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A reference to an advice bean.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="org.aopalliance.aop.Advice"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="pointcut" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A pointcut expression.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="pointcut-ref" type="pointcutRefType">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A reference to a pointcut definition.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="order" type="xsd:token">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.core.Ordered"><![CDATA[
+ Controls the ordering of the execution of this advice when multiple
+ advice executes at a specific joinpoint.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+
+ <xsd:simpleType name="pointcutRefType">
+ <xsd:annotation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="org.springframework.aop.Pointcut"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:union memberTypes="xsd:string"/>
+ </xsd:simpleType>
+
+</xsd:schema>
diff --git a/spring-aop/src/test/java/org/springframework/aop/aspectj/AspectJAdviceParameterNameDiscovererTests.java b/spring-aop/src/test/java/org/springframework/aop/aspectj/AspectJAdviceParameterNameDiscovererTests.java
index 5167b5d7..3a365963 100644
--- a/spring-aop/src/test/java/org/springframework/aop/aspectj/AspectJAdviceParameterNameDiscovererTests.java
+++ b/spring-aop/src/test/java/org/springframework/aop/aspectj/AspectJAdviceParameterNameDiscovererTests.java
@@ -307,7 +307,8 @@ public class AspectJAdviceParameterNameDiscovererTests {
try {
discoverer.getParameterNames(m);
fail("Expecting " + exceptionType.getName() + " with message '" + message + "'");
- } catch (RuntimeException expected) {
+ }
+ catch (RuntimeException expected) {
assertEquals("Expecting exception of type " + exceptionType.getName(),
exceptionType, expected.getClass());
assertEquals("Exception message does not match expected", message, expected.getMessage());
diff --git a/spring-aop/src/test/java/org/springframework/aop/aspectj/MethodInvocationProceedingJoinPointTests.java b/spring-aop/src/test/java/org/springframework/aop/aspectj/MethodInvocationProceedingJoinPointTests.java
index c4b1caac..f8c39e7b 100644
--- a/spring-aop/src/test/java/org/springframework/aop/aspectj/MethodInvocationProceedingJoinPointTests.java
+++ b/spring-aop/src/test/java/org/springframework/aop/aspectj/MethodInvocationProceedingJoinPointTests.java
@@ -215,7 +215,8 @@ public final class MethodInvocationProceedingJoinPointTests {
itb.setSpouse(new TestBean());
try {
itb.unreliableFileOperation();
- } catch (IOException ex) {
+ }
+ catch (IOException ex) {
// we don't realy care...
}
}
diff --git a/spring-aop/src/test/java/org/springframework/aop/aspectj/TrickyAspectJPointcutExpressionTests.java b/spring-aop/src/test/java/org/springframework/aop/aspectj/TrickyAspectJPointcutExpressionTests.java
index 22ef9db8..8a9d63a4 100644
--- a/spring-aop/src/test/java/org/springframework/aop/aspectj/TrickyAspectJPointcutExpressionTests.java
+++ b/spring-aop/src/test/java/org/springframework/aop/aspectj/TrickyAspectJPointcutExpressionTests.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package org.springframework.aop.aspectj;
import java.lang.annotation.Documented;
@@ -97,12 +113,14 @@ public class TrickyAspectJPointcutExpressionTests {
try {
bean.sayHello();
fail("Expected exception");
- } catch (TestException e) {
- assertEquals(message, e.getMessage());
+ }
+ catch (TestException ex) {
+ assertEquals(message, ex.getMessage());
}
assertEquals(1, logAdvice.getCountThrows());
}
+
public static class SimpleThrowawayClassLoader extends OverridingClassLoader {
/**
@@ -115,15 +133,16 @@ public class TrickyAspectJPointcutExpressionTests {
}
+
@SuppressWarnings("serial")
public static class TestException extends RuntimeException {
public TestException(String string) {
super(string);
}
-
}
+
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@@ -131,18 +150,23 @@ public class TrickyAspectJPointcutExpressionTests {
public static @interface Log {
}
+
public static interface TestService {
+
public String sayHello();
}
+
@Log
public static class TestServiceImpl implements TestService {
+
@Override
public String sayHello() {
throw new TestException("TestServiceImpl");
}
}
+
public class LogUserAdvice implements MethodBeforeAdvice, ThrowsAdvice {
private int countBefore = 0;
@@ -154,9 +178,9 @@ public class TrickyAspectJPointcutExpressionTests {
countBefore++;
}
- public void afterThrowing(Exception e) throws Throwable {
+ public void afterThrowing(Exception ex) throws Throwable {
countThrows++;
- throw e;
+ throw ex;
}
public int getCountBefore() {
@@ -171,7 +195,6 @@ public class TrickyAspectJPointcutExpressionTests {
countThrows = 0;
countBefore = 0;
}
-
}
}
diff --git a/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactoryTests.java b/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactoryTests.java
index 3f34ff59..554be1ae 100644
--- a/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactoryTests.java
+++ b/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactoryTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -694,6 +694,11 @@ public abstract class AbstractAspectJAdvisorFactoryTests {
}
@Override
+ public Object getAspectCreationMutex() {
+ return this;
+ }
+
+ @Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
diff --git a/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AspectProxyFactoryTests.java b/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AspectProxyFactoryTests.java
index 4113f169..0c4304c9 100644
--- a/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AspectProxyFactoryTests.java
+++ b/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AspectProxyFactoryTests.java
@@ -16,13 +16,13 @@
package org.springframework.aop.aspectj.annotation;
+import java.io.Serializable;
import java.util.Arrays;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
-import org.junit.Ignore;
import org.junit.Test;
import test.aop.PerThisAspect;
@@ -80,7 +80,18 @@ public class AspectProxyFactoryTests {
}
@Test
- @Ignore // InstantiationModelAwarePointcutAdvisorImpl not serializable yet
+ @SuppressWarnings("unchecked")
+ public void testSerializable() throws Exception {
+ AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new TestBean());
+ proxyFactory.addAspect(LoggingAspectOnVarargs.class);
+ ITestBean proxy = proxyFactory.getProxy();
+ assertTrue(proxy.doWithVarargs(MyEnum.A, MyOtherEnum.C));
+ ITestBean tb = (ITestBean) SerializationTestUtils.serializeAndDeserialize(proxy);
+ assertTrue(tb.doWithVarargs(MyEnum.A, MyOtherEnum.C));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
public void testWithInstance() throws Exception {
MultiplyReturnValue aspect = new MultiplyReturnValue();
int multiple = 3;
@@ -133,7 +144,8 @@ public class AspectProxyFactoryTests {
}
- public static class TestBean implements ITestBean {
+ @SuppressWarnings("serial")
+ public static class TestBean implements ITestBean, Serializable {
private int age;
@@ -171,7 +183,8 @@ public class AspectProxyFactoryTests {
@Aspect
- public static class LoggingAspectOnVarargs {
+ @SuppressWarnings("serial")
+ public static class LoggingAspectOnVarargs implements Serializable {
@Around("execution(* doWithVarargs(*))")
public Object doLog(ProceedingJoinPoint pjp) throws Throwable {
@@ -193,11 +206,9 @@ public class AspectProxyFactoryTests {
}
-/**
- * @author Rod Johnson
- */
@Aspect
-class MultiplyReturnValue {
+@SuppressWarnings("serial")
+class MultiplyReturnValue implements Serializable {
private int multiple = 2;
diff --git a/spring-aop/src/test/java/org/springframework/aop/framework/AopProxyUtilsTests.java b/spring-aop/src/test/java/org/springframework/aop/framework/AopProxyUtilsTests.java
index ad7ad9dc..42b12f66 100644
--- a/spring-aop/src/test/java/org/springframework/aop/framework/AopProxyUtilsTests.java
+++ b/spring-aop/src/test/java/org/springframework/aop/framework/AopProxyUtilsTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author 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,8 +16,6 @@
package org.springframework.aop.framework;
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.List;
@@ -34,7 +32,7 @@ import static org.junit.Assert.*;
* @author Rod Johnson
* @author Chris Beams
*/
-public final class AopProxyUtilsTests {
+public class AopProxyUtilsTests {
@Test
public void testCompleteProxiedInterfacesWorksWithNull() {
@@ -125,15 +123,10 @@ public final class AopProxyUtilsTests {
assertEquals(Comparable.class, userInterfaces[1]);
}
- @Test(expected=IllegalArgumentException.class)
+ @Test(expected = IllegalArgumentException.class)
public void testProxiedUserInterfacesWithNoInterface() {
Object proxy = Proxy.newProxyInstance(getClass().getClassLoader(), new Class[0],
- new InvocationHandler() {
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- return null;
- }
- });
+ (proxy1, method, args) -> null);
AopProxyUtils.proxiedUserInterfaces(proxy);
}
diff --git a/spring-aop/src/test/java/org/springframework/aop/framework/ProxyFactoryTests.java b/spring-aop/src/test/java/org/springframework/aop/framework/ProxyFactoryTests.java
index 7b509ba6..b01b9b80 100644
--- a/spring-aop/src/test/java/org/springframework/aop/framework/ProxyFactoryTests.java
+++ b/spring-aop/src/test/java/org/springframework/aop/framework/ProxyFactoryTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
package org.springframework.aop.framework;
+import java.util.ArrayList;
+import java.util.List;
import javax.accessibility.Accessible;
import javax.swing.JFrame;
import javax.swing.RootPaneContainer;
@@ -31,6 +33,8 @@ import org.springframework.aop.support.AopUtils;
import org.springframework.aop.support.DefaultIntroductionAdvisor;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.DelegatingIntroductionInterceptor;
+import org.springframework.core.annotation.AnnotationAwareOrderComparator;
+import org.springframework.core.annotation.Order;
import org.springframework.tests.TimeStamped;
import org.springframework.tests.aop.advice.CountingBeforeAdvice;
import org.springframework.tests.aop.interceptor.NopInterceptor;
@@ -49,7 +53,7 @@ import static org.junit.Assert.*;
* @author Chris Beams
* @since 14.05.2003
*/
-public final class ProxyFactoryTests {
+public class ProxyFactoryTests {
@Test
public void testIndexOfMethods() {
@@ -337,6 +341,34 @@ public final class ProxyFactoryTests {
assertTrue(proxy instanceof Accessible);
}
+ @Test
+ public void testInterfaceProxiesCanBeOrderedThroughAnnotations() {
+ Object proxy1 = new ProxyFactory(new A()).getProxy();
+ Object proxy2 = new ProxyFactory(new B()).getProxy();
+ List<Object> list = new ArrayList<Object>(2);
+ list.add(proxy1);
+ list.add(proxy2);
+ AnnotationAwareOrderComparator.sort(list);
+ assertSame(proxy2, list.get(0));
+ assertSame(proxy1, list.get(1));
+ }
+
+ @Test
+ public void testTargetClassProxiesCanBeOrderedThroughAnnotations() {
+ ProxyFactory pf1 = new ProxyFactory(new A());
+ pf1.setProxyTargetClass(true);
+ ProxyFactory pf2 = new ProxyFactory(new B());
+ pf2.setProxyTargetClass(true);
+ Object proxy1 = pf1.getProxy();
+ Object proxy2 = pf2.getProxy();
+ List<Object> list = new ArrayList<Object>(2);
+ list.add(proxy1);
+ list.add(proxy2);
+ AnnotationAwareOrderComparator.sort(list);
+ assertSame(proxy2, list.get(0));
+ assertSame(proxy1, list.get(1));
+ }
+
@SuppressWarnings("serial")
private static class TimestampIntroductionInterceptor extends DelegatingIntroductionInterceptor
@@ -361,4 +393,22 @@ public final class ProxyFactoryTests {
}
}
+
+ @Order(2)
+ public static class A implements Runnable {
+
+ @Override
+ public void run() {
+ }
+ }
+
+
+ @Order(1)
+ public static class B implements Runnable{
+
+ @Override
+ public void run() {
+ }
+ }
+
}
diff --git a/spring-aop/src/test/java/org/springframework/aop/interceptor/DebugInterceptorTests.java b/spring-aop/src/test/java/org/springframework/aop/interceptor/DebugInterceptorTests.java
index 5096fe88..a8240839 100644
--- a/spring-aop/src/test/java/org/springframework/aop/interceptor/DebugInterceptorTests.java
+++ b/spring-aop/src/test/java/org/springframework/aop/interceptor/DebugInterceptorTests.java
@@ -61,7 +61,8 @@ public final class DebugInterceptorTests {
try {
interceptor.invoke(methodInvocation);
fail("Must have propagated the IllegalArgumentException.");
- } catch (IllegalArgumentException expected) {
+ }
+ catch (IllegalArgumentException expected) {
}
checkCallCountTotal(interceptor);
diff --git a/spring-aop/src/test/java/org/springframework/aop/interceptor/SimpleTraceInterceptorTests.java b/spring-aop/src/test/java/org/springframework/aop/interceptor/SimpleTraceInterceptorTests.java
index 5bdeb414..3b811b24 100644
--- a/spring-aop/src/test/java/org/springframework/aop/interceptor/SimpleTraceInterceptorTests.java
+++ b/spring-aop/src/test/java/org/springframework/aop/interceptor/SimpleTraceInterceptorTests.java
@@ -60,7 +60,8 @@ public final class SimpleTraceInterceptorTests {
try {
interceptor.invokeUnderTrace(mi, log);
fail("Must have propagated the IllegalArgumentException.");
- } catch (IllegalArgumentException expected) {
+ }
+ catch (IllegalArgumentException expected) {
}
verify(log).trace(anyString());
diff --git a/spring-aop/src/test/java/org/springframework/aop/target/ThreadLocalTargetSourceTests.java b/spring-aop/src/test/java/org/springframework/aop/target/ThreadLocalTargetSourceTests.java
index e805401b..a7890cde 100644
--- a/spring-aop/src/test/java/org/springframework/aop/target/ThreadLocalTargetSourceTests.java
+++ b/spring-aop/src/test/java/org/springframework/aop/target/ThreadLocalTargetSourceTests.java
@@ -154,7 +154,8 @@ public class ThreadLocalTargetSourceTests {
// try second time
try {
source.getTarget();
- } catch(NullPointerException ex) {
+ }
+ catch (NullPointerException ex) {
fail("Should not throw NPE");
}
}
diff --git a/spring-aspects/src/main/java/org/springframework/cache/aspectj/AbstractCacheAspect.aj b/spring-aspects/src/main/java/org/springframework/cache/aspectj/AbstractCacheAspect.aj
index 20f2adb0..3b4b3820 100644
--- a/spring-aspects/src/main/java/org/springframework/cache/aspectj/AbstractCacheAspect.aj
+++ b/spring-aspects/src/main/java/org/springframework/cache/aspectj/AbstractCacheAspect.aj
@@ -67,11 +67,22 @@ public abstract aspect AbstractCacheAspect extends CacheAspectSupport implements
CacheOperationInvoker aspectJInvoker = new CacheOperationInvoker() {
public Object invoke() {
- return proceed(cachedObject);
+ try {
+ return proceed(cachedObject);
+ }
+ catch (Throwable ex) {
+ throw new ThrowableWrapper(ex);
+ }
}
};
- return execute(aspectJInvoker, thisJoinPoint.getTarget(), method, thisJoinPoint.getArgs());
+ try {
+ return execute(aspectJInvoker, thisJoinPoint.getTarget(), method, thisJoinPoint.getArgs());
+ }
+ catch (CacheOperationInvoker.ThrowableWrapper th) {
+ AnyThrow.throwUnchecked(th.getOriginal());
+ return null; // never reached
+ }
}
/**
diff --git a/spring-aspects/src/main/java/org/springframework/cache/aspectj/AnyThrow.java b/spring-aspects/src/main/java/org/springframework/cache/aspectj/AnyThrow.java
new file mode 100644
index 00000000..d391c0a6
--- /dev/null
+++ b/spring-aspects/src/main/java/org/springframework/cache/aspectj/AnyThrow.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2002-2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cache.aspectj;
+
+/**
+ * Utility to trick the compiler to throw a valid checked
+ * exceptions within the interceptor.
+ *
+ * @author Stephane Nicoll
+ */
+class AnyThrow {
+
+ static void throwUnchecked(Throwable e) {
+ AnyThrow.<RuntimeException>throwAny(e);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <E extends Throwable> void throwAny(Throwable e) throws E {
+ throw (E) e;
+ }
+}
diff --git a/spring-aspects/src/main/java/org/springframework/cache/aspectj/JCacheCacheAspect.aj b/spring-aspects/src/main/java/org/springframework/cache/aspectj/JCacheCacheAspect.aj
index 3aeeefbd..4587fc00 100644
--- a/spring-aspects/src/main/java/org/springframework/cache/aspectj/JCacheCacheAspect.aj
+++ b/spring-aspects/src/main/java/org/springframework/cache/aspectj/JCacheCacheAspect.aj
@@ -109,16 +109,4 @@ public aspect JCacheCacheAspect extends JCacheAspectSupport {
execution(@CacheRemoveAll * *(..));
- private static class AnyThrow {
-
- private static void throwUnchecked(Throwable e) {
- AnyThrow.<RuntimeException>throwAny(e);
- }
-
- @SuppressWarnings("unchecked")
- private static <E extends Throwable> void throwAny(Throwable e) throws E {
- throw (E)e;
- }
- }
-
}
diff --git a/spring-aspects/src/main/java/org/springframework/mock/staticmock/AbstractMethodMockingControl.aj b/spring-aspects/src/main/java/org/springframework/mock/staticmock/AbstractMethodMockingControl.aj
index fe378642..f77838e3 100644
--- a/spring-aspects/src/main/java/org/springframework/mock/staticmock/AbstractMethodMockingControl.aj
+++ b/spring-aspects/src/main/java/org/springframework/mock/staticmock/AbstractMethodMockingControl.aj
@@ -36,7 +36,9 @@ import org.springframework.util.ObjectUtils;
* @author Rod Johnson
* @author Ramnivas Laddad
* @author Sam Brannen
+ * @deprecated as of Spring 4.3, in favor of a custom aspect for such purposes
*/
+@Deprecated
public abstract aspect AbstractMethodMockingControl percflow(mockStaticsTestMethod()) {
private final Expectations expectations = new Expectations();
diff --git a/spring-aspects/src/main/java/org/springframework/mock/staticmock/AnnotationDrivenStaticEntityMockingControl.aj b/spring-aspects/src/main/java/org/springframework/mock/staticmock/AnnotationDrivenStaticEntityMockingControl.aj
index b2979303..d67744df 100644
--- a/spring-aspects/src/main/java/org/springframework/mock/staticmock/AnnotationDrivenStaticEntityMockingControl.aj
+++ b/spring-aspects/src/main/java/org/springframework/mock/staticmock/AnnotationDrivenStaticEntityMockingControl.aj
@@ -59,7 +59,9 @@ import org.aspectj.lang.annotation.SuppressAjWarnings;
* @author Ramnivas Laddad
* @author Sam Brannen
* @see MockStaticEntityMethods
+ * @deprecated as of Spring 4.3, in favor of a custom aspect for such purposes
*/
+@Deprecated
@RequiredTypes("javax.persistence.Entity")
public aspect AnnotationDrivenStaticEntityMockingControl extends AbstractMethodMockingControl {
diff --git a/spring-aspects/src/main/java/org/springframework/mock/staticmock/MockStaticEntityMethods.java b/spring-aspects/src/main/java/org/springframework/mock/staticmock/MockStaticEntityMethods.java
index f68b8064..3b2c128c 100644
--- a/spring-aspects/src/main/java/org/springframework/mock/staticmock/MockStaticEntityMethods.java
+++ b/spring-aspects/src/main/java/org/springframework/mock/staticmock/MockStaticEntityMethods.java
@@ -29,7 +29,9 @@ import java.lang.annotation.Target;
*
* @author Rod Johnson
* @author Sam Brannen
+ * @deprecated as of Spring 4.3, in favor of a custom aspect for such purposes
*/
+@Deprecated
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MockStaticEntityMethods {
diff --git a/spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AnnotationAsyncExecutionAspect.aj b/spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AnnotationAsyncExecutionAspect.aj
index e1a1fd97..2b22ec90 100644
--- a/spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AnnotationAsyncExecutionAspect.aj
+++ b/spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AnnotationAsyncExecutionAspect.aj
@@ -19,7 +19,7 @@ package org.springframework.scheduling.aspectj;
import java.lang.reflect.Method;
import java.util.concurrent.Future;
-import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.scheduling.annotation.Async;
/**
@@ -68,9 +68,9 @@ public aspect AnnotationAsyncExecutionAspect extends AbstractAsyncExecutionAspec
protected String getExecutorQualifier(Method method) {
// Maintainer's note: changes made here should also be made in
// AnnotationAsyncExecutionInterceptor#getExecutorQualifier
- Async async = AnnotationUtils.findAnnotation(method, Async.class);
+ Async async = AnnotatedElementUtils.findMergedAnnotation(method, Async.class);
if (async == null) {
- async = AnnotationUtils.findAnnotation(method.getDeclaringClass(), Async.class);
+ async = AnnotatedElementUtils.findMergedAnnotation(method.getDeclaringClass(), Async.class);
}
return (async != null ? async.value() : null);
}
diff --git a/spring-aspects/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java b/spring-aspects/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java
index cebb6788..a640b15c 100644
--- a/spring-aspects/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java
+++ b/spring-aspects/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java
@@ -27,6 +27,7 @@ import org.springframework.cache.annotation.Caching;
/**
* @author Costin Leau
* @author Phillip Webb
+ * @author Stephane Nicoll
*/
@Cacheable("testCache")
public class AnnotatedClassCacheableService implements CacheableService<Object> {
@@ -45,11 +46,28 @@ public class AnnotatedClassCacheableService implements CacheableService<Object>
}
@Override
+ @Cacheable(cacheNames = "testCache", sync = true)
+ public Object cacheSync(Object arg1) {
+ return counter.getAndIncrement();
+ }
+
+ @Override
+ @Cacheable(cacheNames = "testCache", sync = true)
+ public Object cacheSyncNull(Object arg1) {
+ return null;
+ }
+
+ @Override
public Object conditional(int field) {
return null;
}
@Override
+ public Object conditionalSync(int field) {
+ return null;
+ }
+
+ @Override
public Object unless(int arg) {
return arg;
}
@@ -168,6 +186,18 @@ public class AnnotatedClassCacheableService implements CacheableService<Object>
throw new UnsupportedOperationException(arg1.toString());
}
+ @Override
+ @Cacheable(cacheNames = "testCache", sync = true)
+ public Object throwCheckedSync(Object arg1) throws Exception {
+ throw new IOException(arg1.toString());
+ }
+
+ @Override
+ @Cacheable(cacheNames = "testCache", sync = true)
+ public Object throwUncheckedSync(Object arg1) {
+ throw new UnsupportedOperationException(arg1.toString());
+ }
+
// multi annotations
@Override
diff --git a/spring-aspects/src/test/java/org/springframework/cache/config/CacheableService.java b/spring-aspects/src/test/java/org/springframework/cache/config/CacheableService.java
index 17d299d9..a862f2ac 100644
--- a/spring-aspects/src/test/java/org/springframework/cache/config/CacheableService.java
+++ b/spring-aspects/src/test/java/org/springframework/cache/config/CacheableService.java
@@ -21,6 +21,7 @@ package org.springframework.cache.config;
*
* @author Costin Leau
* @author Phillip Webb
+ * @author Stephane Nicoll
*/
public interface CacheableService<T> {
@@ -28,6 +29,10 @@ public interface CacheableService<T> {
T cacheNull(Object arg1);
+ T cacheSync(Object arg1);
+
+ T cacheSyncNull(Object arg1);
+
void invalidate(Object arg1);
void evictEarly(Object arg1);
@@ -42,6 +47,8 @@ public interface CacheableService<T> {
T conditional(int field);
+ T conditionalSync(int field);
+
T unless(int arg);
T key(Object arg1, Object arg2);
@@ -72,6 +79,10 @@ public interface CacheableService<T> {
T throwUnchecked(Object arg1);
+ T throwCheckedSync(Object arg1) throws Exception;
+
+ T throwUncheckedSync(Object arg1);
+
// multi annotations
T multiCache(Object arg1);
diff --git a/spring-aspects/src/test/java/org/springframework/cache/config/DefaultCacheableService.java b/spring-aspects/src/test/java/org/springframework/cache/config/DefaultCacheableService.java
index d539075e..a76d899b 100644
--- a/spring-aspects/src/test/java/org/springframework/cache/config/DefaultCacheableService.java
+++ b/spring-aspects/src/test/java/org/springframework/cache/config/DefaultCacheableService.java
@@ -29,6 +29,7 @@ import org.springframework.cache.annotation.Caching;
*
* @author Costin Leau
* @author Phillip Webb
+ * @author Stephane Nicoll
*/
public class DefaultCacheableService implements CacheableService<Long> {
@@ -48,6 +49,18 @@ public class DefaultCacheableService implements CacheableService<Long> {
}
@Override
+ @Cacheable(cacheNames = "testCache", sync = true)
+ public Long cacheSync(Object arg1) {
+ return counter.getAndIncrement();
+ }
+
+ @Override
+ @Cacheable(cacheNames = "testCache", sync = true)
+ public Long cacheSyncNull(Object arg1) {
+ return null;
+ }
+
+ @Override
@CacheEvict("testCache")
public void invalidate(Object arg1) {
}
@@ -81,12 +94,18 @@ public class DefaultCacheableService implements CacheableService<Long> {
}
@Override
- @Cacheable(cacheNames = "testCache", condition = "#classField == 3")
+ @Cacheable(cacheNames = "testCache", condition = "#p0 == 3")
public Long conditional(int classField) {
return counter.getAndIncrement();
}
@Override
+ @Cacheable(cacheNames = "testCache", sync = true, condition = "#p0 == 3")
+ public Long conditionalSync(int field) {
+ return counter.getAndIncrement();
+ }
+
+ @Override
@Cacheable(cacheNames = "testCache", unless = "#result > 10")
public Long unless(int arg) {
return (long) arg;
@@ -99,7 +118,7 @@ public class DefaultCacheableService implements CacheableService<Long> {
}
@Override
- @Cacheable("testCache")
+ @Cacheable(cacheNames = "testCache")
public Long varArgsKey(Object... args) {
return counter.getAndIncrement();
}
@@ -176,6 +195,18 @@ public class DefaultCacheableService implements CacheableService<Long> {
throw new UnsupportedOperationException(arg1.toString());
}
+ @Override
+ @Cacheable(cacheNames = "testCache", sync = true)
+ public Long throwCheckedSync(Object arg1) throws Exception {
+ throw new IOException(arg1.toString());
+ }
+
+ @Override
+ @Cacheable(cacheNames = "testCache", sync = true)
+ public Long throwUncheckedSync(Object arg1) {
+ throw new UnsupportedOperationException(arg1.toString());
+ }
+
// multi annotations
@Override
diff --git a/spring-aspects/src/test/java/org/springframework/scheduling/aspectj/AnnotationAsyncExecutionAspectTests.java b/spring-aspects/src/test/java/org/springframework/scheduling/aspectj/AnnotationAsyncExecutionAspectTests.java
index d1715d7c..1d7d1af4 100644
--- a/spring-aspects/src/test/java/org/springframework/scheduling/aspectj/AnnotationAsyncExecutionAspectTests.java
+++ b/spring-aspects/src/test/java/org/springframework/scheduling/aspectj/AnnotationAsyncExecutionAspectTests.java
@@ -210,7 +210,8 @@ public class AnnotationAsyncExecutionAspectTests {
public synchronized void waitForCompletion() {
try {
wait(WAIT_TIME);
- } catch (InterruptedException e) {
+ }
+ catch (InterruptedException ex) {
fail("Didn't finish the async job in " + WAIT_TIME + " milliseconds");
}
}
diff --git a/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java b/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java
index cf3ebd9a..f3e3bd14 100644
--- a/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java
+++ b/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java
@@ -90,11 +90,11 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
private int autoGrowCollectionLimit = Integer.MAX_VALUE;
- private Object wrappedObject;
+ Object wrappedObject;
private String nestedPath = "";
- private Object rootObject;
+ Object rootObject;
/**
* Map with cached nested Accessors: nested path -> Accessor instance.
@@ -914,11 +914,9 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
return BeanUtils.instantiate(type);
}
}
- catch (Exception ex) {
- // TODO: Root cause exception context is lost here; just exception message preserved.
- // Should we throw another exception type that preserves context instead?
+ catch (Throwable ex) {
throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + name,
- "Could not instantiate property type [" + type.getName() + "] to auto-grow nested property path: " + ex);
+ "Could not instantiate property type [" + type.getName() + "] to auto-grow nested property path", ex);
}
}
diff --git a/spring-beans/src/main/java/org/springframework/beans/AbstractPropertyAccessor.java b/spring-beans/src/main/java/org/springframework/beans/AbstractPropertyAccessor.java
index 07871217..216e5a4a 100644
--- a/spring-beans/src/main/java/org/springframework/beans/AbstractPropertyAccessor.java
+++ b/spring-beans/src/main/java/org/springframework/beans/AbstractPropertyAccessor.java
@@ -148,7 +148,7 @@ public abstract class AbstractPropertyAccessor extends TypeConverterSupport impl
* @throws InvalidPropertyException if there is no such property or
* if the property isn't writable
* @throws PropertyAccessException if the property was valid but the
- * accessor method failed or a type mismatch occured
+ * accessor method failed or a type mismatch occurred
*/
@Override
public abstract void setPropertyValue(String propertyName, Object value) throws BeansException;
diff --git a/spring-beans/src/main/java/org/springframework/beans/BeanInfoFactory.java b/spring-beans/src/main/java/org/springframework/beans/BeanInfoFactory.java
index 1906fbfd..4c5fd27c 100644
--- a/spring-beans/src/main/java/org/springframework/beans/BeanInfoFactory.java
+++ b/spring-beans/src/main/java/org/springframework/beans/BeanInfoFactory.java
@@ -24,7 +24,7 @@ import java.beans.IntrospectionException;
* Can be used to plug in custom bean property resolution strategies (e.g. for other
* languages on the JVM) or more efficient {@link BeanInfo} retrieval algorithms.
*
- * <p>BeanInfoFactories are are instantiated by the {@link CachedIntrospectionResults},
+ * <p>BeanInfoFactories are instantiated by the {@link CachedIntrospectionResults},
* by using the {@link org.springframework.core.io.support.SpringFactoriesLoader}
* utility class.
*
diff --git a/spring-beans/src/main/java/org/springframework/beans/BeanInstantiationException.java b/spring-beans/src/main/java/org/springframework/beans/BeanInstantiationException.java
index 09190f6f..d8f7d246 100644
--- a/spring-beans/src/main/java/org/springframework/beans/BeanInstantiationException.java
+++ b/spring-beans/src/main/java/org/springframework/beans/BeanInstantiationException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author 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,9 @@
package org.springframework.beans;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
/**
* Exception thrown when instantiation of a bean failed.
* Carries the offending bean class.
@@ -28,6 +31,10 @@ public class BeanInstantiationException extends FatalBeanException {
private Class<?> beanClass;
+ private Constructor<?> constructor;
+
+ private Method constructingMethod;
+
/**
* Create a new BeanInstantiationException.
@@ -49,12 +56,60 @@ public class BeanInstantiationException extends FatalBeanException {
this.beanClass = beanClass;
}
+ /**
+ * Create a new BeanInstantiationException.
+ * @param constructor the offending constructor
+ * @param msg the detail message
+ * @param cause the root cause
+ * @since 4.3
+ */
+ public BeanInstantiationException(Constructor<?> constructor, String msg, Throwable cause) {
+ super("Failed to instantiate [" + constructor.getDeclaringClass().getName() + "]: " + msg, cause);
+ this.beanClass = constructor.getDeclaringClass();
+ this.constructor = constructor;
+ }
/**
- * Return the offending bean class.
+ * Create a new BeanInstantiationException.
+ * @param constructingMethod the delegate for bean construction purposes
+ * (typically, but not necessarily, a static factory method)
+ * @param msg the detail message
+ * @param cause the root cause
+ * @since 4.3
+ */
+ public BeanInstantiationException(Method constructingMethod, String msg, Throwable cause) {
+ super("Failed to instantiate [" + constructingMethod.getReturnType().getName() + "]: " + msg, cause);
+ this.beanClass = constructingMethod.getReturnType();
+ this.constructingMethod = constructingMethod;
+ }
+
+
+ /**
+ * Return the offending bean class (never {@code null}).
+ * @return the class that was to be instantiated
*/
public Class<?> getBeanClass() {
return this.beanClass;
}
+ /**
+ * Return the offending constructor, if known.
+ * @return the constructor in use, or {@code null} in case of a
+ * factory method or in case of default instantiation
+ * @since 4.3
+ */
+ public Constructor<?> getConstructor() {
+ return this.constructor;
+ }
+
+ /**
+ * Return the delegate for bean construction purposes, if known.
+ * @return the method in use (typically a static factory method),
+ * or {@code null} in case of constructor-based instantiation
+ * @since 4.3
+ */
+ public Method getConstructingMethod() {
+ return this.constructingMethod;
+ }
+
}
diff --git a/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java b/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java
index 5c735f16..50c04c3b 100644
--- a/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java
+++ b/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java
@@ -63,11 +63,10 @@ public abstract class BeanUtils {
/**
* Convenience method to instantiate a class using its no-arg constructor.
- * As this method doesn't try to load classes by name, it should avoid
- * class-loading issues.
* @param clazz class to instantiate
* @return the new instance
* @throws BeanInstantiationException if the bean cannot be instantiated
+ * @see Class#newInstance()
*/
public static <T> T instantiate(Class<T> clazz) throws BeanInstantiationException {
Assert.notNull(clazz, "Class must not be null");
@@ -87,13 +86,12 @@ public abstract class BeanUtils {
/**
* Instantiate a class using its no-arg constructor.
- * As this method doesn't try to load classes by name, it should avoid
- * class-loading issues.
* <p>Note that this method tries to set the constructor accessible
* if given a non-accessible (that is, non-public) constructor.
* @param clazz class to instantiate
* @return the new instance
* @throws BeanInstantiationException if the bean cannot be instantiated
+ * @see Constructor#newInstance
*/
public static <T> T instantiateClass(Class<T> clazz) throws BeanInstantiationException {
Assert.notNull(clazz, "Class must not be null");
@@ -110,18 +108,16 @@ public abstract class BeanUtils {
/**
* Instantiate a class using its no-arg constructor and return the new instance
- * as the the specified assignable type.
- * <p>Useful in cases where
- * the type of the class to instantiate (clazz) is not available, but the type
- * desired (assignableTo) is known.
- * <p>As this method doesn't try to load classes by name, it should avoid
- * class-loading issues.
- * <p>Note that this method tries to set the constructor accessible
- * if given a non-accessible (that is, non-public) constructor.
+ * as the specified assignable type.
+ * <p>Useful in cases where the type of the class to instantiate (clazz) is not
+ * available, but the type desired (assignableTo) is known.
+ * <p>Note that this method tries to set the constructor accessible if given a
+ * non-accessible (that is, non-public) constructor.
* @param clazz class to instantiate
* @param assignableTo type that clazz must be assignableTo
* @return the new instance
* @throws BeanInstantiationException if the bean cannot be instantiated
+ * @see Constructor#newInstance
*/
@SuppressWarnings("unchecked")
public static <T> T instantiateClass(Class<?> clazz, Class<T> assignableTo) throws BeanInstantiationException {
@@ -131,14 +127,13 @@ public abstract class BeanUtils {
/**
* Convenience method to instantiate a class using the given constructor.
- * As this method doesn't try to load classes by name, it should avoid
- * class-loading issues.
- * <p>Note that this method tries to set the constructor accessible
- * if given a non-accessible (that is, non-public) constructor.
+ * <p>Note that this method tries to set the constructor accessible if given a
+ * non-accessible (that is, non-public) constructor.
* @param ctor the constructor to instantiate
* @param args the constructor arguments to apply
* @return the new instance
* @throws BeanInstantiationException if the bean cannot be instantiated
+ * @see Constructor#newInstance
*/
public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {
Assert.notNull(ctor, "Constructor must not be null");
@@ -147,20 +142,16 @@ public abstract class BeanUtils {
return ctor.newInstance(args);
}
catch (InstantiationException ex) {
- throw new BeanInstantiationException(ctor.getDeclaringClass(),
- "Is it an abstract class?", ex);
+ throw new BeanInstantiationException(ctor, "Is it an abstract class?", ex);
}
catch (IllegalAccessException ex) {
- throw new BeanInstantiationException(ctor.getDeclaringClass(),
- "Is the constructor accessible?", ex);
+ throw new BeanInstantiationException(ctor, "Is the constructor accessible?", ex);
}
catch (IllegalArgumentException ex) {
- throw new BeanInstantiationException(ctor.getDeclaringClass(),
- "Illegal arguments for constructor", ex);
+ throw new BeanInstantiationException(ctor, "Illegal arguments for constructor", ex);
}
catch (InvocationTargetException ex) {
- throw new BeanInstantiationException(ctor.getDeclaringClass(),
- "Constructor threw exception", ex.getTargetException());
+ throw new BeanInstantiationException(ctor, "Constructor threw exception", ex.getTargetException());
}
}
diff --git a/spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java b/spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java
index 6615661d..4d10dc0c 100644
--- a/spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java
+++ b/spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java
@@ -133,6 +133,19 @@ public class BeanWrapperImpl extends AbstractNestablePropertyAccessor implements
}
+ /**
+ * Set a bean instance to hold, without any unwrapping of {@link java.util.Optional}.
+ * @param object the actual target object
+ * @since 4.3
+ * @see #setWrappedInstance(Object)
+ */
+ public void setBeanInstance(Object object) {
+ this.wrappedObject = object;
+ this.rootObject = object;
+ this.typeConverterDelegate = new TypeConverterDelegate(this, this.wrappedObject);
+ setIntrospectionClass(object.getClass());
+ }
+
@Override
public void setWrappedInstance(Object object, String nestedPath, Object rootObject) {
super.setWrappedInstance(object, nestedPath, rootObject);
diff --git a/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java b/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java
index 41c2f4c5..2efe9dab 100644
--- a/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java
+++ b/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -303,6 +303,24 @@ public class CachedIntrospectionResults {
this.propertyDescriptorCache.put(pd.getName(), pd);
}
+ // Explicitly check implemented interfaces for setter/getter methods as well,
+ // in particular for Java 8 default methods...
+ Class<?> clazz = beanClass;
+ while (clazz != null) {
+ Class<?>[] ifcs = clazz.getInterfaces();
+ for (Class<?> ifc : ifcs) {
+ BeanInfo ifcInfo = Introspector.getBeanInfo(ifc, Introspector.IGNORE_ALL_BEANINFO);
+ PropertyDescriptor[] ifcPds = ifcInfo.getPropertyDescriptors();
+ for (PropertyDescriptor pd : ifcPds) {
+ if (!this.propertyDescriptorCache.containsKey(pd.getName())) {
+ pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd);
+ this.propertyDescriptorCache.put(pd.getName(), pd);
+ }
+ }
+ }
+ clazz = clazz.getSuperclass();
+ }
+
this.typeDescriptorCache = new ConcurrentReferenceHashMap<PropertyDescriptor, TypeDescriptor>();
}
catch (IntrospectionException ex) {
diff --git a/spring-beans/src/main/java/org/springframework/beans/NullValueInNestedPathException.java b/spring-beans/src/main/java/org/springframework/beans/NullValueInNestedPathException.java
index e01fafbf..e15885bc 100644
--- a/spring-beans/src/main/java/org/springframework/beans/NullValueInNestedPathException.java
+++ b/spring-beans/src/main/java/org/springframework/beans/NullValueInNestedPathException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@ package org.springframework.beans;
* spouse property of the target object has a null value.
*
* @author Rod Johnson
+ * @author Juergen Hoeller
*/
@SuppressWarnings("serial")
public class NullValueInNestedPathException extends InvalidPropertyException {
@@ -47,4 +48,16 @@ public class NullValueInNestedPathException extends InvalidPropertyException {
super(beanClass, propertyName, msg);
}
+ /**
+ * Create a new NullValueInNestedPathException.
+ * @param beanClass the offending bean class
+ * @param propertyName the offending property
+ * @param msg the detail message
+ * @param cause the root cause
+ * @since 4.3.2
+ */
+ public NullValueInNestedPathException(Class<?> beanClass, String propertyName, String msg, Throwable cause) {
+ super(beanClass, propertyName, msg, cause);
+ }
+
}
diff --git a/spring-beans/src/main/java/org/springframework/beans/PropertyAccessor.java b/spring-beans/src/main/java/org/springframework/beans/PropertyAccessor.java
index 00068ea4..c7faf355 100644
--- a/spring-beans/src/main/java/org/springframework/beans/PropertyAccessor.java
+++ b/spring-beans/src/main/java/org/springframework/beans/PropertyAccessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -120,7 +120,7 @@ public interface PropertyAccessor {
* @throws InvalidPropertyException if there is no such property or
* if the property isn't writable
* @throws PropertyAccessException if the property was valid but the
- * accessor method failed or a type mismatch occured
+ * accessor method failed or a type mismatch occurred
*/
void setPropertyValue(String propertyName, Object value) throws BeansException;
@@ -130,7 +130,7 @@ public interface PropertyAccessor {
* @throws InvalidPropertyException if there is no such property or
* if the property isn't writable
* @throws PropertyAccessException if the property was valid but the
- * accessor method failed or a type mismatch occured
+ * accessor method failed or a type mismatch occurred
*/
void setPropertyValue(PropertyValue pv) throws BeansException;
@@ -144,7 +144,7 @@ public interface PropertyAccessor {
* @throws InvalidPropertyException if there is no such property or
* if the property isn't writable
* @throws PropertyBatchUpdateException if one or more PropertyAccessExceptions
- * occured for specific properties during the batch update. This exception bundles
+ * occurred for specific properties during the batch update. This exception bundles
* all individual PropertyAccessExceptions. All other properties will have been
* successfully updated.
*/
@@ -164,7 +164,7 @@ public interface PropertyAccessor {
* @throws InvalidPropertyException if there is no such property or
* if the property isn't writable
* @throws PropertyBatchUpdateException if one or more PropertyAccessExceptions
- * occured for specific properties during the batch update. This exception bundles
+ * occurred for specific properties during the batch update. This exception bundles
* all individual PropertyAccessExceptions. All other properties will have been
* successfully updated.
* @see #setPropertyValues(PropertyValues, boolean, boolean)
@@ -185,7 +185,7 @@ public interface PropertyAccessor {
* @throws InvalidPropertyException if there is no such property or
* if the property isn't writable
* @throws PropertyBatchUpdateException if one or more PropertyAccessExceptions
- * occured for specific properties during the batch update. This exception bundles
+ * occurred for specific properties during the batch update. This exception bundles
* all individual PropertyAccessExceptions. All other properties will have been
* successfully updated.
* @see #setPropertyValues(PropertyValues, boolean, boolean)
@@ -208,7 +208,7 @@ public interface PropertyAccessor {
* @throws InvalidPropertyException if there is no such property or
* if the property isn't writable
* @throws PropertyBatchUpdateException if one or more PropertyAccessExceptions
- * occured for specific properties during the batch update. This exception bundles
+ * occurred for specific properties during the batch update. This exception bundles
* all individual PropertyAccessExceptions. All other properties will have been
* successfully updated.
*/
diff --git a/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java b/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java
index f42d6c33..71825c38 100644
--- a/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java
+++ b/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java
@@ -59,6 +59,7 @@ import org.springframework.beans.propertyeditors.FileEditor;
import org.springframework.beans.propertyeditors.InputSourceEditor;
import org.springframework.beans.propertyeditors.InputStreamEditor;
import org.springframework.beans.propertyeditors.LocaleEditor;
+import org.springframework.beans.propertyeditors.PathEditor;
import org.springframework.beans.propertyeditors.PatternEditor;
import org.springframework.beans.propertyeditors.PropertiesEditor;
import org.springframework.beans.propertyeditors.ReaderEditor;
@@ -87,11 +88,21 @@ import org.springframework.util.ClassUtils;
*/
public class PropertyEditorRegistrySupport implements PropertyEditorRegistry {
+ private static Class<?> pathClass;
+
private static Class<?> zoneIdClass;
static {
+ ClassLoader cl = PropertyEditorRegistrySupport.class.getClassLoader();
+ try {
+ pathClass = ClassUtils.forName("java.nio.file.Path", cl);
+ }
+ catch (ClassNotFoundException ex) {
+ // Java 7 Path class not available
+ pathClass = null;
+ }
try {
- zoneIdClass = ClassUtils.forName("java.time.ZoneId", PropertyEditorRegistrySupport.class.getClassLoader());
+ zoneIdClass = ClassUtils.forName("java.time.ZoneId", cl);
}
catch (ClassNotFoundException ex) {
// Java 8 ZoneId class not available
@@ -211,6 +222,9 @@ public class PropertyEditorRegistrySupport implements PropertyEditorRegistry {
this.defaultEditors.put(InputStream.class, new InputStreamEditor());
this.defaultEditors.put(InputSource.class, new InputSourceEditor());
this.defaultEditors.put(Locale.class, new LocaleEditor());
+ if (pathClass != null) {
+ this.defaultEditors.put(pathClass, new PathEditor());
+ }
this.defaultEditors.put(Pattern.class, new PatternEditor());
this.defaultEditors.put(Properties.class, new PropertiesEditor());
this.defaultEditors.put(Reader.class, new ReaderEditor());
diff --git a/spring-beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java b/spring-beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java
index dd63e8e9..908bfeaf 100644
--- a/spring-beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java
+++ b/spring-beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java
@@ -186,8 +186,11 @@ class TypeConverterDelegate {
if (typeDescriptor != null && requiredType != null && Collection.class.isAssignableFrom(requiredType) &&
convertedValue instanceof String) {
TypeDescriptor elementTypeDesc = typeDescriptor.getElementTypeDescriptor();
- if (elementTypeDesc != null && Enum.class.isAssignableFrom(elementTypeDesc.getType())) {
- convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
+ if (elementTypeDesc != null) {
+ Class<?> elementType = elementTypeDesc.getType();
+ if (Class.class == elementType || Enum.class.isAssignableFrom(elementType)) {
+ convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
+ }
}
}
if (editor == null) {
@@ -266,7 +269,7 @@ class TypeConverterDelegate {
}
else {
// convertedValue == null
- if (javaUtilOptionalEmpty != null && requiredType.equals(javaUtilOptionalEmpty.getClass())) {
+ if (javaUtilOptionalEmpty != null && requiredType == javaUtilOptionalEmpty.getClass()) {
convertedValue = javaUtilOptionalEmpty;
}
}
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/BeanCreationException.java b/spring-beans/src/main/java/org/springframework/beans/factory/BeanCreationException.java
index f0c2fa1d..75a38386 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/BeanCreationException.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/BeanCreationException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -63,7 +63,7 @@ public class BeanCreationException extends FatalBeanException {
* @param msg the detail message
*/
public BeanCreationException(String beanName, String msg) {
- super("Error creating bean with name '" + beanName + "': " + msg);
+ super("Error creating bean" + (beanName != null ? " with name '" + beanName + "'" : "") + ": " + msg);
this.beanName = beanName;
}
@@ -86,7 +86,7 @@ public class BeanCreationException extends FatalBeanException {
* @param msg the detail message
*/
public BeanCreationException(String resourceDescription, String beanName, String msg) {
- super("Error creating bean with name '" + beanName + "'" +
+ super("Error creating bean" + (beanName != null ? " with name '" + beanName + "'" : "") +
(resourceDescription != null ? " defined in " + resourceDescription : "") + ": " + msg);
this.resourceDescription = resourceDescription;
this.beanName = beanName;
@@ -123,7 +123,7 @@ public class BeanCreationException extends FatalBeanException {
/**
* Add a related cause to this bean creation exception,
- * not being a direct cause of the failure but having occured
+ * not being a direct cause of the failure but having occurred
* earlier in the creation of the same bean instance.
* @param ex the related cause to add
*/
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java
index 3c6bd4c8..58b2fd97 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java
@@ -188,12 +188,12 @@ public interface BeanFactory {
* Return an instance, which may be shared or independent, of the specified bean.
* <p>Allows for specifying explicit constructor arguments / factory method arguments,
* overriding the specified default arguments (if any) in the bean definition.
- * @param requiredType type the bean must match; can be an interface or superclass.
- * {@code null} is disallowed.
* <p>This method goes into {@link ListableBeanFactory} by-type lookup territory
* but may also be translated into a conventional by-name lookup based on the name
* of the given type. For more extensive retrieval operations across sets of beans,
* use {@link ListableBeanFactory} and/or {@link BeanFactoryUtils}.
+ * @param requiredType type the bean must match; can be an interface or superclass.
+ * {@code null} is disallowed.
* @param args arguments to use when creating a bean instance using explicit arguments
* (only applied when creating a new instance as opposed to retrieving an existing one)
* @return an instance of the bean
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/BeanInitializationException.java b/spring-beans/src/main/java/org/springframework/beans/factory/BeanInitializationException.java
index c52d0ae7..5c5ed79c 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/BeanInitializationException.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/BeanInitializationException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author 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,8 +23,8 @@ import org.springframework.beans.FatalBeanException;
* factory-aware initialization code fails. BeansExceptions thrown by
* bean factory methods themselves should simply be propagated as-is.
*
- * <p>Note that non-factory-aware initialization methods like afterPropertiesSet()
- * or a custom "init-method" can throw any exception.
+ * <p>Note that {@code afterPropertiesSet()} or a custom "init-method"
+ * can throw any exception.
*
* @author Juergen Hoeller
* @since 13.11.2003
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/BeanNotOfRequiredTypeException.java b/spring-beans/src/main/java/org/springframework/beans/factory/BeanNotOfRequiredTypeException.java
index bd6d33fb..6f98f435 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/BeanNotOfRequiredTypeException.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/BeanNotOfRequiredTypeException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,8 +45,8 @@ public class BeanNotOfRequiredTypeException extends BeansException {
* the expected type
*/
public BeanNotOfRequiredTypeException(String beanName, Class<?> requiredType, Class<?> actualType) {
- super("Bean named '" + beanName + "' must be of type [" + requiredType.getName() +
- "], but was actually of type [" + actualType.getName() + "]");
+ super("Bean named '" + beanName + "' is expected to be of type [" + requiredType.getName() +
+ "] but was actually of type [" + actualType.getName() + "]");
this.beanName = beanName;
this.requiredType = requiredType;
this.actualType = actualType;
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/InjectionPoint.java b/spring-beans/src/main/java/org/springframework/beans/factory/InjectionPoint.java
new file mode 100644
index 00000000..0a3d55a9
--- /dev/null
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/InjectionPoint.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.beans.factory;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.util.Assert;
+
+/**
+ * A simple descriptor for an injection point, pointing to a method/constructor
+ * parameter or a field. Exposed by {@link UnsatisfiedDependencyException}.
+ *
+ * @author Juergen Hoeller
+ * @since 4.3
+ * @see UnsatisfiedDependencyException#getInjectionPoint()
+ * @see org.springframework.beans.factory.config.DependencyDescriptor
+ */
+public class InjectionPoint {
+
+ protected MethodParameter methodParameter;
+
+ protected Field field;
+
+ private volatile Annotation[] fieldAnnotations;
+
+
+ /**
+ * Create an injection point descriptor for a method or constructor parameter.
+ * @param methodParameter the MethodParameter to wrap
+ */
+ public InjectionPoint(MethodParameter methodParameter) {
+ Assert.notNull(methodParameter, "MethodParameter must not be null");
+ this.methodParameter = methodParameter;
+ }
+
+ /**
+ * Create an injection point descriptor for a field.
+ * @param field the field to wrap
+ */
+ public InjectionPoint(Field field) {
+ Assert.notNull(field, "Field must not be null");
+ this.field = field;
+ }
+
+ /**
+ * Copy constructor.
+ * @param original the original descriptor to create a copy from
+ */
+ protected InjectionPoint(InjectionPoint original) {
+ this.methodParameter = (original.methodParameter != null ?
+ new MethodParameter(original.methodParameter) : null);
+ this.field = original.field;
+ this.fieldAnnotations = original.fieldAnnotations;
+ }
+
+ /**
+ * Just available for serialization purposes in subclasses.
+ */
+ protected InjectionPoint() {
+ }
+
+
+ /**
+ * Return the wrapped MethodParameter, if any.
+ * <p>Note: Either MethodParameter or Field is available.
+ * @return the MethodParameter, or {@code null} if none
+ */
+ public MethodParameter getMethodParameter() {
+ return this.methodParameter;
+ }
+
+ /**
+ * Return the wrapped Field, if any.
+ * <p>Note: Either MethodParameter or Field is available.
+ * @return the Field, or {@code null} if none
+ */
+ public Field getField() {
+ return this.field;
+ }
+
+ /**
+ * Obtain the annotations associated with the wrapped field or method/constructor parameter.
+ */
+ public Annotation[] getAnnotations() {
+ if (this.field != null) {
+ if (this.fieldAnnotations == null) {
+ this.fieldAnnotations = this.field.getAnnotations();
+ }
+ return this.fieldAnnotations;
+ }
+ else {
+ return this.methodParameter.getParameterAnnotations();
+ }
+ }
+
+ /**
+ * Return the type declared by the underlying field or method/constructor parameter,
+ * indicating the injection type.
+ */
+ public Class<?> getDeclaredType() {
+ return (this.field != null ? this.field.getType() : this.methodParameter.getParameterType());
+ }
+
+ /**
+ * Returns the wrapped member, containing the injection point.
+ * @return the Field / Method / Constructor as Member
+ */
+ public Member getMember() {
+ return (this.field != null ? this.field : this.methodParameter.getMember());
+ }
+
+ /**
+ * Return the wrapped annotated element.
+ * <p>Note: In case of a method/constructor parameter, this exposes
+ * the annotations declared on the method or constructor itself
+ * (i.e. at the method/constructor level, not at the parameter level).
+ * Use {@link #getAnnotations()} to obtain parameter-level annotations in
+ * such a scenario, transparently with corresponding field annotations.
+ * @return the Field / Method / Constructor as AnnotatedElement
+ */
+ public AnnotatedElement getAnnotatedElement() {
+ return (this.field != null ? this.field : this.methodParameter.getAnnotatedElement());
+ }
+
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (getClass() != other.getClass()) {
+ return false;
+ }
+ InjectionPoint otherPoint = (InjectionPoint) other;
+ return (this.field != null ? this.field.equals(otherPoint.field) :
+ this.methodParameter.equals(otherPoint.methodParameter));
+ }
+
+ @Override
+ public int hashCode() {
+ return (this.field != null ? this.field.hashCode() : this.methodParameter.hashCode());
+ }
+
+ @Override
+ public String toString() {
+ return (this.field != null ? "field '" + this.field.getName() + "'" : this.methodParameter.toString());
+ }
+
+}
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/NoUniqueBeanDefinitionException.java b/spring-beans/src/main/java/org/springframework/beans/factory/NoUniqueBeanDefinitionException.java
index 7db0bae6..6bb4f6db 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/NoUniqueBeanDefinitionException.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/NoUniqueBeanDefinitionException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +34,8 @@ public class NoUniqueBeanDefinitionException extends NoSuchBeanDefinitionExcepti
private int numberOfBeansFound;
+ private Collection<String> beanNamesFound;
+
/**
* Create a new {@code NoUniqueBeanDefinitionException}.
@@ -54,6 +56,7 @@ public class NoUniqueBeanDefinitionException extends NoSuchBeanDefinitionExcepti
public NoUniqueBeanDefinitionException(Class<?> type, Collection<String> beanNamesFound) {
this(type, beanNamesFound.size(), "expected single matching bean but found " + beanNamesFound.size() + ": " +
StringUtils.collectionToCommaDelimitedString(beanNamesFound));
+ this.beanNamesFound = beanNamesFound;
}
/**
@@ -76,4 +79,14 @@ public class NoUniqueBeanDefinitionException extends NoSuchBeanDefinitionExcepti
return this.numberOfBeansFound;
}
+ /**
+ * Return the names of all beans found when only one matching bean was expected.
+ * Note that this may be {@code null} if not specified at construction time.
+ * @since 4.3
+ * @see #getBeanType()
+ */
+ public Collection<String> getBeanNamesFound() {
+ return this.beanNamesFound;
+ }
+
}
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/ObjectProvider.java b/spring-beans/src/main/java/org/springframework/beans/factory/ObjectProvider.java
new file mode 100644
index 00000000..ffa2683e
--- /dev/null
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/ObjectProvider.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.beans.factory;
+
+import org.springframework.beans.BeansException;
+
+/**
+ * A variant of {@link ObjectFactory} designed specifically for injection points,
+ * allowing for programmatic optionality and lenient not-unique handling.
+ *
+ * @author Juergen Hoeller
+ * @since 4.3
+ */
+public interface ObjectProvider<T> extends ObjectFactory<T> {
+
+ /**
+ * Return an instance (possibly shared or independent) of the object
+ * managed by this factory.
+ * <p>Allows for specifying explicit construction arguments, along the
+ * lines of {@link BeanFactory#getBean(String, Object...)}.
+ * @param args arguments to use when creating a corresponding instance
+ * @return an instance of the bean
+ * @throws BeansException in case of creation errors
+ * @see #getObject()
+ */
+ T getObject(Object... args) throws BeansException;
+
+ /**
+ * Return an instance (possibly shared or independent) of the object
+ * managed by this factory.
+ * @return an instance of the bean, or {@code null} if not available
+ * @throws BeansException in case of creation errors
+ * @see #getObject()
+ */
+ T getIfAvailable() throws BeansException;
+
+ /**
+ * Return an instance (possibly shared or independent) of the object
+ * managed by this factory.
+ * @return an instance of the bean, or {@code null} if not available or
+ * not unique (i.e. multiple candidates found with none marked as primary)
+ * @throws BeansException in case of creation errors
+ * @see #getObject()
+ */
+ T getIfUnique() throws BeansException;
+
+}
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/UnsatisfiedDependencyException.java b/spring-beans/src/main/java/org/springframework/beans/factory/UnsatisfiedDependencyException.java
index 9ec35a7e..0403abfc 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/UnsatisfiedDependencyException.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/UnsatisfiedDependencyException.java
@@ -31,6 +31,9 @@ import org.springframework.util.ClassUtils;
@SuppressWarnings("serial")
public class UnsatisfiedDependencyException extends BeanCreationException {
+ private InjectionPoint injectionPoint;
+
+
/**
* Create a new UnsatisfiedDependencyException.
* @param resourceDescription description of the resource that the bean definition came from
@@ -64,10 +67,42 @@ public class UnsatisfiedDependencyException extends BeanCreationException {
* Create a new UnsatisfiedDependencyException.
* @param resourceDescription description of the resource that the bean definition came from
* @param beanName the name of the bean requested
+ * @param injectionPoint the injection point (field or method/constructor parameter)
+ * @param msg the detail message
+ * @since 4.3
+ */
+ public UnsatisfiedDependencyException(
+ String resourceDescription, String beanName, InjectionPoint injectionPoint, String msg) {
+
+ super(resourceDescription, beanName, "Unsatisfied dependency expressed through " + injectionPoint + ": " + msg);
+ this.injectionPoint = injectionPoint;
+ }
+
+ /**
+ * Create a new UnsatisfiedDependencyException.
+ * @param resourceDescription description of the resource that the bean definition came from
+ * @param beanName the name of the bean requested
+ * @param injectionPoint the injection point (field or method/constructor parameter)
+ * @param ex the bean creation exception that indicated the unsatisfied dependency
+ * @since 4.3
+ */
+ public UnsatisfiedDependencyException(
+ String resourceDescription, String beanName, InjectionPoint injectionPoint, BeansException ex) {
+
+ this(resourceDescription, beanName, injectionPoint, (ex != null ? ex.getMessage() : ""));
+ initCause(ex);
+ }
+
+ /**
+ * Create a new UnsatisfiedDependencyException.
+ * @param resourceDescription description of the resource that the bean definition came from
+ * @param beanName the name of the bean requested
* @param ctorArgIndex the index of the constructor argument that couldn't be satisfied
* @param ctorArgType the type of the constructor argument that couldn't be satisfied
* @param msg the detail message
+ * @deprecated in favor of {@link #UnsatisfiedDependencyException(String, String, InjectionPoint, String)}
*/
+ @Deprecated
public UnsatisfiedDependencyException(
String resourceDescription, String beanName, int ctorArgIndex, Class<?> ctorArgType, String msg) {
@@ -84,7 +119,9 @@ public class UnsatisfiedDependencyException extends BeanCreationException {
* @param ctorArgIndex the index of the constructor argument that couldn't be satisfied
* @param ctorArgType the type of the constructor argument that couldn't be satisfied
* @param ex the bean creation exception that indicated the unsatisfied dependency
+ * @deprecated in favor of {@link #UnsatisfiedDependencyException(String, String, InjectionPoint, BeansException)}
*/
+ @Deprecated
public UnsatisfiedDependencyException(
String resourceDescription, String beanName, int ctorArgIndex, Class<?> ctorArgType, BeansException ex) {
@@ -92,4 +129,13 @@ public class UnsatisfiedDependencyException extends BeanCreationException {
initCause(ex);
}
+
+ /**
+ * Return the injection point (field or method/constructor parameter), if known.
+ * @since 4.3
+ */
+ public InjectionPoint getInjectionPoint() {
+ return this.injectionPoint;
+ }
+
}
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/access/SingletonBeanFactoryLocator.java b/spring-beans/src/main/java/org/springframework/beans/factory/access/SingletonBeanFactoryLocator.java
index d7758bc1..45be7328 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/access/SingletonBeanFactoryLocator.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/access/SingletonBeanFactoryLocator.java
@@ -88,7 +88,7 @@ import org.springframework.core.io.support.ResourcePatternUtils;
* use object from a BeanFactory/ApplicationContext. One solutions is to make the
* class created by the third party code be just a stub or proxy, which gets the
* real object from a BeanFactory/ApplicationContext, and delegates to it. However,
- * it is is not normally workable for the stub to create the BeanFactory on each
+ * it is not normally workable for the stub to create the BeanFactory on each
* use, as depending on what is inside it, that can be an expensive operation.
* Additionally, there is a fairly tight coupling between the stub and the name of
* the definition resource for the BeanFactory/ApplicationContext. This is where
@@ -291,7 +291,7 @@ public class SingletonBeanFactoryLocator implements BeanFactoryLocator {
}
/**
- * Returns an instance which uses the the specified selector, as the name of the
+ * Returns an instance which uses the specified selector, as the name of the
* definition file(s). In the case of a name with a Spring 'classpath*:' prefix,
* or with no prefix, which is treated the same, the current thread context
* ClassLoader's {@code getResources} method will be called with this value
@@ -341,7 +341,7 @@ public class SingletonBeanFactoryLocator implements BeanFactoryLocator {
/**
- * Constructor which uses the the specified name as the resource name
+ * Constructor which uses the specified name as the resource name
* of the definition file(s).
* @param resourceLocation the Spring resource location to use
* (either a URL or a "classpath:" / "classpath*:" pseudo URL)
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/Autowired.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/Autowired.java
index fc8b49bc..73779365 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/Autowired.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/Autowired.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -64,7 +64,7 @@ import java.lang.annotation.Target;
* @see Qualifier
* @see Value
*/
-@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
+@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java
index c008a796..258f66bf 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,11 +45,12 @@ import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanFactoryUtils;
+import org.springframework.beans.factory.InjectionPoint;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
-import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.LookupOverride;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
@@ -107,6 +108,7 @@ import org.springframework.util.StringUtils;
*
* @author Juergen Hoeller
* @author Mark Fisher
+ * @author Stephane Nicoll
* @since 2.5
* @see #setAutowiredAnnotationType
* @see Autowired
@@ -270,6 +272,19 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
Constructor<?> defaultConstructor = null;
for (Constructor<?> candidate : rawCandidates) {
AnnotationAttributes ann = findAutowiredAnnotation(candidate);
+ if (ann == null) {
+ Class<?> userClass = ClassUtils.getUserClass(beanClass);
+ if (userClass != beanClass) {
+ try {
+ Constructor<?> superCtor =
+ userClass.getDeclaredConstructor(candidate.getParameterTypes());
+ ann = findAutowiredAnnotation(superCtor);
+ }
+ catch (NoSuchMethodException ex) {
+ // Simply proceed, no equivalent superclass constructor found...
+ }
+ }
+ }
if (ann != null) {
if (requiredConstructor != null) {
throw new BeanCreationException(beanName,
@@ -312,6 +327,9 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
}
candidateConstructors = candidates.toArray(new Constructor<?>[candidates.size()]);
}
+ else if (rawCandidates.length == 1 && rawCandidates[0].getParameterTypes().length > 0) {
+ candidateConstructors = new Constructor<?>[] {rawCandidates[0]};
+ }
else {
candidateConstructors = new Constructor<?>[0];
}
@@ -330,6 +348,9 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
try {
metadata.inject(bean, beanName, pvs);
}
+ catch (BeanCreationException ex) {
+ throw ex;
+ }
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
}
@@ -348,6 +369,9 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
try {
metadata.inject(bean, null, null);
}
+ catch (BeanCreationException ex) {
+ throw ex;
+ }
catch (Throwable ex) {
throw new BeanCreationException("Injection of autowired dependencies failed for class [" + clazz + "]", ex);
}
@@ -504,9 +528,6 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
DependencyDescriptor descriptor = (DependencyDescriptor) cachedArgument;
return this.beanFactory.resolveDependency(descriptor, beanName, null, null);
}
- else if (cachedArgument instanceof RuntimeBeanReference) {
- return this.beanFactory.getBean(((RuntimeBeanReference) cachedArgument).getBeanName());
- }
else {
return cachedArgument;
}
@@ -532,45 +553,46 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
@Override
protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
Field field = (Field) this.member;
- try {
- Object value;
- if (this.cached) {
- value = resolvedCachedArgument(beanName, this.cachedFieldValue);
- }
- else {
- DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
- desc.setContainingClass(bean.getClass());
- Set<String> autowiredBeanNames = new LinkedHashSet<String>(1);
- TypeConverter typeConverter = beanFactory.getTypeConverter();
+ Object value;
+ if (this.cached) {
+ value = resolvedCachedArgument(beanName, this.cachedFieldValue);
+ }
+ else {
+ DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
+ desc.setContainingClass(bean.getClass());
+ Set<String> autowiredBeanNames = new LinkedHashSet<String>(1);
+ TypeConverter typeConverter = beanFactory.getTypeConverter();
+ try {
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
- synchronized (this) {
- if (!this.cached) {
- if (value != null || this.required) {
- this.cachedFieldValue = desc;
- registerDependentBeans(beanName, autowiredBeanNames);
- if (autowiredBeanNames.size() == 1) {
- String autowiredBeanName = autowiredBeanNames.iterator().next();
- if (beanFactory.containsBean(autowiredBeanName)) {
- if (beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
- this.cachedFieldValue = new RuntimeBeanReference(autowiredBeanName);
- }
+ }
+ catch (BeansException ex) {
+ throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
+ }
+ synchronized (this) {
+ if (!this.cached) {
+ if (value != null || this.required) {
+ this.cachedFieldValue = desc;
+ registerDependentBeans(beanName, autowiredBeanNames);
+ if (autowiredBeanNames.size() == 1) {
+ String autowiredBeanName = autowiredBeanNames.iterator().next();
+ if (beanFactory.containsBean(autowiredBeanName)) {
+ if (beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
+ this.cachedFieldValue = new ShortcutDependencyDescriptor(
+ desc, autowiredBeanName, field.getType());
}
}
}
- else {
- this.cachedFieldValue = null;
- }
- this.cached = true;
}
+ else {
+ this.cachedFieldValue = null;
+ }
+ this.cached = true;
}
}
- if (value != null) {
- ReflectionUtils.makeAccessible(field);
- field.set(bean, value);
- }
}
- catch (Throwable ex) {
- throw new BeanCreationException("Could not autowire field: " + field, ex);
+ if (value != null) {
+ ReflectionUtils.makeAccessible(field);
+ field.set(bean, value);
}
}
}
@@ -598,67 +620,70 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
return;
}
Method method = (Method) this.member;
- try {
- Object[] arguments;
- if (this.cached) {
- // Shortcut for avoiding synchronization...
- arguments = resolveCachedArguments(beanName);
- }
- else {
- Class<?>[] paramTypes = method.getParameterTypes();
- arguments = new Object[paramTypes.length];
- DependencyDescriptor[] descriptors = new DependencyDescriptor[paramTypes.length];
- Set<String> autowiredBeanNames = new LinkedHashSet<String>(paramTypes.length);
- TypeConverter typeConverter = beanFactory.getTypeConverter();
- for (int i = 0; i < arguments.length; i++) {
- MethodParameter methodParam = new MethodParameter(method, i);
- DependencyDescriptor desc = new DependencyDescriptor(methodParam, this.required);
- desc.setContainingClass(bean.getClass());
- descriptors[i] = desc;
- Object arg = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
+ Object[] arguments;
+ if (this.cached) {
+ // Shortcut for avoiding synchronization...
+ arguments = resolveCachedArguments(beanName);
+ }
+ else {
+ Class<?>[] paramTypes = method.getParameterTypes();
+ arguments = new Object[paramTypes.length];
+ DependencyDescriptor[] descriptors = new DependencyDescriptor[paramTypes.length];
+ Set<String> autowiredBeanNames = new LinkedHashSet<String>(paramTypes.length);
+ TypeConverter typeConverter = beanFactory.getTypeConverter();
+ for (int i = 0; i < arguments.length; i++) {
+ MethodParameter methodParam = new MethodParameter(method, i);
+ DependencyDescriptor currDesc = new DependencyDescriptor(methodParam, this.required);
+ currDesc.setContainingClass(bean.getClass());
+ descriptors[i] = currDesc;
+ try {
+ Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeanNames, typeConverter);
if (arg == null && !this.required) {
arguments = null;
break;
}
arguments[i] = arg;
}
- synchronized (this) {
- if (!this.cached) {
- if (arguments != null) {
- this.cachedMethodArguments = new Object[arguments.length];
- for (int i = 0; i < arguments.length; i++) {
- this.cachedMethodArguments[i] = descriptors[i];
- }
- registerDependentBeans(beanName, autowiredBeanNames);
- if (autowiredBeanNames.size() == paramTypes.length) {
- Iterator<String> it = autowiredBeanNames.iterator();
- for (int i = 0; i < paramTypes.length; i++) {
- String autowiredBeanName = it.next();
- if (beanFactory.containsBean(autowiredBeanName)) {
- if (beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) {
- this.cachedMethodArguments[i] = new RuntimeBeanReference(autowiredBeanName);
- }
+ catch (BeansException ex) {
+ throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(methodParam), ex);
+ }
+ }
+ synchronized (this) {
+ if (!this.cached) {
+ if (arguments != null) {
+ this.cachedMethodArguments = new Object[paramTypes.length];
+ for (int i = 0; i < arguments.length; i++) {
+ this.cachedMethodArguments[i] = descriptors[i];
+ }
+ registerDependentBeans(beanName, autowiredBeanNames);
+ if (autowiredBeanNames.size() == paramTypes.length) {
+ Iterator<String> it = autowiredBeanNames.iterator();
+ for (int i = 0; i < paramTypes.length; i++) {
+ String autowiredBeanName = it.next();
+ if (beanFactory.containsBean(autowiredBeanName)) {
+ if (beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) {
+ this.cachedMethodArguments[i] = new ShortcutDependencyDescriptor(
+ descriptors[i], autowiredBeanName, paramTypes[i]);
}
}
}
}
- else {
- this.cachedMethodArguments = null;
- }
- this.cached = true;
}
+ else {
+ this.cachedMethodArguments = null;
+ }
+ this.cached = true;
}
}
- if (arguments != null) {
+ }
+ if (arguments != null) {
+ try {
ReflectionUtils.makeAccessible(method);
method.invoke(bean, arguments);
}
- }
- catch (InvocationTargetException ex) {
- throw ex.getTargetException();
- }
- catch (Throwable ex) {
- throw new BeanCreationException("Could not autowire method: " + method, ex);
+ catch (InvocationTargetException ex){
+ throw ex.getTargetException();
+ }
}
}
@@ -674,4 +699,27 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
}
}
+
+ /**
+ * DependencyDescriptor variant with a pre-resolved target bean name.
+ */
+ @SuppressWarnings("serial")
+ private static class ShortcutDependencyDescriptor extends DependencyDescriptor {
+
+ private final String shortcutName;
+
+ private final Class<?> requiredType;
+
+ public ShortcutDependencyDescriptor(DependencyDescriptor original, String shortcutName, Class<?> requiredType) {
+ super(original);
+ this.shortcutName = shortcutName;
+ this.requiredType = requiredType;
+ }
+
+ @Override
+ public Object resolveShortcut(BeanFactory beanFactory) {
+ return resolveCandidate(this.shortcutName, this.requiredType, beanFactory);
+ }
+ }
+
}
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/BeanFactoryAnnotationUtils.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/BeanFactoryAnnotationUtils.java
index eb691a14..65901004 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/BeanFactoryAnnotationUtils.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/BeanFactoryAnnotationUtils.java
@@ -26,6 +26,7 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.AutowireCandidateQualifier;
import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ObjectUtils;
/**
@@ -113,6 +114,7 @@ public class BeanFactoryAnnotationUtils {
if (bf.containsBean(beanName)) {
try {
BeanDefinition bd = bf.getMergedBeanDefinition(beanName);
+ // Explicit qualifier metadata on bean definition? (typically in XML definition)
if (bd instanceof AbstractBeanDefinition) {
AbstractBeanDefinition abd = (AbstractBeanDefinition) bd;
AutowireCandidateQualifier candidate = abd.getQualifier(Qualifier.class.getName());
@@ -121,15 +123,24 @@ public class BeanFactoryAnnotationUtils {
return true;
}
}
+ // Corresponding qualifier on factory method? (typically in configuration class)
if (bd instanceof RootBeanDefinition) {
Method factoryMethod = ((RootBeanDefinition) bd).getResolvedFactoryMethod();
if (factoryMethod != null) {
- Qualifier targetAnnotation = factoryMethod.getAnnotation(Qualifier.class);
- if (targetAnnotation != null && qualifier.equals(targetAnnotation.value())) {
- return true;
+ Qualifier targetAnnotation = AnnotationUtils.getAnnotation(factoryMethod, Qualifier.class);
+ if (targetAnnotation != null) {
+ return qualifier.equals(targetAnnotation.value());
}
}
}
+ // Corresponding qualifier on bean implementation class? (for custom user types)
+ Class<?> beanType = bf.getType(beanName);
+ if (beanType != null) {
+ Qualifier targetAnnotation = AnnotationUtils.getAnnotation(beanType, Qualifier.class);
+ if (targetAnnotation != null) {
+ return qualifier.equals(targetAnnotation.value());
+ }
+ }
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore - can't compare qualifiers for a manually registered singleton object
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java
index 3c7c0577..37433239 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java
@@ -136,7 +136,7 @@ public class InitDestroyAnnotationBeanPostProcessor
throw new BeanCreationException(beanName, "Invocation of init method failed", ex.getTargetException());
}
catch (Throwable ex) {
- throw new BeanCreationException(beanName, "Couldn't invoke init method", ex);
+ throw new BeanCreationException(beanName, "Failed to invoke init method", ex);
}
return bean;
}
@@ -162,10 +162,15 @@ public class InitDestroyAnnotationBeanPostProcessor
}
}
catch (Throwable ex) {
- logger.error("Couldn't invoke destroy method on bean with name '" + beanName + "'", ex);
+ logger.error("Failed to invoke destroy method on bean with name '" + beanName + "'", ex);
}
}
+ @Override
+ public boolean requiresDestruction(Object bean) {
+ return findLifecycleMetadata(bean.getClass()).hasDestroyMethods();
+ }
+
private LifecycleMetadata findLifecycleMetadata(Class<?> clazz) {
if (this.lifecycleMetadataCache == null) {
@@ -308,11 +313,11 @@ public class InitDestroyAnnotationBeanPostProcessor
}
public void invokeDestroyMethods(Object target, String beanName) throws Throwable {
- Collection<LifecycleElement> destroyMethodsToIterate =
+ Collection<LifecycleElement> destroyMethodsToUse =
(this.checkedDestroyMethods != null ? this.checkedDestroyMethods : this.destroyMethods);
- if (!destroyMethodsToIterate.isEmpty()) {
+ if (!destroyMethodsToUse.isEmpty()) {
boolean debug = logger.isDebugEnabled();
- for (LifecycleElement element : destroyMethodsToIterate) {
+ for (LifecycleElement element : destroyMethodsToUse) {
if (debug) {
logger.debug("Invoking destroy method on bean '" + beanName + "': " + element.getMethod());
}
@@ -320,6 +325,12 @@ public class InitDestroyAnnotationBeanPostProcessor
}
}
}
+
+ public boolean hasDestroyMethods() {
+ Collection<LifecycleElement> destroyMethodsToUse =
+ (this.checkedDestroyMethods != null ? this.checkedDestroyMethods : this.destroyMethods);
+ return !destroyMethodsToUse.isEmpty();
+ }
}
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java
index d8fcc731..0f6d1021 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,6 +32,8 @@ import org.springframework.beans.factory.support.AutowireCandidateResolver;
import org.springframework.beans.factory.support.GenericTypeAwareAutowireCandidateResolver;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.MethodParameter;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -315,25 +317,20 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
* Determine a suggested value from any of the given candidate annotations.
*/
protected Object findValue(Annotation[] annotationsToSearch) {
- for (Annotation annotation : annotationsToSearch) {
- if (this.valueAnnotationType.isInstance(annotation)) {
- return extractValue(annotation);
- }
- }
- for (Annotation annotation : annotationsToSearch) {
- Annotation metaAnn = annotation.annotationType().getAnnotation(this.valueAnnotationType);
- if (metaAnn != null) {
- return extractValue(metaAnn);
- }
+ AnnotationAttributes attr = AnnotatedElementUtils.getMergedAnnotationAttributes(
+ AnnotatedElementUtils.forAnnotations(annotationsToSearch), this.valueAnnotationType);
+ if (attr != null) {
+ return extractValue(attr);
}
return null;
}
/**
* Extract the value attribute from the given annotation.
+ * @since 4.3
*/
- protected Object extractValue(Annotation valueAnnotation) {
- Object value = AnnotationUtils.getValue(valueAnnotation);
+ protected Object extractValue(AnnotationAttributes attr) {
+ Object value = attr.get(AnnotationUtils.VALUE);
if (value == null) {
throw new IllegalStateException("Value annotation must have a value attribute");
}
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/AbstractFactoryBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/AbstractFactoryBean.java
index bc8a42f7..85faab00 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/config/AbstractFactoryBean.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/AbstractFactoryBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -208,7 +208,7 @@ public abstract class AbstractFactoryBean<T>
* <p>Invoked on initialization of this FactoryBean in case of
* a singleton; else, on each {@link #getObject()} call.
* @return the object returned by this factory
- * @throws Exception if an exception occured during object creation
+ * @throws Exception if an exception occurred during object creation
* @see #getObject()
*/
protected abstract T createInstance() throws Exception;
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/AutowireCapableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/AutowireCapableBeanFactory.java
index ffa8849a..43602b48 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/config/AutowireCapableBeanFactory.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/AutowireCapableBeanFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -114,9 +114,9 @@ public interface AutowireCapableBeanFactory extends BeanFactory {
* <p>Performs full initialization of the bean, including all applicable
* {@link BeanPostProcessor BeanPostProcessors}.
* <p>Note: This is intended for creating a fresh instance, populating annotated
- * fields and methods as well as applying all standard bean initialiation callbacks.
+ * fields and methods as well as applying all standard bean initialization callbacks.
* It does <i>not</> imply traditional by-name or by-type autowiring of properties;
- * use {@link #createBean(Class, int, boolean)} for that purposes.
+ * use {@link #createBean(Class, int, boolean)} for those purposes.
* @param beanClass the class of the bean to create
* @return the new bean instance
* @throws BeansException if instantiation or wiring failed
@@ -129,7 +129,7 @@ public interface AutowireCapableBeanFactory extends BeanFactory {
* <p>Note: This is essentially intended for (re-)populating annotated fields and
* methods, either for new instances or for deserialized instances. It does
* <i>not</i> imply traditional by-name or by-type autowiring of properties;
- * use {@link #autowireBeanProperties} for that purposes.
+ * use {@link #autowireBeanProperties} for those purposes.
* @param existingBean the existing bean instance
* @throws BeansException if wiring failed
*/
@@ -159,7 +159,7 @@ public interface AutowireCapableBeanFactory extends BeanFactory {
* @param descriptor the descriptor for the dependency
* @param beanName the name of the bean which declares the present dependency
* @return the resolved object, or {@code null} if none found
- * @throws BeansException in dependency resolution failed
+ * @throws BeansException if dependency resolution failed
*/
Object resolveDependency(DependencyDescriptor descriptor, String beanName) throws BeansException;
@@ -321,7 +321,7 @@ public interface AutowireCapableBeanFactory extends BeanFactory {
* @param typeConverter the TypeConverter to use for populating arrays and
* collections
* @return the resolved object, or {@code null} if none found
- * @throws BeansException in dependency resolution failed
+ * @throws BeansException if dependency resolution failed
*/
Object resolveDependency(DependencyDescriptor descriptor, String beanName,
Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException;
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java
index d04eb15d..302deec5 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -210,6 +210,13 @@ public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, Single
void addEmbeddedValueResolver(StringValueResolver valueResolver);
/**
+ * Determine whether an embedded value resolver has been registered with this
+ * bean factory, to be applied through {@link #resolveEmbeddedValue(String)}.
+ * @since 4.3
+ */
+ boolean hasEmbeddedValueResolver();
+
+ /**
* Resolve the given embedded value, e.g. an annotation attribute.
* @param value the value to resolve
* @return the resolved value (may be the original value as-is)
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/ConstructorArgumentValues.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/ConstructorArgumentValues.java
index 9a57c7ae..6f7d2d90 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/config/ConstructorArgumentValues.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/ConstructorArgumentValues.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -156,7 +156,7 @@ public class ConstructorArgumentValues {
* @param requiredType the type to match (can be {@code null} to match
* untyped values only)
* @param requiredName the type to match (can be {@code null} to match
- * unnamed values only)
+ * unnamed values only, or empty String to match any name)
* @return the ValueHolder for the argument, or {@code null} if none set
*/
public ValueHolder getIndexedArgumentValue(int index, Class<?> requiredType, String requiredName) {
@@ -165,7 +165,7 @@ public class ConstructorArgumentValues {
if (valueHolder != null &&
(valueHolder.getType() == null ||
(requiredType != null && ClassUtils.matchesTypeName(requiredType, valueHolder.getType()))) &&
- (valueHolder.getName() == null ||
+ (valueHolder.getName() == null || "".equals(requiredName) ||
(requiredName != null && requiredName.equals(valueHolder.getName())))) {
return valueHolder;
}
@@ -268,7 +268,7 @@ public class ConstructorArgumentValues {
* @param requiredType the type to match (can be {@code null} to find
* an arbitrary next generic argument value)
* @param requiredName the name to match (can be {@code null} to not
- * match argument values by name)
+ * match argument values by name, or empty String to match any name)
* @param usedValueHolders a Set of ValueHolder objects that have already been used
* in the current resolution process and should therefore not be returned again
* @return the ValueHolder for the argument, or {@code null} if none found
@@ -278,7 +278,7 @@ public class ConstructorArgumentValues {
if (usedValueHolders != null && usedValueHolders.contains(valueHolder)) {
continue;
}
- if (valueHolder.getName() != null &&
+ if (valueHolder.getName() != null && !"".equals(requiredName) &&
(requiredName == null || !valueHolder.getName().equals(requiredName))) {
continue;
}
@@ -335,7 +335,7 @@ public class ConstructorArgumentValues {
* @param requiredType the parameter type to match (can be {@code null}
* to find an untyped argument value)
* @param requiredName the parameter name to match (can be {@code null}
- * to find an unnamed argument value)
+ * to find an unnamed argument value, or empty String to match any name)
* @param usedValueHolders a Set of ValueHolder objects that have already
* been used in the current resolution process and should therefore not
* be returned again (allowing to return the next generic argument match
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java
index 88642eb4..82c6883c 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java
@@ -19,17 +19,20 @@ package org.springframework.beans.factory.config;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
-import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
+import java.util.Map;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.InjectionPoint;
+import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.core.GenericCollectionTypeResolver;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.ResolvableType;
-import org.springframework.util.Assert;
/**
* Descriptor for a specific dependency that is about to be injected.
@@ -40,15 +43,9 @@ import org.springframework.util.Assert;
* @since 2.5
*/
@SuppressWarnings("serial")
-public class DependencyDescriptor implements Serializable {
+public class DependencyDescriptor extends InjectionPoint implements Serializable {
- private transient MethodParameter methodParameter;
-
- private transient Field field;
-
- private Class<?> declaringClass;
-
- private Class<?> containingClass;
+ private final Class<?> declaringClass;
private String methodName;
@@ -64,7 +61,7 @@ public class DependencyDescriptor implements Serializable {
private int nestingLevel = 1;
- private transient Annotation[] fieldAnnotations;
+ private Class<?> containingClass;
/**
@@ -85,10 +82,8 @@ public class DependencyDescriptor implements Serializable {
* eagerly resolving potential target beans for type matching
*/
public DependencyDescriptor(MethodParameter methodParameter, boolean required, boolean eager) {
- Assert.notNull(methodParameter, "MethodParameter must not be null");
- this.methodParameter = methodParameter;
+ super(methodParameter);
this.declaringClass = methodParameter.getDeclaringClass();
- this.containingClass = methodParameter.getContainingClass();
if (this.methodParameter.getMethod() != null) {
this.methodName = methodParameter.getMethod().getName();
this.parameterTypes = methodParameter.getMethod().getParameterTypes();
@@ -97,6 +92,7 @@ public class DependencyDescriptor implements Serializable {
this.parameterTypes = methodParameter.getConstructor().getParameterTypes();
}
this.parameterIndex = methodParameter.getParameterIndex();
+ this.containingClass = methodParameter.getContainingClass();
this.required = required;
this.eager = eager;
}
@@ -119,8 +115,7 @@ public class DependencyDescriptor implements Serializable {
* eagerly resolving potential target beans for type matching
*/
public DependencyDescriptor(Field field, boolean required, boolean eager) {
- Assert.notNull(field, "Field must not be null");
- this.field = field;
+ super(field);
this.declaringClass = field.getDeclaringClass();
this.fieldName = field.getName();
this.required = required;
@@ -132,52 +127,84 @@ public class DependencyDescriptor implements Serializable {
* @param original the original descriptor to create a copy from
*/
public DependencyDescriptor(DependencyDescriptor original) {
- this.methodParameter = (original.methodParameter != null ? new MethodParameter(original.methodParameter) : null);
- this.field = original.field;
+ super(original);
this.declaringClass = original.declaringClass;
- this.containingClass = original.containingClass;
this.methodName = original.methodName;
this.parameterTypes = original.parameterTypes;
this.parameterIndex = original.parameterIndex;
this.fieldName = original.fieldName;
+ this.containingClass = original.containingClass;
this.required = original.required;
this.eager = original.eager;
this.nestingLevel = original.nestingLevel;
- this.fieldAnnotations = original.fieldAnnotations;
}
/**
- * Return the wrapped MethodParameter, if any.
- * <p>Note: Either MethodParameter or Field is available.
- * @return the MethodParameter, or {@code null} if none
+ * Return whether this dependency is required.
*/
- public MethodParameter getMethodParameter() {
- return this.methodParameter;
+ public boolean isRequired() {
+ return this.required;
}
/**
- * Return the wrapped Field, if any.
- * <p>Note: Either MethodParameter or Field is available.
- * @return the Field, or {@code null} if none
+ * Return whether this dependency is 'eager' in the sense of
+ * eagerly resolving potential target beans for type matching.
*/
- public Field getField() {
- return this.field;
+ public boolean isEager() {
+ return this.eager;
}
/**
- * Return whether this dependency is required.
+ * Resolve the specified not-unique scenario: by default,
+ * throwing a {@link NoUniqueBeanDefinitionException}.
+ * <p>Subclasses may override this to select one of the instances or
+ * to opt out with no result at all through returning {@code null}.
+ * @param type the requested bean type
+ * @param matchingBeans a map of bean names and corresponding bean
+ * instances which have been pre-selected for the given type
+ * (qualifiers etc already applied)
+ * @return a bean instance to proceed with, or {@code null} for none
+ * @throws BeansException in case of the not-unique scenario being fatal
+ * @since 4.3
*/
- public boolean isRequired() {
- return this.required;
+ public Object resolveNotUnique(Class<?> type, Map<String, Object> matchingBeans) throws BeansException {
+ throw new NoUniqueBeanDefinitionException(type, matchingBeans.keySet());
}
/**
- * Return whether this dependency is 'eager' in the sense of
- * eagerly resolving potential target beans for type matching.
+ * Resolve a shortcut for this dependency against the given factory, for example
+ * taking some pre-resolved information into account.
+ * <p>The resolution algorithm will first attempt to resolve a shortcut through this
+ * method before going into the regular type matching algorithm across all beans.
+ * Subclasses may override this method to improve resolution performance based on
+ * pre-cached information while still receiving {@link InjectionPoint} exposure etc.
+ * @param beanFactory the associated factory
+ * @return the shortcut result if any, or {@code null} if none
+ * @throws BeansException if the shortcut could not be obtained
+ * @since 4.3.1
*/
- public boolean isEager() {
- return this.eager;
+ public Object resolveShortcut(BeanFactory beanFactory) throws BeansException {
+ return null;
+ }
+
+ /**
+ * Resolve the specified bean name, as a candidate result of the matching
+ * algorithm for this dependency, to a bean instance from the given factory.
+ * <p>The default implementation calls {@link BeanFactory#getBean(String)}.
+ * Subclasses may provide additional arguments or other customizations.
+ * @param beanName the bean name, as a candidate result for this dependency
+ * @param requiredType the expected type of the bean (as an assertion)
+ * @param beanFactory the associated factory
+ * @return the bean instance (never {@code null})
+ * @throws BeansException if the bean could not be obtained
+ * @since 4.3.2
+ * @see BeanFactory#getBean(String)
+ */
+ public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory)
+ throws BeansException {
+
+ return beanFactory.getBean(beanName, requiredType);
}
@@ -272,6 +299,7 @@ public class DependencyDescriptor implements Serializable {
Type[] args = ((ParameterizedType) type).getActualTypeArguments();
type = args[args.length - 1];
}
+ // TODO: Object.class if unresolvable
}
if (type instanceof Class) {
return (Class<?>) type;
@@ -323,19 +351,18 @@ public class DependencyDescriptor implements Serializable {
GenericCollectionTypeResolver.getMapValueParameterType(this.methodParameter));
}
- /**
- * Obtain the annotations associated with the wrapped parameter/field, if any.
- */
- public Annotation[] getAnnotations() {
- if (this.field != null) {
- if (this.fieldAnnotations == null) {
- this.fieldAnnotations = this.field.getAnnotations();
- }
- return this.fieldAnnotations;
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
}
- else {
- return this.methodParameter.getParameterAnnotations();
+ if (!super.equals(other)) {
+ return false;
}
+ DependencyDescriptor otherDesc = (DependencyDescriptor) other;
+ return (this.required == otherDesc.required && this.eager == otherDesc.eager &&
+ this.nestingLevel == otherDesc.nestingLevel && this.containingClass == otherDesc.containingClass);
}
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/DestructionAwareBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/DestructionAwareBeanPostProcessor.java
index 76de1390..92316f13 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/config/DestructionAwareBeanPostProcessor.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/DestructionAwareBeanPostProcessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -43,4 +43,24 @@ public interface DestructionAwareBeanPostProcessor extends BeanPostProcessor {
*/
void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException;
+ /**
+ * Determine whether the given bean instance requires destruction by this
+ * post-processor.
+ * <p><b>NOTE:</b> Even as a late addition, this method has been introduced on
+ * {@code DestructionAwareBeanPostProcessor} itself instead of on a SmartDABPP
+ * subinterface. This allows existing {@code DestructionAwareBeanPostProcessor}
+ * implementations to easily provide {@code requiresDestruction} logic while
+ * retaining compatibility with Spring <4.3, and it is also an easier onramp to
+ * declaring {@code requiresDestruction} as a Java 8 default method in Spring 5.
+ * <p>If an implementation of {@code DestructionAwareBeanPostProcessor} does
+ * not provide a concrete implementation of this method, Spring's invocation
+ * mechanism silently assumes a method returning {@code true} (the effective
+ * default before 4.3, and the to-be-default in the Java 8 method in Spring 5).
+ * @param bean the bean instance to check
+ * @return {@code true} if {@link #postProcessBeforeDestruction} is supposed to
+ * be called for this bean instance eventually, or {@code false} if not needed
+ * @since 4.3
+ */
+ boolean requiresDestruction(Object bean);
+
}
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/EmbeddedValueResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/EmbeddedValueResolver.java
new file mode 100644
index 00000000..bd8c1666
--- /dev/null
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/EmbeddedValueResolver.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.beans.factory.config;
+
+import org.springframework.util.StringValueResolver;
+
+/**
+ * {@link StringValueResolver} adapter for resolving placeholders and
+ * expressions against a {@link ConfigurableBeanFactory}.
+ *
+ * <p>Note that this adapter resolves expressions as well, in contrast
+ * to the {@link ConfigurableBeanFactory#resolveEmbeddedValue} method.
+ * The {@link BeanExpressionContext} used is for the plain bean factory,
+ * with no scope specified for any contextual objects to access.
+ *
+ * @author Juergen Hoeller
+ * @since 4.3
+ * @see ConfigurableBeanFactory#resolveEmbeddedValue(String)
+ * @see ConfigurableBeanFactory#getBeanExpressionResolver()
+ * @see BeanExpressionContext
+ */
+public class EmbeddedValueResolver implements StringValueResolver {
+
+ private final BeanExpressionContext exprContext;
+
+ private final BeanExpressionResolver exprResolver;
+
+
+ public EmbeddedValueResolver(ConfigurableBeanFactory beanFactory) {
+ this.exprContext = new BeanExpressionContext(beanFactory, null);
+ this.exprResolver = beanFactory.getBeanExpressionResolver();
+ }
+
+
+ @Override
+ public String resolveStringValue(String strVal) {
+ String value = this.exprContext.getBeanFactory().resolveEmbeddedValue(strVal);
+ if (this.exprResolver != null && value != null) {
+ Object evaluated = this.exprResolver.evaluate(value, this.exprContext);
+ value = (evaluated != null ? evaluated.toString() : null);
+ }
+ return value;
+ }
+
+}
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.java
index f508e3ff..964ee204 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.java
@@ -96,7 +96,7 @@ public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
* dependency types - which the factory handles specifically - already filtered out)
* @param bean the bean instance created, but whose properties have not yet been set
* @param beanName the name of the bean
- * @return the actual property values to apply to to the given bean
+ * @return the actual property values to apply to the given bean
* (can be the passed-in PropertyValues instance), or {@code null}
* to skip property population
* @throws org.springframework.beans.BeansException in case of errors
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/PlaceholderConfigurerSupport.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/PlaceholderConfigurerSupport.java
index a0152ed4..f7223a9a 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/config/PlaceholderConfigurerSupport.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/PlaceholderConfigurerSupport.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -107,6 +107,8 @@ public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfi
/** Defaults to {@value #DEFAULT_VALUE_SEPARATOR} */
protected String valueSeparator = DEFAULT_VALUE_SEPARATOR;
+ protected boolean trimValues = false;
+
protected String nullValue;
protected boolean ignoreUnresolvablePlaceholders = false;
@@ -143,6 +145,16 @@ public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfi
}
/**
+ * Specify whether to trim resolved values before applying them,
+ * removing superfluous whitespace from the beginning and end.
+ * <p>Default is {@code false}.
+ * @since 4.3
+ */
+ public void setTrimValues(boolean trimValues) {
+ this.trimValues = trimValues;
+ }
+
+ /**
* Set a value that should be treated as {@code null} when resolved
* as a placeholder value: e.g. "" (empty String) or "null".
* <p>Note that this will only apply to full property values,
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/PropertiesFactoryBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/PropertiesFactoryBean.java
index 93a97a8d..adc415d0 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/config/PropertiesFactoryBean.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/PropertiesFactoryBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -95,7 +95,7 @@ public class PropertiesFactoryBean extends PropertiesLoaderSupport
* <p>Invoked on initialization of this FactoryBean in case of a
* shared singleton; else, on each {@link #getObject()} call.
* @return the object returned by this factory
- * @throws IOException if an exception occured during properties loading
+ * @throws IOException if an exception occurred during properties loading
* @see #mergeProperties()
*/
protected Properties createProperties() throws IOException {
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurer.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurer.java
index f98ce40e..a0243a33 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurer.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -255,8 +255,11 @@ public class PropertyPlaceholderConfigurer extends PlaceholderConfigurerSupport
@Override
public String resolveStringValue(String strVal) throws BeansException {
- String value = this.helper.replacePlaceholders(strVal, this.resolver);
- return (value.equals(nullValue) ? null : value);
+ String resolved = this.helper.replacePlaceholders(strVal, this.resolver);
+ if (trimValues) {
+ resolved = resolved.trim();
+ }
+ return (resolved.equals(nullValue) ? null : resolved);
}
}
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/Scope.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/Scope.java
index 8777fa61..2911a123 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/config/Scope.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/Scope.java
@@ -100,7 +100,7 @@ public interface Scope {
* at the appropriate time. If such a callback is not supported by the
* underlying runtime environment at all, the callback <i>must be
* ignored and a corresponding warning should be logged</i>.
- * <p>Note that 'destruction' refers to to automatic destruction of
+ * <p>Note that 'destruction' refers to automatic destruction of
* the object as part of the scope's own lifecycle, not to the individual
* scoped object having been explicitly removed by the application.
* If a scoped object gets removed via this facade's {@link #remove(String)}
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinitionReader.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinitionReader.java
index e90712fc..bca9efeb 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinitionReader.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinitionReader.java
@@ -68,7 +68,7 @@ public abstract class AbstractBeanDefinitionReader implements EnvironmentCapable
* {@link org.springframework.context.ApplicationContext} implementations.
* <p>If given a plain BeanDefinitionRegistry, the default ResourceLoader will be a
* {@link org.springframework.core.io.support.PathMatchingResourcePatternResolver}.
- * <p>If the the passed-in bean factory also implements {@link EnvironmentCapable} its
+ * <p>If the passed-in bean factory also implements {@link EnvironmentCapable} its
* environment will be used by this reader. Otherwise, the reader will initialize and
* use a {@link StandardEnvironment}. All ApplicationContext implementations are
* EnvironmentCapable, while normal BeanFactory implementations are not.
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 80e91528..62dfa38e 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
@@ -799,6 +799,11 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
}
@Override
+ public boolean hasEmbeddedValueResolver() {
+ return !this.embeddedValueResolvers.isEmpty();
+ }
+
+ @Override
public String resolveEmbeddedValue(String value) {
String result = value;
for (StringValueResolver resolver : this.embeddedValueResolvers) {
@@ -1619,7 +1624,8 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
*/
protected boolean requiresDestruction(Object bean, RootBeanDefinition mbd) {
return (bean != null &&
- (DisposableBeanAdapter.hasDestroyMethod(bean, mbd) || hasDestructionAwareBeanPostProcessors()));
+ (DisposableBeanAdapter.hasDestroyMethod(bean, mbd) || (hasDestructionAwareBeanPostProcessors() &&
+ DisposableBeanAdapter.hasApplicableProcessors(bean, getBeanPostProcessors()))));
}
/**
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java
index 41653c84..cbb9d2f1 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -115,7 +115,7 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
Class<?> subclass = createEnhancedSubclass(this.beanDefinition);
Object instance;
if (ctor == null) {
- instance = BeanUtils.instantiate(subclass);
+ instance = BeanUtils.instantiateClass(subclass);
}
else {
try {
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java
index 50c56791..7c871d46 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -40,12 +40,14 @@ import org.springframework.beans.TypeConverter;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanDefinitionStoreException;
+import org.springframework.beans.factory.InjectionPoint;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter;
+import org.springframework.core.NamedThreadLocal;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.util.ClassUtils;
import org.springframework.util.MethodInvoker;
@@ -68,6 +70,9 @@ import org.springframework.util.StringUtils;
*/
class ConstructorResolver {
+ private static final NamedThreadLocal<InjectionPoint> currentInjectionPoint =
+ new NamedThreadLocal<InjectionPoint>("Current injection point");
+
private final AbstractAutowireCapableBeanFactory beanFactory;
@@ -151,7 +156,7 @@ class ConstructorResolver {
catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Resolution of declared constructors on bean Class [" + beanClass.getName() +
- "] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex);
+ "] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex);
}
}
AutowireUtils.sortConstructors(candidates);
@@ -159,8 +164,7 @@ class ConstructorResolver {
Set<Constructor<?>> ambiguousConstructors = null;
LinkedList<UnsatisfiedDependencyException> causes = null;
- for (int i = 0; i < candidates.length; i++) {
- Constructor<?> candidate = candidates[i];
+ for (Constructor<?> candidate : candidates) {
Class<?>[] paramTypes = candidate.getParameterTypes();
if (constructorToUse != null && argsToUse.length > paramTypes.length) {
@@ -182,8 +186,8 @@ class ConstructorResolver {
paramNames = pnd.getParameterNames(candidate);
}
}
- argsHolder = createArgumentArray(
- beanName, mbd, resolvedValues, bw, paramTypes, paramNames, candidate, autowiring);
+ argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,
+ getUserDeclaredConstructor(candidate), autowiring);
}
catch (UnsatisfiedDependencyException ex) {
if (this.beanFactory.logger.isTraceEnabled()) {
@@ -268,7 +272,7 @@ class ConstructorResolver {
mbd, beanName, this.beanFactory, constructorToUse, argsToUse);
}
- bw.setWrappedInstance(beanInstance);
+ bw.setBeanInstance(beanInstance);
return bw;
}
catch (Throwable ex) {
@@ -444,10 +448,9 @@ class ConstructorResolver {
minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
}
- List<Exception> causes = null;
+ LinkedList<UnsatisfiedDependencyException> causes = null;
- for (int i = 0; i < candidates.length; i++) {
- Method candidate = candidates[i];
+ for (Method candidate : candidates) {
Class<?>[] paramTypes = candidate.getParameterTypes();
if (paramTypes.length >= minNrOfArgs) {
@@ -469,22 +472,12 @@ class ConstructorResolver {
this.beanFactory.logger.trace("Ignoring factory method [" + candidate +
"] of bean '" + beanName + "': " + ex);
}
- if (i == candidates.length - 1 && argsHolderToUse == null) {
- if (causes != null) {
- for (Exception cause : causes) {
- this.beanFactory.onSuppressedException(cause);
- }
- }
- throw ex;
- }
- else {
- // Swallow and try next overloaded factory method.
- if (causes == null) {
- causes = new LinkedList<Exception>();
- }
- causes.add(ex);
- continue;
+ // Swallow and try next overloaded factory method.
+ if (causes == null) {
+ causes = new LinkedList<UnsatisfiedDependencyException>();
}
+ causes.add(ex);
+ continue;
}
}
@@ -525,6 +518,13 @@ class ConstructorResolver {
}
if (factoryMethodToUse == null) {
+ if (causes != null) {
+ UnsatisfiedDependencyException ex = causes.removeLast();
+ for (Exception cause : causes) {
+ this.beanFactory.onSuppressedException(cause);
+ }
+ throw ex;
+ }
List<String> argTypes = new ArrayList<String>(minNrOfArgs);
if (explicitArgs != null) {
for (Object arg : explicitArgs) {
@@ -592,7 +592,7 @@ class ConstructorResolver {
if (beanInstance == null) {
return null;
}
- bw.setWrappedInstance(beanInstance);
+ bw.setBeanInstance(beanInstance);
return bw;
}
catch (Throwable ex) {
@@ -609,8 +609,8 @@ class ConstructorResolver {
private int resolveConstructorArguments(String beanName, RootBeanDefinition mbd, BeanWrapper bw,
ConstructorArgumentValues cargs, ConstructorArgumentValues resolvedValues) {
- TypeConverter converter = (this.beanFactory.getCustomTypeConverter() != null ?
- this.beanFactory.getCustomTypeConverter() : bw);
+ TypeConverter customConverter = this.beanFactory.getCustomTypeConverter();
+ TypeConverter converter = (customConverter != null ? customConverter : bw);
BeanDefinitionValueResolver valueResolver =
new BeanDefinitionValueResolver(this.beanFactory, beanName, mbd, converter);
@@ -665,9 +665,8 @@ class ConstructorResolver {
BeanWrapper bw, Class<?>[] paramTypes, String[] paramNames, Object methodOrCtor,
boolean autowiring) throws UnsatisfiedDependencyException {
- String methodType = (methodOrCtor instanceof Constructor ? "constructor" : "factory method");
- TypeConverter converter = (this.beanFactory.getCustomTypeConverter() != null ?
- this.beanFactory.getCustomTypeConverter() : bw);
+ TypeConverter customConverter = this.beanFactory.getCustomTypeConverter();
+ TypeConverter converter = (customConverter != null ? customConverter : bw);
ArgumentsHolder args = new ArgumentsHolder(paramTypes.length);
Set<ConstructorArgumentValues.ValueHolder> usedValueHolders =
@@ -676,14 +675,14 @@ class ConstructorResolver {
for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) {
Class<?> paramType = paramTypes[paramIndex];
- String paramName = (paramNames != null ? paramNames[paramIndex] : null);
+ String paramName = (paramNames != null ? paramNames[paramIndex] : "");
// Try to find matching constructor argument value, either indexed or generic.
ConstructorArgumentValues.ValueHolder valueHolder =
resolvedValues.getArgumentValue(paramIndex, paramType, paramName, usedValueHolders);
// If we couldn't find a direct match and are not supposed to autowire,
// let's try the next generic, untyped argument value as fallback:
// it could match after type conversion (for example, String -> int).
- if (valueHolder == null && !autowiring) {
+ if (valueHolder == null && (!autowiring || paramTypes.length == resolvedValues.getArgumentCount())) {
valueHolder = resolvedValues.getGenericArgumentValue(null, null, usedValueHolders);
}
if (valueHolder != null) {
@@ -700,9 +699,9 @@ class ConstructorResolver {
ConstructorArgumentValues.ValueHolder sourceHolder =
(ConstructorArgumentValues.ValueHolder) valueHolder.getSource();
Object sourceValue = sourceHolder.getValue();
+ MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, paramIndex);
try {
- convertedValue = converter.convertIfNecessary(originalValue, paramType,
- MethodParameter.forMethodOrConstructor(methodOrCtor, paramIndex));
+ convertedValue = converter.convertIfNecessary(originalValue, paramType, methodParam);
// TODO re-enable once race condition has been found (SPR-7423)
/*
if (originalValue == sourceValue || sourceValue instanceof TypedStringValue) {
@@ -718,8 +717,8 @@ class ConstructorResolver {
}
catch (TypeMismatchException ex) {
throw new UnsatisfiedDependencyException(
- mbd.getResourceDescription(), beanName, paramIndex, paramType,
- "Could not convert " + methodType + " argument value of type [" +
+ mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam),
+ "Could not convert argument value of type [" +
ObjectUtils.nullSafeClassName(valueHolder.getValue()) +
"] to required type [" + paramType.getName() + "]: " + ex.getMessage());
}
@@ -728,17 +727,18 @@ class ConstructorResolver {
args.rawArguments[paramIndex] = originalValue;
}
else {
+ MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, paramIndex);
// No explicit match found: we're either supposed to autowire or
// have to fail creating an argument array for the given constructor.
if (!autowiring) {
throw new UnsatisfiedDependencyException(
- mbd.getResourceDescription(), beanName, paramIndex, paramType,
- "Ambiguous " + methodType + " argument types - " +
- "did you specify the correct bean references as " + methodType + " arguments?");
+ mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam),
+ "Ambiguous argument values for parameter of type [" + paramType.getName() +
+ "] - did you specify the correct bean references as arguments?");
}
try {
- MethodParameter param = MethodParameter.forMethodOrConstructor(methodOrCtor, paramIndex);
- Object autowiredArgument = resolveAutowiredArgument(param, beanName, autowiredBeanNames, converter);
+ Object autowiredArgument =
+ resolveAutowiredArgument(methodParam, beanName, autowiredBeanNames, converter);
args.rawArguments[paramIndex] = autowiredArgument;
args.arguments[paramIndex] = autowiredArgument;
args.preparedArguments[paramIndex] = new AutowiredArgumentMarker();
@@ -746,7 +746,7 @@ class ConstructorResolver {
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(
- mbd.getResourceDescription(), beanName, paramIndex, paramType, ex);
+ mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam), ex);
}
}
}
@@ -755,7 +755,8 @@ class ConstructorResolver {
this.beanFactory.registerDependentBean(autowiredBeanName, beanName);
if (this.beanFactory.logger.isDebugEnabled()) {
this.beanFactory.logger.debug("Autowiring by type from bean name '" + beanName +
- "' via " + methodType + " to bean named '" + autowiredBeanName + "'");
+ "' via " + (methodOrCtor instanceof Constructor ? "constructor" : "factory method") +
+ " to bean named '" + autowiredBeanName + "'");
}
}
@@ -768,12 +769,13 @@ class ConstructorResolver {
private Object[] resolvePreparedArguments(
String beanName, RootBeanDefinition mbd, BeanWrapper bw, Member methodOrCtor, Object[] argsToResolve) {
- Class<?>[] paramTypes = (methodOrCtor instanceof Method ?
- ((Method) methodOrCtor).getParameterTypes() : ((Constructor<?>) methodOrCtor).getParameterTypes());
- TypeConverter converter = (this.beanFactory.getCustomTypeConverter() != null ?
- this.beanFactory.getCustomTypeConverter() : bw);
+ TypeConverter customConverter = this.beanFactory.getCustomTypeConverter();
+ TypeConverter converter = (customConverter != null ? customConverter : bw);
BeanDefinitionValueResolver valueResolver =
new BeanDefinitionValueResolver(this.beanFactory, beanName, mbd, converter);
+ Class<?>[] paramTypes = (methodOrCtor instanceof Method ?
+ ((Method) methodOrCtor).getParameterTypes() : ((Constructor<?>) methodOrCtor).getParameterTypes());
+
Object[] resolvedArgs = new Object[argsToResolve.length];
for (int argIndex = 0; argIndex < argsToResolve.length; argIndex++) {
Object argValue = argsToResolve[argIndex];
@@ -793,28 +795,61 @@ class ConstructorResolver {
resolvedArgs[argIndex] = converter.convertIfNecessary(argValue, paramType, methodParam);
}
catch (TypeMismatchException ex) {
- String methodType = (methodOrCtor instanceof Constructor ? "constructor" : "factory method");
throw new UnsatisfiedDependencyException(
- mbd.getResourceDescription(), beanName, argIndex, paramType,
- "Could not convert " + methodType + " argument value of type [" +
- ObjectUtils.nullSafeClassName(argValue) +
+ mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam),
+ "Could not convert argument value of type [" + ObjectUtils.nullSafeClassName(argValue) +
"] to required type [" + paramType.getName() + "]: " + ex.getMessage());
}
}
return resolvedArgs;
}
+ protected Constructor<?> getUserDeclaredConstructor(Constructor<?> constructor) {
+ Class<?> declaringClass = constructor.getDeclaringClass();
+ Class<?> userClass = ClassUtils.getUserClass(declaringClass);
+ if (userClass != declaringClass) {
+ try {
+ return userClass.getDeclaredConstructor(constructor.getParameterTypes());
+ }
+ catch (NoSuchMethodException ex) {
+ // No equivalent constructor on user class (superclass)...
+ // Let's proceed with the given constructor as we usually would.
+ }
+ }
+ return constructor;
+ }
+
/**
* Template method for resolving the specified argument which is supposed to be autowired.
*/
protected Object resolveAutowiredArgument(
MethodParameter param, String beanName, Set<String> autowiredBeanNames, TypeConverter typeConverter) {
+ if (InjectionPoint.class.isAssignableFrom(param.getParameterType())) {
+ InjectionPoint injectionPoint = currentInjectionPoint.get();
+ if (injectionPoint == null) {
+ throw new IllegalStateException("No current InjectionPoint available for " + param);
+ }
+ return injectionPoint;
+ }
return this.beanFactory.resolveDependency(
new DependencyDescriptor(param, true), beanName, autowiredBeanNames, typeConverter);
}
+
+ static InjectionPoint setCurrentInjectionPoint(InjectionPoint injectionPoint) {
+ InjectionPoint old = currentInjectionPoint.get();
+ if (injectionPoint != null) {
+ currentInjectionPoint.set(injectionPoint);
+ }
+ else {
+ currentInjectionPoint.remove();
+ }
+ return old;
+ }
+
+
/**
* Private inner class for holding argument combinations.
*/
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 7a32060e..bb4ef59b 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,7 +45,6 @@ import java.util.concurrent.ConcurrentHashMap;
import javax.inject.Provider;
import org.springframework.beans.BeansException;
-import org.springframework.beans.FatalBeanException;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanCurrentlyInCreationException;
@@ -53,11 +52,14 @@ import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanFactoryUtils;
+import org.springframework.beans.factory.BeanNotOfRequiredTypeException;
import org.springframework.beans.factory.CannotLoadBeanClassException;
import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.InjectionPoint;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.beans.factory.ObjectFactory;
+import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.SmartFactoryBean;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.config.BeanDefinition;
@@ -615,10 +617,12 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
@Override
public void registerResolvableDependency(Class<?> dependencyType, Object autowiredValue) {
- Assert.notNull(dependencyType, "Type must not be null");
+ Assert.notNull(dependencyType, "Dependency type must not be null");
if (autowiredValue != null) {
- Assert.isTrue((autowiredValue instanceof ObjectFactory || dependencyType.isInstance(autowiredValue)),
- "Value [" + autowiredValue + "] does not implement specified type [" + dependencyType.getName() + "]");
+ if (!(autowiredValue instanceof ObjectFactory || dependencyType.isInstance(autowiredValue))) {
+ throw new IllegalArgumentException("Value [" + autowiredValue +
+ "] does not implement specified dependency type [" + dependencyType.getName() + "]");
+ }
this.resolvableDependencies.put(dependencyType, autowiredValue);
}
}
@@ -999,14 +1003,15 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException {
descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
- if (descriptor.getDependencyType().equals(javaUtilOptionalClass)) {
+ if (javaUtilOptionalClass == descriptor.getDependencyType()) {
return new OptionalDependencyFactory().createOptionalDependency(descriptor, beanName);
}
- else if (ObjectFactory.class == descriptor.getDependencyType()) {
- return new DependencyObjectFactory(descriptor, beanName);
+ else if (ObjectFactory.class == descriptor.getDependencyType() ||
+ ObjectProvider.class == descriptor.getDependencyType()) {
+ return new DependencyObjectProvider(descriptor, beanName);
}
else if (javaxInjectProviderClass == descriptor.getDependencyType()) {
- return new DependencyProviderFactory().createDependencyProvider(descriptor, beanName);
+ return new Jsr330ProviderFactory().createDependencyProvider(descriptor, beanName);
}
else {
Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(descriptor, beanName);
@@ -1020,29 +1025,79 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
public Object doResolveDependency(DependencyDescriptor descriptor, String beanName,
Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException {
- Class<?> type = descriptor.getDependencyType();
- Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
- if (value != null) {
- if (value instanceof String) {
- String strVal = resolveEmbeddedValue((String) value);
- BeanDefinition bd = (beanName != null && containsBean(beanName) ? getMergedBeanDefinition(beanName) : null);
- value = evaluateBeanDefinitionString(strVal, bd);
+ InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
+ try {
+ Object shortcut = descriptor.resolveShortcut(this);
+ if (shortcut != null) {
+ return shortcut;
}
- TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
- return (descriptor.getField() != null ?
- converter.convertIfNecessary(value, type, descriptor.getField()) :
- converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
+
+ Class<?> type = descriptor.getDependencyType();
+ Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
+ if (value != null) {
+ if (value instanceof String) {
+ String strVal = resolveEmbeddedValue((String) value);
+ BeanDefinition bd = (beanName != null && containsBean(beanName) ? getMergedBeanDefinition(beanName) : null);
+ value = evaluateBeanDefinitionString(strVal, bd);
+ }
+ TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
+ return (descriptor.getField() != null ?
+ converter.convertIfNecessary(value, type, descriptor.getField()) :
+ converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
+ }
+
+ Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
+ if (multipleBeans != null) {
+ return multipleBeans;
+ }
+
+ Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
+ if (matchingBeans.isEmpty()) {
+ if (descriptor.isRequired()) {
+ raiseNoMatchingBeanFound(type, descriptor.getResolvableType().toString(), descriptor);
+ }
+ return null;
+ }
+ if (matchingBeans.size() > 1) {
+ String primaryBeanName = determineAutowireCandidate(matchingBeans, descriptor);
+ if (primaryBeanName == null) {
+ if (descriptor.isRequired() || !indicatesMultipleBeans(type)) {
+ return descriptor.resolveNotUnique(type, matchingBeans);
+ }
+ else {
+ // In case of an optional Collection/Map, silently ignore a non-unique case:
+ // possibly it was meant to be an empty collection of multiple regular beans
+ // (before 4.3 in particular when we didn't even look for collection beans).
+ return null;
+ }
+ }
+ if (autowiredBeanNames != null) {
+ autowiredBeanNames.add(primaryBeanName);
+ }
+ return matchingBeans.get(primaryBeanName);
+ }
+ // We have exactly one match.
+ Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
+ if (autowiredBeanNames != null) {
+ autowiredBeanNames.add(entry.getKey());
+ }
+ return entry.getValue();
+ }
+ finally {
+ ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
}
+ }
+
+ private Object resolveMultipleBeans(DependencyDescriptor descriptor, String beanName,
+ Set<String> autowiredBeanNames, TypeConverter typeConverter) {
+ Class<?> type = descriptor.getDependencyType();
if (type.isArray()) {
Class<?> componentType = type.getComponentType();
DependencyDescriptor targetDesc = new DependencyDescriptor(descriptor);
targetDesc.increaseNestingLevel();
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, componentType, targetDesc);
if (matchingBeans.isEmpty()) {
- if (descriptor.isRequired()) {
- raiseNoSuchBeanDefinitionException(componentType, "array of " + componentType.getName(), descriptor);
- }
return null;
}
if (autowiredBeanNames != null) {
@@ -1058,18 +1113,12 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
else if (Collection.class.isAssignableFrom(type) && type.isInterface()) {
Class<?> elementType = descriptor.getCollectionType();
if (elementType == null) {
- if (descriptor.isRequired()) {
- throw new FatalBeanException("No element type declared for collection [" + type.getName() + "]");
- }
return null;
}
DependencyDescriptor targetDesc = new DependencyDescriptor(descriptor);
targetDesc.increaseNestingLevel();
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, elementType, targetDesc);
if (matchingBeans.isEmpty()) {
- if (descriptor.isRequired()) {
- raiseNoSuchBeanDefinitionException(elementType, "collection of " + elementType.getName(), descriptor);
- }
return null;
}
if (autowiredBeanNames != null) {
@@ -1085,26 +1134,16 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
else if (Map.class.isAssignableFrom(type) && type.isInterface()) {
Class<?> keyType = descriptor.getMapKeyType();
if (String.class != keyType) {
- if (descriptor.isRequired()) {
- throw new FatalBeanException("Key type [" + keyType + "] of map [" + type.getName() +
- "] must be [java.lang.String]");
- }
return null;
}
Class<?> valueType = descriptor.getMapValueType();
if (valueType == null) {
- if (descriptor.isRequired()) {
- throw new FatalBeanException("No value type declared for map [" + type.getName() + "]");
- }
return null;
}
DependencyDescriptor targetDesc = new DependencyDescriptor(descriptor);
targetDesc.increaseNestingLevel();
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, valueType, targetDesc);
if (matchingBeans.isEmpty()) {
- if (descriptor.isRequired()) {
- raiseNoSuchBeanDefinitionException(valueType, "map with value type " + valueType.getName(), descriptor);
- }
return null;
}
if (autowiredBeanNames != null) {
@@ -1113,32 +1152,15 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
return matchingBeans;
}
else {
- Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
- if (matchingBeans.isEmpty()) {
- if (descriptor.isRequired()) {
- raiseNoSuchBeanDefinitionException(type, "", descriptor);
- }
- return null;
- }
- if (matchingBeans.size() > 1) {
- String primaryBeanName = determineAutowireCandidate(matchingBeans, descriptor);
- if (primaryBeanName == null) {
- throw new NoUniqueBeanDefinitionException(type, matchingBeans.keySet());
- }
- if (autowiredBeanNames != null) {
- autowiredBeanNames.add(primaryBeanName);
- }
- return matchingBeans.get(primaryBeanName);
- }
- // We have exactly one match.
- Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
- if (autowiredBeanNames != null) {
- autowiredBeanNames.add(entry.getKey());
- }
- return entry.getValue();
+ return null;
}
}
+ private boolean indicatesMultipleBeans(Class<?> type) {
+ return (type.isArray() || (type.isInterface() &&
+ (Collection.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type))));
+ }
+
private Comparator<Object> adaptDependencyComparator(Map<String, Object> matchingBeans) {
Comparator<Object> comparator = getDependencyComparator();
if (comparator instanceof OrderComparator) {
@@ -1189,14 +1211,23 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
}
for (String candidateName : candidateNames) {
if (!isSelfReference(beanName, candidateName) && isAutowireCandidate(candidateName, descriptor)) {
- result.put(candidateName, getBean(candidateName));
+ result.put(candidateName, descriptor.resolveCandidate(candidateName, requiredType, this));
}
}
- if (result.isEmpty()) {
+ if (result.isEmpty() && !indicatesMultipleBeans(requiredType)) {
+ // Consider fallback matches if the first pass failed to find anything...
DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch();
for (String candidateName : candidateNames) {
- if (!candidateName.equals(beanName) && isAutowireCandidate(candidateName, fallbackDescriptor)) {
- result.put(candidateName, getBean(candidateName));
+ if (!isSelfReference(beanName, candidateName) && isAutowireCandidate(candidateName, fallbackDescriptor)) {
+ result.put(candidateName, descriptor.resolveCandidate(candidateName, requiredType, this));
+ }
+ }
+ if (result.isEmpty()) {
+ // Consider self references before as a final pass
+ for (String candidateName : candidateNames) {
+ if (isSelfReference(beanName, candidateName) && isAutowireCandidate(candidateName, fallbackDescriptor)) {
+ result.put(candidateName, descriptor.resolveCandidate(candidateName, requiredType, this));
+ }
}
}
}
@@ -1362,17 +1393,43 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
}
/**
- * Raise a NoSuchBeanDefinitionException for an unresolvable dependency.
+ * Raise a NoSuchBeanDefinitionException or BeanNotOfRequiredTypeException
+ * for an unresolvable dependency.
*/
- private void raiseNoSuchBeanDefinitionException(
- Class<?> type, String dependencyDescription, DependencyDescriptor descriptor)
- throws NoSuchBeanDefinitionException {
+ private void raiseNoMatchingBeanFound(
+ Class<?> type, String dependencyDescription, DependencyDescriptor descriptor) throws BeansException {
+
+ checkBeanNotOfRequiredType(type, descriptor);
throw new NoSuchBeanDefinitionException(type, dependencyDescription,
"expected at least 1 bean which qualifies as autowire candidate for this dependency. " +
"Dependency annotations: " + ObjectUtils.nullSafeToString(descriptor.getAnnotations()));
}
+ /**
+ * Raise a BeanNotOfRequiredTypeException for an unresolvable dependency, if applicable,
+ * i.e. if the target type of the bean would match but an exposed proxy doesn't.
+ */
+ private void checkBeanNotOfRequiredType(Class<?> type, DependencyDescriptor descriptor) {
+ for (String beanName : this.beanDefinitionNames) {
+ RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
+ Class<?> targetType = mbd.getTargetType();
+ if (targetType != null && type.isAssignableFrom(targetType) &&
+ isAutowireCandidate(beanName, mbd, descriptor, getAutowireCandidateResolver())) {
+ // Probably a poxy interfering with target type match -> throw meaningful exception.
+ Object beanInstance = getSingleton(beanName, false);
+ Class<?> beanType = (beanInstance != null ? beanInstance.getClass() : predictBeanType(beanName, mbd));
+ if (type != beanType) {
+ throw new BeanNotOfRequiredTypeException(beanName, type, beanType);
+ }
+ }
+ }
+
+ if (getParentBeanFactory() instanceof DefaultListableBeanFactory) {
+ ((DefaultListableBeanFactory) getParentBeanFactory()).checkBeanNotOfRequiredType(type, descriptor);
+ }
+ }
+
@Override
public String toString() {
@@ -1424,16 +1481,14 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
private Object readResolve() {
Reference<?> ref = serializableFactories.get(this.id);
- if (ref == null) {
- throw new IllegalStateException(
- "Cannot deserialize BeanFactory with id " + this.id + ": no factory registered for this id");
- }
- Object result = ref.get();
- if (result == null) {
- throw new IllegalStateException(
- "Cannot deserialize BeanFactory with id " + this.id + ": factory has been garbage-collected");
+ if (ref != null) {
+ Object result = ref.get();
+ if (result != null) {
+ return result;
+ }
}
- return result;
+ // Lenient fallback: dummy factory in case of original factory not found...
+ return new StaticListableBeanFactory(Collections.<String, Object> emptyMap());
}
}
@@ -1444,12 +1499,17 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
@UsesJava8
private class OptionalDependencyFactory {
- public Object createOptionalDependency(DependencyDescriptor descriptor, String beanName) {
+ public Object createOptionalDependency(DependencyDescriptor descriptor, String beanName, final Object... args) {
DependencyDescriptor descriptorToUse = new DependencyDescriptor(descriptor) {
@Override
public boolean isRequired() {
return false;
}
+ @Override
+ public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory) {
+ return (!ObjectUtils.isEmpty(args) ? beanFactory.getBean(beanName, requiredType, args) :
+ super.resolveCandidate(beanName, requiredType, beanFactory));
+ }
};
descriptorToUse.increaseNestingLevel();
return Optional.ofNullable(doResolveDependency(descriptorToUse, beanName, null, null));
@@ -1458,9 +1518,9 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
/**
- * Serializable ObjectFactory for lazy resolution of a dependency.
+ * Serializable ObjectFactory/ObjectProvider for lazy resolution of a dependency.
*/
- private class DependencyObjectFactory implements ObjectFactory<Object>, Serializable {
+ private class DependencyObjectProvider implements ObjectProvider<Object>, Serializable {
private final DependencyDescriptor descriptor;
@@ -1468,7 +1528,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
private final String beanName;
- public DependencyObjectFactory(DependencyDescriptor descriptor, String beanName) {
+ public DependencyObjectProvider(DependencyDescriptor descriptor, String beanName) {
this.descriptor = new DependencyDescriptor(descriptor);
this.descriptor.increaseNestingLevel();
this.optional = this.descriptor.getDependencyType().equals(javaUtilOptionalClass);
@@ -1484,15 +1544,67 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
return doResolveDependency(this.descriptor, this.beanName, null, null);
}
}
+
+ @Override
+ public Object getObject(final Object... args) throws BeansException {
+ if (this.optional) {
+ return new OptionalDependencyFactory().createOptionalDependency(this.descriptor, this.beanName, args);
+ }
+ else {
+ DependencyDescriptor descriptorToUse = new DependencyDescriptor(descriptor) {
+ @Override
+ public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory) {
+ return ((AbstractBeanFactory) beanFactory).getBean(beanName, requiredType, args);
+ }
+ };
+ return doResolveDependency(descriptorToUse, this.beanName, null, null);
+ }
+ }
+
+ @Override
+ public Object getIfAvailable() throws BeansException {
+ if (this.optional) {
+ return new OptionalDependencyFactory().createOptionalDependency(this.descriptor, this.beanName);
+ }
+ else {
+ DependencyDescriptor descriptorToUse = new DependencyDescriptor(descriptor) {
+ @Override
+ public boolean isRequired() {
+ return false;
+ }
+ };
+ return doResolveDependency(descriptorToUse, this.beanName, null, null);
+ }
+ }
+
+ @Override
+ public Object getIfUnique() throws BeansException {
+ DependencyDescriptor descriptorToUse = new DependencyDescriptor(descriptor) {
+ @Override
+ public boolean isRequired() {
+ return false;
+ }
+ @Override
+ public Object resolveNotUnique(Class<?> type, Map<String, Object> matchingBeans) {
+ return null;
+ }
+ };
+ if (this.optional) {
+ return new OptionalDependencyFactory().createOptionalDependency(descriptorToUse, this.beanName);
+ }
+ else {
+ return doResolveDependency(descriptorToUse, this.beanName, null, null);
+ }
+ }
}
/**
* Serializable ObjectFactory for lazy resolution of a dependency.
*/
- private class DependencyProvider extends DependencyObjectFactory implements Provider<Object> {
+ private class Jsr330DependencyProvider extends DependencyObjectProvider implements Provider<Object> {
- public DependencyProvider(DependencyDescriptor descriptor, String beanName) {
+ public Jsr330DependencyProvider(DependencyDescriptor descriptor, String beanName) {
super(descriptor, beanName);
}
@@ -1506,10 +1618,10 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
/**
* Separate inner class for avoiding a hard dependency on the {@code javax.inject} API.
*/
- private class DependencyProviderFactory {
+ private class Jsr330ProviderFactory {
public Object createDependencyProvider(DependencyDescriptor descriptor, String beanName) {
- return new DependencyProvider(descriptor, beanName);
+ return new Jsr330DependencyProvider(descriptor, beanName);
}
}
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java
index 9042bad5..d3d21347 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -449,10 +449,10 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
}
private boolean isDependent(String beanName, String dependentBeanName, Set<String> alreadySeen) {
- String canonicalName = canonicalName(beanName);
if (alreadySeen != null && alreadySeen.contains(beanName)) {
return false;
}
+ String canonicalName = canonicalName(beanName);
Set<String> dependentBeans = this.dependentBeanMap.get(canonicalName);
if (dependentBeans == null) {
return false;
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java
index d3a6c361..b79403d6 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java
@@ -139,7 +139,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
}
}
}
- this.beanPostProcessors = filterPostProcessors(postProcessors);
+ this.beanPostProcessors = filterPostProcessors(postProcessors, bean);
}
/**
@@ -155,7 +155,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
this.invokeDisposableBean = (this.bean instanceof DisposableBean);
this.nonPublicAccessAllowed = true;
this.acc = acc;
- this.beanPostProcessors = filterPostProcessors(postProcessors);
+ this.beanPostProcessors = filterPostProcessors(postProcessors, bean);
}
/**
@@ -214,16 +214,26 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
/**
* Search for all DestructionAwareBeanPostProcessors in the List.
- * @param postProcessors the List to search
+ * @param processors the List to search
* @return the filtered List of DestructionAwareBeanPostProcessors
*/
- private List<DestructionAwareBeanPostProcessor> filterPostProcessors(List<BeanPostProcessor> postProcessors) {
+ private List<DestructionAwareBeanPostProcessor> filterPostProcessors(List<BeanPostProcessor> processors, Object bean) {
List<DestructionAwareBeanPostProcessor> filteredPostProcessors = null;
- if (!CollectionUtils.isEmpty(postProcessors)) {
- filteredPostProcessors = new ArrayList<DestructionAwareBeanPostProcessor>(postProcessors.size());
- for (BeanPostProcessor postProcessor : postProcessors) {
- if (postProcessor instanceof DestructionAwareBeanPostProcessor) {
- filteredPostProcessors.add((DestructionAwareBeanPostProcessor) postProcessor);
+ if (!CollectionUtils.isEmpty(processors)) {
+ filteredPostProcessors = new ArrayList<DestructionAwareBeanPostProcessor>(processors.size());
+ for (BeanPostProcessor processor : processors) {
+ if (processor instanceof DestructionAwareBeanPostProcessor) {
+ DestructionAwareBeanPostProcessor dabpp = (DestructionAwareBeanPostProcessor) processor;
+ try {
+ if (dabpp.requiresDestruction(bean)) {
+ filteredPostProcessors.add(dabpp);
+ }
+ }
+ catch (AbstractMethodError err) {
+ // A pre-4.3 third-party DestructionAwareBeanPostProcessor...
+ // As of 5.0, we can let requiresDestruction be a Java 8 default method which returns true.
+ filteredPostProcessors.add(dabpp);
+ }
}
}
}
@@ -401,9 +411,36 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
}
String destroyMethodName = beanDefinition.getDestroyMethodName();
if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName)) {
- return ClassUtils.hasMethod(bean.getClass(), CLOSE_METHOD_NAME);
+ return (ClassUtils.hasMethod(bean.getClass(), CLOSE_METHOD_NAME) ||
+ ClassUtils.hasMethod(bean.getClass(), SHUTDOWN_METHOD_NAME));
}
return StringUtils.hasLength(destroyMethodName);
}
+ /**
+ * Check whether the given bean has destruction-aware post-processors applying to it.
+ * @param bean the bean instance
+ * @param postProcessors the post-processor candidates
+ */
+ public static boolean hasApplicableProcessors(Object bean, List<BeanPostProcessor> postProcessors) {
+ if (!CollectionUtils.isEmpty(postProcessors)) {
+ for (BeanPostProcessor processor : postProcessors) {
+ if (processor instanceof DestructionAwareBeanPostProcessor) {
+ DestructionAwareBeanPostProcessor dabpp = (DestructionAwareBeanPostProcessor) processor;
+ try {
+ if (dabpp.requiresDestruction(bean)) {
+ return true;
+ }
+ }
+ catch (AbstractMethodError err) {
+ // A pre-4.3 third-party DestructionAwareBeanPostProcessor...
+ // As of 5.0, we can let requiresDestruction be a Java 8 default method which returns true.
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
}
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java
index d2ac971c..91ad5d8e 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -125,7 +125,7 @@ public class RootBeanDefinition extends AbstractBeanDefinition {
setBeanClass(beanClass);
setAutowireMode(autowireMode);
if (dependencyCheck && getResolvedAutowireMode() != AUTOWIRE_CONSTRUCTOR) {
- setDependencyCheck(RootBeanDefinition.DEPENDENCY_CHECK_OBJECTS);
+ setDependencyCheck(DEPENDENCY_CHECK_OBJECTS);
}
}
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleInstantiationStrategy.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleInstantiationStrategy.java
index f878c57e..421805cb 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleInstantiationStrategy.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleInstantiationStrategy.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -81,7 +81,7 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy {
}
bd.resolvedConstructorOrFactoryMethod = constructorToUse;
}
- catch (Exception ex) {
+ catch (Throwable ex) {
throw new BeanInstantiationException(clazz, "No default constructor found", ex);
}
}
@@ -171,12 +171,12 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy {
}
}
catch (IllegalArgumentException ex) {
- throw new BeanInstantiationException(factoryMethod.getReturnType(),
+ throw new BeanInstantiationException(factoryMethod,
"Illegal arguments to factory method '" + factoryMethod.getName() + "'; " +
"args: " + StringUtils.arrayToCommaDelimitedString(args), ex);
}
catch (IllegalAccessException ex) {
- throw new BeanInstantiationException(factoryMethod.getReturnType(),
+ throw new BeanInstantiationException(factoryMethod,
"Cannot access factory method '" + factoryMethod.getName() + "'; is it public?", ex);
}
catch (InvocationTargetException ex) {
@@ -186,7 +186,7 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy {
msg = "Circular reference involving containing bean '" + bd.getFactoryBeanName() + "' - consider " +
"declaring the factory method as static for independence from its containing instance. " + msg;
}
- throw new BeanInstantiationException(factoryMethod.getReturnType(), msg, ex.getTargetException());
+ throw new BeanInstantiationException(factoryMethod, msg, ex.getTargetException());
}
}
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java
index 6e064c5d..32866846 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java
@@ -34,6 +34,7 @@ import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.beans.factory.SmartFactoryBean;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
@@ -58,7 +59,31 @@ import org.springframework.util.StringUtils;
public class StaticListableBeanFactory implements ListableBeanFactory {
/** Map from bean name to bean instance */
- private final Map<String, Object> beans = new LinkedHashMap<String, Object>();
+ private final Map<String, Object> beans;
+
+
+ /**
+ * Create a regular {@code StaticListableBeanFactory}, to be populated
+ * with singleton bean instances through {@link #addBean} calls.
+ */
+ public StaticListableBeanFactory() {
+ this.beans = new LinkedHashMap<String, Object>();
+ }
+
+ /**
+ * Create a {@code StaticListableBeanFactory} wrapping the given {@code Map}.
+ * <p>Note that the given {@code Map} may be pre-populated with beans;
+ * or new, still allowing for beans to be registered via {@link #addBean};
+ * or {@link java.util.Collections#emptyMap()} for a dummy factory which
+ * enforces operating against an empty set of beans.
+ * @param beans a {@code Map} for holding this factory's beans, with the
+ * bean name String as key and the corresponding singleton object as value
+ * @since 4.3
+ */
+ public StaticListableBeanFactory(Map<String, Object> beans) {
+ Assert.notNull(beans, "Beans Map must not be null");
+ this.beans = beans;
+ }
/**
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultBeanDefinitionDocumentReader.java b/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultBeanDefinitionDocumentReader.java
index 494bcd39..6e2f6ad9 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultBeanDefinitionDocumentReader.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultBeanDefinitionDocumentReader.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -129,6 +129,10 @@ public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocume
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
+ if (logger.isInfoEnabled()) {
+ logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
+ "] not matching: " + getReaderContext().getResource());
+ }
return;
}
}
diff --git a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/CustomCollectionEditor.java b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/CustomCollectionEditor.java
index 607a99af..4c63ac79 100644
--- a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/CustomCollectionEditor.java
+++ b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/CustomCollectionEditor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -154,9 +154,9 @@ public class CustomCollectionEditor extends PropertyEditorSupport {
try {
return collectionType.newInstance();
}
- catch (Exception ex) {
+ catch (Throwable ex) {
throw new IllegalArgumentException(
- "Could not instantiate collection class [" + collectionType.getName() + "]: " + ex.getMessage());
+ "Could not instantiate collection class: " + collectionType.getName(), ex);
}
}
else if (List.class == collectionType) {
diff --git a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/CustomMapEditor.java b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/CustomMapEditor.java
index 85865e54..05b5d872 100644
--- a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/CustomMapEditor.java
+++ b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/CustomMapEditor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -132,9 +132,9 @@ public class CustomMapEditor extends PropertyEditorSupport {
try {
return mapType.newInstance();
}
- catch (Exception ex) {
+ catch (Throwable ex) {
throw new IllegalArgumentException(
- "Could not instantiate map class [" + mapType.getName() + "]: " + ex.getMessage());
+ "Could not instantiate map class: " + mapType.getName(), ex);
}
}
else if (SortedMap.class == mapType) {
diff --git a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/FileEditor.java b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/FileEditor.java
index a57b69ea..676fd0c6 100644
--- a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/FileEditor.java
+++ b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/FileEditor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author 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,16 +59,14 @@ public class FileEditor extends PropertyEditorSupport {
/**
- * Create a new FileEditor,
- * using the default ResourceEditor underneath.
+ * Create a new FileEditor, using a default ResourceEditor underneath.
*/
public FileEditor() {
this.resourceEditor = new ResourceEditor();
}
/**
- * Create a new FileEditor,
- * using the given ResourceEditor underneath.
+ * Create a new FileEditor, using the given ResourceEditor underneath.
* @param resourceEditor the ResourceEditor to use
*/
public FileEditor(ResourceEditor resourceEditor) {
@@ -105,7 +103,7 @@ public class FileEditor extends PropertyEditorSupport {
}
catch (IOException ex) {
throw new IllegalArgumentException(
- "Could not retrieve File for " + resource + ": " + ex.getMessage());
+ "Could not retrieve file for " + resource + ": " + ex.getMessage());
}
}
else {
diff --git a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/InputStreamEditor.java b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/InputStreamEditor.java
index b4b014b7..dc982393 100644
--- a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/InputStreamEditor.java
+++ b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/InputStreamEditor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -47,16 +47,14 @@ public class InputStreamEditor extends PropertyEditorSupport {
/**
- * Create a new InputStreamEditor,
- * using the default ResourceEditor underneath.
+ * Create a new InputStreamEditor, using the default ResourceEditor underneath.
*/
public InputStreamEditor() {
this.resourceEditor = new ResourceEditor();
}
/**
- * Create a new InputStreamEditor,
- * using the given ResourceEditor underneath.
+ * Create a new InputStreamEditor, using the given ResourceEditor underneath.
* @param resourceEditor the ResourceEditor to use
*/
public InputStreamEditor(ResourceEditor resourceEditor) {
diff --git a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/PathEditor.java b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/PathEditor.java
new file mode 100644
index 00000000..4767b198
--- /dev/null
+++ b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/PathEditor.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.beans.propertyeditors;
+
+import java.beans.PropertyEditorSupport;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.FileSystemNotFoundException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceEditor;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.lang.UsesJava7;
+import org.springframework.util.Assert;
+
+/**
+ * Editor for {@code java.nio.file.Path}, to directly populate a Path
+ * property instead of using a String property as bridge.
+ *
+ * <p>Based on {@link Paths#get(URI)}'s resolution algorithm, checking
+ * registered NIO file system providers, including the default file system
+ * for "file:..." paths. Also supports Spring-style URL notation: any fully
+ * qualified standard URL and Spring's special "classpath:" pseudo-URL,
+ * as well as Spring's context-specific relative file paths.
+ *
+ * <p>Note that, in contrast to {@link FileEditor}, relative paths are only
+ * supported by Spring's resource abstraction here. Direct {@code Paths.get}
+ * resolution in a file system always has to go through the corresponding
+ * file system provider's scheme, i.e. "file" for the default file system.
+ *
+ * @author Juergen Hoeller
+ * @since 4.3.2
+ * @see java.nio.file.Path
+ * @see Paths#get(URI)
+ * @see ResourceEditor
+ * @see org.springframework.core.io.ResourceLoader
+ * @see FileEditor
+ * @see URLEditor
+ */
+@UsesJava7
+public class PathEditor extends PropertyEditorSupport {
+
+ private final ResourceEditor resourceEditor;
+
+
+ /**
+ * Create a new PathEditor, using the default ResourceEditor underneath.
+ */
+ public PathEditor() {
+ this.resourceEditor = new ResourceEditor();
+ }
+
+ /**
+ * Create a new PathEditor, using the given ResourceEditor underneath.
+ * @param resourceEditor the ResourceEditor to use
+ */
+ public PathEditor(ResourceEditor resourceEditor) {
+ Assert.notNull(resourceEditor, "ResourceEditor must not be null");
+ this.resourceEditor = resourceEditor;
+ }
+
+
+ @Override
+ public void setAsText(String text) throws IllegalArgumentException {
+ if (!text.startsWith("/") && !text.startsWith(ResourceLoader.CLASSPATH_URL_PREFIX)) {
+ try {
+ URI uri = new URI(text);
+ if (uri.getScheme() != null) {
+ // Let's try NIO file system providers via Paths.get(URI)
+ setValue(Paths.get(uri).normalize());
+ return;
+ }
+ }
+ catch (URISyntaxException ex) {
+ // Not a valid URI: Let's try as Spring resource location.
+ }
+ catch (FileSystemNotFoundException ex) {
+ // URI scheme not registered for NIO:
+ // Let's try URL protocol handlers via Spring's resource mechanism.
+ }
+ }
+
+ this.resourceEditor.setAsText(text);
+ Resource resource = (Resource) this.resourceEditor.getValue();
+ try {
+ setValue(resource != null ? resource.getFile().toPath() : null);
+ }
+ catch (IOException ex) {
+ throw new IllegalArgumentException("Failed to retrieve file for " + resource, ex);
+ }
+ }
+
+ @Override
+ public String getAsText() {
+ Path value = (Path) getValue();
+ return (value != null ? value.toString() : "");
+ }
+
+}
diff --git a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/ReaderEditor.java b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/ReaderEditor.java
index 826760ee..894d9770 100644
--- a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/ReaderEditor.java
+++ b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/ReaderEditor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -47,16 +47,14 @@ public class ReaderEditor extends PropertyEditorSupport {
/**
- * Create a new ReaderEditor,
- * using the default ResourceEditor underneath.
+ * Create a new ReaderEditor, using the default ResourceEditor underneath.
*/
public ReaderEditor() {
this.resourceEditor = new ResourceEditor();
}
/**
- * Create a new ReaderEditor,
- * using the given ResourceEditor underneath.
+ * Create a new ReaderEditor, using the given ResourceEditor underneath.
* @param resourceEditor the ResourceEditor to use
*/
public ReaderEditor(ResourceEditor resourceEditor) {
diff --git a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/StringArrayPropertyEditor.java b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/StringArrayPropertyEditor.java
index c8875b38..85adf515 100644
--- a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/StringArrayPropertyEditor.java
+++ b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/StringArrayPropertyEditor.java
@@ -84,7 +84,7 @@ public class StringArrayPropertyEditor extends PropertyEditorSupport {
* @param emptyArrayAsNull {@code true} if an empty String array
* is to be transformed into {@code null}
* @param trimValues {@code true} if the values in the parsed arrays
- * are to be be trimmed of whitespace (default is true).
+ * are to be trimmed of whitespace (default is true).
*/
public StringArrayPropertyEditor(String separator, boolean emptyArrayAsNull, boolean trimValues) {
this(separator, null, emptyArrayAsNull, trimValues);
@@ -112,7 +112,7 @@ public class StringArrayPropertyEditor extends PropertyEditorSupport {
* @param emptyArrayAsNull {@code true} if an empty String array
* is to be transformed into {@code null}
* @param trimValues {@code true} if the values in the parsed arrays
- * are to be be trimmed of whitespace (default is true).
+ * are to be trimmed of whitespace (default is true).
*/
public StringArrayPropertyEditor(String separator, String charsToDelete, boolean emptyArrayAsNull, boolean trimValues) {
this.separator = separator;
diff --git a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/URIEditor.java b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/URIEditor.java
index f5ac6d6a..7dc173a0 100644
--- a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/URIEditor.java
+++ b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/URIEditor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -60,8 +60,7 @@ public class URIEditor extends PropertyEditorSupport {
* standard URIs (not trying to resolve them into physical resources).
*/
public URIEditor() {
- this.classLoader = null;
- this.encode = true;
+ this(true);
}
/**
@@ -74,7 +73,6 @@ public class URIEditor extends PropertyEditorSupport {
this.encode = encode;
}
-
/**
* Create a new URIEditor, using the given ClassLoader to resolve
* "classpath:" locations into physical resource URLs.
@@ -82,8 +80,7 @@ public class URIEditor extends PropertyEditorSupport {
* (may be {@code null} to indicate the default ClassLoader)
*/
public URIEditor(ClassLoader classLoader) {
- this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
- this.encode = true;
+ this(classLoader, true);
}
/**
diff --git a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/URLEditor.java b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/URLEditor.java
index c5f9755d..85ef824d 100644
--- a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/URLEditor.java
+++ b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/URLEditor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -50,7 +50,7 @@ public class URLEditor extends PropertyEditorSupport {
/**
- * Create a new URLEditor, using the default ResourceEditor underneath.
+ * Create a new URLEditor, using a default ResourceEditor underneath.
*/
public URLEditor() {
this.resourceEditor = new ResourceEditor();
diff --git a/spring-beans/src/main/java/org/springframework/beans/support/ResourceEditorRegistrar.java b/spring-beans/src/main/java/org/springframework/beans/support/ResourceEditorRegistrar.java
index 06d2ee5d..8cffcc78 100644
--- a/spring-beans/src/main/java/org/springframework/beans/support/ResourceEditorRegistrar.java
+++ b/spring-beans/src/main/java/org/springframework/beans/support/ResourceEditorRegistrar.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +33,7 @@ import org.springframework.beans.propertyeditors.ClassEditor;
import org.springframework.beans.propertyeditors.FileEditor;
import org.springframework.beans.propertyeditors.InputSourceEditor;
import org.springframework.beans.propertyeditors.InputStreamEditor;
+import org.springframework.beans.propertyeditors.PathEditor;
import org.springframework.beans.propertyeditors.ReaderEditor;
import org.springframework.beans.propertyeditors.URIEditor;
import org.springframework.beans.propertyeditors.URLEditor;
@@ -43,6 +44,7 @@ import org.springframework.core.io.ResourceEditor;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourceArrayPropertyEditor;
import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.util.ClassUtils;
/**
* PropertyEditorRegistrar implementation that populates a given
@@ -58,6 +60,19 @@ import org.springframework.core.io.support.ResourcePatternResolver;
*/
public class ResourceEditorRegistrar implements PropertyEditorRegistrar {
+ private static Class<?> pathClass;
+
+ static {
+ try {
+ pathClass = ClassUtils.forName("java.nio.file.Path", ResourceEditorRegistrar.class.getClassLoader());
+ }
+ catch (ClassNotFoundException ex) {
+ // Java 7 Path class not available
+ pathClass = null;
+ }
+ }
+
+
private final PropertyResolver propertyResolver;
private final ResourceLoader resourceLoader;
@@ -103,6 +118,9 @@ public class ResourceEditorRegistrar implements PropertyEditorRegistrar {
doRegisterEditor(registry, InputStream.class, new InputStreamEditor(baseEditor));
doRegisterEditor(registry, InputSource.class, new InputSourceEditor(baseEditor));
doRegisterEditor(registry, File.class, new FileEditor(baseEditor));
+ if (pathClass != null) {
+ doRegisterEditor(registry, pathClass, new PathEditor(baseEditor));
+ }
doRegisterEditor(registry, Reader.class, new ReaderEditor(baseEditor));
doRegisterEditor(registry, URL.class, new URLEditor(baseEditor));
diff --git a/spring-beans/src/main/resources/META-INF/spring.schemas b/spring-beans/src/main/resources/META-INF/spring.schemas
index 3f5be7aa..ffd70f14 100644
--- a/spring-beans/src/main/resources/META-INF/spring.schemas
+++ b/spring-beans/src/main/resources/META-INF/spring.schemas
@@ -6,7 +6,8 @@ http\://www.springframework.org/schema/beans/spring-beans-3.2.xsd=org/springfram
http\://www.springframework.org/schema/beans/spring-beans-4.0.xsd=org/springframework/beans/factory/xml/spring-beans-4.0.xsd
http\://www.springframework.org/schema/beans/spring-beans-4.1.xsd=org/springframework/beans/factory/xml/spring-beans-4.1.xsd
http\://www.springframework.org/schema/beans/spring-beans-4.2.xsd=org/springframework/beans/factory/xml/spring-beans-4.2.xsd
-http\://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans-4.2.xsd
+http\://www.springframework.org/schema/beans/spring-beans-4.3.xsd=org/springframework/beans/factory/xml/spring-beans-4.3.xsd
+http\://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans-4.3.xsd
http\://www.springframework.org/schema/tool/spring-tool-2.0.xsd=org/springframework/beans/factory/xml/spring-tool-2.0.xsd
http\://www.springframework.org/schema/tool/spring-tool-2.5.xsd=org/springframework/beans/factory/xml/spring-tool-2.5.xsd
http\://www.springframework.org/schema/tool/spring-tool-3.0.xsd=org/springframework/beans/factory/xml/spring-tool-3.0.xsd
@@ -15,7 +16,8 @@ http\://www.springframework.org/schema/tool/spring-tool-3.2.xsd=org/springframew
http\://www.springframework.org/schema/tool/spring-tool-4.0.xsd=org/springframework/beans/factory/xml/spring-tool-4.0.xsd
http\://www.springframework.org/schema/tool/spring-tool-4.1.xsd=org/springframework/beans/factory/xml/spring-tool-4.1.xsd
http\://www.springframework.org/schema/tool/spring-tool-4.2.xsd=org/springframework/beans/factory/xml/spring-tool-4.2.xsd
-http\://www.springframework.org/schema/tool/spring-tool.xsd=org/springframework/beans/factory/xml/spring-tool-4.2.xsd
+http\://www.springframework.org/schema/tool/spring-tool-4.3.xsd=org/springframework/beans/factory/xml/spring-tool-4.3.xsd
+http\://www.springframework.org/schema/tool/spring-tool.xsd=org/springframework/beans/factory/xml/spring-tool-4.3.xsd
http\://www.springframework.org/schema/util/spring-util-2.0.xsd=org/springframework/beans/factory/xml/spring-util-2.0.xsd
http\://www.springframework.org/schema/util/spring-util-2.5.xsd=org/springframework/beans/factory/xml/spring-util-2.5.xsd
http\://www.springframework.org/schema/util/spring-util-3.0.xsd=org/springframework/beans/factory/xml/spring-util-3.0.xsd
@@ -24,4 +26,5 @@ http\://www.springframework.org/schema/util/spring-util-3.2.xsd=org/springframew
http\://www.springframework.org/schema/util/spring-util-4.0.xsd=org/springframework/beans/factory/xml/spring-util-4.0.xsd
http\://www.springframework.org/schema/util/spring-util-4.1.xsd=org/springframework/beans/factory/xml/spring-util-4.1.xsd
http\://www.springframework.org/schema/util/spring-util-4.2.xsd=org/springframework/beans/factory/xml/spring-util-4.2.xsd
-http\://www.springframework.org/schema/util/spring-util.xsd=org/springframework/beans/factory/xml/spring-util-4.2.xsd
+http\://www.springframework.org/schema/util/spring-util-4.3.xsd=org/springframework/beans/factory/xml/spring-util-4.3.xsd
+http\://www.springframework.org/schema/util/spring-util.xsd=org/springframework/beans/factory/xml/spring-util-4.3.xsd
diff --git a/spring-beans/src/main/resources/org/springframework/beans/factory/xml/spring-beans-4.3.xsd b/spring-beans/src/main/resources/org/springframework/beans/factory/xml/spring-beans-4.3.xsd
new file mode 100644
index 00000000..8532e96f
--- /dev/null
+++ b/spring-beans/src/main/resources/org/springframework/beans/factory/xml/spring-beans-4.3.xsd
@@ -0,0 +1,1201 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+
+<xsd:schema xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ targetNamespace="http://www.springframework.org/schema/beans">
+
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
+
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Spring XML Beans Schema, version 4.3
+ Authors: Juergen Hoeller, Rob Harrop, Mark Fisher, Chris Beams
+
+ This defines a simple and consistent way of creating a namespace
+ of JavaBeans objects, managed by a Spring BeanFactory, read by
+ XmlBeanDefinitionReader (with DefaultBeanDefinitionDocumentReader).
+
+ This document type is used by most Spring functionality, including
+ web application contexts, which are based on bean factories.
+
+ Each "bean" element in this document defines a JavaBean.
+ Typically the bean class is specified, along with JavaBean properties
+ and/or constructor arguments.
+
+ A bean instance can be a "singleton" (shared instance) or a "prototype"
+ (independent instance). Further scopes can be provided by extended
+ bean factories, for example in a web environment.
+
+ References among beans are supported, that is, setting a JavaBean property
+ or a constructor argument to refer to another bean in the same factory
+ (or an ancestor factory).
+
+ As alternative to bean references, "inner bean definitions" can be used.
+ Such inner beans do not have an independent lifecycle; they are typically
+ anonymous nested objects that share the scope of their containing bean.
+
+ There is also support for lists, sets, maps, and java.util.Properties
+ as bean property types or constructor argument types.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+
+ <!-- base types -->
+ <xsd:complexType name="identifiedType" abstract="true">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The unique identifier for a bean. The scope of the identifier
+ is the enclosing bean factory.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="id" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The unique identifier for a bean. A bean id may not be used more than once
+ within the same <beans> element.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+
+ <!-- Top-level <beans> tag -->
+ <xsd:element name="beans">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Container for <bean> and other elements, typically the root element in the document.
+ Allows the definition of default values for all nested bean definitions. May itself
+ be nested for the purpose of defining a subset of beans with certain default values or
+ to be registered only when certain profile(s) are active. Any such nested <beans> element
+ must be declared as the last element in the document.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element ref="description" minOccurs="0"/>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:element ref="import"/>
+ <xsd:element ref="alias"/>
+ <xsd:element ref="bean"/>
+ <xsd:any namespace="##other" processContents="strict" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:choice>
+ <xsd:element ref="beans" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ <xsd:attribute name="profile" use="optional" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The set of profiles for which this <beans> element should be parsed. Multiple profiles
+ can be separated by spaces, commas, or semi-colons.
+
+ If one or more of the specified profiles are active at time of parsing, the <beans>
+ element will be parsed, and all of its <bean> elements registered, &lt;import&gt;
+ elements followed, etc. If none of the specified profiles are active at time of
+ parsing, then the entire element and its contents will be ignored.
+
+ If a profile is prefixed with the NOT operator '!', e.g.
+
+ <beans profile="p1,!p2">
+
+ indicates that the <beans> element should be parsed if profile "p1" is active or
+ if profile "p2" is not active.
+
+ Profiles are activated in one of two ways:
+ Programmatic:
+ ConfigurableEnvironment#setActiveProfiles(String...)
+ ConfigurableEnvironment#setDefaultProfiles(String...)
+
+ Properties (typically through -D system properties, environment variables, or
+ servlet context init params):
+ spring.profiles.active=p1,p2
+ spring.profiles.default=p1,p2
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="default-lazy-init" default="default" type="defaultable-boolean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The default 'lazy-init' value; see the documentation for the
+ 'lazy-init' attribute of the 'bean' element. The default is "default",
+ indicating inheritance from outer 'beans' sections in case of nesting,
+ otherwise falling back to "false".
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="default-merge" default="default" type="defaultable-boolean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The default 'merge' value; see the documentation for the 'merge'
+ attribute of the various collection elements. The default is "default",
+ indicating inheritance from outer 'beans' sections in case of nesting,
+ otherwise falling back to "false".
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="default-autowire" default="default">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The default 'autowire' value; see the documentation for the
+ 'autowire' attribute of the 'bean' element. The default is "default",
+ indicating inheritance from outer 'beans' sections in case of nesting,
+ otherwise falling back to "no" (i.e. no externally driven autowiring).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:NMTOKEN">
+ <xsd:enumeration value="default"/>
+ <xsd:enumeration value="no"/>
+ <xsd:enumeration value="byName"/>
+ <xsd:enumeration value="byType"/>
+ <xsd:enumeration value="constructor"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="default-autowire-candidates" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A default bean name pattern for identifying autowire candidates:
+ e.g. "*Service", "data*", "*Service*", "data*Service".
+ Also accepts a comma-separated list of patterns: e.g. "*Service,*Dao".
+ See the documentation for the 'autowire-candidate' attribute of the
+ 'bean' element for the semantic details of autowire candidate beans.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="default-init-method" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The default 'init-method' value; see the documentation for the
+ 'init-method' attribute of the 'bean' element.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="default-destroy-method" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The default 'destroy-method' value; see the documentation for the
+ 'destroy-method' attribute of the 'bean' element.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:anyAttribute namespace="##other" processContents="lax"/>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="description">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Contains informative text describing the purpose of the enclosing element.
+ Used primarily for user documentation of XML bean definition documents.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType mixed="true">
+ <xsd:choice minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="import">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.core.io.Resource"><![CDATA[
+ Specifies an XML bean definition resource to import.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:restriction base="xsd:anyType">
+ <xsd:attribute name="resource" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The relative resource location of the XML (bean definition) file to import,
+ for example "myImport.xml" or "includes/myImport.xml" or "../myImport.xml".
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:restriction>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="alias">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Defines an alias for a bean (which can reside in a different definition
+ resource).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:restriction base="xsd:anyType">
+ <xsd:attribute name="name" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The name of the bean to define an alias for.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="alias" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The alias name to define for the bean.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:restriction>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:group name="beanElements">
+ <xsd:sequence>
+ <xsd:element ref="description" minOccurs="0"/>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:element ref="meta"/>
+ <xsd:element ref="constructor-arg"/>
+ <xsd:element ref="property"/>
+ <xsd:element ref="qualifier"/>
+ <xsd:element ref="lookup-method"/>
+ <xsd:element ref="replaced-method"/>
+ <xsd:any namespace="##other" processContents="strict" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:choice>
+ </xsd:sequence>
+ </xsd:group>
+
+ <xsd:attributeGroup name="beanAttributes">
+ <xsd:attribute name="name" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Can be used to create one or more aliases illegal in an (XML) id.
+ Multiple aliases can be separated by any number of spaces, commas,
+ or semi-colons (or indeed any mixture of the three).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="class" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:java.lang.Class"><![CDATA[
+ The fully qualified name of the bean's class, except if it serves only
+ as a parent definition for child bean definitions.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="parent" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The name of the parent bean definition.
+
+ Will use the bean class of the parent if none is specified, but can
+ also override it. In the latter case, the child bean class must be
+ compatible with the parent, i.e. accept the parent's property values
+ and constructor argument values, if any.
+
+ A child bean definition will inherit constructor argument values,
+ property values and method overrides from the parent, with the option
+ to add new values. If init method, destroy method, factory bean and/or
+ factory method are specified, they will override the corresponding
+ parent settings.
+
+ The remaining settings will always be taken from the child definition:
+ depends on, autowire mode, scope, lazy init.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="scope" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The scope of this bean: typically "singleton" (one shared instance,
+ which will be returned by all calls to getBean with the given id), or
+ "prototype" (independent instance resulting from each call to getBean).
+
+ By default, a bean will be a singleton, unless the bean has a parent
+ bean definition in which case it will inherit the parent's scope.
+
+ Singletons are most commonly used, and are ideal for multi-threaded
+ service objects. Further scopes, such as "request" or "session", might
+ be supported by extended bean factories (e.g. in a web environment).
+
+ Inner bean definitions inherit the scope of their containing bean
+ definition, unless explicitly specified: The inner bean will be a
+ singleton if the containing bean is a singleton, and a prototype if
+ the containing bean is a prototype, etc.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="abstract" type="xsd:boolean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Is this bean "abstract", that is, not meant to be instantiated itself
+ but rather just serving as parent for concrete child bean definitions?
+ The default is "false". Specify "true" to tell the bean factory to not
+ try to instantiate that particular bean in any case.
+
+ Note: This attribute will not be inherited by child bean definitions.
+ Hence, it needs to be specified per abstract bean definition.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="lazy-init" default="default" type="defaultable-boolean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Indicates whether this bean is to be lazily initialized. If "false",
+ it will be instantiated on startup by bean factories that perform eager
+ initialization of singletons. The effective default is "false".
+
+ Note: This attribute will not be inherited by child bean definitions.
+ Hence, it needs to be specified per concrete bean definition. It can be
+ shared through the 'default-lazy-init' attribute at the 'beans' level
+ and potentially inherited from outer 'beans' defaults in case of nested
+ 'beans' sections (e.g. with different profiles).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="autowire" default="default">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Controls whether bean properties are "autowired".
+ This is an automagical process in which bean references don't need
+ to be coded explicitly in the XML bean definition file, but rather the
+ Spring container works out dependencies. The effective default is "no".
+
+ There are 4 modes:
+
+ 1. "no"
+ The traditional Spring default. No automagical wiring. Bean references
+ must be defined in the XML file via the <ref/> element (or "ref"
+ attribute). We recommend this in most cases as it makes documentation
+ more explicit.
+
+ Note that this default mode also allows for annotation-driven autowiring,
+ if activated. "no" refers to externally driven autowiring only, not
+ affecting any autowiring demands that the bean class itself expresses.
+
+ 2. "byName"
+ Autowiring by property name. If a bean of class Cat exposes a "dog"
+ property, Spring will try to set this to the value of the bean "dog"
+ in the current container. If there is no matching bean by name, nothing
+ special happens.
+
+ 3. "byType"
+ Autowiring if there is exactly one bean of the property type in the
+ container. If there is more than one, a fatal error is raised, and
+ you cannot use byType autowiring for that bean. If there is none,
+ nothing special happens.
+
+ 4. "constructor"
+ Analogous to "byType" for constructor arguments. If there is not exactly
+ one bean of the constructor argument type in the bean factory, a fatal
+ error is raised.
+
+ Note that explicit dependencies, i.e. "property" and "constructor-arg"
+ elements, always override autowiring.
+
+ Note: This attribute will not be inherited by child bean definitions.
+ Hence, it needs to be specified per concrete bean definition. It can be
+ shared through the 'default-autowire' attribute at the 'beans' level
+ and potentially inherited from outer 'beans' defaults in case of nested
+ 'beans' sections (e.g. with different profiles).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:NMTOKEN">
+ <xsd:enumeration value="default"/>
+ <xsd:enumeration value="no"/>
+ <xsd:enumeration value="byName"/>
+ <xsd:enumeration value="byType"/>
+ <xsd:enumeration value="constructor"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="depends-on" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The names of the beans that this bean depends on being initialized.
+ The bean factory will guarantee that these beans get initialized
+ before this bean.
+
+ Note that dependencies are normally expressed through bean properties
+ or constructor arguments. This property should just be necessary for
+ other kinds of dependencies like statics (*ugh*) or database preparation
+ on startup.
+
+ Note: This attribute will not be inherited by child bean definitions.
+ Hence, it needs to be specified per concrete bean definition.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="autowire-candidate" default="default" type="defaultable-boolean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Indicates whether or not this bean should be considered when looking
+ for matching candidates to satisfy another bean's autowiring requirements.
+ Note that this does not affect explicit references by name, which will get
+ resolved even if the specified bean is not marked as an autowire candidate.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="primary" type="xsd:boolean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Specifies that this bean should be given preference when multiple
+ candidates are qualified to autowire a single-valued dependency.
+ If exactly one 'primary' bean exists among the candidates, it
+ will be the autowired value.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="init-method" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The name of the custom initialization method to invoke after setting
+ bean properties. The method must have no arguments, but may throw any
+ exception.
+
+ This is an alternative to implementing Spring's InitializingBean
+ interface or marking a method with the PostConstruct annotation.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="destroy-method" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The name of the custom destroy method to invoke on bean factory shutdown.
+ The method must have no arguments, but may throw any exception.
+
+ This is an alternative to implementing Spring's DisposableBean
+ interface or the standard Java Closeable/AutoCloseable interface,
+ or marking a method with the PreDestroy annotation.
+
+ Note: Only invoked on beans whose lifecycle is under the full
+ control of the factory - which is always the case for singletons,
+ but not guaranteed for any other scope.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="factory-method" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The name of a factory method to use to create this object. Use
+ constructor-arg elements to specify arguments to the factory method,
+ if it takes arguments. Autowiring does not apply to factory methods.
+
+ If the "class" attribute is present, the factory method will be a static
+ method on the class specified by the "class" attribute on this bean
+ definition. Often this will be the same class as that of the constructed
+ object - for example, when the factory method is used as an alternative
+ to a constructor. However, it may be on a different class. In that case,
+ the created object will *not* be of the class specified in the "class"
+ attribute. This is analogous to FactoryBean behavior.
+
+ If the "factory-bean" attribute is present, the "class" attribute is not
+ used, and the factory method will be an instance method on the object
+ returned from a getBean call with the specified bean name. The factory
+ bean may be defined as a singleton or a prototype.
+
+ The factory method can have any number of arguments. Autowiring is not
+ supported. Use indexed constructor-arg elements in conjunction with the
+ factory-method attribute.
+
+ Setter Injection can be used in conjunction with a factory method.
+ Method Injection cannot, as the factory method returns an instance,
+ which will be used when the container creates the bean.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="factory-bean" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Alternative to class attribute for factory-method usage.
+ If this is specified, no class attribute should be used.
+ This must be set to the name of a bean in the current or
+ ancestor factories that contains the relevant factory method.
+ This allows the factory itself to be configured using Dependency
+ Injection, and an instance (rather than static) method to be used.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:anyAttribute namespace="##other" processContents="lax"/>
+ </xsd:attributeGroup>
+
+ <xsd:element name="meta" type="metaType">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Arbitrary metadata attached to a bean definition.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+
+ <xsd:complexType name="metaType">
+ <xsd:attribute name="key" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The key name of the metadata attribute being defined.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="value" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The value of the metadata attribute being defined (as a simple String).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+
+ <xsd:element name="bean">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.beans.factory.config.BeanDefinition"><![CDATA[
+ Defines a single (usually named) bean.
+
+ A bean definition may contain nested tags for constructor arguments,
+ property values, lookup methods, and replaced methods. Mixing constructor
+ injection and setter injection on the same bean is explicitly supported.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="identifiedType">
+ <xsd:group ref="beanElements"/>
+ <xsd:attributeGroup ref="beanAttributes"/>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="constructor-arg">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.beans.factory.config.ConstructorArgumentValues">
+ <![CDATA[
+ Bean definitions can specify zero or more constructor arguments.
+ This is an alternative to "autowire constructor".
+ Arguments correspond to either a specific index of the constructor
+ argument list or are supposed to be matched generically by type.
+
+ Note: A single generic argument value will just be used once, rather
+ than potentially matched multiple times (as of Spring 1.1).
+
+ constructor-arg elements are also used in conjunction with the
+ factory-method element to construct beans using static or instance
+ factory methods.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element ref="description" minOccurs="0"/>
+ <xsd:choice minOccurs="0" maxOccurs="1">
+ <xsd:element ref="bean"/>
+ <xsd:element ref="ref"/>
+ <xsd:element ref="idref"/>
+ <xsd:element ref="value"/>
+ <xsd:element ref="null"/>
+ <xsd:element ref="array"/>
+ <xsd:element ref="list"/>
+ <xsd:element ref="set"/>
+ <xsd:element ref="map"/>
+ <xsd:element ref="props"/>
+ <xsd:any namespace="##other" processContents="strict"/>
+ </xsd:choice>
+ </xsd:sequence>
+ <xsd:attribute name="index" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The exact index of the argument in the constructor argument list.
+ Only needed to avoid ambiguities, e.g. in case of 2 arguments of
+ the exact same type.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="type" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The exact type of the constructor argument. Only needed to avoid
+ ambiguities, e.g. in case of 2 single argument constructors
+ that can both be converted from a String.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="name" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The exact name of the argument in the constructor argument list.
+ Only needed to avoid ambiguities, e.g. in case of 2 arguments of
+ the exact same type. Note: This requires debug symbols to be
+ stored in the class file in order to introspect argument names!
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="ref" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A short-cut alternative to a nested "<ref bean='...'/>" element.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="value" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A short-cut alternative to a nested "<value>...<value/>" element.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="property" type="propertyType">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Bean definitions can have zero or more properties.
+ Property elements correspond to JavaBean setter methods exposed
+ by the bean classes. Spring supports primitives, references to other
+ beans in the same or related factories, lists, maps and properties.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+
+ <xsd:element name="qualifier">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Bean definitions can provide qualifiers to match against annotations
+ on a field or parameter for fine-grained autowire candidate resolution.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element ref="attribute" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ <xsd:attribute name="type" type="xsd:string" default="org.springframework.beans.factory.annotation.Qualifier"/>
+ <xsd:attribute name="value" type="xsd:string"/>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="attribute" type="metaType">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A qualifier element may contain attribute child elements as key-value
+ pairs. These will be available for matching against attributes of a
+ qualifier annotation on an autowired field or parameter if present.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+
+ <xsd:element name="lookup-method">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A lookup method causes the IoC container to override the given method
+ and return the bean with the name given in the bean attribute. This is
+ a form of Method Injection. It is particularly useful as an alternative
+ to implementing the BeanFactoryAware interface, in order to be able to
+ make getBean() calls for non-singleton instances at runtime. In this
+ case, Method Injection is a less invasive alternative.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:restriction base="xsd:anyType">
+ <xsd:attribute name="name" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The name of the lookup method. This method may have arguments which
+ will be passed on to the target constructor or factory method. Note
+ that for backwards compatibility reasons, in a scenario with overloaded
+ non-abstract methods of the given name, only the no-arg variant of a
+ method will be turned into a container-driven lookup method.
+ Consider using the @Lookup annotation for more specific demarcation.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="bean" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The name of the bean in the current or ancestor factories that
+ the lookup method should resolve to. Usually this bean will be a
+ prototype, in which case the lookup method will return a distinct
+ instance on every invocation. If not specified, the lookup method's
+ return type will be used for a type-based lookup.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:restriction>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="replaced-method">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Similar to the lookup method mechanism, the replaced-method element
+ is used to control IoC container method overriding: Method Injection.
+ This mechanism allows the overriding of a method with arbitrary code.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:element ref="arg-type"/>
+ </xsd:choice>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The name of the method whose implementation must be replaced by the
+ IoC container. If this method is not overloaded, there is no need
+ to use arg-type subelements. If this method is overloaded, arg-type
+ subelements must be used for all override definitions for the method.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="replacer" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.beans.factory.support.MethodReplacer"><![CDATA[
+ Bean name of an implementation of the MethodReplacer interface in the
+ current or ancestor factories. This may be a singleton or prototype
+ bean. If it is a prototype, a new instance will be used for each
+ method replacement. Singleton usage is the norm.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="arg-type">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Identifies an argument for a replaced method in the event of
+ method overloading.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType mixed="true">
+ <xsd:choice minOccurs="0" maxOccurs="unbounded"/>
+ <xsd:attribute name="match" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Specification of the type of an overloaded method argument as a String.
+ For convenience, this may be a substring of the FQN. E.g. all the
+ following would match "java.lang.String":
+ - java.lang.String
+ - String
+ - Str
+
+ As the number of arguments will be checked also, this convenience
+ can often be used to save typing.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="ref">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Defines a reference to another bean in this factory or an external
+ factory (parent or included factory).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:restriction base="xsd:anyType">
+ <xsd:attribute name="bean" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The name of the referenced bean.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="parent" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The name of the referenced bean in a parent factory.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:restriction>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="idref">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The id of another bean in this factory or an external factory
+ (parent or included factory).
+ While a regular 'value' element could instead be used for the
+ same effect, using idref indicates that the Spring container
+ should check that the value actually corresponds to a bean id.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:restriction base="xsd:anyType">
+ <xsd:attribute name="bean" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The name of the referenced bean.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:restriction>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="value">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Contains a string representation of a property value.
+ The property may be a string, or may be converted to the required
+ type using the JavaBeans PropertyEditor machinery. This makes it
+ possible for application developers to write custom PropertyEditor
+ implementations that can convert strings to arbitrary target objects.
+
+ Note that this is recommended for simple objects only. Configure
+ more complex objects by populating JavaBean properties with
+ references to other beans.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType mixed="true">
+ <xsd:choice minOccurs="0" maxOccurs="unbounded"/>
+ <xsd:attribute name="type" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The exact type that the value should be converted to. Only needed
+ if the type of the target property or constructor argument is
+ too generic: for example, in case of a collection element.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="null">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Denotes a Java null value. Necessary because an empty "value" tag
+ will resolve to an empty String, which will not be resolved to a
+ null value unless a special PropertyEditor does so.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType mixed="true">
+ <xsd:choice minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:complexType>
+ </xsd:element>
+
+ <!-- Collection Elements -->
+ <xsd:group name="collectionElements">
+ <xsd:sequence>
+ <xsd:element ref="description" minOccurs="0"/>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:element ref="bean"/>
+ <xsd:element ref="ref"/>
+ <xsd:element ref="idref"/>
+ <xsd:element ref="value"/>
+ <xsd:element ref="null"/>
+ <xsd:element ref="array"/>
+ <xsd:element ref="list"/>
+ <xsd:element ref="set"/>
+ <xsd:element ref="map"/>
+ <xsd:element ref="props"/>
+ <xsd:any namespace="##other" processContents="strict" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:choice>
+ </xsd:sequence>
+ </xsd:group>
+
+ <xsd:element name="array">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ An array can contain multiple inner bean, ref, collection, or value elements.
+ This configuration element will always result in an array, even when being
+ defined e.g. as a value for a map with value type Object.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="listOrSetType">
+ <xsd:attribute name="merge" default="default" type="defaultable-boolean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Enables/disables merging for collections when using parent/child beans.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="list">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A list can contain multiple inner bean, ref, collection, or value elements.
+ A list can also map to an array type; the necessary conversion is performed
+ automatically.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="listOrSetType">
+ <xsd:attribute name="merge" default="default" type="defaultable-boolean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Enables/disables merging for collections when using parent/child beans.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="set">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A set can contain multiple inner bean, ref, collection, or value elements.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="listOrSetType">
+ <xsd:attribute name="merge" default="default" type="defaultable-boolean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Enables/disables merging for collections when using parent/child beans.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="map">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A mapping from a key to an object. Maps may be empty.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="mapType">
+ <xsd:attribute name="merge" default="default" type="defaultable-boolean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Enables/disables merging for collections when using parent/child beans.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="entry" type="entryType">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A map entry can be an inner bean, ref, value, or collection.
+ The key of the entry is given by the "key" attribute or child element.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+
+ <xsd:element name="props">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Props elements differ from map elements in that values must be strings.
+ Props may be empty.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="propsType">
+ <xsd:attribute name="merge" default="default" type="defaultable-boolean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Enables/disables merging for collections when using parent/child beans.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="key">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A key element can contain an inner bean, ref, value, or collection.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:group ref="collectionElements"/>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="prop">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The string value of the property. Note that whitespace is trimmed
+ off to avoid unwanted whitespace caused by typical XML formatting.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType mixed="true">
+ <xsd:choice minOccurs="0" maxOccurs="unbounded"/>
+ <xsd:attribute name="key" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The key of the property entry.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:complexType name="propertyType">
+ <xsd:sequence>
+ <xsd:element ref="description" minOccurs="0"/>
+ <xsd:choice minOccurs="0" maxOccurs="1">
+ <xsd:element ref="meta"/>
+ <xsd:element ref="bean"/>
+ <xsd:element ref="ref"/>
+ <xsd:element ref="idref"/>
+ <xsd:element ref="value"/>
+ <xsd:element ref="null"/>
+ <xsd:element ref="array"/>
+ <xsd:element ref="list"/>
+ <xsd:element ref="set"/>
+ <xsd:element ref="map"/>
+ <xsd:element ref="props"/>
+ <xsd:any namespace="##other" processContents="strict"/>
+ </xsd:choice>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The name of the property, following JavaBean naming conventions.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="ref" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A short-cut alternative to a nested "<ref bean='...'/>".
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="value" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A short-cut alternative to a nested "<value>...</value>" element.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+
+ <!-- Collection Types -->
+
+ <!-- base type for collections that have (possibly) typed nested values -->
+ <xsd:complexType name="collectionType">
+ <xsd:attribute name="value-type" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:java.lang.Class"><![CDATA[
+ The default Java type for nested values. Must be a fully qualified
+ class name.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+
+ <!-- 'list' and 'set' collection type -->
+ <xsd:complexType name="listOrSetType">
+ <xsd:complexContent>
+ <xsd:extension base="collectionType">
+ <xsd:group ref="collectionElements"/>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <!-- 'map' element type -->
+ <xsd:complexType name="mapType">
+ <xsd:complexContent>
+ <xsd:extension base="collectionType">
+ <xsd:sequence>
+ <xsd:element ref="description" minOccurs="0"/>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:element ref="entry"/>
+ </xsd:choice>
+ </xsd:sequence>
+ <xsd:attribute name="key-type" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:java.lang.Class"><![CDATA[
+ The default Java type for nested entry keys. Must be a fully qualified
+ class name.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <!-- 'entry' element type -->
+ <xsd:complexType name="entryType">
+ <xsd:sequence>
+ <xsd:element ref="key" minOccurs="0"/>
+ <xsd:group ref="collectionElements"/>
+ </xsd:sequence>
+ <xsd:attribute name="key" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Each map element must specify its key as attribute or as child element.
+ A key attribute is always a String value.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="key-ref" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A short-cut alternative to a to a "key" element with a nested
+ "<ref bean='...'/>".
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="value" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A short-cut alternative to a nested "<value>...</value>"
+ element.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="value-ref" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A short-cut alternative to a nested "<ref bean='...'/>".
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="value-type" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A short-cut alternative to a 'type' attribute on a nested
+ "<value type='...' >...</value>" element.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+
+ <!-- 'props' collection type -->
+ <xsd:complexType name="propsType">
+ <xsd:complexContent>
+ <xsd:extension base="collectionType">
+ <xsd:sequence>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:element ref="prop"/>
+ </xsd:choice>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <!-- simple internal types -->
+ <xsd:simpleType name="defaultable-boolean">
+ <xsd:restriction base="xsd:NMTOKEN">
+ <xsd:enumeration value="default"/>
+ <xsd:enumeration value="true"/>
+ <xsd:enumeration value="false"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+</xsd:schema>
diff --git a/spring-beans/src/main/resources/org/springframework/beans/factory/xml/spring-tool-4.3.xsd b/spring-beans/src/main/resources/org/springframework/beans/factory/xml/spring-tool-4.3.xsd
new file mode 100644
index 00000000..9d84906a
--- /dev/null
+++ b/spring-beans/src/main/resources/org/springframework/beans/factory/xml/spring-tool-4.3.xsd
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+
+<xsd:schema xmlns="http://www.springframework.org/schema/tool"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ targetNamespace="http://www.springframework.org/schema/tool"
+ elementFormDefault="qualified">
+
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
+
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Defines the tool support annotations for Spring's configuration namespaces.
+ Used in other namespace XSD files; not intended for direct use in config files.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+
+ <xsd:element name="annotation">
+ <xsd:complexType>
+ <xsd:sequence minOccurs="0">
+ <xsd:element name="expected-type" type="typedParameterType" minOccurs="0" maxOccurs="1"/>
+ <xsd:element name="assignable-to" type="assignableToType" minOccurs="0" maxOccurs="1"/>
+ <xsd:element name="exports" type="exportsType" minOccurs="0" maxOccurs="unbounded"/>
+ <xsd:element name="registers-scope" type="registersScopeType" minOccurs="0" maxOccurs="unbounded"/>
+ <xsd:element name="expected-method" type="expectedMethodType" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ <xsd:attribute name="kind" default="direct">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="ref"/>
+ <xsd:enumeration value="direct"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:complexType name="typedParameterType">
+ <xsd:attribute name="type" type="xsd:string" use="required"/>
+ </xsd:complexType>
+
+ <xsd:complexType name="assignableToType">
+ <xsd:attribute name="type" type="xsd:string"/>
+ <xsd:attribute name="restriction" default="both">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:NMTOKEN">
+ <xsd:enumeration value="both"/>
+ <xsd:enumeration value="interface-only"/>
+ <xsd:enumeration value="class-only"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:complexType>
+
+ <xsd:complexType name="expectedMethodType">
+ <xsd:attribute name="type" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Defines an XPath query that can be executed against the node annotated with this
+ type to determine the class for which the this method is valid
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="type-ref" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Defines an XPath query that can be executed against the node annotated with this
+ type to determine a referenced bean (by id or alias) for which the given method is valid
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="expression" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Defines an AspectJ method execution pointcut expressions that matches valid methods
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+
+ <xsd:complexType name="exportsType">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Indicates that an annotated type exports an application visible component.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="type" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The type of the exported component. May be null if the type is not known until runtime.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="identifier" type="xsd:string" default="@id">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Defines an XPath query that can be executed against the node annotated with this
+ type to determine the identifier of any exported component.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+
+ <xsd:complexType name="registersScopeType">
+ <xsd:attribute name="name" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Defines the name of a custom bean scope that the annotated type registers, e.g. "conversation".
+ Such a scope will be available in addition to the standard "singleton" and "prototype" scopes
+ (plus "request", "session" and "globalSession" in a web application environment).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+
+</xsd:schema>
diff --git a/spring-beans/src/main/resources/org/springframework/beans/factory/xml/spring-util-4.3.xsd b/spring-beans/src/main/resources/org/springframework/beans/factory/xml/spring-util-4.3.xsd
new file mode 100644
index 00000000..a14a2c51
--- /dev/null
+++ b/spring-beans/src/main/resources/org/springframework/beans/factory/xml/spring-util-4.3.xsd
@@ -0,0 +1,221 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+
+<xsd:schema xmlns="http://www.springframework.org/schema/util"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xmlns:tool="http://www.springframework.org/schema/tool"
+ targetNamespace="http://www.springframework.org/schema/util"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified">
+
+ <xsd:import namespace="http://www.springframework.org/schema/beans" schemaLocation="http://www.springframework.org/schema/beans/spring-beans-4.3.xsd"/>
+ <xsd:import namespace="http://www.springframework.org/schema/tool" schemaLocation="http://www.springframework.org/schema/tool/spring-tool-4.3.xsd"/>
+
+ <xsd:element name="constant">
+ <xsd:annotation>
+ <xsd:documentation>
+ Reference a public, static field on a type and expose its value as
+ a bean. For example <code>&lt;util:constant static-field=&quot;java.lang.Integer.MAX_VALUE&quot;/&gt;</code>.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:attribute name="id" type="xsd:string"/>
+ <xsd:attribute name="static-field" type="xsd:string" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="property-path">
+ <xsd:annotation>
+ <xsd:documentation>
+ Reference a property on a bean (or as a nested value) and expose its values as
+ a bean. For example &lt;util:property-path path=&quot;order.customer.name&quot;/&gt;.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:attribute name="id" type="xsd:string"/>
+ <xsd:attribute name="path" type="xsd:string" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="list">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.beans.factory.config.ListFactoryBean">
+ Builds a List instance of the specified type, populated with the specified content.
+ </xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation>
+ <tool:exports type="java.util.List"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="beans:listOrSetType">
+ <xsd:attribute name="id" type="xsd:string"/>
+ <xsd:attribute name="list-class" type="xsd:string">
+ <xsd:annotation>
+ <xsd:appinfo>
+ <tool:annotation>
+ <tool:expected-type type="java.lang.Class"/>
+ <tool:assignable-to type="java.util.List"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="scope" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The scope of this collection bean: typically "singleton" (one shared instance,
+ which will be returned by all calls to getBean with the given id), or
+ "prototype" (independent instance resulting from each call to getBean).
+ Default is "singleton". Further scopes, such as "request" or "session",
+ might be supported by extended bean factories (e.g. in a web environment).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="set">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.beans.factory.config.SetFactoryBean">
+ Builds a Set instance of the specified type, populated with the specified content.
+ </xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation>
+ <tool:exports type="java.util.Set"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="beans:listOrSetType">
+ <xsd:attribute name="id" type="xsd:string"/>
+ <xsd:attribute name="set-class" type="xsd:string">
+ <xsd:annotation>
+ <xsd:appinfo>
+ <tool:annotation>
+ <tool:expected-type type="java.lang.Class"/>
+ <tool:assignable-to type="java.util.Set"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="scope" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The scope of this collection bean: typically "singleton" (one shared instance,
+ which will be returned by all calls to getBean with the given id), or
+ "prototype" (independent instance resulting from each call to getBean).
+ Default is "singleton". Further scopes, such as "request" or "session",
+ might be supported by extended bean factories (e.g. in a web environment).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="map">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.beans.factory.config.MapFactoryBean">
+ Builds a Map instance of the specified type, populated with the specified content.
+ </xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation>
+ <tool:exports type="java.util.Map"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="beans:mapType">
+ <xsd:attribute name="id" type="xsd:string"/>
+ <xsd:attribute name="map-class" type="xsd:string">
+ <xsd:annotation>
+ <xsd:appinfo>
+ <tool:annotation>
+ <tool:expected-type type="java.lang.Class"/>
+ <tool:assignable-to type="java.util.Map"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="scope" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The scope of this collection bean: typically "singleton" (one shared instance,
+ which will be returned by all calls to getBean with the given id), or
+ "prototype" (independent instance resulting from each call to getBean).
+ Default is "singleton". Further scopes, such as "request" or "session",
+ might be supported by extended bean factories (e.g. in a web environment).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="properties">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.beans.factory.config.PropertiesFactoryBean">
+ Loads a Properties instance from the resource location specified by the '<code>location</code>' attribute.
+ </xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation>
+ <tool:exports type="java.util.Properties"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="beans:propsType">
+ <xsd:attribute name="id" type="xsd:string"/>
+ <xsd:attribute name="location" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The location of the properties file, as a Spring resource location: a URL,
+ a "classpath:" pseudo URL, or a relative file path. Multiple locations may be
+ specified, separated by commas.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="ignore-resource-not-found" type="xsd:boolean" default="false">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Specifies if failure to find the property resource location should be ignored.
+ Default is "false", meaning that if there is no file in the location specified
+ an exception will be raised at runtime.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="local-override" type="xsd:boolean" default="false">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Specifies whether local properties override properties from files.
+ Default is "false": properties from files override local defaults.
+ If set to "true", local properties will override defaults from files.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="scope" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The scope of this collection bean: typically "singleton" (one shared instance,
+ which will be returned by all calls to getBean with the given id), or
+ "prototype" (independent instance resulting from each call to getBean).
+ Default is "singleton". Further scopes, such as "request" or "session",
+ might be supported by extended bean factories (e.g. in a web environment).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+
+</xsd:schema>
diff --git a/spring-beans/src/test/java/org/springframework/beans/AbstractPropertyAccessorTests.java b/spring-beans/src/test/java/org/springframework/beans/AbstractPropertyAccessorTests.java
index 14118896..d801c377 100644
--- a/spring-beans/src/test/java/org/springframework/beans/AbstractPropertyAccessorTests.java
+++ b/spring-beans/src/test/java/org/springframework/beans/AbstractPropertyAccessorTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1763,6 +1763,14 @@ public abstract class AbstractPropertyAccessorTests {
assertEquals("val1", Spr10115Bean.prop1);
}
+ @Test
+ public void cornerSpr13837() {
+ Spr13837Bean target = new Spr13837Bean();
+ AbstractPropertyAccessor accessor = createAccessor(target);
+ accessor.setPropertyValue("something", 42);
+ assertEquals(Integer.valueOf(42), target.something);
+ }
+
private Person createPerson(String name, String city, String country) {
return new Person(name, new Address(city, country));
@@ -2201,6 +2209,30 @@ public abstract class AbstractPropertyAccessorTests {
}
}
+ interface Spr13837 {
+
+ Integer getSomething();
+
+ <T extends Spr13837> T setSomething(Integer something);
+
+ }
+
+ static class Spr13837Bean implements Spr13837 {
+
+ protected Integer something;
+
+ @Override
+ public Integer getSomething() {
+ return this.something;
+ }
+
+ @Override
+ public Spr13837Bean setSomething(final Integer something) {
+ this.something = something;
+ return this;
+ }
+ }
+
@SuppressWarnings("serial")
public static class ReadOnlyMap<K, V> extends HashMap<K, V> {
diff --git a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java
index a99bc92d..89b2922e 100644
--- a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java
+++ b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java
@@ -62,6 +62,14 @@ public class BeanWrapperTests extends AbstractPropertyAccessorTests {
}
@Test
+ public void aliasedSetterThroughDefaultMethod() {
+ GetterBean target = new GetterBean();
+ BeanWrapper accessor = createAccessor(target);
+ accessor.setPropertyValue("aliasedName", "tom");
+ assertTrue("Set name to tom", target.getAliasedName().equals("tom"));
+ }
+
+ @Test
public void setValidAndInvalidPropertyValuesShouldContainExceptionDetails() {
TestBean target = new TestBean();
String newName = "tony";
@@ -209,7 +217,24 @@ public class BeanWrapperTests extends AbstractPropertyAccessorTests {
@SuppressWarnings("unused")
- private static class GetterBean {
+ private interface AliasedProperty {
+
+ default void setAliasedName(String name) {
+ setName(name);
+ }
+
+ default String getAliasedName() {
+ return getName();
+ }
+
+ void setName(String name);
+
+ String getName();
+ }
+
+
+ @SuppressWarnings("unused")
+ private static class GetterBean implements AliasedProperty {
private String name;
diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java
index cbf2f37b..00e41497 100644
--- a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java
+++ b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java
@@ -31,6 +31,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.Callable;
@@ -2711,7 +2712,7 @@ public class DefaultListableBeanFactoryTests {
}
@Test
- public void resolveEmbeddedValue() throws Exception {
+ public void resolveEmbeddedValue() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
StringValueResolver r1 = mock(StringValueResolver.class);
StringValueResolver r2 = mock(StringValueResolver.class);
@@ -2730,6 +2731,25 @@ public class DefaultListableBeanFactoryTests {
verify(r3, never()).resolveStringValue(isNull(String.class));
}
+ @Test
+ public void populatedJavaUtilOptionalBean() {
+ DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
+ RootBeanDefinition bd = new RootBeanDefinition(Optional.class);
+ bd.setFactoryMethodName("of");
+ bd.getConstructorArgumentValues().addGenericArgumentValue("CONTENT");
+ bf.registerBeanDefinition("optionalBean", bd);
+ assertEquals(Optional.of("CONTENT"), bf.getBean(Optional.class));
+ }
+
+ @Test
+ public void emptyJavaUtilOptionalBean() {
+ DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
+ RootBeanDefinition bd = new RootBeanDefinition(Optional.class);
+ bd.setFactoryMethodName("empty");
+ bf.registerBeanDefinition("optionalBean", bd);
+ assertSame(Optional.empty(), bf.getBean(Optional.class));
+ }
+
/**
* Test that by-type bean lookup caching is working effectively by searching for a
* bean of type B 10K times within a container having 1K additional beans of type A.
diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/Spr5475Tests.java b/spring-beans/src/test/java/org/springframework/beans/factory/Spr5475Tests.java
index f61119bc..32fa6847 100644
--- a/spring-beans/src/test/java/org/springframework/beans/factory/Spr5475Tests.java
+++ b/spring-beans/src/test/java/org/springframework/beans/factory/Spr5475Tests.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package org.springframework.beans.factory;
import org.junit.Test;
@@ -49,8 +65,7 @@ public class Spr5475Tests {
cav.addIndexedArgumentValue(1, "bogusArg2".getBytes());
def.setConstructorArgumentValues(cav);
- assertExceptionMessageForMisconfiguredFactoryMethod(
- def,
+ assertExceptionMessageForMisconfiguredFactoryMethod(def,
"Error creating bean with name 'foo': No matching factory method found: factory method 'noArgFactory(CharSequence,byte[])'. " +
"Check that a method with the specified name and arguments exists and that it is static.");
}
@@ -62,7 +77,8 @@ public class Spr5475Tests {
try {
factory.preInstantiateSingletons();
fail("should have failed with BeanCreationException due to incorrectly invoked factory method");
- } catch (BeanCreationException ex) {
+ }
+ catch (BeanCreationException ex) {
assertThat(ex.getMessage(), equalTo(expectedMessage));
}
}
@@ -72,15 +88,17 @@ public class Spr5475Tests {
// calling a factory method that accepts arguments without any arguments emits an exception unlike cases
// where a no-arg factory method is called with arguments. Adding this test just to document the difference
assertExceptionMessageForMisconfiguredFactoryMethod(
- rootBeanDefinition(Foo.class)
- .setFactoryMethod("singleArgFactory").getBeanDefinition(),
+ rootBeanDefinition(Foo.class).
+ setFactoryMethod("singleArgFactory").getBeanDefinition(),
"Error creating bean with name 'foo': " +
- "Unsatisfied dependency expressed through constructor argument with index 0 of type [java.lang.String]: " +
- "Ambiguous factory method argument types - did you specify the correct bean references as factory method arguments?");
+ "Unsatisfied dependency expressed through method 'singleArgFactory' parameter 0: " +
+ "Ambiguous argument values for parameter of type [java.lang.String] - " +
+ "did you specify the correct bean references as arguments?");
}
static class Foo {
+
static Foo noArgFactory() {
return new Foo();
}
diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java
index 3983807c..2a4afd0a 100644
--- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java
+++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,8 +24,15 @@ import java.lang.annotation.Target;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
import java.util.concurrent.Callable;
import org.junit.Ignore;
@@ -35,7 +42,10 @@ import org.mockito.Mockito;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.beans.factory.ObjectFactory;
+import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
@@ -74,6 +84,7 @@ public class AutowiredAnnotationBeanPostProcessorTests {
bf.registerBeanDefinition("testBean", new GenericBeanDefinition());
try {
bf.getBean("testBean");
+ fail("Should have thrown BeanCreationException");
}
catch (BeanCreationException ex) {
assertTrue(ex.getRootCause() instanceof IllegalStateException);
@@ -626,6 +637,7 @@ public class AutowiredAnnotationBeanPostProcessorTests {
}
catch (UnsatisfiedDependencyException ex) {
// expected
+ assertSame(ConstructorWithoutFallbackBean.class, ex.getInjectionPoint().getMethodParameter().getDeclaringClass());
}
}
@@ -741,10 +753,10 @@ public class AutowiredAnnotationBeanPostProcessorTests {
RootBeanDefinition bd = new RootBeanDefinition(MapConstructorInjectionBean.class);
bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
- TestBean tb1 = new TestBean();
- TestBean tb2 = new TestBean();
+ TestBean tb1 = new TestBean("tb1");
+ TestBean tb2 = new TestBean("tb2");
bf.registerSingleton("testBean1", tb1);
- bf.registerSingleton("testBean2", tb1);
+ bf.registerSingleton("testBean2", tb2);
MapConstructorInjectionBean bean = (MapConstructorInjectionBean) bf.getBean("annotatedBean");
assertEquals(2, bean.getTestBeanMap().size());
@@ -770,10 +782,10 @@ public class AutowiredAnnotationBeanPostProcessorTests {
RootBeanDefinition bd = new RootBeanDefinition(MapFieldInjectionBean.class);
bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
- TestBean tb1 = new TestBean();
- TestBean tb2 = new TestBean();
+ TestBean tb1 = new TestBean("tb1");
+ TestBean tb2 = new TestBean("tb2");
bf.registerSingleton("testBean1", tb1);
- bf.registerSingleton("testBean2", tb1);
+ bf.registerSingleton("testBean2", tb2);
MapFieldInjectionBean bean = (MapFieldInjectionBean) bf.getBean("annotatedBean");
assertEquals(2, bean.getTestBeanMap().size());
@@ -829,8 +841,9 @@ public class AutowiredAnnotationBeanPostProcessorTests {
bf.getBean("annotatedBean");
fail("should have failed, more than one bean of type");
}
- catch (BeanCreationException e) {
+ catch (UnsatisfiedDependencyException ex) {
// expected
+ assertSame(MapMethodInjectionBean.class, ex.getInjectionPoint().getMethodParameter().getDeclaringClass());
}
bf.destroySingletons();
}
@@ -871,6 +884,106 @@ public class AutowiredAnnotationBeanPostProcessorTests {
}
@Test
+ public void testConstructorInjectionWithTypedMapAsBean() {
+ DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
+ bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
+ AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
+ bpp.setBeanFactory(bf);
+ bf.addBeanPostProcessor(bpp);
+ RootBeanDefinition bd = new RootBeanDefinition(MapConstructorInjectionBean.class);
+ bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE);
+ bf.registerBeanDefinition("annotatedBean", bd);
+ MyTestBeanMap tbm = new MyTestBeanMap();
+ tbm.put("testBean1", new TestBean("tb1"));
+ tbm.put("testBean2", new TestBean("tb2"));
+ bf.registerSingleton("testBeans", tbm);
+ bf.registerSingleton("otherMap", new Properties());
+
+ MapConstructorInjectionBean bean = (MapConstructorInjectionBean) bf.getBean("annotatedBean");
+ assertSame(tbm, bean.getTestBeanMap());
+ bean = (MapConstructorInjectionBean) bf.getBean("annotatedBean");
+ assertSame(tbm, bean.getTestBeanMap());
+ }
+
+ @Test
+ public void testConstructorInjectionWithPlainMapAsBean() {
+ DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
+ bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
+ AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
+ bpp.setBeanFactory(bf);
+ bf.addBeanPostProcessor(bpp);
+ RootBeanDefinition bd = new RootBeanDefinition(MapConstructorInjectionBean.class);
+ bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE);
+ bf.registerBeanDefinition("annotatedBean", bd);
+ RootBeanDefinition tbm = new RootBeanDefinition(CollectionFactoryMethods.class);
+ tbm.setUniqueFactoryMethodName("testBeanMap");
+ bf.registerBeanDefinition("myTestBeanMap", tbm);
+ bf.registerSingleton("otherMap", new HashMap<Object, Object>());
+
+ MapConstructorInjectionBean bean = (MapConstructorInjectionBean) bf.getBean("annotatedBean");
+ assertSame(bf.getBean("myTestBeanMap"), bean.getTestBeanMap());
+ bean = (MapConstructorInjectionBean) bf.getBean("annotatedBean");
+ assertSame(bf.getBean("myTestBeanMap"), bean.getTestBeanMap());
+ }
+
+ @Test
+ public void testConstructorInjectionWithTypedSetAsBean() {
+ DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
+ bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
+ AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
+ bpp.setBeanFactory(bf);
+ bf.addBeanPostProcessor(bpp);
+ RootBeanDefinition bd = new RootBeanDefinition(SetConstructorInjectionBean.class);
+ bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE);
+ bf.registerBeanDefinition("annotatedBean", bd);
+ MyTestBeanSet tbs = new MyTestBeanSet();
+ tbs.add(new TestBean("tb1"));
+ tbs.add(new TestBean("tb2"));
+ bf.registerSingleton("testBeans", tbs);
+ bf.registerSingleton("otherSet", new HashSet<Object>());
+
+ SetConstructorInjectionBean bean = (SetConstructorInjectionBean) bf.getBean("annotatedBean");
+ assertSame(tbs, bean.getTestBeanSet());
+ bean = (SetConstructorInjectionBean) bf.getBean("annotatedBean");
+ assertSame(tbs, bean.getTestBeanSet());
+ }
+
+ @Test
+ public void testConstructorInjectionWithPlainSetAsBean() {
+ DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
+ bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
+ AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
+ bpp.setBeanFactory(bf);
+ bf.addBeanPostProcessor(bpp);
+ RootBeanDefinition bd = new RootBeanDefinition(SetConstructorInjectionBean.class);
+ bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE);
+ bf.registerBeanDefinition("annotatedBean", bd);
+ RootBeanDefinition tbs = new RootBeanDefinition(CollectionFactoryMethods.class);
+ tbs.setUniqueFactoryMethodName("testBeanSet");
+ bf.registerBeanDefinition("myTestBeanSet", tbs);
+ bf.registerSingleton("otherSet", new HashSet<Object>());
+
+ SetConstructorInjectionBean bean = (SetConstructorInjectionBean) bf.getBean("annotatedBean");
+ assertSame(bf.getBean("myTestBeanSet"), bean.getTestBeanSet());
+ bean = (SetConstructorInjectionBean) bf.getBean("annotatedBean");
+ assertSame(bf.getBean("myTestBeanSet"), bean.getTestBeanSet());
+ }
+
+ @Test
+ public void testSelfReference() {
+ DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
+ bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
+ AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
+ bpp.setBeanFactory(bf);
+ bf.addBeanPostProcessor(bpp);
+ RootBeanDefinition bd = new RootBeanDefinition(SelfInjectionBean.class);
+ bf.registerBeanDefinition("annotatedBean", bd);
+
+ SelfInjectionBean bean = (SelfInjectionBean) bf.getBean("annotatedBean");
+ assertSame(bean, bean.selfReference);
+ }
+
+ @Test
public void testObjectFactoryInjection() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
@@ -938,6 +1051,110 @@ public class AutowiredAnnotationBeanPostProcessorTests {
}
@Test
+ public void testSmartObjectFactoryInjectionWithPrototype() {
+ DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
+ AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
+ bpp.setBeanFactory(bf);
+ bf.addBeanPostProcessor(bpp);
+ bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SmartObjectFactoryInjectionBean.class));
+ RootBeanDefinition tbd = new RootBeanDefinition(TestBean.class);
+ tbd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE);
+ bf.registerBeanDefinition("testBean", tbd);
+
+ SmartObjectFactoryInjectionBean bean = (SmartObjectFactoryInjectionBean) bf.getBean("annotatedBean");
+ assertEquals(bf.getBean("testBean"), bean.getTestBean());
+ assertEquals(bf.getBean("testBean", "myName"), bean.getTestBean("myName"));
+ assertEquals(bf.getBean("testBean"), bean.getOptionalTestBean());
+ assertEquals(bf.getBean("testBean"), bean.getUniqueTestBean());
+ bf.destroySingletons();
+ }
+
+ @Test
+ public void testSmartObjectFactoryInjectionWithSingletonTarget() {
+ DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
+ AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
+ bpp.setBeanFactory(bf);
+ bf.addBeanPostProcessor(bpp);
+ bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SmartObjectFactoryInjectionBean.class));
+ bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class));
+
+ SmartObjectFactoryInjectionBean bean = (SmartObjectFactoryInjectionBean) bf.getBean("annotatedBean");
+ assertSame(bf.getBean("testBean"), bean.getTestBean());
+ assertSame(bf.getBean("testBean"), bean.getOptionalTestBean());
+ assertSame(bf.getBean("testBean"), bean.getUniqueTestBean());
+ bf.destroySingletons();
+ }
+
+ @Test
+ public void testSmartObjectFactoryInjectionWithTargetNotAvailable() {
+ DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
+ AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
+ bpp.setBeanFactory(bf);
+ bf.addBeanPostProcessor(bpp);
+ bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SmartObjectFactoryInjectionBean.class));
+
+ SmartObjectFactoryInjectionBean bean = (SmartObjectFactoryInjectionBean) bf.getBean("annotatedBean");
+ try {
+ bean.getTestBean();
+ fail("Should have thrown NoSuchBeanDefinitionException");
+ }
+ catch (NoSuchBeanDefinitionException ex) {
+ // expected
+ }
+ assertNull(bean.getOptionalTestBean());
+ assertNull(bean.getUniqueTestBean());
+ bf.destroySingletons();
+ }
+
+ @Test
+ public void testSmartObjectFactoryInjectionWithTargetNotUnique() {
+ DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
+ AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
+ bpp.setBeanFactory(bf);
+ bf.addBeanPostProcessor(bpp);
+ bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SmartObjectFactoryInjectionBean.class));
+ bf.registerBeanDefinition("testBean1", new RootBeanDefinition(TestBean.class));
+ bf.registerBeanDefinition("testBean2", new RootBeanDefinition(TestBean.class));
+
+ SmartObjectFactoryInjectionBean bean = (SmartObjectFactoryInjectionBean) bf.getBean("annotatedBean");
+ try {
+ bean.getTestBean();
+ fail("Should have thrown NoUniqueBeanDefinitionException");
+ }
+ catch (NoUniqueBeanDefinitionException ex) {
+ // expected
+ }
+ try {
+ bean.getOptionalTestBean();
+ fail("Should have thrown NoUniqueBeanDefinitionException");
+ }
+ catch (NoUniqueBeanDefinitionException ex) {
+ // expected
+ }
+ assertNull(bean.getUniqueTestBean());
+ bf.destroySingletons();
+ }
+
+ @Test
+ public void testSmartObjectFactoryInjectionWithTargetPrimary() {
+ DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
+ AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
+ bpp.setBeanFactory(bf);
+ bf.addBeanPostProcessor(bpp);
+ bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SmartObjectFactoryInjectionBean.class));
+ RootBeanDefinition tb1 = new RootBeanDefinition(TestBean.class);
+ tb1.setPrimary(true);
+ bf.registerBeanDefinition("testBean1", tb1);
+ bf.registerBeanDefinition("testBean2", new RootBeanDefinition(TestBean.class));
+
+ SmartObjectFactoryInjectionBean bean = (SmartObjectFactoryInjectionBean) bf.getBean("annotatedBean");
+ assertSame(bf.getBean("testBean1"), bean.getTestBean());
+ assertSame(bf.getBean("testBean1"), bean.getOptionalTestBean());
+ assertSame(bf.getBean("testBean1"), bean.getUniqueTestBean());
+ bf.destroySingletons();
+ }
+
+ @Test
public void testCustomAnnotationRequiredFieldResourceInjection() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
@@ -951,7 +1168,8 @@ public class AutowiredAnnotationBeanPostProcessorTests {
TestBean tb = new TestBean();
bf.registerSingleton("testBean", tb);
- CustomAnnotationRequiredFieldResourceInjectionBean bean = (CustomAnnotationRequiredFieldResourceInjectionBean) bf.getBean("customBean");
+ CustomAnnotationRequiredFieldResourceInjectionBean bean =
+ (CustomAnnotationRequiredFieldResourceInjectionBean) bf.getBean("customBean");
assertSame(tb, bean.getTestBean());
bf.destroySingletons();
}
@@ -970,10 +1188,12 @@ public class AutowiredAnnotationBeanPostProcessorTests {
try {
bf.getBean("customBean");
- fail("expected BeanCreationException; no dependency available for required field");
+ fail("Should have thrown UnsatisfiedDependencyException");
}
- catch (BeanCreationException e) {
+ catch (UnsatisfiedDependencyException ex) {
// expected
+ assertSame(CustomAnnotationRequiredFieldResourceInjectionBean.class,
+ ex.getInjectionPoint().getField().getDeclaringClass());
}
bf.destroySingletons();
}
@@ -996,10 +1216,13 @@ public class AutowiredAnnotationBeanPostProcessorTests {
try {
bf.getBean("customBean");
- fail("expected BeanCreationException; multiple beans of dependency type available");
+ fail("Should have thrown UnsatisfiedDependencyException");
}
- catch (BeanCreationException e) {
+ catch (UnsatisfiedDependencyException ex) {
// expected
+ ex.printStackTrace();
+ assertSame(CustomAnnotationRequiredFieldResourceInjectionBean.class,
+ ex.getInjectionPoint().getField().getDeclaringClass());
}
bf.destroySingletons();
}
@@ -1018,7 +1241,8 @@ public class AutowiredAnnotationBeanPostProcessorTests {
TestBean tb = new TestBean();
bf.registerSingleton("testBean", tb);
- CustomAnnotationRequiredMethodResourceInjectionBean bean = (CustomAnnotationRequiredMethodResourceInjectionBean) bf.getBean("customBean");
+ CustomAnnotationRequiredMethodResourceInjectionBean bean =
+ (CustomAnnotationRequiredMethodResourceInjectionBean) bf.getBean("customBean");
assertSame(tb, bean.getTestBean());
bf.destroySingletons();
}
@@ -1037,10 +1261,12 @@ public class AutowiredAnnotationBeanPostProcessorTests {
try {
bf.getBean("customBean");
- fail("expected BeanCreationException; no dependency available for required method");
+ fail("Should have thrown UnsatisfiedDependencyException");
}
- catch (BeanCreationException e) {
+ catch (UnsatisfiedDependencyException ex) {
// expected
+ assertSame(CustomAnnotationRequiredMethodResourceInjectionBean.class,
+ ex.getInjectionPoint().getMethodParameter().getDeclaringClass());
}
bf.destroySingletons();
}
@@ -1063,10 +1289,12 @@ public class AutowiredAnnotationBeanPostProcessorTests {
try {
bf.getBean("customBean");
- fail("expected BeanCreationException; multiple beans of dependency type available");
+ fail("Should have thrown UnsatisfiedDependencyException");
}
- catch (BeanCreationException e) {
+ catch (UnsatisfiedDependencyException ex) {
// expected
+ assertSame(CustomAnnotationRequiredMethodResourceInjectionBean.class,
+ ex.getInjectionPoint().getMethodParameter().getDeclaringClass());
}
bf.destroySingletons();
}
@@ -1085,7 +1313,8 @@ public class AutowiredAnnotationBeanPostProcessorTests {
TestBean tb = new TestBean();
bf.registerSingleton("testBean", tb);
- CustomAnnotationOptionalFieldResourceInjectionBean bean = (CustomAnnotationOptionalFieldResourceInjectionBean) bf.getBean("customBean");
+ CustomAnnotationOptionalFieldResourceInjectionBean bean =
+ (CustomAnnotationOptionalFieldResourceInjectionBean) bf.getBean("customBean");
assertSame(tb, bean.getTestBean3());
assertNull(bean.getTestBean());
assertNull(bean.getTestBean2());
@@ -1104,7 +1333,8 @@ public class AutowiredAnnotationBeanPostProcessorTests {
bf.registerBeanDefinition("customBean", new RootBeanDefinition(
CustomAnnotationOptionalFieldResourceInjectionBean.class));
- CustomAnnotationOptionalFieldResourceInjectionBean bean = (CustomAnnotationOptionalFieldResourceInjectionBean) bf.getBean("customBean");
+ CustomAnnotationOptionalFieldResourceInjectionBean bean =
+ (CustomAnnotationOptionalFieldResourceInjectionBean) bf.getBean("customBean");
assertNull(bean.getTestBean3());
assertNull(bean.getTestBean());
assertNull(bean.getTestBean2());
@@ -1129,10 +1359,12 @@ public class AutowiredAnnotationBeanPostProcessorTests {
try {
bf.getBean("customBean");
- fail("expected BeanCreationException; multiple beans of dependency type available");
+ fail("Should have thrown UnsatisfiedDependencyException");
}
- catch (BeanCreationException e) {
+ catch (UnsatisfiedDependencyException ex) {
// expected
+ assertSame(CustomAnnotationOptionalFieldResourceInjectionBean.class,
+ ex.getInjectionPoint().getField().getDeclaringClass());
}
bf.destroySingletons();
}
@@ -1151,7 +1383,8 @@ public class AutowiredAnnotationBeanPostProcessorTests {
TestBean tb = new TestBean();
bf.registerSingleton("testBean", tb);
- CustomAnnotationOptionalMethodResourceInjectionBean bean = (CustomAnnotationOptionalMethodResourceInjectionBean) bf.getBean("customBean");
+ CustomAnnotationOptionalMethodResourceInjectionBean bean =
+ (CustomAnnotationOptionalMethodResourceInjectionBean) bf.getBean("customBean");
assertSame(tb, bean.getTestBean3());
assertNull(bean.getTestBean());
assertNull(bean.getTestBean2());
@@ -1170,7 +1403,8 @@ public class AutowiredAnnotationBeanPostProcessorTests {
bf.registerBeanDefinition("customBean", new RootBeanDefinition(
CustomAnnotationOptionalMethodResourceInjectionBean.class));
- CustomAnnotationOptionalMethodResourceInjectionBean bean = (CustomAnnotationOptionalMethodResourceInjectionBean) bf.getBean("customBean");
+ CustomAnnotationOptionalMethodResourceInjectionBean bean =
+ (CustomAnnotationOptionalMethodResourceInjectionBean) bf.getBean("customBean");
assertNull(bean.getTestBean3());
assertNull(bean.getTestBean());
assertNull(bean.getTestBean2());
@@ -1195,10 +1429,12 @@ public class AutowiredAnnotationBeanPostProcessorTests {
try {
bf.getBean("customBean");
- fail("expected BeanCreationException; multiple beans of dependency type available");
+ fail("Should have thrown UnsatisfiedDependencyException");
}
- catch (BeanCreationException e) {
+ catch (UnsatisfiedDependencyException ex) {
// expected
+ assertSame(CustomAnnotationOptionalMethodResourceInjectionBean.class,
+ ex.getInjectionPoint().getMethodParameter().getDeclaringClass());
}
bf.destroySingletons();
}
@@ -1351,8 +1587,8 @@ public class AutowiredAnnotationBeanPostProcessorTests {
bf.registerBeanDefinition("integerRepo", rbd);
RepositoryFieldInjectionBeanWithQualifiers bean = (RepositoryFieldInjectionBeanWithQualifiers) bf.getBean("annotatedBean");
- Repository sr = bf.getBean("stringRepo", Repository.class);
- Repository ir = bf.getBean("integerRepo", Repository.class);
+ Repository<?> sr = bf.getBean("stringRepo", Repository.class);
+ Repository<?> ir = bf.getBean("integerRepo", Repository.class);
assertSame(sr, bean.stringRepository);
assertSame(ir, bean.integerRepository);
assertSame(1, bean.stringRepositoryArray.length);
@@ -1383,7 +1619,7 @@ public class AutowiredAnnotationBeanPostProcessorTests {
bf.registerSingleton("repo", new StringRepository());
RepositoryFieldInjectionBeanWithSimpleMatch bean = (RepositoryFieldInjectionBeanWithSimpleMatch) bf.getBean("annotatedBean");
- Repository repo = bf.getBean("repo", Repository.class);
+ Repository<?> repo = bf.getBean("repo", Repository.class);
assertSame(repo, bean.repository);
assertSame(repo, bean.stringRepository);
assertSame(1, bean.repositoryArray.length);
@@ -1415,7 +1651,7 @@ public class AutowiredAnnotationBeanPostProcessorTests {
bf.registerBeanDefinition("repoFactoryBean", new RootBeanDefinition(RepositoryFactoryBean.class));
RepositoryFactoryBeanInjectionBean bean = (RepositoryFactoryBeanInjectionBean) bf.getBean("annotatedBean");
- RepositoryFactoryBean repoFactoryBean = bf.getBean("&repoFactoryBean", RepositoryFactoryBean.class);
+ RepositoryFactoryBean<?> repoFactoryBean = bf.getBean("&repoFactoryBean", RepositoryFactoryBean.class);
assertSame(repoFactoryBean, bean.repositoryFactoryBean);
}
@@ -1432,7 +1668,7 @@ public class AutowiredAnnotationBeanPostProcessorTests {
bf.registerSingleton("repoFactoryBean", new RepositoryFactoryBean<>());
RepositoryFactoryBeanInjectionBean bean = (RepositoryFactoryBeanInjectionBean) bf.getBean("annotatedBean");
- RepositoryFactoryBean repoFactoryBean = bf.getBean("&repoFactoryBean", RepositoryFactoryBean.class);
+ RepositoryFactoryBean<?> repoFactoryBean = bf.getBean("&repoFactoryBean", RepositoryFactoryBean.class);
assertSame(repoFactoryBean, bean.repositoryFactoryBean);
}
@@ -1456,7 +1692,7 @@ public class AutowiredAnnotationBeanPostProcessorTests {
bf.registerBeanDefinition("repo", rbd);
RepositoryFieldInjectionBeanWithSimpleMatch bean = (RepositoryFieldInjectionBeanWithSimpleMatch) bf.getBean("annotatedBean");
- Repository repo = bf.getBean("repo", Repository.class);
+ Repository<?> repo = bf.getBean("repo", Repository.class);
assertSame(repo, bean.repository);
assertSame(repo, bean.stringRepository);
assertSame(1, bean.repositoryArray.length);
@@ -1492,7 +1728,7 @@ public class AutowiredAnnotationBeanPostProcessorTests {
bf.registerBeanDefinition("repo", rbd);
RepositoryFieldInjectionBeanWithSimpleMatch bean = (RepositoryFieldInjectionBeanWithSimpleMatch) bf.getBean("annotatedBean");
- Repository repo = bf.getBean("repo", Repository.class);
+ Repository<?> repo = bf.getBean("repo", Repository.class);
assertSame(repo, bean.repository);
assertSame(repo, bean.stringRepository);
assertSame(1, bean.repositoryArray.length);
@@ -1606,6 +1842,7 @@ public class AutowiredAnnotationBeanPostProcessorTests {
}
@Test
+ @SuppressWarnings("rawtypes")
public void testGenericsBasedConstructorInjectionWithNonTypedTarget() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
@@ -1666,6 +1903,7 @@ public class AutowiredAnnotationBeanPostProcessorTests {
}
@Test
+ @SuppressWarnings("rawtypes")
public void testGenericsBasedConstructorInjectionWithMixedTargets() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
@@ -1730,6 +1968,7 @@ public class AutowiredAnnotationBeanPostProcessorTests {
}
@Test
+ @SuppressWarnings("rawtypes")
public void testGenericsBasedInjectionIntoMatchingTypeVariable() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
@@ -1747,6 +1986,7 @@ public class AutowiredAnnotationBeanPostProcessorTests {
}
@Test
+ @SuppressWarnings("rawtypes")
public void testGenericsBasedInjectionIntoUnresolvedTypeVariable() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
@@ -1764,6 +2004,7 @@ public class AutowiredAnnotationBeanPostProcessorTests {
}
@Test
+ @SuppressWarnings("rawtypes")
public void testGenericsBasedInjectionIntoTypeVariableSelectingBestMatch() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
@@ -1787,6 +2028,7 @@ public class AutowiredAnnotationBeanPostProcessorTests {
@Test
@Ignore // SPR-11521
+ @SuppressWarnings("rawtypes")
public void testGenericsBasedInjectionIntoTypeVariableSelectingBestMatchAgainstFactoryMethodSignature() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
@@ -1833,6 +2075,18 @@ public class AutowiredAnnotationBeanPostProcessorTests {
assertNotNull(bf.getBean(FooBar.class));
}
+ @Test
+ public void testSingleConstructorWithProvidedArgument() {
+ DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
+ AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
+ bpp.setBeanFactory(bf);
+ bf.addBeanPostProcessor(bpp);
+ RootBeanDefinition bd = new RootBeanDefinition(ProvidedArgumentBean.class);
+ bd.getConstructorArgumentValues().addGenericArgumentValue(Collections.singletonList("value"));
+ bf.registerBeanDefinition("beanWithArgs", bd);
+ assertNotNull(bf.getBean(ProvidedArgumentBean.class));
+ }
+
public static class ResourceInjectionBean {
@@ -2237,6 +2491,16 @@ public class AutowiredAnnotationBeanPostProcessorTests {
}
+ @SuppressWarnings("serial")
+ public static class MyTestBeanMap extends LinkedHashMap<String, TestBean> {
+ }
+
+
+ @SuppressWarnings("serial")
+ public static class MyTestBeanSet extends LinkedHashSet<TestBean> {
+ }
+
+
public static class MapConstructorInjectionBean {
private Map<String, TestBean> testBeanMap;
@@ -2252,6 +2516,28 @@ public class AutowiredAnnotationBeanPostProcessorTests {
}
+ public static class SetConstructorInjectionBean {
+
+ private Set<TestBean> testBeanSet;
+
+ @Autowired
+ public SetConstructorInjectionBean(Set<TestBean> testBeanSet) {
+ this.testBeanSet = testBeanSet;
+ }
+
+ public Set<TestBean> getTestBeanSet() {
+ return this.testBeanSet;
+ }
+ }
+
+
+ public static class SelfInjectionBean {
+
+ @Autowired
+ public SelfInjectionBean selfReference;
+ }
+
+
public static class MapFieldInjectionBean {
@Autowired
@@ -2309,6 +2595,29 @@ public class AutowiredAnnotationBeanPostProcessorTests {
}
+ public static class SmartObjectFactoryInjectionBean {
+
+ @Autowired
+ private ObjectProvider<TestBean> testBeanFactory;
+
+ public TestBean getTestBean() {
+ return this.testBeanFactory.getObject();
+ }
+
+ public TestBean getTestBean(String name) {
+ return this.testBeanFactory.getObject(name);
+ }
+
+ public TestBean getOptionalTestBean() {
+ return this.testBeanFactory.getIfAvailable();
+ }
+
+ public TestBean getUniqueTestBean() {
+ return this.testBeanFactory.getIfUnique();
+ }
+ }
+
+
public static class CustomAnnotationRequiredFieldResourceInjectionBean {
@MyAutowired(optional = false)
@@ -2363,7 +2672,7 @@ public class AutowiredAnnotationBeanPostProcessorTests {
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
- public static @interface MyAutowired {
+ public @interface MyAutowired {
boolean optional() default false;
}
@@ -2438,9 +2747,11 @@ public class AutowiredAnnotationBeanPostProcessorTests {
public static class GenericRepository<T> implements Repository<T> {
}
+ @SuppressWarnings("rawtypes")
public static class GenericRepositorySubclass extends GenericRepository {
}
+ @SuppressWarnings("rawtypes")
public static class SimpleRepository implements Repository {
}
@@ -2534,16 +2845,16 @@ public class AutowiredAnnotationBeanPostProcessorTests {
public Repository<?> stringRepository;
@Autowired @Qualifier("integerRepo")
- public Repository integerRepository;
+ public Repository<?> integerRepository;
@Autowired @Qualifier("stringRepo")
public Repository<?>[] stringRepositoryArray;
@Autowired @Qualifier("integerRepo")
- public Repository[] integerRepositoryArray;
+ public Repository<?>[] integerRepositoryArray;
@Autowired @Qualifier("stringRepo")
- public List<Repository> stringRepositoryList;
+ public List<Repository<?>> stringRepositoryList;
@Autowired @Qualifier("integerRepo")
public List<Repository<?>> integerRepositoryList;
@@ -2552,7 +2863,7 @@ public class AutowiredAnnotationBeanPostProcessorTests {
public Map<String, Repository<?>> stringRepositoryMap;
@Autowired @Qualifier("integerRepo")
- public Map<String, Repository> integerRepositoryMap;
+ public Map<String, Repository<?>> integerRepositoryMap;
}
@@ -2565,13 +2876,13 @@ public class AutowiredAnnotationBeanPostProcessorTests {
public Repository<String> stringRepository;
@Autowired
- public Repository[] repositoryArray;
+ public Repository<?>[] repositoryArray;
@Autowired
public Repository<String>[] stringRepositoryArray;
@Autowired
- public List<Repository> repositoryList;
+ public List<Repository<?>> repositoryList;
@Autowired
public List<Repository<String>> stringRepositoryList;
@@ -2793,6 +3104,7 @@ public class AutowiredAnnotationBeanPostProcessorTests {
return new GenericInterface1Impl<String>();
}
+ @SuppressWarnings("rawtypes")
public static GenericInterface1 createPlain() {
return new GenericInterface1Impl();
}
@@ -2827,6 +3139,7 @@ public class AutowiredAnnotationBeanPostProcessorTests {
}
+ @SuppressWarnings("rawtypes")
public static class PlainGenericInterface2Impl implements GenericInterface2 {
@Override
@@ -2836,26 +3149,32 @@ public class AutowiredAnnotationBeanPostProcessorTests {
}
+ @SuppressWarnings("rawtypes")
public interface StockMovement<P extends StockMovementInstruction> {
}
+ @SuppressWarnings("rawtypes")
public interface StockMovementInstruction<C extends StockMovement> {
}
+ @SuppressWarnings("rawtypes")
public interface StockMovementDao<S extends StockMovement> {
}
+ @SuppressWarnings("rawtypes")
public static class StockMovementImpl<P extends StockMovementInstruction> implements StockMovement<P> {
}
+ @SuppressWarnings("rawtypes")
public static class StockMovementInstructionImpl<C extends StockMovement> implements StockMovementInstruction<C> {
}
+ @SuppressWarnings("rawtypes")
public static class StockMovementDaoImpl<E extends StockMovement> implements StockMovementDao<E> {
}
@@ -2863,6 +3182,7 @@ public class AutowiredAnnotationBeanPostProcessorTests {
public static class StockServiceImpl {
@Autowired
+ @SuppressWarnings("rawtypes")
private StockMovementDao<StockMovement> stockMovementDao;
}
@@ -2926,4 +3246,30 @@ public class AutowiredAnnotationBeanPostProcessorTests {
}
}
+
+ public static class ProvidedArgumentBean {
+
+ public ProvidedArgumentBean(String[] args) {
+ }
+ }
+
+
+ public static class CollectionFactoryMethods {
+
+ public static Map<String, TestBean> testBeanMap() {
+ Map<String, TestBean> tbm = new LinkedHashMap<String, TestBean>();
+ tbm.put("testBean1", new TestBean("tb1"));
+ tbm.put("testBean2", new TestBean("tb2"));
+ return tbm;
+ }
+
+ public static Set<TestBean> testBeanSet() {
+ Set<TestBean> tbs = new LinkedHashSet<TestBean>();
+ tbs.add(new TestBean("tb1"));
+ tbs.add(new TestBean("tb2"));
+ return tbs;
+ }
+
+ }
+
}
diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurerTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurerTests.java
index 38a42d1d..903b34f1 100644
--- a/spring-beans/src/test/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurerTests.java
+++ b/spring-beans/src/test/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,11 +39,10 @@ import static org.springframework.beans.factory.support.BeanDefinitionReaderUtil
/**
* Unit tests for {@link PropertyPlaceholderConfigurer}.
*
- * @see PropertySourcesPlaceholderConfigurerTests
- * @see PropertyResourceConfigurerTests
* @author Chris Beams
*/
public class PropertyPlaceholderConfigurerTests {
+
private static final String P1 = "p1";
private static final String P1_LOCAL_PROPS_VAL = "p1LocalPropsVal";
private static final String P1_SYSTEM_PROPS_VAL = "p1SystemPropsVal";
@@ -55,11 +54,12 @@ public class PropertyPlaceholderConfigurerTests {
private AbstractBeanDefinition p1BeanDef;
+
@Before
public void setUp() {
p1BeanDef = rootBeanDefinition(TestBean.class)
- .addPropertyValue("name", "${"+P1+"}")
- .getBeanDefinition();
+ .addPropertyValue("name", "${" + P1 + "}")
+ .getBeanDefinition();
bf = new DefaultListableBeanFactory();
@@ -97,9 +97,9 @@ public class PropertyPlaceholderConfigurerTests {
public void resolveFromSystemProperties() {
getModifiableSystemEnvironment().put("otherKey", "systemValue");
p1BeanDef = rootBeanDefinition(TestBean.class)
- .addPropertyValue("name", "${"+P1+"}")
- .addPropertyValue("sex", "${otherKey}")
- .getBeanDefinition();
+ .addPropertyValue("name", "${" + P1 + "}")
+ .addPropertyValue("sex", "${otherKey}")
+ .getBeanDefinition();
registerWithGeneratedName(p1BeanDef, bf);
ppc.postProcessBeanFactory(bf);
TestBean bean = bf.getBean(TestBean.class);
@@ -167,9 +167,9 @@ public class PropertyPlaceholderConfigurerTests {
String P2_SYSTEM_ENV_VAL = "p2SystemEnvVal";
AbstractBeanDefinition p2BeanDef = rootBeanDefinition(TestBean.class)
- .addPropertyValue("name", "${"+P1+"}")
- .addPropertyValue("country", "${"+P2+"}")
- .getBeanDefinition();
+ .addPropertyValue("name", "${" + P1 + "}")
+ .addPropertyValue("country", "${" + P2 + "}")
+ .getBeanDefinition();
bf.registerBeanDefinition("p1Bean", p1BeanDef);
bf.registerBeanDefinition("p2Bean", p2BeanDef);
@@ -238,6 +238,33 @@ public class PropertyPlaceholderConfigurerTests {
getModifiableSystemEnvironment().remove("my.name");
}
+ @Test
+ public void trimValuesIsOffByDefault() {
+ PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
+ getModifiableSystemEnvironment().put("my.name", " myValue ");
+ DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
+ bf.registerBeanDefinition("testBean", rootBeanDefinition(TestBean.class)
+ .addPropertyValue("name", "${my.name}")
+ .getBeanDefinition());
+ ppc.postProcessBeanFactory(bf);
+ assertThat(bf.getBean(TestBean.class).getName(), equalTo(" myValue "));
+ getModifiableSystemEnvironment().remove("my.name");
+ }
+
+ @Test
+ public void trimValuesIsApplied() {
+ PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
+ ppc.setTrimValues(true);
+ getModifiableSystemEnvironment().put("my.name", " myValue ");
+ DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
+ bf.registerBeanDefinition("testBean", rootBeanDefinition(TestBean.class)
+ .addPropertyValue("name", "${my.name}")
+ .getBeanDefinition());
+ ppc.postProcessBeanFactory(bf);
+ assertThat(bf.getBean(TestBean.class).getName(), equalTo("myValue"));
+ getModifiableSystemEnvironment().remove("my.name");
+ }
+
@SuppressWarnings("unchecked")
private static Map<String, String> getModifiableSystemEnvironment() {
@@ -253,7 +280,8 @@ public class PropertyPlaceholderConfigurerTests {
if (obj != null && obj.getClass().getName().equals("java.lang.ProcessEnvironment$StringEnvironment")) {
return (Map<String, String>) obj;
}
- } catch (Exception ex) {
+ }
+ catch (Exception ex) {
throw new RuntimeException(ex);
}
}
@@ -263,8 +291,9 @@ public class PropertyPlaceholderConfigurerTests {
Class<?> processEnvironmentClass;
try {
processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment");
- } catch (Exception e) {
- throw new RuntimeException(e);
+ }
+ catch (Exception ex) {
+ throw new RuntimeException(ex);
}
try {
@@ -272,10 +301,12 @@ public class PropertyPlaceholderConfigurerTests {
theCaseInsensitiveEnvironmentField.setAccessible(true);
Object obj = theCaseInsensitiveEnvironmentField.get(null);
return (Map<String, String>) obj;
- } catch (NoSuchFieldException e) {
+ }
+ catch (NoSuchFieldException ex) {
// do nothing
- } catch (Exception e) {
- throw new RuntimeException(e);
+ }
+ catch (Exception ex) {
+ throw new RuntimeException(ex);
}
try {
@@ -283,10 +314,12 @@ public class PropertyPlaceholderConfigurerTests {
theEnvironmentField.setAccessible(true);
Object obj = theEnvironmentField.get(null);
return (Map<String, String>) obj;
- } catch (NoSuchFieldException e) {
+ }
+ catch (NoSuchFieldException ex) {
// do nothing
- } catch (Exception e) {
- throw new RuntimeException(e);
+ }
+ catch (Exception ex) {
+ throw new RuntimeException(ex);
}
throw new IllegalStateException();
diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/config/ServiceLocatorFactoryBeanTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/config/ServiceLocatorFactoryBeanTests.java
index 062f7757..e360a669 100644
--- a/spring-beans/src/test/java/org/springframework/beans/factory/config/ServiceLocatorFactoryBeanTests.java
+++ b/spring-beans/src/test/java/org/springframework/beans/factory/config/ServiceLocatorFactoryBeanTests.java
@@ -81,19 +81,22 @@ public final class ServiceLocatorFactoryBeanTests {
TestServiceLocator factory = (TestServiceLocator) bf.getBean("factory");
factory.getTestService();
fail("Must fail on more than one matching type");
- } catch (NoSuchBeanDefinitionException ex) { /* expected */ }
+ }
+ catch (NoSuchBeanDefinitionException ex) { /* expected */ }
try {
TestServiceLocator2 factory = (TestServiceLocator2) bf.getBean("factory2");
factory.getTestService(null);
fail("Must fail on more than one matching type");
- } catch (NoSuchBeanDefinitionException ex) { /* expected */ }
+ }
+ catch (NoSuchBeanDefinitionException ex) { /* expected */ }
try {
TestService2Locator factory = (TestService2Locator) bf.getBean("factory3");
factory.getTestService();
fail("Must fail on no matching types");
- } catch (NoSuchBeanDefinitionException ex) { /* expected */ }
+ }
+ catch (NoSuchBeanDefinitionException ex) { /* expected */ }
}
@Test
@@ -138,7 +141,8 @@ public final class ServiceLocatorFactoryBeanTests {
TestService2Locator factory3 = (TestService2Locator) bf.getBean("factory3");
factory3.getTestService();
fail("Must fail on no matching type");
- } catch (CustomServiceLocatorException3 ex) { /* expected */ }
+ }
+ catch (CustomServiceLocatorException3 ex) { /* expected */ }
}
@Test
@@ -160,7 +164,8 @@ public final class ServiceLocatorFactoryBeanTests {
try {
factory.getTestService("bogusTestService");
fail("Illegal operation allowed");
- } catch (NoSuchBeanDefinitionException ex) { /* expected */ }
+ }
+ catch (NoSuchBeanDefinitionException ex) { /* expected */ }
}
@Ignore @Test // worked when using an ApplicationContext (see commented), fails when using BeanFactory
@@ -275,7 +280,8 @@ public final class ServiceLocatorFactoryBeanTests {
try {
ServiceLocatorFactoryBean factory = new ServiceLocatorFactoryBean();
factory.setBeanFactory(beanFactory);
- } catch (FatalBeanException ex) {
+ }
+ catch (FatalBeanException ex) {
// expected
}
}
diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/security/CallbacksSecurityTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/security/CallbacksSecurityTests.java
index 42bb2334..73cf1ffc 100644
--- a/spring-beans/src/test/java/org/springframework/beans/factory/support/security/CallbacksSecurityTests.java
+++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/security/CallbacksSecurityTests.java
@@ -324,7 +324,8 @@ public class CallbacksSecurityTests {
try {
acc.checkPermission(new PropertyPermission("*", "read"));
fail("Acc should not have any permissions");
- } catch (SecurityException se) {
+ }
+ catch (SecurityException se) {
// expected
}
@@ -342,7 +343,8 @@ public class CallbacksSecurityTests {
}
}, acc);
fail("expected security exception");
- } catch (Exception ex) {
+ }
+ catch (Exception ex) {
}
final Class<ConstructorBean> cl = ConstructorBean.class;
@@ -356,7 +358,8 @@ public class CallbacksSecurityTests {
}
}, acc);
fail("expected security exception");
- } catch (Exception ex) {
+ }
+ catch (Exception ex) {
}
}
@@ -365,7 +368,8 @@ public class CallbacksSecurityTests {
try {
beanFactory.getBean("spring-init");
fail("expected security exception");
- } catch (BeanCreationException ex) {
+ }
+ catch (BeanCreationException ex) {
assertTrue(ex.getCause() instanceof SecurityException);
}
}
@@ -375,7 +379,8 @@ public class CallbacksSecurityTests {
try {
beanFactory.getBean("custom-init");
fail("expected security exception");
- } catch (BeanCreationException ex) {
+ }
+ catch (BeanCreationException ex) {
assertTrue(ex.getCause() instanceof SecurityException);
}
}
@@ -399,7 +404,8 @@ public class CallbacksSecurityTests {
try {
beanFactory.getBean("spring-factory");
fail("expected security exception");
- } catch (BeanCreationException ex) {
+ }
+ catch (BeanCreationException ex) {
assertTrue(ex.getCause() instanceof SecurityException);
}
@@ -416,7 +422,8 @@ public class CallbacksSecurityTests {
try {
beanFactory.getBean("custom-static-factory-method");
fail("expected security exception");
- } catch (BeanCreationException ex) {
+ }
+ catch (BeanCreationException ex) {
assertTrue(ex.getMostSpecificCause() instanceof SecurityException);
}
}
@@ -426,7 +433,8 @@ public class CallbacksSecurityTests {
try {
beanFactory.getBean("custom-factory-method");
fail("expected security exception");
- } catch (BeanCreationException ex) {
+ }
+ catch (BeanCreationException ex) {
assertTrue(ex.getMostSpecificCause() instanceof SecurityException);
}
}
@@ -436,7 +444,8 @@ public class CallbacksSecurityTests {
try {
beanFactory.getBean("privileged-static-factory-method");
fail("expected security exception");
- } catch (BeanCreationException ex) {
+ }
+ catch (BeanCreationException ex) {
assertTrue(ex.getMostSpecificCause() instanceof SecurityException);
}
}
@@ -446,7 +455,8 @@ public class CallbacksSecurityTests {
try {
beanFactory.getBean("constructor");
fail("expected security exception");
- } catch (BeanCreationException ex) {
+ }
+ catch (BeanCreationException ex) {
// expected
assertTrue(ex.getMostSpecificCause() instanceof SecurityException);
}
@@ -472,7 +482,8 @@ public class CallbacksSecurityTests {
try {
beanFactory.getBean("property-injection");
fail("expected security exception");
- } catch (BeanCreationException ex) {
+ }
+ catch (BeanCreationException ex) {
assertTrue(ex.getMessage().contains("security"));
}
diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/xml/DuplicateBeanIdTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/xml/DuplicateBeanIdTests.java
index 04c31efe..a42359b9 100644
--- a/spring-beans/src/test/java/org/springframework/beans/factory/xml/DuplicateBeanIdTests.java
+++ b/spring-beans/src/test/java/org/springframework/beans/factory/xml/DuplicateBeanIdTests.java
@@ -48,7 +48,8 @@ public class DuplicateBeanIdTests {
try {
reader.loadBeanDefinitions(new ClassPathResource("DuplicateBeanIdTests-sameLevel-context.xml", this.getClass()));
fail("expected parsing exception due to duplicate ids in same nesting level");
- } catch (Exception ex) {
+ }
+ catch (Exception ex) {
// expected
}
}
diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/xml/XmlBeanCollectionTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/xml/XmlBeanCollectionTests.java
index 53c951eb..c7ac6910 100644
--- a/spring-beans/src/test/java/org/springframework/beans/factory/xml/XmlBeanCollectionTests.java
+++ b/spring-beans/src/test/java/org/springframework/beans/factory/xml/XmlBeanCollectionTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,6 +29,7 @@ import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
+import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.BeanCreationException;
@@ -52,14 +53,16 @@ import static org.junit.Assert.*;
*/
public class XmlBeanCollectionTests {
- private final DefaultListableBeanFactory beanFactory;
+ private final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
- public XmlBeanCollectionTests() {
- this.beanFactory = new DefaultListableBeanFactory();
+
+ @Before
+ public void loadBeans() {
new XmlBeanDefinitionReader(this.beanFactory).loadBeanDefinitions(
new ClassPathResource("collections.xml", getClass()));
}
+
@Test
public void testCollectionFactoryDefaults() throws Exception {
ListFactoryBean listFactory = new ListFactoryBean();
@@ -328,6 +331,15 @@ public class XmlBeanCollectionTests {
}
@Test
+ public void testIntegerArray() throws Exception {
+ HasMap hasMap = (HasMap) this.beanFactory.getBean("integerArray");
+ assertTrue(hasMap.getIntegerArray().length == 3);
+ assertTrue(hasMap.getIntegerArray()[0].intValue() == 0);
+ assertTrue(hasMap.getIntegerArray()[1].intValue() == 1);
+ assertTrue(hasMap.getIntegerArray()[2].intValue() == 2);
+ }
+
+ @Test
public void testClassArray() throws Exception {
HasMap hasMap = (HasMap) this.beanFactory.getBean("classArray");
assertTrue(hasMap.getClassArray().length == 2);
@@ -336,12 +348,11 @@ public class XmlBeanCollectionTests {
}
@Test
- public void testIntegerArray() throws Exception {
- HasMap hasMap = (HasMap) this.beanFactory.getBean("integerArray");
- assertTrue(hasMap.getIntegerArray().length == 3);
- assertTrue(hasMap.getIntegerArray()[0].intValue() == 0);
- assertTrue(hasMap.getIntegerArray()[1].intValue() == 1);
- assertTrue(hasMap.getIntegerArray()[2].intValue() == 2);
+ public void testClassList() throws Exception {
+ HasMap hasMap = (HasMap) this.beanFactory.getBean("classList");
+ assertTrue(hasMap.getClassList().size()== 2);
+ assertTrue(hasMap.getClassList().get(0).equals(String.class));
+ assertTrue(hasMap.getClassList().get(1).equals(Exception.class));
}
@Test
@@ -447,4 +458,5 @@ public class XmlBeanCollectionTests {
return obj;
}
}
+
}
diff --git a/spring-beans/src/test/java/org/springframework/beans/propertyeditors/CustomEditorTests.java b/spring-beans/src/test/java/org/springframework/beans/propertyeditors/CustomEditorTests.java
index 5f233a66..806b6f3f 100644
--- a/spring-beans/src/test/java/org/springframework/beans/propertyeditors/CustomEditorTests.java
+++ b/spring-beans/src/test/java/org/springframework/beans/propertyeditors/CustomEditorTests.java
@@ -485,7 +485,8 @@ public class CustomEditorTests {
CustomNumberEditor editor = new CustomNumberEditor(Short.class, true);
editor.setAsText(String.valueOf(Short.MAX_VALUE + 1));
fail(Short.MAX_VALUE + 1 + " is greater than max value");
- } catch (NumberFormatException ex) {
+ }
+ catch (NumberFormatException ex) {
// expected
}
}
diff --git a/spring-beans/src/test/java/org/springframework/beans/propertyeditors/FileEditorTests.java b/spring-beans/src/test/java/org/springframework/beans/propertyeditors/FileEditorTests.java
index 17f6d984..ae6a5c68 100644
--- a/spring-beans/src/test/java/org/springframework/beans/propertyeditors/FileEditorTests.java
+++ b/spring-beans/src/test/java/org/springframework/beans/propertyeditors/FileEditorTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2008 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,20 +29,20 @@ import static org.junit.Assert.*;
* @author Thomas Risberg
* @author Chris Beams
*/
-public final class FileEditorTests {
+public class FileEditorTests {
@Test
public void testClasspathFileName() throws Exception {
PropertyEditor fileEditor = new FileEditor();
- fileEditor.setAsText("classpath:" + ClassUtils.classPackageAsResourcePath(getClass()) + "/"
- + ClassUtils.getShortName(getClass()) + ".class");
+ fileEditor.setAsText("classpath:" + ClassUtils.classPackageAsResourcePath(getClass()) + "/" +
+ ClassUtils.getShortName(getClass()) + ".class");
Object value = fileEditor.getValue();
assertTrue(value instanceof File);
File file = (File) value;
assertTrue(file.exists());
}
- @Test(expected=IllegalArgumentException.class)
+ @Test(expected = IllegalArgumentException.class)
public void testWithNonExistentResource() throws Exception {
PropertyEditor propertyEditor = new FileEditor();
propertyEditor.setAsText("classpath:no_way_this_file_is_found.doc");
@@ -71,8 +71,8 @@ public final class FileEditorTests {
@Test
public void testUnqualifiedFileNameFound() throws Exception {
PropertyEditor fileEditor = new FileEditor();
- String fileName = ClassUtils.classPackageAsResourcePath(getClass()) + "/" + ClassUtils.getShortName(getClass())
- + ".class";
+ String fileName = ClassUtils.classPackageAsResourcePath(getClass()) + "/" +
+ ClassUtils.getShortName(getClass()) + ".class";
fileEditor.setAsText(fileName);
Object value = fileEditor.getValue();
assertTrue(value instanceof File);
@@ -88,8 +88,8 @@ public final class FileEditorTests {
@Test
public void testUnqualifiedFileNameNotFound() throws Exception {
PropertyEditor fileEditor = new FileEditor();
- String fileName = ClassUtils.classPackageAsResourcePath(getClass()) + "/" + ClassUtils.getShortName(getClass())
- + ".clazz";
+ String fileName = ClassUtils.classPackageAsResourcePath(getClass()) + "/" +
+ ClassUtils.getShortName(getClass()) + ".clazz";
fileEditor.setAsText(fileName);
Object value = fileEditor.getValue();
assertTrue(value instanceof File);
@@ -101,4 +101,5 @@ public final class FileEditorTests {
}
assertTrue(absolutePath.endsWith(fileName));
}
+
}
diff --git a/spring-beans/src/test/java/org/springframework/beans/propertyeditors/InputStreamEditorTests.java b/spring-beans/src/test/java/org/springframework/beans/propertyeditors/InputStreamEditorTests.java
index 5073e5d5..6e90e92a 100644
--- a/spring-beans/src/test/java/org/springframework/beans/propertyeditors/InputStreamEditorTests.java
+++ b/spring-beans/src/test/java/org/springframework/beans/propertyeditors/InputStreamEditorTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -60,9 +60,8 @@ public class InputStreamEditorTests {
@Test(expected = IllegalArgumentException.class)
public void testWhenResourceDoesNotExist() throws Exception {
- String resource = "classpath:bingo!";
InputStreamEditor editor = new InputStreamEditor();
- editor.setAsText(resource);
+ editor.setAsText("classpath:bingo!");
}
@Test
diff --git a/spring-beans/src/test/java/org/springframework/beans/propertyeditors/PathEditorTests.java b/spring-beans/src/test/java/org/springframework/beans/propertyeditors/PathEditorTests.java
new file mode 100644
index 00000000..440a3e38
--- /dev/null
+++ b/spring-beans/src/test/java/org/springframework/beans/propertyeditors/PathEditorTests.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.beans.propertyeditors;
+
+import java.beans.PropertyEditor;
+import java.io.File;
+import java.nio.file.Path;
+
+import org.junit.Test;
+
+import org.springframework.util.ClassUtils;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Juergen Hoeller
+ * @since 4.3.2
+ */
+public class PathEditorTests {
+
+ @Test
+ public void testClasspathPathName() throws Exception {
+ PropertyEditor pathEditor = new PathEditor();
+ pathEditor.setAsText("classpath:" + ClassUtils.classPackageAsResourcePath(getClass()) + "/" +
+ ClassUtils.getShortName(getClass()) + ".class");
+ Object value = pathEditor.getValue();
+ assertTrue(value instanceof Path);
+ Path path = (Path) value;
+ assertTrue(path.toFile().exists());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testWithNonExistentResource() throws Exception {
+ PropertyEditor propertyEditor = new PathEditor();
+ propertyEditor.setAsText("classpath:/no_way_this_file_is_found.doc");
+ }
+
+ @Test
+ public void testWithNonExistentPath() throws Exception {
+ PropertyEditor pathEditor = new PathEditor();
+ pathEditor.setAsText("file:/no_way_this_file_is_found.doc");
+ Object value = pathEditor.getValue();
+ assertTrue(value instanceof Path);
+ Path path = (Path) value;
+ assertTrue(!path.toFile().exists());
+ }
+
+ @Test
+ public void testUnqualifiedPathNameFound() throws Exception {
+ PropertyEditor pathEditor = new PathEditor();
+ String fileName = ClassUtils.classPackageAsResourcePath(getClass()) + "/" +
+ ClassUtils.getShortName(getClass()) + ".class";
+ pathEditor.setAsText(fileName);
+ Object value = pathEditor.getValue();
+ assertTrue(value instanceof Path);
+ Path path = (Path) value;
+ File file = path.toFile();
+ assertTrue(file.exists());
+ String absolutePath = file.getAbsolutePath();
+ if (File.separatorChar == '\\') {
+ absolutePath = absolutePath.replace('\\', '/');
+ }
+ assertTrue(absolutePath.endsWith(fileName));
+ }
+
+}
diff --git a/spring-beans/src/test/java/org/springframework/beans/propertyeditors/ReaderEditorTests.java b/spring-beans/src/test/java/org/springframework/beans/propertyeditors/ReaderEditorTests.java
index 2729922a..d4e19b8b 100644
--- a/spring-beans/src/test/java/org/springframework/beans/propertyeditors/ReaderEditorTests.java
+++ b/spring-beans/src/test/java/org/springframework/beans/propertyeditors/ReaderEditorTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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 class ReaderEditorTests {
@Test(expected = IllegalArgumentException.class)
public void testCtorWithNullResourceEditor() throws Exception {
- new InputStreamEditor(null);
+ new ReaderEditor(null);
}
@Test
diff --git a/spring-beans/src/test/java/org/springframework/beans/propertyeditors/URIEditorTests.java b/spring-beans/src/test/java/org/springframework/beans/propertyeditors/URIEditorTests.java
index a347cfa8..4eb6245d 100644
--- a/spring-beans/src/test/java/org/springframework/beans/propertyeditors/URIEditorTests.java
+++ b/spring-beans/src/test/java/org/springframework/beans/propertyeditors/URIEditorTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2010 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,15 +31,6 @@ import static org.junit.Assert.*;
*/
public class URIEditorTests {
- private void doTestURI(String uriSpec) {
- PropertyEditor uriEditor = new URIEditor();
- uriEditor.setAsText(uriSpec);
- Object value = uriEditor.getValue();
- assertTrue(value instanceof URI);
- URI uri = (URI) value;
- assertEquals(uriSpec, uri.toString());
- }
-
@Test
public void standardURI() throws Exception {
doTestURI("mailto:juergen.hoeller@interface21.com");
@@ -141,4 +132,14 @@ public class URIEditorTests {
assertEquals("http://example.com/spaces%20and%20%E2%82%AC", uri.toASCIIString());
}
+
+ private void doTestURI(String uriSpec) {
+ PropertyEditor uriEditor = new URIEditor();
+ uriEditor.setAsText(uriSpec);
+ Object value = uriEditor.getValue();
+ assertTrue(value instanceof URI);
+ URI uri = (URI) value;
+ assertEquals(uriSpec, uri.toString());
+ }
+
}
diff --git a/spring-beans/src/test/java/org/springframework/beans/propertyeditors/URLEditorTests.java b/spring-beans/src/test/java/org/springframework/beans/propertyeditors/URLEditorTests.java
index 36738e1b..3259ec47 100644
--- a/spring-beans/src/test/java/org/springframework/beans/propertyeditors/URLEditorTests.java
+++ b/spring-beans/src/test/java/org/springframework/beans/propertyeditors/URLEditorTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2006 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,7 +29,12 @@ import static org.junit.Assert.*;
* @author Rick Evans
* @author Chris Beams
*/
-public final class URLEditorTests {
+public class URLEditorTests {
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testCtorWithNullResourceEditor() throws Exception {
+ new URLEditor(null);
+ }
@Test
public void testStandardURI() throws Exception {
@@ -63,7 +68,7 @@ public final class URLEditorTests {
assertTrue(!url.getProtocol().startsWith("classpath"));
}
- @Test(expected=IllegalArgumentException.class)
+ @Test(expected = IllegalArgumentException.class)
public void testWithNonExistentResource() throws Exception {
PropertyEditor urlEditor = new URLEditor();
urlEditor.setAsText("gonna:/freak/in/the/morning/freak/in/the.evening");
@@ -83,9 +88,4 @@ public final class URLEditorTests {
assertEquals("", urlEditor.getAsText());
}
- @Test(expected=IllegalArgumentException.class)
- public void testCtorWithNullResourceEditor() throws Exception {
- new URLEditor(null);
- }
-
}
diff --git a/spring-beans/src/test/java/org/springframework/tests/sample/beans/HasMap.java b/spring-beans/src/test/java/org/springframework/tests/sample/beans/HasMap.java
index 4639050b..51c5415b 100644
--- a/spring-beans/src/test/java/org/springframework/tests/sample/beans/HasMap.java
+++ b/spring-beans/src/test/java/org/springframework/tests/sample/beans/HasMap.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author 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.tests.sample.beans;
import java.util.IdentityHashMap;
+import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
@@ -38,9 +39,11 @@ public class HasMap {
private Object[] objectArray;
+ private Integer[] intArray;
+
private Class<?>[] classArray;
- private Integer[] intArray;
+ private List<Class<?>> classList;
private IdentityHashMap identityMap;
@@ -81,6 +84,14 @@ public class HasMap {
this.objectArray = objectArray;
}
+ public Integer[] getIntegerArray() {
+ return intArray;
+ }
+
+ public void setIntegerArray(Integer[] is) {
+ intArray = is;
+ }
+
public Class<?>[] getClassArray() {
return classArray;
}
@@ -89,12 +100,12 @@ public class HasMap {
this.classArray = classArray;
}
- public Integer[] getIntegerArray() {
- return intArray;
+ public List<Class<?>> getClassList() {
+ return classList;
}
- public void setIntegerArray(Integer[] is) {
- intArray = is;
+ public void setClassList(List<Class<?>> classList) {
+ this.classList = classList;
}
public IdentityHashMap getIdentityMap() {
diff --git a/spring-beans/src/test/resources/org/springframework/beans/factory/xml/collections.xml b/spring-beans/src/test/resources/org/springframework/beans/factory/xml/collections.xml
index 73294dfb..9047d26b 100644
--- a/spring-beans/src/test/resources/org/springframework/beans/factory/xml/collections.xml
+++ b/spring-beans/src/test/resources/org/springframework/beans/factory/xml/collections.xml
@@ -288,15 +288,6 @@
</property>
</bean>
- <bean id="classArray" class="org.springframework.tests.sample.beans.HasMap">
- <property name="classArray">
- <list>
- <value>java.lang.String</value>
- <value>java.lang.Exception</value>
- </list>
- </property>
- </bean>
-
<bean id="integerArray" class="org.springframework.tests.sample.beans.HasMap">
<property name="integerArray">
<list>
@@ -307,6 +298,14 @@
</property>
</bean>
+ <bean id="classArray" class="org.springframework.tests.sample.beans.HasMap">
+ <property name="classArray" value="java.lang.String,java.lang.Exception"/>
+ </bean>
+
+ <bean id="classList" class="org.springframework.tests.sample.beans.HasMap">
+ <property name="classList" value="java.lang.String,java.lang.Exception"/>
+ </bean>
+
<bean id="listFactory" class="org.springframework.beans.factory.config.ListFactoryBean">
<property name="sourceList">
<list>
diff --git a/spring-beans/src/test/resources/org/springframework/beans/factory/xml/import.xml b/spring-beans/src/test/resources/org/springframework/beans/factory/xml/import.xml
index 96446f02..278e5dff 100644
--- a/spring-beans/src/test/resources/org/springframework/beans/factory/xml/import.xml
+++ b/spring-beans/src/test/resources/org/springframework/beans/factory/xml/import.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
diff --git a/spring-context-support/src/main/java/org/springframework/cache/caffeine/CaffeineCache.java b/spring-context-support/src/main/java/org/springframework/cache/caffeine/CaffeineCache.java
new file mode 100644
index 00000000..d9fcec58
--- /dev/null
+++ b/spring-context-support/src/main/java/org/springframework/cache/caffeine/CaffeineCache.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cache.caffeine;
+
+import java.util.concurrent.Callable;
+import java.util.function.Function;
+
+import com.github.benmanes.caffeine.cache.LoadingCache;
+
+import org.springframework.cache.support.AbstractValueAdaptingCache;
+import org.springframework.lang.UsesJava8;
+import org.springframework.util.Assert;
+
+/**
+ * Spring {@link org.springframework.cache.Cache} adapter implementation
+ * on top of a Caffeine {@link com.github.benmanes.caffeine.cache.Cache} instance.
+ *
+ * <p>Requires Caffeine 2.1 or higher.
+ *
+ * @author Ben Manes
+ * @author Juergen Hoeller
+ * @author Stephane Nicoll
+ * @since 4.3
+ */
+@UsesJava8
+public class CaffeineCache extends AbstractValueAdaptingCache {
+
+ private final String name;
+
+ private final com.github.benmanes.caffeine.cache.Cache<Object, Object> cache;
+
+
+ /**
+ * Create a {@link CaffeineCache} instance with the specified name and the
+ * given internal {@link com.github.benmanes.caffeine.cache.Cache} to use.
+ * @param name the name of the cache
+ * @param cache the backing Caffeine Cache instance
+ */
+ public CaffeineCache(String name, com.github.benmanes.caffeine.cache.Cache<Object, Object> cache) {
+ this(name, cache, true);
+ }
+
+ /**
+ * Create a {@link CaffeineCache} instance with the specified name and the
+ * given internal {@link com.github.benmanes.caffeine.cache.Cache} to use.
+ * @param name the name of the cache
+ * @param cache the backing Caffeine Cache instance
+ * @param allowNullValues whether to accept and convert {@code null}
+ * values for this cache
+ */
+ public CaffeineCache(String name, com.github.benmanes.caffeine.cache.Cache<Object, Object> cache,
+ boolean allowNullValues) {
+
+ super(allowNullValues);
+ Assert.notNull(name, "Name must not be null");
+ Assert.notNull(cache, "Cache must not be null");
+ this.name = name;
+ this.cache = cache;
+ }
+
+
+ @Override
+ public final String getName() {
+ return this.name;
+ }
+
+ @Override
+ public final com.github.benmanes.caffeine.cache.Cache<Object, Object> getNativeCache() {
+ return this.cache;
+ }
+
+ @Override
+ public ValueWrapper get(Object key) {
+ if (this.cache instanceof LoadingCache) {
+ Object value = ((LoadingCache<Object, Object>) this.cache).get(key);
+ return toValueWrapper(value);
+ }
+ return super.get(key);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T> T get(Object key, final Callable<T> valueLoader) {
+ return (T) fromStoreValue(this.cache.get(key, new LoadFunction(valueLoader)));
+ }
+
+ @Override
+ protected Object lookup(Object key) {
+ return this.cache.getIfPresent(key);
+ }
+
+ @Override
+ public void put(Object key, Object value) {
+ this.cache.put(key, toStoreValue(value));
+ }
+
+ @Override
+ public ValueWrapper putIfAbsent(Object key, final Object value) {
+ PutIfAbsentFunction callable = new PutIfAbsentFunction(value);
+ Object result = this.cache.get(key, callable);
+ return (callable.called ? null : toValueWrapper(result));
+ }
+
+ @Override
+ public void evict(Object key) {
+ this.cache.invalidate(key);
+ }
+
+ @Override
+ public void clear() {
+ this.cache.invalidateAll();
+ }
+
+
+ private class PutIfAbsentFunction implements Function<Object, Object> {
+
+ private final Object value;
+
+ private boolean called;
+
+ public PutIfAbsentFunction(Object value) {
+ this.value = value;
+ }
+
+ @Override
+ public Object apply(Object key) {
+ this.called = true;
+ return toStoreValue(this.value);
+ }
+ }
+
+
+ private class LoadFunction implements Function<Object, Object> {
+
+ private final Callable<?> valueLoader;
+
+ public LoadFunction(Callable<?> valueLoader) {
+ this.valueLoader = valueLoader;
+ }
+
+ @Override
+ public Object apply(Object o) {
+ try {
+ return toStoreValue(valueLoader.call());
+ }
+ catch (Exception ex) {
+ throw new ValueRetrievalException(o, valueLoader, ex);
+ }
+ }
+ }
+
+}
diff --git a/spring-context-support/src/main/java/org/springframework/cache/caffeine/CaffeineCacheManager.java b/spring-context-support/src/main/java/org/springframework/cache/caffeine/CaffeineCacheManager.java
new file mode 100644
index 00000000..bedcff29
--- /dev/null
+++ b/spring-context-support/src/main/java/org/springframework/cache/caffeine/CaffeineCacheManager.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cache.caffeine;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import com.github.benmanes.caffeine.cache.CacheLoader;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.github.benmanes.caffeine.cache.CaffeineSpec;
+
+import org.springframework.cache.Cache;
+import org.springframework.cache.CacheManager;
+import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * {@link CacheManager} implementation that lazily builds {@link CaffeineCache}
+ * instances for each {@link #getCache} request. Also supports a 'static' mode
+ * where the set of cache names is pre-defined through {@link #setCacheNames},
+ * with no dynamic creation of further cache regions at runtime.
+ *
+ * <p>The configuration of the underlying cache can be fine-tuned through a
+ * {@link Caffeine} builder or {@link CaffeineSpec}, passed into this
+ * CacheManager through {@link #setCaffeine}/{@link #setCaffeineSpec}.
+ * A {@link CaffeineSpec}-compliant expression value can also be applied
+ * via the {@link #setCacheSpecification "cacheSpecification"} bean property.
+ *
+ * <p>Requires Caffeine 2.1 or higher.
+ *
+ * @author Ben Manes
+ * @author Juergen Hoeller
+ * @author Stephane Nicoll
+ * @since 4.3
+ * @see CaffeineCache
+ */
+public class CaffeineCacheManager implements CacheManager {
+
+ private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<String, Cache>(16);
+
+ private boolean dynamic = true;
+
+ private Caffeine<Object, Object> cacheBuilder = Caffeine.newBuilder();
+
+ private CacheLoader<Object, Object> cacheLoader;
+
+ private boolean allowNullValues = true;
+
+
+ /**
+ * Construct a dynamic CaffeineCacheManager,
+ * lazily creating cache instances as they are being requested.
+ */
+ public CaffeineCacheManager() {
+ }
+
+ /**
+ * Construct a static CaffeineCacheManager,
+ * managing caches for the specified cache names only.
+ */
+ public CaffeineCacheManager(String... cacheNames) {
+ setCacheNames(Arrays.asList(cacheNames));
+ }
+
+
+ /**
+ * Specify the set of cache names for this CacheManager's 'static' mode.
+ * <p>The number of caches and their names will be fixed after a call to this method,
+ * with no creation of further cache regions at runtime.
+ * <p>Calling this with a {@code null} collection argument resets the
+ * mode to 'dynamic', allowing for further creation of caches again.
+ */
+ public void setCacheNames(Collection<String> cacheNames) {
+ if (cacheNames != null) {
+ for (String name : cacheNames) {
+ this.cacheMap.put(name, createCaffeineCache(name));
+ }
+ this.dynamic = false;
+ }
+ else {
+ this.dynamic = true;
+ }
+ }
+
+ /**
+ * Set the Caffeine to use for building each individual
+ * {@link CaffeineCache} instance.
+ * @see #createNativeCaffeineCache
+ * @see com.github.benmanes.caffeine.cache.Caffeine#build()
+ */
+ public void setCaffeine(Caffeine<Object, Object> caffeine) {
+ Assert.notNull(caffeine, "Caffeine must not be null");
+ doSetCaffeine(caffeine);
+ }
+
+ /**
+ * Set the {@link CaffeineSpec} to use for building each individual
+ * {@link CaffeineCache} instance.
+ * @see #createNativeCaffeineCache
+ * @see com.github.benmanes.caffeine.cache.Caffeine#from(CaffeineSpec)
+ */
+ public void setCaffeineSpec(CaffeineSpec caffeineSpec) {
+ doSetCaffeine(Caffeine.from(caffeineSpec));
+ }
+
+ /**
+ * Set the Caffeine cache specification String to use for building each
+ * individual {@link CaffeineCache} instance. The given value needs to
+ * comply with Caffeine's {@link CaffeineSpec} (see its javadoc).
+ * @see #createNativeCaffeineCache
+ * @see com.github.benmanes.caffeine.cache.Caffeine#from(String)
+ */
+ public void setCacheSpecification(String cacheSpecification) {
+ doSetCaffeine(Caffeine.from(cacheSpecification));
+ }
+
+ /**
+ * Set the Caffeine CacheLoader to use for building each individual
+ * {@link CaffeineCache} instance, turning it into a LoadingCache.
+ * @see #createNativeCaffeineCache
+ * @see com.github.benmanes.caffeine.cache.Caffeine#build(CacheLoader)
+ * @see com.github.benmanes.caffeine.cache.LoadingCache
+ */
+ public void setCacheLoader(CacheLoader<Object, Object> cacheLoader) {
+ if (!ObjectUtils.nullSafeEquals(this.cacheLoader, cacheLoader)) {
+ this.cacheLoader = cacheLoader;
+ refreshKnownCaches();
+ }
+ }
+
+ /**
+ * Specify whether to accept and convert {@code null} values for all caches
+ * in this cache manager.
+ * <p>Default is "true", despite Caffeine itself not supporting {@code null} values.
+ * An internal holder object will be used to store user-level {@code null}s.
+ */
+ public void setAllowNullValues(boolean allowNullValues) {
+ if (this.allowNullValues != allowNullValues) {
+ this.allowNullValues = allowNullValues;
+ refreshKnownCaches();
+ }
+ }
+
+ /**
+ * Return whether this cache manager accepts and converts {@code null} values
+ * for all of its caches.
+ */
+ public boolean isAllowNullValues() {
+ return this.allowNullValues;
+ }
+
+
+ @Override
+ public Collection<String> getCacheNames() {
+ return Collections.unmodifiableSet(this.cacheMap.keySet());
+ }
+
+ @Override
+ public Cache getCache(String name) {
+ Cache cache = this.cacheMap.get(name);
+ if (cache == null && this.dynamic) {
+ synchronized (this.cacheMap) {
+ cache = this.cacheMap.get(name);
+ if (cache == null) {
+ cache = createCaffeineCache(name);
+ this.cacheMap.put(name, cache);
+ }
+ }
+ }
+ return cache;
+ }
+
+ /**
+ * Create a new CaffeineCache instance for the specified cache name.
+ * @param name the name of the cache
+ * @return the Spring CaffeineCache adapter (or a decorator thereof)
+ */
+ protected Cache createCaffeineCache(String name) {
+ return new CaffeineCache(name, createNativeCaffeineCache(name), isAllowNullValues());
+ }
+
+ /**
+ * Create a native Caffeine Cache instance for the specified cache name.
+ * @param name the name of the cache
+ * @return the native Caffeine Cache instance
+ */
+ protected com.github.benmanes.caffeine.cache.Cache<Object, Object> createNativeCaffeineCache(String name) {
+ if (this.cacheLoader != null) {
+ return this.cacheBuilder.build(this.cacheLoader);
+ }
+ else {
+ return this.cacheBuilder.build();
+ }
+ }
+
+ private void doSetCaffeine(Caffeine<Object, Object> cacheBuilder) {
+ if (!ObjectUtils.nullSafeEquals(this.cacheBuilder, cacheBuilder)) {
+ this.cacheBuilder = cacheBuilder;
+ refreshKnownCaches();
+ }
+ }
+
+ /**
+ * Create the known caches again with the current state of this manager.
+ */
+ private void refreshKnownCaches() {
+ for (Map.Entry<String, Cache> entry : this.cacheMap.entrySet()) {
+ entry.setValue(createCaffeineCache(entry.getKey()));
+ }
+ }
+
+}
diff --git a/spring-context-support/src/main/java/org/springframework/cache/caffeine/package-info.java b/spring-context-support/src/main/java/org/springframework/cache/caffeine/package-info.java
new file mode 100644
index 00000000..786880a7
--- /dev/null
+++ b/spring-context-support/src/main/java/org/springframework/cache/caffeine/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * Support classes for the open source cache in
+ * <a href="https://github.com/ben-manes/caffeine/">Caffeine</a> library,
+ * allowing to set up Caffeine caches within Spring's cache abstraction.
+ */
+package org.springframework.cache.caffeine;
diff --git a/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheCache.java b/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheCache.java
index 48663414..f1b7fd92 100644
--- a/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheCache.java
+++ b/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheCache.java
@@ -16,6 +16,8 @@
package org.springframework.cache.ehcache;
+import java.util.concurrent.Callable;
+
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import net.sf.ehcache.Status;
@@ -62,10 +64,47 @@ public class EhCacheCache implements Cache {
@Override
public ValueWrapper get(Object key) {
- Element element = this.cache.get(key);
+ Element element = lookup(key);
return toValueWrapper(element);
}
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T> T get(Object key, Callable<T> valueLoader) {
+ Element element = lookup(key);
+ if (element != null) {
+ return (T) element.getObjectValue();
+ }
+ else {
+ this.cache.acquireWriteLockOnKey(key);
+ try {
+ element = lookup(key); // One more attempt with the write lock
+ if (element != null) {
+ return (T) element.getObjectValue();
+ }
+ else {
+ return loadValue(key, valueLoader);
+ }
+ }
+ finally {
+ this.cache.releaseWriteLockOnKey(key);
+ }
+ }
+
+ }
+
+ private <T> T loadValue(Object key, Callable<T> valueLoader) {
+ T value;
+ try {
+ value = valueLoader.call();
+ }
+ catch (Exception ex) {
+ throw new ValueRetrievalException(key, valueLoader, ex);
+ }
+ put(key, value);
+ return value;
+ }
+
@Override
@SuppressWarnings("unchecked")
public <T> T get(Object key, Class<T> type) {
@@ -98,6 +137,11 @@ public class EhCacheCache implements Cache {
this.cache.removeAll();
}
+
+ private Element lookup(Object key) {
+ return this.cache.get(key);
+ }
+
private ValueWrapper toValueWrapper(Element element) {
return (element != null ? new SimpleValueWrapper(element.getObjectValue()) : null);
}
diff --git a/spring-context-support/src/main/java/org/springframework/cache/ehcache/package-info.java b/spring-context-support/src/main/java/org/springframework/cache/ehcache/package-info.java
index 4291b2de..edb951a4 100644
--- a/spring-context-support/src/main/java/org/springframework/cache/ehcache/package-info.java
+++ b/spring-context-support/src/main/java/org/springframework/cache/ehcache/package-info.java
@@ -7,6 +7,6 @@
* <p>Note: EhCache 3.x lives in a different package namespace
* and is not covered by the traditional support classes here.
* Instead, consider using it through JCache (JSR-107), with
- * Spring's support in {@link org.springframework.cache.jcache}.
+ * Spring's support in {@code org.springframework.cache.jcache}.
*/
package org.springframework.cache.ehcache;
diff --git a/spring-context-support/src/main/java/org/springframework/cache/guava/GuavaCache.java b/spring-context-support/src/main/java/org/springframework/cache/guava/GuavaCache.java
index 55e3c9a3..b9900c24 100644
--- a/spring-context-support/src/main/java/org/springframework/cache/guava/GuavaCache.java
+++ b/spring-context-support/src/main/java/org/springframework/cache/guava/GuavaCache.java
@@ -93,6 +93,25 @@ public class GuavaCache extends AbstractValueAdaptingCache {
return super.get(key);
}
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T> T get(Object key, final Callable<T> valueLoader) {
+ try {
+ return (T) fromStoreValue(this.cache.get(key, new Callable<Object>() {
+ @Override
+ public Object call() throws Exception {
+ return toStoreValue(valueLoader.call());
+ }
+ }));
+ }
+ catch (ExecutionException ex) {
+ throw new ValueRetrievalException(key, valueLoader, ex.getCause());
+ }
+ catch (UncheckedExecutionException ex) {
+ throw new ValueRetrievalException(key, valueLoader, ex.getCause());
+ }
+ }
+
@Override
protected Object lookup(Object key) {
return this.cache.getIfPresent(key);
diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/JCacheCache.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/JCacheCache.java
index d149a60b..c8033084 100644
--- a/spring-context-support/src/main/java/org/springframework/cache/jcache/JCacheCache.java
+++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/JCacheCache.java
@@ -16,6 +16,11 @@
package org.springframework.cache.jcache;
+import java.util.concurrent.Callable;
+import javax.cache.processor.EntryProcessor;
+import javax.cache.processor.EntryProcessorException;
+import javax.cache.processor.MutableEntry;
+
import org.springframework.cache.support.AbstractValueAdaptingCache;
import org.springframework.util.Assert;
@@ -70,6 +75,16 @@ public class JCacheCache extends AbstractValueAdaptingCache {
}
@Override
+ public <T> T get(Object key, Callable<T> valueLoader) {
+ try {
+ return this.cache.invoke(key, new ValueLoaderEntryProcessor<T>(), valueLoader);
+ }
+ catch (EntryProcessorException ex) {
+ throw new ValueRetrievalException(key, valueLoader, ex.getCause());
+ }
+ }
+
+ @Override
public void put(Object key, Object value) {
this.cache.put(key, toStoreValue(value));
}
@@ -90,4 +105,30 @@ public class JCacheCache extends AbstractValueAdaptingCache {
this.cache.removeAll();
}
+
+ private class ValueLoaderEntryProcessor<T> implements EntryProcessor<Object, Object, T> {
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public T process(MutableEntry<Object, Object> entry, Object... arguments)
+ throws EntryProcessorException {
+ Callable<T> valueLoader = (Callable<T>) arguments[0];
+ if (entry.exists()) {
+ return (T) fromStoreValue(entry.getValue());
+ }
+ else {
+ T value;
+ try {
+ value = valueLoader.call();
+ }
+ catch (Exception ex) {
+ throw new EntryProcessorException("Value loader '" + valueLoader + "' failed " +
+ "to compute value for key '" + entry.getKey() + "'", ex);
+ }
+ entry.setValue(toStoreValue(value));
+ return value;
+ }
+ }
+ }
+
}
diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractFallbackJCacheOperationSource.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractFallbackJCacheOperationSource.java
index 3f18c3c8..73313d22 100644
--- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractFallbackJCacheOperationSource.java
+++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractFallbackJCacheOperationSource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,8 +24,8 @@ import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.core.BridgeMethodResolver;
+import org.springframework.core.MethodClassKey;
import org.springframework.util.ClassUtils;
/**
@@ -51,12 +51,12 @@ public abstract class AbstractFallbackJCacheOperationSource implements JCacheOpe
protected final Log logger = LogFactory.getLog(getClass());
- private final Map<Object, Object> cache = new ConcurrentHashMap<Object, Object>(1024);
+ private final Map<MethodClassKey, Object> cache = new ConcurrentHashMap<MethodClassKey, Object>(1024);
@Override
public JCacheOperation<?> getCacheOperation(Method method, Class<?> targetClass) {
- Object cacheKey = new AnnotatedElementKey(method, targetClass);
+ MethodClassKey cacheKey = new MethodClassKey(method, targetClass);
Object cached = this.cache.get(cacheKey);
if (cached != null) {
@@ -95,7 +95,7 @@ public abstract class AbstractFallbackJCacheOperationSource implements JCacheOpe
return operation;
}
if (specificMethod != method) {
- // Fall back is to look at the original method.
+ // Fallback is to look at the original method.
operation = findCacheOperation(method, targetClass);
if (operation != null) {
return operation;
diff --git a/spring-context-support/src/main/java/org/springframework/cache/transaction/TransactionAwareCacheDecorator.java b/spring-context-support/src/main/java/org/springframework/cache/transaction/TransactionAwareCacheDecorator.java
index 4c5a7fdb..f96c04c0 100644
--- a/spring-context-support/src/main/java/org/springframework/cache/transaction/TransactionAwareCacheDecorator.java
+++ b/spring-context-support/src/main/java/org/springframework/cache/transaction/TransactionAwareCacheDecorator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
package org.springframework.cache.transaction;
+import java.util.concurrent.Callable;
+
import org.springframework.cache.Cache;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
@@ -73,6 +75,11 @@ public class TransactionAwareCacheDecorator implements Cache {
}
@Override
+ public <T> T get(Object key, Callable<T> valueLoader) {
+ return this.targetCache.get(key, valueLoader);
+ }
+
+ @Override
public void put(final Object key, final Object value) {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
diff --git a/spring-context-support/src/main/java/org/springframework/cache/transaction/package-info.java b/spring-context-support/src/main/java/org/springframework/cache/transaction/package-info.java
index 96743a86..7d210e6f 100644
--- a/spring-context-support/src/main/java/org/springframework/cache/transaction/package-info.java
+++ b/spring-context-support/src/main/java/org/springframework/cache/transaction/package-info.java
@@ -1,5 +1,5 @@
/**
- * Transaction-aware decorators for the the org.springframework.cache package.
+ * Transaction-aware decorators for the org.springframework.cache package.
* Provides synchronization of put operations with Spring-managed transactions.
*/
package org.springframework.cache.transaction;
diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/commonj/WorkManagerTaskExecutor.java b/spring-context-support/src/main/java/org/springframework/scheduling/commonj/WorkManagerTaskExecutor.java
index 3d153001..f39d7262 100644
--- a/spring-context-support/src/main/java/org/springframework/scheduling/commonj/WorkManagerTaskExecutor.java
+++ b/spring-context-support/src/main/java/org/springframework/scheduling/commonj/WorkManagerTaskExecutor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@ import commonj.work.WorkRejectedException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.task.AsyncListenableTaskExecutor;
+import org.springframework.core.task.TaskDecorator;
import org.springframework.core.task.TaskRejectedException;
import org.springframework.jndi.JndiLocatorSupport;
import org.springframework.scheduling.SchedulingException;
@@ -71,6 +72,8 @@ public class WorkManagerTaskExecutor extends JndiLocatorSupport
private WorkListener workListener;
+ private TaskDecorator taskDecorator;
+
/**
* Specify the CommonJ WorkManager to delegate to.
@@ -101,6 +104,20 @@ public class WorkManagerTaskExecutor extends JndiLocatorSupport
this.workListener = workListener;
}
+ /**
+ * Specify a custom {@link TaskDecorator} to be applied to any {@link Runnable}
+ * about to be executed.
+ * <p>Note that such a decorator is not necessarily being applied to the
+ * user-supplied {@code Runnable}/{@code Callable} but rather to the actual
+ * execution callback (which may be a wrapper around the user-supplied task).
+ * <p>The primary use case is to set some execution context around the task's
+ * invocation, or to provide some monitoring/statistics for task execution.
+ * @since 4.3
+ */
+ public void setTaskDecorator(TaskDecorator taskDecorator) {
+ this.taskDecorator = taskDecorator;
+ }
+
@Override
public void afterPropertiesSet() throws NamingException {
if (this.workManager == null) {
@@ -119,7 +136,7 @@ public class WorkManagerTaskExecutor extends JndiLocatorSupport
@Override
public void execute(Runnable task) {
Assert.state(this.workManager != null, "No WorkManager specified");
- Work work = new DelegatingWork(task);
+ Work work = new DelegatingWork(this.taskDecorator != null ? this.taskDecorator.decorate(task) : task);
try {
if (this.workListener != null) {
this.workManager.schedule(work, this.workListener);
diff --git a/spring-context-support/src/main/java/org/springframework/ui/freemarker/FreeMarkerConfigurationFactory.java b/spring-context-support/src/main/java/org/springframework/ui/freemarker/FreeMarkerConfigurationFactory.java
index a5c8a1b1..9cb27d29 100644
--- a/spring-context-support/src/main/java/org/springframework/ui/freemarker/FreeMarkerConfigurationFactory.java
+++ b/spring-context-support/src/main/java/org/springframework/ui/freemarker/FreeMarkerConfigurationFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -316,12 +316,8 @@ public class FreeMarkerConfigurationFactory {
* @throws TemplateException on FreeMarker initialization failure
* @see #createConfiguration()
*/
- @SuppressWarnings("deprecation")
protected Configuration newConfiguration() throws IOException, TemplateException {
- // The default Configuration constructor is deprecated as of FreeMarker 2.3.21,
- // in favor of specifying a compatibility version - which is a 2.3.21 feature.
- // We won't be able to call that for a long while, but custom subclasses can.
- return new Configuration();
+ return new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
}
/**
@@ -362,12 +358,12 @@ public class FreeMarkerConfigurationFactory {
}
/**
- * To be overridden by subclasses that want to to register custom
+ * To be overridden by subclasses that want to register custom
* TemplateLoader instances after this factory created its default
* template loaders.
* <p>Called by {@code createConfiguration()}. Note that specified
* "postTemplateLoaders" will be registered <i>after</i> any loaders
- * registered by this callback; as a consequence, they are are <i>not</i>
+ * registered by this callback; as a consequence, they are <i>not</i>
* included in the given List.
* @param templateLoaders the current List of TemplateLoader instances,
* to be modified by a subclass
@@ -399,7 +395,7 @@ public class FreeMarkerConfigurationFactory {
}
/**
- * To be overridden by subclasses that want to to perform custom
+ * To be overridden by subclasses that want to perform custom
* post-processing of the Configuration object after this factory
* performed its default initialization.
* <p>Called by {@code createConfiguration()}.
diff --git a/spring-context-support/src/main/java/org/springframework/ui/velocity/VelocityEngineFactory.java b/spring-context-support/src/main/java/org/springframework/ui/velocity/VelocityEngineFactory.java
index 18925330..399208e8 100644
--- a/spring-context-support/src/main/java/org/springframework/ui/velocity/VelocityEngineFactory.java
+++ b/spring-context-support/src/main/java/org/springframework/ui/velocity/VelocityEngineFactory.java
@@ -69,7 +69,9 @@ import org.springframework.util.StringUtils;
* @see VelocityEngineFactoryBean
* @see org.springframework.web.servlet.view.velocity.VelocityConfigurer
* @see org.apache.velocity.app.VelocityEngine
+ * @deprecated as of Spring 4.3, in favor of FreeMarker
*/
+@Deprecated
public class VelocityEngineFactory {
protected final Log logger = LogFactory.getLog(getClass());
@@ -338,7 +340,7 @@ public class VelocityEngineFactory {
}
/**
- * To be implemented by subclasses that want to to perform custom
+ * To be implemented by subclasses that want to perform custom
* post-processing of the VelocityEngine after this FactoryBean
* performed its default configuration (but before VelocityEngine.init).
* <p>Called by {@code createVelocityEngine()}.
diff --git a/spring-context-support/src/main/java/org/springframework/ui/velocity/VelocityEngineFactoryBean.java b/spring-context-support/src/main/java/org/springframework/ui/velocity/VelocityEngineFactoryBean.java
index 7c457826..bb2baa16 100644
--- a/spring-context-support/src/main/java/org/springframework/ui/velocity/VelocityEngineFactoryBean.java
+++ b/spring-context-support/src/main/java/org/springframework/ui/velocity/VelocityEngineFactoryBean.java
@@ -46,7 +46,9 @@ import org.springframework.context.ResourceLoaderAware;
* @see #setVelocityProperties
* @see #setResourceLoaderPath
* @see org.springframework.web.servlet.view.velocity.VelocityConfigurer
+ * @deprecated as of Spring 4.3, in favor of FreeMarker
*/
+@Deprecated
public class VelocityEngineFactoryBean extends VelocityEngineFactory
implements FactoryBean<VelocityEngine>, InitializingBean, ResourceLoaderAware {
diff --git a/spring-context-support/src/main/java/org/springframework/ui/velocity/VelocityEngineUtils.java b/spring-context-support/src/main/java/org/springframework/ui/velocity/VelocityEngineUtils.java
index bfc1e135..64022129 100644
--- a/spring-context-support/src/main/java/org/springframework/ui/velocity/VelocityEngineUtils.java
+++ b/spring-context-support/src/main/java/org/springframework/ui/velocity/VelocityEngineUtils.java
@@ -30,7 +30,9 @@ import org.apache.velocity.exception.VelocityException;
*
* @author Juergen Hoeller
* @since 22.01.2004
+ * @deprecated as of Spring 4.3, in favor of FreeMarker
*/
+@Deprecated
public abstract class VelocityEngineUtils {
/**
diff --git a/spring-context-support/src/test/java/org/springframework/cache/caffeine/CaffeineCacheManagerTests.java b/spring-context-support/src/test/java/org/springframework/cache/caffeine/CaffeineCacheManagerTests.java
new file mode 100644
index 00000000..ca4fc2ce
--- /dev/null
+++ b/spring-context-support/src/test/java/org/springframework/cache/caffeine/CaffeineCacheManagerTests.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cache.caffeine;
+
+import com.github.benmanes.caffeine.cache.CacheLoader;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.github.benmanes.caffeine.cache.CaffeineSpec;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import org.springframework.cache.Cache;
+import org.springframework.cache.CacheManager;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * @author Ben Manes
+ * @author Juergen Hoeller
+ * @author Stephane Nicoll
+ */
+public class CaffeineCacheManagerTests {
+
+ @Rule
+ public final ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void testDynamicMode() {
+ CacheManager cm = new CaffeineCacheManager();
+ Cache cache1 = cm.getCache("c1");
+ assertTrue(cache1 instanceof CaffeineCache);
+ Cache cache1again = cm.getCache("c1");
+ assertSame(cache1again, cache1);
+ Cache cache2 = cm.getCache("c2");
+ assertTrue(cache2 instanceof CaffeineCache);
+ Cache cache2again = cm.getCache("c2");
+ assertSame(cache2again, cache2);
+ Cache cache3 = cm.getCache("c3");
+ assertTrue(cache3 instanceof CaffeineCache);
+ Cache cache3again = cm.getCache("c3");
+ assertSame(cache3again, cache3);
+
+ cache1.put("key1", "value1");
+ assertEquals("value1", cache1.get("key1").get());
+ cache1.put("key2", 2);
+ assertEquals(2, cache1.get("key2").get());
+ cache1.put("key3", null);
+ assertNull(cache1.get("key3").get());
+ cache1.evict("key3");
+ assertNull(cache1.get("key3"));
+ }
+
+ @Test
+ public void testStaticMode() {
+ CaffeineCacheManager cm = new CaffeineCacheManager("c1", "c2");
+ Cache cache1 = cm.getCache("c1");
+ assertTrue(cache1 instanceof CaffeineCache);
+ Cache cache1again = cm.getCache("c1");
+ assertSame(cache1again, cache1);
+ Cache cache2 = cm.getCache("c2");
+ assertTrue(cache2 instanceof CaffeineCache);
+ Cache cache2again = cm.getCache("c2");
+ assertSame(cache2again, cache2);
+ Cache cache3 = cm.getCache("c3");
+ assertNull(cache3);
+
+ cache1.put("key1", "value1");
+ assertEquals("value1", cache1.get("key1").get());
+ cache1.put("key2", 2);
+ assertEquals(2, cache1.get("key2").get());
+ cache1.put("key3", null);
+ assertNull(cache1.get("key3").get());
+ cache1.evict("key3");
+ assertNull(cache1.get("key3"));
+
+ cm.setAllowNullValues(false);
+ Cache cache1x = cm.getCache("c1");
+ assertTrue(cache1x instanceof CaffeineCache);
+ assertTrue(cache1x != cache1);
+ Cache cache2x = cm.getCache("c2");
+ assertTrue(cache2x instanceof CaffeineCache);
+ assertTrue(cache2x != cache2);
+ Cache cache3x = cm.getCache("c3");
+ assertNull(cache3x);
+
+ cache1x.put("key1", "value1");
+ assertEquals("value1", cache1x.get("key1").get());
+ cache1x.put("key2", 2);
+ assertEquals(2, cache1x.get("key2").get());
+ try {
+ cache1x.put("key3", null);
+ fail("Should have thrown NullPointerException");
+ }
+ catch (NullPointerException ex) {
+ // expected
+ }
+
+ cm.setAllowNullValues(true);
+ Cache cache1y = cm.getCache("c1");
+
+ cache1y.put("key3", null);
+ assertNull(cache1y.get("key3").get());
+ cache1y.evict("key3");
+ assertNull(cache1y.get("key3"));
+ }
+
+ @Test
+ public void changeCaffeineRecreateCache() {
+ CaffeineCacheManager cm = new CaffeineCacheManager("c1");
+ Cache cache1 = cm.getCache("c1");
+
+ Caffeine<Object, Object> caffeine = Caffeine.newBuilder().maximumSize(10);
+ cm.setCaffeine(caffeine);
+ Cache cache1x = cm.getCache("c1");
+ assertTrue(cache1x != cache1);
+
+ cm.setCaffeine(caffeine); // Set same instance
+ Cache cache1xx = cm.getCache("c1");
+ assertSame(cache1x, cache1xx);
+ }
+
+ @Test
+ public void changeCaffeineSpecRecreateCache() {
+ CaffeineCacheManager cm = new CaffeineCacheManager("c1");
+ Cache cache1 = cm.getCache("c1");
+
+ cm.setCaffeineSpec(CaffeineSpec.parse("maximumSize=10"));
+ Cache cache1x = cm.getCache("c1");
+ assertTrue(cache1x != cache1);
+ }
+
+ @Test
+ public void changeCacheSpecificationRecreateCache() {
+ CaffeineCacheManager cm = new CaffeineCacheManager("c1");
+ Cache cache1 = cm.getCache("c1");
+
+ cm.setCacheSpecification("maximumSize=10");
+ Cache cache1x = cm.getCache("c1");
+ assertTrue(cache1x != cache1);
+ }
+
+ @Test
+ public void changeCacheLoaderRecreateCache() {
+ CaffeineCacheManager cm = new CaffeineCacheManager("c1");
+ Cache cache1 = cm.getCache("c1");
+
+ CacheLoader<Object, Object> loader = mockCacheLoader();
+ cm.setCacheLoader(loader);
+ Cache cache1x = cm.getCache("c1");
+ assertTrue(cache1x != cache1);
+
+ cm.setCacheLoader(loader); // Set same instance
+ Cache cache1xx = cm.getCache("c1");
+ assertSame(cache1x, cache1xx);
+ }
+
+ @Test
+ public void setCacheNameNullRestoreDynamicMode() {
+ CaffeineCacheManager cm = new CaffeineCacheManager("c1");
+ assertNull(cm.getCache("someCache"));
+ cm.setCacheNames(null);
+ assertNotNull(cm.getCache("someCache"));
+ }
+
+ @Test
+ public void cacheLoaderUseLoadingCache() {
+ CaffeineCacheManager cm = new CaffeineCacheManager("c1");
+ cm.setCacheLoader(new CacheLoader<Object, Object>() {
+ @Override
+ public Object load(Object key) throws Exception {
+ if ("ping".equals(key)) {
+ return "pong";
+ }
+ throw new IllegalArgumentException("I only know ping");
+ }
+ });
+ Cache cache1 = cm.getCache("c1");
+ Cache.ValueWrapper value = cache1.get("ping");
+ assertNotNull(value);
+ assertEquals("pong", value.get());
+
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("I only know ping");
+ assertNull(cache1.get("foo"));
+ }
+
+ @SuppressWarnings("unchecked")
+ private CacheLoader<Object, Object> mockCacheLoader() {
+ return mock(CacheLoader.class);
+ }
+
+}
diff --git a/spring-context-support/src/test/java/org/springframework/cache/caffeine/CaffeineCacheTests.java b/spring-context-support/src/test/java/org/springframework/cache/caffeine/CaffeineCacheTests.java
new file mode 100644
index 00000000..5ede180c
--- /dev/null
+++ b/spring-context-support/src/test/java/org/springframework/cache/caffeine/CaffeineCacheTests.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2002-2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cache.caffeine;
+
+import com.github.benmanes.caffeine.cache.Caffeine;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.springframework.cache.AbstractCacheTests;
+import org.springframework.cache.Cache;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Ben Manes
+ * @author Stephane Nicoll
+ */
+public class CaffeineCacheTests extends AbstractCacheTests<CaffeineCache> {
+
+ private com.github.benmanes.caffeine.cache.Cache<Object, Object> nativeCache;
+
+ private CaffeineCache cache;
+
+ @Before
+ public void setUp() {
+ nativeCache = Caffeine.newBuilder().build();
+ cache = new CaffeineCache(CACHE_NAME, nativeCache);
+ }
+
+ @Override
+ protected CaffeineCache getCache() {
+ return cache;
+ }
+
+ @Override
+ protected Object getNativeCache() {
+ return nativeCache;
+ }
+
+ @Test
+ public void testPutIfAbsentNullValue() throws Exception {
+ CaffeineCache cache = getCache();
+
+ Object key = new Object();
+ Object value = null;
+
+ assertNull(cache.get(key));
+ assertNull(cache.putIfAbsent(key, value));
+ assertEquals(value, cache.get(key).get());
+ Cache.ValueWrapper wrapper = cache.putIfAbsent(key, "anotherValue");
+ assertNotNull(wrapper); // A value is set but is 'null'
+ assertEquals(null, wrapper.get());
+ assertEquals(value, cache.get(key).get()); // not changed
+ }
+
+}
diff --git a/spring-context-support/src/test/java/org/springframework/cache/ehcache/EhCacheCacheTests.java b/spring-context-support/src/test/java/org/springframework/cache/ehcache/EhCacheCacheTests.java
index 8d135755..f8541b07 100644
--- a/spring-context-support/src/test/java/org/springframework/cache/ehcache/EhCacheCacheTests.java
+++ b/spring-context-support/src/test/java/org/springframework/cache/ehcache/EhCacheCacheTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2010-2014 the original author or authors.
+ * Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -82,4 +82,5 @@ public class EhCacheCacheTests extends AbstractCacheTests<EhCacheCache> {
Thread.sleep(5 * 1000);
assertNull(cache.get(key));
}
+
}
diff --git a/spring-context-support/src/test/java/org/springframework/cache/guava/GuavaCacheTests.java b/spring-context-support/src/test/java/org/springframework/cache/guava/GuavaCacheTests.java
index f9964e03..d50213c0 100644
--- a/spring-context-support/src/test/java/org/springframework/cache/guava/GuavaCacheTests.java
+++ b/spring-context-support/src/test/java/org/springframework/cache/guava/GuavaCacheTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -64,4 +64,5 @@ public class GuavaCacheTests extends AbstractCacheTests<GuavaCache> {
assertEquals(null, wrapper.get());
assertEquals(value, cache.get(key).get()); // not changed
}
+
}
diff --git a/spring-context/src/main/java/org/springframework/cache/Cache.java b/spring-context/src/main/java/org/springframework/cache/Cache.java
index 3e8a7978..ebd26020 100644
--- a/spring-context/src/main/java/org/springframework/cache/Cache.java
+++ b/spring-context/src/main/java/org/springframework/cache/Cache.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
package org.springframework.cache;
+import java.util.concurrent.Callable;
+
/**
* Interface that defines common cache operations.
*
@@ -25,6 +27,7 @@ package org.springframework.cache;
*
* @author Costin Leau
* @author Juergen Hoeller
+ * @author Stephane Nicoll
* @since 3.1
*/
public interface Cache {
@@ -35,7 +38,7 @@ public interface Cache {
String getName();
/**
- * Return the the underlying native cache provider.
+ * Return the underlying native cache provider.
*/
Object getNativeCache();
@@ -74,6 +77,23 @@ public interface Cache {
<T> T get(Object key, Class<T> type);
/**
+ * Return the value to which this cache maps the specified key, obtaining
+ * that value from {@code valueLoader} if necessary. This method provides
+ * a simple substitute for the conventional "if cached, return; otherwise
+ * create, cache and return" pattern.
+ * <p>If possible, implementations should ensure that the loading operation
+ * is synchronized so that the specified {@code valueLoader} is only called
+ * once in case of concurrent access on the same key.
+ * <p>If the {@code valueLoader} throws an exception, it is wrapped in
+ * a {@link ValueRetrievalException}
+ * @param key the key whose associated value is to be returned
+ * @return the value to which this cache maps the specified key
+ * @throws ValueRetrievalException if the {@code valueLoader} throws an exception
+ * @since 4.3
+ */
+ <T> T get(Object key, Callable<T> valueLoader);
+
+ /**
* Associate the specified value with the specified key in this cache.
* <p>If the cache previously contained a mapping for this key, the old
* value is replaced by the specified value.
@@ -133,4 +153,25 @@ public interface Cache {
Object get();
}
+
+ /**
+ * Wrapper exception to be thrown from {@link #get(Object, Callable)}
+ * in case of the value loader callback failing with an exception.
+ * @since 4.3
+ */
+ @SuppressWarnings("serial")
+ class ValueRetrievalException extends RuntimeException {
+
+ private final Object key;
+
+ public ValueRetrievalException(Object key, Callable<?> loader, Throwable ex) {
+ super(String.format("Value for key '%s' could not be loaded using '%s'", key, loader), ex);
+ this.key = key;
+ }
+
+ public Object getKey() {
+ return this.key;
+ }
+ }
+
}
diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/CacheEvict.java b/spring-context/src/main/java/org/springframework/cache/annotation/CacheEvict.java
index a2d0c2f3..f12d2119 100644
--- a/spring-context/src/main/java/org/springframework/cache/annotation/CacheEvict.java
+++ b/spring-context/src/main/java/org/springframework/cache/annotation/CacheEvict.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,6 +29,9 @@ import org.springframework.core.annotation.AliasFor;
* Annotation indicating that a method (or all methods on a class) triggers a
* {@link org.springframework.cache.Cache#evict(Object) cache evict} operation.
*
+ * <p>This annotation may be used as a <em>meta-annotation</em> to create custom
+ * <em>composed annotations</em> with attribute overrides.
+ *
* @author Costin Leau
* @author Stephane Nicoll
* @author Sam Brannen
@@ -62,18 +65,18 @@ public @interface CacheEvict {
* Spring Expression Language (SpEL) expression for computing the key dynamically.
* <p>Default is {@code ""}, meaning all method parameters are considered as a key,
* unless a custom {@link #keyGenerator} has been set.
- * <p>The SpEL expression evaluates again a dedicated context that provides the
+ * <p>The SpEL expression evaluates against a dedicated context that provides the
* following meta-data:
* <ul>
* <li>{@code #result} for a reference to the result of the method invocation, which
* can only be used if {@link #beforeInvocation()} is {@code false}.</li>
- * <li>{@code #root.method}, {@code #root.target} and {@code #root.caches} for a
- * reference to the {@link java.lang.reflect.Method method}, target object and
+ * <li>{@code #root.method}, {@code #root.target}, and {@code #root.caches} for
+ * references to the {@link java.lang.reflect.Method method}, target object, and
* affected cache(s) respectively.</li>
* <li>Shortcuts for the method name ({@code #root.methodName}) and target class
* ({@code #root.targetClass}) are also available.
* <li>Method arguments can be accessed by index. For instance the second argument
- * can be access via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
+ * can be accessed via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
* can also be accessed by name if that information is available.</li>
* </ul>
*/
@@ -108,16 +111,16 @@ public @interface CacheEvict {
* Spring Expression Language (SpEL) expression used for making the cache
* eviction operation conditional.
* <p>Default is {@code ""}, meaning the cache eviction is always performed.
- * <p>The SpEL expression evaluates again a dedicated context that provides the
+ * <p>The SpEL expression evaluates against a dedicated context that provides the
* following meta-data:
* <ul>
- * <li>{@code #root.method}, {@code #root.target} and {@code #root.caches} for a
- * reference to the {@link java.lang.reflect.Method method}, target object and
+ * <li>{@code #root.method}, {@code #root.target}, and {@code #root.caches} for
+ * references to the {@link java.lang.reflect.Method method}, target object, and
* affected cache(s) respectively.</li>
* <li>Shortcuts for the method name ({@code #root.methodName}) and target class
* ({@code #root.targetClass}) are also available.
* <li>Method arguments can be accessed by index. For instance the second argument
- * can be access via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
+ * can be accessed via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
* can also be accessed by name if that information is available.</li>
* </ul>
*/
diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/CachePut.java b/spring-context/src/main/java/org/springframework/cache/annotation/CachePut.java
index bcea5494..cd48e410 100644
--- a/spring-context/src/main/java/org/springframework/cache/annotation/CachePut.java
+++ b/spring-context/src/main/java/org/springframework/cache/annotation/CachePut.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +33,9 @@ import org.springframework.core.annotation.AliasFor;
* does not cause the advised method to be skipped. Rather, it always causes the
* method to be invoked and its result to be stored in the associated cache.
*
+ * <p>This annotation may be used as a <em>meta-annotation</em> to create custom
+ * <em>composed annotations</em> with attribute overrides.
+ *
* @author Costin Leau
* @author Phillip Webb
* @author Stephane Nicoll
@@ -67,17 +70,17 @@ public @interface CachePut {
* Spring Expression Language (SpEL) expression for computing the key dynamically.
* <p>Default is {@code ""}, meaning all method parameters are considered as a key,
* unless a custom {@link #keyGenerator} has been set.
- * <p>The SpEL expression evaluates again a dedicated context that provides the
+ * <p>The SpEL expression evaluates against a dedicated context that provides the
* following meta-data:
* <ul>
* <li>{@code #result} for a reference to the result of the method invocation.</li>
- * <li>{@code #root.method}, {@code #root.target} and {@code #root.caches} for a
- * reference to the {@link java.lang.reflect.Method method}, target object and
+ * <li>{@code #root.method}, {@code #root.target}, and {@code #root.caches} for
+ * references to the {@link java.lang.reflect.Method method}, target object, and
* affected cache(s) respectively.</li>
* <li>Shortcuts for the method name ({@code #root.methodName}) and target class
* ({@code #root.targetClass}) are also available.
* <li>Method arguments can be accessed by index. For instance the second argument
- * can be access via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
+ * can be accessed via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
* can also be accessed by name if that information is available.</li>
* </ul>
*/
@@ -112,16 +115,16 @@ public @interface CachePut {
* Spring Expression Language (SpEL) expression used for making the cache
* put operation conditional.
* <p>Default is {@code ""}, meaning the method result is always cached.
- * <p>The SpEL expression evaluates again a dedicated context that provides the
+ * <p>The SpEL expression evaluates against a dedicated context that provides the
* following meta-data:
* <ul>
- * <li>{@code #root.method}, {@code #root.target} and {@code #root.caches} for a
- * reference to the {@link java.lang.reflect.Method method}, target object and
+ * <li>{@code #root.method}, {@code #root.target}, and {@code #root.caches} for
+ * references to the {@link java.lang.reflect.Method method}, target object, and
* affected cache(s) respectively.</li>
* <li>Shortcuts for the method name ({@code #root.methodName}) and target class
* ({@code #root.targetClass}) are also available.
* <li>Method arguments can be accessed by index. For instance the second argument
- * can be access via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
+ * can be accessed via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
* can also be accessed by name if that information is available.</li>
* </ul>
*/
@@ -132,17 +135,17 @@ public @interface CachePut {
* <p>Unlike {@link #condition}, this expression is evaluated after the method
* has been called and can therefore refer to the {@code result}.
* <p>Default is {@code ""}, meaning that caching is never vetoed.
- * <p>The SpEL expression evaluates again a dedicated context that provides the
+ * <p>The SpEL expression evaluates against a dedicated context that provides the
* following meta-data:
* <ul>
* <li>{@code #result} for a reference to the result of the method invocation.</li>
- * <li>{@code #root.method}, {@code #root.target} and {@code #root.caches} for a
- * reference to the {@link java.lang.reflect.Method method}, target object and
+ * <li>{@code #root.method}, {@code #root.target}, and {@code #root.caches} for
+ * references to the {@link java.lang.reflect.Method method}, target object, and
* affected cache(s) respectively.</li>
* <li>Shortcuts for the method name ({@code #root.methodName}) and target class
* ({@code #root.targetClass}) are also available.
* <li>Method arguments can be accessed by index. For instance the second argument
- * can be access via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
+ * can be accessed via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
* can also be accessed by name if that information is available.</li>
* </ul>
* @since 3.2
diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/Cacheable.java b/spring-context/src/main/java/org/springframework/cache/annotation/Cacheable.java
index a5d336a6..225ada51 100644
--- a/spring-context/src/main/java/org/springframework/cache/annotation/Cacheable.java
+++ b/spring-context/src/main/java/org/springframework/cache/annotation/Cacheable.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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 java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import java.util.concurrent.Callable;
import org.springframework.core.annotation.AliasFor;
@@ -39,6 +40,9 @@ import org.springframework.core.annotation.AliasFor;
* <p>If no value is found in the cache for the computed key, the target method
* will be invoked and the returned value stored in the associated cache.
*
+ * <p>This annotation may be used as a <em>meta-annotation</em> to create custom
+ * <em>composed annotations</em> with attribute overrides.
+ *
* @author Costin Leau
* @author Phillip Webb
* @author Stephane Nicoll
@@ -73,16 +77,16 @@ public @interface Cacheable {
* Spring Expression Language (SpEL) expression for computing the key dynamically.
* <p>Default is {@code ""}, meaning all method parameters are considered as a key,
* unless a custom {@link #keyGenerator} has been configured.
- * <p>The SpEL expression evaluates again a dedicated context that provides the
+ * <p>The SpEL expression evaluates against a dedicated context that provides the
* following meta-data:
* <ul>
- * <li>{@code #root.method}, {@code #root.target} and {@code #root.caches} for a
- * reference to the {@link java.lang.reflect.Method method}, target object and
+ * <li>{@code #root.method}, {@code #root.target}, and {@code #root.caches} for
+ * references to the {@link java.lang.reflect.Method method}, target object, and
* affected cache(s) respectively.</li>
* <li>Shortcuts for the method name ({@code #root.methodName}) and target class
* ({@code #root.targetClass}) are also available.
* <li>Method arguments can be accessed by index. For instance the second argument
- * can be access via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
+ * can be accessed via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
* can also be accessed by name if that information is available.</li>
* </ul>
*/
@@ -117,16 +121,16 @@ public @interface Cacheable {
* Spring Expression Language (SpEL) expression used for making the method
* caching conditional.
* <p>Default is {@code ""}, meaning the method result is always cached.
- * <p>The SpEL expression evaluates again a dedicated context that provides the
+ * <p>The SpEL expression evaluates against a dedicated context that provides the
* following meta-data:
* <ul>
- * <li>{@code #root.method}, {@code #root.target} and {@code #root.caches} for a
- * reference to the {@link java.lang.reflect.Method method}, target object and
+ * <li>{@code #root.method}, {@code #root.target}, and {@code #root.caches} for
+ * references to the {@link java.lang.reflect.Method method}, target object, and
* affected cache(s) respectively.</li>
* <li>Shortcuts for the method name ({@code #root.methodName}) and target class
* ({@code #root.targetClass}) are also available.
* <li>Method arguments can be accessed by index. For instance the second argument
- * can be access via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
+ * can be accessed via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
* can also be accessed by name if that information is available.</li>
* </ul>
*/
@@ -137,21 +141,38 @@ public @interface Cacheable {
* <p>Unlike {@link #condition}, this expression is evaluated after the method
* has been called and can therefore refer to the {@code result}.
* <p>Default is {@code ""}, meaning that caching is never vetoed.
- * <p>The SpEL expression evaluates again a dedicated context that provides the
+ * <p>The SpEL expression evaluates against a dedicated context that provides the
* following meta-data:
* <ul>
* <li>{@code #result} for a reference to the result of the method invocation.</li>
- * <li>{@code #root.method}, {@code #root.target} and {@code #root.caches} for a
- * reference to the {@link java.lang.reflect.Method method}, target object and
+ * <li>{@code #root.method}, {@code #root.target}, and {@code #root.caches} for
+ * references to the {@link java.lang.reflect.Method method}, target object, and
* affected cache(s) respectively.</li>
* <li>Shortcuts for the method name ({@code #root.methodName}) and target class
* ({@code #root.targetClass}) are also available.
* <li>Method arguments can be accessed by index. For instance the second argument
- * can be access via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
+ * can be accessed via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
* can also be accessed by name if that information is available.</li>
* </ul>
* @since 3.2
*/
String unless() default "";
+ /**
+ * Synchronize the invocation of the underlying method if several threads are
+ * attempting to load a value for the same key. The synchronization leads to
+ * a couple of limitations:
+ * <ol>
+ * <li>{@link #unless()} is not supported</li>
+ * <li>Only one cache may be specified</li>
+ * <li>No other cache-related operation can be combined</li>
+ * </ol>
+ * This is effectively a hint and the actual cache provider that you are
+ * using may not support it in a synchronized fashion. Check your provider
+ * documentation for more details on the actual semantics.
+ * @since 4.3
+ * @see org.springframework.cache.Cache#get(Object, Callable)
+ */
+ boolean sync() default false;
+
}
diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/Caching.java b/spring-context/src/main/java/org/springframework/cache/annotation/Caching.java
index 6802fd5d..08a4482f 100644
--- a/spring-context/src/main/java/org/springframework/cache/annotation/Caching.java
+++ b/spring-context/src/main/java/org/springframework/cache/annotation/Caching.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author 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,9 @@ import java.lang.annotation.Target;
/**
* Group annotation for multiple cache annotations (of different or the same type).
*
+ * <p>This annotation may be used as a <em>meta-annotation</em> to create custom
+ * <em>composed annotations</em> with attribute overrides.
+ *
* @author Costin Leau
* @author Chris Beams
* @since 3.1
diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/CachingConfigurationSelector.java b/spring-context/src/main/java/org/springframework/cache/annotation/CachingConfigurationSelector.java
index f99b40d7..f7f6fa4e 100644
--- a/spring-context/src/main/java/org/springframework/cache/annotation/CachingConfigurationSelector.java
+++ b/spring-context/src/main/java/org/springframework/cache/annotation/CachingConfigurationSelector.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -52,7 +52,7 @@ public class CachingConfigurationSelector extends AdviceModeImportSelector<Enabl
private static final boolean jsr107Present = ClassUtils.isPresent(
"javax.cache.Cache", CachingConfigurationSelector.class.getClassLoader());
- private static final boolean jCacheImplPresent = ClassUtils.isPresent(
+ private static final boolean jcacheImplPresent = ClassUtils.isPresent(
PROXY_JCACHE_CONFIGURATION_CLASS, CachingConfigurationSelector.class.getClassLoader());
@@ -81,7 +81,7 @@ public class CachingConfigurationSelector extends AdviceModeImportSelector<Enabl
List<String> result = new ArrayList<String>();
result.add(AutoProxyRegistrar.class.getName());
result.add(ProxyCachingConfiguration.class.getName());
- if (jsr107Present && jCacheImplPresent) {
+ if (jsr107Present && jcacheImplPresent) {
result.add(PROXY_JCACHE_CONFIGURATION_CLASS);
}
return result.toArray(new String[result.size()]);
@@ -94,7 +94,7 @@ public class CachingConfigurationSelector extends AdviceModeImportSelector<Enabl
private String[] getAspectJImports() {
List<String> result = new ArrayList<String>();
result.add(CACHE_ASPECT_CONFIGURATION_CLASS_NAME);
- if (jsr107Present && jCacheImplPresent) {
+ if (jsr107Present && jcacheImplPresent) {
result.add(JCACHE_ASPECT_CONFIGURATION_CLASS_NAME);
}
return result.toArray(new String[result.size()]);
diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/EnableCaching.java b/spring-context/src/main/java/org/springframework/cache/annotation/EnableCaching.java
index 511e4de6..0ec602b9 100644
--- a/spring-context/src/main/java/org/springframework/cache/annotation/EnableCaching.java
+++ b/spring-context/src/main/java/org/springframework/cache/annotation/EnableCaching.java
@@ -47,7 +47,7 @@ import org.springframework.core.Ordered;
* public CacheManager cacheManager() {
* // configure and return an implementation of Spring's CacheManager SPI
* SimpleCacheManager cacheManager = new SimpleCacheManager();
- * cacheManager.addCaches(Arrays.asList(new ConcurrentMapCache("default")));
+ * cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("default")));
* return cacheManager;
* }
* }</pre>
@@ -99,7 +99,7 @@ import org.springframework.core.Ordered;
* <p>For those that wish to establish a more direct relationship between
* {@code @EnableCaching} and the exact cache manager bean to be used,
* the {@link CachingConfigurer} callback interface may be implemented.
- * Notice the the {@code @Override}-annotated methods below:
+ * Notice the {@code @Override}-annotated methods below:
*
* <pre class="code">
* &#064;Configuration
@@ -117,7 +117,7 @@ import org.springframework.core.Ordered;
* public CacheManager cacheManager() {
* // configure and return an implementation of Spring's CacheManager SPI
* SimpleCacheManager cacheManager = new SimpleCacheManager();
- * cacheManager.addCaches(Arrays.asList(new ConcurrentMapCache("default")));
+ * cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("default")));
* return cacheManager;
* }
*
diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.java b/spring-context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.java
index d6992bd2..d58bd133 100644
--- a/spring-context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.java
+++ b/spring-context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.java
@@ -27,6 +27,7 @@ import org.springframework.cache.interceptor.CacheEvictOperation;
import org.springframework.cache.interceptor.CacheOperation;
import org.springframework.cache.interceptor.CachePutOperation;
import org.springframework.cache.interceptor.CacheableOperation;
+import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
@@ -61,29 +62,29 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria
protected Collection<CacheOperation> parseCacheAnnotations(DefaultCacheConfig cachingConfig, AnnotatedElement ae) {
Collection<CacheOperation> ops = null;
- Collection<Cacheable> cacheables = getAnnotations(ae, Cacheable.class);
- if (cacheables != null) {
+ Collection<Cacheable> cacheables = AnnotatedElementUtils.findAllMergedAnnotations(ae, Cacheable.class);
+ if (!cacheables.isEmpty()) {
ops = lazyInit(ops);
for (Cacheable cacheable : cacheables) {
ops.add(parseCacheableAnnotation(ae, cachingConfig, cacheable));
}
}
- Collection<CacheEvict> evicts = getAnnotations(ae, CacheEvict.class);
- if (evicts != null) {
+ Collection<CacheEvict> evicts = AnnotatedElementUtils.findAllMergedAnnotations(ae, CacheEvict.class);
+ if (!evicts.isEmpty()) {
ops = lazyInit(ops);
for (CacheEvict evict : evicts) {
ops.add(parseEvictAnnotation(ae, cachingConfig, evict));
}
}
- Collection<CachePut> puts = getAnnotations(ae, CachePut.class);
- if (puts != null) {
+ Collection<CachePut> puts = AnnotatedElementUtils.findAllMergedAnnotations(ae, CachePut.class);
+ if (!puts.isEmpty()) {
ops = lazyInit(ops);
for (CachePut put : puts) {
ops.add(parsePutAnnotation(ae, cachingConfig, put));
}
}
- Collection<Caching> cachings = getAnnotations(ae, Caching.class);
- if (cachings != null) {
+ Collection<Caching> cachings = AnnotatedElementUtils.findAllMergedAnnotations(ae, Caching.class);
+ if (!cachings.isEmpty()) {
ops = lazyInit(ops);
for (Caching caching : cachings) {
Collection<CacheOperation> cachingOps = parseCachingAnnotation(ae, cachingConfig, caching);
@@ -101,55 +102,59 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria
}
CacheableOperation parseCacheableAnnotation(AnnotatedElement ae, DefaultCacheConfig defaultConfig, Cacheable cacheable) {
- CacheableOperation op = new CacheableOperation();
-
- op.setCacheNames(cacheable.cacheNames());
- op.setCondition(cacheable.condition());
- op.setUnless(cacheable.unless());
- op.setKey(cacheable.key());
- op.setKeyGenerator(cacheable.keyGenerator());
- op.setCacheManager(cacheable.cacheManager());
- op.setCacheResolver(cacheable.cacheResolver());
- op.setName(ae.toString());
-
- defaultConfig.applyDefault(op);
+ CacheableOperation.Builder builder = new CacheableOperation.Builder();
+
+ builder.setName(ae.toString());
+ builder.setCacheNames(cacheable.cacheNames());
+ builder.setCondition(cacheable.condition());
+ builder.setUnless(cacheable.unless());
+ builder.setKey(cacheable.key());
+ builder.setKeyGenerator(cacheable.keyGenerator());
+ builder.setCacheManager(cacheable.cacheManager());
+ builder.setCacheResolver(cacheable.cacheResolver());
+ builder.setSync(cacheable.sync());
+
+ defaultConfig.applyDefault(builder);
+ CacheableOperation op = builder.build();
validateCacheOperation(ae, op);
return op;
}
CacheEvictOperation parseEvictAnnotation(AnnotatedElement ae, DefaultCacheConfig defaultConfig, CacheEvict cacheEvict) {
- CacheEvictOperation op = new CacheEvictOperation();
-
- op.setCacheNames(cacheEvict.cacheNames());
- op.setCondition(cacheEvict.condition());
- op.setKey(cacheEvict.key());
- op.setKeyGenerator(cacheEvict.keyGenerator());
- op.setCacheManager(cacheEvict.cacheManager());
- op.setCacheResolver(cacheEvict.cacheResolver());
- op.setCacheWide(cacheEvict.allEntries());
- op.setBeforeInvocation(cacheEvict.beforeInvocation());
- op.setName(ae.toString());
-
- defaultConfig.applyDefault(op);
+ CacheEvictOperation.Builder builder = new CacheEvictOperation.Builder();
+
+ builder.setName(ae.toString());
+ builder.setCacheNames(cacheEvict.cacheNames());
+ builder.setCondition(cacheEvict.condition());
+ builder.setKey(cacheEvict.key());
+ builder.setKeyGenerator(cacheEvict.keyGenerator());
+ builder.setCacheManager(cacheEvict.cacheManager());
+ builder.setCacheResolver(cacheEvict.cacheResolver());
+ builder.setCacheWide(cacheEvict.allEntries());
+ builder.setBeforeInvocation(cacheEvict.beforeInvocation());
+
+ defaultConfig.applyDefault(builder);
+ CacheEvictOperation op = builder.build();
validateCacheOperation(ae, op);
return op;
}
CacheOperation parsePutAnnotation(AnnotatedElement ae, DefaultCacheConfig defaultConfig, CachePut cachePut) {
- CachePutOperation op = new CachePutOperation();
-
- op.setCacheNames(cachePut.cacheNames());
- op.setCondition(cachePut.condition());
- op.setUnless(cachePut.unless());
- op.setKey(cachePut.key());
- op.setKeyGenerator(cachePut.keyGenerator());
- op.setCacheManager(cachePut.cacheManager());
- op.setCacheResolver(cachePut.cacheResolver());
- op.setName(ae.toString());
-
- defaultConfig.applyDefault(op);
+ CachePutOperation.Builder builder = new CachePutOperation.Builder();
+
+ builder.setName(ae.toString());
+ builder.setCacheNames(cachePut.cacheNames());
+ builder.setCondition(cachePut.condition());
+ builder.setUnless(cachePut.unless());
+ builder.setKey(cachePut.key());
+ builder.setKeyGenerator(cachePut.keyGenerator());
+ builder.setCacheManager(cachePut.cacheManager());
+ builder.setCacheResolver(cachePut.cacheResolver());
+
+ defaultConfig.applyDefault(builder);
+ CachePutOperation op = builder.build();
validateCacheOperation(ae, op);
return op;
@@ -197,26 +202,6 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria
return new DefaultCacheConfig();
}
- private <A extends Annotation> Collection<A> getAnnotations(AnnotatedElement ae, Class<A> annotationType) {
- Collection<A> anns = new ArrayList<A>(1);
-
- // look at raw annotation
- A ann = ae.getAnnotation(annotationType);
- if (ann != null) {
- anns.add(AnnotationUtils.synthesizeAnnotation(ann, ae));
- }
-
- // scan meta-annotations
- for (Annotation metaAnn : ae.getAnnotations()) {
- ann = metaAnn.annotationType().getAnnotation(annotationType);
- if (ann != null) {
- anns.add(AnnotationUtils.synthesizeAnnotation(ann, ae));
- }
- }
-
- return (!anns.isEmpty() ? anns : null);
- }
-
/**
* Validates the specified {@link CacheOperation}.
* <p>Throws an {@link IllegalStateException} if the state of the operation is
@@ -277,26 +262,26 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria
}
/**
- * Apply the defaults to the specified {@link CacheOperation}.
- * @param operation the operation to update
+ * Apply the defaults to the specified {@link CacheOperation.Builder}.
+ * @param builder the operation builder to update
*/
- public void applyDefault(CacheOperation operation) {
- if (operation.getCacheNames().isEmpty() && this.cacheNames != null) {
- operation.setCacheNames(this.cacheNames);
+ public void applyDefault(CacheOperation.Builder builder) {
+ if (builder.getCacheNames().isEmpty() && this.cacheNames != null) {
+ builder.setCacheNames(this.cacheNames);
}
- if (!StringUtils.hasText(operation.getKey()) && !StringUtils.hasText(operation.getKeyGenerator()) &&
+ if (!StringUtils.hasText(builder.getKey()) && !StringUtils.hasText(builder.getKeyGenerator()) &&
StringUtils.hasText(this.keyGenerator)) {
- operation.setKeyGenerator(this.keyGenerator);
+ builder.setKeyGenerator(this.keyGenerator);
}
- if (StringUtils.hasText(operation.getCacheManager()) || StringUtils.hasText(operation.getCacheResolver())) {
+ if (StringUtils.hasText(builder.getCacheManager()) || StringUtils.hasText(builder.getCacheResolver())) {
// One of these is set so we should not inherit anything
}
else if (StringUtils.hasText(this.cacheResolver)) {
- operation.setCacheResolver(this.cacheResolver);
+ builder.setCacheResolver(this.cacheResolver);
}
else if (StringUtils.hasText(this.cacheManager)) {
- operation.setCacheManager(this.cacheManager);
+ builder.setCacheManager(this.cacheManager);
}
}
diff --git a/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCache.java b/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCache.java
index 4942e14c..df41bf5f 100644
--- a/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCache.java
+++ b/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCache.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,10 +16,15 @@
package org.springframework.cache.concurrent;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.springframework.cache.support.AbstractValueAdaptingCache;
+import org.springframework.core.serializer.support.SerializationDelegate;
import org.springframework.util.Assert;
/**
@@ -37,6 +42,7 @@ import org.springframework.util.Assert;
*
* @author Costin Leau
* @author Juergen Hoeller
+ * @author Stephane Nicoll
* @since 3.1
*/
public class ConcurrentMapCache extends AbstractValueAdaptingCache {
@@ -45,6 +51,8 @@ public class ConcurrentMapCache extends AbstractValueAdaptingCache {
private final ConcurrentMap<Object, Object> store;
+ private final SerializationDelegate serialization;
+
/**
* Create a new ConcurrentMapCache with the specified name.
@@ -73,14 +81,44 @@ public class ConcurrentMapCache extends AbstractValueAdaptingCache {
* (adapting them to an internal null holder value)
*/
public ConcurrentMapCache(String name, ConcurrentMap<Object, Object> store, boolean allowNullValues) {
+ this(name, store, allowNullValues, null);
+ }
+
+ /**
+ * Create a new ConcurrentMapCache with the specified name and the
+ * given internal {@link ConcurrentMap} to use. If the
+ * {@link SerializationDelegate} is specified,
+ * {@link #isStoreByValue() store-by-value} is enabled
+ * @param name the name of the cache
+ * @param store the ConcurrentMap to use as an internal store
+ * @param allowNullValues whether to allow {@code null} values
+ * (adapting them to an internal null holder value)
+ * @param serialization the {@link SerializationDelegate} to use
+ * to serialize cache entry or {@code null} to store the reference
+ * @since 4.3
+ */
+ protected ConcurrentMapCache(String name, ConcurrentMap<Object, Object> store,
+ boolean allowNullValues, SerializationDelegate serialization) {
+
super(allowNullValues);
Assert.notNull(name, "Name must not be null");
Assert.notNull(store, "Store must not be null");
this.name = name;
this.store = store;
+ this.serialization = serialization;
}
+ /**
+ * Return whether this cache stores a copy of each entry ({@code true}) or
+ * a reference ({@code false}, default). If store by value is enabled, each
+ * entry in the cache must be serializable.
+ * @since 4.3
+ */
+ public final boolean isStoreByValue() {
+ return (this.serialization != null);
+ }
+
@Override
public final String getName() {
return this.name;
@@ -96,6 +134,30 @@ public class ConcurrentMapCache extends AbstractValueAdaptingCache {
return this.store.get(key);
}
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T> T get(Object key, Callable<T> valueLoader) {
+ if (this.store.containsKey(key)) {
+ return (T) get(key).get();
+ }
+ else {
+ synchronized (this.store) {
+ if (this.store.containsKey(key)) {
+ return (T) get(key).get();
+ }
+ T value;
+ try {
+ value = valueLoader.call();
+ }
+ catch (Exception ex) {
+ throw new ValueRetrievalException(key, valueLoader, ex);
+ }
+ put(key, value);
+ return value;
+ }
+ }
+ }
+
@Override
public void put(Object key, Object value) {
this.store.put(key, toStoreValue(value));
@@ -117,4 +179,59 @@ public class ConcurrentMapCache extends AbstractValueAdaptingCache {
this.store.clear();
}
+ @Override
+ protected Object toStoreValue(Object userValue) {
+ Object storeValue = super.toStoreValue(userValue);
+ if (this.serialization != null) {
+ try {
+ return serializeValue(storeValue);
+ }
+ catch (Exception ex) {
+ throw new IllegalArgumentException("Failed to serialize cache value '"
+ + userValue + "'. Does it implement Serializable?", ex);
+ }
+ }
+ else {
+ return storeValue;
+ }
+ }
+
+ private Object serializeValue(Object storeValue) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ try {
+ this.serialization.serialize(storeValue, out);
+ return out.toByteArray();
+ }
+ finally {
+ out.close();
+ }
+ }
+
+ @Override
+ protected Object fromStoreValue(Object storeValue) {
+ if (this.serialization != null) {
+ try {
+ return super.fromStoreValue(deserializeValue(storeValue));
+ }
+ catch (Exception ex) {
+ throw new IllegalArgumentException("Failed to deserialize cache value '" +
+ storeValue + "'", ex);
+ }
+ }
+ else {
+ return super.fromStoreValue(storeValue);
+ }
+
+ }
+
+ private Object deserializeValue(Object storeValue) throws IOException {
+ ByteArrayInputStream in = new ByteArrayInputStream((byte[]) storeValue);
+ try {
+ return this.serialization.deserialize(in);
+ }
+ finally {
+ in.close();
+ }
+ }
+
}
diff --git a/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCacheManager.java b/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCacheManager.java
index 14c6956b..cce957b3 100644
--- a/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCacheManager.java
+++ b/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCacheManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author 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,8 +23,10 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
+import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
+import org.springframework.core.serializer.support.SerializationDelegate;
/**
* {@link CacheManager} implementation that lazily builds {@link ConcurrentMapCache}
@@ -35,14 +37,16 @@ import org.springframework.cache.CacheManager;
* <p>Note: This is by no means a sophisticated CacheManager; it comes with no
* cache configuration options. However, it may be useful for testing or simple
* caching scenarios. For advanced local caching needs, consider
- * {@link org.springframework.cache.guava.GuavaCacheManager} or
- * {@link org.springframework.cache.ehcache.EhCacheCacheManager}.
+ * {@link org.springframework.cache.jcache.JCacheCacheManager},
+ * {@link org.springframework.cache.ehcache.EhCacheCacheManager},
+ * {@link org.springframework.cache.caffeine.CaffeineCacheManager} or
+ * {@link org.springframework.cache.guava.GuavaCacheManager}.
*
* @author Juergen Hoeller
* @since 3.1
* @see ConcurrentMapCache
*/
-public class ConcurrentMapCacheManager implements CacheManager {
+public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {
private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<String, Cache>(16);
@@ -50,6 +54,10 @@ public class ConcurrentMapCacheManager implements CacheManager {
private boolean allowNullValues = true;
+ private boolean storeByValue = false;
+
+ private SerializationDelegate serialization;
+
/**
* Construct a dynamic ConcurrentMapCacheManager,
@@ -98,9 +106,7 @@ public class ConcurrentMapCacheManager implements CacheManager {
if (allowNullValues != this.allowNullValues) {
this.allowNullValues = allowNullValues;
// Need to recreate all Cache instances with the new null-value configuration...
- for (Map.Entry<String, Cache> entry : this.cacheMap.entrySet()) {
- entry.setValue(createConcurrentMapCache(entry.getKey()));
- }
+ recreateCaches();
}
}
@@ -112,6 +118,42 @@ public class ConcurrentMapCacheManager implements CacheManager {
return this.allowNullValues;
}
+ /**
+ * Specify whether this cache manager stores a copy of each entry ({@code true}
+ * or the reference ({@code false} for all of its caches.
+ * <p>Default is "false" so that the value itself is stored and no serializable
+ * contract is required on cached values.
+ * <p>Note: A change of the store-by-value setting will reset all existing caches,
+ * if any, to reconfigure them with the new store-by-value requirement.
+ * @since 4.3
+ */
+ public void setStoreByValue(boolean storeByValue) {
+ if (storeByValue != this.storeByValue) {
+ this.storeByValue = storeByValue;
+ // Need to recreate all Cache instances with the new store-by-value configuration...
+ recreateCaches();
+ }
+ }
+
+ /**
+ * Return whether this cache manager stores a copy of each entry or
+ * a reference for all its caches. If store by value is enabled, any
+ * cache entry must be serializable.
+ * @since 4.3
+ */
+ public boolean isStoreByValue() {
+ return this.storeByValue;
+ }
+
+ @Override
+ public void setBeanClassLoader(ClassLoader classLoader) {
+ this.serialization = new SerializationDelegate(classLoader);
+ // Need to recreate all Cache instances with new ClassLoader in store-by-value mode...
+ if (isStoreByValue()) {
+ recreateCaches();
+ }
+ }
+
@Override
public Collection<String> getCacheNames() {
@@ -133,13 +175,22 @@ public class ConcurrentMapCacheManager implements CacheManager {
return cache;
}
+ private void recreateCaches() {
+ for (Map.Entry<String, Cache> entry : this.cacheMap.entrySet()) {
+ entry.setValue(createConcurrentMapCache(entry.getKey()));
+ }
+ }
+
/**
* Create a new ConcurrentMapCache instance for the specified cache name.
* @param name the name of the cache
* @return the ConcurrentMapCache (or a decorator thereof)
*/
protected Cache createConcurrentMapCache(String name) {
- return new ConcurrentMapCache(name, isAllowNullValues());
+ SerializationDelegate actualSerialization = (isStoreByValue() ? this.serialization : null);
+ return new ConcurrentMapCache(name, new ConcurrentHashMap<Object, Object>(256),
+ isAllowNullValues(), actualSerialization);
+
}
}
diff --git a/spring-context/src/main/java/org/springframework/cache/config/AnnotationDrivenCacheBeanDefinitionParser.java b/spring-context/src/main/java/org/springframework/cache/config/AnnotationDrivenCacheBeanDefinitionParser.java
index 4ea5ccad..d9f90dc2 100644
--- a/spring-context/src/main/java/org/springframework/cache/config/AnnotationDrivenCacheBeanDefinitionParser.java
+++ b/spring-context/src/main/java/org/springframework/cache/config/AnnotationDrivenCacheBeanDefinitionParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -60,11 +60,10 @@ class AnnotationDrivenCacheBeanDefinitionParser implements BeanDefinitionParser
private static final String JCACHE_ASPECT_CLASS_NAME =
"org.springframework.cache.aspectj.JCacheCacheAspect";
-
private static final boolean jsr107Present = ClassUtils.isPresent(
"javax.cache.Cache", AnnotationDrivenCacheBeanDefinitionParser.class.getClassLoader());
- private static final boolean jCacheImplPresent = ClassUtils.isPresent(
+ private static final boolean jcacheImplPresent = ClassUtils.isPresent(
"org.springframework.cache.jcache.interceptor.DefaultJCacheOperationSource",
AnnotationDrivenCacheBeanDefinitionParser.class.getClassLoader());
@@ -91,7 +90,7 @@ class AnnotationDrivenCacheBeanDefinitionParser implements BeanDefinitionParser
private void registerCacheAspect(Element element, ParserContext parserContext) {
SpringCachingConfigurer.registerCacheAspect(element, parserContext);
- if (jsr107Present && jCacheImplPresent) { // Register JCache aspect
+ if (jsr107Present && jcacheImplPresent) {
JCacheCachingConfigurer.registerCacheAspect(element, parserContext);
}
}
@@ -99,7 +98,7 @@ class AnnotationDrivenCacheBeanDefinitionParser implements BeanDefinitionParser
private void registerCacheAdvisor(Element element, ParserContext parserContext) {
AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);
SpringCachingConfigurer.registerCacheAdvisor(element, parserContext);
- if (jsr107Present && jCacheImplPresent) { // Register JCache advisor
+ if (jsr107Present && jcacheImplPresent) {
JCacheCachingConfigurer.registerCacheAdvisor(element, parserContext);
}
}
diff --git a/spring-context/src/main/java/org/springframework/cache/config/CacheAdviceParser.java b/spring-context/src/main/java/org/springframework/cache/config/CacheAdviceParser.java
index 07bcea27..f4a626a7 100644
--- a/spring-context/src/main/java/org/springframework/cache/config/CacheAdviceParser.java
+++ b/spring-context/src/main/java/org/springframework/cache/config/CacheAdviceParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -107,15 +107,17 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
String name = prop.merge(opElement, parserContext.getReaderContext());
TypedStringValue nameHolder = new TypedStringValue(name);
nameHolder.setSource(parserContext.extractSource(opElement));
- CacheableOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CacheableOperation());
- op.setUnless(getAttributeValue(opElement, "unless", ""));
+ CacheableOperation.Builder builder = prop.merge(opElement,
+ parserContext.getReaderContext(), new CacheableOperation.Builder());
+ builder.setUnless(getAttributeValue(opElement, "unless", ""));
+ builder.setSync(Boolean.valueOf(getAttributeValue(opElement, "sync", "false")));
Collection<CacheOperation> col = cacheOpMap.get(nameHolder);
if (col == null) {
col = new ArrayList<CacheOperation>(2);
cacheOpMap.put(nameHolder, col);
}
- col.add(op);
+ col.add(builder.build());
}
List<Element> evictCacheMethods = DomUtils.getChildElementsByTagName(definition, CACHE_EVICT_ELEMENT);
@@ -124,16 +126,17 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
String name = prop.merge(opElement, parserContext.getReaderContext());
TypedStringValue nameHolder = new TypedStringValue(name);
nameHolder.setSource(parserContext.extractSource(opElement));
- CacheEvictOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CacheEvictOperation());
+ CacheEvictOperation.Builder builder = prop.merge(opElement,
+ parserContext.getReaderContext(), new CacheEvictOperation.Builder());
String wide = opElement.getAttribute("all-entries");
if (StringUtils.hasText(wide)) {
- op.setCacheWide(Boolean.valueOf(wide.trim()));
+ builder.setCacheWide(Boolean.valueOf(wide.trim()));
}
String after = opElement.getAttribute("before-invocation");
if (StringUtils.hasText(after)) {
- op.setBeforeInvocation(Boolean.valueOf(after.trim()));
+ builder.setBeforeInvocation(Boolean.valueOf(after.trim()));
}
Collection<CacheOperation> col = cacheOpMap.get(nameHolder);
@@ -141,7 +144,7 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
col = new ArrayList<CacheOperation>(2);
cacheOpMap.put(nameHolder, col);
}
- col.add(op);
+ col.add(builder.build());
}
List<Element> putCacheMethods = DomUtils.getChildElementsByTagName(definition, CACHE_PUT_ELEMENT);
@@ -150,15 +153,16 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
String name = prop.merge(opElement, parserContext.getReaderContext());
TypedStringValue nameHolder = new TypedStringValue(name);
nameHolder.setSource(parserContext.extractSource(opElement));
- CachePutOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CachePutOperation());
- op.setUnless(getAttributeValue(opElement, "unless", ""));
+ CachePutOperation.Builder builder = prop.merge(opElement,
+ parserContext.getReaderContext(), new CachePutOperation.Builder());
+ builder.setUnless(getAttributeValue(opElement, "unless", ""));
Collection<CacheOperation> col = cacheOpMap.get(nameHolder);
if (col == null) {
col = new ArrayList<CacheOperation>(2);
cacheOpMap.put(nameHolder, col);
}
- col.add(op);
+ col.add(builder.build());
}
RootBeanDefinition attributeSourceDefinition = new RootBeanDefinition(NameMatchCacheOperationSource.class);
@@ -196,45 +200,45 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
Props(Element root) {
String defaultCache = root.getAttribute("cache");
- key = root.getAttribute("key");
- keyGenerator = root.getAttribute("key-generator");
- cacheManager = root.getAttribute("cache-manager");
- condition = root.getAttribute("condition");
- method = root.getAttribute(METHOD_ATTRIBUTE);
+ this.key = root.getAttribute("key");
+ this.keyGenerator = root.getAttribute("key-generator");
+ this.cacheManager = root.getAttribute("cache-manager");
+ this.condition = root.getAttribute("condition");
+ this.method = root.getAttribute(METHOD_ATTRIBUTE);
if (StringUtils.hasText(defaultCache)) {
- caches = StringUtils.commaDelimitedListToStringArray(defaultCache.trim());
+ this.caches = StringUtils.commaDelimitedListToStringArray(defaultCache.trim());
}
}
- <T extends CacheOperation> T merge(Element element, ReaderContext readerCtx, T op) {
+ <T extends CacheOperation.Builder> T merge(Element element, ReaderContext readerCtx, T builder) {
String cache = element.getAttribute("cache");
// sanity check
- String[] localCaches = caches;
+ String[] localCaches = this.caches;
if (StringUtils.hasText(cache)) {
localCaches = StringUtils.commaDelimitedListToStringArray(cache.trim());
}
else {
- if (caches == null) {
- readerCtx.error("No cache specified specified for " + element.getNodeName(), element);
+ if (this.caches == null) {
+ readerCtx.error("No cache specified for " + element.getNodeName(), element);
}
}
- op.setCacheNames(localCaches);
+ builder.setCacheNames(localCaches);
- op.setKey(getAttributeValue(element, "key", this.key));
- op.setKeyGenerator(getAttributeValue(element, "key-generator", this.keyGenerator));
- op.setCacheManager(getAttributeValue(element, "cache-manager", this.cacheManager));
- op.setCondition(getAttributeValue(element, "condition", this.condition));
+ builder.setKey(getAttributeValue(element, "key", this.key));
+ builder.setKeyGenerator(getAttributeValue(element, "key-generator", this.keyGenerator));
+ builder.setCacheManager(getAttributeValue(element, "cache-manager", this.cacheManager));
+ builder.setCondition(getAttributeValue(element, "condition", this.condition));
- if (StringUtils.hasText(op.getKey()) && StringUtils.hasText(op.getKeyGenerator())) {
+ if (StringUtils.hasText(builder.getKey()) && StringUtils.hasText(builder.getKeyGenerator())) {
throw new IllegalStateException("Invalid cache advice configuration on '"
+ element.toString() + "'. Both 'key' and 'keyGenerator' attributes have been set. " +
"These attributes are mutually exclusive: either set the SpEL expression used to" +
"compute the key at runtime or set the name of the KeyGenerator bean to use.");
}
- return op;
+ return builder;
}
String merge(Element element, ReaderContext readerCtx) {
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractFallbackCacheOperationSource.java b/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractFallbackCacheOperationSource.java
index d383b281..88d0aec5 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractFallbackCacheOperationSource.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractFallbackCacheOperationSource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,27 +26,24 @@ import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.core.BridgeMethodResolver;
+import org.springframework.core.MethodClassKey;
import org.springframework.util.ClassUtils;
/**
- * Abstract implementation of {@link CacheOperation} that caches
- * attributes for methods and implements a fallback policy: 1. specific
- * target method; 2. target class; 3. declaring method; 4. declaring
- * class/interface.
+ * Abstract implementation of {@link CacheOperation} that caches attributes
+ * for methods and implements a fallback policy: 1. specific target method;
+ * 2. target class; 3. declaring method; 4. declaring class/interface.
*
* <p>Defaults to using the target class's caching attribute if none is
- * associated with the target method. Any caching attribute associated
- * with the target method completely overrides a class caching attribute.
- * If none found on the target class, the interface that the invoked
- * method has been called through (in case of a JDK proxy) will be
- * checked.
+ * associated with the target method. Any caching attribute associated with
+ * the target method completely overrides a class caching attribute.
+ * If none found on the target class, the interface that the invoked method
+ * has been called through (in case of a JDK proxy) will be checked.
*
- * <p>This implementation caches attributes by method after they are
- * first used. If it is ever desirable to allow dynamic changing of
- * cacheable attributes (which is very unlikely), caching could be made
- * configurable.
+ * <p>This implementation caches attributes by method after they are first
+ * used. If it is ever desirable to allow dynamic changing of cacheable
+ * attributes (which is very unlikely), caching could be made configurable.
*
* @author Costin Leau
* @author Juergen Hoeller
@@ -69,7 +66,7 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
protected final Log logger = LogFactory.getLog(getClass());
/**
- * Cache of CacheOperations, keyed by {@link AnnotatedElementKey}.
+ * Cache of CacheOperations, keyed by method on a specific target class.
* <p>As this base class is not marked Serializable, the cache will be recreated
* after serialization - provided that the concrete subclass is Serializable.
*/
@@ -117,7 +114,7 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
* @return the cache key (never {@code null})
*/
protected Object getCacheKey(Method method, Class<?> targetClass) {
- return new AnnotatedElementKey(method, targetClass);
+ return new MethodClassKey(method, targetClass);
}
private Collection<CacheOperation> computeCacheOperations(Method method, Class<?> targetClass) {
@@ -140,19 +137,23 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
// Second try is the caching operation on the target class.
opDef = findCacheOperations(specificMethod.getDeclaringClass());
- if (opDef != null) {
+ if (opDef != null && ClassUtils.isUserLevelMethod(method)) {
return opDef;
}
if (specificMethod != method) {
- // Fall back is to look at the original method.
+ // Fallback is to look at the original method.
opDef = findCacheOperations(method);
if (opDef != null) {
return opDef;
}
- // Last fall back is the class of the original method.
- return findCacheOperations(method.getDeclaringClass());
+ // Last fallback is the class of the original method.
+ opDef = findCacheOperations(method.getDeclaringClass());
+ if (opDef != null && ClassUtils.isUserLevelMethod(method)) {
+ return opDef;
+ }
}
+
return null;
}
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java
index 6dd21c1d..2de8bd8f 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,12 +23,16 @@ import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.AopProxyUtils;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
@@ -36,11 +40,10 @@ import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
-import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.context.ApplicationContext;
-import org.springframework.context.ApplicationContextAware;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.expression.EvaluationContext;
+import org.springframework.lang.UsesJava8;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
@@ -76,17 +79,26 @@ import org.springframework.util.StringUtils;
* @since 3.1
*/
public abstract class CacheAspectSupport extends AbstractCacheInvoker
- implements InitializingBean, SmartInitializingSingleton, ApplicationContextAware {
+ implements BeanFactoryAware, InitializingBean, SmartInitializingSingleton {
+
+ private static Class<?> javaUtilOptionalClass = null;
+
+ static {
+ try {
+ javaUtilOptionalClass =
+ ClassUtils.forName("java.util.Optional", CacheAspectSupport.class.getClassLoader());
+ }
+ catch (ClassNotFoundException ex) {
+ // Java 8 not available - Optional references simply not supported then.
+ }
+ }
protected final Log logger = LogFactory.getLog(getClass());
- /**
- * Cache of CacheOperationMetadata, keyed by {@link CacheOperationCacheKey}.
- */
private final Map<CacheOperationCacheKey, CacheOperationMetadata> metadataCache =
new ConcurrentHashMap<CacheOperationCacheKey, CacheOperationMetadata>(1024);
- private final ExpressionEvaluator evaluator = new ExpressionEvaluator();
+ private final CacheOperationExpressionEvaluator evaluator = new CacheOperationExpressionEvaluator();
private CacheOperationSource cacheOperationSource;
@@ -94,7 +106,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
private CacheResolver cacheResolver;
- private ApplicationContext applicationContext;
+ private BeanFactory beanFactory;
private boolean initialized = false;
@@ -163,12 +175,26 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
return this.cacheResolver;
}
+ /**
+ * Set the containing {@link BeanFactory} for {@link CacheManager} and other
+ * service lookups.
+ * @since 4.3
+ */
@Override
+ public void setBeanFactory(BeanFactory beanFactory) {
+ this.beanFactory = beanFactory;
+ }
+
+ /**
+ * @deprecated as of 4.3, in favor of {@link #setBeanFactory}
+ */
+ @Deprecated
public void setApplicationContext(ApplicationContext applicationContext) {
- this.applicationContext = applicationContext;
+ this.beanFactory = applicationContext;
}
+ @Override
public void afterPropertiesSet() {
Assert.state(getCacheOperationSource() != null, "The 'cacheOperationSources' property is required: " +
"If there are no cacheable methods, then don't use a cache aspect.");
@@ -180,7 +206,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
if (getCacheResolver() == null) {
// Lazily initialize cache resolver via default cache manager...
try {
- setCacheManager(this.applicationContext.getBean(CacheManager.class));
+ setCacheManager(this.beanFactory.getBean(CacheManager.class));
}
catch (NoUniqueBeanDefinitionException ex) {
throw new IllegalStateException("No CacheResolver specified, and no unique bean of type " +
@@ -273,7 +299,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
* Return a bean with the specified name and type. Used to resolve services that
* are referenced by name in a {@link CacheOperation}.
* @param beanName the name of the bean, as defined by the operation
- * @param expectedType type type for the bean
+ * @param expectedType type for the bean
* @return the bean matching that name
* @throws org.springframework.beans.factory.NoSuchBeanDefinitionException if such bean does not exist
* @see CacheOperation#keyGenerator
@@ -281,7 +307,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
* @see CacheOperation#cacheResolver
*/
protected <T> T getBean(String beanName, Class<T> expectedType) {
- return BeanFactoryAnnotationUtils.qualifiedBeanOfType(this.applicationContext, expectedType, beanName);
+ return BeanFactoryAnnotationUtils.qualifiedBeanOfType(this.beanFactory, expectedType, beanName);
}
/**
@@ -293,13 +319,12 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
}
protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
- // check whether aspect is enabled
- // to cope with cases where the AJ is pulled in automatically
+ // Check whether aspect is enabled (to cope with cases where the AJ is pulled in automatically)
if (this.initialized) {
Class<?> targetClass = getTargetClass(target);
Collection<CacheOperation> operations = getCacheOperationSource().getCacheOperations(method, targetClass);
if (!CollectionUtils.isEmpty(operations)) {
- return execute(invoker, new CacheOperationContexts(operations, method, args, target, targetClass));
+ return execute(invoker, method, new CacheOperationContexts(operations, method, args, target, targetClass));
}
}
@@ -328,9 +353,37 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
return targetClass;
}
- private Object execute(CacheOperationInvoker invoker, CacheOperationContexts contexts) {
+ private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
+ // Special handling of synchronized invocation
+ if (contexts.isSynchronized()) {
+ CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next();
+ if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
+ Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
+ Cache cache = context.getCaches().iterator().next();
+ try {
+ return cache.get(key, new Callable<Object>() {
+ @Override
+ public Object call() throws Exception {
+ return invokeOperation(invoker);
+ }
+ });
+ }
+ catch (Cache.ValueRetrievalException ex) {
+ // The invoker wraps any Throwable in a ThrowableWrapper instance so we
+ // can just make sure that one bubbles up the stack.
+ throw (CacheOperationInvoker.ThrowableWrapper) ex.getCause();
+ }
+ }
+ else {
+ // No caching required, only call the underlying method
+ return invokeOperation(invoker);
+ }
+ }
+
+
// Process any early evictions
- processCacheEvicts(contexts.get(CacheEvictOperation.class), true, ExpressionEvaluator.NO_RESULT);
+ processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
+ CacheOperationExpressionEvaluator.NO_RESULT);
// Check if we have a cached item matching the conditions
Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));
@@ -338,42 +391,56 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
// Collect puts from any @Cacheable miss, if no cached item is found
List<CachePutRequest> cachePutRequests = new LinkedList<CachePutRequest>();
if (cacheHit == null) {
- collectPutRequests(contexts.get(CacheableOperation.class), ExpressionEvaluator.NO_RESULT, cachePutRequests);
+ collectPutRequests(contexts.get(CacheableOperation.class),
+ CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
}
- Cache.ValueWrapper result = null;
+ Object cacheValue;
+ Object returnValue;
- // If there are no put requests, just use the cache hit
- if (cachePutRequests.isEmpty() && !hasCachePut(contexts)) {
- result = cacheHit;
+ if (cacheHit != null && cachePutRequests.isEmpty() && !hasCachePut(contexts)) {
+ // If there are no put requests, just use the cache hit
+ cacheValue = cacheHit.get();
+ if (method.getReturnType() == javaUtilOptionalClass &&
+ (cacheValue == null || cacheValue.getClass() != javaUtilOptionalClass)) {
+ returnValue = OptionalUnwrapper.wrap(cacheValue);
+ }
+ else {
+ returnValue = cacheValue;
+ }
}
-
- // Invoke the method if don't have a cache hit
- if (result == null) {
- result = new SimpleValueWrapper(invokeOperation(invoker));
+ else {
+ // Invoke the method if we don't have a cache hit
+ returnValue = invokeOperation(invoker);
+ if (returnValue != null && returnValue.getClass() == javaUtilOptionalClass) {
+ cacheValue = OptionalUnwrapper.unwrap(returnValue);
+ }
+ else {
+ cacheValue = returnValue;
+ }
}
// Collect any explicit @CachePuts
- collectPutRequests(contexts.get(CachePutOperation.class), result.get(), cachePutRequests);
+ collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
// Process any collected put requests, either from @CachePut or a @Cacheable miss
for (CachePutRequest cachePutRequest : cachePutRequests) {
- cachePutRequest.apply(result.get());
+ cachePutRequest.apply(cacheValue);
}
// Process any late evictions
- processCacheEvicts(contexts.get(CacheEvictOperation.class), false, result.get());
+ processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
- return result.get();
+ return returnValue;
}
private boolean hasCachePut(CacheOperationContexts contexts) {
- // Evaluate the conditions *without* the result object because we don't have it yet.
+ // Evaluate the conditions *without* the result object because we don't have it yet...
Collection<CacheOperationContext> cachePutContexts = contexts.get(CachePutOperation.class);
Collection<CacheOperationContext> excluded = new ArrayList<CacheOperationContext>();
for (CacheOperationContext context : cachePutContexts) {
try {
- if (!context.isConditionPassing(ExpressionEvaluator.RESULT_UNAVAILABLE)) {
+ if (!context.isConditionPassing(CacheOperationExpressionEvaluator.RESULT_UNAVAILABLE)) {
excluded.add(context);
}
}
@@ -425,7 +492,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
* or {@code null} if none is found
*/
private Cache.ValueWrapper findCachedItem(Collection<CacheOperationContext> contexts) {
- Object result = ExpressionEvaluator.NO_RESULT;
+ Object result = CacheOperationExpressionEvaluator.NO_RESULT;
for (CacheOperationContext context : contexts) {
if (isConditionPassing(context, result)) {
Object key = generateKey(context, result);
@@ -501,18 +568,57 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
private final MultiValueMap<Class<? extends CacheOperation>, CacheOperationContext> contexts =
new LinkedMultiValueMap<Class<? extends CacheOperation>, CacheOperationContext>();
+ private final boolean sync;
+
public CacheOperationContexts(Collection<? extends CacheOperation> operations, Method method,
Object[] args, Object target, Class<?> targetClass) {
for (CacheOperation operation : operations) {
this.contexts.add(operation.getClass(), getOperationContext(operation, method, args, target, targetClass));
}
+ this.sync = determineSyncFlag(method);
}
public Collection<CacheOperationContext> get(Class<? extends CacheOperation> operationClass) {
Collection<CacheOperationContext> result = this.contexts.get(operationClass);
return (result != null ? result : Collections.<CacheOperationContext>emptyList());
}
+
+ public boolean isSynchronized() {
+ return this.sync;
+ }
+
+ private boolean determineSyncFlag(Method method) {
+ List<CacheOperationContext> cacheOperationContexts = this.contexts.get(CacheableOperation.class);
+ if (cacheOperationContexts == null) { // no @Cacheable operation at all
+ return false;
+ }
+ boolean syncEnabled = false;
+ for (CacheOperationContext cacheOperationContext : cacheOperationContexts) {
+ if (((CacheableOperation) cacheOperationContext.getOperation()).isSync()) {
+ syncEnabled = true;
+ break;
+ }
+ }
+ if (syncEnabled) {
+ if (this.contexts.size() > 1) {
+ throw new IllegalStateException("@Cacheable(sync=true) cannot be combined with other cache operations on '" + method + "'");
+ }
+ if (cacheOperationContexts.size() > 1) {
+ throw new IllegalStateException("Only one @Cacheable(sync=true) entry is allowed on '" + method + "'");
+ }
+ CacheOperationContext cacheOperationContext = cacheOperationContexts.iterator().next();
+ CacheableOperation operation = (CacheableOperation) cacheOperationContext.getOperation();
+ if (cacheOperationContext.getCaches().size() > 1) {
+ throw new IllegalStateException("@Cacheable(sync=true) only allows a single cache on '" + operation + "'");
+ }
+ if (StringUtils.hasText(operation.getUnless())) {
+ throw new IllegalStateException("@Cacheable(sync=true) does not support unless attribute on '" + operation + "'");
+ }
+ return true;
+ }
+ return false;
+ }
}
@@ -635,8 +741,8 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
}
private EvaluationContext createEvaluationContext(Object result) {
- return evaluator.createEvaluationContext(
- this.caches, this.metadata.method, this.args, this.target, this.metadata.targetClass, result);
+ return evaluator.createEvaluationContext(this.caches, this.metadata.method, this.args,
+ this.target, this.metadata.targetClass, result, beanFactory);
}
protected Collection<? extends Cache> getCaches() {
@@ -678,7 +784,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
}
- private static class CacheOperationCacheKey {
+ private static final class CacheOperationCacheKey implements Comparable<CacheOperationCacheKey> {
private final CacheOperation cacheOperation;
@@ -706,6 +812,42 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
public int hashCode() {
return (this.cacheOperation.hashCode() * 31 + this.methodCacheKey.hashCode());
}
+
+ @Override
+ public String toString() {
+ return this.cacheOperation + " on " + this.methodCacheKey;
+ }
+
+ @Override
+ public int compareTo(CacheOperationCacheKey other) {
+ int result = this.cacheOperation.getName().compareTo(other.cacheOperation.getName());
+ if (result == 0) {
+ result = this.methodCacheKey.compareTo(other.methodCacheKey);
+ }
+ return result;
+ }
+ }
+
+
+ /**
+ * Inner class to avoid a hard dependency on Java 8.
+ */
+ @UsesJava8
+ private static class OptionalUnwrapper {
+
+ public static Object unwrap(Object optionalObject) {
+ Optional<?> optional = (Optional<?>) optionalObject;
+ if (!optional.isPresent()) {
+ return null;
+ }
+ Object result = optional.get();
+ Assert.isTrue(!(result instanceof Optional), "Multi-level Optional usage not supported");
+ return result;
+ }
+
+ public static Object wrap(Object value) {
+ return Optional.ofNullable(value);
+ }
}
}
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheEvictOperation.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheEvictOperation.java
index 6bb937a5..5f5e0dc0 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheEvictOperation.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheEvictOperation.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,38 +20,65 @@ package org.springframework.cache.interceptor;
* Class describing a cache 'evict' operation.
*
* @author Costin Leau
+ * @author Marcin Kamionowski
* @since 3.1
*/
public class CacheEvictOperation extends CacheOperation {
- private boolean cacheWide = false;
+ private final boolean cacheWide;
- private boolean beforeInvocation = false;
+ private final boolean beforeInvocation;
- public void setCacheWide(boolean cacheWide) {
- this.cacheWide = cacheWide;
+ /**
+ * @since 4.3
+ */
+ public CacheEvictOperation(CacheEvictOperation.Builder b) {
+ super(b);
+ this.cacheWide = b.cacheWide;
+ this.beforeInvocation = b.beforeInvocation;
}
+
public boolean isCacheWide() {
return this.cacheWide;
}
- public void setBeforeInvocation(boolean beforeInvocation) {
- this.beforeInvocation = beforeInvocation;
- }
-
public boolean isBeforeInvocation() {
return this.beforeInvocation;
}
- @Override
- protected StringBuilder getOperationDescription() {
- StringBuilder sb = super.getOperationDescription();
- sb.append(",");
- sb.append(this.cacheWide);
- sb.append(",");
- sb.append(this.beforeInvocation);
- return sb;
+
+ /**
+ * @since 4.3
+ */
+ public static class Builder extends CacheOperation.Builder {
+
+ private boolean cacheWide = false;
+
+ private boolean beforeInvocation = false;
+
+ public void setCacheWide(boolean cacheWide) {
+ this.cacheWide = cacheWide;
+ }
+
+ public void setBeforeInvocation(boolean beforeInvocation) {
+ this.beforeInvocation = beforeInvocation;
+ }
+
+ @Override
+ protected StringBuilder getOperationDescription() {
+ StringBuilder sb = super.getOperationDescription();
+ sb.append(",");
+ sb.append(this.cacheWide);
+ sb.append(",");
+ sb.append(this.beforeInvocation);
+ return sb;
+ }
+
+ public CacheEvictOperation build() {
+ return new CacheEvictOperation(this);
+ }
}
+
}
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperation.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperation.java
index 6264a040..271b59f1 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperation.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperation.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,45 +27,45 @@ import org.springframework.util.Assert;
*
* @author Costin Leau
* @author Stephane Nicoll
+ * @author Marcin Kamionowski
* @since 3.1
*/
public abstract class CacheOperation implements BasicOperation {
- private String name = "";
+ private final String name;
- private Set<String> cacheNames = Collections.emptySet();
+ private final Set<String> cacheNames;
- private String key = "";
+ private final String key;
- private String keyGenerator = "";
+ private final String keyGenerator;
- private String cacheManager = "";
+ private final String cacheManager;
- private String cacheResolver = "";
+ private final String cacheResolver;
- private String condition = "";
+ private final String condition;
+ private final String toString;
- public void setName(String name) {
- Assert.hasText(name);
- this.name = name;
- }
- public String getName() {
- return this.name;
+ /**
+ * @since 4.3
+ */
+ protected CacheOperation(Builder b) {
+ this.name = b.name;
+ this.cacheNames = b.cacheNames;
+ this.key = b.key;
+ this.keyGenerator = b.keyGenerator;
+ this.cacheManager = b.cacheManager;
+ this.cacheResolver = b.cacheResolver;
+ this.condition = b.condition;
+ this.toString = b.getOperationDescription().toString();
}
- public void setCacheName(String cacheName) {
- Assert.hasText(cacheName);
- this.cacheNames = Collections.singleton(cacheName);
- }
- public void setCacheNames(String... cacheNames) {
- this.cacheNames = new LinkedHashSet<String>(cacheNames.length);
- for (String cacheName : cacheNames) {
- Assert.hasText(cacheName, "Cache name must be non-null if specified");
- this.cacheNames.add(cacheName);
- }
+ public String getName() {
+ return this.name;
}
@Override
@@ -73,47 +73,22 @@ public abstract class CacheOperation implements BasicOperation {
return this.cacheNames;
}
- public void setKey(String key) {
- Assert.notNull(key);
- this.key = key;
- }
-
public String getKey() {
return this.key;
}
- public void setKeyGenerator(String keyGenerator) {
- Assert.notNull(keyGenerator);
- this.keyGenerator = keyGenerator;
- }
-
public String getKeyGenerator() {
return this.keyGenerator;
}
- public void setCacheManager(String cacheManager) {
- Assert.notNull(cacheManager);
- this.cacheManager = cacheManager;
- }
-
public String getCacheManager() {
return this.cacheManager;
}
- public void setCacheResolver(String cacheResolver) {
- Assert.notNull(cacheManager);
- this.cacheResolver = cacheResolver;
- }
-
public String getCacheResolver() {
return this.cacheResolver;
}
- public void setCondition(String condition) {
- Assert.notNull(condition);
- this.condition = condition;
- }
-
public String getCondition() {
return this.condition;
}
@@ -139,29 +114,116 @@ public abstract class CacheOperation implements BasicOperation {
/**
* Return an identifying description for this cache operation.
- * <p>Has to be overridden in subclasses for correct {@code equals}
- * and {@code hashCode} behavior. Alternatively, {@link #equals}
- * and {@link #hashCode} can be overridden themselves.
+ * <p>Returned value is produced by calling {@link Builder#getOperationDescription()}
+ * during object construction. This method is used in {@link #hashCode} and
+ * {@link #equals}.
+ * @see Builder#getOperationDescription()
*/
@Override
- public String toString() {
- return getOperationDescription().toString();
+ public final String toString() {
+ return this.toString;
}
+
/**
- * Return an identifying description for this caching operation.
- * <p>Available to subclasses, for inclusion in their {@code toString()} result.
+ * @since 4.3
*/
- protected StringBuilder getOperationDescription() {
- StringBuilder result = new StringBuilder(getClass().getSimpleName());
- result.append("[").append(this.name);
- result.append("] caches=").append(this.cacheNames);
- result.append(" | key='").append(this.key);
- result.append("' | keyGenerator='").append(this.keyGenerator);
- result.append("' | cacheManager='").append(this.cacheManager);
- result.append("' | cacheResolver='").append(this.cacheResolver);
- result.append("' | condition='").append(this.condition).append("'");
- return result;
+ public abstract static class Builder {
+
+ private String name = "";
+
+ private Set<String> cacheNames = Collections.emptySet();
+
+ private String key = "";
+
+ private String keyGenerator = "";
+
+ private String cacheManager = "";
+
+ private String cacheResolver = "";
+
+ private String condition = "";
+
+ public void setName(String name) {
+ Assert.hasText(name);
+ this.name = name;
+ }
+
+ public void setCacheName(String cacheName) {
+ Assert.hasText(cacheName);
+ this.cacheNames = Collections.singleton(cacheName);
+ }
+
+ public void setCacheNames(String... cacheNames) {
+ this.cacheNames = new LinkedHashSet<String>(cacheNames.length);
+ for (String cacheName : cacheNames) {
+ Assert.hasText(cacheName, "Cache name must be non-null if specified");
+ this.cacheNames.add(cacheName);
+ }
+ }
+
+ public Set<String> getCacheNames() {
+ return this.cacheNames;
+ }
+
+ public void setKey(String key) {
+ Assert.notNull(key);
+ this.key = key;
+ }
+
+ public String getKey() {
+ return this.key;
+ }
+
+ public String getKeyGenerator() {
+ return this.keyGenerator;
+ }
+
+ public String getCacheManager() {
+ return this.cacheManager;
+ }
+
+ public String getCacheResolver() {
+ return this.cacheResolver;
+ }
+
+ public void setKeyGenerator(String keyGenerator) {
+ Assert.notNull(keyGenerator);
+ this.keyGenerator = keyGenerator;
+ }
+
+ public void setCacheManager(String cacheManager) {
+ Assert.notNull(cacheManager);
+ this.cacheManager = cacheManager;
+ }
+
+ public void setCacheResolver(String cacheResolver) {
+ Assert.notNull(this.cacheManager);
+ this.cacheResolver = cacheResolver;
+ }
+
+ public void setCondition(String condition) {
+ Assert.notNull(condition);
+ this.condition = condition;
+ }
+
+ /**
+ * Return an identifying description for this caching operation.
+ * <p>Available to subclasses, for inclusion in their {@code toString()} result.
+ */
+ protected StringBuilder getOperationDescription() {
+ StringBuilder result = new StringBuilder(getClass().getSimpleName());
+ result.append("[").append(this.name);
+ result.append("] caches=").append(this.cacheNames);
+ result.append(" | key='").append(this.key);
+ result.append("' | keyGenerator='").append(this.keyGenerator);
+ result.append("' | cacheManager='").append(this.cacheManager);
+ result.append("' | cacheResolver='").append(this.cacheResolver);
+ result.append("' | condition='").append(this.condition).append("'");
+ return result;
+ }
+
+ public abstract CacheOperation build();
}
}
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/ExpressionEvaluator.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationExpressionEvaluator.java
index 88ed1484..9111bab0 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/ExpressionEvaluator.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationExpressionEvaluator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,11 +22,11 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.aop.support.AopUtils;
+import org.springframework.beans.factory.BeanFactory;
import org.springframework.cache.Cache;
import org.springframework.context.expression.AnnotatedElementKey;
+import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.context.expression.CachedExpressionEvaluator;
-import org.springframework.core.DefaultParameterNameDiscoverer;
-import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
@@ -43,7 +43,7 @@ import org.springframework.expression.Expression;
* @author Stephane Nicoll
* @since 3.1
*/
-class ExpressionEvaluator extends CachedExpressionEvaluator {
+class CacheOperationExpressionEvaluator extends CachedExpressionEvaluator {
/**
* Indicate that there is no result variable.
@@ -60,8 +60,6 @@ class ExpressionEvaluator extends CachedExpressionEvaluator {
*/
public static final String RESULT_VARIABLE = "result";
- // shared param discoverer since it caches data internally
- private final ParameterNameDiscoverer paramNameDiscoverer = new DefaultParameterNameDiscoverer();
private final Map<ExpressionKey, Expression> keyCache = new ConcurrentHashMap<ExpressionKey, Expression>(64);
@@ -75,12 +73,12 @@ class ExpressionEvaluator extends CachedExpressionEvaluator {
/**
* Create an {@link EvaluationContext} without a return value.
- * @see #createEvaluationContext(Collection, Method, Object[], Object, Class, Object)
+ * @see #createEvaluationContext(Collection, Method, Object[], Object, Class, Object, BeanFactory)
*/
public EvaluationContext createEvaluationContext(Collection<? extends Cache> caches,
- Method method, Object[] args, Object target, Class<?> targetClass) {
+ Method method, Object[] args, Object target, Class<?> targetClass, BeanFactory beanFactory) {
- return createEvaluationContext(caches, method, args, target, targetClass, NO_RESULT);
+ return createEvaluationContext(caches, method, args, target, targetClass, NO_RESULT, beanFactory);
}
/**
@@ -95,19 +93,23 @@ class ExpressionEvaluator extends CachedExpressionEvaluator {
* @return the evaluation context
*/
public EvaluationContext createEvaluationContext(Collection<? extends Cache> caches,
- Method method, Object[] args, Object target, Class<?> targetClass, Object result) {
+ Method method, Object[] args, Object target, Class<?> targetClass, Object result,
+ BeanFactory beanFactory) {
- CacheExpressionRootObject rootObject = new CacheExpressionRootObject(caches,
- method, args, target, targetClass);
+ CacheExpressionRootObject rootObject = new CacheExpressionRootObject(
+ caches, method, args, target, targetClass);
Method targetMethod = getTargetMethod(targetClass, method);
- CacheEvaluationContext evaluationContext = new CacheEvaluationContext(rootObject,
- targetMethod, args, this.paramNameDiscoverer);
+ CacheEvaluationContext evaluationContext = new CacheEvaluationContext(
+ rootObject, targetMethod, args, getParameterNameDiscoverer());
if (result == RESULT_UNAVAILABLE) {
evaluationContext.addUnavailableVariable(RESULT_VARIABLE);
}
else if (result != NO_RESULT) {
evaluationContext.setVariable(RESULT_VARIABLE, result);
}
+ if (beanFactory != null) {
+ evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
+ }
return evaluationContext;
}
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationInvoker.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationInvoker.java
index 8901ba19..3529438d 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationInvoker.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationInvoker.java
@@ -30,9 +30,8 @@ package org.springframework.cache.interceptor;
public interface CacheOperationInvoker {
/**
- * Invoke the cache operation defined by this instance. Wraps any
- * exception that is thrown during the invocation in a
- * {@link ThrowableWrapper}.
+ * Invoke the cache operation defined by this instance. Wraps any exception
+ * that is thrown during the invocation in a {@link ThrowableWrapper}.
* @return the result of the operation
* @throws ThrowableWrapper if an error occurred while invoking the operation
*/
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CachePutOperation.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CachePutOperation.java
index e6a61b0a..01cf15f1 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/CachePutOperation.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CachePutOperation.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,27 +21,51 @@ package org.springframework.cache.interceptor;
*
* @author Costin Leau
* @author Phillip Webb
+ * @author Marcin Kamionowski
* @since 3.1
*/
public class CachePutOperation extends CacheOperation {
- private String unless;
+ private final String unless;
- public String getUnless() {
- return unless;
+ /**
+ * @since 4.3
+ */
+ public CachePutOperation(CachePutOperation.Builder b) {
+ super(b);
+ this.unless = b.unless;
}
- public void setUnless(String unless) {
- this.unless = unless;
+
+ public String getUnless() {
+ return this.unless;
}
- @Override
- protected StringBuilder getOperationDescription() {
- StringBuilder sb = super.getOperationDescription();
- sb.append(" | unless='");
- sb.append(this.unless);
- sb.append("'");
- return sb;
+
+ /**
+ * @since 4.3
+ */
+ public static class Builder extends CacheOperation.Builder {
+
+ private String unless;
+
+ public void setUnless(String unless) {
+ this.unless = unless;
+ }
+
+ @Override
+ protected StringBuilder getOperationDescription() {
+ StringBuilder sb = super.getOperationDescription();
+ sb.append(" | unless='");
+ sb.append(this.unless);
+ sb.append("'");
+ return sb;
+ }
+
+ public CachePutOperation build() {
+ return new CachePutOperation(this);
+ }
}
+
}
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheableOperation.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheableOperation.java
index f9375a9a..c3414e4d 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheableOperation.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheableOperation.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,27 +21,68 @@ package org.springframework.cache.interceptor;
*
* @author Costin Leau
* @author Phillip Webb
+ * @author Marcin Kamionowski
* @since 3.1
*/
public class CacheableOperation extends CacheOperation {
- private String unless;
+ private final String unless;
+
+ private final boolean sync;
+
+
+ /**
+ * @since 4.3
+ */
+ public CacheableOperation(CacheableOperation.Builder b) {
+ super(b);
+ this.unless = b.unless;
+ this.sync = b.sync;
+ }
public String getUnless() {
- return unless;
+ return this.unless;
}
- public void setUnless(String unless) {
- this.unless = unless;
+ public boolean isSync() {
+ return this.sync;
}
- @Override
- protected StringBuilder getOperationDescription() {
- StringBuilder sb = super.getOperationDescription();
- sb.append(" | unless='");
- sb.append(this.unless);
- sb.append("'");
- return sb;
+
+ /**
+ * @since 4.3
+ */
+ public static class Builder extends CacheOperation.Builder {
+
+ private String unless;
+
+ private boolean sync;
+
+ public void setUnless(String unless) {
+ this.unless = unless;
+ }
+
+ public void setSync(boolean sync) {
+ this.sync = sync;
+ }
+
+ @Override
+ protected StringBuilder getOperationDescription() {
+ StringBuilder sb = super.getOperationDescription();
+ sb.append(" | unless='");
+ sb.append(this.unless);
+ sb.append("'");
+ sb.append(" | sync='");
+ sb.append(this.sync);
+ sb.append("'");
+ return sb;
+ }
+
+ @Override
+ public CacheableOperation build() {
+ return new CacheableOperation(this);
+ }
}
+
}
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/NamedCacheResolver.java b/spring-context/src/main/java/org/springframework/cache/interceptor/NamedCacheResolver.java
index 51e95c15..f75fd2e8 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/NamedCacheResolver.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/NamedCacheResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -51,7 +51,7 @@ public class NamedCacheResolver extends AbstractCacheResolver {
@Override
protected Collection<String> getCacheNames(CacheOperationInvocationContext<?> context) {
- return cacheNames;
+ return this.cacheNames;
}
}
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/SimpleKey.java b/spring-context/src/main/java/org/springframework/cache/interceptor/SimpleKey.java
index b9304539..e6573045 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/SimpleKey.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/SimpleKey.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -57,7 +57,7 @@ public class SimpleKey implements Serializable {
@Override
public final int hashCode() {
- return hashCode;
+ return this.hashCode;
}
@Override
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/VariableNotAvailableException.java b/spring-context/src/main/java/org/springframework/cache/interceptor/VariableNotAvailableException.java
index b6f52d61..6f7fdde3 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/VariableNotAvailableException.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/VariableNotAvailableException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +37,6 @@ class VariableNotAvailableException extends EvaluationException {
public String getName() {
- return name;
+ return this.name;
}
}
diff --git a/spring-context/src/main/java/org/springframework/cache/support/AbstractCacheManager.java b/spring-context/src/main/java/org/springframework/cache/support/AbstractCacheManager.java
index 87ab3798..85a1168f 100644
--- a/spring-context/src/main/java/org/springframework/cache/support/AbstractCacheManager.java
+++ b/spring-context/src/main/java/org/springframework/cache/support/AbstractCacheManager.java
@@ -131,7 +131,9 @@ public abstract class AbstractCacheManager implements CacheManager, Initializing
/**
* Dynamically register an additional Cache with this manager.
* @param cache the Cache to register
+ * @deprecated as of Spring 4.3, in favor of {@link #getMissingCache(String)}
*/
+ @Deprecated
protected final void addCache(Cache cache) {
String name = cache.getName();
synchronized (this.cacheMap) {
@@ -171,10 +173,10 @@ public abstract class AbstractCacheManager implements CacheManager, Initializing
/**
* Return a missing cache with the specified {@code name} or {@code null} if
* such cache does not exist or could not be created on the fly.
- * <p>Some caches may be created at runtime in the native provided. If a lookup
- * by name does not yield any result, a subclass gets a chance to register
- * such a cache at runtime. The returned cache will be automatically added to
- * this instance.
+ * <p>Some caches may be created at runtime if the native provider supports
+ * it. If a lookup by name does not yield any result, a subclass gets a chance
+ * to register such a cache at runtime. The returned cache will be automatically
+ * added to this instance.
* @param name the name of the cache to retrieve
* @return the missing cache or {@code null} if no such cache exists or could be
* created
diff --git a/spring-context/src/main/java/org/springframework/cache/support/NoOpCacheManager.java b/spring-context/src/main/java/org/springframework/cache/support/NoOpCacheManager.java
index 0b486804..c27f59e1 100644
--- a/spring-context/src/main/java/org/springframework/cache/support/NoOpCacheManager.java
+++ b/spring-context/src/main/java/org/springframework/cache/support/NoOpCacheManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
+import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -100,6 +101,16 @@ public class NoOpCacheManager implements CacheManager {
}
@Override
+ public <T> T get(Object key, Callable<T> valueLoader) {
+ try {
+ return valueLoader.call();
+ }
+ catch (Exception ex) {
+ throw new ValueRetrievalException(key, valueLoader, ex);
+ }
+ }
+
+ @Override
public String getName() {
return this.name;
}
diff --git a/spring-context/src/main/java/org/springframework/cache/support/package-info.java b/spring-context/src/main/java/org/springframework/cache/support/package-info.java
index 38c95daf..cf3f5d4f 100644
--- a/spring-context/src/main/java/org/springframework/cache/support/package-info.java
+++ b/spring-context/src/main/java/org/springframework/cache/support/package-info.java
@@ -1,5 +1,5 @@
/**
- * Support classes for the the org.springframework.cache package.
+ * Support classes for the org.springframework.cache package.
* Provides abstract classes for cache managers and caches.
*/
package org.springframework.cache.support;
diff --git a/spring-context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java b/spring-context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java
index 09048cbe..7cfeb9e2 100644
--- a/spring-context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java
+++ b/spring-context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@ import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
+import org.springframework.core.io.ProtocolResolver;
/**
* SPI interface to be implemented by most if not all application contexts.
@@ -113,9 +114,9 @@ public interface ConfigurableApplicationContext extends ApplicationContext, Life
* Add a new BeanFactoryPostProcessor that will get applied to the internal
* bean factory of this application context on refresh, before any of the
* bean definitions get evaluated. To be invoked during context configuration.
- * @param beanFactoryPostProcessor the factory processor to register
+ * @param postProcessor the factory processor to register
*/
- void addBeanFactoryPostProcessor(BeanFactoryPostProcessor beanFactoryPostProcessor);
+ void addBeanFactoryPostProcessor(BeanFactoryPostProcessor postProcessor);
/**
* Add a new ApplicationListener that will be notified on context events
@@ -130,6 +131,15 @@ public interface ConfigurableApplicationContext extends ApplicationContext, Life
void addApplicationListener(ApplicationListener<?> listener);
/**
+ * Register the given protocol resolver with this application context,
+ * allowing for additional resource protocols to be handled.
+ * <p>Any such resolver will be invoked ahead of this context's standard
+ * resolution rules. It may therefore also override any default rules.
+ * @since 4.3
+ */
+ void addProtocolResolver(ProtocolResolver resolver);
+
+ /**
* Load or refresh the persistent representation of the configuration,
* which might an XML file, properties file, or relational database schema.
* <p>As this is a startup method, it should destroy already created singletons
diff --git a/spring-context/src/main/java/org/springframework/context/EmbeddedValueResolverAware.java b/spring-context/src/main/java/org/springframework/context/EmbeddedValueResolverAware.java
index 98d25eaa..a3345c2c 100644
--- a/spring-context/src/main/java/org/springframework/context/EmbeddedValueResolverAware.java
+++ b/spring-context/src/main/java/org/springframework/context/EmbeddedValueResolverAware.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,7 +29,9 @@ import org.springframework.util.StringValueResolver;
* @author Juergen Hoeller
* @author Chris Beams
* @since 3.0.3
- * @see org.springframework.beans.factory.config.ConfigurableBeanFactory#resolveEmbeddedValue
+ * @see org.springframework.beans.factory.config.ConfigurableBeanFactory#resolveEmbeddedValue(String)
+ * @see org.springframework.beans.factory.config.ConfigurableBeanFactory#getBeanExpressionResolver()
+ * @see org.springframework.beans.factory.config.EmbeddedValueResolver
*/
public interface EmbeddedValueResolverAware extends Aware {
diff --git a/spring-context/src/main/java/org/springframework/context/access/ContextSingletonBeanFactoryLocator.java b/spring-context/src/main/java/org/springframework/context/access/ContextSingletonBeanFactoryLocator.java
index 57df5694..619e334a 100644
--- a/spring-context/src/main/java/org/springframework/context/access/ContextSingletonBeanFactoryLocator.java
+++ b/spring-context/src/main/java/org/springframework/context/access/ContextSingletonBeanFactoryLocator.java
@@ -70,7 +70,7 @@ public class ContextSingletonBeanFactoryLocator extends SingletonBeanFactoryLoca
}
/**
- * Returns an instance which uses the the specified selector, as the name of the
+ * Returns an instance which uses the specified selector, as the name of the
* definition file(s). In the case of a name with a Spring "classpath*:" prefix,
* or with no prefix, which is treated the same, the current thread's context class
* loader's {@code getResources} method will be called with this value to get
@@ -112,7 +112,7 @@ public class ContextSingletonBeanFactoryLocator extends SingletonBeanFactoryLoca
/**
- * Constructor which uses the the specified name as the resource name
+ * Constructor which uses the specified name as the resource name
* of the definition file(s).
* @param resourceLocation the Spring resource location to use
* (either a URL or a "classpath:" / "classpath*:" pseudo URL)
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java
index 780f8be0..4c16bb0e 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -127,15 +127,13 @@ public class AnnotatedBeanDefinitionReader {
registerBean(annotatedClass, null, (Class<? extends Annotation>[]) null);
}
- public void registerBean(Class<?> annotatedClass,
- @SuppressWarnings("unchecked") Class<? extends Annotation>... qualifiers) {
-
+ @SuppressWarnings("unchecked")
+ public void registerBean(Class<?> annotatedClass, Class<? extends Annotation>... qualifiers) {
registerBean(annotatedClass, null, qualifiers);
}
- public void registerBean(Class<?> annotatedClass, String name,
- @SuppressWarnings("unchecked") Class<? extends Annotation>... qualifiers) {
-
+ @SuppressWarnings("unchecked")
+ public void registerBean(Class<?> annotatedClass, String name, Class<? extends Annotation>... qualifiers) {
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
return;
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationScopeMetadataResolver.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationScopeMetadataResolver.java
index d7d86e96..ddbf312a 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationScopeMetadataResolver.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationScopeMetadataResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -79,9 +79,10 @@ public class AnnotationScopeMetadataResolver implements ScopeMetadataResolver {
ScopeMetadata metadata = new ScopeMetadata();
if (definition instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition;
- AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(annDef.getMetadata(), this.scopeAnnotationType);
+ AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(
+ annDef.getMetadata(), this.scopeAnnotationType);
if (attributes != null) {
- metadata.setScopeName(attributes.getAliasedString("value", this.scopeAnnotationType, definition.getSource()));
+ metadata.setScopeName(attributes.getString("value"));
ScopedProxyMode proxyMode = attributes.getEnum("proxyMode");
if (proxyMode == null || proxyMode == ScopedProxyMode.DEFAULT) {
proxyMode = this.defaultProxyMode;
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AspectJAutoProxyRegistrar.java b/spring-context/src/main/java/org/springframework/context/annotation/AspectJAutoProxyRegistrar.java
index c73fcc7a..5ec21e9c 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/AspectJAutoProxyRegistrar.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/AspectJAutoProxyRegistrar.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@ import org.springframework.core.type.AnnotationMetadata;
* as appropriate based on a given @{@link EnableAspectJAutoProxy} annotation.
*
* @author Chris Beams
+ * @author Juergen Hoeller
* @since 3.1
* @see EnableAspectJAutoProxy
*/
@@ -43,11 +44,14 @@ class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
- AnnotationAttributes enableAJAutoProxy =
+ AnnotationAttributes enableAspectJAutoProxy =
AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
- if (enableAJAutoProxy.getBoolean("proxyTargetClass")) {
+ if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
+ if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
+ AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
+ }
}
}
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AutoProxyRegistrar.java b/spring-context/src/main/java/org/springframework/context/annotation/AutoProxyRegistrar.java
index cc5c911c..58b54814 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/AutoProxyRegistrar.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/AutoProxyRegistrar.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -79,7 +79,7 @@ public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
logger.warn(String.format("%s was imported but no annotations were found " +
"having both 'mode' and 'proxyTargetClass' attributes of type " +
"AdviceMode and boolean respectively. This means that auto proxy " +
- "creator registration and configuration may not have occured as " +
+ "creator registration and configuration may not have occurred as " +
"intended, and components may not be proxied as expected. Check to " +
"ensure that %s has been @Import'ed on the same class where these " +
"annotations are declared; otherwise remove the import of %s " +
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/BeanAnnotationHelper.java b/spring-context/src/main/java/org/springframework/context/annotation/BeanAnnotationHelper.java
index 626a328f..b28ab049 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/BeanAnnotationHelper.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/BeanAnnotationHelper.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,22 +18,19 @@ package org.springframework.context.annotation;
import java.lang.reflect.Method;
-import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.AnnotatedElementUtils;
/**
* Utilities for processing {@link Bean}-annotated methods.
*
* @author Chris Beams
+ * @author Juergen Hoeller
* @since 3.1
*/
class BeanAnnotationHelper {
- /**
- * Return whether the given method is directly or indirectly annotated with
- * the {@link Bean} annotation.
- */
public static boolean isBeanAnnotated(Method method) {
- return (AnnotationUtils.findAnnotation(method, Bean.class) != null);
+ return AnnotatedElementUtils.hasAnnotation(method, Bean.class);
}
public static String determineBeanNameFor(Method beanMethod) {
@@ -41,7 +38,7 @@ class BeanAnnotationHelper {
String beanName = beanMethod.getName();
// Check to see if the user has explicitly set a custom bean name...
- Bean bean = AnnotationUtils.findAnnotation(beanMethod, Bean.class);
+ Bean bean = AnnotatedElementUtils.findMergedAnnotation(beanMethod, Bean.class);
if (bean != null && bean.name().length > 0) {
beanName = bean.name()[0];
}
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java
index b811d52d..8e90baa5 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java
@@ -92,7 +92,7 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo
* implementations.
* <p>If given a plain {@code BeanDefinitionRegistry}, the default {@code ResourceLoader}
* will be a {@link org.springframework.core.io.support.PathMatchingResourcePatternResolver}.
- * <p>If the the passed-in bean factory also implements {@link EnvironmentCapable} its
+ * <p>If the passed-in bean factory also implements {@link EnvironmentCapable} its
* environment will be used by this reader. Otherwise, the reader will initialize and
* use a {@link org.springframework.core.env.StandardEnvironment}. All
* {@code ApplicationContext} implementations are {@code EnvironmentCapable}, while
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java b/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java
index f621ea24..c3dbc027 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -58,6 +58,7 @@ import org.springframework.beans.factory.annotation.InjectionMetadata;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.DependencyDescriptor;
+import org.springframework.beans.factory.config.EmbeddedValueResolver;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.BridgeMethodResolver;
@@ -68,6 +69,7 @@ import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
+import org.springframework.util.StringValueResolver;
/**
* {@link org.springframework.beans.factory.config.BeanPostProcessor} implementation
@@ -140,6 +142,9 @@ import org.springframework.util.StringUtils;
public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBeanPostProcessor
implements InstantiationAwareBeanPostProcessor, BeanFactoryAware, Serializable {
+ // Common Annotations 1.1 Resource.lookup() available? Not present on JDK 6...
+ private static final Method lookupAttribute = ClassUtils.getMethodIfAvailable(Resource.class, "lookup");
+
private static Class<? extends Annotation> webServiceRefClass = null;
private static Class<? extends Annotation> ejbRefClass = null;
@@ -178,6 +183,8 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
private transient BeanFactory beanFactory;
+ private transient StringValueResolver embeddedValueResolver;
+
private transient final Map<String, InjectionMetadata> injectionMetadataCache =
new ConcurrentHashMap<String, InjectionMetadata>(256);
@@ -271,12 +278,15 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
}
@Override
- public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
+ public void setBeanFactory(BeanFactory beanFactory) {
Assert.notNull(beanFactory, "BeanFactory must not be null");
this.beanFactory = beanFactory;
if (this.resourceFactory == null) {
this.resourceFactory = beanFactory;
}
+ if (beanFactory instanceof ConfigurableBeanFactory) {
+ this.embeddedValueResolver = new EmbeddedValueResolver((ConfigurableBeanFactory) beanFactory);
+ }
}
@@ -592,8 +602,8 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
resourceName = Introspector.decapitalize(resourceName.substring(3));
}
}
- else if (beanFactory instanceof ConfigurableBeanFactory){
- resourceName = ((ConfigurableBeanFactory) beanFactory).resolveEmbeddedValue(resourceName);
+ else if (embeddedValueResolver != null) {
+ resourceName = embeddedValueResolver.resolveStringValue(resourceName);
}
if (resourceType != null && Object.class != resourceType) {
checkResourceType(resourceType);
@@ -604,7 +614,9 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
}
this.name = resourceName;
this.lookupType = resourceType;
- this.mappedName = resource.mappedName();
+ String lookupValue = (lookupAttribute != null ?
+ (String) ReflectionUtils.invokeMethod(lookupAttribute, resource) : null);
+ this.mappedName = (StringUtils.hasLength(lookupValue) ? lookupValue : resource.mappedName());
Lazy lazy = ae.getAnnotation(Lazy.class);
this.lazyLookup = (lazy != null && lazy.value());
}
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScan.java b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScan.java
index f7ad662c..4d0d9464 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScan.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScan.java
@@ -18,6 +18,7 @@ package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@@ -54,6 +55,7 @@ import org.springframework.core.type.filter.TypeFilter;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
+@Repeatable(ComponentScans.class)
public @interface ComponentScan {
/**
@@ -187,6 +189,15 @@ public @interface ComponentScan {
* </table>
* <p>When multiple classes are specified, <em>OR</em> logic is applied
* &mdash; for example, "include types annotated with {@code @Foo} OR {@code @Bar}".
+ * <p>Custom {@link TypeFilter TypeFilters} may optionally implement any of the
+ * following {@link org.springframework.beans.factory.Aware Aware} interfaces, and
+ * their respective methods will be called prior to {@link TypeFilter#match match}:
+ * <ul>
+ * <li>{@link org.springframework.context.EnvironmentAware EnvironmentAware}</li>
+ * <li>{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}
+ * <li>{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}
+ * <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}
+ * </ul>
* <p>Specifying zero classes is permitted but will have no effect on component
* scanning.
* @since 4.2
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java
index 1119012b..247c5838 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,10 +25,17 @@ import java.util.Set;
import java.util.regex.Pattern;
import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.Aware;
+import org.springframework.beans.factory.BeanClassLoaderAware;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.EnvironmentAware;
+import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
@@ -115,7 +122,7 @@ class ComponentScanAnnotationParser {
}
Set<String> basePackages = new LinkedHashSet<String>();
- String[] basePackagesArray = componentScan.getAliasedStringArray("basePackages", ComponentScan.class, declaringClass);
+ String[] basePackagesArray = componentScan.getStringArray("basePackages");
for (String pkg : basePackagesArray) {
String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
@@ -141,11 +148,11 @@ class ComponentScanAnnotationParser {
List<TypeFilter> typeFilters = new ArrayList<TypeFilter>();
FilterType filterType = filterAttributes.getEnum("type");
- for (Class<?> filterClass : filterAttributes.getAliasedClassArray("classes", ComponentScan.Filter.class, null)) {
+ for (Class<?> filterClass : filterAttributes.getClassArray("classes")) {
switch (filterType) {
case ANNOTATION:
Assert.isAssignable(Annotation.class, filterClass,
- "An error occured while processing a @ComponentScan ANNOTATION type filter: ");
+ "An error occurred while processing a @ComponentScan ANNOTATION type filter: ");
@SuppressWarnings("unchecked")
Class<Annotation> annotationType = (Class<Annotation>) filterClass;
typeFilters.add(new AnnotationTypeFilter(annotationType));
@@ -155,8 +162,10 @@ class ComponentScanAnnotationParser {
break;
case CUSTOM:
Assert.isAssignable(TypeFilter.class, filterClass,
- "An error occured while processing a @ComponentScan CUSTOM type filter: ");
- typeFilters.add(BeanUtils.instantiateClass(filterClass, TypeFilter.class));
+ "An error occurred while processing a @ComponentScan CUSTOM type filter: ");
+ TypeFilter filter = BeanUtils.instantiateClass(filterClass, TypeFilter.class);
+ invokeAwareMethods(filter);
+ typeFilters.add(filter);
break;
default:
throw new IllegalArgumentException("Filter type not supported with Class value: " + filterType);
@@ -179,4 +188,27 @@ class ComponentScanAnnotationParser {
return typeFilters;
}
+ /**
+ * Invoke {@link ResourceLoaderAware}, {@link BeanClassLoaderAware} and
+ * {@link BeanFactoryAware} contracts if implemented by the given {@code filter}.
+ */
+ private void invokeAwareMethods(TypeFilter filter) {
+ if (filter instanceof Aware) {
+ if (filter instanceof EnvironmentAware) {
+ ((EnvironmentAware) filter).setEnvironment(this.environment);
+ }
+ if (filter instanceof ResourceLoaderAware) {
+ ((ResourceLoaderAware) filter).setResourceLoader(this.resourceLoader);
+ }
+ if (filter instanceof BeanClassLoaderAware) {
+ ClassLoader classLoader = (this.registry instanceof ConfigurableBeanFactory ?
+ ((ConfigurableBeanFactory) this.registry).getBeanClassLoader() :
+ this.resourceLoader.getClassLoader());
+ ((BeanClassLoaderAware) filter).setBeanClassLoader(classLoader);
+ }
+ if (filter instanceof BeanFactoryAware && this.registry instanceof BeanFactory) {
+ ((BeanFactoryAware) filter).setBeanFactory((BeanFactory) this.registry);
+ }
+ }
+ }
}
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScans.java b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScans.java
new file mode 100644
index 00000000..c9498556
--- /dev/null
+++ b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScans.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2002-2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.context.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Container annotation that aggregates several {@link ComponentScan} annotations.
+ *
+ * <p>Can be used natively, declaring several nested {@link ComponentScan} annotations.
+ * Can also be used in conjunction with Java 8's support for repeatable annotations,
+ * where {@link ComponentScan} can simply be declared several times on the same method,
+ * implicitly generating this container annotation.
+ *
+ * @author Juergen Hoeller
+ * @since 4.3
+ * @see ComponentScan
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Documented
+public @interface ComponentScans {
+
+ ComponentScan[] value();
+
+}
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java
index 5cb5c0f3..7fa587fa 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -103,7 +103,7 @@ final class ConfigurationClass {
public ConfigurationClass(Class<?> clazz, String beanName) {
Assert.hasText(beanName, "Bean name must not be null");
this.metadata = new StandardAnnotationMetadata(clazz, true);
- this.resource = new DescriptiveResource(clazz.toString());
+ this.resource = new DescriptiveResource(clazz.getName());
this.beanName = beanName;
}
@@ -117,7 +117,7 @@ final class ConfigurationClass {
*/
public ConfigurationClass(Class<?> clazz, ConfigurationClass importedBy) {
this.metadata = new StandardAnnotationMetadata(clazz, true);
- this.resource = new DescriptiveResource(clazz.toString());
+ this.resource = new DescriptiveResource(clazz.getName());
this.importedBy.add(importedBy);
}
@@ -233,7 +233,7 @@ final class ConfigurationClass {
@Override
public String toString() {
- return "ConfigurationClass:beanName=" + this.beanName + ",resource=" + this.resource;
+ return "ConfigurationClass: beanName '" + this.beanName + "', " + this.resource;
}
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java
index 9031872e..522acf33 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java
@@ -236,7 +236,7 @@ class ConfigurationClassBeanDefinitionReader {
ScopedProxyMode proxyMode = ScopedProxyMode.NO;
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(metadata, Scope.class);
if (attributes != null) {
- beanDef.setScope(attributes.getAliasedString("value", Scope.class, configClass.getResource()));
+ beanDef.setScope(attributes.getString("value"));
proxyMode = attributes.getEnum("proxyMode");
if (proxyMode == ScopedProxyMode.DEFAULT) {
proxyMode = ScopedProxyMode.NO;
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java
index 4aea07f9..63497ff9 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java
@@ -45,7 +45,7 @@ import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.cglib.proxy.NoOp;
import org.springframework.cglib.transform.ClassEmitterTransformer;
import org.springframework.cglib.transform.TransformingClassGenerator;
-import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.objenesis.ObjenesisException;
import org.springframework.objenesis.SpringObjenesis;
import org.springframework.util.Assert;
@@ -311,7 +311,7 @@ class ConfigurationClassEnhancer {
String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);
// Determine whether this bean is a scoped-proxy
- Scope scope = AnnotationUtils.findAnnotation(beanMethod, Scope.class);
+ Scope scope = AnnotatedElementUtils.findMergedAnnotation(beanMethod, Scope.class);
if (scope != null && scope.proxyMode() != ScopedProxyMode.NO) {
String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
@@ -466,7 +466,7 @@ class ConfigurationClassEnhancer {
try {
fbProxy = fbClass.newInstance();
}
- catch (Exception ex) {
+ catch (Throwable ex) {
throw new IllegalStateException("Unable to instantiate enhanced FactoryBean using Objenesis, " +
"and regular FactoryBean instantiation via default constructor fails as well", ex);
}
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java
index 023371b4..46d98634 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java
@@ -66,6 +66,9 @@ import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
+import org.springframework.core.io.support.DefaultPropertySourceFactory;
+import org.springframework.core.io.support.EncodedResource;
+import org.springframework.core.io.support.PropertySourceFactory;
import org.springframework.core.io.support.ResourcePropertySource;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.MethodMetadata;
@@ -102,6 +105,8 @@ import org.springframework.util.StringUtils;
*/
class ConfigurationClassParser {
+ private static final PropertySourceFactory DEFAULT_PROPERTY_SOURCE_FACTORY = new DefaultPropertySourceFactory();
+
private static final Comparator<DeferredImportSelectorHolder> DEFERRED_IMPORT_COMPARATOR =
new Comparator<ConfigurationClassParser.DeferredImportSelectorHolder>() {
@Override
@@ -177,7 +182,7 @@ class ConfigurationClassParser {
catch (BeanDefinitionStoreException ex) {
throw ex;
}
- catch (Exception ex) {
+ catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
}
@@ -261,15 +266,18 @@ class ConfigurationClassParser {
}
// Process any @ComponentScan annotations
- AnnotationAttributes componentScan = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ComponentScan.class);
- if (componentScan != null && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
- // The config class is annotated with @ComponentScan -> perform the scan immediately
- Set<BeanDefinitionHolder> scannedBeanDefinitions =
- this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
- // Check the set of scanned definitions for any further config classes and parse recursively if necessary
- for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
- if (ConfigurationClassUtils.checkConfigurationClassCandidate(holder.getBeanDefinition(), this.metadataReaderFactory)) {
- parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
+ Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
+ sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
+ if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
+ for (AnnotationAttributes componentScan : componentScans) {
+ // The config class is annotated with @ComponentScan -> perform the scan immediately
+ Set<BeanDefinitionHolder> scannedBeanDefinitions =
+ this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
+ // Check the set of scanned definitions for any further config classes and parse recursively if necessary
+ for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
+ if (ConfigurationClassUtils.checkConfigurationClassCandidate(holder.getBeanDefinition(), this.metadataReaderFactory)) {
+ parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
+ }
}
}
}
@@ -279,8 +287,9 @@ class ConfigurationClassParser {
// Process any @ImportResource annotations
if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) {
- AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
- String[] resources = importResource.getAliasedStringArray("locations", ImportResource.class, sourceClass);
+ AnnotationAttributes importResource =
+ AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
+ String[] resources = importResource.getStringArray("locations");
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
for (String resource : resources) {
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
@@ -357,16 +366,26 @@ class ConfigurationClassParser {
*/
private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
String name = propertySource.getString("name");
+ if (!StringUtils.hasLength(name)) {
+ name = null;
+ }
+ String encoding = propertySource.getString("encoding");
+ if (!StringUtils.hasLength(encoding)) {
+ encoding = null;
+ }
String[] locations = propertySource.getStringArray("value");
- boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required");
+ boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
+
+ Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
+ PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
+ DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));
+
for (String location : locations) {
try {
String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
Resource resource = this.resourceLoader.getResource(resolvedLocation);
- ResourcePropertySource rps = (StringUtils.hasText(name) ?
- new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource));
- addPropertySource(rps);
+ addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
}
catch (IllegalArgumentException ex) {
// from resolveRequiredPlaceholders
@@ -383,21 +402,23 @@ class ConfigurationClassParser {
}
}
- private void addPropertySource(ResourcePropertySource propertySource) {
+ private void addPropertySource(PropertySource<?> propertySource) {
String name = propertySource.getName();
MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment).getPropertySources();
if (propertySources.contains(name) && this.propertySourceNames.contains(name)) {
// We've already added a version, we need to extend it
PropertySource<?> existing = propertySources.get(name);
+ PropertySource<?> newSource = (propertySource instanceof ResourcePropertySource ?
+ ((ResourcePropertySource) propertySource).withResourceName() : propertySource);
if (existing instanceof CompositePropertySource) {
- ((CompositePropertySource) existing).addFirstPropertySource(propertySource.withResourceName());
+ ((CompositePropertySource) existing).addFirstPropertySource(newSource);
}
else {
if (existing instanceof ResourcePropertySource) {
existing = ((ResourcePropertySource) existing).withResourceName();
}
CompositePropertySource composite = new CompositePropertySource(name);
- composite.addPropertySource(propertySource.withResourceName());
+ composite.addPropertySource(newSource);
composite.addPropertySource(existing);
propertySources.replace(name, composite);
}
@@ -463,7 +484,7 @@ class ConfigurationClassParser {
catch (BeanDefinitionStoreException ex) {
throw ex;
}
- catch (Exception ex) {
+ catch (Throwable ex) {
throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" +
configClass.getMetadata().getClassName() + "]", ex);
}
@@ -477,7 +498,7 @@ class ConfigurationClassParser {
return;
}
- if (checkForCircularImports && this.importStack.contains(configClass)) {
+ if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
@@ -520,7 +541,7 @@ class ConfigurationClassParser {
catch (BeanDefinitionStoreException ex) {
throw ex;
}
- catch (Exception ex) {
+ catch (Throwable ex) {
throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" +
configClass.getMetadata().getClassName() + "]", ex);
}
@@ -530,6 +551,20 @@ class ConfigurationClassParser {
}
}
+ private boolean isChainedImportOnStack(ConfigurationClass configClass) {
+ if (this.importStack.contains(configClass)) {
+ String configClassName = configClass.getMetadata().getClassName();
+ AnnotationMetadata importingClass = this.importStack.getImportingClassFor(configClassName);
+ while (importingClass != null) {
+ if (configClassName.equals(importingClass.getClassName())) {
+ return true;
+ }
+ importingClass = this.importStack.getImportingClassFor(importingClass.getClassName());
+ }
+ }
+ return false;
+ }
+
/**
* Invoke {@link ResourceLoaderAware}, {@link BeanClassLoaderAware} and
* {@link BeanFactoryAware} contracts if implemented by the given {@code bean}.
@@ -836,7 +871,7 @@ class ConfigurationClassParser {
private SourceClass getRelated(String className) throws IOException {
if (this.source instanceof Class<?>) {
try {
- Class<?> clazz = resourceLoader.getClassLoader().loadClass(className);
+ Class<?> clazz = ((Class<?>) this.source).getClassLoader().loadClass(className);
return asSourceClass(clazz);
}
catch (ClassNotFoundException ex) {
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/EnableAspectJAutoProxy.java b/spring-context/src/main/java/org/springframework/context/annotation/EnableAspectJAutoProxy.java
index e2d652c8..bb82665f 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/EnableAspectJAutoProxy.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/EnableAspectJAutoProxy.java
@@ -102,6 +102,7 @@ import java.lang.annotation.Target;
* }</pre>
*
* @author Chris Beams
+ * @author Juergen Hoeller
* @since 3.1
* @see org.aspectj.lang.annotation.Aspect
*/
@@ -117,4 +118,12 @@ public @interface EnableAspectJAutoProxy {
*/
boolean proxyTargetClass() default false;
+ /**
+ * Indicate that the proxy should be exposed by the AOP framework as a {@code ThreadLocal}
+ * for retrieval via the {@link org.springframework.aop.framework.AopContext} class.
+ * Off by default, i.e. no guarantees that {@code AopContext} access will work.
+ * @since 4.3.1
+ */
+ boolean exposeProxy() default false;
+
}
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ImportBeanDefinitionRegistrar.java b/spring-context/src/main/java/org/springframework/context/annotation/ImportBeanDefinitionRegistrar.java
index 6795d617..1dce3d41 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/ImportBeanDefinitionRegistrar.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/ImportBeanDefinitionRegistrar.java
@@ -33,7 +33,7 @@ import org.springframework.core.type.AnnotationMetadata;
* {@link org.springframework.beans.factory.Aware Aware} interfaces, and their respective
* methods will be called prior to {@link #registerBeanDefinitions}:
* <ul>
- * <li>{@link org.springframework.context.EnvironmentAware}</li>
+ * <li>{@link org.springframework.context.EnvironmentAware EnvironmentAware}</li>
* <li>{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}
* <li>{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}
* <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ImportSelector.java b/spring-context/src/main/java/org/springframework/context/annotation/ImportSelector.java
index b98719f9..c684426f 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/ImportSelector.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/ImportSelector.java
@@ -27,7 +27,7 @@ import org.springframework.core.type.AnnotationMetadata;
* {@link org.springframework.beans.factory.Aware Aware} interfaces, and their respective
* methods will be called prior to {@link #selectImports}:
* <ul>
- * <li>{@link org.springframework.context.EnvironmentAware}</li>
+ * <li>{@link org.springframework.context.EnvironmentAware EnvironmentAware}</li>
* <li>{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}</li>
* <li>{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}</li>
* <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}</li>
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/PropertySource.java b/spring-context/src/main/java/org/springframework/context/annotation/PropertySource.java
index 908f70a7..e4237110 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/PropertySource.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/PropertySource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,8 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import org.springframework.core.io.support.PropertySourceFactory;
+
/**
* Annotation providing a convenient and declarative mechanism for adding a
* {@link org.springframework.core.env.PropertySource PropertySource} to Spring's
@@ -138,6 +140,7 @@ import java.lang.annotation.Target;
* javadocs for details.
*
* @author Chris Beams
+ * @author Juergen Hoeller
* @author Phillip Webb
* @since 3.1
* @see PropertySources
@@ -153,9 +156,8 @@ import java.lang.annotation.Target;
public @interface PropertySource {
/**
- * Indicate the name of this property source. If omitted, a name
- * will be generated based on the description of the underlying
- * resource.
+ * Indicate the name of this property source. If omitted, a name will
+ * be generated based on the description of the underlying resource.
* @see org.springframework.core.env.PropertySource#getName()
* @see org.springframework.core.io.Resource#getDescription()
*/
@@ -184,4 +186,19 @@ public @interface PropertySource {
*/
boolean ignoreResourceNotFound() default false;
+ /**
+ * A specific character encoding for the given resources, e.g. "UTF-8".
+ * @since 4.3
+ */
+ String encoding() default "";
+
+ /**
+ * Specify a custom {@link PropertySourceFactory}, if any.
+ * <p>By default, a default factory for standard resource files will be used.
+ * @since 4.3
+ * @see org.springframework.core.io.support.DefaultPropertySourceFactory
+ * @see org.springframework.core.io.support.ResourcePropertySource
+ */
+ Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;
+
}
diff --git a/spring-context/src/main/java/org/springframework/context/config/LoadTimeWeaverBeanDefinitionParser.java b/spring-context/src/main/java/org/springframework/context/config/LoadTimeWeaverBeanDefinitionParser.java
index 5fb8af96..ae717b09 100644
--- a/spring-context/src/main/java/org/springframework/context/config/LoadTimeWeaverBeanDefinitionParser.java
+++ b/spring-context/src/main/java/org/springframework/context/config/LoadTimeWeaverBeanDefinitionParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@ package org.springframework.context.config;
import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.RootBeanDefinition;
@@ -36,15 +37,22 @@ import org.springframework.util.ClassUtils;
*/
class LoadTimeWeaverBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
- private static final String WEAVER_CLASS_ATTRIBUTE = "weaver-class";
+ /**
+ * The bean name of the internally managed AspectJ weaving enabler.
+ * @since 4.3.1
+ */
+ public static final String ASPECTJ_WEAVING_ENABLER_BEAN_NAME =
+ "org.springframework.context.config.internalAspectJWeavingEnabler";
- private static final String ASPECTJ_WEAVING_ATTRIBUTE = "aspectj-weaving";
+ private static final String ASPECTJ_WEAVING_ENABLER_CLASS_NAME =
+ "org.springframework.context.weaving.AspectJWeavingEnabler";
private static final String DEFAULT_LOAD_TIME_WEAVER_CLASS_NAME =
"org.springframework.context.weaving.DefaultContextLoadTimeWeaver";
- private static final String ASPECTJ_WEAVING_ENABLER_CLASS_NAME =
- "org.springframework.context.weaving.AspectJWeavingEnabler";
+ private static final String WEAVER_CLASS_ATTRIBUTE = "weaver-class";
+
+ private static final String ASPECTJ_WEAVING_ATTRIBUTE = "aspectj-weaving";
@Override
@@ -65,9 +73,11 @@ class LoadTimeWeaverBeanDefinitionParser extends AbstractSingleBeanDefinitionPar
builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
if (isAspectJWeavingEnabled(element.getAttribute(ASPECTJ_WEAVING_ATTRIBUTE), parserContext)) {
- RootBeanDefinition weavingEnablerDef = new RootBeanDefinition();
- weavingEnablerDef.setBeanClassName(ASPECTJ_WEAVING_ENABLER_CLASS_NAME);
- parserContext.getReaderContext().registerWithGeneratedName(weavingEnablerDef);
+ if (!parserContext.getRegistry().containsBeanDefinition(ASPECTJ_WEAVING_ENABLER_BEAN_NAME)) {
+ RootBeanDefinition def = new RootBeanDefinition(ASPECTJ_WEAVING_ENABLER_CLASS_NAME);
+ parserContext.registerBeanComponent(
+ new BeanComponentDefinition(def, ASPECTJ_WEAVING_ENABLER_BEAN_NAME));
+ }
if (isBeanConfigurerAspectEnabled(parserContext.getReaderContext().getBeanClassLoader())) {
new SpringConfiguredBeanDefinitionParser().parse(element, parserContext);
diff --git a/spring-context/src/main/java/org/springframework/context/config/PropertyPlaceholderBeanDefinitionParser.java b/spring-context/src/main/java/org/springframework/context/config/PropertyPlaceholderBeanDefinitionParser.java
index f2801597..cb38e6b0 100644
--- a/spring-context/src/main/java/org/springframework/context/config/PropertyPlaceholderBeanDefinitionParser.java
+++ b/spring-context/src/main/java/org/springframework/context/config/PropertyPlaceholderBeanDefinitionParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -69,7 +69,9 @@ class PropertyPlaceholderBeanDefinitionParser extends AbstractPropertyLoadingBea
if (element.hasAttribute("value-separator")) {
builder.addPropertyValue("valueSeparator", element.getAttribute("value-separator"));
}
-
+ if (element.hasAttribute("trim-values")) {
+ builder.addPropertyValue("trimValues", element.getAttribute("trim-values"));
+ }
if (element.hasAttribute("null-value")) {
builder.addPropertyValue("nullValue", element.getAttribute("null-value"));
}
diff --git a/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java b/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java
index 0eb68e9e..b7dd9186 100644
--- a/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java
+++ b/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -286,7 +286,7 @@ public abstract class AbstractApplicationEventMulticaster
/**
* Cache key for ListenerRetrievers, based on event type and source type.
*/
- private static class ListenerCacheKey {
+ private static final class ListenerCacheKey implements Comparable<ListenerCacheKey> {
private final ResolvableType eventType;
@@ -311,6 +311,23 @@ public abstract class AbstractApplicationEventMulticaster
public int hashCode() {
return (ObjectUtils.nullSafeHashCode(this.eventType) * 29 + ObjectUtils.nullSafeHashCode(this.sourceType));
}
+
+ @Override
+ public String toString() {
+ return "ListenerCacheKey [eventType = " + this.eventType + ", sourceType = " + this.sourceType.getName() + "]";
+ }
+
+ @Override
+ public int compareTo(ListenerCacheKey other) {
+ int result = 0;
+ if (this.eventType != null) {
+ result = this.eventType.toString().compareTo(other.eventType.toString());
+ }
+ if (result == 0 && this.sourceType != null) {
+ result = this.sourceType.getName().compareTo(other.sourceType.getName());
+ }
+ return result;
+ }
}
diff --git a/spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java b/spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java
index 243d1642..a57eb0c6 100644
--- a/spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java
+++ b/spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,7 +35,6 @@ import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotatedElementUtils;
-import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.expression.EvaluationContext;
import org.springframework.util.Assert;
@@ -207,14 +206,14 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
if (StringUtils.hasText(condition)) {
Assert.notNull(this.evaluator, "EventExpressionEvaluator must no be null");
EvaluationContext evaluationContext = this.evaluator.createEvaluationContext(
- event, this.targetClass, this.method, args);
+ event, this.targetClass, this.method, args, this.applicationContext);
return this.evaluator.condition(condition, this.methodKey, evaluationContext);
}
return true;
}
protected <A extends Annotation> A getMethodAnnotation(Class<A> annotationType) {
- return AnnotationUtils.findAnnotation(this.method, annotationType);
+ return AnnotatedElementUtils.findMergedAnnotation(this.method, annotationType);
}
/**
diff --git a/spring-context/src/main/java/org/springframework/context/event/EventExpressionEvaluator.java b/spring-context/src/main/java/org/springframework/context/event/EventExpressionEvaluator.java
index 9bf4fcf6..8cb00fe8 100644
--- a/spring-context/src/main/java/org/springframework/context/event/EventExpressionEvaluator.java
+++ b/spring-context/src/main/java/org/springframework/context/event/EventExpressionEvaluator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,12 +21,12 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.aop.support.AopUtils;
+import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.expression.AnnotatedElementKey;
+import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.context.expression.CachedExpressionEvaluator;
import org.springframework.context.expression.MethodBasedEvaluationContext;
-import org.springframework.core.DefaultParameterNameDiscoverer;
-import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
@@ -40,23 +40,26 @@ import org.springframework.expression.Expression;
*/
class EventExpressionEvaluator extends CachedExpressionEvaluator {
- // shared param discoverer since it caches data internally
- private final ParameterNameDiscoverer paramNameDiscoverer = new DefaultParameterNameDiscoverer();
-
private final Map<ExpressionKey, Expression> conditionCache = new ConcurrentHashMap<ExpressionKey, Expression>(64);
private final Map<AnnotatedElementKey, Method> targetMethodCache = new ConcurrentHashMap<AnnotatedElementKey, Method>(64);
+
/**
* Create the suitable {@link EvaluationContext} for the specified event handling
* on the specified method.
*/
public EvaluationContext createEvaluationContext(ApplicationEvent event, Class<?> targetClass,
- Method method, Object[] args) {
+ Method method, Object[] args, BeanFactory beanFactory) {
Method targetMethod = getTargetMethod(targetClass, method);
EventExpressionRootObject root = new EventExpressionRootObject(event, args);
- return new MethodBasedEvaluationContext(root, targetMethod, args, this.paramNameDiscoverer);
+ MethodBasedEvaluationContext evaluationContext = new MethodBasedEvaluationContext(
+ root, targetMethod, args, getParameterNameDiscoverer());
+ if (beanFactory != null) {
+ evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
+ }
+ return evaluationContext;
}
/**
diff --git a/spring-context/src/main/java/org/springframework/context/event/EventListenerMethodProcessor.java b/spring-context/src/main/java/org/springframework/context/event/EventListenerMethodProcessor.java
index e24cac8d..7a95f172 100644
--- a/spring-context/src/main/java/org/springframework/context/event/EventListenerMethodProcessor.java
+++ b/spring-context/src/main/java/org/springframework/context/event/EventListenerMethodProcessor.java
@@ -30,6 +30,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.autoproxy.AutoProxyUtils;
import org.springframework.aop.scope.ScopedObject;
import org.springframework.aop.scope.ScopedProxyUtils;
+import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.SmartInitializingSingleton;
@@ -38,8 +39,8 @@ import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.MethodIntrospector;
+import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
-import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
@@ -131,7 +132,7 @@ public class EventListenerMethodProcessor implements SmartInitializingSingleton,
new MethodIntrospector.MetadataLookup<EventListener>() {
@Override
public EventListener inspect(Method method) {
- return AnnotationUtils.findAnnotation(method, EventListener.class);
+ return AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class);
}
});
}
@@ -152,7 +153,7 @@ public class EventListenerMethodProcessor implements SmartInitializingSingleton,
for (Method method : annotatedMethods.keySet()) {
for (EventListenerFactory factory : factories) {
if (factory.supportsMethod(method)) {
- Method methodToUse = MethodIntrospector.selectInvocableMethod(
+ Method methodToUse = AopUtils.selectInvocableMethod(
method, this.applicationContext.getType(beanName));
ApplicationListener<?> applicationListener =
factory.createApplicationListener(beanName, targetType, methodToUse);
diff --git a/spring-context/src/main/java/org/springframework/context/expression/AnnotatedElementKey.java b/spring-context/src/main/java/org/springframework/context/expression/AnnotatedElementKey.java
index 8f7f88a9..50812c11 100644
--- a/spring-context/src/main/java/org/springframework/context/expression/AnnotatedElementKey.java
+++ b/spring-context/src/main/java/org/springframework/context/expression/AnnotatedElementKey.java
@@ -30,7 +30,7 @@ import org.springframework.util.ObjectUtils;
* @since 4.2
* @see CachedExpressionEvaluator
*/
-public final class AnnotatedElementKey {
+public final class AnnotatedElementKey implements Comparable<AnnotatedElementKey> {
private final AnnotatedElement element;
@@ -66,4 +66,18 @@ public final class AnnotatedElementKey {
return this.element.hashCode() + (this.targetClass != null ? this.targetClass.hashCode() * 29 : 0);
}
+ @Override
+ public String toString() {
+ return this.element + (this.targetClass != null ? " on " + this.targetClass : "");
+ }
+
+ @Override
+ public int compareTo(AnnotatedElementKey other) {
+ int result = this.element.toString().compareTo(other.element.toString());
+ if (result == 0 && this.targetClass != null) {
+ result = this.targetClass.getName().compareTo(other.targetClass.getName());
+ }
+ return result;
+ }
+
}
diff --git a/spring-context/src/main/java/org/springframework/context/expression/CachedExpressionEvaluator.java b/spring-context/src/main/java/org/springframework/context/expression/CachedExpressionEvaluator.java
index 4bb20b28..d530db80 100644
--- a/spring-context/src/main/java/org/springframework/context/expression/CachedExpressionEvaluator.java
+++ b/spring-context/src/main/java/org/springframework/context/expression/CachedExpressionEvaluator.java
@@ -18,6 +18,8 @@ package org.springframework.context.expression;
import java.util.Map;
+import org.springframework.core.DefaultParameterNameDiscoverer;
+import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.util.Assert;
@@ -35,12 +37,14 @@ public abstract class CachedExpressionEvaluator {
private final SpelExpressionParser parser;
+ private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
+
/**
* Create a new instance with the specified {@link SpelExpressionParser}.
*/
protected CachedExpressionEvaluator(SpelExpressionParser parser) {
- Assert.notNull(parser, "Parser must not be null");
+ Assert.notNull(parser, "SpelExpressionParser must not be null");
this.parser = parser;
}
@@ -59,6 +63,14 @@ public abstract class CachedExpressionEvaluator {
return this.parser;
}
+ /**
+ * Return a shared parameter name discoverer which caches data internally.
+ * @since 4.3
+ */
+ protected ParameterNameDiscoverer getParameterNameDiscoverer() {
+ return this.parameterNameDiscoverer;
+ }
+
/**
* Return the {@link Expression} for the specified SpEL value
@@ -84,14 +96,14 @@ public abstract class CachedExpressionEvaluator {
}
- protected static class ExpressionKey {
+ protected static class ExpressionKey implements Comparable<ExpressionKey> {
- private final AnnotatedElementKey key;
+ private final AnnotatedElementKey element;
private final String expression;
- protected ExpressionKey(AnnotatedElementKey key, String expression) {
- this.key = key;
+ protected ExpressionKey(AnnotatedElementKey element, String expression) {
+ this.element = element;
this.expression = expression;
}
@@ -104,13 +116,27 @@ public abstract class CachedExpressionEvaluator {
return false;
}
ExpressionKey otherKey = (ExpressionKey) other;
- return (this.key.equals(otherKey.key) &&
+ return (this.element.equals(otherKey.element) &&
ObjectUtils.nullSafeEquals(this.expression, otherKey.expression));
}
@Override
public int hashCode() {
- return this.key.hashCode() + (this.expression != null ? this.expression.hashCode() * 29 : 0);
+ return this.element.hashCode() + (this.expression != null ? this.expression.hashCode() * 29 : 0);
+ }
+
+ @Override
+ public String toString() {
+ return this.element + (this.expression != null ? " with expression \"" + this.expression : "\"");
+ }
+
+ @Override
+ public int compareTo(ExpressionKey other) {
+ int result = this.element.toString().compareTo(other.element.toString());
+ if (result == 0 && this.expression != null) {
+ result = this.expression.compareTo(other.expression);
+ }
+ return result;
}
}
diff --git a/spring-context/src/main/java/org/springframework/context/expression/MethodBasedEvaluationContext.java b/spring-context/src/main/java/org/springframework/context/expression/MethodBasedEvaluationContext.java
index b6d783b2..6c564535 100644
--- a/spring-context/src/main/java/org/springframework/context/expression/MethodBasedEvaluationContext.java
+++ b/spring-context/src/main/java/org/springframework/context/expression/MethodBasedEvaluationContext.java
@@ -34,6 +34,7 @@ import org.springframework.util.ObjectUtils;
* </ol>
*
* @author Stephane Nicoll
+ * @author Sergey Podgurskiy
* @since 4.2
*/
public class MethodBasedEvaluationContext extends StandardEvaluationContext {
@@ -89,7 +90,7 @@ public class MethodBasedEvaluationContext extends StandardEvaluationContext {
String[] parameterNames = this.paramDiscoverer.getParameterNames(this.method);
// save parameter names (if discovered)
if (parameterNames != null) {
- for (int i = 0; i < parameterNames.length; i++) {
+ for (int i = 0; i < this.args.length; i++) {
setVariable(parameterNames[i], this.args[i]);
}
}
diff --git a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java
index d21d2817..2e462c01 100644
--- a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java
+++ b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -78,6 +78,7 @@ import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
+import org.springframework.util.StringValueResolver;
/**
* Abstract implementation of the {@link org.springframework.context.ApplicationContext}
@@ -461,8 +462,9 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
}
@Override
- public void addBeanFactoryPostProcessor(BeanFactoryPostProcessor beanFactoryPostProcessor) {
- this.beanFactoryPostProcessors.add(beanFactoryPostProcessor);
+ public void addBeanFactoryPostProcessor(BeanFactoryPostProcessor postProcessor) {
+ Assert.notNull(postProcessor, "BeanFactoryPostProcessor must not be null");
+ this.beanFactoryPostProcessors.add(postProcessor);
}
@@ -476,6 +478,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
@Override
public void addApplicationListener(ApplicationListener<?> listener) {
+ Assert.notNull(listener, "ApplicationListener must not be null");
if (this.applicationEventMulticaster != null) {
this.applicationEventMulticaster.addApplicationListener(listener);
}
@@ -676,6 +679,13 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
*/
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
+
+ // Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
+ // (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
+ if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
+ beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
+ beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
+ }
}
/**
@@ -823,6 +833,18 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
}
+ // Register a default embedded value resolver if no bean post-processor
+ // (such as a PropertyPlaceholderConfigurer bean) registered any before:
+ // at this point, primarily for resolution in annotation attribute values.
+ if (!beanFactory.hasEmbeddedValueResolver()) {
+ beanFactory.addEmbeddedValueResolver(new StringValueResolver() {
+ @Override
+ public String resolveStringValue(String strVal) {
+ return getEnvironment().resolvePlaceholders(strVal);
+ }
+ });
+ }
+
// Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
for (String weaverAwareName : weaverAwareNames) {
diff --git a/spring-context/src/main/java/org/springframework/context/support/AbstractResourceBasedMessageSource.java b/spring-context/src/main/java/org/springframework/context/support/AbstractResourceBasedMessageSource.java
new file mode 100644
index 00000000..404af2eb
--- /dev/null
+++ b/spring-context/src/main/java/org/springframework/context/support/AbstractResourceBasedMessageSource.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.context.support;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * Abstract base class for {@code MessageSource} implementations based on
+ * resource bundle conventions, such as {@link ResourceBundleMessageSource}
+ * and {@link ReloadableResourceBundleMessageSource}. Provides common
+ * configuration methods and corresponding semantic definitions.
+ *
+ * @author Juergen Hoeller
+ * @since 4.3
+ * @see ResourceBundleMessageSource
+ * @see ReloadableResourceBundleMessageSource
+ */
+public abstract class AbstractResourceBasedMessageSource extends AbstractMessageSource {
+
+ private final Set<String> basenameSet = new LinkedHashSet<String>(4);
+
+ private String defaultEncoding;
+
+ private boolean fallbackToSystemLocale = true;
+
+ private long cacheMillis = -1;
+
+
+ /**
+ * Set a single basename, following the basic ResourceBundle convention
+ * of not specifying file extension or language codes. The resource location
+ * format is up to the specific {@code MessageSource} implementation.
+ * <p>Regular and XMl properties files are supported: e.g. "messages" will find
+ * a "messages.properties", "messages_en.properties" etc arrangement as well
+ * as "messages.xml", "messages_en.xml" etc.
+ * @param basename the single basename
+ * @see #setBasenames
+ * @see org.springframework.core.io.ResourceEditor
+ * @see java.util.ResourceBundle
+ */
+ public void setBasename(String basename) {
+ setBasenames(basename);
+ }
+
+ /**
+ * Set an array of basenames, each following the basic ResourceBundle convention
+ * of not specifying file extension or language codes. The resource location
+ * format is up to the specific {@code MessageSource} implementation.
+ * <p>Regular and XMl properties files are supported: e.g. "messages" will find
+ * a "messages.properties", "messages_en.properties" etc arrangement as well
+ * as "messages.xml", "messages_en.xml" etc.
+ * <p>The associated resource bundles will be checked sequentially when resolving
+ * a message code. Note that message definitions in a <i>previous</i> resource
+ * bundle will override ones in a later bundle, due to the sequential lookup.
+ * <p>Note: In contrast to {@link #addBasenames}, this replaces existing entries
+ * with the given names and can therefore also be used to reset the configuration.
+ * @param basenames an array of basenames
+ * @see #setBasename
+ * @see java.util.ResourceBundle
+ */
+ public void setBasenames(String... basenames) {
+ this.basenameSet.clear();
+ addBasenames(basenames);
+ }
+
+ /**
+ * Add the specified basenames to the existing basename configuration.
+ * <p>Note: If a given basename already exists, the position of its entry
+ * will remain as in the original set. New entries will be added at the
+ * end of the list, to be searched after existing basenames.
+ * @since 4.3
+ * @see #setBasenames
+ * @see java.util.ResourceBundle
+ */
+ public void addBasenames(String... basenames) {
+ if (!ObjectUtils.isEmpty(basenames)) {
+ for (String basename : basenames) {
+ Assert.hasText(basename, "Basename must not be empty");
+ this.basenameSet.add(basename.trim());
+ }
+ }
+ }
+
+ /**
+ * Return this {@code MessageSource}'s basename set, containing entries
+ * in the order of registration.
+ * <p>Calling code may introspect this set as well as add or remove entries.
+ * @since 4.3
+ * @see #addBasenames
+ */
+ public Set<String> getBasenameSet() {
+ return this.basenameSet;
+ }
+
+ /**
+ * Set the default charset to use for parsing properties files.
+ * Used if no file-specific charset is specified for a file.
+ * <p>Default is none, using the {@code java.util.Properties}
+ * default encoding: ISO-8859-1.
+ * <p>Only applies to classic properties files, not to XML files.
+ * @param defaultEncoding the default charset
+ */
+ public void setDefaultEncoding(String defaultEncoding) {
+ this.defaultEncoding = defaultEncoding;
+ }
+
+ /**
+ * Return the default charset to use for parsing properties files, if any.
+ * @since 4.3
+ */
+ protected String getDefaultEncoding() {
+ return this.defaultEncoding;
+ }
+
+ /**
+ * Set whether to fall back to the system Locale if no files for a specific
+ * Locale have been found. Default is "true"; if this is turned off, the only
+ * fallback will be the default file (e.g. "messages.properties" for
+ * basename "messages").
+ * <p>Falling back to the system Locale is the default behavior of
+ * {@code java.util.ResourceBundle}. However, this is often not desirable
+ * in an application server environment, where the system Locale is not relevant
+ * to the application at all: set this flag to "false" in such a scenario.
+ */
+ public void setFallbackToSystemLocale(boolean fallbackToSystemLocale) {
+ this.fallbackToSystemLocale = fallbackToSystemLocale;
+ }
+
+ /**
+ * Return whether to fall back to the system Locale if no files for a specific
+ * Locale have been found.
+ * @since 4.3
+ */
+ protected boolean isFallbackToSystemLocale() {
+ return this.fallbackToSystemLocale;
+ }
+
+ /**
+ * Set the number of seconds to cache loaded properties files.
+ * <ul>
+ * <li>Default is "-1", indicating to cache forever (just like
+ * {@code java.util.ResourceBundle}).
+ * <li>A positive number will cache loaded properties files for the given
+ * number of seconds. This is essentially the interval between refresh checks.
+ * Note that a refresh attempt will first check the last-modified timestamp
+ * of the file before actually reloading it; so if files don't change, this
+ * interval can be set rather low, as refresh attempts will not actually reload.
+ * <li>A value of "0" will check the last-modified timestamp of the file on
+ * every message access. <b>Do not use this in a production environment!</b>
+ * </ul>
+ * <p><b>Note that depending on your ClassLoader, expiration might not work reliably
+ * since the ClassLoader may hold on to a cached version of the bundle file.</b>
+ * Prefer {@link ReloadableResourceBundleMessageSource} over
+ * {@link ResourceBundleMessageSource} in such a scenario, in combination with
+ * a non-classpath location.
+ */
+ public void setCacheSeconds(int cacheSeconds) {
+ this.cacheMillis = (cacheSeconds * 1000);
+ }
+
+ /**
+ * Set the number of milliseconds to cache loaded properties files.
+ * Note that it is common to set seconds instead: {@link #setCacheSeconds}.
+ * <ul>
+ * <li>Default is "-1", indicating to cache forever (just like
+ * {@code java.util.ResourceBundle}).
+ * <li>A positive number will cache loaded properties files for the given
+ * number of milliseconds. This is essentially the interval between refresh checks.
+ * Note that a refresh attempt will first check the last-modified timestamp
+ * of the file before actually reloading it; so if files don't change, this
+ * interval can be set rather low, as refresh attempts will not actually reload.
+ * <li>A value of "0" will check the last-modified timestamp of the file on
+ * every message access. <b>Do not use this in a production environment!</b>
+ * </ul>
+ * @since 4.3
+ * @see #setCacheSeconds
+ */
+ public void setCacheMillis(long cacheMillis) {
+ this.cacheMillis = cacheMillis;
+ }
+
+ /**
+ * Return the number of milliseconds to cache loaded properties files.
+ * @since 4.3
+ */
+ protected long getCacheMillis() {
+ return this.cacheMillis;
+ }
+
+}
diff --git a/spring-context/src/main/java/org/springframework/context/support/ApplicationContextAwareProcessor.java b/spring-context/src/main/java/org/springframework/context/support/ApplicationContextAwareProcessor.java
index 2a521b3b..c3f3dc54 100644
--- a/spring-context/src/main/java/org/springframework/context/support/ApplicationContextAwareProcessor.java
+++ b/spring-context/src/main/java/org/springframework/context/support/ApplicationContextAwareProcessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,7 +23,7 @@ import java.security.PrivilegedAction;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.Aware;
import org.springframework.beans.factory.config.BeanPostProcessor;
-import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.beans.factory.config.EmbeddedValueResolver;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.ConfigurableApplicationContext;
@@ -61,12 +61,15 @@ class ApplicationContextAwareProcessor implements BeanPostProcessor {
private final ConfigurableApplicationContext applicationContext;
+ private final StringValueResolver embeddedValueResolver;
+
/**
* Create a new ApplicationContextAwareProcessor for the given context.
*/
public ApplicationContextAwareProcessor(ConfigurableApplicationContext applicationContext) {
this.applicationContext = applicationContext;
+ this.embeddedValueResolver = new EmbeddedValueResolver(applicationContext.getBeanFactory());
}
@@ -103,8 +106,7 @@ class ApplicationContextAwareProcessor implements BeanPostProcessor {
((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
}
if (bean instanceof EmbeddedValueResolverAware) {
- ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(
- new EmbeddedValueResolver(this.applicationContext.getBeanFactory()));
+ ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
}
if (bean instanceof ResourceLoaderAware) {
((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
@@ -126,19 +128,4 @@ class ApplicationContextAwareProcessor implements BeanPostProcessor {
return bean;
}
-
- private static class EmbeddedValueResolver implements StringValueResolver {
-
- private final ConfigurableBeanFactory beanFactory;
-
- public EmbeddedValueResolver(ConfigurableBeanFactory beanFactory) {
- this.beanFactory = beanFactory;
- }
-
- @Override
- public String resolveStringValue(String strVal) {
- return this.beanFactory.resolveEmbeddedValue(strVal);
- }
- }
-
}
diff --git a/spring-context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java b/spring-context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java
index 9419e14b..521d273f 100644
--- a/spring-context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java
+++ b/spring-context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java
@@ -292,7 +292,7 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor
* <p>The default implementation checks for the {@link Phased} interface.
* Can be overridden to apply other/further policies.
* @param bean the bean to introspect
- * @return the phase an an integer value. The suggested default is 0.
+ * @return the phase an integer value. The suggested default is 0.
* @see Phased
* @see SmartLifecycle
*/
diff --git a/spring-context/src/main/java/org/springframework/context/support/PostProcessorRegistrationDelegate.java b/spring-context/src/main/java/org/springframework/context/support/PostProcessorRegistrationDelegate.java
index 70fd2e83..0557b6bd 100644
--- a/spring-context/src/main/java/org/springframework/context/support/PostProcessorRegistrationDelegate.java
+++ b/spring-context/src/main/java/org/springframework/context/support/PostProcessorRegistrationDelegate.java
@@ -409,6 +409,11 @@ class PostProcessorRegistrationDelegate {
multicaster.removeApplicationListenerBean(beanName);
}
}
+
+ @Override
+ public boolean requiresDestruction(Object bean) {
+ return (bean instanceof ApplicationListener);
+ }
}
}
diff --git a/spring-context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java b/spring-context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java
index 577eb93f..4a12a31e 100644
--- a/spring-context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java
+++ b/spring-context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -167,9 +167,12 @@ public class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerS
StringValueResolver valueResolver = new StringValueResolver() {
@Override
public String resolveStringValue(String strVal) {
- String resolved = ignoreUnresolvablePlaceholders ?
+ String resolved = (ignoreUnresolvablePlaceholders ?
propertyResolver.resolvePlaceholders(strVal) :
- propertyResolver.resolveRequiredPlaceholders(strVal);
+ propertyResolver.resolveRequiredPlaceholders(strVal));
+ if (trimValues) {
+ resolved = resolved.trim();
+ }
return (resolved.equals(nullValue) ? null : resolved);
}
};
diff --git a/spring-context/src/main/java/org/springframework/context/support/ReloadableResourceBundleMessageSource.java b/spring-context/src/main/java/org/springframework/context/support/ReloadableResourceBundleMessageSource.java
index 73de21f4..92fecb1a 100644
--- a/spring-context/src/main/java/org/springframework/context/support/ReloadableResourceBundleMessageSource.java
+++ b/spring-context/src/main/java/org/springframework/context/support/ReloadableResourceBundleMessageSource.java
@@ -33,7 +33,6 @@ import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
-import org.springframework.util.Assert;
import org.springframework.util.DefaultPropertiesPersister;
import org.springframework.util.PropertiesPersister;
import org.springframework.util.StringUtils;
@@ -58,14 +57,14 @@ import org.springframework.util.StringUtils;
* prefix, resources can still be loaded from the classpath, but "cacheSeconds" values
* other than "-1" (caching forever) might not work reliably in this case.
*
- * <p>For a typical web application, message files could be placed into {@code WEB-INF}:
- * e.g. a "WEB-INF/messages" basename would fine a "WEB-INF/messages.properties",
+ * <p>For a typical web application, message files could be placed in {@code WEB-INF}:
+ * e.g. a "WEB-INF/messages" basename would find a "WEB-INF/messages.properties",
* "WEB-INF/messages_en.properties" etc arrangement as well as "WEB-INF/messages.xml",
* "WEB-INF/messages_en.xml" etc. Note that message definitions in a <i>previous</i>
* resource bundle will override ones in a later bundle, due to sequential lookup.
* <p>This MessageSource can easily be used outside of an
- * {@link org.springframework.context.ApplicationContext}: It will use a
+ * {@link org.springframework.context.ApplicationContext}: it will use a
* {@link org.springframework.core.io.DefaultResourceLoader} as default,
* simply getting overridden with the ApplicationContext's resource loader
* if running in a context. It does not have any other specific dependencies.
@@ -85,23 +84,15 @@ import org.springframework.util.StringUtils;
* @see ResourceBundleMessageSource
* @see java.util.ResourceBundle
*/
-public class ReloadableResourceBundleMessageSource extends AbstractMessageSource implements ResourceLoaderAware {
+public class ReloadableResourceBundleMessageSource extends AbstractResourceBasedMessageSource implements ResourceLoaderAware {
private static final String PROPERTIES_SUFFIX = ".properties";
private static final String XML_SUFFIX = ".xml";
- private String[] basenames = new String[0];
-
- private String defaultEncoding;
-
private Properties fileEncodings;
- private boolean fallbackToSystemLocale = true;
-
- private long cacheMillis = -1;
-
private boolean concurrentRefresh = true;
private PropertiesPersister propertiesPersister = new DefaultPropertiesPersister();
@@ -122,71 +113,11 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource
/**
- * Set a single basename, following the basic ResourceBundle convention of
- * not specifying file extension or language codes, but in contrast to
- * {@link ResourceBundleMessageSource} referring to a Spring resource location:
- * e.g. "WEB-INF/messages" for "WEB-INF/messages.properties",
- * "WEB-INF/messages_en.properties", etc.
- * <p>XML properties files are also supported: .g. "WEB-INF/messages" will find
- * and load "WEB-INF/messages.xml", "WEB-INF/messages_en.xml", etc as well.
- * @param basename the single basename
- * @see #setBasenames
- * @see org.springframework.core.io.ResourceEditor
- * @see java.util.ResourceBundle
- */
- public void setBasename(String basename) {
- setBasenames(basename);
- }
-
- /**
- * Set an array of basenames, each following the basic ResourceBundle convention
- * of not specifying file extension or language codes, but in contrast to
- * {@link ResourceBundleMessageSource} referring to a Spring resource location:
- * e.g. "WEB-INF/messages" for "WEB-INF/messages.properties",
- * "WEB-INF/messages_en.properties", etc.
- * <p>XML properties files are also supported: .g. "WEB-INF/messages" will find
- * and load "WEB-INF/messages.xml", "WEB-INF/messages_en.xml", etc as well.
- * <p>The associated resource bundles will be checked sequentially when resolving
- * a message code. Note that message definitions in a <i>previous</i> resource
- * bundle will override ones in a later bundle, due to the sequential lookup.
- * @param basenames an array of basenames
- * @see #setBasename
- * @see java.util.ResourceBundle
- */
- public void setBasenames(String... basenames) {
- if (basenames != null) {
- this.basenames = new String[basenames.length];
- for (int i = 0; i < basenames.length; i++) {
- String basename = basenames[i];
- Assert.hasText(basename, "Basename must not be empty");
- this.basenames[i] = basename.trim();
- }
- }
- else {
- this.basenames = new String[0];
- }
- }
-
- /**
- * Set the default charset to use for parsing properties files.
- * Used if no file-specific charset is specified for a file.
- * <p>Default is none, using the {@code java.util.Properties}
- * default encoding: ISO-8859-1.
- * <p>Only applies to classic properties files, not to XML files.
- * @param defaultEncoding the default charset
- * @see #setFileEncodings
- * @see org.springframework.util.PropertiesPersister#load
- */
- public void setDefaultEncoding(String defaultEncoding) {
- this.defaultEncoding = defaultEncoding;
- }
-
- /**
* Set per-file charsets to use for parsing properties files.
* <p>Only applies to classic properties files, not to XML files.
* @param fileEncodings Properties with filenames as keys and charset
* names as values. Filenames have to match the basename syntax,
- * with optional locale-specific appendices: e.g. "WEB-INF/messages"
+ * with optional locale-specific components: e.g. "WEB-INF/messages"
* or "WEB-INF/messages_en".
* @see #setBasenames
* @see org.springframework.util.PropertiesPersister#load
@@ -196,43 +127,11 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource
}
/**
- * Set whether to fall back to the system Locale if no files for a specific
- * Locale have been found. Default is "true"; if this is turned off, the only
- * fallback will be the default file (e.g. "messages.properties" for
- * basename "messages").
- * <p>Falling back to the system Locale is the default behavior of
- * {@code java.util.ResourceBundle}. However, this is often not desirable
- * in an application server environment, where the system Locale is not relevant
- * to the application at all: Set this flag to "false" in such a scenario.
- */
- public void setFallbackToSystemLocale(boolean fallbackToSystemLocale) {
- this.fallbackToSystemLocale = fallbackToSystemLocale;
- }
-
- /**
- * Set the number of seconds to cache loaded properties files.
- * <ul>
- * <li>Default is "-1", indicating to cache forever (just like
- * {@code java.util.ResourceBundle}).
- * <li>A positive number will cache loaded properties files for the given
- * number of seconds. This is essentially the interval between refresh checks.
- * Note that a refresh attempt will first check the last-modified timestamp
- * of the file before actually reloading it; so if files don't change, this
- * interval can be set rather low, as refresh attempts will not actually reload.
- * <li>A value of "0" will check the last-modified timestamp of the file on
- * every message access. <b>Do not use this in a production environment!</b>
- * </ul>
- */
- public void setCacheSeconds(int cacheSeconds) {
- this.cacheMillis = (cacheSeconds * 1000);
- }
-
- /**
* Specify whether to allow for concurrent refresh behavior, i.e. one thread
* locked in a refresh attempt for a specific cached properties file whereas
* other threads keep returning the old properties for the time being, until
* the refresh attempt has completed.
- * <p>Default is "true": This behavior is new as of Spring Framework 4.1,
+ * <p>Default is "true": this behavior is new as of Spring Framework 4.1,
* minimizing contention between threads. If you prefer the old behavior,
* i.e. to fully block on refresh, switch this flag to "false".
* @since 4.1
@@ -273,7 +172,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource
*/
@Override
protected String resolveCodeWithoutArguments(String code, Locale locale) {
- if (this.cacheMillis < 0) {
+ if (getCacheMillis() < 0) {
PropertiesHolder propHolder = getMergedProperties(locale);
String result = propHolder.getProperty(code);
if (result != null) {
@@ -281,7 +180,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource
}
}
else {
- for (String basename : this.basenames) {
+ for (String basename : getBasenameSet()) {
List<String> filenames = calculateAllFilenames(basename, locale);
for (String filename : filenames) {
PropertiesHolder propHolder = getProperties(filename);
@@ -301,7 +200,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource
*/
@Override
protected MessageFormat resolveCode(String code, Locale locale) {
- if (this.cacheMillis < 0) {
+ if (getCacheMillis() < 0) {
PropertiesHolder propHolder = getMergedProperties(locale);
MessageFormat result = propHolder.getMessageFormat(code, locale);
if (result != null) {
@@ -309,7 +208,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource
}
}
else {
- for (String basename : this.basenames) {
+ for (String basename : getBasenameSet()) {
List<String> filenames = calculateAllFilenames(basename, locale);
for (String filename : filenames) {
PropertiesHolder propHolder = getProperties(filename);
@@ -339,8 +238,9 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource
}
Properties mergedProps = newProperties();
mergedHolder = new PropertiesHolder(mergedProps, -1);
- for (int i = this.basenames.length - 1; i >= 0; i--) {
- List<String> filenames = calculateAllFilenames(this.basenames[i], locale);
+ String[] basenames = StringUtils.toStringArray(getBasenameSet());
+ for (int i = basenames.length - 1; i >= 0; i--) {
+ List<String> filenames = calculateAllFilenames(basenames[i], locale);
for (int j = filenames.size() - 1; j >= 0; j--) {
String filename = filenames.get(j);
PropertiesHolder propHolder = getProperties(filename);
@@ -376,7 +276,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource
}
List<String> filenames = new ArrayList<String>(7);
filenames.addAll(calculateFilenamesForLocale(basename, locale));
- if (this.fallbackToSystemLocale && !locale.equals(Locale.getDefault())) {
+ if (isFallbackToSystemLocale() && !locale.equals(Locale.getDefault())) {
List<String> fallbackFilenames = calculateFilenamesForLocale(basename, Locale.getDefault());
for (String fallbackFilename : fallbackFilenames) {
if (!filenames.contains(fallbackFilename)) {
@@ -447,7 +347,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource
if (propHolder != null) {
originalTimestamp = propHolder.getRefreshTimestamp();
- if (originalTimestamp == -1 || originalTimestamp > System.currentTimeMillis() - this.cacheMillis) {
+ if (originalTimestamp == -1 || originalTimestamp > System.currentTimeMillis() - getCacheMillis()) {
// Up to date
return propHolder;
}
@@ -492,7 +392,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource
* @param propHolder the current PropertiesHolder for the bundle
*/
protected PropertiesHolder refreshProperties(String filename, PropertiesHolder propHolder) {
- long refreshTimestamp = (this.cacheMillis < 0 ? -1 : System.currentTimeMillis());
+ long refreshTimestamp = (getCacheMillis() < 0 ? -1 : System.currentTimeMillis());
Resource resource = this.resourceLoader.getResource(filename + PROPERTIES_SUFFIX);
if (!resource.exists()) {
@@ -501,7 +401,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource
if (resource.exists()) {
long fileTimestamp = -1;
- if (this.cacheMillis >= 0) {
+ if (getCacheMillis() >= 0) {
// Last-modified timestamp of file will just be read if caching with timeout.
try {
fileTimestamp = resource.lastModified();
@@ -571,7 +471,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource
encoding = this.fileEncodings.getProperty(filename);
}
if (encoding == null) {
- encoding = this.defaultEncoding;
+ encoding = getDefaultEncoding();
}
if (encoding != null) {
if (logger.isDebugEnabled()) {
@@ -631,7 +531,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource
@Override
public String toString() {
- return getClass().getName() + ": basenames=[" + StringUtils.arrayToCommaDelimitedString(this.basenames) + "]";
+ return getClass().getName() + ": basenames=" + getBasenameSet();
}
diff --git a/spring-context/src/main/java/org/springframework/context/support/ResourceBundleMessageSource.java b/spring-context/src/main/java/org/springframework/context/support/ResourceBundleMessageSource.java
index 1f623674..d7dce2b5 100644
--- a/spring-context/src/main/java/org/springframework/context/support/ResourceBundleMessageSource.java
+++ b/spring-context/src/main/java/org/springframework/context/support/ResourceBundleMessageSource.java
@@ -32,11 +32,10 @@ import java.util.Map;
import java.util.MissingResourceException;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
+import java.util.Set;
import org.springframework.beans.factory.BeanClassLoaderAware;
-import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
-import org.springframework.util.StringUtils;
/**
* {@link org.springframework.context.MessageSource} implementation that
@@ -64,15 +63,7 @@ import org.springframework.util.StringUtils;
* @see java.util.ResourceBundle
* @see java.text.MessageFormat
*/
-public class ResourceBundleMessageSource extends AbstractMessageSource implements BeanClassLoaderAware {
-
- private String[] basenames = new String[0];
-
- private String defaultEncoding = "ISO-8859-1";
-
- private boolean fallbackToSystemLocale = true;
-
- private long cacheMillis = -1;
+public class ResourceBundleMessageSource extends AbstractResourceBasedMessageSource implements BeanClassLoaderAware {
private ClassLoader bundleClassLoader;
@@ -101,102 +92,6 @@ public class ResourceBundleMessageSource extends AbstractMessageSource implement
/**
- * Set a single basename, following {@link java.util.ResourceBundle} conventions:
- * essentially, a fully-qualified classpath location. If it doesn't contain a
- * package qualifier (such as {@code org.mypackage}), it will be resolved
- * from the classpath root.
- * <p>Messages will normally be held in the "/lib" or "/classes" directory of
- * a web application's WAR structure. They can also be held in jar files on
- * the class path.
- * <p>Note that ResourceBundle names are effectively classpath locations: As a
- * consequence, the JDK's standard ResourceBundle treats dots as package separators.
- * This means that "test.theme" is effectively equivalent to "test/theme",
- * just like it is for programmatic {@code java.util.ResourceBundle} usage.
- * @see #setBasenames
- * @see java.util.ResourceBundle#getBundle(String)
- */
- public void setBasename(String basename) {
- setBasenames(basename);
- }
-
- /**
- * Set an array of basenames, each following {@link java.util.ResourceBundle}
- * conventions: essentially, a fully-qualified classpath location. If it
- * doesn't contain a package qualifier (such as {@code org.mypackage}),
- * it will be resolved from the classpath root.
- * <p>The associated resource bundles will be checked sequentially
- * when resolving a message code. Note that message definitions in a
- * <i>previous</i> resource bundle will override ones in a later bundle,
- * due to the sequential lookup.
- * <p>Note that ResourceBundle names are effectively classpath locations: As a
- * consequence, the JDK's standard ResourceBundle treats dots as package separators.
- * This means that "test.theme" is effectively equivalent to "test/theme",
- * just like it is for programmatic {@code java.util.ResourceBundle} usage.
- * @see #setBasename
- * @see java.util.ResourceBundle#getBundle(String)
- */
- public void setBasenames(String... basenames) {
- if (basenames != null) {
- this.basenames = new String[basenames.length];
- for (int i = 0; i < basenames.length; i++) {
- String basename = basenames[i];
- Assert.hasText(basename, "Basename must not be empty");
- this.basenames[i] = basename.trim();
- }
- }
- else {
- this.basenames = new String[0];
- }
- }
-
- /**
- * Set the default charset to use for parsing resource bundle files.
- * <p>Default is the {@code java.util.ResourceBundle} default encoding:
- * ISO-8859-1.
- * @since 3.1.3
- */
- public void setDefaultEncoding(String defaultEncoding) {
- this.defaultEncoding = defaultEncoding;
- }
-
- /**
- * Set whether to fall back to the system Locale if no files for a specific
- * Locale have been found. Default is "true"; if this is turned off, the only
- * fallback will be the default file (e.g. "messages.properties" for
- * basename "messages").
- * <p>Falling back to the system Locale is the default behavior of
- * {@code java.util.ResourceBundle}. However, this is often not desirable
- * in an application server environment, where the system Locale is not relevant
- * to the application at all: Set this flag to "false" in such a scenario.
- * @since 3.1.3
- */
- public void setFallbackToSystemLocale(boolean fallbackToSystemLocale) {
- this.fallbackToSystemLocale = fallbackToSystemLocale;
- }
-
- /**
- * Set the number of seconds to cache loaded resource bundle files.
- * <ul>
- * <li>Default is "-1", indicating to cache forever.
- * <li>A positive number will expire resource bundles after the given
- * number of seconds. This is essentially the interval between refresh checks.
- * Note that a refresh attempt will first check the last-modified timestamp
- * of the file before actually reloading it; so if files don't change, this
- * interval can be set rather low, as refresh attempts will not actually reload.
- * <li>A value of "0" will check the last-modified timestamp of the file on
- * every message access. <b>Do not use this in a production environment!</b>
- * <li><b>Note that depending on your ClassLoader, expiration might not work reliably
- * since the ClassLoader may hold on to a cached version of the bundle file.</b>
- * Consider {@link ReloadableResourceBundleMessageSource} in combination
- * with resource bundle files in a non-classpath location.
- * </ul>
- * @since 3.1.3
- */
- public void setCacheSeconds(int cacheSeconds) {
- this.cacheMillis = (cacheSeconds * 1000);
- }
-
- /**
* Set the ClassLoader to load resource bundles with.
* <p>Default is the containing BeanFactory's
* {@link org.springframework.beans.factory.BeanClassLoaderAware bean ClassLoader},
@@ -229,14 +124,17 @@ public class ResourceBundleMessageSource extends AbstractMessageSource implement
*/
@Override
protected String resolveCodeWithoutArguments(String code, Locale locale) {
- String result = null;
- for (int i = 0; result == null && i < this.basenames.length; i++) {
- ResourceBundle bundle = getResourceBundle(this.basenames[i], locale);
+ Set<String> basenames = getBasenameSet();
+ for (String basename : basenames) {
+ ResourceBundle bundle = getResourceBundle(basename, locale);
if (bundle != null) {
- result = getStringOrNull(bundle, code);
+ String result = getStringOrNull(bundle, code);
+ if (result != null) {
+ return result;
+ }
}
}
- return result;
+ return null;
}
/**
@@ -245,14 +143,17 @@ public class ResourceBundleMessageSource extends AbstractMessageSource implement
*/
@Override
protected MessageFormat resolveCode(String code, Locale locale) {
- MessageFormat messageFormat = null;
- for (int i = 0; messageFormat == null && i < this.basenames.length; i++) {
- ResourceBundle bundle = getResourceBundle(this.basenames[i], locale);
+ Set<String> basenames = getBasenameSet();
+ for (String basename : basenames) {
+ ResourceBundle bundle = getResourceBundle(basename, locale);
if (bundle != null) {
- messageFormat = getMessageFormat(bundle, code, locale);
+ MessageFormat messageFormat = getMessageFormat(bundle, code, locale);
+ if (messageFormat != null) {
+ return messageFormat;
+ }
}
}
- return messageFormat;
+ return null;
}
@@ -265,7 +166,7 @@ public class ResourceBundleMessageSource extends AbstractMessageSource implement
* found for the given basename and Locale
*/
protected ResourceBundle getResourceBundle(String basename, Locale locale) {
- if (this.cacheMillis >= 0) {
+ if (getCacheMillis() >= 0) {
// Fresh ResourceBundle.getBundle call in order to let ResourceBundle
// do its native caching, at the expense of more extensive lookup steps.
return doGetBundle(basename, locale);
@@ -404,7 +305,7 @@ public class ResourceBundleMessageSource extends AbstractMessageSource implement
*/
@Override
public String toString() {
- return getClass().getName() + ": basenames=[" + StringUtils.arrayToCommaDelimitedString(this.basenames) + "]";
+ return getClass().getName() + ": basenames=" + getBasenameSet();
}
@@ -453,8 +354,12 @@ public class ResourceBundleMessageSource extends AbstractMessageSource implement
throw (IOException) ex.getException();
}
if (stream != null) {
+ String encoding = getDefaultEncoding();
+ if (encoding == null) {
+ encoding = "ISO-8859-1";
+ }
try {
- return loadBundle(new InputStreamReader(stream, defaultEncoding));
+ return loadBundle(new InputStreamReader(stream, encoding));
}
finally {
stream.close();
@@ -472,11 +377,12 @@ public class ResourceBundleMessageSource extends AbstractMessageSource implement
@Override
public Locale getFallbackLocale(String baseName, Locale locale) {
- return (fallbackToSystemLocale ? super.getFallbackLocale(baseName, locale) : null);
+ return (isFallbackToSystemLocale() ? super.getFallbackLocale(baseName, locale) : null);
}
@Override
public long getTimeToLive(String baseName, Locale locale) {
+ long cacheMillis = getCacheMillis();
return (cacheMillis >= 0 ? cacheMillis : super.getTimeToLive(baseName, locale));
}
diff --git a/spring-context/src/main/java/org/springframework/format/annotation/DateTimeFormat.java b/spring-context/src/main/java/org/springframework/format/annotation/DateTimeFormat.java
index c889df80..3c84ff39 100644
--- a/spring-context/src/main/java/org/springframework/format/annotation/DateTimeFormat.java
+++ b/spring-context/src/main/java/org/springframework/format/annotation/DateTimeFormat.java
@@ -75,6 +75,12 @@ public @interface DateTimeFormat {
* <p>Defaults to empty String, indicating no custom pattern String has been specified.
* Set this attribute when you wish to format your field in accordance with a custom
* date time pattern not represented by a style or ISO format.
+ * <p>Note: This pattern follows the original {@link java.text.SimpleDateFormat} style,
+ * as also supported by Joda-Time, with strict parsing semantics towards overflows
+ * (e.g. rejecting a Feb 29 value for a non-leap-year). As a consequence, 'yy'
+ * characters indicate a year in the traditional style, not a "year-of-era" as in the
+ * {@link java.time.format.DateTimeFormatter} specification (i.e. 'yy' turns into 'uu'
+ * when going through that {@code DateTimeFormatter} with strict resolution mode).
*/
String pattern() default "";
diff --git a/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeFormatterFactory.java b/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeFormatterFactory.java
index ca895d85..fb4f50b3 100644
--- a/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeFormatterFactory.java
+++ b/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeFormatterFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.format.datetime.standard;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
+import java.time.format.ResolverStyle;
import java.util.TimeZone;
import org.springframework.format.annotation.DateTimeFormat.ISO;
@@ -174,7 +175,11 @@ public class DateTimeFormatterFactory {
public DateTimeFormatter createDateTimeFormatter(DateTimeFormatter fallbackFormatter) {
DateTimeFormatter dateTimeFormatter = null;
if (StringUtils.hasLength(this.pattern)) {
- dateTimeFormatter = DateTimeFormatter.ofPattern(this.pattern);
+ // Using strict parsing to align with Joda-Time and standard DateFormat behavior:
+ // otherwise, an overflow like e.g. Feb 29 for a non-leap-year wouldn't get rejected.
+ // However, with strict parsing, a year digit needs to be specified as 'u'...
+ String patternToUse = this.pattern.replace("yy", "uu");
+ dateTimeFormatter = DateTimeFormatter.ofPattern(patternToUse).withResolverStyle(ResolverStyle.STRICT);
}
else if (this.iso != null && this.iso != ISO.NONE) {
switch (this.iso) {
diff --git a/spring-context/src/main/java/org/springframework/format/datetime/standard/InstantFormatter.java b/spring-context/src/main/java/org/springframework/format/datetime/standard/InstantFormatter.java
index ee8d039a..9063f82d 100644
--- a/spring-context/src/main/java/org/springframework/format/datetime/standard/InstantFormatter.java
+++ b/spring-context/src/main/java/org/springframework/format/datetime/standard/InstantFormatter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.format.datetime.standard;
import java.text.ParseException;
import java.time.Instant;
+import java.time.format.DateTimeFormatter;
import java.util.Locale;
import org.springframework.format.Formatter;
@@ -26,18 +27,29 @@ import org.springframework.lang.UsesJava8;
/**
* {@link Formatter} implementation for a JSR-310 {@link java.time.Instant},
* following JSR-310's parsing rules for an Instant (that is, not using a
- * configurable {@link java.time.format.DateTimeFormatter}).
+ * configurable {@link java.time.format.DateTimeFormatter}): accepting the
+ * default {@code ISO_INSTANT} format as well as {@code RFC_1123_DATE_TIME}
+ * (which is commonly used for HTTP date header values), as of Spring 4.3.
*
* @author Juergen Hoeller
* @since 4.0
* @see java.time.Instant#parse
+ * @see java.time.format.DateTimeFormatter#ISO_INSTANT
+ * @see java.time.format.DateTimeFormatter#RFC_1123_DATE_TIME
*/
@UsesJava8
public class InstantFormatter implements Formatter<Instant> {
@Override
public Instant parse(String text, Locale locale) throws ParseException {
- return Instant.parse(text);
+ if (text.length() > 0 && Character.isDigit(text.charAt(0))) {
+ // assuming UTC instant a la "2007-12-03T10:15:30.00Z"
+ return Instant.parse(text);
+ }
+ else {
+ // assuming RFC-1123 value a la "Tue, 3 Jun 2008 11:05:30 GMT"
+ return Instant.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(text));
+ }
}
@Override
diff --git a/spring-context/src/main/java/org/springframework/instrument/classloading/ReflectiveLoadTimeWeaver.java b/spring-context/src/main/java/org/springframework/instrument/classloading/ReflectiveLoadTimeWeaver.java
index f4fa2cc6..ac807eee 100644
--- a/spring-context/src/main/java/org/springframework/instrument/classloading/ReflectiveLoadTimeWeaver.java
+++ b/spring-context/src/main/java/org/springframework/instrument/classloading/ReflectiveLoadTimeWeaver.java
@@ -22,6 +22,8 @@ import java.lang.reflect.Method;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import org.springframework.core.DecoratingClassLoader;
+import org.springframework.core.OverridingClassLoader;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
@@ -129,7 +131,10 @@ public class ReflectiveLoadTimeWeaver implements LoadTimeWeaver {
@Override
public ClassLoader getThrowawayClassLoader() {
if (this.getThrowawayClassLoaderMethod != null) {
- return (ClassLoader) ReflectionUtils.invokeMethod(this.getThrowawayClassLoaderMethod, this.classLoader);
+ ClassLoader target = (ClassLoader)
+ ReflectionUtils.invokeMethod(this.getThrowawayClassLoaderMethod, this.classLoader);
+ return (target instanceof DecoratingClassLoader ? target :
+ new OverridingClassLoader(this.classLoader, target));
}
else {
return new SimpleThrowawayClassLoader(this.classLoader);
diff --git a/spring-context/src/main/java/org/springframework/instrument/classloading/glassfish/GlassFishLoadTimeWeaver.java b/spring-context/src/main/java/org/springframework/instrument/classloading/glassfish/GlassFishLoadTimeWeaver.java
index 81222f3f..663a7175 100644
--- a/spring-context/src/main/java/org/springframework/instrument/classloading/glassfish/GlassFishLoadTimeWeaver.java
+++ b/spring-context/src/main/java/org/springframework/instrument/classloading/glassfish/GlassFishLoadTimeWeaver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ import java.lang.instrument.ClassFileTransformer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import org.springframework.core.OverridingClassLoader;
import org.springframework.instrument.classloading.LoadTimeWeaver;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -109,7 +110,7 @@ public class GlassFishLoadTimeWeaver implements LoadTimeWeaver {
@Override
public ClassLoader getThrowawayClassLoader() {
try {
- return (ClassLoader) this.copyMethod.invoke(this.classLoader);
+ return new OverridingClassLoader(this.classLoader, (ClassLoader) this.copyMethod.invoke(this.classLoader));
}
catch (InvocationTargetException ex) {
throw new IllegalStateException("GlassFish copy method threw exception", ex.getCause());
diff --git a/spring-context/src/main/java/org/springframework/instrument/classloading/tomcat/TomcatLoadTimeWeaver.java b/spring-context/src/main/java/org/springframework/instrument/classloading/tomcat/TomcatLoadTimeWeaver.java
index 1ef36c95..fe0f7980 100644
--- a/spring-context/src/main/java/org/springframework/instrument/classloading/tomcat/TomcatLoadTimeWeaver.java
+++ b/spring-context/src/main/java/org/springframework/instrument/classloading/tomcat/TomcatLoadTimeWeaver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,13 +20,14 @@ import java.lang.instrument.ClassFileTransformer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import org.springframework.core.OverridingClassLoader;
import org.springframework.instrument.classloading.LoadTimeWeaver;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
- * {@link org.springframework.instrument.classloading.LoadTimeWeaver} implementation for Tomcat's
- * new {@link org.apache.tomcat.InstrumentableClassLoader InstrumentableClassLoader}.
+ * {@link org.springframework.instrument.classloading.LoadTimeWeaver} implementation
+ * for Tomcat's new {@code org.apache.tomcat.InstrumentableClassLoader}.
* Also capable of handling Spring's TomcatInstrumentableClassLoader when encountered.
*
* @author Juergen Hoeller
@@ -103,7 +104,7 @@ public class TomcatLoadTimeWeaver implements LoadTimeWeaver {
@Override
public ClassLoader getThrowawayClassLoader() {
try {
- return (ClassLoader) this.copyMethod.invoke(this.classLoader);
+ return new OverridingClassLoader(this.classLoader, (ClassLoader) this.copyMethod.invoke(this.classLoader));
}
catch (InvocationTargetException ex) {
throw new IllegalStateException("Tomcat copy method threw exception", ex.getCause());
diff --git a/spring-context/src/main/java/org/springframework/instrument/classloading/weblogic/WebLogicLoadTimeWeaver.java b/spring-context/src/main/java/org/springframework/instrument/classloading/weblogic/WebLogicLoadTimeWeaver.java
index 46622173..f4ebb47a 100644
--- a/spring-context/src/main/java/org/springframework/instrument/classloading/weblogic/WebLogicLoadTimeWeaver.java
+++ b/spring-context/src/main/java/org/springframework/instrument/classloading/weblogic/WebLogicLoadTimeWeaver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.instrument.classloading.weblogic;
import java.lang.instrument.ClassFileTransformer;
+import org.springframework.core.OverridingClassLoader;
import org.springframework.instrument.classloading.LoadTimeWeaver;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -70,6 +71,8 @@ public class WebLogicLoadTimeWeaver implements LoadTimeWeaver {
@Override
public ClassLoader getThrowawayClassLoader() {
- return this.classLoader.getThrowawayClassLoader();
+ return new OverridingClassLoader(this.classLoader.getClassLoader(),
+ this.classLoader.getThrowawayClassLoader());
}
+
}
diff --git a/spring-context/src/main/java/org/springframework/instrument/classloading/websphere/WebSphereLoadTimeWeaver.java b/spring-context/src/main/java/org/springframework/instrument/classloading/websphere/WebSphereLoadTimeWeaver.java
index 55c6f863..aeed6929 100644
--- a/spring-context/src/main/java/org/springframework/instrument/classloading/websphere/WebSphereLoadTimeWeaver.java
+++ b/spring-context/src/main/java/org/springframework/instrument/classloading/websphere/WebSphereLoadTimeWeaver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.instrument.classloading.websphere;
import java.lang.instrument.ClassFileTransformer;
+import org.springframework.core.OverridingClassLoader;
import org.springframework.instrument.classloading.LoadTimeWeaver;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -67,7 +68,8 @@ public class WebSphereLoadTimeWeaver implements LoadTimeWeaver {
@Override
public ClassLoader getThrowawayClassLoader() {
- return this.classLoader.getThrowawayClassLoader();
+ return new OverridingClassLoader(this.classLoader.getClassLoader(),
+ this.classLoader.getThrowawayClassLoader());
}
}
diff --git a/spring-context/src/main/java/org/springframework/jmx/access/MBeanClientInterceptor.java b/spring-context/src/main/java/org/springframework/jmx/access/MBeanClientInterceptor.java
index fb59a362..9d7c6777 100644
--- a/spring-context/src/main/java/org/springframework/jmx/access/MBeanClientInterceptor.java
+++ b/spring-context/src/main/java/org/springframework/jmx/access/MBeanClientInterceptor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -63,6 +63,7 @@ import org.springframework.jmx.support.JmxUtils;
import org.springframework.jmx.support.ObjectNameManager;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
+import org.springframework.util.StringUtils;
/**
* {@link org.aopalliance.intercept.MethodInterceptor} that routes calls to an
@@ -609,7 +610,7 @@ public class MBeanClientInterceptor
* Simple wrapper class around a method name and its signature.
* Used as the key when caching methods.
*/
- private static class MethodCacheKey {
+ private static final class MethodCacheKey implements Comparable<MethodCacheKey> {
private final String name;
@@ -628,7 +629,7 @@ public class MBeanClientInterceptor
@Override
public boolean equals(Object other) {
- if (other == this) {
+ if (this == other) {
return true;
}
MethodCacheKey otherKey = (MethodCacheKey) other;
@@ -639,6 +640,32 @@ public class MBeanClientInterceptor
public int hashCode() {
return this.name.hashCode();
}
+
+ @Override
+ public String toString() {
+ return this.name + "(" + StringUtils.arrayToCommaDelimitedString(this.parameterTypes) + ")";
+ }
+
+ @Override
+ public int compareTo(MethodCacheKey other) {
+ int result = this.name.compareTo(other.name);
+ if (result != 0) {
+ return result;
+ }
+ if (this.parameterTypes.length < other.parameterTypes.length) {
+ return -1;
+ }
+ if (this.parameterTypes.length > other.parameterTypes.length) {
+ return 1;
+ }
+ for (int i = 0; i < this.parameterTypes.length; i++) {
+ result = this.parameterTypes[i].getName().compareTo(other.parameterTypes[i].getName());
+ if (result != 0) {
+ return result;
+ }
+ }
+ return 0;
+ }
}
}
diff --git a/spring-context/src/main/java/org/springframework/jmx/export/MBeanExporter.java b/spring-context/src/main/java/org/springframework/jmx/export/MBeanExporter.java
index 6fc0013e..b9eb86ca 100644
--- a/spring-context/src/main/java/org/springframework/jmx/export/MBeanExporter.java
+++ b/spring-context/src/main/java/org/springframework/jmx/export/MBeanExporter.java
@@ -570,7 +570,7 @@ public class MBeanExporter extends MBeanRegistrationSupport implements MBeanExpo
* should be exposed to the {@code MBeanServer}. Specifically, if the
* supplied {@code mapValue} is the name of a bean that is configured
* for lazy initialization, then a proxy to the resource is registered with
- * the {@code MBeanServer} so that the the lazy load behavior is
+ * the {@code MBeanServer} so that the lazy load behavior is
* honored. If the bean is already an MBean then it will be registered
* directly with the {@code MBeanServer} without any intervention. For
* all other beans or bean names, the resource itself is registered with
diff --git a/spring-context/src/main/java/org/springframework/jmx/export/annotation/AnnotationJmxAttributeSource.java b/spring-context/src/main/java/org/springframework/jmx/export/annotation/AnnotationJmxAttributeSource.java
index 0a802724..23d0d3a5 100644
--- a/spring-context/src/main/java/org/springframework/jmx/export/annotation/AnnotationJmxAttributeSource.java
+++ b/spring-context/src/main/java/org/springframework/jmx/export/annotation/AnnotationJmxAttributeSource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@ package org.springframework.jmx.export.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Set;
@@ -27,6 +28,7 @@ import org.springframework.beans.annotation.AnnotationBeanUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.beans.factory.config.EmbeddedValueResolver;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.jmx.export.metadata.InvalidMetadataException;
import org.springframework.jmx.export.metadata.JmxAttributeSource;
@@ -39,6 +41,7 @@ import org.springframework.util.StringValueResolver;
* @author Rob Harrop
* @author Juergen Hoeller
* @author Jennifer Hickey
+ * @author Stephane Nicoll
* @since 1.2
* @see ManagedResource
* @see ManagedAttribute
@@ -50,25 +53,24 @@ public class AnnotationJmxAttributeSource implements JmxAttributeSource, BeanFac
@Override
- public void setBeanFactory(final BeanFactory beanFactory) {
+ public void setBeanFactory(BeanFactory beanFactory) {
if (beanFactory instanceof ConfigurableBeanFactory) {
- // Not using EmbeddedValueResolverAware in order to avoid a spring-context dependency:
- // ConfigurableBeanFactory and its resolveEmbeddedValue live in the spring-beans module.
- this.embeddedValueResolver = new StringValueResolver() {
- @Override
- public String resolveStringValue(String strVal) {
- return ((ConfigurableBeanFactory) beanFactory).resolveEmbeddedValue(strVal);
- }
- };
+ this.embeddedValueResolver = new EmbeddedValueResolver((ConfigurableBeanFactory) beanFactory);
}
}
+
@Override
public org.springframework.jmx.export.metadata.ManagedResource getManagedResource(Class<?> beanClass) throws InvalidMetadataException {
ManagedResource ann = AnnotationUtils.findAnnotation(beanClass, ManagedResource.class);
if (ann == null) {
return null;
}
+ Class<?> declaringClass = AnnotationUtils.findAnnotationDeclaringClass(ManagedResource.class, beanClass);
+ Class<?> target = (declaringClass != null && !declaringClass.isInterface() ? declaringClass : beanClass);
+ if (!Modifier.isPublic(target.getModifiers())) {
+ throw new InvalidMetadataException("@ManagedResource class '" + target.getName() + "' must be public");
+ }
org.springframework.jmx.export.metadata.ManagedResource managedResource = new org.springframework.jmx.export.metadata.ManagedResource();
AnnotationBeanUtils.copyPropertiesToBean(ann, managedResource, this.embeddedValueResolver);
return managedResource;
@@ -133,7 +135,7 @@ public class AnnotationJmxAttributeSource implements JmxAttributeSource, BeanFac
if (ann == null) {
return null;
}
- T bean = BeanUtils.instantiate(beanClass);
+ T bean = BeanUtils.instantiateClass(beanClass);
AnnotationBeanUtils.copyPropertiesToBean(ann, bean);
return bean;
}
diff --git a/spring-context/src/main/java/org/springframework/jmx/export/annotation/ManagedNotifications.java b/spring-context/src/main/java/org/springframework/jmx/export/annotation/ManagedNotifications.java
index 7c2f2aba..42bedaa4 100644
--- a/spring-context/src/main/java/org/springframework/jmx/export/annotation/ManagedNotifications.java
+++ b/spring-context/src/main/java/org/springframework/jmx/export/annotation/ManagedNotifications.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,7 +25,7 @@ import java.lang.annotation.Target;
/**
* Type-level annotation that indicates JMX notifications emitted by a bean,
- * containing multiple {@link ManagedNotification ManagedNotifications}
+ * containing multiple {@link ManagedNotification ManagedNotifications}.
*
* @author Rob Harrop
* @since 2.0
diff --git a/spring-context/src/main/java/org/springframework/jmx/export/assembler/MethodNameBasedMBeanInfoAssembler.java b/spring-context/src/main/java/org/springframework/jmx/export/assembler/MethodNameBasedMBeanInfoAssembler.java
index 3df5350d..30f5da76 100644
--- a/spring-context/src/main/java/org/springframework/jmx/export/assembler/MethodNameBasedMBeanInfoAssembler.java
+++ b/spring-context/src/main/java/org/springframework/jmx/export/assembler/MethodNameBasedMBeanInfoAssembler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -72,7 +72,7 @@ public class MethodNameBasedMBeanInfoAssembler extends AbstractConfigurableMBean
* @param methodNames an array of method names indicating the methods to use
* @see #setMethodMappings
*/
- public void setManagedMethods(String[] methodNames) {
+ public void setManagedMethods(String... methodNames) {
this.managedMethods = new HashSet<String>(Arrays.asList(methodNames));
}
diff --git a/spring-context/src/main/java/org/springframework/jmx/export/naming/IdentityNamingStrategy.java b/spring-context/src/main/java/org/springframework/jmx/export/naming/IdentityNamingStrategy.java
index 7db52fb8..96626f07 100644
--- a/spring-context/src/main/java/org/springframework/jmx/export/naming/IdentityNamingStrategy.java
+++ b/spring-context/src/main/java/org/springframework/jmx/export/naming/IdentityNamingStrategy.java
@@ -26,7 +26,7 @@ import org.springframework.util.ObjectUtils;
/**
* An implementation of the {@code ObjectNamingStrategy} interface that
- * creates a name based on the the identity of a given instance.
+ * creates a name based on the identity of a given instance.
*
* <p>The resulting {@code ObjectName} will be in the form
* <i>package</i>:class=<i>class name</i>,hashCode=<i>identity hash (in hex)</i>
diff --git a/spring-context/src/main/java/org/springframework/jmx/export/naming/KeyNamingStrategy.java b/spring-context/src/main/java/org/springframework/jmx/export/naming/KeyNamingStrategy.java
index 0f4ed459..5cb99a88 100644
--- a/spring-context/src/main/java/org/springframework/jmx/export/naming/KeyNamingStrategy.java
+++ b/spring-context/src/main/java/org/springframework/jmx/export/naming/KeyNamingStrategy.java
@@ -70,7 +70,7 @@ public class KeyNamingStrategy implements ObjectNamingStrategy, InitializingBean
/**
* Stores the result of merging the {@code mappings} {@code Properties}
- * with the the properties stored in the resources defined by {@code mappingLocations}.
+ * with the properties stored in the resources defined by {@code mappingLocations}.
*/
private Properties mergedMappings;
diff --git a/spring-context/src/main/java/org/springframework/jmx/support/ConnectorServerFactoryBean.java b/spring-context/src/main/java/org/springframework/jmx/support/ConnectorServerFactoryBean.java
index 5a4d05a7..48d8eb7f 100644
--- a/spring-context/src/main/java/org/springframework/jmx/support/ConnectorServerFactoryBean.java
+++ b/spring-context/src/main/java/org/springframework/jmx/support/ConnectorServerFactoryBean.java
@@ -138,7 +138,7 @@ public class ConnectorServerFactoryBean extends MBeanRegistrationSupport
* the {@code JMXConnectorServer} will be started in a separate thread.
* If the {@code daemon} flag is set to {@code true}, that thread will be
* started as a daemon thread.
- * @throws JMException if a problem occured when registering the connector server
+ * @throws JMException if a problem occurred when registering the connector server
* with the {@code MBeanServer}
* @throws IOException if there is a problem starting the connector server
*/
diff --git a/spring-context/src/main/java/org/springframework/jndi/JndiLocatorDelegate.java b/spring-context/src/main/java/org/springframework/jndi/JndiLocatorDelegate.java
index 2aa25520..53aaebb7 100644
--- a/spring-context/src/main/java/org/springframework/jndi/JndiLocatorDelegate.java
+++ b/spring-context/src/main/java/org/springframework/jndi/JndiLocatorDelegate.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,8 @@ package org.springframework.jndi;
import javax.naming.InitialContext;
import javax.naming.NamingException;
+import org.springframework.core.SpringProperties;
+
/**
* {@link JndiLocatorSupport} subclass with public lookup methods,
* for convenient use as a delegate.
@@ -28,6 +30,29 @@ import javax.naming.NamingException;
*/
public class JndiLocatorDelegate extends JndiLocatorSupport {
+ /**
+ * System property that instructs Spring to ignore a default JNDI environment, i.e.
+ * to always return {@code false} from {@link #isDefaultJndiEnvironmentAvailable()}.
+ * <p>The default is "false", allowing for regular default JNDI access e.g. in
+ * {@link JndiPropertySource}. Switching this flag to {@code true} is an optimization
+ * for scenarios where nothing is ever to be found for such JNDI fallback searches
+ * to begin with, avoiding the repeated JNDI lookup overhead.
+ * <p>Note that this flag just affects JNDI fallback searches, not explicitly configured
+ * JNDI lookups such as for a {@code DataSource} or some other environment resource.
+ * The flag literally just affects code which attempts JNDI searches based on the
+ * {@code JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()} check: in particular,
+ * {@code StandardServletEnvironment} and {@code StandardPortletEnvironment}.
+ * @since 4.3
+ * @see #isDefaultJndiEnvironmentAvailable()
+ * @see JndiPropertySource
+ */
+ public static final String IGNORE_JNDI_PROPERTY_NAME = "spring.jndi.ignore";
+
+
+ private static final boolean shouldIgnoreDefaultJndiEnvironment =
+ SpringProperties.getFlag(IGNORE_JNDI_PROPERTY_NAME);
+
+
@Override
public Object lookup(String jndiName) throws NamingException {
return super.lookup(jndiName);
@@ -57,6 +82,9 @@ public class JndiLocatorDelegate extends JndiLocatorSupport {
* {@code false} if not
*/
public static boolean isDefaultJndiEnvironmentAvailable() {
+ if (shouldIgnoreDefaultJndiEnvironment) {
+ return false;
+ }
try {
new InitialContext().getEnvironment();
return true;
diff --git a/spring-context/src/main/java/org/springframework/jndi/JndiObjectFactoryBean.java b/spring-context/src/main/java/org/springframework/jndi/JndiObjectFactoryBean.java
index b86838bd..dc22f0fb 100644
--- a/spring-context/src/main/java/org/springframework/jndi/JndiObjectFactoryBean.java
+++ b/spring-context/src/main/java/org/springframework/jndi/JndiObjectFactoryBean.java
@@ -219,7 +219,7 @@ public class JndiObjectFactoryBean extends JndiObjectLocator
}
/**
- * Lookup variant that that returns the specified "defaultObject"
+ * Lookup variant that returns the specified "defaultObject"
* (if any) in case of lookup failure.
* @return the located object, or the "defaultObject" as fallback
* @throws NamingException in case of lookup failure without fallback
diff --git a/spring-context/src/main/java/org/springframework/jndi/JndiPropertySource.java b/spring-context/src/main/java/org/springframework/jndi/JndiPropertySource.java
index b33fc6d8..e2c40f34 100644
--- a/spring-context/src/main/java/org/springframework/jndi/JndiPropertySource.java
+++ b/spring-context/src/main/java/org/springframework/jndi/JndiPropertySource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -78,6 +78,15 @@ public class JndiPropertySource extends PropertySource<JndiLocatorDelegate> {
*/
@Override
public Object getProperty(String name) {
+ if (getSource().isResourceRef() && name.indexOf(':') != -1) {
+ // We're in resource-ref (prefixing with "java:comp/env") mode. Let's not bother
+ // with property names with a colon it since they're probably just containing a
+ // default value clause, very unlikely to match including the colon part even in
+ // a textual property source, and effectively never meant to match that way in
+ // JNDI where a colon indicates a separator between JNDI scheme and actual name.
+ return null;
+ }
+
try {
Object value = this.source.lookup(name);
if (logger.isDebugEnabled()) {
diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/AnnotationAsyncExecutionInterceptor.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/AnnotationAsyncExecutionInterceptor.java
index 117de48d..7c23ad64 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/annotation/AnnotationAsyncExecutionInterceptor.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/AnnotationAsyncExecutionInterceptor.java
@@ -21,7 +21,7 @@ import java.util.concurrent.Executor;
import org.springframework.aop.interceptor.AsyncExecutionInterceptor;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
-import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.AnnotatedElementUtils;
/**
* Specialization of {@link AsyncExecutionInterceptor} that delegates method execution to
@@ -78,9 +78,9 @@ public class AnnotationAsyncExecutionInterceptor extends AsyncExecutionIntercept
protected String getExecutorQualifier(Method method) {
// Maintainer's note: changes made here should also be made in
// AnnotationAsyncExecutionAspect#getExecutorQualifier
- Async async = AnnotationUtils.findAnnotation(method, Async.class);
+ Async async = AnnotatedElementUtils.findMergedAnnotation(method, Async.class);
if (async == null) {
- async = AnnotationUtils.findAnnotation(method.getDeclaringClass(), Async.class);
+ async = AnnotatedElementUtils.findMergedAnnotation(method.getDeclaringClass(), Async.class);
}
return (async != null ? async.value() : null);
}
diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncResult.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncResult.java
index 6d67ac3e..fdd33a61 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncResult.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncResult.java
@@ -103,18 +103,18 @@ public class AsyncResult<V> implements ListenableFuture<V> {
@Override
public void addCallback(SuccessCallback<? super V> successCallback, FailureCallback failureCallback) {
- if (this.executionException != null) {
- Throwable cause = this.executionException.getCause();
- failureCallback.onFailure(cause != null ? cause : this.executionException);
- }
- else {
- try {
- successCallback.onSuccess(this.value);
+ try {
+ if (this.executionException != null) {
+ Throwable cause = this.executionException.getCause();
+ failureCallback.onFailure(cause != null ? cause : this.executionException);
}
- catch (Throwable ex) {
- failureCallback.onFailure(ex);
+ else {
+ successCallback.onSuccess(this.value);
}
}
+ catch (Throwable ex) {
+ // Ignore
+ }
}
diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java
index d46c7386..045dcd8a 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,18 +24,22 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
- * Annotation that marks a method to be scheduled. Exactly one of the
- * {@link #cron()}, {@link #fixedDelay()}, or {@link #fixedRate()}
+ * An annotation that marks a method to be scheduled. Exactly one of
+ * the {@link #cron()}, {@link #fixedDelay()}, or {@link #fixedRate()}
* attributes must be specified.
*
- * <p>The annotated method must expect no arguments and have a
- * {@code void} return type.
+ * <p>The annotated method must expect no arguments. It will typically have
+ * a {@code void} return type; if not, the returned value will be ignored
+ * when called through the scheduler.
*
* <p>Processing of {@code @Scheduled} annotations is performed by
* registering a {@link ScheduledAnnotationBeanPostProcessor}. This can be
* done manually or, more conveniently, through the {@code <task:annotation-driven/>}
* element or @{@link EnableScheduling} annotation.
*
+ * <p>This annotation may be used as a <em>meta-annotation</em> to create custom
+ * <em>composed annotations</em> with attribute overrides.
+ *
* @author Mark Fisher
* @author Dave Syer
* @author Chris Beams
@@ -72,28 +76,30 @@ public @interface Scheduled {
String zone() default "";
/**
- * Execute the annotated method with a fixed period between the end
- * of the last invocation and the start of the next.
+ * Execute the annotated method with a fixed period in milliseconds between the
+ * end of the last invocation and the start of the next.
* @return the delay in milliseconds
*/
long fixedDelay() default -1;
/**
- * Execute the annotated method with a fixed period between the end
- * of the last invocation and the start of the next.
+ * Execute the annotated method with a fixed period in milliseconds between the
+ * end of the last invocation and the start of the next.
* @return the delay in milliseconds as a String value, e.g. a placeholder
* @since 3.2.2
*/
String fixedDelayString() default "";
/**
- * Execute the annotated method with a fixed period between invocations.
+ * Execute the annotated method with a fixed period in milliseconds between
+ * invocations.
* @return the period in milliseconds
*/
long fixedRate() default -1;
/**
- * Execute the annotated method with a fixed period between invocations.
+ * Execute the annotated method with a fixed period in milliseconds between
+ * invocations.
* @return the period in milliseconds as a String value, e.g. a placeholder
* @since 3.2.2
*/
diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java
index bacd01b5..89d35299 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,8 +17,9 @@
package org.springframework.scheduling.annotation;
import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
+import java.util.Collection;
import java.util.Collections;
+import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
@@ -36,7 +37,7 @@ import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.beans.factory.SmartInitializingSingleton;
-import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
@@ -44,16 +45,16 @@ import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.Ordered;
-import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.config.CronTask;
import org.springframework.scheduling.config.IntervalTask;
+import org.springframework.scheduling.config.ScheduledTask;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.scheduling.support.ScheduledMethodRunnable;
import org.springframework.util.Assert;
-import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.StringValueResolver;
@@ -83,14 +84,15 @@ import org.springframework.util.StringValueResolver;
* @see org.springframework.scheduling.config.ScheduledTaskRegistrar
* @see AsyncAnnotationBeanPostProcessor
*/
-public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor, Ordered,
- EmbeddedValueResolverAware, BeanFactoryAware, ApplicationContextAware,
+public class ScheduledAnnotationBeanPostProcessor implements DestructionAwareBeanPostProcessor,
+ Ordered, EmbeddedValueResolverAware, BeanFactoryAware, ApplicationContextAware,
SmartInitializingSingleton, ApplicationListener<ContextRefreshedEvent>, DisposableBean {
/**
* The default name of the {@link TaskScheduler} bean to pick up: "taskScheduler".
* <p>Note that the initial lookup happens by type; this is just the fallback
* in case of multiple scheduler beans found in the context.
+ * @since 4.2
*/
public static final String DEFAULT_TASK_SCHEDULER_BEAN_NAME = "taskScheduler";
@@ -110,6 +112,9 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor,
private final Set<Class<?>> nonAnnotatedClasses =
Collections.newSetFromMap(new ConcurrentHashMap<Class<?>, Boolean>(64));
+ private final Map<Object, Set<ScheduledTask>> scheduledTasks =
+ new ConcurrentHashMap<Object, Set<ScheduledTask>>(16);
+
@Override
public int getOrder() {
@@ -120,6 +125,12 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor,
* Set the {@link org.springframework.scheduling.TaskScheduler} that will invoke
* the scheduled methods, or a {@link java.util.concurrent.ScheduledExecutorService}
* to be wrapped as a TaskScheduler.
+ * <p>If not specified, default scheduler resolution will apply: searching for a
+ * unique {@link TaskScheduler} bean in the context, or for a {@link TaskScheduler}
+ * bean named "taskScheduler" otherwise; the same lookup will also be performed for
+ * a {@link ScheduledExecutorService} bean. If neither of the two is resolvable,
+ * a local single-threaded default scheduler will be created within the registrar.
+ * @see #DEFAULT_TASK_SCHEDULER_BEAN_NAME
*/
public void setScheduler(Object scheduler) {
this.scheduler = scheduler;
@@ -197,10 +208,13 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor,
this.beanFactory.getBean(DEFAULT_TASK_SCHEDULER_BEAN_NAME, TaskScheduler.class));
}
catch (NoSuchBeanDefinitionException ex2) {
- throw new IllegalStateException("More than one TaskScheduler bean exists within the context, and " +
- "none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' "+
- "(possibly as an alias); or implement the SchedulingConfigurer interface and call " +
- "ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback.", ex);
+ if (logger.isInfoEnabled()) {
+ logger.info("More than one TaskScheduler bean exists within the context, and " +
+ "none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' " +
+ "(possibly as an alias); or implement the SchedulingConfigurer interface and call " +
+ "ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: " +
+ ex.getBeanNamesFound());
+ }
}
}
catch (NoSuchBeanDefinitionException ex) {
@@ -210,14 +224,24 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor,
this.registrar.setScheduler(this.beanFactory.getBean(ScheduledExecutorService.class));
}
catch (NoUniqueBeanDefinitionException ex2) {
- throw new IllegalStateException("More than one ScheduledExecutorService bean exists within " +
- "the context. Mark one of them as primary; or implement the SchedulingConfigurer " +
- "interface and call ScheduledTaskRegistrar#setScheduler explicitly within the " +
- "configureTasks() callback.", ex);
+ try {
+ this.registrar.setScheduler(
+ this.beanFactory.getBean(DEFAULT_TASK_SCHEDULER_BEAN_NAME, ScheduledExecutorService.class));
+ }
+ catch (NoSuchBeanDefinitionException ex3) {
+ if (logger.isInfoEnabled()) {
+ logger.info("More than one ScheduledExecutorService bean exists within the context, and " +
+ "none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' " +
+ "(possibly as an alias); or implement the SchedulingConfigurer interface and call " +
+ "ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: " +
+ ex2.getBeanNamesFound());
+ }
+ }
}
catch (NoSuchBeanDefinitionException ex2) {
- logger.debug("Could not find default ScheduledExecutorService bean", ex);
+ logger.debug("Could not find default ScheduledExecutorService bean", ex2);
// Giving up -> falling back to default scheduler within the registrar...
+ logger.info("No TaskScheduler/ScheduledExecutorService bean found for scheduled processing");
}
}
}
@@ -240,7 +264,7 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor,
@Override
public Set<Scheduled> inspect(Method method) {
Set<Scheduled> scheduledMethods =
- AnnotationUtils.getRepeatableAnnotations(method, Scheduled.class, Schedules.class);
+ AnnotatedElementUtils.getMergedRepeatableAnnotations(method, Scheduled.class, Schedules.class);
return (!scheduledMethods.isEmpty() ? scheduledMethods : null);
}
});
@@ -269,44 +293,21 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor,
protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
try {
- Assert.isTrue(void.class == method.getReturnType(),
- "Only void-returning methods may be annotated with @Scheduled");
Assert.isTrue(method.getParameterTypes().length == 0,
"Only no-arg methods may be annotated with @Scheduled");
- if (AopUtils.isJdkDynamicProxy(bean)) {
- try {
- // Found a @Scheduled method on the target class for this JDK proxy ->
- // is it also present on the proxy itself?
- method = bean.getClass().getMethod(method.getName(), method.getParameterTypes());
- }
- catch (SecurityException ex) {
- ReflectionUtils.handleReflectionException(ex);
- }
- catch (NoSuchMethodException ex) {
- throw new IllegalStateException(String.format(
- "@Scheduled method '%s' found on bean target class '%s' but not " +
- "found in any interface(s) for a dynamic proxy. Either pull the " +
- "method up to a declared interface or switch to subclass (CGLIB) " +
- "proxies by setting proxy-target-class/proxyTargetClass to 'true'.",
- method.getName(), method.getDeclaringClass().getSimpleName()));
- }
- }
- else if (AopUtils.isCglibProxy(bean)) {
- // Common problem: private methods end up in the proxy instance, not getting delegated.
- if (Modifier.isPrivate(method.getModifiers())) {
- throw new IllegalStateException(String.format(
- "@Scheduled method '%s' found on CGLIB proxy for target class '%s' but cannot " +
- "be delegated to target bean. Switch its visibility to package or protected.",
- method.getName(), method.getDeclaringClass().getSimpleName()));
- }
- }
-
- Runnable runnable = new ScheduledMethodRunnable(bean, method);
+ Method invocableMethod = AopUtils.selectInvocableMethod(method, bean.getClass());
+ Runnable runnable = new ScheduledMethodRunnable(bean, invocableMethod);
boolean processedSchedule = false;
String errorMessage =
"Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";
+ Set<ScheduledTask> tasks = this.scheduledTasks.get(bean);
+ if (tasks == null) {
+ tasks = new LinkedHashSet<ScheduledTask>(4);
+ this.scheduledTasks.put(bean, tasks);
+ }
+
// Determine initial delay
long initialDelay = scheduled.initialDelay();
String initialDelayString = scheduled.initialDelayString();
@@ -341,7 +342,7 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor,
else {
timeZone = TimeZone.getDefault();
}
- this.registrar.addCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone)));
+ tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
}
// At this point we don't need to differentiate between initial delay set or not anymore
@@ -354,7 +355,7 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor,
if (fixedDelay >= 0) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
- this.registrar.addFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay));
+ tasks.add(this.registrar.scheduleFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay)));
}
String fixedDelayString = scheduled.fixedDelayString();
if (StringUtils.hasText(fixedDelayString)) {
@@ -370,7 +371,7 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor,
throw new IllegalArgumentException(
"Invalid fixedDelayString value \"" + fixedDelayString + "\" - cannot parse into integer");
}
- this.registrar.addFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay));
+ tasks.add(this.registrar.scheduleFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay)));
}
// Check fixed rate
@@ -378,7 +379,7 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor,
if (fixedRate >= 0) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
- this.registrar.addFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay));
+ tasks.add(this.registrar.scheduleFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay)));
}
String fixedRateString = scheduled.fixedRateString();
if (StringUtils.hasText(fixedRateString)) {
@@ -394,7 +395,7 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor,
throw new IllegalArgumentException(
"Invalid fixedRateString value \"" + fixedRateString + "\" - cannot parse into integer");
}
- this.registrar.addFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay));
+ tasks.add(this.registrar.scheduleFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay)));
}
// Check whether we had any attribute set
@@ -408,7 +409,29 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor,
@Override
+ public void postProcessBeforeDestruction(Object bean, String beanName) {
+ Set<ScheduledTask> tasks = this.scheduledTasks.remove(bean);
+ if (tasks != null) {
+ for (ScheduledTask task : tasks) {
+ task.cancel();
+ }
+ }
+ }
+
+ @Override
+ public boolean requiresDestruction(Object bean) {
+ return this.scheduledTasks.containsKey(bean);
+ }
+
+ @Override
public void destroy() {
+ Collection<Set<ScheduledTask>> allTasks = this.scheduledTasks.values();
+ for (Set<ScheduledTask> tasks : allTasks) {
+ for (ScheduledTask task : tasks) {
+ task.cancel();
+ }
+ }
+ this.scheduledTasks.clear();
this.registrar.destroy();
}
diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/Schedules.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/Schedules.java
index 3cba4186..d27877ae 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/annotation/Schedules.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/Schedules.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +30,9 @@ import java.lang.annotation.Target;
* where {@link Scheduled} can simply be declared several times on the same method,
* implicitly generating this container annotation.
*
+ * <p>This annotation may be used as a <em>meta-annotation</em> to create custom
+ * <em>composed annotations</em>.
+ *
* @author Juergen Hoeller
* @since 4.0
* @see Scheduled
diff --git a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ConcurrentTaskExecutor.java b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ConcurrentTaskExecutor.java
index e9f2d255..b888092d 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ConcurrentTaskExecutor.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ConcurrentTaskExecutor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author 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.enterprise.concurrent.ManagedExecutors;
import javax.enterprise.concurrent.ManagedTask;
import org.springframework.core.task.AsyncListenableTaskExecutor;
+import org.springframework.core.task.TaskDecorator;
import org.springframework.core.task.support.TaskExecutorAdapter;
import org.springframework.scheduling.SchedulingAwareRunnable;
import org.springframework.scheduling.SchedulingTaskExecutor;
@@ -127,6 +128,20 @@ public class ConcurrentTaskExecutor implements AsyncListenableTaskExecutor, Sche
return this.concurrentExecutor;
}
+ /**
+ * Specify a custom {@link TaskDecorator} to be applied to any {@link Runnable}
+ * about to be executed.
+ * <p>Note that such a decorator is not necessarily being applied to the
+ * user-supplied {@code Runnable}/{@code Callable} but rather to the actual
+ * execution callback (which may be a wrapper around the user-supplied task).
+ * <p>The primary use case is to set some execution context around the task's
+ * invocation, or to provide some monitoring/statistics for task execution.
+ * @since 4.3
+ */
+ public final void setTaskDecorator(TaskDecorator taskDecorator) {
+ this.adaptedExecutor.setTaskDecorator(taskDecorator);
+ }
+
@Override
public void execute(Runnable task) {
diff --git a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.java b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.java
index 6cd5ccf9..3391276b 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +30,7 @@ import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.springframework.core.task.AsyncListenableTaskExecutor;
+import org.springframework.core.task.TaskDecorator;
import org.springframework.core.task.TaskRejectedException;
import org.springframework.scheduling.SchedulingTaskExecutor;
import org.springframework.util.Assert;
@@ -82,6 +83,8 @@ public class ThreadPoolTaskExecutor extends ExecutorConfigurationSupport
private boolean allowCoreThreadTimeOut = false;
+ private TaskDecorator taskDecorator;
+
private ThreadPoolExecutor threadPoolExecutor;
@@ -177,15 +180,51 @@ public class ThreadPoolTaskExecutor extends ExecutorConfigurationSupport
this.allowCoreThreadTimeOut = allowCoreThreadTimeOut;
}
+ /**
+ * Specify a custom {@link TaskDecorator} to be applied to any {@link Runnable}
+ * about to be executed.
+ * <p>Note that such a decorator is not necessarily being applied to the
+ * user-supplied {@code Runnable}/{@code Callable} but rather to the actual
+ * execution callback (which may be a wrapper around the user-supplied task).
+ * <p>The primary use case is to set some execution context around the task's
+ * invocation, or to provide some monitoring/statistics for task execution.
+ * @since 4.3
+ */
+ public void setTaskDecorator(TaskDecorator taskDecorator) {
+ this.taskDecorator = taskDecorator;
+ }
+
+ /**
+ * Note: This method exposes an {@link ExecutorService} to its base class
+ * but stores the actual {@link ThreadPoolExecutor} handle internally.
+ * Do not override this method for replacing the executor, rather just for
+ * decorating its {@code ExecutorService} handle or storing custom state.
+ */
@Override
protected ExecutorService initializeExecutor(
ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {
BlockingQueue<Runnable> queue = createQueue(this.queueCapacity);
- ThreadPoolExecutor executor = new ThreadPoolExecutor(
- this.corePoolSize, this.maxPoolSize, this.keepAliveSeconds, TimeUnit.SECONDS,
- queue, threadFactory, rejectedExecutionHandler);
+
+ ThreadPoolExecutor executor;
+ if (this.taskDecorator != null) {
+ executor = new ThreadPoolExecutor(
+ this.corePoolSize, this.maxPoolSize, this.keepAliveSeconds, TimeUnit.SECONDS,
+ queue, threadFactory, rejectedExecutionHandler) {
+ @Override
+ public void execute(Runnable command) {
+ super.execute(taskDecorator.decorate(command));
+ }
+ };
+ }
+ else {
+ executor = new ThreadPoolExecutor(
+ this.corePoolSize, this.maxPoolSize, this.keepAliveSeconds, TimeUnit.SECONDS,
+ queue, threadFactory, rejectedExecutionHandler);
+
+ }
+
if (this.allowCoreThreadTimeOut) {
executor.allowCoreThreadTimeOut(true);
}
diff --git a/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTask.java b/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTask.java
new file mode 100644
index 00000000..dffdb3e2
--- /dev/null
+++ b/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTask.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.scheduling.config;
+
+import java.util.concurrent.ScheduledFuture;
+
+/**
+ * A representation of a scheduled task,
+ * used as a return value for scheduling methods.
+ *
+ * @author Juergen Hoeller
+ * @since 4.3
+ * @see ScheduledTaskRegistrar#scheduleTriggerTask
+ * @see ScheduledTaskRegistrar#scheduleFixedRateTask
+ */
+public final class ScheduledTask {
+
+ volatile ScheduledFuture<?> future;
+
+
+ ScheduledTask() {
+ }
+
+
+ /**
+ * Trigger cancellation of this scheduled task.
+ */
+ public void cancel() {
+ ScheduledFuture<?> future = this.future;
+ if (future != null) {
+ future.cancel(true);
+ }
+ }
+
+}
diff --git a/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTaskRegistrar.java b/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTaskRegistrar.java
index 1cc43ca7..d7fe6be3 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTaskRegistrar.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTaskRegistrar.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,13 +19,13 @@ package org.springframework.scheduling.config;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
+import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
@@ -67,7 +67,9 @@ public class ScheduledTaskRegistrar implements InitializingBean, DisposableBean
private List<IntervalTask> fixedDelayTasks;
- private final Set<ScheduledFuture<?>> scheduledFutures = new LinkedHashSet<ScheduledFuture<?>>();
+ private final Map<Task, ScheduledTask> unresolvedTasks = new HashMap<Task, ScheduledTask>(16);
+
+ private final Set<ScheduledTask> scheduledTasks = new LinkedHashSet<ScheduledTask>(16);
/**
@@ -228,6 +230,7 @@ public class ScheduledTaskRegistrar implements InitializingBean, DisposableBean
Collections.<IntervalTask>emptyList());
}
+
/**
* Add a Runnable task to be triggered per the given {@link Trigger}.
* @see TaskScheduler#scheduleAtFixedRate(Runnable, long)
@@ -306,6 +309,7 @@ public class ScheduledTaskRegistrar implements InitializingBean, DisposableBean
this.fixedDelayTasks.add(task);
}
+
/**
* Return whether this {@code ScheduledTaskRegistrar} has any tasks registered.
* @since 3.2
@@ -331,56 +335,155 @@ public class ScheduledTaskRegistrar implements InitializingBean, DisposableBean
* #setTaskScheduler(TaskScheduler) task scheduler}.
*/
protected void scheduleTasks() {
- long now = System.currentTimeMillis();
-
if (this.taskScheduler == null) {
this.localExecutor = Executors.newSingleThreadScheduledExecutor();
this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
}
if (this.triggerTasks != null) {
for (TriggerTask task : this.triggerTasks) {
- this.scheduledFutures.add(this.taskScheduler.schedule(
- task.getRunnable(), task.getTrigger()));
+ addScheduledTask(scheduleTriggerTask(task));
}
}
if (this.cronTasks != null) {
for (CronTask task : this.cronTasks) {
- this.scheduledFutures.add(this.taskScheduler.schedule(
- task.getRunnable(), task.getTrigger()));
+ addScheduledTask(scheduleCronTask(task));
}
}
if (this.fixedRateTasks != null) {
for (IntervalTask task : this.fixedRateTasks) {
- if (task.getInitialDelay() > 0) {
- Date startTime = new Date(now + task.getInitialDelay());
- this.scheduledFutures.add(this.taskScheduler.scheduleAtFixedRate(
- task.getRunnable(), startTime, task.getInterval()));
- }
- else {
- this.scheduledFutures.add(this.taskScheduler.scheduleAtFixedRate(
- task.getRunnable(), task.getInterval()));
- }
+ addScheduledTask(scheduleFixedRateTask(task));
}
}
if (this.fixedDelayTasks != null) {
for (IntervalTask task : this.fixedDelayTasks) {
- if (task.getInitialDelay() > 0) {
- Date startTime = new Date(now + task.getInitialDelay());
- this.scheduledFutures.add(this.taskScheduler.scheduleWithFixedDelay(
- task.getRunnable(), startTime, task.getInterval()));
- }
- else {
- this.scheduledFutures.add(this.taskScheduler.scheduleWithFixedDelay(
- task.getRunnable(), task.getInterval()));
- }
+ addScheduledTask(scheduleFixedDelayTask(task));
}
}
}
+ private void addScheduledTask(ScheduledTask task) {
+ if (task != null) {
+ this.scheduledTasks.add(task);
+ }
+ }
+
+
+ /**
+ * Schedule the specified trigger task, either right away if possible
+ * or on initialization of the scheduler.
+ * @return a handle to the scheduled task, allowing to cancel it
+ * @since 4.3
+ */
+ public ScheduledTask scheduleTriggerTask(TriggerTask task) {
+ ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);
+ boolean newTask = false;
+ if (scheduledTask == null) {
+ scheduledTask = new ScheduledTask();
+ newTask = true;
+ }
+ if (this.taskScheduler != null) {
+ scheduledTask.future = this.taskScheduler.schedule(task.getRunnable(), task.getTrigger());
+ }
+ else {
+ addTriggerTask(task);
+ this.unresolvedTasks.put(task, scheduledTask);
+ }
+ return (newTask ? scheduledTask : null);
+ }
+
+ /**
+ * Schedule the specified cron task, either right away if possible
+ * or on initialization of the scheduler.
+ * @return a handle to the scheduled task, allowing to cancel it
+ * (or {@code null} if processing a previously registered task)
+ * @since 4.3
+ */
+ public ScheduledTask scheduleCronTask(CronTask task) {
+ ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);
+ boolean newTask = false;
+ if (scheduledTask == null) {
+ scheduledTask = new ScheduledTask();
+ newTask = true;
+ }
+ if (this.taskScheduler != null) {
+ scheduledTask.future = this.taskScheduler.schedule(task.getRunnable(), task.getTrigger());
+ }
+ else {
+ addCronTask(task);
+ this.unresolvedTasks.put(task, scheduledTask);
+ }
+ return (newTask ? scheduledTask : null);
+ }
+
+ /**
+ * Schedule the specified fixed-rate task, either right away if possible
+ * or on initialization of the scheduler.
+ * @return a handle to the scheduled task, allowing to cancel it
+ * (or {@code null} if processing a previously registered task)
+ * @since 4.3
+ */
+ public ScheduledTask scheduleFixedRateTask(IntervalTask task) {
+ ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);
+ boolean newTask = false;
+ if (scheduledTask == null) {
+ scheduledTask = new ScheduledTask();
+ newTask = true;
+ }
+ if (this.taskScheduler != null) {
+ if (task.getInitialDelay() > 0) {
+ Date startTime = new Date(System.currentTimeMillis() + task.getInitialDelay());
+ scheduledTask.future =
+ this.taskScheduler.scheduleAtFixedRate(task.getRunnable(), startTime, task.getInterval());
+ }
+ else {
+ scheduledTask.future =
+ this.taskScheduler.scheduleAtFixedRate(task.getRunnable(), task.getInterval());
+ }
+ }
+ else {
+ addFixedRateTask(task);
+ this.unresolvedTasks.put(task, scheduledTask);
+ }
+ return (newTask ? scheduledTask : null);
+ }
+
+ /**
+ * Schedule the specified fixed-delay task, either right away if possible
+ * or on initialization of the scheduler.
+ * @return a handle to the scheduled task, allowing to cancel it
+ * (or {@code null} if processing a previously registered task)
+ * @since 4.3
+ */
+ public ScheduledTask scheduleFixedDelayTask(IntervalTask task) {
+ ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);
+ boolean newTask = false;
+ if (scheduledTask == null) {
+ scheduledTask = new ScheduledTask();
+ newTask = true;
+ }
+ if (this.taskScheduler != null) {
+ if (task.getInitialDelay() > 0) {
+ Date startTime = new Date(System.currentTimeMillis() + task.getInitialDelay());
+ scheduledTask.future =
+ this.taskScheduler.scheduleWithFixedDelay(task.getRunnable(), startTime, task.getInterval());
+ }
+ else {
+ scheduledTask.future =
+ this.taskScheduler.scheduleWithFixedDelay(task.getRunnable(), task.getInterval());
+ }
+ }
+ else {
+ addFixedDelayTask(task);
+ this.unresolvedTasks.put(task, scheduledTask);
+ }
+ return (newTask ? scheduledTask : null);
+ }
+
+
@Override
public void destroy() {
- for (ScheduledFuture<?> future : this.scheduledFutures) {
- future.cancel(true);
+ for (ScheduledTask task : this.scheduledTasks) {
+ task.cancel();
}
if (this.localExecutor != null) {
this.localExecutor.shutdownNow();
diff --git a/spring-context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java b/spring-context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java
index f53894c7..c7b356fd 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -262,7 +262,7 @@ public class CronSequenceGenerator {
*/
private void parse(String expression) throws IllegalArgumentException {
String[] fields = StringUtils.tokenizeToStringArray(expression, " ");
- if (fields.length != 6) {
+ if (!areValidCronFields(fields)) {
throw new IllegalArgumentException(String.format(
"Cron expression must consist of 6 fields (found %d in \"%s\")", fields.length, expression));
}
@@ -379,10 +379,32 @@ public class CronSequenceGenerator {
throw new IllegalArgumentException("Range less than minimum (" + min + "): '" +
field + "' in expression \"" + this.expression + "\"");
}
+ if (result[0] > result[1]) {
+ throw new IllegalArgumentException("Invalid inverted range: '" + field +
+ "' in expression \"" + this.expression + "\"");
+ }
return result;
}
+ /**
+ * Determine whether the specified expression represents a valid cron pattern.
+ * <p>Specifically, this method verifies that the expression contains six
+ * fields separated by single spaces.
+ * @param expression the expression to evaluate
+ * @return {@code true} if the given expression is a valid cron expression
+ * @since 4.3
+ */
+ public static boolean isValidExpression(String expression) {
+ String[] fields = StringUtils.tokenizeToStringArray(expression, " ");
+ return areValidCronFields(fields);
+ }
+
+ private static boolean areValidCronFields(String[] fields) {
+ return (fields != null && fields.length == 6);
+ }
+
+
@Override
public boolean equals(Object other) {
if (this == other) {
diff --git a/spring-context/src/main/java/org/springframework/scripting/bsh/BshScriptUtils.java b/spring-context/src/main/java/org/springframework/scripting/bsh/BshScriptUtils.java
index 5fb732dc..57d18c2f 100644
--- a/spring-context/src/main/java/org/springframework/scripting/bsh/BshScriptUtils.java
+++ b/spring-context/src/main/java/org/springframework/scripting/bsh/BshScriptUtils.java
@@ -94,8 +94,7 @@ public abstract class BshScriptUtils {
return clazz.newInstance();
}
catch (Throwable ex) {
- throw new IllegalStateException("Could not instantiate script class [" +
- clazz.getName() + "]. Root cause is " + ex);
+ throw new IllegalStateException("Could not instantiate script class: " + clazz.getName(), ex);
}
}
else {
diff --git a/spring-context/src/main/java/org/springframework/scripting/config/ScriptingDefaultsParser.java b/spring-context/src/main/java/org/springframework/scripting/config/ScriptingDefaultsParser.java
index b5452624..f967a3a6 100644
--- a/spring-context/src/main/java/org/springframework/scripting/config/ScriptingDefaultsParser.java
+++ b/spring-context/src/main/java/org/springframework/scripting/config/ScriptingDefaultsParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@ import org.springframework.util.StringUtils;
* @author Mark Fisher
* @since 2.5
*/
-public class ScriptingDefaultsParser implements BeanDefinitionParser {
+class ScriptingDefaultsParser implements BeanDefinitionParser {
private static final String REFRESH_CHECK_DELAY_ATTRIBUTE = "refresh-check-delay";
@@ -41,7 +41,7 @@ public class ScriptingDefaultsParser implements BeanDefinitionParser {
LangNamespaceUtils.registerScriptFactoryPostProcessorIfNecessary(parserContext.getRegistry());
String refreshCheckDelay = element.getAttribute(REFRESH_CHECK_DELAY_ATTRIBUTE);
if (StringUtils.hasText(refreshCheckDelay)) {
- bd.getPropertyValues().add("defaultRefreshCheckDelay", new Long(refreshCheckDelay));
+ bd.getPropertyValues().add("defaultRefreshCheckDelay", Long.valueOf(refreshCheckDelay));
}
String proxyTargetClass = element.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE);
if (StringUtils.hasText(proxyTargetClass)) {
diff --git a/spring-context/src/main/java/org/springframework/scripting/groovy/GroovyScriptFactory.java b/spring-context/src/main/java/org/springframework/scripting/groovy/GroovyScriptFactory.java
index 439f3c1e..fdc2bb83 100644
--- a/spring-context/src/main/java/org/springframework/scripting/groovy/GroovyScriptFactory.java
+++ b/spring-context/src/main/java/org/springframework/scripting/groovy/GroovyScriptFactory.java
@@ -266,7 +266,7 @@ public class GroovyScriptFactory implements ScriptFactory, BeanFactoryAware, Bea
}
catch (InstantiationException ex) {
throw new ScriptCompilationException(
- scriptSource, "Could not instantiate Groovy script class: " + scriptClass.getName(), ex);
+ scriptSource, "Unable to instantiate Groovy script class: " + scriptClass.getName(), ex);
}
catch (IllegalAccessException ex) {
throw new ScriptCompilationException(
diff --git a/spring-context/src/main/java/org/springframework/scripting/support/ScriptFactoryPostProcessor.java b/spring-context/src/main/java/org/springframework/scripting/support/ScriptFactoryPostProcessor.java
index d91573d0..51ff49e1 100644
--- a/spring-context/src/main/java/org/springframework/scripting/support/ScriptFactoryPostProcessor.java
+++ b/spring-context/src/main/java/org/springframework/scripting/support/ScriptFactoryPostProcessor.java
@@ -381,7 +381,7 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces
* If the {@link BeanDefinition} has a
* {@link org.springframework.core.AttributeAccessor metadata attribute}
* under the key {@link #REFRESH_CHECK_DELAY_ATTRIBUTE} which is a valid {@link Number}
- * type, then this value is used. Otherwise, the the {@link #defaultRefreshCheckDelay}
+ * type, then this value is used. Otherwise, the {@link #defaultRefreshCheckDelay}
* value is used.
* @param beanDefinition the BeanDefinition to check
* @return the refresh check delay
diff --git a/spring-context/src/main/java/org/springframework/scripting/support/StandardScriptFactory.java b/spring-context/src/main/java/org/springframework/scripting/support/StandardScriptFactory.java
index bff51a79..f8bc2365 100644
--- a/spring-context/src/main/java/org/springframework/scripting/support/StandardScriptFactory.java
+++ b/spring-context/src/main/java/org/springframework/scripting/support/StandardScriptFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -154,7 +154,7 @@ public class StandardScriptFactory implements ScriptFactory, BeanClassLoaderAwar
}
catch (InstantiationException ex) {
throw new ScriptCompilationException(
- scriptSource, "Could not instantiate script class: " + scriptClass.getName(), ex);
+ scriptSource, "Unable to instantiate script class: " + scriptClass.getName(), ex);
}
catch (IllegalAccessException ex) {
throw new ScriptCompilationException(
diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/LocalValidatorFactoryBean.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/LocalValidatorFactoryBean.java
index 913406f0..33d53bf9 100644
--- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/LocalValidatorFactoryBean.java
+++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/LocalValidatorFactoryBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,9 +31,12 @@ import javax.validation.ConstraintValidatorFactory;
import javax.validation.MessageInterpolator;
import javax.validation.TraversableResolver;
import javax.validation.Validation;
+import javax.validation.ValidationProviderResolver;
import javax.validation.Validator;
import javax.validation.ValidatorContext;
import javax.validation.ValidatorFactory;
+import javax.validation.bootstrap.GenericBootstrap;
+import javax.validation.bootstrap.ProviderSpecificBootstrap;
import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
@@ -51,9 +54,9 @@ import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
/**
- * This is the central class for {@code javax.validation} (JSR-303) setup
- * in a Spring application context: It bootstraps a {@code javax.validation.ValidationFactory}
- * and exposes it through the Spring {@link org.springframework.validation.Validator} interface
+ * This is the central class for {@code javax.validation} (JSR-303) setup in a Spring
+ * application context: It bootstraps a {@code javax.validation.ValidationFactory} and
+ * exposes it through the Spring {@link org.springframework.validation.Validator} interface
* as well as through the JSR-303 {@link javax.validation.Validator} interface and the
* {@link javax.validation.ValidatorFactory} interface itself.
*
@@ -92,6 +95,8 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
@SuppressWarnings("rawtypes")
private Class providerClass;
+ private ValidationProviderResolver validationProviderResolver;
+
private MessageInterpolator messageInterpolator;
private TraversableResolver traversableResolver;
@@ -121,6 +126,15 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
}
/**
+ * Specify a JSR-303 {@link ValidationProviderResolver} for bootstrapping the
+ * provider of choice, as an alternative to {@code META-INF} driven resolution.
+ * @since 4.3
+ */
+ public void setValidationProviderResolver(ValidationProviderResolver validationProviderResolver) {
+ this.validationProviderResolver = validationProviderResolver;
+ }
+
+ /**
* Specify a custom MessageInterpolator to use for this ValidatorFactory
* and its exposed default Validator.
*/
@@ -216,11 +230,23 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
@Override
+ @SuppressWarnings({"rawtypes", "unchecked"})
public void afterPropertiesSet() {
- @SuppressWarnings({"rawtypes", "unchecked"})
- Configuration<?> configuration = (this.providerClass != null ?
- Validation.byProvider(this.providerClass).configure() :
- Validation.byDefaultProvider().configure());
+ Configuration<?> configuration;
+ if (this.providerClass != null) {
+ ProviderSpecificBootstrap bootstrap = Validation.byProvider(this.providerClass);
+ if (this.validationProviderResolver != null) {
+ bootstrap = bootstrap.providerResolver(this.validationProviderResolver);
+ }
+ configuration = bootstrap.configure();
+ }
+ else {
+ GenericBootstrap bootstrap = Validation.byDefaultProvider();
+ if (this.validationProviderResolver != null) {
+ bootstrap = bootstrap.providerResolver(this.validationProviderResolver);
+ }
+ configuration = bootstrap.configure();
+ }
// Try Hibernate Validator 5.2's externalClassLoader(ClassLoader) method
if (this.applicationContext != null) {
diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java
index aa88b18c..6d46113d 100644
--- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java
+++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@ import javax.validation.metadata.BeanDescriptor;
import javax.validation.metadata.ConstraintDescriptor;
import org.springframework.beans.NotReadablePropertyException;
+import org.springframework.context.MessageSourceResolvable;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.util.Assert;
import org.springframework.validation.BindingResult;
@@ -190,9 +191,9 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation.
* Return FieldError arguments for a validation error on the given field.
* Invoked for each violated constraint.
* <p>The default implementation returns a first argument indicating the field name
- * (of type DefaultMessageSourceResolvable, with "objectName.field" and "field" as codes).
- * Afterwards, it adds all actual constraint annotation attributes (i.e. excluding
- * "message", "groups" and "payload") in alphabetical order of their attribute names.
+ * (see {@link #getResolvableField}). Afterwards, it adds all actual constraint
+ * annotation attributes (i.e. excluding "message", "groups" and "payload") in
+ * alphabetical order of their attribute names.
* <p>Can be overridden to e.g. add further attributes from the constraint descriptor.
* @param objectName the name of the target object
* @param field the field that caused the binding error
@@ -204,14 +205,16 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation.
*/
protected Object[] getArgumentsForConstraint(String objectName, String field, ConstraintDescriptor<?> descriptor) {
List<Object> arguments = new LinkedList<Object>();
- String[] codes = new String[] {objectName + Errors.NESTED_PATH_SEPARATOR + field, field};
- arguments.add(new DefaultMessageSourceResolvable(codes, field));
+ arguments.add(getResolvableField(objectName, field));
// Using a TreeMap for alphabetical ordering of attribute names
Map<String, Object> attributesToExpose = new TreeMap<String, Object>();
for (Map.Entry<String, Object> entry : descriptor.getAttributes().entrySet()) {
String attributeName = entry.getKey();
Object attributeValue = entry.getValue();
if (!internalAnnotationAttributes.contains(attributeName)) {
+ if (attributeValue instanceof String) {
+ attributeValue = new ResolvableAttribute(attributeValue.toString());
+ }
attributesToExpose.put(attributeName, attributeValue);
}
}
@@ -220,6 +223,22 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation.
}
/**
+ * Build a resolvable wrapper for the specified field, allowing to resolve the field's
+ * name in a {@code MessageSource}.
+ * <p>The default implementation returns a first argument indicating the field:
+ * of type {@code DefaultMessageSourceResolvable}, with "objectName.field" and "field"
+ * as codes, and with the plain field name as default message.
+ * @param objectName the name of the target object
+ * @param field the field that caused the binding error
+ * @return a corresponding {@code MessageSourceResolvable} for the specified field
+ * @since 4.3
+ */
+ protected MessageSourceResolvable getResolvableField(String objectName, String field) {
+ String[] codes = new String[] {objectName + Errors.NESTED_PATH_SEPARATOR + field, field};
+ return new DefaultMessageSourceResolvable(codes, field);
+ }
+
+ /**
* Extract the rejected value behind the given constraint violation,
* for exposure through the Spring errors representation.
* @param field the field that caused the binding error
@@ -249,13 +268,13 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation.
@Override
public <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups) {
- Assert.notNull(this.targetValidator, "No target Validator set");
+ Assert.state(this.targetValidator != null, "No target Validator set");
return this.targetValidator.validate(object, groups);
}
@Override
public <T> Set<ConstraintViolation<T>> validateProperty(T object, String propertyName, Class<?>... groups) {
- Assert.notNull(this.targetValidator, "No target Validator set");
+ Assert.state(this.targetValidator != null, "No target Validator set");
return this.targetValidator.validateProperty(object, propertyName, groups);
}
@@ -263,20 +282,50 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation.
public <T> Set<ConstraintViolation<T>> validateValue(
Class<T> beanType, String propertyName, Object value, Class<?>... groups) {
- Assert.notNull(this.targetValidator, "No target Validator set");
+ Assert.state(this.targetValidator != null, "No target Validator set");
return this.targetValidator.validateValue(beanType, propertyName, value, groups);
}
@Override
public BeanDescriptor getConstraintsForClass(Class<?> clazz) {
- Assert.notNull(this.targetValidator, "No target Validator set");
+ Assert.state(this.targetValidator != null, "No target Validator set");
return this.targetValidator.getConstraintsForClass(clazz);
}
@Override
+ @SuppressWarnings("unchecked")
public <T> T unwrap(Class<T> type) {
- Assert.notNull(this.targetValidator, "No target Validator set");
- return this.targetValidator.unwrap(type);
+ Assert.state(this.targetValidator != null, "No target Validator set");
+ return (type != null ? this.targetValidator.unwrap(type) : (T) this.targetValidator);
+ }
+
+
+ /**
+ * Wrapper for a String attribute which can be resolved via a {@code MessageSource},
+ * falling back to the original attribute as a default value otherwise.
+ */
+ private static class ResolvableAttribute implements MessageSourceResolvable {
+
+ private final String resolvableString;
+
+ public ResolvableAttribute(String resolvableString) {
+ this.resolvableString = resolvableString;
+ }
+
+ @Override
+ public String[] getCodes() {
+ return new String[] {this.resolvableString};
+ }
+
+ @Override
+ public Object[] getArguments() {
+ return null;
+ }
+
+ @Override
+ public String getDefaultMessage() {
+ return this.resolvableString;
+ }
}
}
diff --git a/spring-context/src/main/resources/META-INF/spring.schemas b/spring-context/src/main/resources/META-INF/spring.schemas
index 3cac0ddd..e971bae9 100644
--- a/spring-context/src/main/resources/META-INF/spring.schemas
+++ b/spring-context/src/main/resources/META-INF/spring.schemas
@@ -5,7 +5,8 @@ http\://www.springframework.org/schema/context/spring-context-3.2.xsd=org/spring
http\://www.springframework.org/schema/context/spring-context-4.0.xsd=org/springframework/context/config/spring-context-4.0.xsd
http\://www.springframework.org/schema/context/spring-context-4.1.xsd=org/springframework/context/config/spring-context-4.1.xsd
http\://www.springframework.org/schema/context/spring-context-4.2.xsd=org/springframework/context/config/spring-context-4.2.xsd
-http\://www.springframework.org/schema/context/spring-context.xsd=org/springframework/context/config/spring-context-4.2.xsd
+http\://www.springframework.org/schema/context/spring-context-4.3.xsd=org/springframework/context/config/spring-context-4.3.xsd
+http\://www.springframework.org/schema/context/spring-context.xsd=org/springframework/context/config/spring-context-4.3.xsd
http\://www.springframework.org/schema/jee/spring-jee-2.0.xsd=org/springframework/ejb/config/spring-jee-2.0.xsd
http\://www.springframework.org/schema/jee/spring-jee-2.5.xsd=org/springframework/ejb/config/spring-jee-2.5.xsd
http\://www.springframework.org/schema/jee/spring-jee-3.0.xsd=org/springframework/ejb/config/spring-jee-3.0.xsd
@@ -14,7 +15,8 @@ http\://www.springframework.org/schema/jee/spring-jee-3.2.xsd=org/springframewor
http\://www.springframework.org/schema/jee/spring-jee-4.0.xsd=org/springframework/ejb/config/spring-jee-4.0.xsd
http\://www.springframework.org/schema/jee/spring-jee-4.1.xsd=org/springframework/ejb/config/spring-jee-4.1.xsd
http\://www.springframework.org/schema/jee/spring-jee-4.2.xsd=org/springframework/ejb/config/spring-jee-4.2.xsd
-http\://www.springframework.org/schema/jee/spring-jee.xsd=org/springframework/ejb/config/spring-jee-4.2.xsd
+http\://www.springframework.org/schema/jee/spring-jee-4.3.xsd=org/springframework/ejb/config/spring-jee-4.3.xsd
+http\://www.springframework.org/schema/jee/spring-jee.xsd=org/springframework/ejb/config/spring-jee-4.3.xsd
http\://www.springframework.org/schema/lang/spring-lang-2.0.xsd=org/springframework/scripting/config/spring-lang-2.0.xsd
http\://www.springframework.org/schema/lang/spring-lang-2.5.xsd=org/springframework/scripting/config/spring-lang-2.5.xsd
http\://www.springframework.org/schema/lang/spring-lang-3.0.xsd=org/springframework/scripting/config/spring-lang-3.0.xsd
@@ -23,17 +25,20 @@ http\://www.springframework.org/schema/lang/spring-lang-3.2.xsd=org/springframew
http\://www.springframework.org/schema/lang/spring-lang-4.0.xsd=org/springframework/scripting/config/spring-lang-4.0.xsd
http\://www.springframework.org/schema/lang/spring-lang-4.1.xsd=org/springframework/scripting/config/spring-lang-4.1.xsd
http\://www.springframework.org/schema/lang/spring-lang-4.2.xsd=org/springframework/scripting/config/spring-lang-4.2.xsd
-http\://www.springframework.org/schema/lang/spring-lang.xsd=org/springframework/scripting/config/spring-lang-4.2.xsd
+http\://www.springframework.org/schema/lang/spring-lang-4.3.xsd=org/springframework/scripting/config/spring-lang-4.3.xsd
+http\://www.springframework.org/schema/lang/spring-lang.xsd=org/springframework/scripting/config/spring-lang-4.3.xsd
http\://www.springframework.org/schema/task/spring-task-3.0.xsd=org/springframework/scheduling/config/spring-task-3.0.xsd
http\://www.springframework.org/schema/task/spring-task-3.1.xsd=org/springframework/scheduling/config/spring-task-3.1.xsd
http\://www.springframework.org/schema/task/spring-task-3.2.xsd=org/springframework/scheduling/config/spring-task-3.2.xsd
http\://www.springframework.org/schema/task/spring-task-4.0.xsd=org/springframework/scheduling/config/spring-task-4.0.xsd
http\://www.springframework.org/schema/task/spring-task-4.1.xsd=org/springframework/scheduling/config/spring-task-4.1.xsd
http\://www.springframework.org/schema/task/spring-task-4.2.xsd=org/springframework/scheduling/config/spring-task-4.2.xsd
-http\://www.springframework.org/schema/task/spring-task.xsd=org/springframework/scheduling/config/spring-task-4.2.xsd
+http\://www.springframework.org/schema/task/spring-task-4.3.xsd=org/springframework/scheduling/config/spring-task-4.3.xsd
+http\://www.springframework.org/schema/task/spring-task.xsd=org/springframework/scheduling/config/spring-task-4.3.xsd
http\://www.springframework.org/schema/cache/spring-cache-3.1.xsd=org/springframework/cache/config/spring-cache-3.1.xsd
http\://www.springframework.org/schema/cache/spring-cache-3.2.xsd=org/springframework/cache/config/spring-cache-3.2.xsd
http\://www.springframework.org/schema/cache/spring-cache-4.0.xsd=org/springframework/cache/config/spring-cache-4.0.xsd
http\://www.springframework.org/schema/cache/spring-cache-4.1.xsd=org/springframework/cache/config/spring-cache-4.1.xsd
http\://www.springframework.org/schema/cache/spring-cache-4.2.xsd=org/springframework/cache/config/spring-cache-4.2.xsd
-http\://www.springframework.org/schema/cache/spring-cache.xsd=org/springframework/cache/config/spring-cache-4.2.xsd
+http\://www.springframework.org/schema/cache/spring-cache-4.3.xsd=org/springframework/cache/config/spring-cache-4.3.xsd
+http\://www.springframework.org/schema/cache/spring-cache.xsd=org/springframework/cache/config/spring-cache-4.3.xsd
diff --git a/spring-context/src/main/resources/org/springframework/cache/config/spring-cache-4.3.xsd b/spring-context/src/main/resources/org/springframework/cache/config/spring-cache-4.3.xsd
new file mode 100644
index 00000000..6b1103ac
--- /dev/null
+++ b/spring-context/src/main/resources/org/springframework/cache/config/spring-cache-4.3.xsd
@@ -0,0 +1,317 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+
+<xsd:schema xmlns="http://www.springframework.org/schema/cache"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xmlns:tool="http://www.springframework.org/schema/tool"
+ targetNamespace="http://www.springframework.org/schema/cache"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified">
+
+ <xsd:import namespace="http://www.springframework.org/schema/beans" schemaLocation="http://www.springframework.org/schema/beans/spring-beans-4.3.xsd"/>
+ <xsd:import namespace="http://www.springframework.org/schema/tool" schemaLocation="http://www.springframework.org/schema/tool/spring-tool-4.3.xsd"/>
+
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Defines the elements used in the Spring Framework's declarative
+ cache management infrastructure.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+
+ <xsd:element name="annotation-driven">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.cache.annotation.AnnotationCacheOperationDefinitionSource"><![CDATA[
+ Indicates that cache configuration is defined by Java 5
+ annotations on bean classes, and that proxies are automatically
+ to be created for the relevant annotated beans.
+
+ The default annotations supported are Spring's @Cacheable, @CachePut and @CacheEvict. If
+ spring-context-support and the JSR-107 API are on the classpath, additional proxies are
+ automatically created for JSR-107 annotated beans, that is @CacheResult, @CachePut,
+ @CacheRemove and @CacheRemoveAll.
+
+ See org.springframework.cache.annotation.EnableCaching Javadoc
+ for information on code-based alternatives to this XML element.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="cache-manager" type="xsd:string" default="cacheManager">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.cache.CacheManager"><![CDATA[
+ The bean name of the CacheManager that is to be used to retrieve the backing
+ caches. A default CacheResolver will be initialized behind the scenes with
+ this cache manager (or "cacheManager" if not set). For more fine-grained
+ management of the cache resolution, consider setting the 'cache-resolver'
+ attribute.
+
+ Note that this attribute is still mandatory if you are using JSR-107 as an
+ additional exception cache resolver should be created and requires a CacheManager
+ to do so.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="org.springframework.cache.CacheManager"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="cache-resolver" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.cache.interceptor.CacheResolver"><![CDATA[
+ The bean name of the CacheResolver that is to be used to resolve the backing caches.
+
+ This attribute is not required, and only needs to be specified as an alternative to
+ the 'cache-manager' attribute. See the javadoc of CacheResolver for more details.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="org.springframework.cache.interceptor.CacheResolver"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="key-generator" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.cache.interceptor.KeyGenerator"><![CDATA[
+ The bean name of the KeyGenerator that is to be used to retrieve the backing caches.
+
+ This attribute is not required, and only needs to be specified
+ explicitly if the default strategy (DefaultKeyGenerator) is not sufficient.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="org.springframework.cache.interceptor.KeyGenerator"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="error-handler" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.cache.interceptor.CacheErrorHandler"><![CDATA[
+ The bean name of the CacheErrorHandler that is to be used to handle cache-related errors.
+
+ This attribute is not required, and only needs to be specified
+ explicitly if the default strategy (SimpleCacheErrorHandler) is not sufficient.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="org.springframework.cache.interceptor.CacheErrorHandler"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="mode" default="proxy">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Should annotated beans be proxied using Spring's AOP framework,
+ or should they rather be weaved with an AspectJ transaction aspect?
+
+ AspectJ weaving requires spring-aspects.jar on the classpath,
+ as well as load-time weaving (or compile-time weaving) enabled.
+
+ Note: The weaving-based aspect requires the @Cacheable and @CacheInvalidate
+ annotations to be defined on the concrete class. Annotations in interfaces
+ will not work in that case (they will rather only work with interface-based proxies)!
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="proxy"/>
+ <xsd:enumeration value="aspectj"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="proxy-target-class" type="xsd:boolean" default="false">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Are class-based (CGLIB) proxies to be created? By default, standard
+ Java interface-based proxies are created.
+
+ Note: Class-based proxies require the @Cacheable and @CacheInvalidate annotations
+ to be defined on the concrete class. Annotations in interfaces will not work
+ in that case (they will rather only work with interface-based proxies)!
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="order" type="xsd:token">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.core.Ordered"><![CDATA[
+ Controls the ordering of the execution of the cache advisor
+ when multiple advice executes at a specific joinpoint.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="advice">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.cache.interceptor.CacheInterceptor"><![CDATA[
+ Defines the cache semantics of the AOP advice that is to be
+ executed.
+
+ That is, this advice element is where the cacheable semantics of
+ any number of methods are defined (where cacheable semantics
+ includes the backing cache(s), the key, cache condition rules, and suchlike).
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation>
+ <tool:exports type="java:org.springframework.cache.interceptor.CacheInterceptor"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:complexContent>
+ <xsd:extension base="beans:identifiedType">
+ <xsd:sequence>
+ <xsd:element name="caching" type="definitionsType" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ <xsd:attribute name="cache-manager" type="xsd:string" default="cacheManager">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.cache.CacheManager"><![CDATA[
+ The bean name of the CacheManager that is to be used
+ for storing and retrieving data.
+
+ This attribute is not required, and only needs to be specified
+ explicitly if the bean name of the desired CacheManager
+ is not 'cacheManager'.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="org.springframework.cache.CacheManager"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="key-generator" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.cache.interceptor.KeyGenerator"><![CDATA[
+ The bean name of the KeyGenerator that is to be used to retrieve the backing caches.
+
+ This attribute is not required, and only needs to be specified
+ explicitly if the default strategy (DefaultKeyGenerator) is not sufficient.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="org.springframework.cache.interceptor.KeyGenerator"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:complexType name="basedefinitionType">
+ <xsd:attribute name="cache" type="xsd:string" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The name of the backing cache(s). Multiple caches can be specified by separating them using comma: 'orders, books']]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="key" type="xsd:string" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The SpEL expression used for computing the cache key, mutually exclusive with the key-generator parameter.]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="key-generator" type="xsd:string" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The name of the KeyGenerator bean responsible to compute the key, mutually exclusive with the key parameter.]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="cache-manager" type="xsd:string" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The name of the CacheManager bean responsible to manage the operation.]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="condition" type="xsd:string" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The SpEL expression used for conditioning the method caching.]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="method" type="xsd:string" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The method name(s) with which the cache attributes are to be
+ associated. The wildcard (*) character can be used to associate the
+ same cache attribute settings with a number of methods; for
+ example, 'get*', 'handle*', '*Order', 'on*Event', etc.]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+
+ </xsd:complexType>
+
+ <xsd:complexType name="definitionsType">
+ <xsd:complexContent>
+ <xsd:extension base="basedefinitionType">
+ <xsd:sequence>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="cacheable" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="basedefinitionType">
+ <xsd:attribute name="unless" type="xsd:string" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The SpEL expression used to veto the method caching.]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="sync" type="xsd:boolean" use="optional" default="false">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Synchronize the invocation of the underlying method if several threads
+ are attempting to load a value for the same key]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="cache-put" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="basedefinitionType">
+ <xsd:attribute name="unless" type="xsd:string" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The SpEL expression used to veto the method caching.]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="cache-evict" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="basedefinitionType">
+ <xsd:attribute name="all-entries" type="xsd:boolean" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Whether all the entries should be evicted.]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="before-invocation" type="xsd:boolean" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Whether the eviction should occur after the method is successfully
+ invoked (default) or before.]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+</xsd:schema>
diff --git a/spring-context/src/main/resources/org/springframework/context/config/spring-context-4.3.xsd b/spring-context/src/main/resources/org/springframework/context/config/spring-context-4.3.xsd
new file mode 100644
index 00000000..c7439e98
--- /dev/null
+++ b/spring-context/src/main/resources/org/springframework/context/config/spring-context-4.3.xsd
@@ -0,0 +1,547 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<xsd:schema xmlns="http://www.springframework.org/schema/context"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xmlns:tool="http://www.springframework.org/schema/tool"
+ targetNamespace="http://www.springframework.org/schema/context"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified">
+
+ <xsd:import namespace="http://www.springframework.org/schema/beans" schemaLocation="http://www.springframework.org/schema/beans/spring-beans-4.3.xsd"/>
+ <xsd:import namespace="http://www.springframework.org/schema/tool" schemaLocation="http://www.springframework.org/schema/tool/spring-tool-4.3.xsd"/>
+
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Defines the configuration elements for the Spring Framework's application
+ context support. Effects the activation of various configuration styles
+ for the containing Spring ApplicationContext.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+
+ <xsd:complexType name="propertyLoading">
+ <xsd:attribute name="location" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The location of the properties file to resolve placeholders against, as a Spring
+ resource location: a URL, a "classpath:" pseudo URL, or a relative file path.
+ Multiple locations may be specified, separated by commas. If neither location nor
+ properties-ref is specified, placeholders will be resolved against system properties.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="properties-ref" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:java.util.Properties"><![CDATA[
+ The bean name of a Properties object that will be used for property substitution.
+ If neither location nor properties-ref is specified, placeholders will be resolved
+ against system properties.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="file-encoding" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Specifies the encoding to use for parsing properties files. Default is none,
+ using the java.util.Properties default encoding. Only applies to classic
+ properties files, not to XML files.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="order" type="xsd:token">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Specifies the order for this placeholder configurer. If more than one is present
+ in a context, the order can be important since the first one to be match a
+ placeholder will win.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="ignore-resource-not-found" type="xsd:boolean" default="false">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Specifies if failure to find the property resource location should be ignored.
+ Default is "false", meaning that if there is no file in the location specified
+ an exception will be raised at runtime.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="ignore-unresolvable" type="xsd:boolean" default="false">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Specifies if failure to find the property value to replace a key should be ignored.
+ Default is "false", meaning that this placeholder configurer will raise an exception
+ if it cannot resolve a key. Set to "true" to allow the configurer to pass on the key
+ to any others in the context that have not yet visited the key in question.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="local-override" type="xsd:boolean" default="false">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Specifies whether local properties override properties from files.
+ Default is "false": Properties from files override local defaults.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+
+ <xsd:element name="property-placeholder">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Activates replacement of ${...} placeholders by registering a
+ PropertySourcesPlaceholderConfigurer within the application context. Properties will
+ be resolved against the specified properties file or Properties object -- so called
+ "local properties", if any, and against the Spring Environment's current set of
+ PropertySources.
+
+ Note that as of Spring 3.1 the system-properties-mode attribute has been removed in
+ favor of the more flexible PropertySources mechanism. However, applications may
+ continue to use the 3.0 (and older) versions of the spring-context schema in order
+ to preserve system-properties-mode behavior. In this case, the traditional
+ PropertyPlaceholderConfigurer component will be registered instead of the newer
+ PropertySourcesPlaceholderConfigurer.
+
+ See ConfigurableEnvironment javadoc for more information on usage.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation>
+ <tool:exports type="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="propertyLoading">
+ <xsd:attribute name="system-properties-mode" default="ENVIRONMENT">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Controls how to resolve placeholders against system properties. As of Spring 3.1, this
+ attribute value defaults to "ENVIRONMENT", indicating that resolution of placeholders
+ against system properties is handled via PropertySourcesPlaceholderConfigurer and its
+ delegation to the current Spring Environment object.
+
+ For maximum backward compatibility, this attribute is preserved going forward with the
+ 3.1 version of the context schema, and any values other than the default "ENVIRONMENT"
+ will cause a traditional PropertyPlaceholderConfigurer to be registered instead of the
+ newer PropertySourcesPlaceholderConfigurer variant. In this case, the Spring Environment
+ and its property sources are not interrogated when resolving placeholders. Users are
+ encouraged to consider this attribute deprecated, and to take advantage of the
+ Environment and PropertySource mechanisms. See ConfigurableEnvironment javadoc for examples.
+
+ "ENVIRONMENT" indicates placeholders should be resolved against the current Environment and against any local properties;
+ "NEVER" indicates placeholders should be resolved only against local properties and never against system properties;
+ "FALLBACK" indicates placeholders should be resolved against any local properties and then against system properties;
+ "OVERRIDE" indicates placeholders should be resolved first against system properties and then against any local properties;
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="ENVIRONMENT"/>
+ <xsd:enumeration value="NEVER"/>
+ <xsd:enumeration value="FALLBACK"/>
+ <xsd:enumeration value="OVERRIDE"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="value-separator" default=":">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The separating character between the placeholder variable and the associated
+ default value: by default, a ':' symbol.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="trim-values">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Whether to trim resolved values before applying them, removing superfluous
+ whitespace (in particular tab characters) from the beginning and end.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="null-value">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A value that should be treated as 'null' when resolved as a placeholder value:
+ e.g. "" (empty String) or "null". By default, no such null value is defined.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="property-override">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Activates pushing of override values into bean properties, based on configuration
+ lines of the following format: beanName.property=value
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation>
+ <tool:exports type="org.springframework.beans.factory.config.PropertyOverrideConfigurer"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="propertyLoading"/>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="annotation-config">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Activates various annotations to be detected in bean classes: Spring's @Required and
+ @Autowired, as well as JSR 250's @PostConstruct, @PreDestroy and @Resource (if available),
+ JAX-WS's @WebServiceRef (if available), EJB 3's @EJB (if available), and JPA's
+ @PersistenceContext and @PersistenceUnit (if available). Alternatively, you may
+ choose to activate the individual BeanPostProcessors for those annotations.
+
+ Note: This tag does not activate processing of Spring's @Transactional or EJB 3's
+ @TransactionAttribute annotation. Consider the use of the <tx:annotation-driven>
+ tag for that purpose.
+
+ See javadoc for org.springframework.context.annotation.AnnotationConfigApplicationContext
+ for information on code-based alternatives to bootstrapping annotation-driven support.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+
+ <xsd:element name="component-scan">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Scans the classpath for annotated components that will be auto-registered as
+ Spring beans. By default, the Spring-provided @Component, @Repository, @Service,
+ @Controller, @RestController, @ControllerAdvice, and @Configuration stereotypes
+ will be detected.
+
+ Note: This tag implies the effects of the 'annotation-config' tag, activating @Required,
+ @Autowired, @PostConstruct, @PreDestroy, @Resource, @PersistenceContext and @PersistenceUnit
+ annotations in the component classes, which is usually desired for autodetected components
+ (without external configuration). Turn off the 'annotation-config' attribute to deactivate
+ this default behavior, for example in order to use custom BeanPostProcessor definitions
+ for handling those annotations.
+
+ Note: You may use placeholders in package paths, but only resolved against system
+ properties (analogous to resource paths). A component scan results in new bean definitions
+ being registered; Spring's PropertySourcesPlaceholderConfigurer will apply to those bean
+ definitions just like to regular bean definitions, but it won't apply to the component
+ scan settings themselves.
+
+ See javadoc for org.springframework.context.annotation.ComponentScan for information
+ on code-based alternatives to bootstrapping component-scanning.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="include-filter" type="filterType"
+ minOccurs="0" maxOccurs="unbounded">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Controls which eligible types to include for component scanning.
+ Note that these filters will be applied in addition to the default filters, if specified.
+ Any type under the specified base packages which matches a given filter will be included,
+ even if it does not match the default filters (i.e. is not annotated with @Component).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="exclude-filter" type="filterType"
+ minOccurs="0" maxOccurs="unbounded">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Controls which eligible types to exclude for component scanning.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="base-package" type="xsd:string"
+ use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The comma/semicolon/space/tab/linefeed-separated list of packages to scan for annotated components.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="resource-pattern" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Controls the class files eligible for component detection. Defaults to "**/*.class", the recommended value.
+ Consider use of the include-filter and exclude-filter elements for a more fine-grained approach.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="use-default-filters" type="xsd:boolean"
+ default="true">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Indicates whether automatic detection of classes annotated with @Component, @Repository, @Service,
+ or @Controller should be enabled. Default is "true".
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="annotation-config" type="xsd:boolean"
+ default="true">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Indicates whether the implicit annotation post-processors should be enabled. Default is "true".
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="name-generator" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The fully-qualified class name of the BeanNameGenerator to be used for naming detected components.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation>
+ <tool:expected-type type="java.lang.Class"/>
+ <tool:assignable-to type="org.springframework.beans.factory.support.BeanNameGenerator"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="scope-resolver" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The fully-qualified class name of the ScopeMetadataResolver to be used for resolving the scope of
+ detected components.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation>
+ <tool:expected-type type="java.lang.Class"/>
+ <tool:assignable-to type="org.springframework.context.annotation.ScopeMetadataResolver"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="scoped-proxy">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Indicates whether proxies should be generated for detected components, which may be necessary
+ when using scopes in a proxy-style fashion. Default is to generate no such proxies.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="no"/>
+ <xsd:enumeration value="interfaces"/>
+ <xsd:enumeration value="targetClass"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="load-time-weaver">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Activates a Spring LoadTimeWeaver for this application context, available as
+ a bean with the name "loadTimeWeaver". Any bean that implements the
+ LoadTimeWeaverAware interface will then receive the LoadTimeWeaver reference
+ automatically; for example, Spring's JPA bootstrap support.
+
+ The default weaver is determined automatically: see DefaultContextLoadTimeWeaver's
+ javadoc for details.
+
+ The activation of AspectJ load-time weaving is specified via a simple flag
+ (the 'aspectj-weaving' attribute), with the AspectJ class transformer
+ registered through Spring's LoadTimeWeaver. AspectJ weaving will be activated
+ by default if a "META-INF/aop.xml" resource is present in the classpath.
+
+ This also activates the current application context for applying dependency
+ injection to non-managed classes that are instantiated outside of the Spring
+ bean factory (typically classes annotated with the @Configurable annotation).
+ This will only happen if the AnnotationBeanConfigurerAspect is on the classpath
+ (i.e. spring-aspects.jar), effectively activating "spring-configured" by default.
+
+ See javadoc for org.springframework.context.annotation.EnableLoadTimeWeaving
+ for information on code-based alternatives to bootstrapping load-time weaving support.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation>
+ <tool:exports type="org.springframework.instrument.classloading.LoadTimeWeaver"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:attribute name="weaver-class" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The fully-qualified classname of the LoadTimeWeaver that is to be activated.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation>
+ <tool:expected-type type="java.lang.Class"/>
+ <tool:assignable-to type="org.springframework.instrument.classloading.LoadTimeWeaver"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="aspectj-weaving" default="autodetect">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="on">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Switches Spring-based AspectJ load-time weaving on.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="off">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Switches Spring-based AspectJ load-time weaving off.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="autodetect">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Switches AspectJ load-time weaving on if a "META-INF/aop.xml" resource
+ is present in the classpath. If there is no such resource, then AspectJ
+ load-time weaving will be switched off.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="spring-configured">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect"><![CDATA[
+ Signals the current application context to apply dependency injection
+ to non-managed classes that are instantiated outside of the Spring bean
+ factory (typically classes annotated with the @Configurable annotation).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string"/>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="mbean-export">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.jmx.export.annotation.AnnotationMBeanExporter"><![CDATA[
+ Activates default exporting of MBeans by detecting standard MBeans in the Spring
+ context as well as @ManagedResource annotations on Spring-defined beans.
+
+ The resulting MBeanExporter bean is defined under the name "mbeanExporter".
+ Alternatively, consider defining a custom AnnotationMBeanExporter bean explicitly.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation>
+ <tool:exports type="org.springframework.jmx.export.annotation.AnnotationMBeanExporter"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:attribute name="default-domain" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The default domain to use when generating JMX ObjectNames.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="server" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The bean name of the MBeanServer to which MBeans should be exported.
+ Default is to use the platform's default MBeanServer (autodetecting
+ WebLogic, WebSphere and the JVM's platform MBeanServer).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="registration">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The registration behavior, indicating how to deal with existing MBeans
+ of the same name: fail with an exception, ignore and keep the existing
+ MBean, or replace the existing one with the new MBean.
+
+ Default is to fail with an exception.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:NMTOKEN">
+ <xsd:enumeration value="failOnExisting"/>
+ <xsd:enumeration value="ignoreExisting"/>
+ <xsd:enumeration value="replaceExisting"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="mbean-server">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.jmx.support.MBeanServerFactoryBean"><![CDATA[
+ Exposes a default MBeanServer for the current platform.
+ Autodetects WebLogic, WebSphere and the JVM's platform MBeanServer.
+
+ The default bean name for the exposed MBeanServer is "mbeanServer".
+ This may be customized through specifying the "id" attribute.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation>
+ <tool:exports type="javax.management.MBeanServer"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="beans:identifiedType">
+ <xsd:attribute name="agent-id" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The agent id of the target MBeanServer, if any.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:complexType name="filterType">
+ <xsd:attribute name="type" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Controls the type of filtering to apply to the expression.
+
+ "annotation" indicates an annotation to be present at the type level in target components;
+ "assignable" indicates a class (or interface) that the target components are assignable to (extend/implement);
+ "aspectj" indicates an AspectJ type pattern expression to be matched by the target components;
+ "regex" indicates a regex pattern to be matched by the target components' class names;
+ "custom" indicates a custom implementation of the org.springframework.core.type.TypeFilter interface.
+
+ Note: This attribute will not be inherited by child bean definitions.
+ Hence, it needs to be specified per concrete bean definition.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="annotation"/>
+ <xsd:enumeration value="assignable"/>
+ <xsd:enumeration value="aspectj"/>
+ <xsd:enumeration value="regex"/>
+ <xsd:enumeration value="custom"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="expression" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Indicates the filter expression, the type of which is indicated by "type".
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+
+</xsd:schema>
diff --git a/spring-context/src/main/resources/org/springframework/ejb/config/spring-jee-4.3.xsd b/spring-context/src/main/resources/org/springframework/ejb/config/spring-jee-4.3.xsd
new file mode 100644
index 00000000..5714ca95
--- /dev/null
+++ b/spring-context/src/main/resources/org/springframework/ejb/config/spring-jee-4.3.xsd
@@ -0,0 +1,267 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+
+<xsd:schema xmlns="http://www.springframework.org/schema/jee"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xmlns:tool="http://www.springframework.org/schema/tool"
+ targetNamespace="http://www.springframework.org/schema/jee"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified">
+
+ <xsd:import namespace="http://www.springframework.org/schema/beans" schemaLocation="http://www.springframework.org/schema/beans/spring-beans-4.3.xsd"/>
+ <xsd:import namespace="http://www.springframework.org/schema/tool" schemaLocation="http://www.springframework.org/schema/tool/spring-tool-4.3.xsd"/>
+
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Defines configuration elements for access to traditional Java EE components
+ such as JNDI resources and EJB session beans.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+
+ <xsd:element name="jndi-lookup">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.jndi.JndiObjectFactoryBean"><![CDATA[
+ Exposes an object reference via a JNDI lookup.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="jndiLocatingType">
+ <xsd:attribute name="cache" type="xsd:boolean" default="true">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Controls whether the object returned from the JNDI lookup is cached
+ after the first lookup.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="expected-type" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:java.lang.Class"><![CDATA[
+ The type that the located JNDI object is supposed to be assignable
+ to, if indeed any.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="lookup-on-startup" type="xsd:boolean" default="true">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Controls whether the JNDI lookup is performed immediately on startup
+ (if true, the default), or on first access (if false).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="proxy-interface" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:java.lang.Class"><![CDATA[
+ The proxy interface to use for the JNDI object.
+
+ Needs to be specified because the actual JNDI object type is not
+ known in advance in case of a lazy lookup.
+
+ Typically used in conjunction with "lookupOnStartup"=false and/or
+ "cache"=false.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation>
+ <tool:expected-type type="java.lang.Class"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="default-value" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Specify a default literal value to fall back to if the JNDI lookup fails.
+ This is typically used for literal values in scenarios where the JNDI environment
+ might define specific config settings but those are not required to be present.
+
+ Default is none. Note: This is only supported for lookup on startup.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="default-ref" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Specify a default bean reference to fall back to if the JNDI lookup fails.
+ This might for example point to a local fallback DataSource.
+
+ Default is none. Note: This is only supported for lookup on startup.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="local-slsb" type="ejbType">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean"><![CDATA[
+ Exposes a reference to a local EJB Stateless SessionBean.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+
+ <xsd:element name="remote-slsb">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.ejb.access.SimpleRemoteStatelessSessionProxyFactoryBean"><![CDATA[
+ Exposes a reference to a remote EJB Stateless SessionBean.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ejbType">
+ <xsd:attribute name="home-interface" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:java.lang.Class"><![CDATA[
+ The home interface that will be narrowed to before performing
+ the parameterless SLSB create() call that returns the actual
+ SLSB proxy.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="refresh-home-on-connect-failure" type="xsd:boolean" default="false">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Controls whether to refresh the EJB home on connect failure.
+
+ Can be turned on to allow for hot restart of the EJB server.
+ If a cached EJB home throws an RMI exception that indicates a
+ remote connect failure, a fresh home will be fetched and the
+ invocation will be retried.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="cache-session-bean" type="xsd:boolean" default="false">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Controls whether to cache the actual session bean object.
+
+ Off by default for standard EJB compliance. Turn this flag
+ on to optimize session bean access for servers that are
+ known to allow for caching the actual session bean object.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+
+ <!-- base types -->
+ <xsd:complexType name="jndiLocatingType" abstract="true">
+ <xsd:complexContent>
+ <xsd:extension base="beans:identifiedType">
+ <xsd:sequence>
+ <xsd:element name="environment" minOccurs="0" maxOccurs="1">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The newline-separated, key-value pairs for the JNDI environment
+ (in standard Properties format, namely 'key=value' pairs)
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string"/>
+ </xsd:simpleType>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="environment-ref" type="environmentRefType">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A reference to JNDI environment properties, indicating the name of a
+ shared bean of type [java.util.Properties}.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="jndi-name" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The JNDI name to look up. This may be a fully-qualified JNDI path
+ or a local Java EE environment naming context path in which case the
+ prefix "java:comp/env/" will be prepended if applicable.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="resource-ref" type="xsd:boolean" default="true">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Controls whether the lookup occurs in a Java EE container, i.e. if the
+ prefix "java:comp/env/" needs to be added if the JNDI name doesn't
+ already contain it. Default is "true" (since Spring 2.5).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="expose-access-context" type="xsd:boolean" default="false">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Set whether to expose the JNDI environment context for all access to the target
+ EJB, i.e. for all method invocations on the exposed object reference.
+ Default is "false", i.e. to only expose the JNDI context for object lookup.
+
+ Switch this flag to "true" in order to expose the JNDI environment (including
+ the authorization context) for each EJB invocation, as needed by WebLogic
+ for EJBs with authorization requirements.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="lazy-init" default="default" type="beans:defaultable-boolean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Indicates whether or not this bean is to be lazily initialized.
+ If false, it will be instantiated on startup by bean factories
+ that perform eager initialization of singletons. The default is
+ "false".
+
+ Note: This attribute will not be inherited by child bean definitions.
+ Hence, it needs to be specified per concrete bean definition.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="ejbType">
+ <xsd:complexContent>
+ <xsd:extension base="jndiLocatingType">
+ <xsd:attribute name="lookup-home-on-startup" type="xsd:boolean" default="true">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Controls whether the lookup of the EJB home object is performed
+ immediately on startup (if true, the default), or on first access
+ (if false).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="cache-home" type="xsd:boolean" default="true">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Controls whether the EJB home object is cached once it has been located.
+ On by default; turn this flag off to always reobtain fresh home objects.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="business-interface" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation source="java:java.lang.Class"><![CDATA[
+ The business interface of the EJB being proxied.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:simpleType name="environmentRefType">
+ <xsd:annotation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="java.util.Properties"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:union memberTypes="xsd:string"/>
+ </xsd:simpleType>
+
+</xsd:schema>
diff --git a/spring-context/src/main/resources/org/springframework/scheduling/config/spring-task-4.3.xsd b/spring-context/src/main/resources/org/springframework/scheduling/config/spring-task-4.3.xsd
new file mode 100644
index 00000000..08358de9
--- /dev/null
+++ b/spring-context/src/main/resources/org/springframework/scheduling/config/spring-task-4.3.xsd
@@ -0,0 +1,309 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+
+<xsd:schema xmlns="http://www.springframework.org/schema/task"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:tool="http://www.springframework.org/schema/tool"
+ targetNamespace="http://www.springframework.org/schema/task"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified">
+
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Defines the elements used in the Spring Framework's support for task execution and scheduling.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+
+ <xsd:import namespace="http://www.springframework.org/schema/beans" schemaLocation="http://www.springframework.org/schema/beans/spring-beans-4.3.xsd"/>
+ <xsd:import namespace="http://www.springframework.org/schema/tool" schemaLocation="http://www.springframework.org/schema/tool/spring-tool-4.3.xsd"/>
+
+ <xsd:element name="annotation-driven">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Enables the detection of @Async and @Scheduled annotations on any Spring-managed
+ object. If present, a proxy will be generated for executing the annotated methods
+ asynchronously.
+
+ See Javadoc for the org.springframework.scheduling.annotation.EnableAsync and
+ org.springframework.scheduling.annotation.EnableScheduling annotations for information
+ on code-based alternatives to this XML element.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:attribute name="executor" type="xsd:string" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Specifies the java.util.Executor instance to use when invoking asynchronous methods.
+ If not provided, an instance of org.springframework.core.task.SimpleAsyncTaskExecutor
+ will be used by default.
+ Note that as of Spring 3.1.2, individual @Async methods may qualify which executor to
+ use, meaning that the executor specified here acts as a default for all non-qualified
+ @Async methods.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="exception-handler" type="xsd:string" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Specifies the org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler
+ instance to use when an exception is thrown during an asynchronous method execution
+ and cannot be accessed by the caller. If not provided, an instance of
+ org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler will be
+ used by default.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="scheduler" type="xsd:string" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Specifies the org.springframework.scheduling.TaskScheduler or
+ java.util.ScheduledExecutorService instance to use when invoking scheduled
+ methods. If no reference is provided, a TaskScheduler backed by a single
+ thread scheduled executor will be used.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="mode" default="proxy">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Should annotated beans be proxied using Spring's AOP framework,
+ or should they rather be weaved with an AspectJ async execution aspect?
+
+ AspectJ weaving requires spring-aspects.jar on the classpath,
+ as well as load-time weaving (or compile-time weaving) enabled.
+
+ Note: The weaving-based aspect requires the @Async annotation to be
+ defined on the concrete class. Annotations in interfaces will not work
+ in that case (they will rather only work with interface-based proxies)!
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="proxy"/>
+ <xsd:enumeration value="aspectj"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="proxy-target-class" type="xsd:boolean" default="false">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Are class-based (CGLIB) proxies to be created? By default, standard
+ Java interface-based proxies are created.
+
+ Note: Class-based proxies require the @Async annotation to be defined
+ on the concrete class. Annotations in interfaces will not work in
+ that case (they will rather only work with interface-based proxies)!
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="scheduler">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Defines a ThreadPoolTaskScheduler instance with configurable pool size. See Javadoc
+ for the org.springframework.scheduling.annotation.EnableScheduling annotation for
+ information on a code-based alternative to this XML element.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:attribute name="id" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The bean name for the generated ThreadPoolTaskScheduler instance.
+ It will also be used as the default thread name prefix.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="pool-size" type="xsd:string" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The size of the ScheduledExecutorService's thread pool. The default is 1.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="executor">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Defines a ThreadPoolTaskExecutor instance with configurable pool size,
+ queue-capacity, keep-alive, and rejection-policy values.
+
+ See Javadoc for the org.springframework.scheduling.annotation.EnableAsync annotation
+ for information on code-based alternatives to this XML element.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:attribute name="id" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The bean name for the generated ThreadPoolTaskExecutor instance.
+ This value will also be used as the thread name prefix which is why it is
+ required even when defining the executor as an inner bean: The executor
+ won't be directly accessible then but will nevertheless use the specified
+ id as the thread name prefix of the threads that it manages.
+ In the case of multiple task:executors, as of Spring 3.1.2 this value may be used to
+ qualify which executor should handle a given @Async method, e.g. @Async("executorId").
+ See the Javadoc for the #value attribute of Spring's @Async annotation for details.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="pool-size" type="xsd:string" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The size of the executor's thread pool as either a single value or a range
+ (e.g. 5-10). If no bounded queue-capacity value is provided, then a max value
+ has no effect unless the range is specified as 0-n. In that case, the core pool
+ will have a size of n, but the 'allowCoreThreadTimeout' flag will be set to true.
+ If a queue-capacity is provided, then the lower bound of a range will map to the
+ core size and the upper bound will map to the max size. If this attribute is not
+ provided, the default core size will be 1, and the default max size will be
+ Integer.MAX_VALUE (i.e. unbounded).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="queue-capacity" type="xsd:string" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Queue capacity for the ThreadPoolTaskExecutor. If not specified, the default will
+ be Integer.MAX_VALUE (i.e. unbounded).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="keep-alive" type="xsd:string" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Keep-alive time in seconds. Inactive threads that have been created beyond the
+ core size will timeout after the specified number of seconds elapse. If the
+ executor has an unbounded queue capacity and a size range represented as 0-n,
+ then the core threads will also be configured to timeout when inactive.
+ Otherwise, core threads will not ever timeout.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="rejection-policy" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The RejectedExecutionHandler type. When a bounded queue cannot accept any
+ additional tasks, this determines the behavior. While the default is ABORT,
+ consider using CALLER_RUNS to throttle inbound tasks. In other words, by forcing
+ the caller to run the task itself, it will not be able to provide another task
+ until after it completes the task at hand. In the meantime, one or more tasks
+ may be removed from the queue. Alternatively, if it is not critical to run every
+ task, consider using DISCARD to drop the current task or DISCARD_OLDEST to drop
+ the task at the head of the queue.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="ABORT"/>
+ <xsd:enumeration value="CALLER_RUNS"/>
+ <xsd:enumeration value="DISCARD"/>
+ <xsd:enumeration value="DISCARD_OLDEST"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="scheduled-tasks">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Top-level element that contains one or more task sub-elements to be
+ managed by a given TaskScheduler.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="scheduled" type="scheduledTaskType" minOccurs="1" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ <xsd:attribute name="scheduler" type="xsd:string" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Reference to an instance of TaskScheduler to manage the provided tasks. If not specified,
+ the default value will be a wrapper for a single-threaded Executor.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="org.springframework.scheduling.TaskScheduler"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:complexType name="scheduledTaskType">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Element defining a scheduled method-invoking task and its corresponding trigger.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="cron" type="xsd:string" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A cron-based trigger. See the org.springframework.scheduling.support.CronSequenceGenerator
+ JavaDoc for example patterns.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="fixed-delay" type="xsd:string" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ An interval-based trigger where the interval is measured from the completion time of the
+ previous task. The time unit value is measured in milliseconds.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="fixed-rate" type="xsd:string" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ An interval-based trigger where the interval is measured from the start time of the
+ previous task. The time unit value is measured in milliseconds.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="trigger" type="xsd:string" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A reference to a bean that implements the Trigger interface.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="initial-delay" type="xsd:string" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Number of milliseconds to delay before the first execution of a 'fixed-rate' or
+ 'fixed-delay' task.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="ref" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Reference to an object that provides a method to be invoked.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref" />
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="method" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The name of the method to be invoked. The target method must expect no arguments.
+ It will typically have a void return type; if not, the returned value will be
+ ignored when called through the scheduler.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation>
+ <tool:expected-method type-ref="@ref"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+
+</xsd:schema>
diff --git a/spring-context/src/main/resources/org/springframework/scripting/config/spring-lang-4.3.xsd b/spring-context/src/main/resources/org/springframework/scripting/config/spring-lang-4.3.xsd
new file mode 100644
index 00000000..c2f090ba
--- /dev/null
+++ b/spring-context/src/main/resources/org/springframework/scripting/config/spring-lang-4.3.xsd
@@ -0,0 +1,240 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+
+<xsd:schema xmlns="http://www.springframework.org/schema/lang"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ targetNamespace="http://www.springframework.org/schema/lang"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified">
+
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Defines the elements used in the Spring Framework's dynamic language
+ support, which allows bean definitions that are backed by classes
+ written in a language other than Java.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+
+ <xsd:import namespace="http://www.springframework.org/schema/beans" schemaLocation="http://www.springframework.org/schema/beans/spring-beans-4.3.xsd"/>
+ <xsd:import namespace="http://www.springframework.org/schema/tool" schemaLocation="http://www.springframework.org/schema/tool/spring-tool-4.3.xsd"/>
+
+ <xsd:element name="defaults">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Default settings for any scripted beans registered within this context.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:attributeGroup ref="defaultableAttributes"/>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="groovy">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A Spring bean backed by a Groovy class definition.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="customizableScriptType">
+ <xsd:attributeGroup ref="defaultableAttributes"/>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="bsh">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A Spring bean backed by a BeanShell script.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="dynamicScriptType">
+ <xsd:attributeGroup ref="vanillaScriptAttributes"/>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="std">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A Spring bean backed by a standard JSR-223 based script.
+ Supports JavaScript, Groovy, JRuby and other JSR-223 compliant engines.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="dynamicScriptType">
+ <xsd:attribute name="engine" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The name of the script engine (if not inferred from the file extension).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attributeGroup ref="vanillaScriptAttributes"/>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+
+ <!-- Script Types -->
+ <xsd:complexType name="simpleScriptType">
+ <xsd:complexContent>
+ <xsd:extension base="beans:identifiedType">
+ <xsd:sequence>
+ <xsd:element name="inline-script" minOccurs="0" maxOccurs="1">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The source code for the dynamic language-backed bean.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="property" type="beans:propertyType" minOccurs="0" maxOccurs="unbounded">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Dynamic language-backed bean definitions can have zero or more properties.
+ Property elements correspond to JavaBean setter methods exposed
+ by the bean classes. Spring supports primitives, references to other
+ beans in the same or related factories, lists, maps and properties.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="script-source" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.core.io.Resource"><![CDATA[
+ The resource containing the script for the dynamic language-backed bean.
+
+ Examples might be '/WEB-INF/scripts/Anais.groovy', 'classpath:Nin.bsh', etc.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="name" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The name of this scripted bean as an alias or replacement for the id.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="scope" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The scope of this scripted bean: typically "singleton" (one shared instance,
+ which will be returned by all calls to getBean with the given id), or
+ "prototype" (independent instance resulting from each call to getBean).
+ Default is "singleton".
+
+ Singletons are most commonly used, and are ideal for multi-threaded
+ service objects. Further scopes, such as "request" or "session", might
+ be supported by extended bean factories (e.g. in a web environment).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="autowire" default="default">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The autowire mode for the scripted bean.
+ Analogous to the 'autowire' attribute on a standard bean definition.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:NMTOKEN">
+ <xsd:enumeration value="default"/>
+ <xsd:enumeration value="no"/>
+ <xsd:enumeration value="byName"/>
+ <xsd:enumeration value="byType"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="depends-on" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The names of the beans that this bean depends on being initialized.
+ The bean factory will guarantee that these beans get initialized
+ before this bean.
+
+ Note that dependencies are normally expressed through bean properties.
+ This property should just be necessary for other kinds of dependencies
+ like statics (*ugh*) or database preparation on startup.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="init-method" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The name of an initialization method defined on the scripted bean.
+ Analogous to the 'init-method' attribute on a standard bean definition.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="destroy-method" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The name of a destruction method defined on the scripted bean.
+ Analogous to the 'destroy-method' attribute on a standard bean definition.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="dynamicScriptType">
+ <xsd:complexContent>
+ <xsd:extension base="simpleScriptType">
+ <xsd:attribute name="script-interfaces">
+ <xsd:annotation>
+ <xsd:documentation source="java:java.lang.Class"><![CDATA[
+ The Java interfaces that the dynamic language-backed object is to expose; comma-delimited.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="customizableScriptType">
+ <xsd:complexContent>
+ <xsd:extension base="simpleScriptType">
+ <xsd:attribute name="customizer-ref">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Reference to a GroovyObjectCustomizer or similar customizer bean.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:attributeGroup name="vanillaScriptAttributes">
+ <xsd:attribute name="refresh-check-delay" type="xsd:long">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The delay (in milliseconds) between checks for updated sources when
+ using the refreshable beans feature.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:attributeGroup>
+
+ <xsd:attributeGroup name="defaultableAttributes">
+ <xsd:attribute name="proxy-target-class" type="xsd:boolean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Flag to tell the bean factory that if this bean is proxied it should be done using the target class type,
+ not its interfaces. A refreshable script is normally proxied, so often this is useful in conjunction with
+ refresh-check-delay. Defaults to false requiring no additional library dependencies, but hiding behavior
+ in the bean that is not defined in an interface.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attributeGroup ref="vanillaScriptAttributes"></xsd:attributeGroup>
+ </xsd:attributeGroup>
+
+</xsd:schema>
diff --git a/spring-context/src/test/java/org/springframework/aop/aspectj/AspectAndAdvicePrecedenceTests.java b/spring-context/src/test/java/org/springframework/aop/aspectj/AspectAndAdvicePrecedenceTests.java
index 2e4cfe2f..99bde98e 100644
--- a/spring-context/src/test/java/org/springframework/aop/aspectj/AspectAndAdvicePrecedenceTests.java
+++ b/spring-context/src/test/java/org/springframework/aop/aspectj/AspectAndAdvicePrecedenceTests.java
@@ -186,7 +186,9 @@ class PrecedenceTestAspect implements BeanNameAware, Ordered {
try {
ret = ((Integer)pjp.proceed()).intValue();
}
- catch(Throwable t) { throw new RuntimeException(t); }
+ catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
this.collaborator.aroundAdviceOne(this.name);
return ret;
}
@@ -197,7 +199,9 @@ class PrecedenceTestAspect implements BeanNameAware, Ordered {
try {
ret = ((Integer)pjp.proceed()).intValue();
}
- catch(Throwable t) {throw new RuntimeException(t);}
+ catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
this.collaborator.aroundAdviceTwo(this.name);
return ret;
}
diff --git a/spring-context/src/test/java/org/springframework/aop/aspectj/_TestTypes.java b/spring-context/src/test/java/org/springframework/aop/aspectj/_TestTypes.java
index 1490cf35..e4ebeb13 100644
--- a/spring-context/src/test/java/org/springframework/aop/aspectj/_TestTypes.java
+++ b/spring-context/src/test/java/org/springframework/aop/aspectj/_TestTypes.java
@@ -36,7 +36,7 @@ final class _TestTypes { }
/**
- * Aspect used as part of before before advice binding tests and
+ * Aspect used as part of before advice binding tests and
* serves as base class for a number of more specialized test aspects.
*
* @author Adrian Colyer
diff --git a/spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/AnnotationBindingTests.java b/spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/AnnotationBindingTests.java
index 82b20d96..a7f24b51 100644
--- a/spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/AnnotationBindingTests.java
+++ b/spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/AnnotationBindingTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,18 +27,19 @@ import static org.junit.Assert.*;
* @author Adrian Colyer
* @author Chris Beams
*/
-public final class AnnotationBindingTests {
+public class AnnotationBindingTests {
private AnnotatedTestBean testBean;
+
@Before
public void setUp() {
ClassPathXmlApplicationContext ctx =
- new ClassPathXmlApplicationContext(getClass().getSimpleName() + "-context.xml", getClass());
-
+ new ClassPathXmlApplicationContext(getClass().getSimpleName() + "-context.xml", getClass());
testBean = (AnnotatedTestBean) ctx.getBean("testBean");
}
+
@Test
public void testAnnotationBindingInAroundAdvice() {
assertEquals("this value", testBean.doThis());
diff --git a/spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/AspectJAutoProxyCreatorTests.java b/spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/AspectJAutoProxyCreatorTests.java
index d1c2e3f0..c753fd9f 100644
--- a/spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/AspectJAutoProxyCreatorTests.java
+++ b/spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/AspectJAutoProxyCreatorTests.java
@@ -513,11 +513,13 @@ class RetryAspect {
try {
o = jp.proceed();
this.commitCalls++;
- } catch (RetryableException e) {
+ }
+ catch (RetryableException re) {
this.rollbackCalls++;
- throw e;
+ throw re;
}
- } catch (RetryableException re) {
+ }
+ catch (RetryableException re) {
retry = true;
}
}
diff --git a/spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/spr3064/SPR3064Tests.java b/spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/spr3064/SPR3064Tests.java
index e2cf0114..b4229d15 100644
--- a/spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/spr3064/SPR3064Tests.java
+++ b/spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/spr3064/SPR3064Tests.java
@@ -1,5 +1,5 @@
/**
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package org.springframework.aop.aspectj.autoproxy.spr3064;
import java.lang.annotation.Retention;
@@ -35,6 +36,7 @@ public final class SPR3064Tests {
private Service service;
+
@Test
public void testServiceIsAdvised() {
ClassPathXmlApplicationContext ctx =
@@ -46,7 +48,7 @@ public final class SPR3064Tests {
this.service.serveMe();
fail("service operation has not been advised by transaction interceptor");
}
- catch(RuntimeException ex) {
+ catch (RuntimeException ex) {
assertEquals("advice invoked",ex.getMessage());
}
}
@@ -56,7 +58,6 @@ public final class SPR3064Tests {
@Retention(RetentionPolicy.RUNTIME)
@interface Transaction {
-
}
@@ -68,14 +69,12 @@ class TransactionInterceptor {
throw new RuntimeException("advice invoked");
//return pjp.proceed();
}
-
}
interface Service {
void serveMe();
-
}
@@ -85,5 +84,4 @@ class ServiceImpl implements Service {
@Transaction
public void serveMe() {
}
-
}
diff --git a/spring-context/src/test/java/org/springframework/aop/framework/AbstractAopProxyTests.java b/spring-context/src/test/java/org/springframework/aop/framework/AbstractAopProxyTests.java
index 3114bf62..09b73f1c 100644
--- a/spring-context/src/test/java/org/springframework/aop/framework/AbstractAopProxyTests.java
+++ b/spring-context/src/test/java/org/springframework/aop/framework/AbstractAopProxyTests.java
@@ -400,7 +400,8 @@ public abstract class AbstractAopProxyTests {
public Object invoke(MethodInvocation invocation) throws Throwable {
if (!context) {
assertNoInvocationContext();
- } else {
+ }
+ else {
assertNotNull("have context", ExposeInvocationInterceptor.currentInvocation());
}
return s;
diff --git a/spring-context/src/test/java/org/springframework/aop/framework/ClassWithComplexConstructor.java b/spring-context/src/test/java/org/springframework/aop/framework/ClassWithComplexConstructor.java
index 92118c0c..19bb244b 100644
--- a/spring-context/src/test/java/org/springframework/aop/framework/ClassWithComplexConstructor.java
+++ b/spring-context/src/test/java/org/springframework/aop/framework/ClassWithComplexConstructor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
package org.springframework.aop.framework;
+import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
@@ -28,6 +29,8 @@ public class ClassWithComplexConstructor {
private final Dependency dependency;
+ @Autowired ClassWithComplexConstructor selfReference;
+
@Autowired
public ClassWithComplexConstructor(Dependency dependency) {
Assert.notNull(dependency);
@@ -39,6 +42,7 @@ public class ClassWithComplexConstructor {
}
public void method() {
+ Assert.isTrue(this.selfReference != this && AopUtils.isCglibProxy(this.selfReference));
this.dependency.method();
}
diff --git a/spring-context/src/test/java/org/springframework/aop/framework/JdkDynamicProxyTests.java b/spring-context/src/test/java/org/springframework/aop/framework/JdkDynamicProxyTests.java
index dd8d1f3a..384be563 100644
--- a/spring-context/src/test/java/org/springframework/aop/framework/JdkDynamicProxyTests.java
+++ b/spring-context/src/test/java/org/springframework/aop/framework/JdkDynamicProxyTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,10 +32,10 @@ import org.springframework.tests.sample.beans.TestBean;
import static org.junit.Assert.*;
/**
- * @since 13.03.2003
* @author Rod Johnson
* @author Juergen Hoeller
* @author Chris Beams
+ * @since 13.03.2003
*/
@SuppressWarnings("serial")
public class JdkDynamicProxyTests extends AbstractAopProxyTests implements Serializable {
diff --git a/spring-context/src/test/java/org/springframework/aop/framework/autoproxy/AdvisorAutoProxyCreatorTests.java b/spring-context/src/test/java/org/springframework/aop/framework/autoproxy/AdvisorAutoProxyCreatorTests.java
index 5c7dfce7..0170cfd7 100644
--- a/spring-context/src/test/java/org/springframework/aop/framework/autoproxy/AdvisorAutoProxyCreatorTests.java
+++ b/spring-context/src/test/java/org/springframework/aop/framework/autoproxy/AdvisorAutoProxyCreatorTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@ import java.io.IOException;
import org.junit.Test;
import test.mixin.Lockable;
+import org.springframework.aop.Advisor;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.autoproxy.target.AbstractBeanFactoryBasedTargetSourceCreator;
import org.springframework.aop.support.AopUtils;
@@ -48,7 +49,7 @@ import static org.junit.Assert.*;
* @author Chris Beams
*/
@SuppressWarnings("resource")
-public final class AdvisorAutoProxyCreatorTests {
+public class AdvisorAutoProxyCreatorTests {
private static final Class<?> CLASS = AdvisorAutoProxyCreatorTests.class;
private static final String CLASSNAME = CLASS.getSimpleName();
@@ -59,6 +60,7 @@ public final class AdvisorAutoProxyCreatorTests {
private static final String QUICK_TARGETSOURCE_CONTEXT = CLASSNAME + "-quick-targetsource.xml";
private static final String OPTIMIZED_CONTEXT = CLASSNAME + "-optimized.xml";
+
/**
* Return a bean factory with attributes and EnterpriseServices configured.
*/
@@ -66,6 +68,7 @@ public final class AdvisorAutoProxyCreatorTests {
return new ClassPathXmlApplicationContext(DEFAULT_CONTEXT, CLASS);
}
+
/**
* Check that we can provide a common interceptor that will
* appear in the chain before "specific" interceptors,
@@ -78,8 +81,8 @@ public final class AdvisorAutoProxyCreatorTests {
assertTrue(AopUtils.isAopProxy(test1));
Lockable lockable1 = (Lockable) test1;
- NopInterceptor nop = (NopInterceptor) bf.getBean("nopInterceptor");
- assertEquals(0, nop.getCount());
+ NopInterceptor nop1 = (NopInterceptor) bf.getBean("nopInterceptor");
+ NopInterceptor nop2 = (NopInterceptor) bf.getBean("pointcutAdvisor", Advisor.class).getAdvice();
ITestBean test2 = (ITestBean) bf.getBean("test2");
Lockable lockable2 = (Lockable) test2;
@@ -87,14 +90,28 @@ public final class AdvisorAutoProxyCreatorTests {
// Locking should be independent; nop is shared
assertFalse(lockable1.locked());
assertFalse(lockable2.locked());
- // equals 2 calls on shared nop, because it's first
- // and sees calls against the Lockable interface introduced
- // by the specific advisor
- assertEquals(2, nop.getCount());
+ // equals 2 calls on shared nop, because it's first and sees calls
+ // against the Lockable interface introduced by the specific advisor
+ assertEquals(2, nop1.getCount());
+ assertEquals(0, nop2.getCount());
lockable1.lock();
assertTrue(lockable1.locked());
assertFalse(lockable2.locked());
- assertEquals(5, nop.getCount());
+ assertEquals(5, nop1.getCount());
+ assertEquals(0, nop2.getCount());
+
+ PackageVisibleMethod packageVisibleMethod = (PackageVisibleMethod) bf.getBean("packageVisibleMethod");
+ assertEquals(5, nop1.getCount());
+ assertEquals(0, nop2.getCount());
+ packageVisibleMethod.doSomething();
+ assertEquals(6, nop1.getCount());
+ assertEquals(1, nop2.getCount());
+ assertTrue(packageVisibleMethod instanceof Lockable);
+ Lockable lockable3 = (Lockable) packageVisibleMethod;
+ lockable3.lock();
+ assertTrue(lockable3.locked());
+ lockable3.unlock();
+ assertFalse(lockable3.locked());
}
/**
@@ -202,6 +219,7 @@ public final class AdvisorAutoProxyCreatorTests {
}
+
class SelectivePrototypeTargetSourceCreator extends AbstractBeanFactoryBasedTargetSourceCreator {
@Override
diff --git a/spring-context/src/test/java/org/springframework/aop/framework/autoproxy/AutoProxyCreatorTests.java b/spring-context/src/test/java/org/springframework/aop/framework/autoproxy/AutoProxyCreatorTests.java
index acaa8b72..5ed2091c 100644
--- a/spring-context/src/test/java/org/springframework/aop/framework/autoproxy/AutoProxyCreatorTests.java
+++ b/spring-context/src/test/java/org/springframework/aop/framework/autoproxy/AutoProxyCreatorTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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 @@ import static org.junit.Assert.*;
* @since 09.12.2003
*/
@SuppressWarnings("resource")
-public final class AutoProxyCreatorTests {
+public class AutoProxyCreatorTests {
@Test
public void testBeanNameAutoProxyCreator() {
@@ -253,6 +253,23 @@ public final class AutoProxyCreatorTests {
}
@Test
+ public void testAutoProxyCreatorWithPackageVisibleMethod() {
+ StaticApplicationContext sac = new StaticApplicationContext();
+ sac.registerSingleton("testAutoProxyCreator", TestAutoProxyCreator.class);
+ sac.registerSingleton("packageVisibleMethodToBeProxied", PackageVisibleMethod.class);
+ sac.refresh();
+
+ TestAutoProxyCreator tapc = (TestAutoProxyCreator) sac.getBean("testAutoProxyCreator");
+ tapc.testInterceptor.nrOfInvocations = 0;
+
+ PackageVisibleMethod tb = (PackageVisibleMethod) sac.getBean("packageVisibleMethodToBeProxied");
+ assertTrue(AopUtils.isCglibProxy(tb));
+ assertEquals(0, tapc.testInterceptor.nrOfInvocations);
+ tb.doSomething();
+ assertEquals(1, tapc.testInterceptor.nrOfInvocations);
+ }
+
+ @Test
public void testAutoProxyCreatorWithFactoryBean() {
StaticApplicationContext sac = new StaticApplicationContext();
sac.registerSingleton("testAutoProxyCreator", TestAutoProxyCreator.class);
diff --git a/spring-context/src/test/java/org/springframework/aop/framework/autoproxy/PackageVisibleMethod.java b/spring-context/src/test/java/org/springframework/aop/framework/autoproxy/PackageVisibleMethod.java
new file mode 100644
index 00000000..cf5e5a39
--- /dev/null
+++ b/spring-context/src/test/java/org/springframework/aop/framework/autoproxy/PackageVisibleMethod.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.aop.framework.autoproxy;
+
+public class PackageVisibleMethod {
+
+ void doSomething() {
+ }
+
+}
diff --git a/spring-context/src/test/java/org/springframework/beans/factory/support/QualifierAnnotationAutowireContextTests.java b/spring-context/src/test/java/org/springframework/beans/factory/support/QualifierAnnotationAutowireContextTests.java
index b86f1480..e6e7f023 100644
--- a/spring-context/src/test/java/org/springframework/beans/factory/support/QualifierAnnotationAutowireContextTests.java
+++ b/spring-context/src/test/java/org/springframework/beans/factory/support/QualifierAnnotationAutowireContextTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -42,6 +42,7 @@ import static org.junit.Assert.*;
* @author Mark Fisher
* @author Juergen Hoeller
* @author Chris Beams
+ * @author Sam Brannen
*/
public class QualifierAnnotationAutowireContextTests {
@@ -51,7 +52,7 @@ public class QualifierAnnotationAutowireContextTests {
@Test
- public void testAutowiredFieldWithSingleNonQualifiedCandidate() {
+ public void autowiredFieldWithSingleNonQualifiedCandidate() {
GenericApplicationContext context = new GenericApplicationContext();
ConstructorArgumentValues cavs = new ConstructorArgumentValues();
cavs.addGenericArgumentValue(JUERGEN);
@@ -71,7 +72,7 @@ public class QualifierAnnotationAutowireContextTests {
}
@Test
- public void testAutowiredMethodParameterWithSingleNonQualifiedCandidate() {
+ public void autowiredMethodParameterWithSingleNonQualifiedCandidate() {
GenericApplicationContext context = new GenericApplicationContext();
ConstructorArgumentValues cavs = new ConstructorArgumentValues();
cavs.addGenericArgumentValue(JUERGEN);
@@ -91,7 +92,7 @@ public class QualifierAnnotationAutowireContextTests {
}
@Test
- public void testAutowiredConstructorArgumentWithSingleNonQualifiedCandidate() {
+ public void autowiredConstructorArgumentWithSingleNonQualifiedCandidate() {
GenericApplicationContext context = new GenericApplicationContext();
ConstructorArgumentValues cavs = new ConstructorArgumentValues();
cavs.addGenericArgumentValue(JUERGEN);
@@ -111,7 +112,7 @@ public class QualifierAnnotationAutowireContextTests {
}
@Test
- public void testAutowiredFieldWithSingleQualifiedCandidate() {
+ public void autowiredFieldWithSingleQualifiedCandidate() {
GenericApplicationContext context = new GenericApplicationContext();
ConstructorArgumentValues cavs = new ConstructorArgumentValues();
cavs.addGenericArgumentValue(JUERGEN);
@@ -126,7 +127,7 @@ public class QualifierAnnotationAutowireContextTests {
}
@Test
- public void testAutowiredMethodParameterWithSingleQualifiedCandidate() {
+ public void autowiredMethodParameterWithSingleQualifiedCandidate() {
GenericApplicationContext context = new GenericApplicationContext();
ConstructorArgumentValues cavs = new ConstructorArgumentValues();
cavs.addGenericArgumentValue(JUERGEN);
@@ -143,7 +144,7 @@ public class QualifierAnnotationAutowireContextTests {
}
@Test
- public void testAutowiredMethodParameterWithStaticallyQualifiedCandidate() {
+ public void autowiredMethodParameterWithStaticallyQualifiedCandidate() {
GenericApplicationContext context = new GenericApplicationContext();
ConstructorArgumentValues cavs = new ConstructorArgumentValues();
cavs.addGenericArgumentValue(JUERGEN);
@@ -160,7 +161,7 @@ public class QualifierAnnotationAutowireContextTests {
}
@Test
- public void testAutowiredMethodParameterWithStaticallyQualifiedCandidateAmongOthers() {
+ public void autowiredMethodParameterWithStaticallyQualifiedCandidateAmongOthers() {
GenericApplicationContext context = new GenericApplicationContext();
ConstructorArgumentValues cavs = new ConstructorArgumentValues();
cavs.addGenericArgumentValue(JUERGEN);
@@ -180,7 +181,7 @@ public class QualifierAnnotationAutowireContextTests {
}
@Test
- public void testAutowiredConstructorArgumentWithSingleQualifiedCandidate() {
+ public void autowiredConstructorArgumentWithSingleQualifiedCandidate() {
GenericApplicationContext context = new GenericApplicationContext();
ConstructorArgumentValues cavs = new ConstructorArgumentValues();
cavs.addGenericArgumentValue(JUERGEN);
@@ -197,7 +198,7 @@ public class QualifierAnnotationAutowireContextTests {
}
@Test
- public void testAutowiredFieldWithMultipleNonQualifiedCandidates() {
+ public void autowiredFieldWithMultipleNonQualifiedCandidates() {
GenericApplicationContext context = new GenericApplicationContext();
ConstructorArgumentValues cavs1 = new ConstructorArgumentValues();
cavs1.addGenericArgumentValue(JUERGEN);
@@ -221,7 +222,7 @@ public class QualifierAnnotationAutowireContextTests {
}
@Test
- public void testAutowiredMethodParameterWithMultipleNonQualifiedCandidates() {
+ public void autowiredMethodParameterWithMultipleNonQualifiedCandidates() {
GenericApplicationContext context = new GenericApplicationContext();
ConstructorArgumentValues cavs1 = new ConstructorArgumentValues();
cavs1.addGenericArgumentValue(JUERGEN);
@@ -245,7 +246,7 @@ public class QualifierAnnotationAutowireContextTests {
}
@Test
- public void testAutowiredConstructorArgumentWithMultipleNonQualifiedCandidates() {
+ public void autowiredConstructorArgumentWithMultipleNonQualifiedCandidates() {
GenericApplicationContext context = new GenericApplicationContext();
ConstructorArgumentValues cavs1 = new ConstructorArgumentValues();
cavs1.addGenericArgumentValue(JUERGEN);
@@ -269,7 +270,7 @@ public class QualifierAnnotationAutowireContextTests {
}
@Test
- public void testAutowiredFieldResolvesQualifiedCandidate() {
+ public void autowiredFieldResolvesQualifiedCandidate() {
GenericApplicationContext context = new GenericApplicationContext();
ConstructorArgumentValues cavs1 = new ConstructorArgumentValues();
cavs1.addGenericArgumentValue(JUERGEN);
@@ -289,7 +290,7 @@ public class QualifierAnnotationAutowireContextTests {
}
@Test
- public void testAutowiredFieldResolvesMetaQualifiedCandidate() {
+ public void autowiredFieldResolvesMetaQualifiedCandidate() {
GenericApplicationContext context = new GenericApplicationContext();
ConstructorArgumentValues cavs1 = new ConstructorArgumentValues();
cavs1.addGenericArgumentValue(JUERGEN);
@@ -309,7 +310,7 @@ public class QualifierAnnotationAutowireContextTests {
}
@Test
- public void testAutowiredMethodParameterResolvesQualifiedCandidate() {
+ public void autowiredMethodParameterResolvesQualifiedCandidate() {
GenericApplicationContext context = new GenericApplicationContext();
ConstructorArgumentValues cavs1 = new ConstructorArgumentValues();
cavs1.addGenericArgumentValue(JUERGEN);
@@ -330,7 +331,7 @@ public class QualifierAnnotationAutowireContextTests {
}
@Test
- public void testAutowiredConstructorArgumentResolvesQualifiedCandidate() {
+ public void autowiredConstructorArgumentResolvesQualifiedCandidate() {
GenericApplicationContext context = new GenericApplicationContext();
ConstructorArgumentValues cavs1 = new ConstructorArgumentValues();
cavs1.addGenericArgumentValue(JUERGEN);
@@ -351,7 +352,7 @@ public class QualifierAnnotationAutowireContextTests {
}
@Test
- public void testAutowiredFieldResolvesQualifiedCandidateWithDefaultValueAndNoValueOnBeanDefinition() {
+ public void autowiredFieldResolvesQualifiedCandidateWithDefaultValueAndNoValueOnBeanDefinition() {
GenericApplicationContext context = new GenericApplicationContext();
ConstructorArgumentValues cavs1 = new ConstructorArgumentValues();
cavs1.addGenericArgumentValue(JUERGEN);
@@ -373,7 +374,7 @@ public class QualifierAnnotationAutowireContextTests {
}
@Test
- public void testAutowiredFieldDoesNotResolveCandidateWithDefaultValueAndConflictingValueOnBeanDefinition() {
+ public void autowiredFieldDoesNotResolveCandidateWithDefaultValueAndConflictingValueOnBeanDefinition() {
GenericApplicationContext context = new GenericApplicationContext();
ConstructorArgumentValues cavs1 = new ConstructorArgumentValues();
cavs1.addGenericArgumentValue(JUERGEN);
@@ -399,7 +400,7 @@ public class QualifierAnnotationAutowireContextTests {
}
@Test
- public void testAutowiredFieldResolvesWithDefaultValueAndExplicitDefaultValueOnBeanDefinition() {
+ public void autowiredFieldResolvesWithDefaultValueAndExplicitDefaultValueOnBeanDefinition() {
GenericApplicationContext context = new GenericApplicationContext();
ConstructorArgumentValues cavs1 = new ConstructorArgumentValues();
cavs1.addGenericArgumentValue(JUERGEN);
@@ -421,7 +422,7 @@ public class QualifierAnnotationAutowireContextTests {
}
@Test
- public void testAutowiredFieldResolvesWithMultipleQualifierValues() {
+ public void autowiredFieldResolvesWithMultipleQualifierValues() {
GenericApplicationContext context = new GenericApplicationContext();
ConstructorArgumentValues cavs1 = new ConstructorArgumentValues();
cavs1.addGenericArgumentValue(JUERGEN);
@@ -447,7 +448,7 @@ public class QualifierAnnotationAutowireContextTests {
}
@Test
- public void testAutowiredFieldDoesNotResolveWithMultipleQualifierValuesAndConflictingDefaultValue() {
+ public void autowiredFieldDoesNotResolveWithMultipleQualifierValuesAndConflictingDefaultValue() {
GenericApplicationContext context = new GenericApplicationContext();
ConstructorArgumentValues cavs1 = new ConstructorArgumentValues();
cavs1.addGenericArgumentValue(JUERGEN);
@@ -478,7 +479,7 @@ public class QualifierAnnotationAutowireContextTests {
}
@Test
- public void testAutowiredFieldResolvesWithMultipleQualifierValuesAndExplicitDefaultValue() {
+ public void autowiredFieldResolvesWithMultipleQualifierValuesAndExplicitDefaultValue() {
GenericApplicationContext context = new GenericApplicationContext();
ConstructorArgumentValues cavs1 = new ConstructorArgumentValues();
cavs1.addGenericArgumentValue(JUERGEN);
@@ -505,7 +506,7 @@ public class QualifierAnnotationAutowireContextTests {
}
@Test
- public void testAutowiredFieldDoesNotResolveWithMultipleQualifierValuesAndMultipleMatchingCandidates() {
+ public void autowiredFieldDoesNotResolveWithMultipleQualifierValuesAndMultipleMatchingCandidates() {
GenericApplicationContext context = new GenericApplicationContext();
ConstructorArgumentValues cavs1 = new ConstructorArgumentValues();
cavs1.addGenericArgumentValue(JUERGEN);
@@ -536,7 +537,7 @@ public class QualifierAnnotationAutowireContextTests {
}
@Test
- public void testAutowiredFieldResolvesWithBaseQualifierAndDefaultValue() {
+ public void autowiredFieldResolvesWithBaseQualifierAndDefaultValue() {
GenericApplicationContext context = new GenericApplicationContext();
ConstructorArgumentValues cavs1 = new ConstructorArgumentValues();
cavs1.addGenericArgumentValue(JUERGEN);
@@ -557,7 +558,7 @@ public class QualifierAnnotationAutowireContextTests {
}
@Test
- public void testAutowiredFieldResolvesWithBaseQualifierAndNonDefaultValue() {
+ public void autowiredFieldResolvesWithBaseQualifierAndNonDefaultValue() {
GenericApplicationContext context = new GenericApplicationContext();
ConstructorArgumentValues cavs1 = new ConstructorArgumentValues();
cavs1.addGenericArgumentValue("the real juergen");
@@ -579,7 +580,7 @@ public class QualifierAnnotationAutowireContextTests {
}
@Test
- public void testAutowiredFieldDoesNotResolveWithBaseQualifierAndNonDefaultValueAndMultipleMatchingCandidates() {
+ public void autowiredFieldDoesNotResolveWithBaseQualifierAndNonDefaultValueAndMultipleMatchingCandidates() {
GenericApplicationContext context = new GenericApplicationContext();
ConstructorArgumentValues cavs1 = new ConstructorArgumentValues();
cavs1.addGenericArgumentValue("the real juergen");
@@ -631,7 +632,7 @@ public class QualifierAnnotationAutowireContextTests {
@Autowired
@TestQualifier
@Retention(RetentionPolicy.RUNTIME)
- public static @interface MyAutowired {
+ @interface MyAutowired {
}
@@ -666,7 +667,7 @@ public class QualifierAnnotationAutowireContextTests {
}
- public static class QualifiedFieldWithDefaultValueTestBean {
+ private static class QualifiedFieldWithDefaultValueTestBean {
@Autowired
@TestQualifierWithDefaultValue
@@ -678,7 +679,7 @@ public class QualifierAnnotationAutowireContextTests {
}
- public static class QualifiedFieldWithMultipleAttributesTestBean {
+ private static class QualifiedFieldWithMultipleAttributesTestBean {
@Autowired
@TestQualifierWithMultipleAttributes(number=123)
@@ -702,7 +703,7 @@ public class QualifierAnnotationAutowireContextTests {
}
- public static class QualifiedConstructorArgumentWithBaseQualifierNonDefaultValueTestBean {
+ private static class QualifiedConstructorArgumentWithBaseQualifierNonDefaultValueTestBean {
private Person person;
@@ -760,13 +761,13 @@ public class QualifierAnnotationAutowireContextTests {
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
- public static @interface TestQualifier {
+ @interface TestQualifier {
}
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
- public static @interface TestQualifierWithDefaultValue {
+ @interface TestQualifierWithDefaultValue {
String value() default "default";
}
@@ -775,7 +776,7 @@ public class QualifierAnnotationAutowireContextTests {
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
- public static @interface TestQualifierWithMultipleAttributes {
+ @interface TestQualifierWithMultipleAttributes {
String value() default "default";
diff --git a/spring-context/src/test/java/org/springframework/beans/factory/xml/XmlBeanFactoryTests.java b/spring-context/src/test/java/org/springframework/beans/factory/xml/XmlBeanFactoryTests.java
index 94ec9db8..35f76782 100644
--- a/spring-context/src/test/java/org/springframework/beans/factory/xml/XmlBeanFactoryTests.java
+++ b/spring-context/src/test/java/org/springframework/beans/factory/xml/XmlBeanFactoryTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +25,7 @@ import java.lang.reflect.Method;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;
@@ -78,7 +79,7 @@ import static org.junit.Assert.*;
* @author Chris Beams
* @author Sam Brannen
*/
-public final class XmlBeanFactoryTests {
+public class XmlBeanFactoryTests {
private static final Class<?> CLASS = XmlBeanFactoryTests.class;
private static final String CLASSNAME = CLASS.getSimpleName();
@@ -1611,6 +1612,16 @@ public final class XmlBeanFactoryTests {
}
@Test
+ public void testConstructorWithUnresolvableParameterName() {
+ DefaultListableBeanFactory xbf = new DefaultListableBeanFactory();
+ new XmlBeanDefinitionReader(xbf).loadBeanDefinitions(CONSTRUCTOR_ARG_CONTEXT);
+ AtomicInteger bean = (AtomicInteger) xbf.getBean("constructorUnresolvableName");
+ assertEquals(1, bean.get());
+ bean = (AtomicInteger) xbf.getBean("constructorUnresolvableNameWithIndex");
+ assertEquals(1, bean.get());
+ }
+
+ @Test
public void testWithDuplicateName() throws Exception {
DefaultListableBeanFactory xbf = new DefaultListableBeanFactory();
try {
diff --git a/spring-context-support/src/test/java/org/springframework/cache/AbstractCacheTests.java b/spring-context/src/test/java/org/springframework/cache/AbstractCacheTests.java
index 97e33a01..c71f133d 100644
--- a/spring-context-support/src/test/java/org/springframework/cache/AbstractCacheTests.java
+++ b/spring-context/src/test/java/org/springframework/cache/AbstractCacheTests.java
@@ -16,10 +16,17 @@
package org.springframework.cache;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.UUID;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import static org.hamcrest.core.Is.*;
import static org.junit.Assert.*;
/**
@@ -27,6 +34,9 @@ import static org.junit.Assert.*;
*/
public abstract class AbstractCacheTests<T extends Cache> {
+ @Rule
+ public final ExpectedException thrown = ExpectedException.none();
+
protected final static String CACHE_NAME = "testCache";
protected abstract T getCache();
@@ -59,7 +69,7 @@ public abstract class AbstractCacheTests<T extends Cache> {
assertEquals(value, cache.get(key).get());
assertEquals(value, cache.get(key, String.class));
assertEquals(value, cache.get(key, Object.class));
- assertEquals(value, cache.get(key, null));
+ assertEquals(value, cache.get(key, (Class<?>) null));
cache.put(key, null);
assertNotNull(cache.get(key));
@@ -106,8 +116,102 @@ public abstract class AbstractCacheTests<T extends Cache> {
assertNull(cache.get("enescu"));
}
+ @Test
+ public void testCacheGetCallable() {
+ doTestCacheGetCallable("test");
+ }
+
+ @Test
+ public void testCacheGetCallableWithNull() {
+ doTestCacheGetCallable(null);
+ }
+
+ private void doTestCacheGetCallable(Object returnValue) {
+ T cache = getCache();
+
+ String key = createRandomKey();
+
+ assertNull(cache.get(key));
+ Object value = cache.get(key, () -> returnValue );
+ assertEquals(returnValue, value);
+ assertEquals(value, cache.get(key).get());
+ }
+
+ @Test
+ public void testCacheGetCallableNotInvokedWithHit() {
+ doTestCacheGetCallableNotInvokedWithHit("existing");
+ }
+
+ @Test
+ public void testCacheGetCallableNotInvokedWithHitNull() {
+ doTestCacheGetCallableNotInvokedWithHit(null);
+ }
+
+ private void doTestCacheGetCallableNotInvokedWithHit(Object initialValue) {
+ T cache = getCache();
+
+ String key = createRandomKey();
+ cache.put(key, initialValue);
+
+ Object value = cache.get(key, () -> {
+ throw new IllegalStateException("Should not have been invoked");
+ });
+ assertEquals(initialValue, value);
+ }
+
+ @Test
+ public void testCacheGetCallableFail() {
+ T cache = getCache();
+
+ String key = createRandomKey();
+ assertNull(cache.get(key));
+
+ try {
+ cache.get(key, () -> {
+ throw new UnsupportedOperationException("Expected exception");
+ });
+ }
+ catch (Cache.ValueRetrievalException ex) {
+ assertNotNull(ex.getCause());
+ assertEquals(UnsupportedOperationException.class, ex.getCause().getClass());
+ }
+ }
+
+ /**
+ * Test that a call to get with a Callable concurrently properly synchronize the
+ * invocations.
+ */
+ @Test
+ public void testCacheGetSynchronized() throws InterruptedException {
+ T cache = getCache();
+ final AtomicInteger counter = new AtomicInteger();
+ final List<Object> results = new CopyOnWriteArrayList<>();
+ final CountDownLatch latch = new CountDownLatch(10);
+
+ String key = createRandomKey();
+ Runnable run = () -> {
+ try {
+ Integer value = cache.get(key, () -> {
+ Thread.sleep(50); // make sure the thread will overlap
+ return counter.incrementAndGet();
+ });
+ results.add(value);
+ }
+ finally {
+ latch.countDown();
+ }
+ };
+
+ for (int i = 0; i < 10; i++) {
+ new Thread(run).start();
+ }
+ latch.await();
+
+ assertEquals(10, results.size());
+ results.forEach(r -> assertThat(r, is(1))); // Only one method got invoked
+ }
- private String createRandomKey() {
+ protected String createRandomKey() {
return UUID.randomUUID().toString();
}
diff --git a/spring-context/src/test/java/org/springframework/cache/CacheReproTests.java b/spring-context/src/test/java/org/springframework/cache/CacheReproTests.java
index 8781e82a..729bb61d 100644
--- a/spring-context/src/test/java/org/springframework/cache/CacheReproTests.java
+++ b/spring-context/src/test/java/org/springframework/cache/CacheReproTests.java
@@ -20,12 +20,14 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Optional;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.Mockito;
+import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.cache.annotation.CachingConfigurerSupport;
@@ -39,6 +41,7 @@ import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.tests.sample.beans.TestBean;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
@@ -130,6 +133,23 @@ public class CacheReproTests {
bean.getSimple(null);
}
+ @Test
+ public void spr14230AdaptsToOptional() {
+ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Spr14230Config.class);
+ Spr14230Service bean = context.getBean(Spr14230Service.class);
+ Cache cache = context.getBean(CacheManager.class).getCache("itemCache");
+
+ TestBean tb = new TestBean("tb1");
+ bean.insertItem(tb);
+ assertSame(tb, bean.findById("tb1").get());
+ assertSame(tb, cache.get("tb1").get());
+
+ cache.clear();
+ TestBean tb2 = bean.findById("tb1").get();
+ assertNotSame(tb, tb2);
+ assertSame(tb2, cache.get("tb1").get());
+ }
+
@Configuration
@EnableCaching
@@ -292,4 +312,34 @@ public class CacheReproTests {
}
}
+
+ public static class Spr14230Service {
+
+ @Cacheable("itemCache")
+ public Optional<TestBean> findById(String id) {
+ return Optional.of(new TestBean(id));
+ }
+
+ @CachePut(cacheNames = "itemCache", key = "#item.name")
+ public TestBean insertItem(TestBean item) {
+ return item;
+ }
+ }
+
+
+ @Configuration
+ @EnableCaching
+ public static class Spr14230Config {
+
+ @Bean
+ public CacheManager cacheManager() {
+ return new ConcurrentMapCacheManager();
+ }
+
+ @Bean
+ public Spr14230Service service() {
+ return new Spr14230Service();
+ }
+ }
+
}
diff --git a/spring-context/src/test/java/org/springframework/cache/NoOpCacheManagerTests.java b/spring-context/src/test/java/org/springframework/cache/NoOpCacheManagerTests.java
index dc65b678..9489b847 100644
--- a/spring-context/src/test/java/org/springframework/cache/NoOpCacheManagerTests.java
+++ b/spring-context/src/test/java/org/springframework/cache/NoOpCacheManagerTests.java
@@ -18,33 +18,33 @@ package org.springframework.cache;
import java.util.UUID;
-import org.junit.Before;
import org.junit.Test;
import org.springframework.cache.support.NoOpCacheManager;
import static org.junit.Assert.*;
+/**
+ * Tests for {@link NoOpCacheManager}.
+ *
+ * @author Costin Leau
+ * @author Stephane Nicoll
+ */
public class NoOpCacheManagerTests {
- private CacheManager manager;
-
- @Before
- public void setup() {
- manager = new NoOpCacheManager();
- }
+ private final CacheManager manager = new NoOpCacheManager();
@Test
public void testGetCache() throws Exception {
- Cache cache = manager.getCache("bucket");
+ Cache cache = this.manager.getCache("bucket");
assertNotNull(cache);
- assertSame(cache, manager.getCache("bucket"));
+ assertSame(cache, this.manager.getCache("bucket"));
}
@Test
public void testNoOpCache() throws Exception {
- String name = UUID.randomUUID().toString();
- Cache cache = manager.getCache(name);
+ String name = createRandomKey();
+ Cache cache = this.manager.getCache(name);
assertEquals(name, cache.getName());
Object key = new Object();
cache.put(key, new Object());
@@ -56,8 +56,37 @@ public class NoOpCacheManagerTests {
@Test
public void testCacheName() throws Exception {
String name = "bucket";
- assertFalse(manager.getCacheNames().contains(name));
- manager.getCache(name);
- assertTrue(manager.getCacheNames().contains(name));
+ assertFalse(this.manager.getCacheNames().contains(name));
+ this.manager.getCache(name);
+ assertTrue(this.manager.getCacheNames().contains(name));
+ }
+
+ @Test
+ public void testCacheCallable() throws Exception {
+ String name = createRandomKey();
+ Cache cache = this.manager.getCache(name);
+ Object returnValue = new Object();
+ Object value = cache.get(new Object(), () -> returnValue);
+ assertEquals(returnValue, value);
}
+
+ @Test
+ public void testCacheGetCallableFail() {
+ Cache cache = this.manager.getCache(createRandomKey());
+ String key = createRandomKey();
+ try {
+ cache.get(key, () -> {
+ throw new UnsupportedOperationException("Expected exception");
+ });
+ }
+ catch (Cache.ValueRetrievalException ex) {
+ assertNotNull(ex.getCause());
+ assertEquals(UnsupportedOperationException.class, ex.getCause().getClass());
+ }
+ }
+
+ private String createRandomKey() {
+ return UUID.randomUUID().toString();
+ }
+
}
diff --git a/spring-context/src/test/java/org/springframework/cache/annotation/AnnotationCacheOperationSourceTests.java b/spring-context/src/test/java/org/springframework/cache/annotation/AnnotationCacheOperationSourceTests.java
index bfafce1d..409d9692 100644
--- a/spring-context/src/test/java/org/springframework/cache/annotation/AnnotationCacheOperationSourceTests.java
+++ b/spring-context/src/test/java/org/springframework/cache/annotation/AnnotationCacheOperationSourceTests.java
@@ -23,6 +23,7 @@ import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.Iterator;
import org.junit.Rule;
@@ -34,6 +35,7 @@ import org.springframework.cache.interceptor.CacheOperation;
import org.springframework.cache.interceptor.CacheableOperation;
import org.springframework.core.annotation.AliasFor;
+import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
/**
@@ -96,6 +98,48 @@ public class AnnotationCacheOperationSourceTests {
}
@Test
+ public void singleComposedAnnotation() throws Exception {
+ Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "singleComposed", 2);
+ Iterator<CacheOperation> it = ops.iterator();
+
+ CacheOperation cacheOperation = it.next();
+ assertThat(cacheOperation, instanceOf(CacheableOperation.class));
+ assertThat(cacheOperation.getCacheNames(), equalTo(Collections.singleton("directly declared")));
+ assertThat(cacheOperation.getKey(), equalTo(""));
+
+ cacheOperation = it.next();
+ assertThat(cacheOperation, instanceOf(CacheableOperation.class));
+ assertThat(cacheOperation.getCacheNames(), equalTo(Collections.singleton("composedCache")));
+ assertThat(cacheOperation.getKey(), equalTo("composedKey"));
+ }
+
+ @Test
+ public void multipleComposedAnnotations() throws Exception {
+ Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "multipleComposed", 4);
+ Iterator<CacheOperation> it = ops.iterator();
+
+ CacheOperation cacheOperation = it.next();
+ assertThat(cacheOperation, instanceOf(CacheableOperation.class));
+ assertThat(cacheOperation.getCacheNames(), equalTo(Collections.singleton("directly declared")));
+ assertThat(cacheOperation.getKey(), equalTo(""));
+
+ cacheOperation = it.next();
+ assertThat(cacheOperation, instanceOf(CacheableOperation.class));
+ assertThat(cacheOperation.getCacheNames(), equalTo(Collections.singleton("composedCache")));
+ assertThat(cacheOperation.getKey(), equalTo("composedKey"));
+
+ cacheOperation = it.next();
+ assertThat(cacheOperation, instanceOf(CacheableOperation.class));
+ assertThat(cacheOperation.getCacheNames(), equalTo(Collections.singleton("foo")));
+ assertThat(cacheOperation.getKey(), equalTo(""));
+
+ cacheOperation = it.next();
+ assertThat(cacheOperation, instanceOf(CacheEvictOperation.class));
+ assertThat(cacheOperation.getCacheNames(), equalTo(Collections.singleton("composedCacheEvict")));
+ assertThat(cacheOperation.getKey(), equalTo("composedEvictionKey"));
+ }
+
+ @Test
public void customKeyGenerator() {
Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "customKeyGenerator", 1);
CacheOperation cacheOperation = ops.iterator().next();
diff --git a/spring-context/src/test/java/org/springframework/cache/concurrent/ConcurrentCacheTests.java b/spring-context/src/test/java/org/springframework/cache/concurrent/ConcurrentCacheTests.java
deleted file mode 100644
index cba7db7d..00000000
--- a/spring-context/src/test/java/org/springframework/cache/concurrent/ConcurrentCacheTests.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright 2002-2014 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.cache.concurrent;
-
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import org.springframework.cache.Cache;
-
-import static org.junit.Assert.*;
-
-/**
- * @author Costin Leau
- * @author Juergen Hoeller
- * @author Stephane Nicoll
- */
-public class ConcurrentCacheTests {
-
- protected final static String CACHE_NAME = "testCache";
-
- protected ConcurrentMap<Object, Object> nativeCache;
-
- protected Cache cache;
-
-
- @Before
- public void setUp() throws Exception {
- nativeCache = new ConcurrentHashMap<Object, Object>();
- cache = new ConcurrentMapCache(CACHE_NAME, nativeCache, true);
- cache.clear();
- }
-
-
- @Test
- public void testCacheName() throws Exception {
- assertEquals(CACHE_NAME, cache.getName());
- }
-
- @Test
- public void testNativeCache() throws Exception {
- assertSame(nativeCache, cache.getNativeCache());
- }
-
- @Test
- public void testCachePut() throws Exception {
- Object key = "enescu";
- Object value = "george";
-
- assertNull(cache.get(key));
- assertNull(cache.get(key, String.class));
- assertNull(cache.get(key, Object.class));
-
- cache.put(key, value);
- assertEquals(value, cache.get(key).get());
- assertEquals(value, cache.get(key, String.class));
- assertEquals(value, cache.get(key, Object.class));
- assertEquals(value, cache.get(key, null));
-
- cache.put(key, null);
- assertNotNull(cache.get(key));
- assertNull(cache.get(key).get());
- assertNull(cache.get(key, String.class));
- assertNull(cache.get(key, Object.class));
- }
-
- @Test
- public void testCachePutIfAbsent() throws Exception {
- Object key = new Object();
- Object value = "initialValue";
-
- assertNull(cache.get(key));
- assertNull(cache.putIfAbsent(key, value));
- assertEquals(value, cache.get(key).get());
- assertEquals("initialValue", cache.putIfAbsent(key, "anotherValue").get());
- assertEquals(value, cache.get(key).get()); // not changed
- }
-
- @Test
- public void testCacheRemove() throws Exception {
- Object key = "enescu";
- Object value = "george";
-
- assertNull(cache.get(key));
- cache.put(key, value);
- }
-
- @Test
- public void testCacheClear() throws Exception {
- assertNull(cache.get("enescu"));
- cache.put("enescu", "george");
- assertNull(cache.get("vlaicu"));
- cache.put("vlaicu", "aurel");
- cache.clear();
- assertNull(cache.get("vlaicu"));
- assertNull(cache.get("enescu"));
- }
-
-}
diff --git a/spring-context/src/test/java/org/springframework/cache/concurrent/ConcurrentMapCacheManagerTests.java b/spring-context/src/test/java/org/springframework/cache/concurrent/ConcurrentMapCacheManagerTests.java
index 14fdb547..1598b0fa 100644
--- a/spring-context/src/test/java/org/springframework/cache/concurrent/ConcurrentMapCacheManagerTests.java
+++ b/spring-context/src/test/java/org/springframework/cache/concurrent/ConcurrentMapCacheManagerTests.java
@@ -25,6 +25,7 @@ import static org.junit.Assert.*;
/**
* @author Juergen Hoeller
+ * @author Stephane Nicoll
*/
public class ConcurrentMapCacheManagerTests {
@@ -120,4 +121,21 @@ public class ConcurrentMapCacheManagerTests {
assertNull(cache1y.get("key3"));
}
+ @Test
+ public void testChangeStoreByValue() {
+ ConcurrentMapCacheManager cm = new ConcurrentMapCacheManager("c1", "c2");
+ assertFalse(cm.isStoreByValue());
+ Cache cache1 = cm.getCache("c1");
+ assertTrue(cache1 instanceof ConcurrentMapCache);
+ assertFalse(((ConcurrentMapCache)cache1).isStoreByValue());
+ cache1.put("key", "value");
+
+ cm.setStoreByValue(true);
+ assertTrue(cm.isStoreByValue());
+ Cache cache1x = cm.getCache("c1");
+ assertTrue(cache1x instanceof ConcurrentMapCache);
+ assertTrue(cache1x != cache1);
+ assertNull(cache1x.get("key"));
+ }
+
}
diff --git a/spring-context/src/test/java/org/springframework/cache/concurrent/ConcurrentMapCacheTests.java b/spring-context/src/test/java/org/springframework/cache/concurrent/ConcurrentMapCacheTests.java
new file mode 100644
index 00000000..6836ba70
--- /dev/null
+++ b/spring-context/src/test/java/org/springframework/cache/concurrent/ConcurrentMapCacheTests.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2002-2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cache.concurrent;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.springframework.cache.AbstractCacheTests;
+import org.springframework.core.serializer.support.SerializationDelegate;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Costin Leau
+ * @author Juergen Hoeller
+ * @author Stephane Nicoll
+ */
+public class ConcurrentMapCacheTests extends AbstractCacheTests<ConcurrentMapCache> {
+
+ protected ConcurrentMap<Object, Object> nativeCache;
+
+ protected ConcurrentMapCache cache;
+
+
+ @Before
+ public void setUp() throws Exception {
+ nativeCache = new ConcurrentHashMap<Object, Object>();
+ cache = new ConcurrentMapCache(CACHE_NAME, nativeCache, true);
+ cache.clear();
+ }
+
+ @Override
+ protected ConcurrentMapCache getCache() {
+ return this.cache;
+ }
+
+ @Override
+ protected ConcurrentMap<Object, Object> getNativeCache() {
+ return this.nativeCache;
+ }
+
+ @Test
+ public void testIsStoreByReferenceByDefault() {
+ assertFalse(this.cache.isStoreByValue());
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testSerializer() {
+ ConcurrentMapCache serializeCache = createCacheWithStoreByValue();
+ assertTrue(serializeCache.isStoreByValue());
+
+ Object key = createRandomKey();
+ List<String> content = new ArrayList<>();
+ content.addAll(Arrays.asList("one", "two", "three"));
+ serializeCache.put(key, content);
+ content.remove(0);
+ List<String> entry = (List<String>) serializeCache.get(key).get();
+ assertEquals(3, entry.size());
+ assertEquals("one", entry.get(0));
+ }
+
+ @Test
+ public void testNonSerializableContent() {
+ ConcurrentMapCache serializeCache = createCacheWithStoreByValue();
+
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Failed to serialize");
+ thrown.expectMessage(this.cache.getClass().getName());
+ serializeCache.put(createRandomKey(), this.cache);
+ }
+
+ @Test
+ public void testInvalidSerializedContent() {
+ ConcurrentMapCache serializeCache = createCacheWithStoreByValue();
+
+ String key = createRandomKey();
+ this.nativeCache.put(key, "Some garbage");
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Failed to deserialize");
+ thrown.expectMessage("Some garbage");
+ serializeCache.get(key);
+ }
+
+
+ private ConcurrentMapCache createCacheWithStoreByValue() {
+ return new ConcurrentMapCache(CACHE_NAME, nativeCache, true,
+ new SerializationDelegate(ConcurrentMapCacheTests.class.getClassLoader()));
+ }
+
+}
diff --git a/spring-context/src/test/java/org/springframework/cache/config/AbstractCacheAnnotationTests.java b/spring-context/src/test/java/org/springframework/cache/config/AbstractCacheAnnotationTests.java
index 73b9d4d6..8a00e735 100644
--- a/spring-context/src/test/java/org/springframework/cache/config/AbstractCacheAnnotationTests.java
+++ b/spring-context/src/test/java/org/springframework/cache/config/AbstractCacheAnnotationTests.java
@@ -105,6 +105,32 @@ public abstract class AbstractCacheAnnotationTests {
assertNull("Cached value should be null", r3);
}
+ public void testCacheableSync(CacheableService<?> service) throws Exception {
+ Object o1 = new Object();
+
+ Object r1 = service.cacheSync(o1);
+ Object r2 = service.cacheSync(o1);
+ Object r3 = service.cacheSync(o1);
+
+ assertSame(r1, r2);
+ assertSame(r1, r3);
+ }
+
+ public void testCacheableSyncNull(CacheableService<?> service) throws Exception {
+ Object o1 = new Object();
+ assertNull(cm.getCache("testCache").get(o1));
+
+ Object r1 = service.cacheSyncNull(o1);
+ Object r2 = service.cacheSyncNull(o1);
+ Object r3 = service.cacheSyncNull(o1);
+
+ assertSame(r1, r2);
+ assertSame(r1, r3);
+
+ assertEquals(r3, cm.getCache("testCache").get(o1).get());
+ assertNull("Cached value should be null", r3);
+ }
+
public void testEvict(CacheableService<?> service) throws Exception {
Object o1 = new Object();
@@ -225,6 +251,18 @@ public abstract class AbstractCacheAnnotationTests {
assertSame(r3, r4);
}
+ public void testConditionalExpressionSync(CacheableService<?> service) throws Exception {
+ Object r1 = service.conditionalSync(4);
+ Object r2 = service.conditionalSync(4);
+
+ assertNotSame(r1, r2);
+
+ Object r3 = service.conditionalSync(3);
+ Object r4 = service.conditionalSync(3);
+
+ assertSame(r3, r4);
+ }
+
public void testUnlessExpression(CacheableService<?> service) throws Exception {
Cache cache = cm.getCache("testCache");
cache.clear();
@@ -311,6 +349,30 @@ public abstract class AbstractCacheAnnotationTests {
}
}
+ public void testCheckedThrowableSync(CacheableService<?> service) throws Exception {
+ String arg = UUID.randomUUID().toString();
+ try {
+ service.throwCheckedSync(arg);
+ fail("Excepted exception");
+ }
+ catch (Exception ex) {
+ ex.printStackTrace();
+ assertEquals("Wrong exception type", IOException.class, ex.getClass());
+ assertEquals(arg, ex.getMessage());
+ }
+ }
+
+ public void testUncheckedThrowableSync(CacheableService<?> service) throws Exception {
+ try {
+ service.throwUncheckedSync(Long.valueOf(1));
+ fail("Excepted exception");
+ }
+ catch (RuntimeException ex) {
+ assertEquals("Wrong exception type", UnsupportedOperationException.class, ex.getClass());
+ assertEquals("1", ex.getMessage());
+ }
+ }
+
public void testNullArg(CacheableService<?> service) {
Object r1 = service.cache(null);
assertSame(r1, service.cache(null));
@@ -484,6 +546,16 @@ public abstract class AbstractCacheAnnotationTests {
}
@Test
+ public void testCacheableSync() throws Exception {
+ testCacheableSync(cs);
+ }
+
+ @Test
+ public void testCacheableSyncNull() throws Exception {
+ testCacheableSyncNull(cs);
+ }
+
+ @Test
public void testInvalidate() throws Exception {
testEvict(cs);
}
@@ -519,6 +591,11 @@ public abstract class AbstractCacheAnnotationTests {
}
@Test
+ public void testConditionalExpressionSync() throws Exception {
+ testConditionalExpressionSync(cs);
+ }
+
+ @Test
public void testUnlessExpression() throws Exception {
testUnlessExpression(cs);
}
@@ -678,6 +755,16 @@ public abstract class AbstractCacheAnnotationTests {
}
@Test
+ public void testCheckedExceptionSync() throws Exception {
+ testCheckedThrowableSync(cs);
+ }
+
+ @Test
+ public void testClassCheckedExceptionSync() throws Exception {
+ testCheckedThrowableSync(ccs);
+ }
+
+ @Test
public void testUncheckedException() throws Exception {
testUncheckedThrowable(cs);
}
@@ -688,6 +775,16 @@ public abstract class AbstractCacheAnnotationTests {
}
@Test
+ public void testUncheckedExceptionSync() throws Exception {
+ testUncheckedThrowableSync(cs);
+ }
+
+ @Test
+ public void testClassUncheckedExceptionSync() throws Exception {
+ testUncheckedThrowableSync(ccs);
+ }
+
+ @Test
public void testUpdate() {
testCacheUpdate(cs);
}
diff --git a/spring-context/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java b/spring-context/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java
index 450a01fb..88886b6a 100644
--- a/spring-context/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java
+++ b/spring-context/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java
@@ -47,11 +47,28 @@ public class AnnotatedClassCacheableService implements CacheableService<Object>
}
@Override
+ @Cacheable(cacheNames = "testCache", sync = true)
+ public Object cacheSync(Object arg1) {
+ return counter.getAndIncrement();
+ }
+
+ @Override
+ @Cacheable(cacheNames = "testCache", sync = true)
+ public Object cacheSyncNull(Object arg1) {
+ return null;
+ }
+
+ @Override
public Object conditional(int field) {
return null;
}
@Override
+ public Object conditionalSync(int field) {
+ return null;
+ }
+
+ @Override
@Cacheable(cacheNames = "testCache", unless = "#result > 10")
public Object unless(int arg) {
return arg;
@@ -171,6 +188,18 @@ public class AnnotatedClassCacheableService implements CacheableService<Object>
throw new UnsupportedOperationException(arg1.toString());
}
+ @Override
+ @Cacheable(cacheNames = "testCache", sync = true)
+ public Object throwCheckedSync(Object arg1) throws Exception {
+ throw new IOException(arg1.toString());
+ }
+
+ @Override
+ @Cacheable(cacheNames = "testCache", sync = true)
+ public Object throwUncheckedSync(Object arg1) {
+ throw new UnsupportedOperationException(arg1.toString());
+ }
+
// multi annotations
@Override
diff --git a/spring-context/src/test/java/org/springframework/cache/config/CacheAdviceParserTests.java b/spring-context/src/test/java/org/springframework/cache/config/CacheAdviceParserTests.java
index 5e624bda..3d461ce3 100644
--- a/spring-context/src/test/java/org/springframework/cache/config/CacheAdviceParserTests.java
+++ b/spring-context/src/test/java/org/springframework/cache/config/CacheAdviceParserTests.java
@@ -35,7 +35,10 @@ public class CacheAdviceParserTests {
try {
new GenericXmlApplicationContext("/org/springframework/cache/config/cache-advice-invalid.xml");
fail("Should have failed to load context, one advise define both a key and a key generator");
- } catch (BeanDefinitionStoreException e) { // TODO better exception handling
+ }
+ catch (BeanDefinitionStoreException ex) {
+ // TODO better exception handling
}
}
+
}
diff --git a/spring-context/src/test/java/org/springframework/cache/config/CacheableService.java b/spring-context/src/test/java/org/springframework/cache/config/CacheableService.java
index a129f617..f7030351 100644
--- a/spring-context/src/test/java/org/springframework/cache/config/CacheableService.java
+++ b/spring-context/src/test/java/org/springframework/cache/config/CacheableService.java
@@ -29,6 +29,10 @@ public interface CacheableService<T> {
T cacheNull(Object arg1);
+ T cacheSync(Object arg1);
+
+ T cacheSyncNull(Object arg1);
+
void invalidate(Object arg1);
void evictEarly(Object arg1);
@@ -43,6 +47,8 @@ public interface CacheableService<T> {
T conditional(int field);
+ T conditionalSync(int field);
+
T unless(int arg);
T key(Object arg1, Object arg2);
@@ -73,6 +79,10 @@ public interface CacheableService<T> {
T throwUnchecked(Object arg1);
+ T throwCheckedSync(Object arg1) throws Exception;
+
+ T throwUncheckedSync(Object arg1);
+
// multi annotations
T multiCache(Object arg1);
diff --git a/spring-context/src/test/java/org/springframework/cache/config/DefaultCacheableService.java b/spring-context/src/test/java/org/springframework/cache/config/DefaultCacheableService.java
index f5564df5..93b7a239 100644
--- a/spring-context/src/test/java/org/springframework/cache/config/DefaultCacheableService.java
+++ b/spring-context/src/test/java/org/springframework/cache/config/DefaultCacheableService.java
@@ -49,6 +49,18 @@ public class DefaultCacheableService implements CacheableService<Long> {
}
@Override
+ @Cacheable(cacheNames = "testCache", sync = true)
+ public Long cacheSync(Object arg1) {
+ return counter.getAndIncrement();
+ }
+
+ @Override
+ @Cacheable(cacheNames = "testCache", sync = true)
+ public Long cacheSyncNull(Object arg1) {
+ return null;
+ }
+
+ @Override
@CacheEvict("testCache")
public void invalidate(Object arg1) {
}
@@ -82,12 +94,18 @@ public class DefaultCacheableService implements CacheableService<Long> {
}
@Override
- @Cacheable(cacheNames = "testCache", condition = "#classField == 3")
+ @Cacheable(cacheNames = "testCache", condition = "#p0 == 3")
public Long conditional(int classField) {
return counter.getAndIncrement();
}
@Override
+ @Cacheable(cacheNames = "testCache", sync = true, condition = "#p0 == 3")
+ public Long conditionalSync(int classField) {
+ return counter.getAndIncrement();
+ }
+
+ @Override
@Cacheable(cacheNames = "testCache", unless = "#result > 10")
public Long unless(int arg) {
return (long) arg;
@@ -177,6 +195,18 @@ public class DefaultCacheableService implements CacheableService<Long> {
throw new UnsupportedOperationException(arg1.toString());
}
+ @Override
+ @Cacheable(cacheNames = "testCache", sync = true)
+ public Long throwCheckedSync(Object arg1) throws Exception {
+ throw new IOException(arg1.toString());
+ }
+
+ @Override
+ @Cacheable(cacheNames = "testCache", sync = true)
+ public Long throwUncheckedSync(Object arg1) {
+ throw new UnsupportedOperationException(arg1.toString());
+ }
+
// multi annotations
@Override
diff --git a/spring-context/src/test/java/org/springframework/cache/config/EnableCachingIntegrationTests.java b/spring-context/src/test/java/org/springframework/cache/config/EnableCachingIntegrationTests.java
index d51cc524..4ab2d706 100644
--- a/spring-context/src/test/java/org/springframework/cache/config/EnableCachingIntegrationTests.java
+++ b/spring-context/src/test/java/org/springframework/cache/config/EnableCachingIntegrationTests.java
@@ -1,7 +1,24 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package org.springframework.cache.config;
import java.util.concurrent.atomic.AtomicLong;
+import org.junit.After;
import org.junit.Test;
import org.springframework.cache.Cache;
@@ -11,7 +28,6 @@ import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
-import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
@@ -21,29 +37,37 @@ import org.springframework.context.annotation.Import;
import static org.springframework.cache.CacheTestUtils.*;
/**
- * Tests that represent real use cases with advanced configuration
+ * Tests that represent real use cases with advanced configuration.
+ *
* @author Stephane Nicoll
*/
public class EnableCachingIntegrationTests {
+ private ConfigurableApplicationContext context;
+
+ @After
+ public void closeContext() {
+ if (this.context != null) {
+ this.context.close();
+ }
+ }
+
@Test
public void fooServiceWithInterface() {
- ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(FooConfig.class);
- FooService service = context.getBean(FooService.class);
- fooGetSimple(context, service);
+ this.context = new AnnotationConfigApplicationContext(FooConfig.class);
+ FooService service = this.context.getBean(FooService.class);
+ fooGetSimple(service);
}
@Test
public void fooServiceWithInterfaceCglib() {
- ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(FooConfigCglib.class);
- FooService service = context.getBean(FooService.class);
- fooGetSimple(context, service);
+ this.context = new AnnotationConfigApplicationContext(FooConfigCglib.class);
+ FooService service = this.context.getBean(FooService.class);
+ fooGetSimple(service);
}
- private void fooGetSimple(ApplicationContext context, FooService service) {
- CacheManager cacheManager = context.getBean(CacheManager.class);
-
- Cache cache = cacheManager.getCache("testCache");
+ private void fooGetSimple(FooService service) {
+ Cache cache = getCache();
Object key = new Object();
assertCacheMiss(key, cache);
@@ -52,6 +76,21 @@ public class EnableCachingIntegrationTests {
assertCacheHit(key, value, cache);
}
+ @Test
+ public void beanCondition() {
+ this.context = new AnnotationConfigApplicationContext(BeanConditionConfig.class);
+ Cache cache = getCache();
+ FooService service = context.getBean(FooService.class);
+
+ Object key = new Object();
+ service.getWithCondition(key);
+ assertCacheMiss(key, cache);
+ }
+
+ private Cache getCache() {
+ return this.context.getBean(CacheManager.class).getCache("testCache");
+ }
+
@Configuration
static class SharedConfig extends CachingConfigurerSupport {
@Override
@@ -81,8 +120,10 @@ public class EnableCachingIntegrationTests {
}
}
- private static interface FooService {
- public Object getSimple(Object key);
+ private interface FooService {
+ Object getSimple(Object key);
+
+ Object getWithCondition(Object key);
}
@CacheConfig(cacheNames = "testCache")
@@ -94,6 +135,35 @@ public class EnableCachingIntegrationTests {
public Object getSimple(Object key) {
return counter.getAndIncrement();
}
+
+ @Override
+ @Cacheable(condition = "@bar.enabled")
+ public Object getWithCondition(Object key) {
+ return counter.getAndIncrement();
+ }
+ }
+
+ @Configuration
+ @Import(FooConfig.class)
+ @EnableCaching
+ static class BeanConditionConfig {
+
+ @Bean
+ public Bar bar() {
+ return new Bar(false);
+ }
+
+ static class Bar {
+ private final boolean enabled;
+
+ public Bar(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+ }
}
}
diff --git a/spring-context/src/test/java/org/springframework/cache/config/ExpressionCachingIntegrationTests.java b/spring-context/src/test/java/org/springframework/cache/config/ExpressionCachingIntegrationTests.java
index 3f1f5a9b..f00ddde3 100644
--- a/spring-context/src/test/java/org/springframework/cache/config/ExpressionCachingIntegrationTests.java
+++ b/spring-context/src/test/java/org/springframework/cache/config/ExpressionCachingIntegrationTests.java
@@ -92,6 +92,7 @@ public class ExpressionCachingIntegrationTests {
this.id = id;
}
+ @SuppressWarnings("unused")
public String getId() {
return id;
}
@@ -104,6 +105,7 @@ public class ExpressionCachingIntegrationTests {
this.id = id;
}
+ @SuppressWarnings("unused")
public String getId() {
return id;
}
diff --git a/spring-context/src/test/java/org/springframework/cache/interceptor/CacheSyncFailureTests.java b/spring-context/src/test/java/org/springframework/cache/interceptor/CacheSyncFailureTests.java
new file mode 100644
index 00000000..531bc01a
--- /dev/null
+++ b/spring-context/src/test/java/org/springframework/cache/interceptor/CacheSyncFailureTests.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cache.interceptor;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.CacheTestUtils;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.cache.annotation.Caching;
+import org.springframework.cache.annotation.CachingConfigurerSupport;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Provides various failure scenario linked to the use of {@link Cacheable#sync()}.
+ *
+ * @author Stephane Nicoll
+ * @since 4.3
+ */
+public class CacheSyncFailureTests {
+
+ @Rule
+ public final ExpectedException thrown = ExpectedException.none();
+
+ private ConfigurableApplicationContext context;
+
+ private SimpleService simpleService;
+
+ @Before
+ public void setUp() {
+ this.context = new AnnotationConfigApplicationContext(Config.class);
+ this.simpleService = context.getBean(SimpleService.class);
+ }
+
+ @After
+ public void closeContext() {
+ if (this.context != null) {
+ this.context.close();
+ }
+ }
+
+ @Test
+ public void unlessSync() {
+ thrown.expect(IllegalStateException.class);
+ thrown.expectMessage("@Cacheable(sync=true) does not support unless attribute");
+ this.simpleService.unlessSync("key");
+ }
+
+ @Test
+ public void severalCachesSync() {
+ thrown.expect(IllegalStateException.class);
+ thrown.expectMessage("@Cacheable(sync=true) only allows a single cache");
+ this.simpleService.severalCachesSync("key");
+ }
+
+ @Test
+ public void severalCachesWithResolvedSync() {
+ thrown.expect(IllegalStateException.class);
+ thrown.expectMessage("@Cacheable(sync=true) only allows a single cache");
+ this.simpleService.severalCachesWithResolvedSync("key");
+ }
+
+ @Test
+ public void syncWithAnotherOperation() {
+ thrown.expect(IllegalStateException.class);
+ thrown.expectMessage("@Cacheable(sync=true) cannot be combined with other cache operations");
+ this.simpleService.syncWithAnotherOperation("key");
+ }
+
+ @Test
+ public void syncWithTwoGetOperations() {
+ thrown.expect(IllegalStateException.class);
+ thrown.expectMessage("Only one @Cacheable(sync=true) entry is allowed");
+ this.simpleService.syncWithTwoGetOperations("key");
+ }
+
+
+ static class SimpleService {
+
+ private final AtomicLong counter = new AtomicLong();
+
+ @Cacheable(cacheNames = "testCache", sync = true, unless = "#result > 10")
+ public Object unlessSync(Object arg1) {
+ return this.counter.getAndIncrement();
+ }
+
+ @Cacheable(cacheNames = {"testCache", "anotherTestCache"}, sync = true)
+ public Object severalCachesSync(Object arg1) {
+ return this.counter.getAndIncrement();
+ }
+
+ @Cacheable(cacheResolver = "testCacheResolver", sync = true)
+ public Object severalCachesWithResolvedSync(Object arg1) {
+ return this.counter.getAndIncrement();
+ }
+
+ @Cacheable(cacheNames = "testCache", sync = true)
+ @CacheEvict(cacheNames = "anotherTestCache", key = "#arg1")
+ public Object syncWithAnotherOperation(Object arg1) {
+ return this.counter.getAndIncrement();
+ }
+
+ @Caching(cacheable = {
+ @Cacheable(cacheNames = "testCache", sync = true),
+ @Cacheable(cacheNames = "anotherTestCache", sync = true)
+ })
+ public Object syncWithTwoGetOperations(Object arg1) {
+ return this.counter.getAndIncrement();
+ }
+ }
+
+ @Configuration
+ @EnableCaching
+ static class Config extends CachingConfigurerSupport {
+
+ @Override
+ @Bean
+ public CacheManager cacheManager() {
+ return CacheTestUtils.createSimpleCacheManager("testCache", "anotherTestCache");
+ }
+
+ @Bean
+ public CacheResolver testCacheResolver() {
+ return new NamedCacheResolver(cacheManager(), "testCache", "anotherTestCache");
+ }
+
+ @Bean
+ public SimpleService simpleService() {
+ return new SimpleService();
+ }
+ }
+
+}
diff --git a/spring-context/src/test/java/org/springframework/cache/interceptor/ExpressionEvaluatorTests.java b/spring-context/src/test/java/org/springframework/cache/interceptor/ExpressionEvaluatorTests.java
index 508a21fd..6e3b5633 100644
--- a/spring-context/src/test/java/org/springframework/cache/interceptor/ExpressionEvaluatorTests.java
+++ b/spring-context/src/test/java/org/springframework/cache/interceptor/ExpressionEvaluatorTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,11 +23,15 @@ import java.util.Iterator;
import org.junit.Test;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.cache.annotation.AnnotationCacheOperationSource;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.context.expression.AnnotatedElementKey;
+import org.springframework.context.support.StaticApplicationContext;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.util.ReflectionUtils;
@@ -43,15 +47,17 @@ import static org.junit.Assert.*;
*/
public class ExpressionEvaluatorTests {
- private ExpressionEvaluator eval = new ExpressionEvaluator();
+ private final CacheOperationExpressionEvaluator eval = new CacheOperationExpressionEvaluator();
+
+ private final AnnotationCacheOperationSource source = new AnnotationCacheOperationSource();
- private AnnotationCacheOperationSource source = new AnnotationCacheOperationSource();
private Collection<CacheOperation> getOps(String name) {
Method method = ReflectionUtils.findMethod(AnnotatedClass.class, name, Object.class, Object.class);
- return source.getCacheOperations(method, AnnotatedClass.class);
+ return this.source.getCacheOperations(method, AnnotatedClass.class);
}
+
@Test
public void testMultipleCachingSource() throws Exception {
Collection<CacheOperation> ops = getOps("multipleCaching");
@@ -75,7 +81,8 @@ public class ExpressionEvaluatorTests {
Object[] args = new Object[] { new Object(), new Object() };
Collection<ConcurrentMapCache> caches = Collections.singleton(new ConcurrentMapCache("test"));
- EvaluationContext evalCtx = eval.createEvaluationContext(caches, method, args, target, target.getClass());
+ EvaluationContext evalCtx = eval.createEvaluationContext(caches, method, args,
+ target, target.getClass(), null);
Collection<CacheOperation> ops = getOps("multipleCaching");
Iterator<CacheOperation> it = ops.iterator();
@@ -105,14 +112,14 @@ public class ExpressionEvaluatorTests {
@Test
public void withoutReturnValue() throws Exception {
- EvaluationContext context = createEvaluationContext(ExpressionEvaluator.NO_RESULT);
+ EvaluationContext context = createEvaluationContext(CacheOperationExpressionEvaluator.NO_RESULT);
Object value = new SpelExpressionParser().parseExpression("#result").getValue(context);
assertThat(value, nullValue());
}
@Test
public void unavailableReturnValue() throws Exception {
- EvaluationContext context = createEvaluationContext(ExpressionEvaluator.RESULT_UNAVAILABLE);
+ EvaluationContext context = createEvaluationContext(CacheOperationExpressionEvaluator.RESULT_UNAVAILABLE);
try {
new SpelExpressionParser().parseExpression("#result").getValue(context);
fail("Should have failed to parse expression, result not available");
@@ -122,14 +129,29 @@ public class ExpressionEvaluatorTests {
}
}
+ @Test
+ public void resolveBeanReference() throws Exception {
+ StaticApplicationContext applicationContext = new StaticApplicationContext();
+ BeanDefinition beanDefinition = new RootBeanDefinition(String.class);
+ applicationContext.registerBeanDefinition("myBean", beanDefinition);
+ applicationContext.refresh();
+
+ EvaluationContext context = createEvaluationContext(CacheOperationExpressionEvaluator.NO_RESULT, applicationContext);
+ Object value = new SpelExpressionParser().parseExpression("@myBean.class.getName()").getValue(context);
+ assertThat(value, is(String.class.getName()));
+ }
+
private EvaluationContext createEvaluationContext(Object result) {
+ return createEvaluationContext(result, null);
+ }
+
+ private EvaluationContext createEvaluationContext(Object result, BeanFactory beanFactory) {
AnnotatedClass target = new AnnotatedClass();
Method method = ReflectionUtils.findMethod(AnnotatedClass.class, "multipleCaching", Object.class,
Object.class);
Object[] args = new Object[] { new Object(), new Object() };
Collection<ConcurrentMapCache> caches = Collections.singleton(new ConcurrentMapCache("test"));
- EvaluationContext context = eval.createEvaluationContext(caches, method, args, target, target.getClass(), result);
- return context;
+ return eval.createEvaluationContext(caches, method, args, target, target.getClass(), result, beanFactory);
}
diff --git a/spring-context/src/test/java/org/springframework/context/annotation/AbstractCircularImportDetectionTests.java b/spring-context/src/test/java/org/springframework/context/annotation/AbstractCircularImportDetectionTests.java
index 10aa8ee9..d73412eb 100644
--- a/spring-context/src/test/java/org/springframework/context/annotation/AbstractCircularImportDetectionTests.java
+++ b/spring-context/src/test/java/org/springframework/context/annotation/AbstractCircularImportDetectionTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -72,58 +72,71 @@ public abstract class AbstractCircularImportDetectionTests {
@Configuration
@Import(B.class)
static class A {
+
@Bean
TestBean b1() {
return new TestBean();
}
}
+
@Configuration
@Import(A.class)
static class B {
+
@Bean
TestBean b2() {
return new TestBean();
}
}
+
@Configuration
- @Import( { Y.class, Z.class })
+ @Import({Y.class, Z.class})
class X {
+
@Bean
TestBean x() {
return new TestBean();
}
}
+
@Configuration
class Y {
+
@Bean
TestBean y() {
return new TestBean();
}
}
+
@Configuration
- @Import( { Z1.class, Z2.class })
+ @Import({Z1.class, Z2.class})
class Z {
+
@Bean
TestBean z() {
return new TestBean();
}
}
+
@Configuration
class Z1 {
+
@Bean
TestBean z1() {
return new TestBean();
}
}
+
@Configuration
@Import(Z.class)
class Z2 {
+
@Bean
TestBean z2() {
return new TestBean();
diff --git a/spring-context/src/test/java/org/springframework/context/annotation/AsmCircularImportDetectionTests.java b/spring-context/src/test/java/org/springframework/context/annotation/AsmCircularImportDetectionTests.java
index 8cb5152e..c0c32c38 100644
--- a/spring-context/src/test/java/org/springframework/context/annotation/AsmCircularImportDetectionTests.java
+++ b/spring-context/src/test/java/org/springframework/context/annotation/AsmCircularImportDetectionTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author 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,12 +23,8 @@ import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
/**
- * Unit test proving that ASM-based {@link ConfigurationClassParser} correctly detects circular
- * use of the {@link Import @Import} annotation.
- *
- * <p>While this test is the only subclass of {@link AbstractCircularImportDetectionTests},
- * the hierarchy remains in place in case a JDT-based ConfigurationParser implementation
- * needs to be developed.
+ * Unit test proving that ASM-based {@link ConfigurationClassParser} correctly detects
+ * circular use of the {@link Import @Import} annotation.
*
* @author Chris Beams
*/
diff --git a/spring-context/src/test/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessorTests.java b/spring-context/src/test/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessorTests.java
index d4f47fb0..2dfd1ef0 100644
--- a/spring-context/src/test/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessorTests.java
+++ b/spring-context/src/test/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessorTests.java
@@ -560,6 +560,11 @@ public class CommonAnnotationBeanPostProcessorTests {
assertFalse(((AnnotatedInitDestroyBean) bean).destroyCalled);
}
}
+
+ @Override
+ public boolean requiresDestruction(Object bean) {
+ return true;
+ }
}
diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ComponentScanAndImportAnnotationInteractionTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ComponentScanAndImportAnnotationInteractionTests.java
index 177e153e..96b18066 100644
--- a/spring-context/src/test/java/org/springframework/context/annotation/ComponentScanAndImportAnnotationInteractionTests.java
+++ b/spring-context/src/test/java/org/springframework/context/annotation/ComponentScanAndImportAnnotationInteractionTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@ package org.springframework.context.annotation;
import org.junit.Test;
import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.context.annotation.componentscan.importing.ImportingConfig;
import org.springframework.context.annotation.componentscan.simple.SimpleComponent;
/**
@@ -58,7 +59,7 @@ public class ComponentScanAndImportAnnotationInteractionTests {
@Test
public void componentScanViaImportUsingAsm() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
- ctx.registerBeanDefinition("config4", new RootBeanDefinition(Config3.class.getName()));
+ ctx.registerBeanDefinition("config", new RootBeanDefinition(Config3.class.getName()));
ctx.refresh();
ctx.getBean(SimpleComponent.class);
}
@@ -71,6 +72,14 @@ public class ComponentScanAndImportAnnotationInteractionTests {
ctx.getBean(SimpleComponent.class);
}
+ @Test
+ public void circularImportViaComponentScan() {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
+ ctx.registerBeanDefinition("config", new RootBeanDefinition(ImportingConfig.class.getName()));
+ ctx.refresh();
+ ctx.getBean(SimpleComponent.class);
+ }
+
@ComponentScan("org.springframework.context.annotation.componentscan.simple")
static final class Config1 {
@@ -88,6 +97,7 @@ public class ComponentScanAndImportAnnotationInteractionTests {
@ComponentScan("org.springframework.context.annotation.componentscan.simple")
+ @ComponentScan("org.springframework.context.annotation.componentscan.importing")
public static final class ImportedConfig {
}
diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationIntegrationTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationIntegrationTests.java
index f579aa32..2c51a7c8 100644
--- a/spring-context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationIntegrationTests.java
+++ b/spring-context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationIntegrationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,18 +33,31 @@ import example.scannable_implicitbasepackage.ComponentScanAnnotatedConfigWithImp
import example.scannable_implicitbasepackage.ConfigurableComponent;
import example.scannable_scoped.CustomScopeAnnotationBean;
import example.scannable_scoped.MyScope;
+
import org.junit.Test;
import org.springframework.aop.support.AopUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanClassLoaderAware;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.CustomAutowireConfigurer;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.context.EnvironmentAware;
+import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.ComponentScanParserTests.KustomAnnotationAutowiredBean;
import org.springframework.context.annotation.componentscan.simple.ClassWithNestedComponents;
import org.springframework.context.annotation.componentscan.simple.SimpleComponent;
import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.Environment;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.core.type.classreading.MetadataReader;
+import org.springframework.core.type.classreading.MetadataReaderFactory;
+import org.springframework.core.type.filter.TypeFilter;
import org.springframework.tests.context.SimpleMapScope;
import org.springframework.util.SerializationTestUtils;
@@ -159,6 +172,14 @@ public class ComponentScanAnnotationIntegrationTests {
// custom scope annotation makes the bean prototype scoped. subsequent calls
// to getBean should return distinct instances.
assertThat(ctx.getBean(CustomScopeAnnotationBean.class), not(sameInstance(ctx.getBean(CustomScopeAnnotationBean.class))));
+ assertThat(ctx.containsBean("scannedComponent"), is(false));
+ }
+
+ @Test
+ public void multiComponentScan() {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MultiComponentScan.class);
+ assertThat(ctx.getBean(CustomScopeAnnotationBean.class), not(sameInstance(ctx.getBean(CustomScopeAnnotationBean.class))));
+ assertThat(ctx.containsBean("scannedComponent"), is(true));
}
@Test
@@ -170,6 +191,12 @@ public class ComponentScanAnnotationIntegrationTests {
}
@Test
+ public void withAwareTypeFilter() {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ComponentScanWithAwareTypeFilter.class);
+ assertTrue(ctx.getEnvironment().acceptsProfiles("the-filter-ran"));
+ }
+
+ @Test
public void withScopedProxy() throws IOException, ClassNotFoundException {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ComponentScanWithScopedProxy.class);
@@ -250,6 +277,47 @@ public class ComponentScanAnnotationIntegrationTests {
public static class ComposedAnnotationConfig {
}
+ public static class AwareTypeFilter implements TypeFilter, EnvironmentAware,
+ ResourceLoaderAware, BeanClassLoaderAware, BeanFactoryAware {
+
+ private BeanFactory beanFactory;
+ private ClassLoader classLoader;
+ private ResourceLoader resourceLoader;
+ private Environment environment;
+
+ @Override
+ public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
+ this.beanFactory = beanFactory;
+ }
+
+ @Override
+ public void setBeanClassLoader(ClassLoader classLoader) {
+ this.classLoader = classLoader;
+ }
+
+ @Override
+ public void setResourceLoader(ResourceLoader resourceLoader) {
+ this.resourceLoader = resourceLoader;
+ }
+
+ @Override
+ public void setEnvironment(Environment environment) {
+ this.environment = environment;
+ }
+
+ @Override
+ public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) {
+ ((ConfigurableEnvironment) this.environment).addActiveProfile("the-filter-ran");
+ assertNotNull(this.beanFactory);
+ assertNotNull(this.classLoader);
+ assertNotNull(this.resourceLoader);
+ assertNotNull(this.environment);
+ return false;
+ }
+
+ }
+
+
}
@@ -296,6 +364,12 @@ class MyBeanNameGenerator extends AnnotationBeanNameGenerator {
class ComponentScanWithScopeResolver {
}
+@Configuration
+@ComponentScan(basePackages = "example.scannable_scoped", scopeResolver = MyScopeMetadataResolver.class)
+@ComponentScan(basePackages = "example.scannable_implicitbasepackage")
+class MultiComponentScan {
+}
+
class MyScopeMetadataResolver extends AnnotationScopeMetadataResolver {
MyScopeMetadataResolver() {
@@ -327,6 +401,14 @@ class ComponentScanWithCustomTypeFilter {
}
@Configuration
+@ComponentScan(
+ basePackages = "org.springframework.context.annotation",
+ useDefaultFilters = false,
+ includeFilters = @Filter(type = FilterType.CUSTOM, classes = ComponentScanAnnotationIntegrationTests.AwareTypeFilter.class),
+ lazyInit = true)
+class ComponentScanWithAwareTypeFilter {}
+
+@Configuration
@ComponentScan(basePackages = "example.scannable",
scopedProxy = ScopedProxyMode.INTERFACES,
useDefaultFilters = false,
@@ -370,3 +452,5 @@ class ComponentScanWithMultipleAnnotationIncludeFilters2 {}
basePackages = "example.scannable",
basePackageClasses = example.scannable._package.class)
class ComponentScanWithBasePackagesAndValueAlias {}
+
+
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 5bebb953..8c96aacd 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
@@ -955,7 +955,7 @@ public class ConfigurationClassPostProcessorTests {
@ComponentScan(basePackages = "org.springframework.context.annotation.componentscan.simple")
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
- public static @interface ComposedConfiguration {
+ public @interface ComposedConfiguration {
}
@ComposedConfiguration
@@ -966,7 +966,7 @@ public class ConfigurationClassPostProcessorTests {
@ComponentScan
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
- public static @interface ComposedConfigurationWithAttributeOverrides {
+ public @interface ComposedConfigurationWithAttributeOverrides {
String[] basePackages() default {};
@@ -985,7 +985,7 @@ public class ConfigurationClassPostProcessorTests {
@ComposedConfigurationWithAttributeOverrides
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
- public static @interface ComposedComposedConfigurationWithAttributeOverrides {
+ public @interface ComposedComposedConfigurationWithAttributeOverrides {
String[] basePackages() default {};
}
@@ -997,14 +997,14 @@ public class ConfigurationClassPostProcessorTests {
@ComponentScan
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
- public static @interface MetaComponentScan {
+ public @interface MetaComponentScan {
}
@MetaComponentScan
@Configuration
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
- public static @interface MetaComponentScanConfigurationWithAttributeOverrides {
+ public @interface MetaComponentScanConfigurationWithAttributeOverrides {
String[] basePackages() default {};
}
diff --git a/spring-context/src/test/java/org/springframework/context/annotation/EnableAspectJAutoProxyTests.java b/spring-context/src/test/java/org/springframework/context/annotation/EnableAspectJAutoProxyTests.java
index 9995fc5a..feeee8a8 100644
--- a/spring-context/src/test/java/org/springframework/context/annotation/EnableAspectJAutoProxyTests.java
+++ b/spring-context/src/test/java/org/springframework/context/annotation/EnableAspectJAutoProxyTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,11 +20,13 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import example.scannable.FooService;
+import example.scannable.FooServiceImpl;
import example.scannable.ServiceInvocationCounter;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.junit.Test;
+import org.springframework.aop.framework.AopContext;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
@@ -54,6 +56,14 @@ public class EnableAspectJAutoProxyTests {
assertThat(AopUtils.isCglibProxy(ctx.getBean(FooService.class)), is(true));
}
+ @Test
+ public void withExposedProxy() {
+ ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigWithExposedProxy.class);
+
+ aspectIsApplied(ctx);
+ assertThat(AopUtils.isJdkDynamicProxy(ctx.getBean(FooService.class)), is(true));
+ }
+
private void aspectIsApplied(ApplicationContext ctx) {
FooService fooService = ctx.getBean(FooService.class);
ServiceInvocationCounter counter = ctx.getBean(ServiceInvocationCounter.class);
@@ -96,30 +106,49 @@ public class EnableAspectJAutoProxyTests {
}
- @Configuration
@ComponentScan("example.scannable")
@EnableAspectJAutoProxy
static class ConfigWithJdkProxy {
}
- @Configuration
+
@ComponentScan("example.scannable")
@EnableAspectJAutoProxy(proxyTargetClass = true)
static class ConfigWithCglibProxy {
}
+ @ComponentScan("example.scannable")
+ @EnableAspectJAutoProxy(exposeProxy = true)
+ static class ConfigWithExposedProxy {
+
+ @Bean
+ public FooService fooServiceImpl() {
+ return new FooServiceImpl() {
+ @Override
+ public String foo(int id) {
+ assertNotNull(AopContext.currentProxy());
+ return super.foo(id);
+ }
+ };
+ }
+ }
+
+
@Retention(RetentionPolicy.RUNTIME)
public @interface Loggable {
}
+
@Loggable
public static class SampleDto {
}
+
public static class SampleInputBean {
}
+
public static class SampleService {
// Not matched method on {@link LoggingAspect}.
@@ -131,6 +160,7 @@ public class EnableAspectJAutoProxyTests {
}
}
+
@Aspect
public static class LoggingAspect {
diff --git a/spring-context/src/test/java/org/springframework/context/annotation/PropertySourceAnnotationTests.java b/spring-context/src/test/java/org/springframework/context/annotation/PropertySourceAnnotationTests.java
index 7150c4b0..b20afea1 100644
--- a/spring-context/src/test/java/org/springframework/context/annotation/PropertySourceAnnotationTests.java
+++ b/spring-context/src/test/java/org/springframework/context/annotation/PropertySourceAnnotationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author 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,8 +17,12 @@
package org.springframework.context.annotation;
import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
import java.util.Iterator;
+import java.util.Properties;
import javax.inject.Inject;
import org.junit.Rule;
@@ -27,9 +31,13 @@ import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.FactoryBean;
+import org.springframework.core.annotation.AliasFor;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
+import org.springframework.core.io.support.EncodedResource;
+import org.springframework.core.io.support.PropertiesLoaderUtils;
+import org.springframework.core.io.support.PropertySourceFactory;
import org.springframework.tests.sample.beans.TestBean;
import static org.hamcrest.CoreMatchers.*;
@@ -64,7 +72,7 @@ public class PropertySourceAnnotationTests {
do {
name = iterator.next().getName();
}
- while(iterator.hasNext());
+ while (iterator.hasNext());
assertThat(name, is("p1"));
}
@@ -112,6 +120,22 @@ public class PropertySourceAnnotationTests {
}
@Test
+ public void withCustomFactory() {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
+ ctx.register(ConfigWithImplicitName.class, WithCustomFactory.class);
+ ctx.refresh();
+ assertThat(ctx.getBean(TestBean.class).getName(), equalTo("P2TESTBEAN"));
+ }
+
+ @Test
+ public void withCustomFactoryAsMeta() {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
+ ctx.register(ConfigWithImplicitName.class, WithCustomFactoryAsMeta.class);
+ ctx.refresh();
+ assertThat(ctx.getBean(TestBean.class).getName(), equalTo("P2TESTBEAN"));
+ }
+
+ @Test
public void withUnresolvablePlaceholder() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ConfigWithUnresolvablePlaceholder.class);
@@ -355,6 +379,43 @@ public class PropertySourceAnnotationTests {
@Configuration
+ @PropertySource(value = "classpath:org/springframework/context/annotation/p2.properties", factory = MyCustomFactory.class)
+ static class WithCustomFactory {
+ }
+
+
+ @Configuration
+ @MyPropertySource(value = "classpath:org/springframework/context/annotation/p2.properties")
+ static class WithCustomFactoryAsMeta {
+ }
+
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @PropertySource(value = {}, factory = MyCustomFactory.class)
+ public @interface MyPropertySource {
+
+ @AliasFor(annotation = PropertySource.class)
+ String value();
+ }
+
+
+ public static class MyCustomFactory implements PropertySourceFactory {
+
+ @Override
+ public org.springframework.core.env.PropertySource createPropertySource(String name, EncodedResource resource) throws IOException {
+ Properties props = PropertiesLoaderUtils.loadProperties(resource);
+ return new org.springframework.core.env.PropertySource<Properties>("my" + name, props) {
+ @Override
+ public Object getProperty(String name) {
+ String value = props.getProperty(name);
+ return (value != null ? value.toUpperCase() : null);
+ }
+ };
+ }
+ }
+
+
+ @Configuration
@PropertySource(
name = "psName",
value = {
diff --git a/spring-context/src/test/java/org/springframework/context/annotation/Spr12278Tests.java b/spring-context/src/test/java/org/springframework/context/annotation/Spr12278Tests.java
new file mode 100644
index 00000000..13dcbccc
--- /dev/null
+++ b/spring-context/src/test/java/org/springframework/context/annotation/Spr12278Tests.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2002-2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.context.annotation;
+
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import org.springframework.beans.factory.BeanCreationException;
+
+import static org.hamcrest.core.Is.*;
+import static org.junit.Assert.*;
+
+/**
+ * @author Stephane Nicoll
+ */
+public class Spr12278Tests {
+
+ @Rule
+ public final ExpectedException thrown = ExpectedException.none();
+
+ private AnnotationConfigApplicationContext context;
+
+ @After
+ public void close() {
+ if (context != null) {
+ context.close();
+ }
+ }
+
+ @Test
+ public void componentSingleConstructor() {
+ this.context = new AnnotationConfigApplicationContext(BaseConfiguration.class,
+ SingleConstructorComponent.class);
+ assertThat(this.context.getBean(SingleConstructorComponent.class).autowiredName, is("foo"));
+ }
+
+ @Test
+ public void componentTwoConstructorsNoHint() {
+ this.context = new AnnotationConfigApplicationContext(BaseConfiguration.class,
+ TwoConstructorsComponent.class);
+ assertThat(this.context.getBean(TwoConstructorsComponent.class).name, is("fallback"));
+ }
+
+ @Test
+ public void componentTwoSpecificConstructorsNoHint() {
+ thrown.expect(BeanCreationException.class);
+ thrown.expectMessage(NoSuchMethodException.class.getName());
+ new AnnotationConfigApplicationContext(BaseConfiguration.class,
+ TwoSpecificConstructorsComponent.class);
+ }
+
+
+ @Configuration
+ static class BaseConfiguration {
+
+ @Bean
+ public String autowiredName() {
+ return "foo";
+ }
+ }
+
+ private static class SingleConstructorComponent {
+
+ private final String autowiredName;
+
+ // No @Autowired - implicit wiring
+ public SingleConstructorComponent(String autowiredName) {
+ this.autowiredName = autowiredName;
+ }
+
+ }
+
+ private static class TwoConstructorsComponent {
+
+ private final String name;
+
+ public TwoConstructorsComponent(String name) {
+ this.name = name;
+ }
+
+ public TwoConstructorsComponent() {
+ this("fallback");
+ }
+ }
+
+ private static class TwoSpecificConstructorsComponent {
+
+ private final Integer counter;
+
+ public TwoSpecificConstructorsComponent(Integer counter) {
+ this.counter = counter;
+ }
+
+ public TwoSpecificConstructorsComponent(String name) {
+ this(Integer.valueOf(name));
+ }
+ }
+
+}
diff --git a/spring-context/src/test/java/org/springframework/context/annotation/configuration/AutowiredConfigurationTests.java b/spring-context/src/test/java/org/springframework/context/annotation/configuration/AutowiredConfigurationTests.java
index 651bff79..cedd45bd 100644
--- a/spring-context/src/test/java/org/springframework/context/annotation/configuration/AutowiredConfigurationTests.java
+++ b/spring-context/src/test/java/org/springframework/context/annotation/configuration/AutowiredConfigurationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,14 +17,16 @@
package org.springframework.context.annotation.configuration;
import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.List;
import java.util.Optional;
import javax.inject.Provider;
import org.junit.Test;
-import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
@@ -36,6 +38,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.core.annotation.AliasFor;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.tests.sample.beans.Colour;
@@ -89,19 +92,40 @@ public class AutowiredConfigurationTests {
assertThat(context.getBean(TestBean.class).getName(), equalTo(""));
}
- /**
- * {@link Autowired} constructors are not supported on {@link Configuration} classes
- * due to CGLIB constraints
- */
- @Test(expected = BeanCreationException.class)
- public void testAutowiredConfigurationConstructorsAreNotSupported() {
- DefaultListableBeanFactory context = new DefaultListableBeanFactory();
- new XmlBeanDefinitionReader(context).loadBeanDefinitions(
+ @Test
+ public void testAutowiredSingleConstructorSupported() {
+ DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
+ new XmlBeanDefinitionReader(factory).loadBeanDefinitions(
new ClassPathResource("annotation-config.xml", AutowiredConstructorConfig.class));
- GenericApplicationContext ctx = new GenericApplicationContext(context);
+ GenericApplicationContext ctx = new GenericApplicationContext(factory);
ctx.registerBeanDefinition("config1", new RootBeanDefinition(AutowiredConstructorConfig.class));
ctx.registerBeanDefinition("config2", new RootBeanDefinition(ColorConfig.class));
- ctx.refresh(); // should throw
+ ctx.refresh();
+ assertSame(ctx.getBean(AutowiredConstructorConfig.class).colour, ctx.getBean(Colour.class));
+ }
+
+ @Test
+ public void testObjectFactoryConstructorWithTypeVariable() {
+ DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
+ new XmlBeanDefinitionReader(factory).loadBeanDefinitions(
+ new ClassPathResource("annotation-config.xml", ObjectFactoryConstructorConfig.class));
+ GenericApplicationContext ctx = new GenericApplicationContext(factory);
+ ctx.registerBeanDefinition("config1", new RootBeanDefinition(ObjectFactoryConstructorConfig.class));
+ ctx.registerBeanDefinition("config2", new RootBeanDefinition(ColorConfig.class));
+ ctx.refresh();
+ assertSame(ctx.getBean(ObjectFactoryConstructorConfig.class).colour, ctx.getBean(Colour.class));
+ }
+
+ @Test
+ public void testAutowiredAnnotatedConstructorSupported() {
+ DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
+ new XmlBeanDefinitionReader(factory).loadBeanDefinitions(
+ new ClassPathResource("annotation-config.xml", MultipleConstructorConfig.class));
+ GenericApplicationContext ctx = new GenericApplicationContext(factory);
+ ctx.registerBeanDefinition("config1", new RootBeanDefinition(MultipleConstructorConfig.class));
+ ctx.registerBeanDefinition("config2", new RootBeanDefinition(ColorConfig.class));
+ ctx.refresh();
+ assertSame(ctx.getBean(MultipleConstructorConfig.class).colour, ctx.getBean(Colour.class));
}
@Test
@@ -112,6 +136,20 @@ public class AutowiredConfigurationTests {
}
@Test
+ public void testValueInjectionWithMetaAnnotation() {
+ AnnotationConfigApplicationContext context =
+ new AnnotationConfigApplicationContext(ValueConfigWithMetaAnnotation.class);
+ doTestValueInjection(context);
+ }
+
+ @Test
+ public void testValueInjectionWithAliasedMetaAnnotation() {
+ AnnotationConfigApplicationContext context =
+ new AnnotationConfigApplicationContext(ValueConfigWithAliasedMetaAnnotation.class);
+ doTestValueInjection(context);
+ }
+
+ @Test
public void testValueInjectionWithProviderFields() {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(ValueConfigWithProviderFields.class);
@@ -225,7 +263,7 @@ public class AutowiredConfigurationTests {
Colour colour;
- @Autowired
+ // @Autowired
AutowiredConstructorConfig(Colour colour) {
this.colour = colour;
}
@@ -233,6 +271,34 @@ public class AutowiredConfigurationTests {
@Configuration
+ static class ObjectFactoryConstructorConfig {
+
+ Colour colour;
+
+ // @Autowired
+ ObjectFactoryConstructorConfig(ObjectFactory<Colour> colourFactory) {
+ this.colour = colourFactory.getObject();
+ }
+ }
+
+
+ @Configuration
+ static class MultipleConstructorConfig {
+
+ Colour colour;
+
+ @Autowired
+ MultipleConstructorConfig(Colour colour) {
+ this.colour = colour;
+ }
+
+ MultipleConstructorConfig(String test) {
+ this.colour = new Colour(test);
+ }
+ }
+
+
+ @Configuration
static class ColorConfig {
@Bean
@@ -267,6 +333,73 @@ public class AutowiredConfigurationTests {
}
+ @Value("#{systemProperties[myProp]}")
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface MyProp {
+ }
+
+
+ @Configuration
+ @Scope("prototype")
+ static class ValueConfigWithMetaAnnotation {
+
+ @MyProp
+ private String name;
+
+ private String name2;
+
+ @MyProp
+ public void setName2(String name) {
+ this.name2 = name;
+ }
+
+ @Bean @Scope("prototype")
+ public TestBean testBean() {
+ return new TestBean(name);
+ }
+
+ @Bean @Scope("prototype")
+ public TestBean testBean2() {
+ return new TestBean(name2);
+ }
+ }
+
+
+ @Value("")
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface AliasedProp {
+
+ @AliasFor(annotation = Value.class)
+ String value();
+ }
+
+
+ @Configuration
+ @Scope("prototype")
+ static class ValueConfigWithAliasedMetaAnnotation {
+
+ @AliasedProp("#{systemProperties[myProp]}")
+ private String name;
+
+ private String name2;
+
+ @AliasedProp("#{systemProperties[myProp]}")
+ public void setName2(String name) {
+ this.name2 = name;
+ }
+
+ @Bean @Scope("prototype")
+ public TestBean testBean() {
+ return new TestBean(name);
+ }
+
+ @Bean @Scope("prototype")
+ public TestBean testBean2() {
+ return new TestBean(name2);
+ }
+ }
+
+
@Configuration
static class ValueConfigWithProviderFields {
diff --git a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassAspectIntegrationTests.java b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassAspectIntegrationTests.java
index 2e56b557..77f2df5e 100644
--- a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassAspectIntegrationTests.java
+++ b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassAspectIntegrationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -82,7 +82,9 @@ public class ConfigurationClassAspectIntegrationTests {
public void withInnerClassAndLambdaExpression() {
ApplicationContext ctx = new AnnotationConfigApplicationContext(Application.class, CountingAspect.class);
ctx.getBeansOfType(Runnable.class).forEach((k, v) -> v.run());
- assertEquals(2, ctx.getBean(CountingAspect.class).count);
+
+ // TODO: returns just 1 as of AspectJ 1.9 beta 3, not detecting the applicable lambda expression anymore
+ // assertEquals(2, ctx.getBean(CountingAspect.class).count);
}
diff --git a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java
index 78b990b1..bf4c9e6a 100644
--- a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java
+++ b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,8 +27,11 @@ import org.junit.Test;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.InjectionPoint;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor;
import org.springframework.beans.factory.annotation.Value;
@@ -36,6 +39,7 @@ import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.config.ListFactoryBean;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
@@ -71,7 +75,7 @@ public class ConfigurationClassProcessingTests {
* When complete, the factory is ready to service requests for any {@link Bean} methods
* declared by <var>configClasses</var>.
*/
- private ListableBeanFactory initBeanFactory(Class<?>... configClasses) {
+ private DefaultListableBeanFactory initBeanFactory(Class<?>... configClasses) {
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
for (Class<?> configClass : configClasses) {
String configBeanName = configClass.getName();
@@ -111,7 +115,7 @@ public class ConfigurationClassProcessingTests {
BeanFactory factory = initBeanFactory(ConfigWithBeanWithAliases.class);
assertSame(factory.getBean("name1"), ConfigWithBeanWithAliases.testBean);
String[] aliases = factory.getAliases("name1");
- for(String alias : aliases)
+ for (String alias : aliases)
assertSame(factory.getBean(alias), ConfigWithBeanWithAliases.testBean);
// method name should not be registered
@@ -203,6 +207,21 @@ public class ConfigurationClassProcessingTests {
}
@Test
+ public void configurationWithAdaptivePrototypes() {
+ AnnotationConfigApplicationContext factory = new AnnotationConfigApplicationContext();
+ factory.register(ConfigWithPrototypeBean.class, AdaptiveInjectionPoints.class);
+ factory.refresh();
+
+ AdaptiveInjectionPoints adaptive = factory.getBean(AdaptiveInjectionPoints.class);
+ assertEquals("adaptiveInjectionPoint1", adaptive.adaptiveInjectionPoint1.getName());
+ assertEquals("setAdaptiveInjectionPoint2", adaptive.adaptiveInjectionPoint2.getName());
+
+ adaptive = factory.getBean(AdaptiveInjectionPoints.class);
+ assertEquals("adaptiveInjectionPoint1", adaptive.adaptiveInjectionPoint1.getName());
+ assertEquals("setAdaptiveInjectionPoint2", adaptive.adaptiveInjectionPoint2.getName());
+ }
+
+ @Test
public void configurationWithPostProcessor() {
AnnotationConfigApplicationContext factory = new AnnotationConfigApplicationContext();
factory.register(ConfigWithPostProcessor.class);
@@ -324,6 +343,31 @@ public class ConfigurationClassProcessingTests {
public TestBean baz() {
return new TestBean("baz");
}
+
+ @Bean @Scope("prototype")
+ public TestBean adaptive1(InjectionPoint ip) {
+ return new TestBean(ip.getMember().getName());
+ }
+
+ @Bean @Scope("prototype")
+ public TestBean adaptive2(DependencyDescriptor dd) {
+ return new TestBean(dd.getMember().getName());
+ }
+ }
+
+
+ @Scope("prototype")
+ static class AdaptiveInjectionPoints {
+
+ @Autowired @Qualifier("adaptive1")
+ public TestBean adaptiveInjectionPoint1;
+
+ public TestBean adaptiveInjectionPoint2;
+
+ @Autowired @Qualifier("adaptive2")
+ public void setAdaptiveInjectionPoint2(TestBean adaptiveInjectionPoint2) {
+ this.adaptiveInjectionPoint2 = adaptiveInjectionPoint2;
+ }
}
diff --git a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassWithPlaceholderConfigurerBeanTests.java b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassWithPlaceholderConfigurerBeanTests.java
index 43c3397b..3c8d933d 100644
--- a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassWithPlaceholderConfigurerBeanTests.java
+++ b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassWithPlaceholderConfigurerBeanTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author 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,15 +41,16 @@ import static org.junit.Assert.*;
* and @Value fields in the same configuration class are mutually exclusive.
*
* @author Chris Beams
+ * @author Juergen Hoeller
*/
public class ConfigurationClassWithPlaceholderConfigurerBeanTests {
/**
* Intentionally ignored test proving that a property placeholder bean
* cannot be declared in the same configuration class that has a @Value
- * field in need of placeholder replacement. It's an obvious chicken-and-egg issue.
+ * field in need of placeholder replacement. It's an obvious chicken-and-egg issue.
* The solution is to do as {@link #valueFieldsAreProcessedWhenPlaceholderConfigurerIsSegregated()}
- * does and segragate the two bean definitions across configuration classes.
+ * does and segregate the two bean definitions across configuration classes.
*/
@Ignore @Test
public void valueFieldsAreNotProcessedWhenPlaceholderConfigurerIsIntegrated() {
@@ -75,44 +76,57 @@ public class ConfigurationClassWithPlaceholderConfigurerBeanTests {
TestBean testBean = ctx.getBean(TestBean.class);
assertThat(testBean.getName(), equalTo("foo"));
}
-}
-@Configuration
-class ConfigWithValueField {
+ @Test
+ public void valueFieldsResolveToPlaceholderSpecifiedDefaultValue() {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
+ ctx.register(ConfigWithValueField.class);
+ ctx.register(ConfigWithPlaceholderConfigurer.class);
+ ctx.refresh();
+
+ TestBean testBean = ctx.getBean(TestBean.class);
+ assertThat(testBean.getName(), equalTo("bar"));
+ }
+
- @Value("${test.name}")
- private String name;
+ @Configuration
+ static class ConfigWithValueField {
- @Bean
- public ITestBean testBean() {
- return new TestBean(this.name);
+ @Value("${test.name:bar}")
+ private String name;
+
+ @Bean
+ public ITestBean testBean() {
+ return new TestBean(this.name);
+ }
}
-}
-@Configuration
-class ConfigWithPlaceholderConfigurer {
- @Bean
- public PropertySourcesPlaceholderConfigurer ppc() {
- return new PropertySourcesPlaceholderConfigurer();
+ @Configuration
+ static class ConfigWithPlaceholderConfigurer {
+
+ @Bean
+ public PropertySourcesPlaceholderConfigurer ppc() {
+ return new PropertySourcesPlaceholderConfigurer();
+ }
}
-}
-@Configuration
-class ConfigWithValueFieldAndPlaceholderConfigurer {
+ @Configuration
+ static class ConfigWithValueFieldAndPlaceholderConfigurer {
- @Value("${test.name}")
- private String name;
+ @Value("${test.name}")
+ private String name;
- @Bean
- public ITestBean testBean() {
- return new TestBean(this.name);
- }
+ @Bean
+ public ITestBean testBean() {
+ return new TestBean(this.name);
+ }
- @Bean
- public PropertySourcesPlaceholderConfigurer ppc() {
- return new PropertySourcesPlaceholderConfigurer();
+ @Bean
+ public PropertySourcesPlaceholderConfigurer ppc() {
+ return new PropertySourcesPlaceholderConfigurer();
+ }
}
-} \ No newline at end of file
+}
diff --git a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ImportResourceTests.java b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ImportResourceTests.java
index 72ed279f..a0f6a0df 100644
--- a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ImportResourceTests.java
+++ b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ImportResourceTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,15 +20,12 @@ import java.util.Collections;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
-
-import org.junit.Ignore;
import org.junit.Test;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.support.PropertiesBeanDefinitionReader;
-import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -59,26 +56,6 @@ public class ImportResourceTests {
ctx.close();
}
- @Ignore // TODO: SPR-6310
- @Test
- public void importXmlWithRelativePath() {
- AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ImportXmlWithRelativePathConfig.class);
- assertTrue("did not contain java-declared bean", ctx.containsBean("javaDeclaredBean"));
- assertTrue("did not contain xml-declared bean", ctx.containsBean("xmlDeclaredBean"));
- TestBean tb = ctx.getBean("javaDeclaredBean", TestBean.class);
- assertEquals("myName", tb.getName());
- ctx.close();
- }
-
- @Ignore // TODO: SPR-6310
- @Test
- public void importXmlByConvention() {
- AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
- ImportXmlByConventionConfig.class);
- assertTrue("context does not contain xml-declared bean", ctx.containsBean("xmlDeclaredBean"));
- ctx.close();
- }
-
@Test
public void importXmlIsInheritedFromSuperclassDeclarations() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(FirstLevelSubConfig.class);
@@ -112,15 +89,6 @@ public class ImportResourceTests {
ctx.close();
}
- @Ignore // TODO: SPR-6327
- @Test
- public void importDifferentResourceTypes() {
- AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SubResourceConfig.class);
- assertTrue(ctx.containsBean("propertiesDeclaredBean"));
- assertTrue(ctx.containsBean("xmlDeclaredBean"));
- ctx.close();
- }
-
@Test
public void importWithPlaceholder() throws Exception {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
@@ -160,19 +128,6 @@ public class ImportResourceTests {
}
@Configuration
- @ImportResource("ImportXmlConfig-context.xml")
- static class ImportXmlWithRelativePathConfig {
- public @Bean TestBean javaDeclaredBean() {
- return new TestBean("java.declared");
- }
- }
-
- @Configuration
- //@ImportXml
- static class ImportXmlByConventionConfig {
- }
-
- @Configuration
@ImportResource("classpath:org/springframework/context/annotation/configuration/ImportXmlConfig-context.xml")
static class BaseConfig {
}
@@ -217,9 +172,4 @@ public class ImportResourceTests {
static class ImportNonXmlResourceConfig {
}
- @Configuration
- @ImportResource(locations = "classpath:org/springframework/context/annotation/configuration/ImportXmlConfig-context.xml", reader = XmlBeanDefinitionReader.class)
- static class SubResourceConfig extends ImportNonXmlResourceConfig {
- }
-
}
diff --git a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ScopingTests.java b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ScopingTests.java
index f553b215..a9bba8cf 100644
--- a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ScopingTests.java
+++ b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ScopingTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,9 +31,9 @@ import org.springframework.aop.scope.ScopedObject;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.context.support.GenericApplicationContext;
@@ -79,8 +79,7 @@ public class ScopingTests {
beanFactory.registerScope(SCOPE, customScope);
}
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(configClass));
- GenericApplicationContext ctx = new GenericApplicationContext(beanFactory);
- ctx.addBeanFactoryPostProcessor(new ConfigurationClassPostProcessor());
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(beanFactory);
ctx.refresh();
return ctx;
}
@@ -222,13 +221,6 @@ public class ScopingTests {
assertSame(spouse.getName(), spouseFromBF.getName());
}
- @Test
- public void testScopedConfigurationBeanDefinitionCount() throws Exception {
- // count the beans
- // 6 @Beans + 1 Configuration + 2 scoped proxy + 1 importRegistry + 1 enhanced config post processor
- assertEquals(11, ctx.getBeanDefinitionCount());
- }
-
static class Foo {
@@ -365,7 +357,7 @@ public class ScopingTests {
@Override
public void registerDestructionCallback(String name, Runnable callback) {
- // do nothing
+ throw new IllegalStateException("Not supposed to be called");
}
@Override
diff --git a/spring-context/src/test/java/org/springframework/context/annotation/spr10546/Spr10546Tests.java b/spring-context/src/test/java/org/springframework/context/annotation/spr10546/Spr10546Tests.java
index d02b2ba3..758fa053 100644
--- a/spring-context/src/test/java/org/springframework/context/annotation/spr10546/Spr10546Tests.java
+++ b/spring-context/src/test/java/org/springframework/context/annotation/spr10546/Spr10546Tests.java
@@ -37,7 +37,7 @@ public class Spr10546Tests {
@After
public void closeContext() {
- if(context != null) {
+ if (context != null) {
context.close();
}
}
diff --git a/spring-context/src/test/java/org/springframework/context/config/ContextNamespaceHandlerTests.java b/spring-context/src/test/java/org/springframework/context/config/ContextNamespaceHandlerTests.java
index e6a3b304..ba04db49 100644
--- a/spring-context/src/test/java/org/springframework/context/config/ContextNamespaceHandlerTests.java
+++ b/spring-context/src/test/java/org/springframework/context/config/ContextNamespaceHandlerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,14 +18,10 @@ package org.springframework.context.config;
import java.util.Calendar;
import java.util.Date;
-import java.util.Map;
import org.junit.After;
import org.junit.Test;
-import org.springframework.beans.factory.config.PlaceholderConfigurerSupport;
-import org.springframework.beans.factory.config.PropertyOverrideConfigurer;
-import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
@@ -53,9 +49,6 @@ public class ContextNamespaceHandlerTests {
public void propertyPlaceholder() throws Exception {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
"contextNamespaceHandlerTests-replace.xml", getClass());
- Map<String, PlaceholderConfigurerSupport> beans = applicationContext
- .getBeansOfType(PlaceholderConfigurerSupport.class);
- assertFalse("No PropertyPlaceholderConfigurer found", beans.isEmpty());
assertEquals("bar", applicationContext.getBean("string"));
assertEquals("null", applicationContext.getBean("nullString"));
}
@@ -66,9 +59,6 @@ public class ContextNamespaceHandlerTests {
try {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
"contextNamespaceHandlerTests-system.xml", getClass());
- Map<String, PropertyPlaceholderConfigurer> beans = applicationContext
- .getBeansOfType(PropertyPlaceholderConfigurer.class);
- assertFalse("No PropertyPlaceholderConfigurer found", beans.isEmpty());
assertEquals("spam", applicationContext.getBean("string"));
assertEquals("none", applicationContext.getBean("fallback"));
}
@@ -86,9 +76,6 @@ public class ContextNamespaceHandlerTests {
applicationContext.setEnvironment(env);
applicationContext.load(new ClassPathResource("contextNamespaceHandlerTests-simple.xml", getClass()));
applicationContext.refresh();
- Map<String, PlaceholderConfigurerSupport> beans = applicationContext
- .getBeansOfType(PlaceholderConfigurerSupport.class);
- assertFalse("No PropertyPlaceholderConfigurer found", beans.isEmpty());
assertEquals("spam", applicationContext.getBean("string"));
assertEquals("none", applicationContext.getBean("fallback"));
}
@@ -97,9 +84,6 @@ public class ContextNamespaceHandlerTests {
public void propertyPlaceholderLocation() throws Exception {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
"contextNamespaceHandlerTests-location.xml", getClass());
- Map<String, PropertyPlaceholderConfigurer> beans = applicationContext
- .getBeansOfType(PropertyPlaceholderConfigurer.class);
- assertFalse("No PropertyPlaceholderConfigurer found", beans.isEmpty());
assertEquals("bar", applicationContext.getBean("foo"));
assertEquals("foo", applicationContext.getBean("bar"));
assertEquals("maps", applicationContext.getBean("spam"));
@@ -109,9 +93,6 @@ public class ContextNamespaceHandlerTests {
public void propertyPlaceholderIgnored() throws Exception {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
"contextNamespaceHandlerTests-replace-ignore.xml", getClass());
- Map<String, PlaceholderConfigurerSupport> beans = applicationContext
- .getBeansOfType(PlaceholderConfigurerSupport.class);
- assertFalse("No PropertyPlaceholderConfigurer found", beans.isEmpty());
assertEquals("${bar}", applicationContext.getBean("string"));
assertEquals("null", applicationContext.getBean("nullString"));
}
@@ -120,9 +101,6 @@ public class ContextNamespaceHandlerTests {
public void propertyOverride() throws Exception {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
"contextNamespaceHandlerTests-override.xml", getClass());
- Map<String, PropertyOverrideConfigurer> beans = applicationContext
- .getBeansOfType(PropertyOverrideConfigurer.class);
- assertFalse("No PropertyOverrideConfigurer found", beans.isEmpty());
Date date = (Date) applicationContext.getBean("date");
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
diff --git a/spring-context/src/test/java/org/springframework/context/event/AbstractApplicationEventListenerTests.java b/spring-context/src/test/java/org/springframework/context/event/AbstractApplicationEventListenerTests.java
index 72a2561b..f7aedb13 100644
--- a/spring-context/src/test/java/org/springframework/context/event/AbstractApplicationEventListenerTests.java
+++ b/spring-context/src/test/java/org/springframework/context/event/AbstractApplicationEventListenerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -49,7 +49,7 @@ public abstract class AbstractApplicationEventListenerTests {
}
public T getPayload() {
- return payload;
+ return this.payload;
}
}
@@ -117,13 +117,13 @@ public abstract class AbstractApplicationEventListenerTests {
}
}
+ @SuppressWarnings("rawtypes")
static class RawApplicationListener implements ApplicationListener {
@Override
public void onApplicationEvent(ApplicationEvent event) {
}
}
- @SuppressWarnings("unused")
static class TestEvents {
public ApplicationEvent applicationEvent;
diff --git a/spring-context/src/test/java/org/springframework/context/event/AnnotationDrivenEventListenerTests.java b/spring-context/src/test/java/org/springframework/context/event/AnnotationDrivenEventListenerTests.java
index 3e52c58b..89738660 100644
--- a/spring-context/src/test/java/org/springframework/context/event/AnnotationDrivenEventListenerTests.java
+++ b/spring-context/src/test/java/org/springframework/context/event/AnnotationDrivenEventListenerTests.java
@@ -54,6 +54,7 @@ import org.springframework.context.event.test.GenericEventPojo;
import org.springframework.context.event.test.Identifiable;
import org.springframework.context.event.test.TestEvent;
import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.springframework.core.annotation.AliasFor;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
@@ -116,7 +117,7 @@ public class AnnotationDrivenEventListenerTests {
public void metaAnnotationIsDiscovered() {
load(MetaAnnotationListenerTestBean.class);
- MetaAnnotationListenerTestBean bean = context.getBean(MetaAnnotationListenerTestBean.class);
+ MetaAnnotationListenerTestBean bean = this.context.getBean(MetaAnnotationListenerTestBean.class);
this.eventCollector.assertNoEventReceived(bean);
TestEvent event = new TestEvent();
@@ -148,9 +149,9 @@ public class AnnotationDrivenEventListenerTests {
failingContext.register(BasicConfiguration.class,
InvalidMethodSignatureEventListener.class);
- thrown.expect(BeanInitializationException.class);
- thrown.expectMessage(InvalidMethodSignatureEventListener.class.getName());
- thrown.expectMessage("cannotBeCalled");
+ this.thrown.expect(BeanInitializationException.class);
+ this.thrown.expectMessage(InvalidMethodSignatureEventListener.class.getName());
+ this.thrown.expectMessage("cannotBeCalled");
failingContext.refresh();
}
@@ -286,6 +287,17 @@ public class AnnotationDrivenEventListenerTests {
}
@Test
+ public void privateMethodOnCglibProxyFails() throws Exception {
+ try {
+ load(CglibProxyWithPrivateMethod.class);
+ fail("Should have thrown BeanInitializationException");
+ }
+ catch (BeanInitializationException ex) {
+ assertTrue(ex.getCause() instanceof IllegalStateException);
+ }
+ }
+
+ @Test
public void eventListenerWorksWithCustomScope() throws Exception {
load(CustomScopeTestBean.class);
CustomScope customScope = new CustomScope();
@@ -329,7 +341,7 @@ public class AnnotationDrivenEventListenerTests {
this.eventCollector.assertNoEventReceived(listener);
this.context.publishEvent(event);
- countDownLatch.await(2, TimeUnit.SECONDS);
+ this.countDownLatch.await(2, TimeUnit.SECONDS);
this.eventCollector.assertEvent(listener, event);
this.eventCollector.assertTotalEventsCount(1);
}
@@ -344,7 +356,7 @@ public class AnnotationDrivenEventListenerTests {
this.eventCollector.assertNoEventReceived(listener);
this.context.publishEvent(event);
- countDownLatch.await(2, TimeUnit.SECONDS);
+ this.countDownLatch.await(2, TimeUnit.SECONDS);
this.eventCollector.assertEvent(listener, event);
this.eventCollector.assertTotalEventsCount(1);
}
@@ -359,7 +371,7 @@ public class AnnotationDrivenEventListenerTests {
this.eventCollector.assertNoEventReceived(listener);
this.context.publishEvent(event);
- countDownLatch.await(2, TimeUnit.SECONDS);
+ this.countDownLatch.await(2, TimeUnit.SECONDS);
this.eventCollector.assertEvent(listener, event);
this.eventCollector.assertTotalEventsCount(1);
}
@@ -389,7 +401,7 @@ public class AnnotationDrivenEventListenerTests {
this.eventCollector.assertNoEventReceived(listener);
this.context.publishEvent(event);
- countDownLatch.await(2, TimeUnit.SECONDS);
+ this.countDownLatch.await(2, TimeUnit.SECONDS);
this.eventCollector.assertEvent(listener, event);
this.eventCollector.assertTotalEventsCount(1);
@@ -487,6 +499,10 @@ public class AnnotationDrivenEventListenerTests {
this.context.publishEvent(timestamp);
this.eventCollector.assertEvent(listener, event, "OK", timestamp);
this.eventCollector.assertTotalEventsCount(3);
+
+ this.context.publishEvent(42d);
+ this.eventCollector.assertEvent(listener, event, "OK", timestamp, 42d);
+ this.eventCollector.assertTotalEventsCount(4);
}
@Test
@@ -508,6 +524,10 @@ public class AnnotationDrivenEventListenerTests {
this.context.publishEvent(maxLong);
this.eventCollector.assertNoEventReceived(listener);
this.eventCollector.assertTotalEventsCount(0);
+
+ this.context.publishEvent(24d);
+ this.eventCollector.assertNoEventReceived(listener);
+ this.eventCollector.assertTotalEventsCount(0);
}
@Test
@@ -559,6 +579,18 @@ public class AnnotationDrivenEventListenerTests {
public CountDownLatch testCountDownLatch() {
return new CountDownLatch(1);
}
+
+ @Bean
+ public TestConditionEvaluator conditionEvaluator() {
+ return new TestConditionEvaluator();
+ }
+
+ static class TestConditionEvaluator {
+
+ public boolean valid(Double ratio) {
+ return new Double(42).equals(ratio);
+ }
+ }
}
@@ -667,7 +699,7 @@ public class AnnotationDrivenEventListenerTests {
public void handleAsync(AnotherTestEvent event) {
collectEvent(event);
if ("fail".equals(event.content)) {
- countDownLatch.countDown();
+ this.countDownLatch.countDown();
throw new IllegalStateException("Test exception");
}
}
@@ -685,7 +717,7 @@ public class AnnotationDrivenEventListenerTests {
public void handleAsync(AnotherTestEvent event) {
assertTrue(!Thread.currentThread().getName().equals(event.content));
collectEvent(event);
- countDownLatch.countDown();
+ this.countDownLatch.countDown();
}
}
@@ -724,15 +756,15 @@ public class AnnotationDrivenEventListenerTests {
@EventListener
@Override
public void handleIt(TestEvent event) {
- eventCollector.addEvent(this, event);
+ this.eventCollector.addEvent(this, event);
}
@EventListener
@Async
public void handleAsync(AnotherTestEvent event) {
assertTrue(!Thread.currentThread().getName().equals(event.content));
- eventCollector.addEvent(this, event);
- countDownLatch.countDown();
+ this.eventCollector.addEvent(this, event);
+ this.countDownLatch.countDown();
}
}
@@ -750,15 +782,15 @@ public class AnnotationDrivenEventListenerTests {
@EventListener
@Override
public void handleIt(TestEvent event) {
- eventCollector.addEvent(this, event);
+ this.eventCollector.addEvent(this, event);
}
@EventListener
@Async
public void handleAsync(AnotherTestEvent event) {
assertTrue(!Thread.currentThread().getName().equals(event.content));
- eventCollector.addEvent(this, event);
- countDownLatch.countDown();
+ this.eventCollector.addEvent(this, event);
+ this.countDownLatch.countDown();
}
}
@@ -779,7 +811,7 @@ public class AnnotationDrivenEventListenerTests {
@Override
public void handleIt(TestEvent event) {
- eventCollector.addEvent(this, event);
+ this.eventCollector.addEvent(this, event);
}
}
@@ -796,6 +828,17 @@ public class AnnotationDrivenEventListenerTests {
@Component
+ @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
+ static class CglibProxyWithPrivateMethod extends AbstractTestEventListener {
+
+ @EventListener
+ private void handleIt(TestEvent event) {
+ collectEvent(event);
+ }
+ }
+
+
+ @Component
@Scope(scopeName = "custom", proxyMode = ScopedProxyMode.TARGET_CLASS)
static class CustomScopeTestBean extends AbstractTestEventListener {
@@ -826,6 +869,16 @@ public class AnnotationDrivenEventListenerTests {
}
+
+ @EventListener
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface ConditionalEvent {
+
+ @AliasFor(annotation = EventListener.class, attribute = "condition")
+ String value();
+ }
+
+
@Component
static class ConditionalEventListener extends TestEventListener {
@@ -841,10 +894,15 @@ public class AnnotationDrivenEventListenerTests {
super.handleString(payload);
}
- @EventListener(condition = "#root.event.timestamp > #p0")
+ @ConditionalEvent("#root.event.timestamp > #p0")
public void handleTimestamp(Long timestamp) {
collectEvent(timestamp);
}
+
+ @ConditionalEvent("@conditionEvaluator.valid(#p0)")
+ public void handleRatio(Double ratio) {
+ collectEvent(ratio);
+ }
}
@@ -856,18 +914,18 @@ public class AnnotationDrivenEventListenerTests {
@EventListener
@Order(50)
public void handleThird(String payload) {
- order.add("third");
+ this.order.add("third");
}
@EventListener
@Order(-50)
public void handleFirst(String payload) {
- order.add("first");
+ this.order.add("first");
}
@EventListener
public void handleSecond(String payload) {
- order.add("second");
+ this.order.add("second");
}
}
diff --git a/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java b/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java
index a56c77be..c65d60b8 100644
--- a/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java
+++ b/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -452,7 +452,7 @@ public class ApplicationContextEventTests extends AbstractApplicationEventListen
@Override
public void onApplicationEvent(MyEvent event) {
- assertTrue(otherListener.seenEvents.contains(event));
+ assertTrue(this.otherListener.seenEvents.contains(event));
}
}
@@ -503,7 +503,7 @@ public class ApplicationContextEventTests extends AbstractApplicationEventListen
@Override
public void onApplicationEvent(MyEvent event) {
- assertTrue(otherListener.seenEvents.contains(event));
+ assertTrue(this.otherListener.seenEvents.contains(event));
}
}
diff --git a/spring-context/src/test/java/org/springframework/context/event/ApplicationListenerMethodAdapterTests.java b/spring-context/src/test/java/org/springframework/context/event/ApplicationListenerMethodAdapterTests.java
index b9cc4dd0..77373180 100644
--- a/spring-context/src/test/java/org/springframework/context/event/ApplicationListenerMethodAdapterTests.java
+++ b/spring-context/src/test/java/org/springframework/context/event/ApplicationListenerMethodAdapterTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -148,7 +148,7 @@ public class ApplicationListenerMethodAdapterTests extends AbstractApplicationEv
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"tooManyParameters", String.class, String.class);
- thrown.expect(IllegalStateException.class);
+ this.thrown.expect(IllegalStateException.class);
createTestInstance(method);
}
@@ -157,7 +157,7 @@ public class ApplicationListenerMethodAdapterTests extends AbstractApplicationEv
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"noParameter");
- thrown.expect(IllegalStateException.class);
+ this.thrown.expect(IllegalStateException.class);
createTestInstance(method);
}
@@ -166,7 +166,7 @@ public class ApplicationListenerMethodAdapterTests extends AbstractApplicationEv
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"moreThanOneParameter", String.class, Integer.class);
- thrown.expect(IllegalStateException.class);
+ this.thrown.expect(IllegalStateException.class);
createTestInstance(method);
}
@@ -237,9 +237,9 @@ public class ApplicationListenerMethodAdapterTests extends AbstractApplicationEv
"generateRuntimeException", GenericTestEvent.class);
GenericTestEvent<String> event = createGenericTestEvent("fail");
- thrown.expect(IllegalStateException.class);
- thrown.expectMessage("Test exception");
- thrown.expectCause(is(isNull(Throwable.class)));
+ this.thrown.expect(IllegalStateException.class);
+ this.thrown.expectMessage("Test exception");
+ this.thrown.expectCause(is(isNull(Throwable.class)));
invokeListener(method, event);
}
@@ -249,8 +249,8 @@ public class ApplicationListenerMethodAdapterTests extends AbstractApplicationEv
"generateCheckedException", GenericTestEvent.class);
GenericTestEvent<String> event = createGenericTestEvent("fail");
- thrown.expect(UndeclaredThrowableException.class);
- thrown.expectCause(is(instanceOf(IOException.class)));
+ this.thrown.expect(UndeclaredThrowableException.class);
+ this.thrown.expectCause(is(instanceOf(IOException.class)));
invokeListener(method, event);
}
@@ -265,8 +265,8 @@ public class ApplicationListenerMethodAdapterTests extends AbstractApplicationEv
Method method = ReflectionUtils.findMethod(InvalidProxyTestBean.class, "handleIt2", ApplicationEvent.class);
StaticApplicationListenerMethodAdapter listener =
new StaticApplicationListenerMethodAdapter(method, bean);
- thrown.expect(IllegalStateException.class);
- thrown.expectMessage("handleIt2");
+ this.thrown.expect(IllegalStateException.class);
+ this.thrown.expectMessage("handleIt2");
listener.onApplicationEvent(createGenericTestEvent("test"));
}
@@ -373,7 +373,7 @@ public class ApplicationListenerMethodAdapterTests extends AbstractApplicationEv
@Override
public Object getTargetBean() {
- return targetBean;
+ return this.targetBean;
}
}
diff --git a/spring-context/src/test/java/org/springframework/context/event/EventPublicationInterceptorTests.java b/spring-context/src/test/java/org/springframework/context/event/EventPublicationInterceptorTests.java
index 0cb677c3..6437ac57 100644
--- a/spring-context/src/test/java/org/springframework/context/event/EventPublicationInterceptorTests.java
+++ b/spring-context/src/test/java/org/springframework/context/event/EventPublicationInterceptorTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -46,20 +46,20 @@ public class EventPublicationInterceptorTests {
@Before
public void setUp() {
- publisher = mock(ApplicationEventPublisher.class);
+ this.publisher = mock(ApplicationEventPublisher.class);
}
@Test(expected=IllegalArgumentException.class)
public void testWithNoApplicationEventClassSupplied() throws Exception {
EventPublicationInterceptor interceptor = new EventPublicationInterceptor();
- interceptor.setApplicationEventPublisher(publisher);
+ interceptor.setApplicationEventPublisher(this.publisher);
interceptor.afterPropertiesSet();
}
@Test(expected=IllegalArgumentException.class)
public void testWithNonApplicationEventClassSupplied() throws Exception {
EventPublicationInterceptor interceptor = new EventPublicationInterceptor();
- interceptor.setApplicationEventPublisher(publisher);
+ interceptor.setApplicationEventPublisher(this.publisher);
interceptor.setApplicationEventClass(getClass());
interceptor.afterPropertiesSet();
}
@@ -67,7 +67,7 @@ public class EventPublicationInterceptorTests {
@Test(expected=IllegalArgumentException.class)
public void testWithAbstractStraightApplicationEventClassSupplied() throws Exception {
EventPublicationInterceptor interceptor = new EventPublicationInterceptor();
- interceptor.setApplicationEventPublisher(publisher);
+ interceptor.setApplicationEventPublisher(this.publisher);
interceptor.setApplicationEventClass(ApplicationEvent.class);
interceptor.afterPropertiesSet();
}
@@ -75,7 +75,7 @@ public class EventPublicationInterceptorTests {
@Test(expected=IllegalArgumentException.class)
public void testWithApplicationEventClassThatDoesntExposeAValidCtor() throws Exception {
EventPublicationInterceptor interceptor = new EventPublicationInterceptor();
- interceptor.setApplicationEventPublisher(publisher);
+ interceptor.setApplicationEventPublisher(this.publisher);
interceptor.setApplicationEventClass(TestEventWithNoValidOneArgObjectCtor.class);
interceptor.afterPropertiesSet();
}
diff --git a/spring-context/src/test/java/org/springframework/context/event/test/AbstractIdentifiable.java b/spring-context/src/test/java/org/springframework/context/event/test/AbstractIdentifiable.java
index b960d8aa..3a61bc01 100644
--- a/spring-context/src/test/java/org/springframework/context/event/test/AbstractIdentifiable.java
+++ b/spring-context/src/test/java/org/springframework/context/event/test/AbstractIdentifiable.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,7 +31,7 @@ public abstract class AbstractIdentifiable implements Identifiable {
@Override
public String getId() {
- return id;
+ return this.id;
}
@Override
@@ -41,12 +41,12 @@ public abstract class AbstractIdentifiable implements Identifiable {
AbstractIdentifiable that = (AbstractIdentifiable) o;
- return id.equals(that.id);
+ return this.id.equals(that.id);
}
@Override
public int hashCode() {
- return id.hashCode();
+ return this.id.hashCode();
}
}
diff --git a/spring-context/src/test/java/org/springframework/context/event/test/EventCollector.java b/spring-context/src/test/java/org/springframework/context/event/test/EventCollector.java
index 78dddb96..9df254c2 100644
--- a/spring-context/src/test/java/org/springframework/context/event/test/EventCollector.java
+++ b/spring-context/src/test/java/org/springframework/context/event/test/EventCollector.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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 EventCollector {
* Assert that the listener identified by the specified id has not received any event.
*/
public void assertNoEventReceived(String listenerId) {
- List<Object> events = content.getOrDefault(listenerId, Collections.emptyList());
+ List<Object> events = this.content.getOrDefault(listenerId, Collections.emptyList());
assertEquals("Expected no events but got " + events, 0, events.size());
}
@@ -72,7 +72,7 @@ public class EventCollector {
* specified events, in that specific order.
*/
public void assertEvent(String listenerId, Object... events) {
- List<Object> actual = content.getOrDefault(listenerId, Collections.emptyList());
+ List<Object> actual = this.content.getOrDefault(listenerId, Collections.emptyList());
assertEquals("wrong number of events", events.length, actual.size());
for (int i = 0; i < events.length; i++) {
assertEquals("Wrong event at index " + i, events[i], actual.get(i));
diff --git a/spring-context/src/test/java/org/springframework/context/event/test/IdentifiableApplicationEvent.java b/spring-context/src/test/java/org/springframework/context/event/test/IdentifiableApplicationEvent.java
index 8694c977..6e2df2dc 100644
--- a/spring-context/src/test/java/org/springframework/context/event/test/IdentifiableApplicationEvent.java
+++ b/spring-context/src/test/java/org/springframework/context/event/test/IdentifiableApplicationEvent.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,7 +45,7 @@ public abstract class IdentifiableApplicationEvent extends ApplicationEvent impl
@Override
public String getId() {
- return id;
+ return this.id;
}
@Override
@@ -55,13 +55,13 @@ public abstract class IdentifiableApplicationEvent extends ApplicationEvent impl
IdentifiableApplicationEvent that = (IdentifiableApplicationEvent) o;
- return id.equals(that.id);
+ return this.id.equals(that.id);
}
@Override
public int hashCode() {
- return id.hashCode();
+ return this.id.hashCode();
}
}
diff --git a/spring-context/src/test/java/org/springframework/context/expression/FactoryBeanAccessTests.java b/spring-context/src/test/java/org/springframework/context/expression/FactoryBeanAccessTests.java
new file mode 100644
index 00000000..e1e1ca3e
--- /dev/null
+++ b/spring-context/src/test/java/org/springframework/context/expression/FactoryBeanAccessTests.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.context.expression;
+
+import org.junit.Test;
+import org.springframework.beans.factory.BeanIsNotAFactoryException;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.context.expression.FactoryBeanAccessTests.SimpleBeanResolver.Boat;
+import org.springframework.context.expression.FactoryBeanAccessTests.SimpleBeanResolver.CarFactoryBean;
+import org.springframework.context.support.StaticApplicationContext;
+import org.springframework.expression.AccessException;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.Expression;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+
+import static org.junit.Assert.*;
+
+/**
+ * Unit tests for expressions accessing beans and factory beans.
+ *
+ * @author Andy Clement
+ */
+public class FactoryBeanAccessTests {
+
+ @Test
+ public void factoryBeanAccess() { // SPR9511
+ StandardEvaluationContext context = new StandardEvaluationContext();
+ context.setBeanResolver(new SimpleBeanResolver());
+ Expression expr = new SpelExpressionParser().parseRaw("@car.colour");
+ assertEquals("red", expr.getValue(context));
+ expr = new SpelExpressionParser().parseRaw("&car.class.name");
+ assertEquals(CarFactoryBean.class.getName(), expr.getValue(context));
+
+ expr = new SpelExpressionParser().parseRaw("@boat.colour");
+ assertEquals("blue",expr.getValue(context));
+ expr = new SpelExpressionParser().parseRaw("&boat.class.name");
+ try {
+ assertEquals(Boat.class.getName(), expr.getValue(context));
+ fail("Expected BeanIsNotAFactoryException");
+ }
+ catch (BeanIsNotAFactoryException binafe) {
+ // success
+ }
+
+ // No such bean
+ try {
+ expr = new SpelExpressionParser().parseRaw("@truck");
+ assertEquals("red", expr.getValue(context));
+ fail("Expected NoSuchBeanDefinitionException");
+ }
+ catch (NoSuchBeanDefinitionException nsbde) {
+ // success
+ }
+
+ // No such factory bean
+ try {
+ expr = new SpelExpressionParser().parseRaw("&truck");
+ assertEquals(CarFactoryBean.class.getName(), expr.getValue(context));
+ fail("Expected NoSuchBeanDefinitionException");
+ }
+ catch (NoSuchBeanDefinitionException nsbde) {
+ // success
+ }
+ }
+
+ static class SimpleBeanResolver
+ implements org.springframework.expression.BeanResolver {
+
+ static class Car {
+
+ public String getColour() {
+ return "red";
+ }
+ }
+
+ static class CarFactoryBean implements FactoryBean<Car> {
+
+ public Car getObject() {
+ return new Car();
+ }
+
+ public Class<Car> getObjectType() {
+ return Car.class;
+ }
+
+ public boolean isSingleton() {
+ return false;
+ }
+
+ }
+
+ static class Boat {
+
+ public String getColour() {
+ return "blue";
+ }
+
+ }
+
+ StaticApplicationContext ac = new StaticApplicationContext();
+
+ public SimpleBeanResolver() {
+ ac.registerSingleton("car", CarFactoryBean.class);
+ ac.registerSingleton("boat", Boat.class);
+ }
+
+ @Override
+ public Object resolve(EvaluationContext context, String beanName)
+ throws AccessException {
+ return ac.getBean(beanName);
+ }
+ }
+
+} \ No newline at end of file
diff --git a/spring-context/src/test/java/org/springframework/context/expression/MapAccessorTests.java b/spring-context/src/test/java/org/springframework/context/expression/MapAccessorTests.java
index db50071c..41646319 100644
--- a/spring-context/src/test/java/org/springframework/context/expression/MapAccessorTests.java
+++ b/spring-context/src/test/java/org/springframework/context/expression/MapAccessorTests.java
@@ -37,50 +37,50 @@ public class MapAccessorTests {
@Test
public void mapAccessorCompilable() {
Map<String, Object> testMap = getSimpleTestMap();
- StandardEvaluationContext sec = new StandardEvaluationContext();
+ StandardEvaluationContext sec = new StandardEvaluationContext();
sec.addPropertyAccessor(new MapAccessor());
SpelExpressionParser sep = new SpelExpressionParser();
-
+
// basic
Expression ex = sep.parseExpression("foo");
assertEquals("bar",ex.getValue(sec,testMap));
- assertTrue(SpelCompiler.compile(ex));
+ assertTrue(SpelCompiler.compile(ex));
assertEquals("bar",ex.getValue(sec,testMap));
// compound expression
ex = sep.parseExpression("foo.toUpperCase()");
assertEquals("BAR",ex.getValue(sec,testMap));
- assertTrue(SpelCompiler.compile(ex));
+ assertTrue(SpelCompiler.compile(ex));
assertEquals("BAR",ex.getValue(sec,testMap));
-
+
// nested map
Map<String,Map<String,Object>> nestedMap = getNestedTestMap();
ex = sep.parseExpression("aaa.foo.toUpperCase()");
assertEquals("BAR",ex.getValue(sec,nestedMap));
- assertTrue(SpelCompiler.compile(ex));
+ assertTrue(SpelCompiler.compile(ex));
assertEquals("BAR",ex.getValue(sec,nestedMap));
-
+
// avoiding inserting checkcast because first part of expression returns a Map
ex = sep.parseExpression("getMap().foo");
MapGetter mapGetter = new MapGetter();
assertEquals("bar",ex.getValue(sec,mapGetter));
- assertTrue(SpelCompiler.compile(ex));
+ assertTrue(SpelCompiler.compile(ex));
assertEquals("bar",ex.getValue(sec,mapGetter));
}
-
+
public static class MapGetter {
Map<String,Object> map = new HashMap<String,Object>();
public MapGetter() {
map.put("foo", "bar");
}
-
+
@SuppressWarnings("rawtypes")
public Map getMap() {
return map;
}
}
-
+
public Map<String,Object> getSimpleTestMap() {
Map<String,Object> map = new HashMap<String,Object>();
map.put("foo","bar");
diff --git a/spring-context/src/test/java/org/springframework/context/expression/MethodBasedEvaluationContextTests.java b/spring-context/src/test/java/org/springframework/context/expression/MethodBasedEvaluationContextTests.java
index 4c156989..2711b006 100644
--- a/spring-context/src/test/java/org/springframework/context/expression/MethodBasedEvaluationContextTests.java
+++ b/spring-context/src/test/java/org/springframework/context/expression/MethodBasedEvaluationContextTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +30,7 @@ import static org.junit.Assert.*;
* Unit tests for {@link MethodBasedEvaluationContext}.
*
* @author Stephane Nicoll
+ * @author Sergey Podgurskiy
*/
public class MethodBasedEvaluationContextTests {
@@ -62,6 +63,43 @@ public class MethodBasedEvaluationContextTests {
assertNull(context.lookupVariable("p0"));
}
+ @Test
+ public void varArgEmpty() {
+ Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello", Boolean.class, String[].class);
+ MethodBasedEvaluationContext context = createEvaluationContext(method, new Object[] {null});
+
+ assertNull(context.lookupVariable("p0"));
+ assertNull(context.lookupVariable("p1"));
+ }
+
+ @Test
+ public void varArgNull() {
+ Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello", Boolean.class, String[].class);
+ MethodBasedEvaluationContext context = createEvaluationContext(method, new Object[] {null, null});
+
+ assertNull(context.lookupVariable("p0"));
+ assertNull(context.lookupVariable("p1"));
+ }
+
+ @Test
+ public void varArgSingle() {
+ Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello", Boolean.class, String[].class);
+ MethodBasedEvaluationContext context = createEvaluationContext(method, new Object[] {null, "hello"});
+
+ assertNull(context.lookupVariable("p0"));
+ assertEquals("hello", context.lookupVariable("p1"));
+ }
+
+ @Test
+ public void varArgMultiple() {
+ Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello", Boolean.class, String[].class);
+ MethodBasedEvaluationContext context = createEvaluationContext(method,
+ new Object[] {null, new String[]{"hello", "hi"}});
+
+ assertNull(context.lookupVariable("p0"));
+ assertArrayEquals(new String[]{"hello", "hi"}, (String[]) context.lookupVariable("p1"));
+ }
+
private MethodBasedEvaluationContext createEvaluationContext(Method method, Object[] args) {
return new MethodBasedEvaluationContext(this, method, args, this.paramDiscover);
}
@@ -73,6 +111,10 @@ public class MethodBasedEvaluationContextTests {
private void hello(String foo, Boolean flag) {
}
+ private void hello(Boolean flag, String... vararg){
+
+ }
+
}
} \ No newline at end of file
diff --git a/spring-context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.java b/spring-context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.java
index e11ae476..03044630 100644
--- a/spring-context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.java
+++ b/spring-context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
package org.springframework.context.support;
+import java.util.Optional;
import java.util.Properties;
import org.junit.Rule;
@@ -24,6 +25,7 @@ import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.StandardEnvironment;
@@ -39,6 +41,7 @@ import static org.springframework.beans.factory.support.BeanDefinitionBuilder.*;
/**
* @author Chris Beams
+ * @author Juergen Hoeller
* @since 3.1
*/
public class PropertySourcesPlaceholderConfigurerTests {
@@ -46,6 +49,7 @@ public class PropertySourcesPlaceholderConfigurerTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
+
@Test
public void replacementFromEnvironmentProperties() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
@@ -57,8 +61,7 @@ public class PropertySourcesPlaceholderConfigurerTests {
MockEnvironment env = new MockEnvironment();
env.setProperty("my.name", "myValue");
- PropertySourcesPlaceholderConfigurer ppc =
- new PropertySourcesPlaceholderConfigurer();
+ PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
ppc.setEnvironment(env);
ppc.postProcessBeanFactory(bf);
assertThat(bf.getBean(TestBean.class).getName(), equalTo("myValue"));
@@ -73,10 +76,10 @@ public class PropertySourcesPlaceholderConfigurerTests {
.addPropertyValue("name", "${my.name}")
.getBeanDefinition());
- PropertySourcesPlaceholderConfigurer pc = new PropertySourcesPlaceholderConfigurer();
+ PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
Resource resource = new ClassPathResource("PropertySourcesPlaceholderConfigurerTests.properties", this.getClass());
- pc.setLocation(resource);
- pc.postProcessBeanFactory(bf);
+ ppc.setLocation(resource);
+ ppc.postProcessBeanFactory(bf);
assertThat(bf.getBean(TestBean.class).getName(), equalTo("foo"));
}
@@ -101,11 +104,11 @@ public class PropertySourcesPlaceholderConfigurerTests {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addLast(new MockPropertySource().withProperty("my.name", "foo"));
- PropertySourcesPlaceholderConfigurer pc = new PropertySourcesPlaceholderConfigurer();
- pc.setPropertySources(propertySources);
- pc.postProcessBeanFactory(bf);
+ PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
+ ppc.setPropertySources(propertySources);
+ ppc.postProcessBeanFactory(bf);
assertThat(bf.getBean(TestBean.class).getName(), equalTo("foo"));
- assertEquals(pc.getAppliedPropertySources().iterator().next(), propertySources.iterator().next());
+ assertEquals(ppc.getAppliedPropertySources().iterator().next(), propertySources.iterator().next());
}
@Test
@@ -119,13 +122,13 @@ public class PropertySourcesPlaceholderConfigurerTests {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addLast(new MockPropertySource());
- PropertySourcesPlaceholderConfigurer pc = new PropertySourcesPlaceholderConfigurer();
- pc.setPropertySources(propertySources);
- pc.setEnvironment(new MockEnvironment().withProperty("my.name", "env"));
- pc.setIgnoreUnresolvablePlaceholders(true);
- pc.postProcessBeanFactory(bf);
+ PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
+ ppc.setPropertySources(propertySources);
+ ppc.setEnvironment(new MockEnvironment().withProperty("my.name", "env"));
+ ppc.setIgnoreUnresolvablePlaceholders(true);
+ ppc.postProcessBeanFactory(bf);
assertThat(bf.getBean(TestBean.class).getName(), equalTo("${my.name}"));
- assertEquals(pc.getAppliedPropertySources().iterator().next(), propertySources.iterator().next());
+ assertEquals(ppc.getAppliedPropertySources().iterator().next(), propertySources.iterator().next());
}
@Test
@@ -140,17 +143,17 @@ public class PropertySourcesPlaceholderConfigurerTests {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addLast(new MockPropertySource());
- PropertySourcesPlaceholderConfigurer pc = new PropertySourcesPlaceholderConfigurer();
- pc.setPropertySources(propertySources);
- pc.setProperties(new Properties() {{
+ PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
+ ppc.setPropertySources(propertySources);
+ ppc.setProperties(new Properties() {{
put("my.name", "local");
}});
- pc.setIgnoreUnresolvablePlaceholders(true);
- pc.postProcessBeanFactory(bf);
+ ppc.setIgnoreUnresolvablePlaceholders(true);
+ ppc.postProcessBeanFactory(bf);
assertThat(bf.getBean(TestBean.class).getName(), equalTo("${my.name}"));
}
- @Test(expected=BeanDefinitionStoreException.class)
+ @Test(expected = BeanDefinitionStoreException.class)
public void ignoreUnresolvablePlaceholders_falseIsDefault() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.registerBeanDefinition("testBean",
@@ -158,9 +161,9 @@ public class PropertySourcesPlaceholderConfigurerTests {
.addPropertyValue("name", "${my.name}")
.getBeanDefinition());
- PropertySourcesPlaceholderConfigurer pc = new PropertySourcesPlaceholderConfigurer();
+ PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
//pc.setIgnoreUnresolvablePlaceholders(false); // the default
- pc.postProcessBeanFactory(bf); // should throw
+ ppc.postProcessBeanFactory(bf); // should throw
}
@Test
@@ -171,13 +174,13 @@ public class PropertySourcesPlaceholderConfigurerTests {
.addPropertyValue("name", "${my.name}")
.getBeanDefinition());
- PropertySourcesPlaceholderConfigurer pc = new PropertySourcesPlaceholderConfigurer();
- pc.setIgnoreUnresolvablePlaceholders(true);
- pc.postProcessBeanFactory(bf);
+ PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
+ ppc.setIgnoreUnresolvablePlaceholders(true);
+ ppc.postProcessBeanFactory(bf);
assertThat(bf.getBean(TestBean.class).getName(), equalTo("${my.name}"));
}
- @Test(expected=BeanDefinitionStoreException.class)
+ @Test(expected = BeanDefinitionStoreException.class)
@SuppressWarnings("serial")
public void nestedUnresolvablePlaceholder() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
@@ -186,11 +189,11 @@ public class PropertySourcesPlaceholderConfigurerTests {
.addPropertyValue("name", "${my.name}")
.getBeanDefinition());
- PropertySourcesPlaceholderConfigurer pc = new PropertySourcesPlaceholderConfigurer();
- pc.setProperties(new Properties() {{
+ PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
+ ppc.setProperties(new Properties() {{
put("my.name", "${bogus}");
}});
- pc.postProcessBeanFactory(bf); // should throw
+ ppc.postProcessBeanFactory(bf); // should throw
}
@Test
@@ -202,12 +205,12 @@ public class PropertySourcesPlaceholderConfigurerTests {
.addPropertyValue("name", "${my.name}")
.getBeanDefinition());
- PropertySourcesPlaceholderConfigurer pc = new PropertySourcesPlaceholderConfigurer();
- pc.setProperties(new Properties() {{
+ PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
+ ppc.setProperties(new Properties() {{
put("my.name", "${bogus}");
}});
- pc.setIgnoreUnresolvablePlaceholders(true);
- pc.postProcessBeanFactory(bf);
+ ppc.setIgnoreUnresolvablePlaceholders(true);
+ ppc.postProcessBeanFactory(bf);
assertThat(bf.getBean(TestBean.class).getName(), equalTo("${bogus}"));
}
@@ -296,6 +299,31 @@ public class PropertySourcesPlaceholderConfigurerTests {
}
@Test
+ public void trimValuesIsOffByDefault() {
+ PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
+ DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
+ bf.registerBeanDefinition("testBean", rootBeanDefinition(TestBean.class)
+ .addPropertyValue("name", "${my.name}")
+ .getBeanDefinition());
+ ppc.setEnvironment(new MockEnvironment().withProperty("my.name", " myValue "));
+ ppc.postProcessBeanFactory(bf);
+ assertThat(bf.getBean(TestBean.class).getName(), equalTo(" myValue "));
+ }
+
+ @Test
+ public void trimValuesIsApplied() {
+ PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
+ ppc.setTrimValues(true);
+ DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
+ bf.registerBeanDefinition("testBean", rootBeanDefinition(TestBean.class)
+ .addPropertyValue("name", "${my.name}")
+ .getBeanDefinition());
+ ppc.setEnvironment(new MockEnvironment().withProperty("my.name", " myValue "));
+ ppc.postProcessBeanFactory(bf);
+ assertThat(bf.getBean(TestBean.class).getName(), equalTo("myValue"));
+ }
+
+ @Test
public void getAppliedPropertySourcesTooEarly() throws Exception {
PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
thrown.expect(IllegalStateException.class);
@@ -308,7 +336,7 @@ public class PropertySourcesPlaceholderConfigurerTests {
PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
ClassPathResource doesNotHave = new ClassPathResource("test.properties", getClass());
ClassPathResource setToTrue = new ClassPathResource("placeholder.properties", getClass());
- ppc.setLocations(new Resource[] { doesNotHave, setToTrue });
+ ppc.setLocations(doesNotHave, setToTrue);
ppc.setIgnoreResourceNotFound(true);
ppc.setIgnoreUnresolvablePlaceholders(true);
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
@@ -320,4 +348,57 @@ public class PropertySourcesPlaceholderConfigurerTests {
assertThat(bf.getBean(TestBean.class).isJedi(), equalTo(true));
}
+ @Test
+ public void optionalPropertyWithValue() {
+ DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
+ bf.setConversionService(new DefaultConversionService());
+ bf.registerBeanDefinition("testBean",
+ genericBeanDefinition(OptionalTestBean.class)
+ .addPropertyValue("name", "${my.name}")
+ .getBeanDefinition());
+
+ MockEnvironment env = new MockEnvironment();
+ env.setProperty("my.name", "myValue");
+
+ PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
+ ppc.setEnvironment(env);
+ ppc.setIgnoreUnresolvablePlaceholders(true);
+ ppc.postProcessBeanFactory(bf);
+ assertThat(bf.getBean(OptionalTestBean.class).getName(), equalTo(Optional.of("myValue")));
+ }
+
+ @Test
+ public void optionalPropertyWithoutValue() {
+ DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
+ bf.setConversionService(new DefaultConversionService());
+ bf.registerBeanDefinition("testBean",
+ genericBeanDefinition(OptionalTestBean.class)
+ .addPropertyValue("name", "${my.name}")
+ .getBeanDefinition());
+
+ MockEnvironment env = new MockEnvironment();
+ env.setProperty("my.name", "");
+
+ PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
+ ppc.setEnvironment(env);
+ ppc.setIgnoreUnresolvablePlaceholders(true);
+ ppc.setNullValue("");
+ ppc.postProcessBeanFactory(bf);
+ assertThat(bf.getBean(OptionalTestBean.class).getName(), equalTo(Optional.empty()));
+ }
+
+
+ private static class OptionalTestBean {
+
+ private Optional<String> name;
+
+ public Optional<String> getName() {
+ return name;
+ }
+
+ public void setName(Optional<String> name) {
+ this.name = name;
+ }
+ }
+
}
diff --git a/spring-context/src/test/java/org/springframework/context/support/SerializableBeanFactoryMemoryLeakTests.java b/spring-context/src/test/java/org/springframework/context/support/SerializableBeanFactoryMemoryLeakTests.java
index 527ca333..7e2c8b86 100644
--- a/spring-context/src/test/java/org/springframework/context/support/SerializableBeanFactoryMemoryLeakTests.java
+++ b/spring-context/src/test/java/org/springframework/context/support/SerializableBeanFactoryMemoryLeakTests.java
@@ -85,9 +85,11 @@ public class SerializableBeanFactoryMemoryLeakTests {
ctx.refresh();
assertThat(serializableFactoryCount(), equalTo(1));
ctx.close();
- } catch (BeanCreationException ex) {
+ }
+ catch (BeanCreationException ex) {
// ignore - this is expected on refresh() for failure case tests
- } finally {
+ }
+ finally {
assertThat(serializableFactoryCount(), equalTo(0));
}
}
diff --git a/spring-context/src/test/java/org/springframework/ejb/access/LocalStatelessSessionProxyFactoryBeanTests.java b/spring-context/src/test/java/org/springframework/ejb/access/LocalStatelessSessionProxyFactoryBeanTests.java
index 9fbf0bbd..29b40fc1 100644
--- a/spring-context/src/test/java/org/springframework/ejb/access/LocalStatelessSessionProxyFactoryBeanTests.java
+++ b/spring-context/src/test/java/org/springframework/ejb/access/LocalStatelessSessionProxyFactoryBeanTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -182,19 +182,19 @@ public class LocalStatelessSessionProxyFactoryBeanTests {
}
- public static interface MyHome extends EJBLocalHome {
+ public interface MyHome extends EJBLocalHome {
MyBusinessMethods create() throws CreateException;
}
- public static interface MyBusinessMethods {
+ public interface MyBusinessMethods {
int getValue();
}
- public static interface MyEjb extends EJBLocalObject, MyBusinessMethods {
+ public interface MyEjb extends EJBLocalObject, MyBusinessMethods {
}
}
diff --git a/spring-context/src/test/java/org/springframework/ejb/access/SimpleRemoteStatelessSessionProxyFactoryBeanTests.java b/spring-context/src/test/java/org/springframework/ejb/access/SimpleRemoteStatelessSessionProxyFactoryBeanTests.java
index 8efc5b57..2d62ee73 100644
--- a/spring-context/src/test/java/org/springframework/ejb/access/SimpleRemoteStatelessSessionProxyFactoryBeanTests.java
+++ b/spring-context/src/test/java/org/springframework/ejb/access/SimpleRemoteStatelessSessionProxyFactoryBeanTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -279,26 +279,25 @@ public class SimpleRemoteStatelessSessionProxyFactoryBeanTests extends SimpleRem
}
- protected static interface MyHome extends EJBHome {
+ protected interface MyHome extends EJBHome {
MyBusinessMethods create() throws CreateException, RemoteException;
}
- protected static interface MyBusinessMethods {
+ protected interface MyBusinessMethods {
int getValue() throws RemoteException;
}
- protected static interface MyLocalBusinessMethods {
+ protected interface MyLocalBusinessMethods {
int getValue();
}
- protected static interface MyEjb extends EJBObject, MyBusinessMethods {
-
+ protected interface MyEjb extends EJBObject, MyBusinessMethods {
}
}
diff --git a/spring-context/src/test/java/org/springframework/format/datetime/DateFormattingTests.java b/spring-context/src/test/java/org/springframework/format/datetime/DateFormattingTests.java
index ffbd422e..0681d4ec 100644
--- a/spring-context/src/test/java/org/springframework/format/datetime/DateFormattingTests.java
+++ b/spring-context/src/test/java/org/springframework/format/datetime/DateFormattingTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -149,6 +149,14 @@ public class DateFormattingTests {
}
@Test
+ public void testBindDateTimeOverflow() {
+ MutablePropertyValues propertyValues = new MutablePropertyValues();
+ propertyValues.add("dateAnnotatedPattern", "02/29/09 12:00 PM");
+ binder.bind(propertyValues);
+ assertEquals(1, binder.getBindingResult().getErrorCount());
+ }
+
+ @Test
public void testBindISODate() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("isoDate", "2009-10-31");
diff --git a/spring-context/src/test/java/org/springframework/format/datetime/joda/JodaTimeFormattingTests.java b/spring-context/src/test/java/org/springframework/format/datetime/joda/JodaTimeFormattingTests.java
index 1e96b13c..b0de33ec 100644
--- a/spring-context/src/test/java/org/springframework/format/datetime/joda/JodaTimeFormattingTests.java
+++ b/spring-context/src/test/java/org/springframework/format/datetime/joda/JodaTimeFormattingTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -318,6 +318,14 @@ public class JodaTimeFormattingTests {
}
@Test
+ public void testBindDateTimeOverflow() {
+ MutablePropertyValues propertyValues = new MutablePropertyValues();
+ propertyValues.add("dateTimeAnnotatedPattern", "02/29/09 12:00 PM");
+ binder.bind(propertyValues);
+ assertEquals(1, binder.getBindingResult().getErrorCount());
+ }
+
+ @Test
public void testBindDateTimeAnnotatedDefault() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("dateTimeAnnotatedDefault", new DateTime(2009, 10, 31, 12, 0, ISOChronology.getInstanceUTC()));
diff --git a/spring-context/src/test/java/org/springframework/format/datetime/standard/DateTimeFormattingTests.java b/spring-context/src/test/java/org/springframework/format/datetime/standard/DateTimeFormattingTests.java
index a60fbd69..3bc0efd3 100644
--- a/spring-context/src/test/java/org/springframework/format/datetime/standard/DateTimeFormattingTests.java
+++ b/spring-context/src/test/java/org/springframework/format/datetime/standard/DateTimeFormattingTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -124,7 +124,7 @@ public class DateTimeFormattingTests {
@Test
public void testBindLocalDateArray() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
- propertyValues.add("localDate", new String[]{"10/31/09"});
+ propertyValues.add("localDate", new String[] {"10/31/09"});
binder.bind(propertyValues);
assertEquals(0, binder.getBindingResult().getErrorCount());
}
@@ -293,6 +293,14 @@ public class DateTimeFormattingTests {
}
@Test
+ public void testBindDateTimeOverflow() {
+ MutablePropertyValues propertyValues = new MutablePropertyValues();
+ propertyValues.add("dateTimeAnnotatedPattern", "02/29/09 12:00 PM");
+ binder.bind(propertyValues);
+ assertEquals(1, binder.getBindingResult().getErrorCount());
+ }
+
+ @Test
public void testBindISODate() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("isoDate", "2009-10-31");
diff --git a/spring-context/src/test/java/org/springframework/format/support/FormattingConversionServiceTests.java b/spring-context/src/test/java/org/springframework/format/support/FormattingConversionServiceTests.java
index a6f5779b..e7c034c3 100644
--- a/spring-context/src/test/java/org/springframework/format/support/FormattingConversionServiceTests.java
+++ b/spring-context/src/test/java/org/springframework/format/support/FormattingConversionServiceTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -218,7 +218,7 @@ public class FormattingConversionServiceTests {
assertEquals(new LocalDate(2009, 11, 1), new LocalDate(dates.get(1)));
assertEquals(new LocalDate(2009, 11, 2), new LocalDate(dates.get(2)));
- Object model = BeanUtils.instantiate(modelClass);
+ Object model = modelClass.newInstance();
ConfigurablePropertyAccessor accessor = directFieldAccess ? PropertyAccessorFactory.forDirectFieldAccess(model) :
PropertyAccessorFactory.forBeanPropertyAccess(model);
accessor.setConversionService(formattingService);
diff --git a/spring-context/src/test/java/org/springframework/jmx/access/MBeanClientInterceptorTests.java b/spring-context/src/test/java/org/springframework/jmx/access/MBeanClientInterceptorTests.java
index b2f4569b..3f30e901 100644
--- a/spring-context/src/test/java/org/springframework/jmx/access/MBeanClientInterceptorTests.java
+++ b/spring-context/src/test/java/org/springframework/jmx/access/MBeanClientInterceptorTests.java
@@ -195,7 +195,8 @@ public class MBeanClientInterceptorTests extends AbstractMBeanServerTests {
// now start the connector
try {
connector.start();
- } catch (BindException ex) {
+ }
+ catch (BindException ex) {
System.out.println("Skipping remainder of JMX LazyConnectionToRemote test because binding to local port ["
+ port + "] failed: " + ex.getMessage());
return;
@@ -205,13 +206,15 @@ public class MBeanClientInterceptorTests extends AbstractMBeanServerTests {
try {
assertEquals("Rob Harrop", bean.getName());
assertEquals(100, bean.getAge());
- } finally {
+ }
+ finally {
connector.stop();
}
try {
bean.getName();
- } catch (JmxException ex) {
+ }
+ catch (JmxException ex) {
// expected
}
@@ -222,7 +225,8 @@ public class MBeanClientInterceptorTests extends AbstractMBeanServerTests {
try {
assertEquals("Rob Harrop", bean.getName());
assertEquals(100, bean.getAge());
- } finally {
+ }
+ finally {
connector.stop();
}
}
diff --git a/spring-context/src/test/java/org/springframework/jmx/access/RemoteMBeanClientInterceptorTests.java b/spring-context/src/test/java/org/springframework/jmx/access/RemoteMBeanClientInterceptorTests.java
index 218fb8e9..dea449ec 100644
--- a/spring-context/src/test/java/org/springframework/jmx/access/RemoteMBeanClientInterceptorTests.java
+++ b/spring-context/src/test/java/org/springframework/jmx/access/RemoteMBeanClientInterceptorTests.java
@@ -65,7 +65,8 @@ public class RemoteMBeanClientInterceptorTests extends MBeanClientInterceptorTes
this.connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(getServiceUrl(), null, getServer());
try {
this.connectorServer.start();
- } catch (BindException ex) {
+ }
+ catch (BindException ex) {
System.out.println("Skipping remote JMX tests because binding to local port ["
+ SERVICE_PORT + "] failed: " + ex.getMessage());
runTests = false;
diff --git a/spring-context/src/test/java/org/springframework/jmx/export/NotificationListenerTests.java b/spring-context/src/test/java/org/springframework/jmx/export/NotificationListenerTests.java
index 81dc5307..149e574c 100644
--- a/spring-context/src/test/java/org/springframework/jmx/export/NotificationListenerTests.java
+++ b/spring-context/src/test/java/org/springframework/jmx/export/NotificationListenerTests.java
@@ -172,7 +172,8 @@ public class NotificationListenerTests extends AbstractMBeanServerTests {
if (notification instanceof AttributeChangeNotification) {
AttributeChangeNotification changeNotification = (AttributeChangeNotification) notification;
return "Name".equals(changeNotification.getAttributeName());
- } else {
+ }
+ else {
return false;
}
}
@@ -200,7 +201,8 @@ public class NotificationListenerTests extends AbstractMBeanServerTests {
try {
new NotificationListenerBean().afterPropertiesSet();
fail("Must have thrown an IllegalArgumentException (no NotificationListener supplied)");
- } catch (IllegalArgumentException expected) {
+ }
+ catch (IllegalArgumentException expected) {
}
}
@@ -463,7 +465,8 @@ public class NotificationListenerTests extends AbstractMBeanServerTests {
if (currentCount != null) {
int count = currentCount.intValue() + 1;
this.attributeCounts.put(attributeName, new Integer(count));
- } else {
+ }
+ else {
this.attributeCounts.put(attributeName, new Integer(1));
}
diff --git a/spring-context/src/test/java/org/springframework/jmx/export/annotation/AnnotationTestBean.java b/spring-context/src/test/java/org/springframework/jmx/export/annotation/AnnotationTestBean.java
index 559b6449..92479607 100644
--- a/spring-context/src/test/java/org/springframework/jmx/export/annotation/AnnotationTestBean.java
+++ b/spring-context/src/test/java/org/springframework/jmx/export/annotation/AnnotationTestBean.java
@@ -28,7 +28,7 @@ import org.springframework.stereotype.Service;
@ManagedResource(objectName = "bean:name=testBean4", description = "My Managed Bean", log = true,
logFile = "jmx.log", currencyTimeLimit = 15, persistPolicy = "OnUpdate", persistPeriod = 200,
persistLocation = "./foo", persistName = "bar.jmx")
-@ManagedNotification(name="My Notification", notificationTypes={"type.foo", "type.bar"})
+@ManagedNotification(name = "My Notification", notificationTypes = { "type.foo", "type.bar" })
public class AnnotationTestBean implements IJmxTestBean {
private String name;
diff --git a/spring-context/src/test/java/org/springframework/jmx/export/annotation/AnotherAnnotationTestBeanImpl.java b/spring-context/src/test/java/org/springframework/jmx/export/annotation/AnotherAnnotationTestBeanImpl.java
index 1acaefb0..8857bcc0 100644
--- a/spring-context/src/test/java/org/springframework/jmx/export/annotation/AnotherAnnotationTestBeanImpl.java
+++ b/spring-context/src/test/java/org/springframework/jmx/export/annotation/AnotherAnnotationTestBeanImpl.java
@@ -19,7 +19,7 @@ package org.springframework.jmx.export.annotation;
/**
* @author Stephane Nicoll
*/
-class AnotherAnnotationTestBeanImpl implements AnotherAnnotationTestBean {
+public class AnotherAnnotationTestBeanImpl implements AnotherAnnotationTestBean {
private String bar;
diff --git a/spring-context/src/test/java/org/springframework/jmx/export/annotation/EnableMBeanExportConfigurationTests.java b/spring-context/src/test/java/org/springframework/jmx/export/annotation/EnableMBeanExportConfigurationTests.java
index caad3982..90488829 100644
--- a/spring-context/src/test/java/org/springframework/jmx/export/annotation/EnableMBeanExportConfigurationTests.java
+++ b/spring-context/src/test/java/org/springframework/jmx/export/annotation/EnableMBeanExportConfigurationTests.java
@@ -19,7 +19,10 @@ package org.springframework.jmx.export.annotation;
import javax.management.MBeanServer;
import javax.management.ObjectName;
+import org.junit.After;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
@@ -33,6 +36,7 @@ import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.jmx.export.MBeanExporterTests;
import org.springframework.jmx.export.TestDynamicMBean;
+import org.springframework.jmx.export.metadata.InvalidMetadataException;
import org.springframework.jmx.support.MBeanServerFactoryBean;
import org.springframework.jmx.support.ObjectNameManager;
import org.springframework.jmx.support.RegistrationPolicy;
@@ -44,109 +48,109 @@ import static org.junit.Assert.*;
* Tests for {@link EnableMBeanExport} and {@link MBeanExportConfiguration}.
*
* @author Phillip Webb
+ * @author Stephane Nicoll
* @see AnnotationLazyInitMBeanTests
*/
public class EnableMBeanExportConfigurationTests {
+ @Rule
+ public final ExpectedException thrown = ExpectedException.none();
+
+ private AnnotationConfigApplicationContext ctx;
+
+ @After
+ public void closeContext() {
+ if (this.ctx != null) {
+ this.ctx.close();
+ }
+ }
+
@Test
public void testLazyNaming() throws Exception {
- AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
- LazyNamingConfiguration.class);
- try {
- MBeanServer server = (MBeanServer) ctx.getBean("server");
- ObjectName oname = ObjectNameManager.getInstance("bean:name=testBean4");
- assertNotNull(server.getObjectInstance(oname));
- String name = (String) server.getAttribute(oname, "Name");
- assertEquals("Invalid name returned", "TEST", name);
- }
- finally {
- ctx.close();
- }
+ load(LazyNamingConfiguration.class);
+ validateAnnotationTestBean();
+ }
+
+ private void load(Class<?>... config) {
+ this.ctx = new AnnotationConfigApplicationContext(config);
}
@Test
public void testOnlyTargetClassIsExposed() throws Exception {
- AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
- ProxyConfiguration.class);
- try {
- MBeanServer server = (MBeanServer) ctx.getBean("server");
- ObjectName oname = ObjectNameManager.getInstance("bean:name=testBean4");
- assertNotNull(server.getObjectInstance(oname));
- assertEquals("TEST", server.getAttribute(oname, "Name"));
- }
- finally {
- ctx.close();
- }
+ load(ProxyConfiguration.class);
+ validateAnnotationTestBean();
+ }
+
+ @Test
+ @SuppressWarnings("resource")
+ public void testPackagePrivateExtensionCantBeExposed() {
+ this.thrown.expect(InvalidMetadataException.class);
+ this.thrown.expectMessage(PackagePrivateTestBean.class.getName());
+ this.thrown.expectMessage("must be public");
+ new AnnotationConfigApplicationContext(PackagePrivateConfiguration.class);
+ }
+
+ @Test
+ @SuppressWarnings("resource")
+ public void testPackagePrivateImplementationCantBeExposed() {
+ this.thrown.expect(InvalidMetadataException.class);
+ this.thrown.expectMessage(PackagePrivateAnnotationTestBean.class.getName());
+ this.thrown.expectMessage("must be public");
+ new AnnotationConfigApplicationContext(PackagePrivateInterfaceImplementationConfiguration.class);
+ }
+
+ @Test
+ public void testPackagePrivateClassExtensionCanBeExposed() throws Exception {
+ load(PackagePrivateExtensionConfiguration.class);
+ validateAnnotationTestBean();
}
@Test
public void testPlaceholderBased() throws Exception {
MockEnvironment env = new MockEnvironment();
env.setProperty("serverName", "server");
- AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
- ctx.setEnvironment(env);
- ctx.register(PlaceholderBasedConfiguration.class);
- ctx.refresh();
- try {
- MBeanServer server = (MBeanServer) ctx.getBean("server");
- ObjectName oname = ObjectNameManager.getInstance("bean:name=testBean4");
- assertNotNull(server.getObjectInstance(oname));
- String name = (String) server.getAttribute(oname, "Name");
- assertEquals("Invalid name returned", "TEST", name);
- }
- finally {
- ctx.close();
- }
+ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
+ context.setEnvironment(env);
+ context.register(PlaceholderBasedConfiguration.class);
+ context.refresh();
+ this.ctx = context;
+ validateAnnotationTestBean();
}
@Test
public void testLazyAssembling() throws Exception {
System.setProperty("domain", "bean");
- AnnotationConfigApplicationContext ctx =
- new AnnotationConfigApplicationContext(LazyAssemblingConfiguration.class);
+ load(LazyAssemblingConfiguration.class);
try {
- MBeanServer server = (MBeanServer) ctx.getBean("server");
-
- ObjectName oname = ObjectNameManager.getInstance("bean:name=testBean4");
- assertNotNull(server.getObjectInstance(oname));
- String name = (String) server.getAttribute(oname, "Name");
- assertEquals("Invalid name returned", "TEST", name);
-
- oname = ObjectNameManager.getInstance("bean:name=testBean5");
- assertNotNull(server.getObjectInstance(oname));
- name = (String) server.getAttribute(oname, "Name");
- assertEquals("Invalid name returned", "FACTORY", name);
-
- oname = ObjectNameManager.getInstance("spring:mbean=true");
- assertNotNull(server.getObjectInstance(oname));
- name = (String) server.getAttribute(oname, "Name");
- assertEquals("Invalid name returned", "Rob Harrop", name);
-
- oname = ObjectNameManager.getInstance("spring:mbean=another");
- assertNotNull(server.getObjectInstance(oname));
- name = (String) server.getAttribute(oname, "Name");
- assertEquals("Invalid name returned", "Juergen Hoeller", name);
+ MBeanServer server = (MBeanServer) this.ctx.getBean("server");
+
+ validateMBeanAttribute(server, "bean:name=testBean4", "TEST");
+ validateMBeanAttribute(server, "bean:name=testBean5", "FACTORY");
+ validateMBeanAttribute(server, "spring:mbean=true", "Rob Harrop");
+ validateMBeanAttribute(server, "spring:mbean=another", "Juergen Hoeller");
}
finally {
System.clearProperty("domain");
- ctx.close();
}
}
@Test
public void testComponentScan() throws Exception {
- AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
- ComponentScanConfiguration.class);
- try {
- MBeanServer server = (MBeanServer) ctx.getBean("server");
- ObjectName oname = ObjectNameManager.getInstance("bean:name=testBean4");
- assertNotNull(server.getObjectInstance(oname));
- String name = (String) server.getAttribute(oname, "Name");
- assertNull(name);
- }
- finally {
- ctx.close();
- }
+ load(ComponentScanConfiguration.class);
+ MBeanServer server = (MBeanServer) this.ctx.getBean("server");
+ validateMBeanAttribute(server, "bean:name=testBean4", null);
+ }
+
+ private void validateAnnotationTestBean() throws Exception {
+ MBeanServer server = (MBeanServer) this.ctx.getBean("server");
+ validateMBeanAttribute(server,"bean:name=testBean4", "TEST");
+ }
+
+ private void validateMBeanAttribute(MBeanServer server, String objectName, String expected) throws Exception {
+ ObjectName oname = ObjectNameManager.getInstance(objectName);
+ assertNotNull(server.getObjectInstance(oname));
+ String name = (String) server.getAttribute(oname, "Name");
+ assertEquals("Invalid name returned", expected, name);
}
@@ -271,4 +275,97 @@ public class EnableMBeanExportConfigurationTests {
}
}
+ @Configuration
+ @EnableMBeanExport(server = "server")
+ static class PackagePrivateConfiguration {
+
+ @Bean
+ public MBeanServerFactoryBean server() throws Exception {
+ return new MBeanServerFactoryBean();
+ }
+
+ @Bean
+ public PackagePrivateTestBean testBean() {
+ return new PackagePrivateTestBean();
+ }
+ }
+
+ @ManagedResource(objectName = "bean:name=packagePrivate")
+ private static class PackagePrivateTestBean {
+
+ private String name;
+
+ @ManagedAttribute
+ public String getName() {
+ return this.name;
+ }
+
+ @ManagedAttribute
+ public void setName(String name) {
+ this.name = name;
+ }
+ }
+
+
+ @Configuration
+ @EnableMBeanExport(server = "server")
+ static class PackagePrivateExtensionConfiguration {
+
+ @Bean
+ public MBeanServerFactoryBean server() throws Exception {
+ return new MBeanServerFactoryBean();
+ }
+
+ @Bean
+ public PackagePrivateTestBeanExtension testBean() {
+ PackagePrivateTestBeanExtension bean = new PackagePrivateTestBeanExtension();
+ bean.setName("TEST");
+ return bean;
+ }
+ }
+
+ private static class PackagePrivateTestBeanExtension extends AnnotationTestBean {
+
+ }
+
+ @Configuration
+ @EnableMBeanExport(server = "server")
+ static class PackagePrivateInterfaceImplementationConfiguration {
+
+ @Bean
+ public MBeanServerFactoryBean server() throws Exception {
+ return new MBeanServerFactoryBean();
+ }
+
+ @Bean
+ public PackagePrivateAnnotationTestBean testBean() {
+ return new PackagePrivateAnnotationTestBean();
+ }
+ }
+
+ private static class PackagePrivateAnnotationTestBean implements AnotherAnnotationTestBean {
+
+ private String bar;
+
+ @Override
+ public void foo() {
+
+ }
+
+ @Override
+ public String getBar() {
+ return this.bar;
+ }
+
+ @Override
+ public void setBar(String bar) {
+ this.bar = bar;
+ }
+
+ @Override
+ public int getCacheEntries() {
+ return 0;
+ }
+ }
+
}
diff --git a/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodNameBasedMBeanInfoAssemblerMappedTests.java b/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodNameBasedMBeanInfoAssemblerMappedTests.java
index 21efe0ba..9b8ec774 100644
--- a/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodNameBasedMBeanInfoAssemblerMappedTests.java
+++ b/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodNameBasedMBeanInfoAssemblerMappedTests.java
@@ -47,7 +47,7 @@ public class MethodNameBasedMBeanInfoAssemblerMappedTests extends AbstractJmxAss
public void testWithFallThrough() throws Exception {
MethodNameBasedMBeanInfoAssembler assembler =
getWithMapping("foobar", "add,myOperation,getName,setName,getAge");
- assembler.setManagedMethods(new String[]{"getNickName", "setNickName"});
+ assembler.setManagedMethods("getNickName", "setNickName");
ModelMBeanInfo inf = assembler.getMBeanInfo(getBean(), getObjectName());
MBeanAttributeInfo attr = inf.getAttribute("NickName");
diff --git a/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodNameBasedMBeanInfoAssemblerTests.java b/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodNameBasedMBeanInfoAssemblerTests.java
index 3b51e92b..dae9a8a3 100644
--- a/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodNameBasedMBeanInfoAssemblerTests.java
+++ b/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodNameBasedMBeanInfoAssemblerTests.java
@@ -52,7 +52,7 @@ public class MethodNameBasedMBeanInfoAssemblerTests extends AbstractJmxAssembler
@Override
protected MBeanInfoAssembler getAssembler() {
MethodNameBasedMBeanInfoAssembler assembler = new MethodNameBasedMBeanInfoAssembler();
- assembler.setManagedMethods(new String[] {"add", "myOperation", "getName", "setName", "getAge"});
+ assembler.setManagedMethods("add", "myOperation", "getName", "setName", "getAge");
return assembler;
}
diff --git a/spring-context/src/test/java/org/springframework/jmx/support/ConnectorServerFactoryBeanTests.java b/spring-context/src/test/java/org/springframework/jmx/support/ConnectorServerFactoryBeanTests.java
index d1b6758d..3fbbde43 100644
--- a/spring-context/src/test/java/org/springframework/jmx/support/ConnectorServerFactoryBeanTests.java
+++ b/spring-context/src/test/java/org/springframework/jmx/support/ConnectorServerFactoryBeanTests.java
@@ -68,7 +68,8 @@ public class ConnectorServerFactoryBeanTests extends AbstractMBeanServerTests {
try {
checkServerConnection(getServer());
- } finally {
+ }
+ finally {
bean.destroy();
}
}
@@ -85,7 +86,8 @@ public class ConnectorServerFactoryBeanTests extends AbstractMBeanServerTests {
try {
checkServerConnection(getServer());
- } finally {
+ }
+ finally {
bean.destroy();
}
}
@@ -103,7 +105,8 @@ public class ConnectorServerFactoryBeanTests extends AbstractMBeanServerTests {
// Try to get the connector bean.
ObjectInstance instance = getServer().getObjectInstance(ObjectName.getInstance(OBJECT_NAME));
assertNotNull("ObjectInstance should not be null", instance);
- } finally {
+ }
+ finally {
bean.destroy();
}
}
@@ -117,9 +120,11 @@ public class ConnectorServerFactoryBeanTests extends AbstractMBeanServerTests {
// Try to get the connector bean.
getServer().getObjectInstance(ObjectName.getInstance(OBJECT_NAME));
fail("Instance should not be found");
- } catch (InstanceNotFoundException ex) {
+ }
+ catch (InstanceNotFoundException ex) {
// expected
- } finally {
+ }
+ finally {
bean.destroy();
}
}
diff --git a/spring-context/src/test/java/org/springframework/jmx/support/MBeanServerConnectionFactoryBeanTests.java b/spring-context/src/test/java/org/springframework/jmx/support/MBeanServerConnectionFactoryBeanTests.java
index de4746c4..66058126 100644
--- a/spring-context/src/test/java/org/springframework/jmx/support/MBeanServerConnectionFactoryBeanTests.java
+++ b/spring-context/src/test/java/org/springframework/jmx/support/MBeanServerConnectionFactoryBeanTests.java
@@ -74,10 +74,12 @@ public class MBeanServerConnectionFactoryBeanTests extends AbstractMBeanServerTe
// perform simple MBean count test
assertEquals("MBean count should be the same", getServer().getMBeanCount(), connection.getMBeanCount());
- } finally {
+ }
+ finally {
bean.destroy();
}
- } finally {
+ }
+ finally {
connectorServer.stop();
}
}
@@ -104,7 +106,8 @@ public class MBeanServerConnectionFactoryBeanTests extends AbstractMBeanServerTe
connector = getConnectorServer();
connector.start();
assertEquals("Incorrect MBean count", getServer().getMBeanCount(), connection.getMBeanCount());
- } finally {
+ }
+ finally {
bean.destroy();
if (connector != null) {
connector.stop();
diff --git a/spring-context/src/test/java/org/springframework/jndi/JndiPropertySourceTests.java b/spring-context/src/test/java/org/springframework/jndi/JndiPropertySourceTests.java
index a8a68cd0..cc8423dd 100644
--- a/spring-context/src/test/java/org/springframework/jndi/JndiPropertySourceTests.java
+++ b/spring-context/src/test/java/org/springframework/jndi/JndiPropertySourceTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +30,7 @@ import static org.junit.Assert.*;
* Unit tests for {@link JndiPropertySource}.
*
* @author Chris Beams
+ * @author Juergen Hoeller
* @since 3.1
*/
public class JndiPropertySourceTests {
@@ -56,7 +57,7 @@ public class JndiPropertySourceTests {
jndiLocator.setJndiTemplate(jndiTemplate);
JndiPropertySource ps = new JndiPropertySource("jndiProperties", jndiLocator);
- assertThat((String)ps.getProperty("p1"), equalTo("v1"));
+ assertThat(ps.getProperty("p1"), equalTo("v1"));
}
@Test
@@ -75,7 +76,36 @@ public class JndiPropertySourceTests {
jndiLocator.setJndiTemplate(jndiTemplate);
JndiPropertySource ps = new JndiPropertySource("jndiProperties", jndiLocator);
- assertThat((String)ps.getProperty("p1"), equalTo("v1"));
+ assertThat(ps.getProperty("p1"), equalTo("v1"));
+ }
+
+ @Test
+ public void propertyWithDefaultClauseInResourceRefMode() {
+ JndiLocatorDelegate jndiLocator = new JndiLocatorDelegate() {
+ @Override
+ public Object lookup(String jndiName) throws NamingException {
+ throw new IllegalStateException("Should not get called");
+ }
+ };
+ jndiLocator.setResourceRef(true);
+
+ JndiPropertySource ps = new JndiPropertySource("jndiProperties", jndiLocator);
+ assertThat(ps.getProperty("propertyKey:defaultValue"), nullValue());
+ }
+
+ @Test
+ public void propertyWithColonInNonResourceRefMode() {
+ JndiLocatorDelegate jndiLocator = new JndiLocatorDelegate() {
+ @Override
+ public Object lookup(String jndiName) throws NamingException {
+ assertEquals("my:key", jndiName);
+ return "my:value";
+ }
+ };
+ jndiLocator.setResourceRef(false);
+
+ JndiPropertySource ps = new JndiPropertySource("jndiProperties", jndiLocator);
+ assertThat(ps.getProperty("my:key"), equalTo("my:value"));
}
}
diff --git a/spring-context/src/test/java/org/springframework/remoting/rmi/RmiSupportTests.java b/spring-context/src/test/java/org/springframework/remoting/rmi/RmiSupportTests.java
index 6debb825..a3aba60d 100644
--- a/spring-context/src/test/java/org/springframework/remoting/rmi/RmiSupportTests.java
+++ b/spring-context/src/test/java/org/springframework/remoting/rmi/RmiSupportTests.java
@@ -30,7 +30,6 @@ import java.rmi.UnknownHostException;
import java.rmi.UnmarshalException;
import org.aopalliance.intercept.MethodInvocation;
-
import org.junit.Test;
import org.springframework.remoting.RemoteAccessException;
@@ -342,7 +341,7 @@ public class RmiSupportTests {
client.afterPropertiesSet();
fail("url isn't set, expected IllegalArgumentException");
}
- catch(IllegalArgumentException e){
+ catch (IllegalArgumentException ex){
// expected
}
}
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 ed7ea99f..5fa835f4 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,19 +26,24 @@ import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import org.junit.Test;
+import org.mockito.Mockito;
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.BeanDefinitionStoreException;
+import org.springframework.beans.factory.BeanNotOfRequiredTypeException;
+import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Lazy;
import org.springframework.core.Ordered;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import static org.hamcrest.CoreMatchers.anyOf;
@@ -68,6 +73,48 @@ public class EnableAsyncTests {
}
@Test
+ public void proxyingOccursWithMockitoStub() {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
+ ctx.register(AsyncConfigWithMockito.class, AsyncBeanUser.class);
+ ctx.refresh();
+
+ AsyncBeanUser asyncBeanUser = ctx.getBean(AsyncBeanUser.class);
+ AsyncBean asyncBean = asyncBeanUser.getAsyncBean();
+ assertThat(AopUtils.isAopProxy(asyncBean), is(true));
+ asyncBean.work();
+ }
+
+ @Test
+ public void properExceptionForExistingProxyDependencyMismatch() {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
+ ctx.register(AsyncConfig.class, AsyncBeanWithInterface.class, AsyncBeanUser.class);
+
+ try {
+ ctx.refresh();
+ fail("Should have thrown UnsatisfiedDependencyException");
+ }
+ catch (UnsatisfiedDependencyException ex) {
+ ex.printStackTrace();
+ assertTrue(ex.getCause() instanceof BeanNotOfRequiredTypeException);
+ }
+ }
+
+ @Test
+ public void properExceptionForResolvedProxyDependencyMismatch() {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
+ ctx.register(AsyncConfig.class, AsyncBeanUser.class, AsyncBeanWithInterface.class);
+
+ try {
+ ctx.refresh();
+ fail("Should have thrown UnsatisfiedDependencyException");
+ }
+ catch (UnsatisfiedDependencyException ex) {
+ ex.printStackTrace();
+ assertTrue(ex.getCause() instanceof BeanNotOfRequiredTypeException);
+ }
+ }
+
+ @Test
public void withAsyncBeanWithExecutorQualifiedByName() throws ExecutionException, InterruptedException {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AsyncWithExecutorQualifiedByNameConfig.class);
@@ -107,7 +154,7 @@ public class EnableAsyncTests {
@Test
public void customAsyncAnnotationIsPropagated() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
- ctx.register(CustomAsyncAnnotationConfig.class);
+ ctx.register(CustomAsyncAnnotationConfig.class, CustomAsyncBean.class);
ctx.refresh();
Object bean = ctx.getBean(CustomAsyncBean.class);
@@ -200,14 +247,31 @@ public class EnableAsyncTests {
}
- @Configuration
- @EnableAsync(annotation = CustomAsync.class)
- static class CustomAsyncAnnotationConfig {
+ @Component("asyncBean")
+ static class AsyncBeanWithInterface extends AsyncBean implements Runnable {
- @Bean
- public CustomAsyncBean asyncBean() {
- return new CustomAsyncBean();
+ @Override
+ public void run() {
+ }
+ }
+
+
+ static class AsyncBeanUser {
+
+ private final AsyncBean asyncBean;
+
+ public AsyncBeanUser(AsyncBean asyncBean) {
+ this.asyncBean = asyncBean;
}
+
+ public AsyncBean getAsyncBean() {
+ return asyncBean;
+ }
+ }
+
+
+ @EnableAsync(annotation = CustomAsync.class)
+ static class CustomAsyncAnnotationConfig {
}
@@ -260,6 +324,17 @@ public class EnableAsyncTests {
@Configuration
@EnableAsync
+ static class AsyncConfigWithMockito {
+
+ @Bean @Lazy
+ public AsyncBean asyncBean() {
+ return Mockito.mock(AsyncBean.class);
+ }
+ }
+
+
+ @Configuration
+ @EnableAsync
static class CustomExecutorAsyncConfig implements AsyncConfigurer {
@Bean
diff --git a/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableSchedulingTests.java b/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableSchedulingTests.java
index 62399fbf..c3a46c6e 100644
--- a/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableSchedulingTests.java
+++ b/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableSchedulingTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,10 +19,8 @@ package org.springframework.scheduling.annotation;
import java.util.Date;
import java.util.concurrent.atomic.AtomicInteger;
-import org.junit.Before;
-import org.junit.Rule;
+import org.junit.After;
import org.junit.Test;
-import org.junit.rules.ExpectedException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
@@ -48,65 +46,151 @@ import static org.junit.Assert.*;
*/
public class EnableSchedulingTests {
- @Rule
- public final ExpectedException exception = ExpectedException.none();
+ private AnnotationConfigApplicationContext ctx;
- @Before
- public void setUp() {
- Assume.group(TestGroup.PERFORMANCE);
+ @After
+ public void tearDown() {
+ if (ctx != null) {
+ ctx.close();
+ }
}
@Test
public void withFixedRateTask() throws InterruptedException {
- AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(FixedRateTaskConfig.class);
+ Assume.group(TestGroup.PERFORMANCE);
+
+ ctx = new AnnotationConfigApplicationContext(FixedRateTaskConfig.class);
Thread.sleep(100);
assertThat(ctx.getBean(AtomicInteger.class).get(), greaterThanOrEqualTo(10));
- ctx.close();
}
+ @Test
+ public void withSubclass() throws InterruptedException {
+ Assume.group(TestGroup.PERFORMANCE);
- @Configuration
- @EnableScheduling
- static class FixedRateTaskConfig {
+ ctx = new AnnotationConfigApplicationContext(FixedRateTaskConfigSubclass.class);
- @Bean
- public AtomicInteger counter() {
- return new AtomicInteger();
- }
+ Thread.sleep(100);
+ assertThat(ctx.getBean(AtomicInteger.class).get(), greaterThanOrEqualTo(10));
+ }
- @Scheduled(fixedRate = 10)
- public void task() {
- counter().incrementAndGet();
- }
+ @Test
+ public void withExplicitScheduler() throws InterruptedException {
+ Assume.group(TestGroup.PERFORMANCE);
+
+ ctx = new AnnotationConfigApplicationContext(ExplicitSchedulerConfig.class);
+
+ Thread.sleep(100);
+ assertThat(ctx.getBean(AtomicInteger.class).get(), greaterThanOrEqualTo(10));
+ assertThat(ctx.getBean(ExplicitSchedulerConfig.class).threadName, startsWith("explicitScheduler-"));
}
+ @Test
+ public void withExplicitSchedulerAmbiguity_andSchedulingEnabled() {
+ // No exception raised as of 4.3, aligned with the behavior for @Async methods (SPR-14030)
+ ctx = new AnnotationConfigApplicationContext(AmbiguousExplicitSchedulerConfig.class);
+ }
@Test
- public void withSubclass() throws InterruptedException {
- AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(FixedRateTaskConfigSubclass.class);
+ public void withExplicitScheduledTaskRegistrar() throws InterruptedException {
+ Assume.group(TestGroup.PERFORMANCE);
+
+ ctx = new AnnotationConfigApplicationContext(ExplicitScheduledTaskRegistrarConfig.class);
Thread.sleep(100);
assertThat(ctx.getBean(AtomicInteger.class).get(), greaterThanOrEqualTo(10));
- ctx.close();
+ assertThat(ctx.getBean(ExplicitScheduledTaskRegistrarConfig.class).threadName, startsWith("explicitScheduler1"));
}
+ @Test
+ public void withAmbiguousTaskSchedulers_butNoActualTasks() {
+ ctx = new AnnotationConfigApplicationContext(SchedulingEnabled_withAmbiguousTaskSchedulers_butNoActualTasks.class);
+ }
- @Configuration
- static class FixedRateTaskConfigSubclass extends FixedRateTaskConfig {
+ @Test
+ public void withAmbiguousTaskSchedulers_andSingleTask() {
+ // No exception raised as of 4.3, aligned with the behavior for @Async methods (SPR-14030)
+ ctx = new AnnotationConfigApplicationContext(SchedulingEnabled_withAmbiguousTaskSchedulers_andSingleTask.class);
+ }
+
+ @Test
+ public void withAmbiguousTaskSchedulers_andSingleTask_disambiguatedByScheduledTaskRegistrarBean() throws InterruptedException {
+ Assume.group(TestGroup.PERFORMANCE);
+
+ ctx = new AnnotationConfigApplicationContext(
+ SchedulingEnabled_withAmbiguousTaskSchedulers_andSingleTask_disambiguatedByScheduledTaskRegistrar.class);
+
+ Thread.sleep(100);
+ assertThat(ctx.getBean(ThreadAwareWorker.class).executedByThread, startsWith("explicitScheduler2-"));
}
+ @Test
+ public void withAmbiguousTaskSchedulers_andSingleTask_disambiguatedBySchedulerNameAttribute() throws InterruptedException {
+ Assume.group(TestGroup.PERFORMANCE);
+
+ ctx = new AnnotationConfigApplicationContext(
+ SchedulingEnabled_withAmbiguousTaskSchedulers_andSingleTask_disambiguatedBySchedulerNameAttribute.class);
+
+ Thread.sleep(100);
+ assertThat(ctx.getBean(ThreadAwareWorker.class).executedByThread, startsWith("explicitScheduler2-"));
+ }
@Test
- public void withExplicitScheduler() throws InterruptedException {
- AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ExplicitSchedulerConfig.class);
+ public void withTaskAddedVia_configureTasks() throws InterruptedException {
+ Assume.group(TestGroup.PERFORMANCE);
+
+ ctx = new AnnotationConfigApplicationContext(SchedulingEnabled_withTaskAddedVia_configureTasks.class);
Thread.sleep(100);
- assertThat(ctx.getBean(AtomicInteger.class).get(), greaterThanOrEqualTo(10));
- assertThat(ctx.getBean(ExplicitSchedulerConfig.class).threadName, startsWith("explicitScheduler-"));
- ctx.close();
+ assertThat(ctx.getBean(ThreadAwareWorker.class).executedByThread, startsWith("taskScheduler-"));
+ }
+
+ @Test
+ public void withTriggerTask() throws InterruptedException {
+ Assume.group(TestGroup.PERFORMANCE);
+
+ ctx = new AnnotationConfigApplicationContext(TriggerTaskConfig.class);
+
+ Thread.sleep(100);
+ assertThat(ctx.getBean(AtomicInteger.class).get(), greaterThan(1));
+ }
+
+ @Test
+ public void withInitiallyDelayedFixedRateTask() throws InterruptedException {
+ Assume.group(TestGroup.PERFORMANCE);
+
+ ctx = new AnnotationConfigApplicationContext(FixedRateTaskConfig_withInitialDelay.class);
+
+ Thread.sleep(1950);
+ AtomicInteger counter = ctx.getBean(AtomicInteger.class);
+
+ // The @Scheduled method should have been called at least once but
+ // not more times than the delay allows.
+ assertThat(counter.get(), both(greaterThan(0)).and(lessThanOrEqualTo(10)));
+ }
+
+
+ @Configuration
+ @EnableScheduling
+ static class FixedRateTaskConfig {
+
+ @Bean
+ public AtomicInteger counter() {
+ return new AtomicInteger();
+ }
+
+ @Scheduled(fixedRate = 10)
+ public void task() {
+ counter().incrementAndGet();
+ }
+ }
+
+
+ @Configuration
+ static class FixedRateTaskConfigSubclass extends FixedRateTaskConfig {
}
@@ -136,15 +220,6 @@ public class EnableSchedulingTests {
}
- @Test
- @SuppressWarnings("resource")
- public void withExplicitSchedulerAmbiguity_andSchedulingEnabled() {
- exception.expect(IllegalStateException.class);
- exception.expectMessage(startsWith("More than one TaskScheduler bean exists within the context"));
- new AnnotationConfigApplicationContext(AmbiguousExplicitSchedulerConfig.class);
- }
-
-
@Configuration
@EnableScheduling
static class AmbiguousExplicitSchedulerConfig {
@@ -169,18 +244,6 @@ public class EnableSchedulingTests {
}
- @Test
- public void withExplicitScheduledTaskRegistrar() throws InterruptedException {
- AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
- ExplicitScheduledTaskRegistrarConfig.class);
-
- Thread.sleep(100);
- assertThat(ctx.getBean(AtomicInteger.class).get(), greaterThanOrEqualTo(10));
- assertThat(ctx.getBean(ExplicitScheduledTaskRegistrarConfig.class).threadName, startsWith("explicitScheduler1"));
- ctx.close();
- }
-
-
@Configuration
@EnableScheduling
static class ExplicitScheduledTaskRegistrarConfig implements SchedulingConfigurer {
@@ -223,14 +286,6 @@ public class EnableSchedulingTests {
}
- @Test
- public void withAmbiguousTaskSchedulers_butNoActualTasks() {
- AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
- SchedulingEnabled_withAmbiguousTaskSchedulers_butNoActualTasks.class);
- ctx.close();
- }
-
-
@Configuration
@EnableScheduling
static class SchedulingEnabled_withAmbiguousTaskSchedulers_butNoActualTasks {
@@ -251,15 +306,6 @@ public class EnableSchedulingTests {
}
- @Test
- @SuppressWarnings("resource")
- public void withAmbiguousTaskSchedulers_andSingleTask() {
- exception.expect(IllegalStateException.class);
- exception.expectMessage(startsWith("More than one TaskScheduler bean exists within the context"));
- new AnnotationConfigApplicationContext(SchedulingEnabled_withAmbiguousTaskSchedulers_andSingleTask.class);
- }
-
-
@Configuration
@EnableScheduling
static class SchedulingEnabled_withAmbiguousTaskSchedulers_andSingleTask {
@@ -284,17 +330,6 @@ public class EnableSchedulingTests {
}
- @Test
- public void withAmbiguousTaskSchedulers_andSingleTask_disambiguatedByScheduledTaskRegistrarBean() throws InterruptedException {
- AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
- SchedulingEnabled_withAmbiguousTaskSchedulers_andSingleTask_disambiguatedByScheduledTaskRegistrar.class);
-
- Thread.sleep(20);
- assertThat(ctx.getBean(ThreadAwareWorker.class).executedByThread, startsWith("explicitScheduler2-"));
- ctx.close();
- }
-
-
static class ThreadAwareWorker {
String executedByThread;
@@ -336,17 +371,6 @@ public class EnableSchedulingTests {
}
- @Test
- public void withAmbiguousTaskSchedulers_andSingleTask_disambiguatedBySchedulerNameAttribute() throws InterruptedException {
- AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
- SchedulingEnabled_withAmbiguousTaskSchedulers_andSingleTask_disambiguatedBySchedulerNameAttribute.class);
-
- Thread.sleep(20);
- assertThat(ctx.getBean(ThreadAwareWorker.class).executedByThread, startsWith("explicitScheduler2-"));
- ctx.close();
- }
-
-
@Configuration
@EnableScheduling
static class SchedulingEnabled_withAmbiguousTaskSchedulers_andSingleTask_disambiguatedBySchedulerNameAttribute implements SchedulingConfigurer {
@@ -382,17 +406,6 @@ public class EnableSchedulingTests {
}
- @Test
- public void withTaskAddedVia_configureTasks() throws InterruptedException {
- AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
- SchedulingEnabled_withTaskAddedVia_configureTasks.class);
-
- Thread.sleep(20);
- assertThat(ctx.getBean(ThreadAwareWorker.class).executedByThread, startsWith("taskScheduler-"));
- ctx.close();
- }
-
-
@Configuration
@EnableScheduling
static class SchedulingEnabled_withTaskAddedVia_configureTasks implements SchedulingConfigurer {
@@ -422,16 +435,6 @@ public class EnableSchedulingTests {
}
- @Test
- public void withTriggerTask() throws InterruptedException {
- AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(TriggerTaskConfig.class);
-
- Thread.sleep(100);
- assertThat(ctx.getBean(AtomicInteger.class).get(), greaterThan(1));
- ctx.close();
- }
-
-
@Configuration
static class TriggerTaskConfig {
@@ -462,21 +465,6 @@ public class EnableSchedulingTests {
}
- @Test
- public void withInitiallyDelayedFixedRateTask() throws InterruptedException {
- AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
- FixedRateTaskConfig_withInitialDelay.class);
-
- Thread.sleep(1950);
- AtomicInteger counter = ctx.getBean(AtomicInteger.class);
- ctx.close();
-
- // The @Scheduled method should have been called at least once but
- // not more times than the delay allows.
- assertThat(counter.get(), both(greaterThan(0)).and(lessThanOrEqualTo(10)));
- }
-
-
@Configuration
@EnableScheduling
static class FixedRateTaskConfig_withInitialDelay {
diff --git a/spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java b/spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java
index 0da2dddc..6f5a00b2 100644
--- a/spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java
+++ b/spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,7 +24,9 @@ import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.Calendar;
import java.util.Date;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Properties;
import java.util.TimeZone;
@@ -37,6 +39,7 @@ import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.support.StaticApplicationContext;
+import org.springframework.core.annotation.AliasFor;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
import org.springframework.scheduling.config.CronTask;
@@ -331,6 +334,32 @@ public class ScheduledAnnotationBeanPostProcessorTests {
}
@Test
+ public void composedAnnotationWithInitialDelayAndFixedRate() {
+ BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
+ BeanDefinition targetDefinition = new RootBeanDefinition(ComposedAnnotationFixedRateTestBean.class);
+ context.registerBeanDefinition("postProcessor", processorDefinition);
+ context.registerBeanDefinition("target", targetDefinition);
+ context.refresh();
+
+ Object postProcessor = context.getBean("postProcessor");
+ Object target = context.getBean("target");
+ ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) new DirectFieldAccessor(
+ postProcessor).getPropertyValue("registrar");
+ @SuppressWarnings("unchecked")
+ List<IntervalTask> fixedRateTasks = (List<IntervalTask>) new DirectFieldAccessor(registrar).getPropertyValue(
+ "fixedRateTasks");
+ assertEquals(1, fixedRateTasks.size());
+ IntervalTask task = fixedRateTasks.get(0);
+ ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
+ Object targetObject = runnable.getTarget();
+ Method targetMethod = runnable.getMethod();
+ assertEquals(target, targetObject);
+ assertEquals("checkForUpdates", targetMethod.getName());
+ assertEquals(5000L, task.getInterval());
+ assertEquals(1000L, task.getInitialDelay());
+ }
+
+ @Test
public void metaAnnotationWithCronExpression() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(MetaAnnotationCronTestBean.class);
@@ -364,8 +393,8 @@ public class ScheduledAnnotationBeanPostProcessorTests {
properties.setProperty("schedules.businessHours", businessHoursCronExpression);
placeholderDefinition.getPropertyValues().addPropertyValue("properties", properties);
BeanDefinition targetDefinition = new RootBeanDefinition(PropertyPlaceholderWithCronTestBean.class);
- context.registerBeanDefinition("placeholder", placeholderDefinition);
context.registerBeanDefinition("postProcessor", processorDefinition);
+ context.registerBeanDefinition("placeholder", placeholderDefinition);
context.registerBeanDefinition("target", targetDefinition);
context.refresh();
@@ -395,8 +424,8 @@ public class ScheduledAnnotationBeanPostProcessorTests {
properties.setProperty("initialDelay", "1000");
placeholderDefinition.getPropertyValues().addPropertyValue("properties", properties);
BeanDefinition targetDefinition = new RootBeanDefinition(PropertyPlaceholderWithFixedDelayTestBean.class);
- context.registerBeanDefinition("placeholder", placeholderDefinition);
context.registerBeanDefinition("postProcessor", processorDefinition);
+ context.registerBeanDefinition("placeholder", placeholderDefinition);
context.registerBeanDefinition("target", targetDefinition);
context.refresh();
@@ -427,8 +456,8 @@ public class ScheduledAnnotationBeanPostProcessorTests {
properties.setProperty("initialDelay", "1000");
placeholderDefinition.getPropertyValues().addPropertyValue("properties", properties);
BeanDefinition targetDefinition = new RootBeanDefinition(PropertyPlaceholderWithFixedRateTestBean.class);
- context.registerBeanDefinition("placeholder", placeholderDefinition);
context.registerBeanDefinition("postProcessor", processorDefinition);
+ context.registerBeanDefinition("placeholder", placeholderDefinition);
context.registerBeanDefinition("target", targetDefinition);
context.refresh();
@@ -451,6 +480,35 @@ public class ScheduledAnnotationBeanPostProcessorTests {
}
@Test
+ public void expressionWithCron() {
+ String businessHoursCronExpression = "0 0 9-17 * * MON-FRI";
+ BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
+ BeanDefinition targetDefinition = new RootBeanDefinition(ExpressionWithCronTestBean.class);
+ context.registerBeanDefinition("postProcessor", processorDefinition);
+ context.registerBeanDefinition("target", targetDefinition);
+ Map<String, String> schedules = new HashMap<String, String>();
+ schedules.put("businessHours", businessHoursCronExpression);
+ context.getBeanFactory().registerSingleton("schedules", schedules);
+ context.refresh();
+
+ Object postProcessor = context.getBean("postProcessor");
+ Object target = context.getBean("target");
+ ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
+ new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
+ @SuppressWarnings("unchecked")
+ List<CronTask> cronTasks = (List<CronTask>)
+ new DirectFieldAccessor(registrar).getPropertyValue("cronTasks");
+ assertEquals(1, cronTasks.size());
+ CronTask task = cronTasks.get(0);
+ ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
+ Object targetObject = runnable.getTarget();
+ Method targetMethod = runnable.getMethod();
+ assertEquals(target, targetObject);
+ assertEquals("x", targetMethod.getName());
+ assertEquals(businessHoursCronExpression, task.getExpression());
+ }
+
+ @Test
public void propertyPlaceholderForMetaAnnotation() {
String businessHoursCronExpression = "0 0 9-17 * * MON-FRI";
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
@@ -481,28 +539,44 @@ public class ScheduledAnnotationBeanPostProcessorTests {
assertEquals(businessHoursCronExpression, task.getExpression());
}
- @Test(expected = BeanCreationException.class)
- public void emptyAnnotation() {
+ @Test
+ public void nonVoidReturnType() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
- BeanDefinition targetDefinition = new RootBeanDefinition(EmptyAnnotationTestBean.class);
+ BeanDefinition targetDefinition = new RootBeanDefinition(NonVoidReturnTypeTestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition);
context.registerBeanDefinition("target", targetDefinition);
context.refresh();
+
+ Object postProcessor = context.getBean("postProcessor");
+ Object target = context.getBean("target");
+ ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
+ new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
+ @SuppressWarnings("unchecked")
+ List<CronTask> cronTasks = (List<CronTask>)
+ new DirectFieldAccessor(registrar).getPropertyValue("cronTasks");
+ assertEquals(1, cronTasks.size());
+ CronTask task = cronTasks.get(0);
+ ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
+ Object targetObject = runnable.getTarget();
+ Method targetMethod = runnable.getMethod();
+ assertEquals(target, targetObject);
+ assertEquals("cron", targetMethod.getName());
+ assertEquals("0 0 9-17 * * MON-FRI", task.getExpression());
}
@Test(expected = BeanCreationException.class)
- public void invalidCron() throws Throwable {
+ public void emptyAnnotation() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
- BeanDefinition targetDefinition = new RootBeanDefinition(InvalidCronTestBean.class);
+ BeanDefinition targetDefinition = new RootBeanDefinition(EmptyAnnotationTestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition);
context.registerBeanDefinition("target", targetDefinition);
context.refresh();
}
@Test(expected = BeanCreationException.class)
- public void nonVoidReturnType() {
+ public void invalidCron() throws Throwable {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
- BeanDefinition targetDefinition = new RootBeanDefinition(NonVoidReturnTypeTestBean.class);
+ BeanDefinition targetDefinition = new RootBeanDefinition(InvalidCronTestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition);
context.registerBeanDefinition("target", targetDefinition);
context.refresh();
@@ -520,7 +594,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
static class FixedDelayTestBean {
- @Scheduled(fixedDelay=5000)
+ @Scheduled(fixedDelay = 5000)
public void fixedDelay() {
}
}
@@ -528,7 +602,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
static class FixedRateTestBean {
- @Scheduled(fixedRate=3000)
+ @Scheduled(fixedRate = 3000)
public void fixedRate() {
}
}
@@ -536,7 +610,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
static class FixedRateWithInitialDelayTestBean {
- @Scheduled(fixedRate=3000, initialDelay=1000)
+ @Scheduled(fixedRate = 3000, initialDelay = 1000)
public void fixedRate() {
}
}
@@ -553,8 +627,8 @@ public class ScheduledAnnotationBeanPostProcessorTests {
static class SeveralFixedRatesWithRepeatedScheduledAnnotationTestBean {
- @Scheduled(fixedRate=4000)
- @Scheduled(fixedRate=4000, initialDelay=2000)
+ @Scheduled(fixedRate = 4000)
+ @Scheduled(fixedRate = 4000, initialDelay = 2000)
public void fixedRate() {
}
}
@@ -562,8 +636,8 @@ public class ScheduledAnnotationBeanPostProcessorTests {
static class FixedRatesBaseBean {
- @Scheduled(fixedRate=4000)
- @Scheduled(fixedRate=4000, initialDelay=2000)
+ @Scheduled(fixedRate = 4000)
+ @Scheduled(fixedRate = 4000, initialDelay = 2000)
public void fixedRate() {
}
}
@@ -573,10 +647,10 @@ public class ScheduledAnnotationBeanPostProcessorTests {
}
- static interface FixedRatesDefaultMethod {
+ interface FixedRatesDefaultMethod {
- @Scheduled(fixedRate=4000)
- @Scheduled(fixedRate=4000, initialDelay=2000)
+ @Scheduled(fixedRate = 4000)
+ @Scheduled(fixedRate = 4000, initialDelay = 2000)
default void fixedRate() {
}
}
@@ -589,7 +663,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
@Validated
static class CronTestBean {
- @Scheduled(cron="*/7 * * * * ?")
+ @Scheduled(cron = "*/7 * * * * ?")
private void cron() throws IOException {
throw new IOException("no no no");
}
@@ -598,7 +672,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
static class CronWithTimezoneTestBean {
- @Scheduled(cron="0 0 0-4,6-23 * * ?", zone = "GMT+10")
+ @Scheduled(cron = "0 0 0-4,6-23 * * ?", zone = "GMT+10")
protected void cron() throws IOException {
throw new IOException("no no no");
}
@@ -607,56 +681,68 @@ public class ScheduledAnnotationBeanPostProcessorTests {
static class CronWithInvalidTimezoneTestBean {
- @Scheduled(cron="0 0 0-4,6-23 * * ?", zone = "FOO")
+ @Scheduled(cron = "0 0 0-4,6-23 * * ?", zone = "FOO")
public void cron() throws IOException {
throw new IOException("no no no");
}
}
- static class EmptyAnnotationTestBean {
+ static class NonVoidReturnTypeTestBean {
- @Scheduled
- public void invalid() {
+ @Scheduled(cron = "0 0 9-17 * * MON-FRI")
+ public String cron() {
+ return "oops";
}
}
- static class InvalidCronTestBean {
+ static class EmptyAnnotationTestBean {
- @Scheduled(cron="abc")
+ @Scheduled
public void invalid() {
}
}
- static class NonVoidReturnTypeTestBean {
+ static class InvalidCronTestBean {
- @Scheduled(fixedRate=3000)
- public String invalid() {
- return "oops";
+ @Scheduled(cron = "abc")
+ public void invalid() {
}
}
static class NonEmptyParamListTestBean {
- @Scheduled(fixedRate=3000)
+ @Scheduled(fixedRate = 3000)
public void invalid(String oops) {
}
}
- @Scheduled(fixedRate=5000)
+ @Scheduled(fixedRate = 5000)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
- private static @interface EveryFiveSeconds {}
-
+ private @interface EveryFiveSeconds {
+ }
- @Scheduled(cron="0 0 * * * ?")
+ @Scheduled(cron = "0 0 * * * ?")
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
- private static @interface Hourly {}
+ private @interface Hourly {
+ }
+
+ @Scheduled(initialDelay = 1000)
+ @Retention(RetentionPolicy.RUNTIME)
+ private @interface WaitASec {
+
+ @AliasFor(annotation = Scheduled.class)
+ long fixedDelay() default -1;
+
+ @AliasFor(annotation = Scheduled.class)
+ long fixedRate() default -1;
+ }
static class MetaAnnotationFixedRateTestBean {
@@ -667,6 +753,14 @@ public class ScheduledAnnotationBeanPostProcessorTests {
}
+ static class ComposedAnnotationFixedRateTestBean {
+
+ @WaitASec(fixedRate = 5000)
+ public void checkForUpdates() {
+ }
+ }
+
+
static class MetaAnnotationCronTestBean {
@Hourly
@@ -674,7 +768,6 @@ public class ScheduledAnnotationBeanPostProcessorTests {
}
}
-
static class PropertyPlaceholderWithCronTestBean {
@Scheduled(cron = "${schedules.businessHours}")
@@ -685,7 +778,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
static class PropertyPlaceholderWithFixedDelayTestBean {
- @Scheduled(fixedDelayString="${fixedDelay}", initialDelayString="${initialDelay}")
+ @Scheduled(fixedDelayString = "${fixedDelay}", initialDelayString = "${initialDelay}")
public void fixedDelay() {
}
}
@@ -693,16 +786,24 @@ public class ScheduledAnnotationBeanPostProcessorTests {
static class PropertyPlaceholderWithFixedRateTestBean {
- @Scheduled(fixedRateString="${fixedRate}", initialDelayString="${initialDelay}")
+ @Scheduled(fixedRateString = "${fixedRate}", initialDelayString = "${initialDelay}")
public void fixedRate() {
}
}
- @Scheduled(cron="${schedules.businessHours}")
+ static class ExpressionWithCronTestBean {
+
+ @Scheduled(cron = "#{schedules.businessHours}")
+ public void x() {
+ }
+ }
+
+
+ @Scheduled(cron = "${schedules.businessHours}")
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
- private static @interface BusinessHours {
+ private @interface BusinessHours {
}
diff --git a/spring-context/src/test/java/org/springframework/scheduling/config/LazyScheduledTasksBeanDefinitionParserTests.java b/spring-context/src/test/java/org/springframework/scheduling/config/LazyScheduledTasksBeanDefinitionParserTests.java
index 3eef8738..191553a8 100644
--- a/spring-context/src/test/java/org/springframework/scheduling/config/LazyScheduledTasksBeanDefinitionParserTests.java
+++ b/spring-context/src/test/java/org/springframework/scheduling/config/LazyScheduledTasksBeanDefinitionParserTests.java
@@ -41,15 +41,19 @@ public class LazyScheduledTasksBeanDefinitionParserTests {
while (!task.executed) {
try {
Thread.sleep(10);
- } catch (Exception e) { /* Do Nothing */ }
+ }
+ catch (Exception ex) { /* Do Nothing */ }
}
}
+
static class Task {
+
volatile boolean executed = false;
public void doWork() {
executed = true;
}
}
+
}
diff --git a/spring-context/src/test/java/org/springframework/scheduling/support/CronSequenceGeneratorTests.java b/spring-context/src/test/java/org/springframework/scheduling/support/CronSequenceGeneratorTests.java
index 6c4df6b0..07b7537c 100644
--- a/spring-context/src/test/java/org/springframework/scheduling/support/CronSequenceGeneratorTests.java
+++ b/spring-context/src/test/java/org/springframework/scheduling/support/CronSequenceGeneratorTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,31 +29,66 @@ import static org.junit.Assert.*;
public class CronSequenceGeneratorTests {
@Test
- public void testAt50Seconds() {
+ public void at50Seconds() {
assertEquals(new Date(2012, 6, 2, 1, 0),
new CronSequenceGenerator("*/15 * 1-4 * * *").next(new Date(2012, 6, 1, 9, 53, 50)));
}
@Test
- public void testAt0Seconds() {
+ public void at0Seconds() {
assertEquals(new Date(2012, 6, 2, 1, 0),
new CronSequenceGenerator("*/15 * 1-4 * * *").next(new Date(2012, 6, 1, 9, 53)));
}
@Test
- public void testAt0Minutes() {
+ public void at0Minutes() {
assertEquals(new Date(2012, 6, 2, 1, 0),
new CronSequenceGenerator("0 */2 1-4 * * *").next(new Date(2012, 6, 1, 9, 0)));
}
@Test(expected = IllegalArgumentException.class)
- public void testWith0Increment() {
+ public void with0Increment() {
new CronSequenceGenerator("*/0 * * * * *").next(new Date(2012, 6, 1, 9, 0));
}
@Test(expected = IllegalArgumentException.class)
- public void testWithNegativeIncrement() {
+ public void withNegativeIncrement() {
new CronSequenceGenerator("*/-1 * * * * *").next(new Date(2012, 6, 1, 9, 0));
}
+ @Test(expected = IllegalArgumentException.class)
+ public void withInvertedMinuteRange() {
+ new CronSequenceGenerator("* 6-5 * * * *").next(new Date(2012, 6, 1, 9, 0));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void withInvertedHourRange() {
+ new CronSequenceGenerator("* * 6-5 * * *").next(new Date(2012, 6, 1, 9, 0));
+ }
+
+ @Test
+ public void withSameMinuteRange() {
+ new CronSequenceGenerator("* 6-6 * * * *").next(new Date(2012, 6, 1, 9, 0));
+ }
+
+ @Test
+ public void withSameHourRange() {
+ new CronSequenceGenerator("* * 6-6 * * *").next(new Date(2012, 6, 1, 9, 0));
+ }
+
+ @Test
+ public void validExpression() {
+ assertTrue(CronSequenceGenerator.isValidExpression("0 */2 1-4 * * *"));
+ }
+
+ @Test
+ public void invalidExpression() {
+ assertFalse(CronSequenceGenerator.isValidExpression("0 */2 1-4 * * * *"));
+ }
+
+ @Test
+ public void nullExpression() {
+ assertFalse(CronSequenceGenerator.isValidExpression(null));
+ }
+
}
diff --git a/spring-context/src/test/java/org/springframework/scripting/groovy/GroovyScriptFactoryTests.java b/spring-context/src/test/java/org/springframework/scripting/groovy/GroovyScriptFactoryTests.java
index 9ab27884..c8abcc42 100644
--- a/spring-context/src/test/java/org/springframework/scripting/groovy/GroovyScriptFactoryTests.java
+++ b/spring-context/src/test/java/org/springframework/scripting/groovy/GroovyScriptFactoryTests.java
@@ -268,7 +268,7 @@ public class GroovyScriptFactoryTests {
@Test
public void testScriptedClassThatDoesNotHaveANoArgCtor() throws Exception {
ScriptSource script = mock(ScriptSource.class);
- final String badScript = "class Foo { public Foo(String foo) {}}";
+ String badScript = "class Foo { public Foo(String foo) {}}";
given(script.getScriptAsString()).willReturn(badScript);
given(script.suggestedClassName()).willReturn("someName");
GroovyScriptFactory factory = new GroovyScriptFactory(ScriptFactoryPostProcessor.INLINE_SCRIPT_PREFIX
@@ -285,11 +285,10 @@ public class GroovyScriptFactoryTests {
@Test
public void testScriptedClassThatHasNoPublicNoArgCtor() throws Exception {
ScriptSource script = mock(ScriptSource.class);
- final String badScript = "class Foo { protected Foo() {}}";
+ String badScript = "class Foo { protected Foo() {} \n String toString() { 'X' }}";
given(script.getScriptAsString()).willReturn(badScript);
given(script.suggestedClassName()).willReturn("someName");
- GroovyScriptFactory factory = new GroovyScriptFactory(ScriptFactoryPostProcessor.INLINE_SCRIPT_PREFIX
- + badScript);
+ GroovyScriptFactory factory = new GroovyScriptFactory(ScriptFactoryPostProcessor.INLINE_SCRIPT_PREFIX + badScript);
try {
factory.getScriptedObject(script);
fail("Must have thrown a ScriptCompilationException (no oublic no-arg ctor in scripted class).");
@@ -466,7 +465,7 @@ public class GroovyScriptFactoryTests {
@Test // SPR-6268
public void testProxyTargetClassNotAllowedIfNotGroovy() throws Exception {
try {
- new ClassPathXmlApplicationContext("jruby-with-xsd-proxy-target-class.xml", getClass());
+ new ClassPathXmlApplicationContext("groovy-with-xsd-proxy-target-class.xml", getClass());
}
catch (BeanCreationException ex) {
assertTrue(ex.getMessage().contains("Cannot use proxyTargetClass=true"));
@@ -562,7 +561,7 @@ public class GroovyScriptFactoryTests {
testMetaClass("org/springframework/scripting/groovy/calculators-with-xsd.xml");
}
- private void testMetaClass(final String xmlFile) {
+ private void testMetaClass(String xmlFile) {
// expect the exception we threw in the custom metaclass to show it got invoked
try {
ApplicationContext ctx = new ClassPathXmlApplicationContext(xmlFile);
diff --git a/spring-context/src/test/java/org/springframework/scripting/jruby/AdvisedJRubyScriptFactoryTests.java b/spring-context/src/test/java/org/springframework/scripting/jruby/AdvisedJRubyScriptFactoryTests.java
index 368effe8..10d1391a 100644
--- a/spring-context/src/test/java/org/springframework/scripting/jruby/AdvisedJRubyScriptFactoryTests.java
+++ b/spring-context/src/test/java/org/springframework/scripting/jruby/AdvisedJRubyScriptFactoryTests.java
@@ -58,7 +58,8 @@ public final class AdvisedJRubyScriptFactoryTests {
assertEquals(0, advice.getCalls());
bean.getMessage();
assertEquals(1, advice.getCalls());
- } finally {
+ }
+ finally {
ctx.close();
}
}
@@ -76,7 +77,8 @@ public final class AdvisedJRubyScriptFactoryTests {
assertEquals(0, advice.getCalls());
bean.getMessage();
assertEquals(1, advice.getCalls());
- } finally {
+ }
+ finally {
ctx.close();
}
}
diff --git a/spring-context/src/test/java/org/springframework/tests/sample/beans/Employee.java b/spring-context/src/test/java/org/springframework/tests/sample/beans/Employee.java
index 40301c7a..fc2c639f 100644
--- a/spring-context/src/test/java/org/springframework/tests/sample/beans/Employee.java
+++ b/spring-context/src/test/java/org/springframework/tests/sample/beans/Employee.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,13 @@ public class Employee extends TestBean {
private String co;
+ public Employee() {
+ }
+
+ public Employee(String name) {
+ super(name);
+ }
+
public String getCompany() {
return co;
}
diff --git a/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java b/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java
index b9090e8b..bdb12118 100644
--- a/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java
+++ b/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java
@@ -32,6 +32,7 @@ import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
@@ -1112,6 +1113,27 @@ public class DataBinderTests {
}
@Test
+ public void testOptionalProperty() {
+ OptionalHolder bean = new OptionalHolder();
+ DataBinder binder = new DataBinder(bean);
+ binder.setConversionService(new DefaultConversionService());
+
+ MutablePropertyValues pvs = new MutablePropertyValues();
+ pvs.add("id", "1");
+ pvs.add("name", null);
+ binder.bind(pvs);
+ assertEquals("1", bean.getId());
+ assertFalse(bean.getName().isPresent());
+
+ pvs = new MutablePropertyValues();
+ pvs.add("id", "2");
+ pvs.add("name", "myName");
+ binder.bind(pvs);
+ assertEquals("2", bean.getId());
+ assertEquals("myName", bean.getName().get());
+ }
+
+ @Test
public void testValidatorNoErrors() {
TestBean tb = new TestBean();
tb.setAge(33);
@@ -1976,7 +1998,6 @@ public class DataBinderTests {
}
- @SuppressWarnings("unused")
private static class Book {
private String Title;
@@ -2011,6 +2032,30 @@ public class DataBinderTests {
}
+ private static class OptionalHolder {
+
+ private String id;
+
+ private Optional<String> name;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public Optional<String> getName() {
+ return name;
+ }
+
+ public void setName(Optional<String> name) {
+ this.name = name;
+ }
+ }
+
+
private static class TestBeanValidator implements Validator {
@Override
diff --git a/spring-context/src/test/java/org/springframework/validation/beanvalidation/SpringValidatorAdapterTests.java b/spring-context/src/test/java/org/springframework/validation/beanvalidation/SpringValidatorAdapterTests.java
new file mode 100644
index 00000000..90d0c480
--- /dev/null
+++ b/spring-context/src/test/java/org/springframework/validation/beanvalidation/SpringValidatorAdapterTests.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.validation.beanvalidation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.util.Locale;
+import javax.validation.Constraint;
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+import javax.validation.Payload;
+import javax.validation.Validation;
+import javax.validation.constraints.Size;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.springframework.beans.BeanWrapper;
+import org.springframework.beans.BeanWrapperImpl;
+import org.springframework.context.support.StaticMessageSource;
+import org.springframework.util.ObjectUtils;
+import org.springframework.validation.BeanPropertyBindingResult;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.*;
+import static org.hamcrest.core.Is.*;
+import static org.junit.Assert.*;
+
+/**
+ * @author Kazuki Shimizu
+ * @since 4.3
+ */
+public class SpringValidatorAdapterTests {
+
+ private final SpringValidatorAdapter validatorAdapter = new SpringValidatorAdapter(
+ Validation.buildDefaultValidatorFactory().getValidator());
+
+ private final StaticMessageSource messageSource = new StaticMessageSource();
+
+
+ @Before
+ public void setupSpringValidatorAdapter() {
+ messageSource.addMessage("Size", Locale.ENGLISH, "Size of {0} is must be between {2} and {1}");
+ messageSource.addMessage("Same", Locale.ENGLISH, "{2} must be same value with {1}");
+ messageSource.addMessage("password", Locale.ENGLISH, "Password");
+ messageSource.addMessage("confirmPassword", Locale.ENGLISH, "Password(Confirm)");
+ }
+
+
+ @Test // SPR-13406
+ public void testApplyMessageSourceResolvableToStringArgumentValueWithResolvedLogicalFieldName() {
+ TestBean testBean = new TestBean();
+ testBean.setPassword("password");
+ testBean.setConfirmPassword("PASSWORD");
+
+ BeanPropertyBindingResult errors = new BeanPropertyBindingResult(testBean, "testBean");
+ validatorAdapter.validate(testBean, errors);
+
+ assertThat(errors.getFieldErrorCount("password"), is(1));
+ assertThat(messageSource.getMessage(errors.getFieldError("password"), Locale.ENGLISH),
+ is("Password must be same value with Password(Confirm)"));
+
+ }
+
+ @Test // SPR-13406
+ public void testApplyMessageSourceResolvableToStringArgumentValueWithUnresolvedLogicalFieldName() {
+ TestBean testBean = new TestBean();
+ testBean.setEmail("test@example.com");
+ testBean.setConfirmEmail("TEST@EXAMPLE.IO");
+
+ BeanPropertyBindingResult errors = new BeanPropertyBindingResult(testBean, "testBean");
+ validatorAdapter.validate(testBean, errors);
+
+ assertThat(errors.getFieldErrorCount("email"), is(1));
+ assertThat(messageSource.getMessage(errors.getFieldError("email"), Locale.ENGLISH),
+ is("email must be same value with confirmEmail"));
+
+ }
+
+ @Test // SPR-13406
+ public void testNoStringArgumentValue() {
+ TestBean testBean = new TestBean();
+ testBean.setPassword("pass");
+ testBean.setConfirmPassword("pass");
+
+ BeanPropertyBindingResult errors = new BeanPropertyBindingResult(testBean, "testBean");
+ validatorAdapter.validate(testBean, errors);
+
+ assertThat(errors.getFieldErrorCount("password"), is(1));
+ assertThat(messageSource.getMessage(errors.getFieldError("password"), Locale.ENGLISH),
+ is("Size of Password is must be between 8 and 128"));
+
+ }
+
+
+ @Same(field = "password", comparingField = "confirmPassword")
+ @Same(field = "email", comparingField = "confirmEmail")
+ static class TestBean {
+
+ @Size(min = 8, max = 128)
+ private String password;
+ private String confirmPassword;
+
+ private String email;
+ private String confirmEmail;
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public String getConfirmPassword() {
+ return confirmPassword;
+ }
+
+ public void setConfirmPassword(String confirmPassword) {
+ this.confirmPassword = confirmPassword;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public String getConfirmEmail() {
+ return confirmEmail;
+ }
+
+ public void setConfirmEmail(String confirmEmail) {
+ this.confirmEmail = confirmEmail;
+ }
+ }
+
+
+ @Documented
+ @Constraint(validatedBy = {SameValidator.class})
+ @Target({TYPE, ANNOTATION_TYPE})
+ @Retention(RUNTIME)
+ @Repeatable(SameGroup.class)
+ @interface Same {
+
+ String message() default "{org.springframework.validation.beanvalidation.Same.message}";
+
+ Class<?>[] groups() default {};
+
+ Class<? extends Payload>[] payload() default {};
+
+ String field();
+
+ String comparingField();
+
+ @Target({TYPE, ANNOTATION_TYPE})
+ @Retention(RUNTIME)
+ @Documented
+ @interface List {
+ Same[] value();
+ }
+ }
+
+
+ @Documented
+ @Inherited
+ @Retention(RUNTIME)
+ @Target({TYPE, ANNOTATION_TYPE})
+ @interface SameGroup {
+
+ Same[] value();
+ }
+
+ public static class SameValidator implements ConstraintValidator<Same, Object> {
+
+ private String field;
+
+ private String comparingField;
+
+ private String message;
+
+ public void initialize(Same constraintAnnotation) {
+ field = constraintAnnotation.field();
+ comparingField = constraintAnnotation.comparingField();
+ message = constraintAnnotation.message();
+ }
+
+ public boolean isValid(Object value, ConstraintValidatorContext context) {
+ BeanWrapper beanWrapper = new BeanWrapperImpl(value);
+ Object fieldValue = beanWrapper.getPropertyValue(field);
+ Object comparingFieldValue = beanWrapper.getPropertyValue(comparingField);
+ boolean matched = ObjectUtils.nullSafeEquals(fieldValue, comparingFieldValue);
+ if (matched) {
+ return true;
+ }
+ else {
+ context.disableDefaultConstraintViolation();
+ context.buildConstraintViolationWithTemplate(message)
+ .addNode(field)
+ .addConstraintViolation();
+ return false;
+ }
+ }
+ }
+
+}
diff --git a/spring-context/src/test/resources/org/springframework/aop/framework/autoproxy/AdvisorAutoProxyCreatorTests-common-interceptors.xml b/spring-context/src/test/resources/org/springframework/aop/framework/autoproxy/AdvisorAutoProxyCreatorTests-common-interceptors.xml
index 08486fba..cfcc8285 100644
--- a/spring-context/src/test/resources/org/springframework/aop/framework/autoproxy/AdvisorAutoProxyCreatorTests-common-interceptors.xml
+++ b/spring-context/src/test/resources/org/springframework/aop/framework/autoproxy/AdvisorAutoProxyCreatorTests-common-interceptors.xml
@@ -10,37 +10,38 @@
Matches all Advisors in the factory: we don't use a prefix
</description>
- <bean id="aapc"
- class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
-
- <!-- This common interceptor will be applied always,
- before custom lockable advisor -->
+ <bean id="aapc" class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
+ <!-- This common interceptor will be applied always, before custom lockable advisor -->
<property name="interceptorNames">
<value>nopInterceptor</value>
</property>
</bean>
- <bean id="nopInterceptor" class="org.springframework.tests.aop.interceptor.NopInterceptor" />
+ <bean id="nopInterceptor" class="org.springframework.tests.aop.interceptor.NopInterceptor"/>
- <!--
- Stateful mixin. Will apply to all objects
- Note that singleton property is false.
- -->
- <bean id="lockableAdvisor"
- class="test.mixin.LockMixinAdvisor"
- scope="prototype"
- />
+ <bean id="pointcutAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
+ <property name="pointcut">
+ <bean class="org.springframework.aop.support.NameMatchMethodPointcut">
+ <property name="mappedName" value="doSomething"/>
+ </bean>
+ </property>
+ <property name="advice">
+ <bean class="org.springframework.tests.aop.interceptor.NopInterceptor"/>
+ </property>
+ </bean>
+
+ <!-- Stateful mixin. Will apply to all objects. Note that singleton property is false. -->
+ <bean id="lockableAdvisor" class="test.mixin.LockMixinAdvisor" scope="prototype"/>
- <bean id="test1"
- class="org.springframework.tests.sample.beans.TestBean">
- <property name="age"><value>4</value></property>
+ <bean id="test1" class="org.springframework.tests.sample.beans.TestBean">
+ <property name="age" value="4"/>
</bean>
- <bean id="test2"
- class="org.springframework.tests.sample.beans.TestBean">
- <property name="age"><value>4</value></property>
+ <bean id="test2" class="org.springframework.tests.sample.beans.TestBean">
+ <property name="age" value="4"/>
</bean>
+ <bean id="packageVisibleMethod" class="org.springframework.aop.framework.autoproxy.PackageVisibleMethod"/>
-</beans>
+</beans>
diff --git a/spring-context/src/test/resources/org/springframework/beans/factory/xml/XmlBeanFactoryTests-constructorArg.xml b/spring-context/src/test/resources/org/springframework/beans/factory/xml/XmlBeanFactoryTests-constructorArg.xml
index d500c488..d7241c9c 100644
--- a/spring-context/src/test/resources/org/springframework/beans/factory/xml/XmlBeanFactoryTests-constructorArg.xml
+++ b/spring-context/src/test/resources/org/springframework/beans/factory/xml/XmlBeanFactoryTests-constructorArg.xml
@@ -227,4 +227,12 @@
</constructor-arg>
</bean>
+ <bean id="constructorUnresolvableName" class="java.util.concurrent.atomic.AtomicInteger" scope="prototype">
+ <constructor-arg name="initialValue" value="1"/>
+ </bean>
+
+ <bean id="constructorUnresolvableNameWithIndex" class="java.util.concurrent.atomic.AtomicInteger" scope="prototype">
+ <constructor-arg index="0" name="initialValue" value="1"/>
+ </bean>
+
</beans>
diff --git a/spring-context/src/test/resources/org/springframework/cache/config/cache-advice.xml b/spring-context/src/test/resources/org/springframework/cache/config/cache-advice.xml
index 86db01a0..e108bb0f 100644
--- a/spring-context/src/test/resources/org/springframework/cache/config/cache-advice.xml
+++ b/spring-context/src/test/resources/org/springframework/cache/config/cache-advice.xml
@@ -11,7 +11,10 @@
<cache:caching cache="testCache">
<cache:cacheable method="cache"/>
<cache:cacheable method="cacheNull"/>
+ <cache:cacheable method="cacheSync" sync="true"/>
+ <cache:cacheable method="cacheSyncNull" sync="true"/>
<cache:cacheable method="conditional" condition="#classField == 3"/>
+ <cache:cacheable method="conditionalSync" sync="true" condition="#classField == 3"/>
<cache:cacheable method="unless" unless="#result > 10"/>
<cache:cacheable method="key" key="#p0"/>
<cache:cacheable method="varArgsKey"/>
diff --git a/spring-context/src/test/resources/org/springframework/context/config/contextNamespaceHandlerTests-location.xml b/spring-context/src/test/resources/org/springframework/context/config/contextNamespaceHandlerTests-location.xml
index e90a5e42..f6a4be6f 100644
--- a/spring-context/src/test/resources/org/springframework/context/config/contextNamespaceHandlerTests-location.xml
+++ b/spring-context/src/test/resources/org/springframework/context/config/contextNamespaceHandlerTests-location.xml
@@ -2,12 +2,12 @@
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
- xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
- http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
+ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<context:property-placeholder
location="classpath*:/org/springframework/context/config/test-*.properties,classpath*:/org/springframework/context/config/empty-*.properties,classpath*:/org/springframework/context/config/missing-*.properties"
- file-encoding="ISO-8859-1"/>
+ file-encoding="ISO-8859-1" trim-values="true"/>
<bean id="foo" class="java.lang.String">
<constructor-arg value="${foo}"/>
diff --git a/spring-context/src/test/resources/org/springframework/context/config/test-bar.properties b/spring-context/src/test/resources/org/springframework/context/config/test-bar.properties
index b0e29169..6d7afb4e 100644
--- a/spring-context/src/test/resources/org/springframework/context/config/test-bar.properties
+++ b/spring-context/src/test/resources/org/springframework/context/config/test-bar.properties
@@ -1,2 +1,2 @@
-bar=foo
-spam=maps
+bar= foo\t
+spam=\tmaps
diff --git a/spring-context/src/test/resources/org/springframework/scripting/groovy/jruby-with-xsd-proxy-target-class.xml b/spring-context/src/test/resources/org/springframework/scripting/groovy/jruby-with-xsd-proxy-target-class.xml
deleted file mode 100644
index 58a9b402..00000000
--- a/spring-context/src/test/resources/org/springframework/scripting/groovy/jruby-with-xsd-proxy-target-class.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:lang="http://www.springframework.org/schema/lang"
- xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
-
- http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-3.1.xsd">
-
- <lang:defaults proxy-target-class="true"/>
-
- <lang:jruby id="refreshableMessenger" refresh-check-delay="1000"
- script-source="classpath:org/springframework/scripting/jruby/Messenger.rb"
- script-interfaces="org.springframework.scripting.Messenger">
- <lang:property name="message" value="Hello World!"/>
- </lang:jruby>
-
-</beans>
diff --git a/spring-core/src/main/java/org/springframework/asm/ClassReader.java b/spring-core/src/main/java/org/springframework/asm/ClassReader.java
index 25ad9aeb..316c14ec 100644
--- a/spring-core/src/main/java/org/springframework/asm/ClassReader.java
+++ b/spring-core/src/main/java/org/springframework/asm/ClassReader.java
@@ -2502,11 +2502,12 @@ public class ClassReader {
int tag = readByte(index);
int[] items = this.items;
int cpIndex = items[readUnsignedShort(index + 1)];
+ boolean itf = b[cpIndex - 1] == ClassWriter.IMETH;
String owner = readClass(cpIndex, buf);
cpIndex = items[readUnsignedShort(cpIndex + 2)];
String name = readUTF8(cpIndex, buf);
String desc = readUTF8(cpIndex + 2, buf);
- return new Handle(tag, owner, name, desc);
+ return new Handle(tag, owner, name, desc, itf);
}
}
}
diff --git a/spring-core/src/main/java/org/springframework/asm/ClassWriter.java b/spring-core/src/main/java/org/springframework/asm/ClassWriter.java
index 4ec8c53a..72add9d6 100644
--- a/spring-core/src/main/java/org/springframework/asm/ClassWriter.java
+++ b/spring-core/src/main/java/org/springframework/asm/ClassWriter.java
@@ -1052,7 +1052,7 @@ public class ClassWriter extends ClassVisitor {
}
} else if (cst instanceof Handle) {
Handle h = (Handle) cst;
- return newHandleItem(h.tag, h.owner, h.name, h.desc);
+ return newHandleItem(h.tag, h.owner, h.name, h.desc, h.itf);
} else {
throw new IllegalArgumentException("value " + cst);
}
@@ -1187,10 +1187,12 @@ public class ClassWriter extends ClassVisitor {
* the name of the field or method.
* @param desc
* the descriptor of the field or method.
+ * @param itf
+ * true if the owner is an interface.
* @return a new or an already existing method type reference item.
*/
Item newHandleItem(final int tag, final String owner, final String name,
- final String desc) {
+ final String desc, final boolean itf) {
key4.set(HANDLE_BASE + tag, owner, name, desc);
Item result = get(key4);
if (result == null) {
@@ -1199,8 +1201,7 @@ public class ClassWriter extends ClassVisitor {
} else {
put112(HANDLE,
tag,
- newMethod(owner, name, desc,
- tag == Opcodes.H_INVOKEINTERFACE));
+ newMethod(owner, name, desc, itf));
}
result = new Item(index++, key4);
put(result);
@@ -1230,10 +1231,44 @@ public class ClassWriter extends ClassVisitor {
* the descriptor of the field or method.
* @return the index of a new or already existing method type reference
* item.
+ *
+ * @deprecated this method is superseded by
+ * {@link #newHandle(int, String, String, String, boolean)}.
*/
+ @Deprecated
public int newHandle(final int tag, final String owner, final String name,
final String desc) {
- return newHandleItem(tag, owner, name, desc).index;
+ return newHandle(tag, owner, name, desc, tag == Opcodes.H_INVOKEINTERFACE);
+ }
+
+ /**
+ * Adds a handle to the constant pool of the class being build. Does nothing
+ * if the constant pool already contains a similar item. <i>This method is
+ * intended for {@link Attribute} sub classes, and is normally not needed by
+ * class generators or adapters.</i>
+ *
+ * @param tag
+ * the kind of this handle. Must be {@link Opcodes#H_GETFIELD},
+ * {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD},
+ * {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL},
+ * {@link Opcodes#H_INVOKESTATIC},
+ * {@link Opcodes#H_INVOKESPECIAL},
+ * {@link Opcodes#H_NEWINVOKESPECIAL} or
+ * {@link Opcodes#H_INVOKEINTERFACE}.
+ * @param owner
+ * the internal name of the field or method owner class.
+ * @param name
+ * the name of the field or method.
+ * @param desc
+ * the descriptor of the field or method.
+ * @param itf
+ * true if the owner is an interface.
+ * @return the index of a new or already existing method type reference
+ * item.
+ */
+ public int newHandle(final int tag, final String owner, final String name,
+ final String desc, final boolean itf) {
+ return newHandleItem(tag, owner, name, desc, itf).index;
}
/**
@@ -1265,7 +1300,7 @@ public class ClassWriter extends ClassVisitor {
int hashCode = bsm.hashCode();
bootstrapMethods.putShort(newHandle(bsm.tag, bsm.owner, bsm.name,
- bsm.desc));
+ bsm.desc, bsm.isInterface()));
int argsLength = bsmArgs.length;
bootstrapMethods.putShort(argsLength);
diff --git a/spring-core/src/main/java/org/springframework/asm/Frame.java b/spring-core/src/main/java/org/springframework/asm/Frame.java
index 29e71c5a..389e416b 100644
--- a/spring-core/src/main/java/org/springframework/asm/Frame.java
+++ b/spring-core/src/main/java/org/springframework/asm/Frame.java
@@ -163,7 +163,7 @@ final class Frame {
private static final int LOCAL = 0x2000000;
/**
- * Kind of the the types that are relative to the stack of an input stack
+ * Kind of the types that are relative to the stack of an input stack
* map frame. The value of such types is a position relatively to the top of
* this stack.
*/
diff --git a/spring-core/src/main/java/org/springframework/asm/Handle.java b/spring-core/src/main/java/org/springframework/asm/Handle.java
index adc6f097..02d4a58c 100644
--- a/spring-core/src/main/java/org/springframework/asm/Handle.java
+++ b/spring-core/src/main/java/org/springframework/asm/Handle.java
@@ -64,6 +64,12 @@ public final class Handle {
*/
final String desc;
+
+ /**
+ * Indicate if the owner is an interface or not.
+ */
+ final boolean itf;
+
/**
* Constructs a new field or method handle.
*
@@ -84,12 +90,44 @@ public final class Handle {
* @param desc
* the descriptor of the field or method designated by this
* handle.
+ *
+ * @deprecated this constructor has been superseded
+ * by {@link #Handle(int, String, String, String, boolean)}.
*/
+ @Deprecated
public Handle(int tag, String owner, String name, String desc) {
+ this(tag, owner, name, desc, tag == Opcodes.H_INVOKEINTERFACE);
+ }
+
+ /**
+ * Constructs a new field or method handle.
+ *
+ * @param tag
+ * the kind of field or method designated by this Handle. Must be
+ * {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC},
+ * {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC},
+ * {@link Opcodes#H_INVOKEVIRTUAL},
+ * {@link Opcodes#H_INVOKESTATIC},
+ * {@link Opcodes#H_INVOKESPECIAL},
+ * {@link Opcodes#H_NEWINVOKESPECIAL} or
+ * {@link Opcodes#H_INVOKEINTERFACE}.
+ * @param owner
+ * the internal name of the class that owns the field or method
+ * designated by this handle.
+ * @param name
+ * the name of the field or method designated by this handle.
+ * @param desc
+ * the descriptor of the field or method designated by this
+ * handle.
+ * @param itf
+ * true if the owner is an interface.
+ */
+ public Handle(int tag, String owner, String name, String desc, boolean itf) {
this.tag = tag;
this.owner = owner;
this.name = name;
this.desc = desc;
+ this.itf = itf;
}
/**
@@ -135,6 +173,17 @@ public final class Handle {
return desc;
}
+ /**
+ * Returns true if the owner of the field or method designated
+ * by this handle is an interface.
+ *
+ * @return true if the owner of the field or method designated
+ * by this handle is an interface.
+ */
+ public boolean isInterface() {
+ return itf;
+ }
+
@Override
public boolean equals(Object obj) {
if (obj == this) {
@@ -144,13 +193,13 @@ public final class Handle {
return false;
}
Handle h = (Handle) obj;
- return tag == h.tag && owner.equals(h.owner) && name.equals(h.name)
- && desc.equals(h.desc);
+ return tag == h.tag && itf == h.itf && owner.equals(h.owner)
+ && name.equals(h.name) && desc.equals(h.desc);
}
@Override
public int hashCode() {
- return tag + owner.hashCode() * name.hashCode() * desc.hashCode();
+ return tag + (itf? 64: 0) + owner.hashCode() * name.hashCode() * desc.hashCode();
}
/**
@@ -158,13 +207,16 @@ public final class Handle {
* representation is:
*
* <pre>
+ * for a reference to a class:
* owner '.' name desc ' ' '(' tag ')'
+ * for a reference to an interface:
+ * owner '.' name desc ' ' '(' tag ' ' itf ')'
* </pre>
*
* . As this format is unambiguous, it can be parsed if necessary.
*/
@Override
public String toString() {
- return owner + '.' + name + desc + " (" + tag + ')';
+ return owner + '.' + name + desc + " (" + tag + (itf? " itf": "") + ')';
}
}
diff --git a/spring-core/src/main/java/org/springframework/core/CollectionFactory.java b/spring-core/src/main/java/org/springframework/core/CollectionFactory.java
index e9af0617..0a83068c 100644
--- a/spring-core/src/main/java/org/springframework/core/CollectionFactory.java
+++ b/spring-core/src/main/java/org/springframework/core/CollectionFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -203,7 +203,7 @@ public abstract class CollectionFactory {
try {
return (Collection<E>) collectionType.newInstance();
}
- catch (Exception ex) {
+ catch (Throwable ex) {
throw new IllegalArgumentException(
"Could not instantiate Collection type: " + collectionType.getName(), ex);
}
@@ -318,7 +318,7 @@ public abstract class CollectionFactory {
try {
return (Map<K, V>) mapType.newInstance();
}
- catch (Exception ex) {
+ catch (Throwable ex) {
throw new IllegalArgumentException("Could not instantiate Map type: " + mapType.getName(), ex);
}
}
diff --git a/spring-core/src/main/java/org/springframework/core/ConfigurableObjectInputStream.java b/spring-core/src/main/java/org/springframework/core/ConfigurableObjectInputStream.java
index 5af282f6..70a0a87c 100644
--- a/spring-core/src/main/java/org/springframework/core/ConfigurableObjectInputStream.java
+++ b/spring-core/src/main/java/org/springframework/core/ConfigurableObjectInputStream.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,6 @@ import java.io.InputStream;
import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
-import java.lang.reflect.Proxy;
import org.springframework.util.ClassUtils;
@@ -101,7 +100,7 @@ public class ConfigurableObjectInputStream extends ObjectInputStream {
}
}
try {
- return Proxy.getProxyClass(this.classLoader, resolvedInterfaces);
+ return ClassUtils.createCompositeInterface(resolvedInterfaces, this.classLoader);
}
catch (IllegalArgumentException ex) {
throw new ClassNotFoundException(null, ex);
@@ -117,7 +116,7 @@ public class ConfigurableObjectInputStream extends ObjectInputStream {
for (int i = 0; i < interfaces.length; i++) {
resolvedInterfaces[i] = resolveFallbackIfPossible(interfaces[i], ex);
}
- return Proxy.getProxyClass(getFallbackClassLoader(), resolvedInterfaces);
+ return ClassUtils.createCompositeInterface(resolvedInterfaces, getFallbackClassLoader());
}
}
}
@@ -139,8 +138,9 @@ public class ConfigurableObjectInputStream extends ObjectInputStream {
/**
* Return the fallback ClassLoader to use when no ClassLoader was specified
- * and ObjectInputStream's own default ClassLoader failed.
- * <p>The default implementation simply returns {@code null}.
+ * and ObjectInputStream's own default class loader failed.
+ * <p>The default implementation simply returns {@code null}, indicating
+ * that no specific fallback is available.
*/
protected ClassLoader getFallbackClassLoader() throws IOException {
return null;
diff --git a/spring-core/src/main/java/org/springframework/core/DecoratingClassLoader.java b/spring-core/src/main/java/org/springframework/core/DecoratingClassLoader.java
index 350df5cf..b29ac071 100644
--- a/spring-core/src/main/java/org/springframework/core/DecoratingClassLoader.java
+++ b/spring-core/src/main/java/org/springframework/core/DecoratingClassLoader.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author 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,8 +16,9 @@
package org.springframework.core;
-import java.util.HashSet;
+import java.util.Collections;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import org.springframework.lang.UsesJava7;
import org.springframework.util.Assert;
@@ -49,11 +50,11 @@ public abstract class DecoratingClassLoader extends ClassLoader {
}
- private final Set<String> excludedPackages = new HashSet<String>();
+ private final Set<String> excludedPackages =
+ Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(8));
- private final Set<String> excludedClasses = new HashSet<String>();
-
- private final Object exclusionMonitor = new Object();
+ private final Set<String> excludedClasses =
+ Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(8));
/**
@@ -79,9 +80,7 @@ public abstract class DecoratingClassLoader extends ClassLoader {
*/
public void excludePackage(String packageName) {
Assert.notNull(packageName, "Package name must not be null");
- synchronized (this.exclusionMonitor) {
- this.excludedPackages.add(packageName);
- }
+ this.excludedPackages.add(packageName);
}
/**
@@ -92,9 +91,7 @@ public abstract class DecoratingClassLoader extends ClassLoader {
*/
public void excludeClass(String className) {
Assert.notNull(className, "Class name must not be null");
- synchronized (this.exclusionMonitor) {
- this.excludedClasses.add(className);
- }
+ this.excludedClasses.add(className);
}
/**
@@ -107,15 +104,13 @@ public abstract class DecoratingClassLoader extends ClassLoader {
* @see #excludeClass
*/
protected boolean isExcluded(String className) {
- synchronized (this.exclusionMonitor) {
- if (this.excludedClasses.contains(className)) {
+ if (this.excludedClasses.contains(className)) {
+ return true;
+ }
+ for (String packageName : this.excludedPackages) {
+ if (className.startsWith(packageName)) {
return true;
}
- for (String packageName : this.excludedPackages) {
- if (className.startsWith(packageName)) {
- return true;
- }
- }
}
return false;
}
diff --git a/spring-core/src/main/java/org/springframework/core/DecoratingProxy.java b/spring-core/src/main/java/org/springframework/core/DecoratingProxy.java
new file mode 100644
index 00000000..b3ae9fce
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/DecoratingProxy.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core;
+
+/**
+ * Interface to be implemented by decorating proxies, in particular Spring AOP
+ * proxies but potentially also custom proxies with decorator semantics.
+ *
+ * <p>Note that this interface should just be implemented if the decorated class
+ * is not within the hierarchy of the proxy class to begin with. In particular,
+ * a "target-class" proxy such as a Spring AOP CGLIB proxy should not implement
+ * it since any lookup on the target class can simply be performed on the proxy
+ * class there anyway.
+ *
+ * <p>Defined in the core module in order to allow
+ * #{@link org.springframework.core.annotation.AnnotationAwareOrderComparator}
+ * (and potential other candidates without spring-aop dependencies) to use it
+ * for introspection purposes, in particular annotation lookups.
+ *
+ * @author Juergen Hoeller
+ * @since 4.3
+ */
+public interface DecoratingProxy {
+
+ /**
+ * Return the (ultimate) decorated class behind this proxy.
+ * <p>In case of an AOP proxy, this will be the ultimate target class,
+ * not just the immediate target (in case of multiple nested proxies).
+ * @return the decorated class (never {@code null})
+ */
+ Class<?> getDecoratedClass();
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/MethodClassKey.java b/spring-core/src/main/java/org/springframework/core/MethodClassKey.java
new file mode 100644
index 00000000..b837fc64
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/MethodClassKey.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core;
+
+import java.lang.reflect.Method;
+
+import org.springframework.util.ObjectUtils;
+
+/**
+ * A common key class for a method against a specific target class,
+ * including {@link #toString()} representation and {@link Comparable}
+ * support (as suggested for custom {@code HashMap} keys as of Java 8).
+ *
+ * @author Juergen Hoeller
+ * @since 4.3
+ */
+public final class MethodClassKey implements Comparable<MethodClassKey> {
+
+ private final Method method;
+
+ private final Class<?> targetClass;
+
+
+ /**
+ * Create a key object for the given method and target class.
+ * @param method the method to wrap (must not be {@code null})
+ * @param targetClass the target class that the method will be invoked
+ * on (may be {@code null} if identical to the declaring class)
+ */
+ public MethodClassKey(Method method, Class<?> targetClass) {
+ this.method = method;
+ this.targetClass = targetClass;
+ }
+
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof MethodClassKey)) {
+ return false;
+ }
+ MethodClassKey otherKey = (MethodClassKey) other;
+ return (this.method.equals(otherKey.method) &&
+ ObjectUtils.nullSafeEquals(this.targetClass, otherKey.targetClass));
+ }
+
+ @Override
+ public int hashCode() {
+ return this.method.hashCode() + (this.targetClass != null ? this.targetClass.hashCode() * 29 : 0);
+ }
+
+ @Override
+ public String toString() {
+ return this.method + (this.targetClass != null ? " on " + this.targetClass : "");
+ }
+
+ @Override
+ public int compareTo(MethodClassKey other) {
+ int result = this.method.getName().compareTo(other.method.getName());
+ if (result == 0) {
+ result = this.method.toString().compareTo(other.method.toString());
+ if (result == 0 && this.targetClass != null) {
+ result = this.targetClass.getName().compareTo(other.targetClass.getName());
+ }
+ }
+ return result;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/MethodIntrospector.java b/spring-core/src/main/java/org/springframework/core/MethodIntrospector.java
index d402c10b..804c26e7 100644
--- a/spring-core/src/main/java/org/springframework/core/MethodIntrospector.java
+++ b/spring-core/src/main/java/org/springframework/core/MethodIntrospector.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -85,10 +85,9 @@ public abstract class MethodIntrospector {
/**
* Select methods on the given target type based on a filter.
- * <p>Callers define methods of interest through the
- * {@link ReflectionUtils.MethodFilter} parameter.
+ * <p>Callers define methods of interest through the {@code MethodFilter} parameter.
* @param targetType the target type to search methods on
- * @param methodFilter a {@link ReflectionUtils.MethodFilter} to help
+ * @param methodFilter a {@code MethodFilter} to help
* recognize handler methods of interest
* @return the selected methods, or an empty set in case of no match
*/
@@ -111,22 +110,26 @@ public abstract class MethodIntrospector {
* @param targetType the target type to search methods on
* (typically an interface-based JDK proxy)
* @return a corresponding invocable method on the target type
+ * @throws IllegalStateException if the given method is not invocable on the given
+ * target type (typically due to a proxy mismatch)
*/
public static Method selectInvocableMethod(Method method, Class<?> targetType) {
if (method.getDeclaringClass().isAssignableFrom(targetType)) {
return method;
}
try {
+ String methodName = method.getName();
+ Class<?>[] parameterTypes = method.getParameterTypes();
for (Class<?> ifc : targetType.getInterfaces()) {
try {
- return ifc.getMethod(method.getName(), method.getParameterTypes());
+ return ifc.getMethod(methodName, parameterTypes);
}
catch (NoSuchMethodException ex) {
// Alright, not on this interface then...
}
}
// A final desperate attempt on the proxy class itself...
- return targetType.getMethod(method.getName(), method.getParameterTypes());
+ return targetType.getMethod(methodName, parameterTypes);
}
catch (NoSuchMethodException ex) {
throw new IllegalStateException(String.format(
diff --git a/spring-core/src/main/java/org/springframework/core/MethodParameter.java b/spring-core/src/main/java/org/springframework/core/MethodParameter.java
index e34b8d00..48344a91 100644
--- a/spring-core/src/main/java/org/springframework/core/MethodParameter.java
+++ b/spring-core/src/main/java/org/springframework/core/MethodParameter.java
@@ -27,6 +27,7 @@ import java.util.HashMap;
import java.util.Map;
import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
/**
* Helper class that encapsulates the specification of a method parameter, i.e. a {@link Method}
@@ -47,6 +48,21 @@ import org.springframework.util.Assert;
*/
public class MethodParameter {
+ private static final Class<?> javaUtilOptionalClass;
+
+ static {
+ Class<?> clazz;
+ try {
+ clazz = ClassUtils.forName("java.util.Optional", MethodParameter.class.getClassLoader());
+ }
+ catch (ClassNotFoundException ex) {
+ // Java 8 not available - Optional references simply not supported then.
+ clazz = null;
+ }
+ javaUtilOptionalClass = clazz;
+ }
+
+
private final Method method;
private final Constructor<?> constructor;
@@ -70,6 +86,8 @@ public class MethodParameter {
private volatile String parameterName;
+ private volatile MethodParameter nestedMethodParameter;
+
/**
* Create a new {@code MethodParameter} for the given method, with nesting level 1.
@@ -279,6 +297,44 @@ public class MethodParameter {
return this.typeIndexesPerLevel;
}
+ /**
+ * Return a variant of this {@code MethodParameter} which points to the
+ * same parameter but one nesting level deeper. This is effectively the
+ * same as {@link #increaseNestingLevel()}, just with an independent
+ * {@code MethodParameter} object (e.g. in case of the original being cached).
+ * @since 4.3
+ */
+ public MethodParameter nested() {
+ if (this.nestedMethodParameter != null) {
+ return this.nestedMethodParameter;
+ }
+ MethodParameter nestedParam = clone();
+ nestedParam.nestingLevel = this.nestingLevel + 1;
+ this.nestedMethodParameter = nestedParam;
+ return nestedParam;
+ }
+
+ /**
+ * Return whether this method parameter is declared as optional
+ * in the form of Java 8's {@link java.util.Optional}.
+ * @since 4.3
+ */
+ public boolean isOptional() {
+ return (getParameterType() == javaUtilOptionalClass);
+ }
+
+ /**
+ * Return a variant of this {@code MethodParameter} which points to
+ * the same parameter but one nesting level deeper in case of a
+ * {@link java.util.Optional} declaration.
+ * @since 4.3
+ * @see #isOptional()
+ * @see #nested()
+ */
+ public MethodParameter nestedIfOptional() {
+ return (isOptional() ? nested() : this);
+ }
+
/**
* Set a containing class to resolve the parameter type against.
@@ -350,6 +406,7 @@ public class MethodParameter {
Integer index = getTypeIndexForLevel(i);
type = args[index != null ? index : args.length - 1];
}
+ // TODO: Object.class if unresolvable
}
if (type instanceof Class) {
return (Class<?>) type;
@@ -407,6 +464,16 @@ public class MethodParameter {
}
/**
+ * Return whether the method/constructor is annotated with the given type.
+ * @param annotationType the annotation type to look for
+ * @since 4.3
+ * @see #getMethodAnnotation(Class)
+ */
+ public <A extends Annotation> boolean hasMethodAnnotation(Class<A> annotationType) {
+ return getAnnotatedElement().isAnnotationPresent(annotationType);
+ }
+
+ /**
* Return the annotations associated with the specific method/constructor parameter.
*/
public Annotation[] getParameterAnnotations() {
@@ -530,6 +597,17 @@ public class MethodParameter {
return (getMember().hashCode() * 31 + this.parameterIndex);
}
+ @Override
+ public String toString() {
+ return (this.method != null ? "method '" + this.method.getName() + "'" : "constructor") +
+ " parameter " + this.parameterIndex;
+ }
+
+ @Override
+ public MethodParameter clone() {
+ return new MethodParameter(this);
+ }
+
/**
* Create a new MethodParameter for the given method or constructor.
diff --git a/spring-core/src/main/java/org/springframework/core/OverridingClassLoader.java b/spring-core/src/main/java/org/springframework/core/OverridingClassLoader.java
index 684d8561..c0bc9f32 100644
--- a/spring-core/src/main/java/org/springframework/core/OverridingClassLoader.java
+++ b/spring-core/src/main/java/org/springframework/core/OverridingClassLoader.java
@@ -23,12 +23,12 @@ import org.springframework.lang.UsesJava7;
import org.springframework.util.FileCopyUtils;
/**
- * {@code ClassLoader} that does <i>not</i> always delegate to the
- * parent loader, as normal class loaders do. This enables, for example,
- * instrumentation to be forced in the overriding ClassLoader, or a
- * "throwaway" class loading behavior, where selected classes are
- * temporarily loaded in the overriding ClassLoader, in order to load
- * an instrumented version of the class in the parent ClassLoader later on.
+ * {@code ClassLoader} that does <i>not</i> always delegate to the parent loader
+ * as normal class loaders do. This enables, for example, instrumentation to be
+ * forced in the overriding ClassLoader, or a "throwaway" class loading behavior
+ * where selected application classes are temporarily loaded in the overriding
+ * {@code ClassLoader} for introspection purposes before eventually loading an
+ * instrumented version of the class in the given parent {@code ClassLoader}.
*
* @author Rod Johnson
* @author Juergen Hoeller
@@ -38,7 +38,8 @@ import org.springframework.util.FileCopyUtils;
public class OverridingClassLoader extends DecoratingClassLoader {
/** Packages that are excluded by default */
- public static final String[] DEFAULT_EXCLUDED_PACKAGES = new String[] {"java.", "javax.", "sun.", "oracle."};
+ public static final String[] DEFAULT_EXCLUDED_PACKAGES = new String[]
+ {"java.", "javax.", "sun.", "oracle.", "javassist.", "org.aspectj.", "net.sf.cglib."};
private static final String CLASS_FILE_SUFFIX = ".class";
@@ -49,12 +50,26 @@ public class OverridingClassLoader extends DecoratingClassLoader {
}
+ private final ClassLoader overrideDelegate;
+
+
/**
* Create a new OverridingClassLoader for the given ClassLoader.
* @param parent the ClassLoader to build an overriding ClassLoader for
*/
public OverridingClassLoader(ClassLoader parent) {
+ this(parent, null);
+ }
+
+ /**
+ * Create a new OverridingClassLoader for the given ClassLoader.
+ * @param parent the ClassLoader to build an overriding ClassLoader for
+ * @param overrideDelegate the ClassLoader to delegate to for overriding
+ * @since 4.3
+ */
+ public OverridingClassLoader(ClassLoader parent, ClassLoader overrideDelegate) {
super(parent);
+ this.overrideDelegate = overrideDelegate;
for (String packageName : DEFAULT_EXCLUDED_PACKAGES) {
excludePackage(packageName);
}
@@ -62,6 +77,14 @@ public class OverridingClassLoader extends DecoratingClassLoader {
@Override
+ public Class<?> loadClass(String name) throws ClassNotFoundException {
+ if (this.overrideDelegate != null && isEligibleForOverriding(name)) {
+ return this.overrideDelegate.loadClass(name);
+ }
+ return super.loadClass(name);
+ }
+
+ @Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (isEligibleForOverriding(name)) {
Class<?> result = loadClassForOverriding(name);
diff --git a/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java b/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java
index 8f1fd9d9..eaae658a 100644
--- a/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java
+++ b/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -372,6 +372,8 @@ abstract class SerializableTypeWrapper {
private final String methodName;
+ private final Class<?> declaringClass;
+
private final int index;
private transient Method method;
@@ -381,6 +383,7 @@ abstract class SerializableTypeWrapper {
public MethodInvokeTypeProvider(TypeProvider provider, Method method, int index) {
this.provider = provider;
this.methodName = method.getName();
+ this.declaringClass = method.getDeclaringClass();
this.index = index;
this.method = method;
}
@@ -404,7 +407,7 @@ abstract class SerializableTypeWrapper {
private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException {
inputStream.defaultReadObject();
- this.method = ReflectionUtils.findMethod(this.provider.getType().getClass(), this.methodName);
+ this.method = ReflectionUtils.findMethod(this.declaringClass, this.methodName);
Assert.state(Type.class == this.method.getReturnType() || Type[].class == this.method.getReturnType());
}
}
diff --git a/spring-core/src/main/java/org/springframework/core/SpringProperties.java b/spring-core/src/main/java/org/springframework/core/SpringProperties.java
index f0b25d6d..f5c34994 100644
--- a/spring-core/src/main/java/org/springframework/core/SpringProperties.java
+++ b/spring-core/src/main/java/org/springframework/core/SpringProperties.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -42,6 +42,7 @@ import org.apache.commons.logging.LogFactory;
* @see org.springframework.core.env.AbstractEnvironment#IGNORE_GETENV_PROPERTY_NAME
* @see org.springframework.beans.CachedIntrospectionResults#IGNORE_BEANINFO_PROPERTY_NAME
* @see org.springframework.jdbc.core.StatementCreatorUtils#IGNORE_GETPARAMETERTYPE_PROPERTY_NAME
+ * @see org.springframework.test.context.cache.ContextCache#MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME
*/
public abstract class SpringProperties {
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AbstractAliasAwareAnnotationAttributeExtractor.java b/spring-core/src/main/java/org/springframework/core/annotation/AbstractAliasAwareAnnotationAttributeExtractor.java
index 0febd916..32b8725c 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/AbstractAliasAwareAnnotationAttributeExtractor.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/AbstractAliasAwareAnnotationAttributeExtractor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
-import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
@@ -35,13 +34,13 @@ import org.springframework.util.ObjectUtils;
* @param <S> the type of source supported by this extractor
* @see Annotation
* @see AliasFor
- * @see AnnotationUtils#synthesizeAnnotation(Annotation, AnnotatedElement)
+ * @see AnnotationUtils#synthesizeAnnotation(Annotation, Object)
*/
abstract class AbstractAliasAwareAnnotationAttributeExtractor<S> implements AnnotationAttributeExtractor<S> {
private final Class<? extends Annotation> annotationType;
- private final AnnotatedElement annotatedElement;
+ private final Object annotatedElement;
private final S source;
@@ -56,7 +55,7 @@ abstract class AbstractAliasAwareAnnotationAttributeExtractor<S> implements Anno
* @param source the underlying source of annotation attributes; never {@code null}
*/
AbstractAliasAwareAnnotationAttributeExtractor(
- Class<? extends Annotation> annotationType, AnnotatedElement annotatedElement, S source) {
+ Class<? extends Annotation> annotationType, Object annotatedElement, S source) {
Assert.notNull(annotationType, "annotationType must not be null");
Assert.notNull(source, "source must not be null");
@@ -73,7 +72,7 @@ abstract class AbstractAliasAwareAnnotationAttributeExtractor<S> implements Anno
}
@Override
- public final AnnotatedElement getAnnotatedElement() {
+ public final Object getAnnotatedElement() {
return this.annotatedElement;
}
@@ -89,18 +88,18 @@ abstract class AbstractAliasAwareAnnotationAttributeExtractor<S> implements Anno
List<String> aliasNames = this.attributeAliasMap.get(attributeName);
if (aliasNames != null) {
- Object defaultValue = AnnotationUtils.getDefaultValue(getAnnotationType(), attributeName);
+ Object defaultValue = AnnotationUtils.getDefaultValue(this.annotationType, attributeName);
for (String aliasName : aliasNames) {
Object aliasValue = getRawAttributeValue(aliasName);
if (!ObjectUtils.nullSafeEquals(attributeValue, aliasValue) &&
!ObjectUtils.nullSafeEquals(attributeValue, defaultValue) &&
!ObjectUtils.nullSafeEquals(aliasValue, defaultValue)) {
- String elementName = (getAnnotatedElement() != null ? getAnnotatedElement().toString() : "unknown element");
+ String elementName = (this.annotatedElement != null ? this.annotatedElement.toString() : "unknown element");
throw new AnnotationConfigurationException(String.format(
"In annotation [%s] declared on %s and synthesized from [%s], attribute '%s' and its " +
"alias '%s' are present with values of [%s] and [%s], but only one is permitted.",
- getAnnotationType().getName(), elementName, getSource(), attributeName, aliasName,
+ this.annotationType.getName(), elementName, this.source, attributeName, aliasName,
ObjectUtils.nullSafeToString(attributeValue), ObjectUtils.nullSafeToString(aliasValue)));
}
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java
index 74549cf2..6021ab16 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,8 @@ import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
- * General utility methods for finding annotations and meta-annotations on
- * {@link AnnotatedElement AnnotatedElements}.
+ * General utility methods for finding annotations, meta-annotations, and
+ * repeatable annotations on {@link AnnotatedElement AnnotatedElements}.
*
* <p>{@code AnnotatedElementUtils} defines the public API for Spring's
* meta-annotation programming model with support for <em>annotation attribute
@@ -48,7 +48,9 @@ import org.springframework.util.MultiValueMap;
* <p>Support for meta-annotations with <em>attribute overrides</em> in
* <em>composed annotations</em> is provided by all variants of the
* {@code getMergedAnnotationAttributes()}, {@code getMergedAnnotation()},
- * {@code findMergedAnnotationAttributes()}, and {@code findMergedAnnotation()}
+ * {@code getAllMergedAnnotations()}, {@code getMergedRepeatableAnnotations()},
+ * {@code findMergedAnnotationAttributes()}, {@code findMergedAnnotation()},
+ * {@code findAllMergedAnnotations()}, and {@code findMergedRepeatableAnnotations()}
* methods.
*
* <h3>Find vs. Get Semantics</h3>
@@ -94,8 +96,44 @@ import org.springframework.util.MultiValueMap;
*/
public class AnnotatedElementUtils {
+ /**
+ * {@code null} constant used to denote that the search algorithm should continue.
+ */
private static final Boolean CONTINUE = null;
+ private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0];
+
+ private static final Processor<Boolean> alwaysTrueAnnotationProcessor = new AlwaysTrueBooleanAnnotationProcessor();
+
+
+ /**
+ * Build an adapted {@link AnnotatedElement} for the given annotations,
+ * typically for use with other methods on {@link AnnotatedElementUtils}.
+ * @param annotations the annotations to expose through the {@code AnnotatedElement}
+ * @since 4.3
+ */
+ public static AnnotatedElement forAnnotations(final Annotation... annotations) {
+ return new AnnotatedElement() {
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
+ for (Annotation ann : annotations) {
+ if (ann.annotationType() == annotationClass) {
+ return (T) ann;
+ }
+ }
+ return null;
+ }
+ @Override
+ public Annotation[] getAnnotations() {
+ return annotations;
+ }
+ @Override
+ public Annotation[] getDeclaredAnnotations() {
+ return annotations;
+ }
+ };
+ }
/**
* Get the fully qualified class names of all meta-annotation types
@@ -114,26 +152,8 @@ public class AnnotatedElementUtils {
public static Set<String> getMetaAnnotationTypes(AnnotatedElement element, Class<? extends Annotation> annotationType) {
Assert.notNull(element, "AnnotatedElement must not be null");
Assert.notNull(annotationType, "annotationType must not be null");
- final Set<String> types = new LinkedHashSet<String>();
-
- try {
- Annotation annotation = element.getAnnotation(annotationType);
- if (annotation != null) {
- searchWithGetSemantics(annotation.annotationType(), annotationType, null, new SimpleAnnotationProcessor<Object>() {
- @Override
- public Object process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
- types.add(annotation.annotationType().getName());
- return CONTINUE;
- }
- }, new HashSet<AnnotatedElement>(), 1);
- }
- }
- catch (Throwable ex) {
- AnnotationUtils.rethrowAnnotationConfigurationException(ex);
- throw new IllegalStateException("Failed to introspect annotations on " + element, ex);
- }
- return (!types.isEmpty() ? types : null);
+ return getMetaAnnotationTypes(element, element.getAnnotation(annotationType));
}
/**
@@ -153,51 +173,49 @@ public class AnnotatedElementUtils {
public static Set<String> getMetaAnnotationTypes(AnnotatedElement element, String annotationName) {
Assert.notNull(element, "AnnotatedElement must not be null");
Assert.hasLength(annotationName, "annotationName must not be null or empty");
- final Set<String> types = new LinkedHashSet<String>();
+
+ return getMetaAnnotationTypes(element, AnnotationUtils.getAnnotation(element, annotationName));
+ }
+
+ private static Set<String> getMetaAnnotationTypes(AnnotatedElement element, Annotation composed) {
+ if (composed == null) {
+ return null;
+ }
try {
- Annotation annotation = AnnotationUtils.getAnnotation(element, annotationName);
- if (annotation != null) {
- searchWithGetSemantics(annotation.annotationType(), null, annotationName, new SimpleAnnotationProcessor<Object>() {
+ final Set<String> types = new LinkedHashSet<String>();
+ searchWithGetSemantics(composed.annotationType(), null, null, null, new SimpleAnnotationProcessor<Object>(true) {
@Override
public Object process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
types.add(annotation.annotationType().getName());
return CONTINUE;
}
}, new HashSet<AnnotatedElement>(), 1);
- }
+ return (!types.isEmpty() ? types : null);
}
catch (Throwable ex) {
AnnotationUtils.rethrowAnnotationConfigurationException(ex);
throw new IllegalStateException("Failed to introspect annotations on " + element, ex);
}
-
- return (!types.isEmpty() ? types : null);
}
/**
* Determine if the supplied {@link AnnotatedElement} is annotated with
* a <em>composed annotation</em> that is meta-annotated with an
- * annotation of the specified {@code annotationName}.
+ * annotation of the specified {@code annotationType}.
* <p>This method follows <em>get semantics</em> as described in the
* {@linkplain AnnotatedElementUtils class-level javadoc}.
* @param element the annotated element
- * @param annotationType the annotation type on which to find meta-annotations
+ * @param annotationType the meta-annotation type to find
* @return {@code true} if a matching meta-annotation is present
* @since 4.2.3
* @see #getMetaAnnotationTypes
*/
- public static boolean hasMetaAnnotationTypes(AnnotatedElement element, final Class<? extends Annotation> annotationType) {
+ public static boolean hasMetaAnnotationTypes(AnnotatedElement element, Class<? extends Annotation> annotationType) {
Assert.notNull(element, "AnnotatedElement must not be null");
Assert.notNull(annotationType, "annotationType must not be null");
- return Boolean.TRUE.equals(searchWithGetSemantics(element, annotationType, null, new SimpleAnnotationProcessor<Boolean>() {
- @Override
- public Boolean process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
- boolean found = (annotation.annotationType() == annotationType);
- return (found && metaDepth > 0 ? Boolean.TRUE : CONTINUE);
- }
- }));
+ return hasMetaAnnotationTypes(element, annotationType, null);
}
/**
@@ -212,21 +230,28 @@ public class AnnotatedElementUtils {
* @return {@code true} if a matching meta-annotation is present
* @see #getMetaAnnotationTypes
*/
- public static boolean hasMetaAnnotationTypes(AnnotatedElement element, final String annotationName) {
+ public static boolean hasMetaAnnotationTypes(AnnotatedElement element, String annotationName) {
Assert.notNull(element, "AnnotatedElement must not be null");
Assert.hasLength(annotationName, "annotationName must not be null or empty");
- return Boolean.TRUE.equals(searchWithGetSemantics(element, null, annotationName, new SimpleAnnotationProcessor<Boolean>() {
- @Override
- public Boolean process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
- boolean found = annotation.annotationType().getName().equals(annotationName);
- return (found && metaDepth > 0 ? Boolean.TRUE : CONTINUE);
- }
- }));
+ return hasMetaAnnotationTypes(element, null, annotationName);
+ }
+
+ private static boolean hasMetaAnnotationTypes(AnnotatedElement element, Class<? extends Annotation> annotationType,
+ String annotationName) {
+
+ return Boolean.TRUE.equals(
+ searchWithGetSemantics(element, annotationType, annotationName, new SimpleAnnotationProcessor<Boolean>() {
+
+ @Override
+ public Boolean process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
+ return (metaDepth > 0 ? Boolean.TRUE : CONTINUE);
+ }
+ }));
}
/**
- * Determine if an annotation of the specified {@code annotationName}
+ * Determine if an annotation of the specified {@code annotationType}
* is <em>present</em> on the supplied {@link AnnotatedElement} or
* within the annotation hierarchy <em>above</em> the specified element.
* <p>If this method returns {@code true}, then {@link #getMergedAnnotationAttributes}
@@ -234,21 +259,21 @@ public class AnnotatedElementUtils {
* <p>This method follows <em>get semantics</em> as described in the
* {@linkplain AnnotatedElementUtils class-level javadoc}.
* @param element the annotated element
- * @param annotationType the annotation type on which to find meta-annotations
+ * @param annotationType the annotation type to find
* @return {@code true} if a matching annotation is present
* @since 4.2.3
+ * @see #hasAnnotation(AnnotatedElement, Class)
*/
- public static boolean isAnnotated(AnnotatedElement element, final Class<? extends Annotation> annotationType) {
+ public static boolean isAnnotated(AnnotatedElement element, Class<? extends Annotation> annotationType) {
Assert.notNull(element, "AnnotatedElement must not be null");
Assert.notNull(annotationType, "annotationType must not be null");
- return Boolean.TRUE.equals(searchWithGetSemantics(element, annotationType, null, new SimpleAnnotationProcessor<Boolean>() {
- @Override
- public Boolean process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
- boolean found = annotation.annotationType() == annotationType;
- return (found ? Boolean.TRUE : CONTINUE);
- }
- }));
+ // Shortcut: directly present on the element, with no processing needed?
+ if (element.isAnnotationPresent(annotationType)) {
+ return true;
+ }
+
+ return Boolean.TRUE.equals(searchWithGetSemantics(element, annotationType, null, alwaysTrueAnnotationProcessor));
}
/**
@@ -263,40 +288,29 @@ public class AnnotatedElementUtils {
* @param annotationName the fully qualified class name of the annotation type to find
* @return {@code true} if a matching annotation is present
*/
- public static boolean isAnnotated(AnnotatedElement element, final String annotationName) {
+ public static boolean isAnnotated(AnnotatedElement element, String annotationName) {
Assert.notNull(element, "AnnotatedElement must not be null");
Assert.hasLength(annotationName, "annotationName must not be null or empty");
- return Boolean.TRUE.equals(searchWithGetSemantics(element, null, annotationName, new SimpleAnnotationProcessor<Boolean>() {
- @Override
- public Boolean process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
- boolean found = annotation.annotationType().getName().equals(annotationName);
- return (found ? Boolean.TRUE : CONTINUE);
- }
- }));
+ return Boolean.TRUE.equals(searchWithGetSemantics(element, null, annotationName, alwaysTrueAnnotationProcessor));
}
/**
- * Get the first annotation of the specified {@code annotationType} within
- * the annotation hierarchy <em>above</em> the supplied {@code element},
- * merge that annotation's attributes with <em>matching</em> attributes from
- * annotations in lower levels of the annotation hierarchy, and synthesize
- * the result back into an annotation of the specified {@code annotationType}.
- * <p>{@link AliasFor @AliasFor} semantics are fully supported, both
- * within a single annotation and within the annotation hierarchy.
- * <p>This method delegates to {@link #getMergedAnnotationAttributes(AnnotatedElement, Class)}
- * and {@link AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)}.
- * @param element the annotated element
- * @param annotationType the annotation type to find
- * @return the merged, synthesized {@code Annotation}, or {@code null} if not found
- * @since 4.2
- * @see #getMergedAnnotationAttributes(AnnotatedElement, Class)
- * @see #findMergedAnnotation(AnnotatedElement, Class)
- * @see AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)
+ * @deprecated As of Spring Framework 4.2, use {@link #getMergedAnnotationAttributes(AnnotatedElement, String)} instead.
*/
- public static <A extends Annotation> A getMergedAnnotation(AnnotatedElement element, Class<A> annotationType) {
- AnnotationAttributes attributes = getMergedAnnotationAttributes(element, annotationType);
- return AnnotationUtils.synthesizeAnnotation(attributes, annotationType, element);
+ @Deprecated
+ public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationName) {
+ return getMergedAnnotationAttributes(element, annotationName);
+ }
+
+ /**
+ * @deprecated As of Spring Framework 4.2, use {@link #getMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean)} instead.
+ */
+ @Deprecated
+ public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationName,
+ boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
+
+ return getMergedAnnotationAttributes(element, annotationName, classValuesAsString, nestedAnnotationsAsMap);
}
/**
@@ -321,7 +335,7 @@ public class AnnotatedElementUtils {
Assert.notNull(annotationType, "annotationType must not be null");
AnnotationAttributes attributes = searchWithGetSemantics(element, annotationType, null,
- new MergedAnnotationAttributesProcessor(annotationType, null, false, false));
+ new MergedAnnotationAttributesProcessor());
AnnotationUtils.postProcessAnnotationAttributes(element, attributes, false, false);
return attributes;
}
@@ -377,62 +391,238 @@ public class AnnotatedElementUtils {
public static AnnotationAttributes getMergedAnnotationAttributes(AnnotatedElement element,
String annotationName, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
+ Assert.hasLength(annotationName, "annotationName must not be null or empty");
AnnotationAttributes attributes = searchWithGetSemantics(element, null, annotationName,
- new MergedAnnotationAttributesProcessor(null, annotationName, classValuesAsString, nestedAnnotationsAsMap));
+ new MergedAnnotationAttributesProcessor(classValuesAsString, nestedAnnotationsAsMap));
AnnotationUtils.postProcessAnnotationAttributes(element, attributes, classValuesAsString, nestedAnnotationsAsMap);
return attributes;
}
/**
- * Find the first annotation of the specified {@code annotationType} within
+ * Get the first annotation of the specified {@code annotationType} within
* the annotation hierarchy <em>above</em> the supplied {@code element},
* merge that annotation's attributes with <em>matching</em> attributes from
* annotations in lower levels of the annotation hierarchy, and synthesize
* the result back into an annotation of the specified {@code annotationType}.
* <p>{@link AliasFor @AliasFor} semantics are fully supported, both
* within a single annotation and within the annotation hierarchy.
+ * <p>This method delegates to {@link #getMergedAnnotationAttributes(AnnotatedElement, Class)}
+ * and {@link AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)}.
* @param element the annotated element
* @param annotationType the annotation type to find
* @return the merged, synthesized {@code Annotation}, or {@code null} if not found
* @since 4.2
- * @see #findMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
* @see #getMergedAnnotationAttributes(AnnotatedElement, Class)
+ * @see #findMergedAnnotation(AnnotatedElement, Class)
+ * @see AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)
*/
- public static <A extends Annotation> A findMergedAnnotation(AnnotatedElement element, Class<A> annotationType) {
+ public static <A extends Annotation> A getMergedAnnotation(AnnotatedElement element, Class<A> annotationType) {
Assert.notNull(annotationType, "annotationType must not be null");
- AnnotationAttributes attributes = findMergedAnnotationAttributes(element, annotationType, false, false);
+
+ // Shortcut: directly present on the element, with no merging needed?
+ if (!(element instanceof Class)) {
+ // Do not use this shortcut against a Class: Inherited annotations
+ // would get preferred over locally declared composed annotations.
+ A annotation = element.getAnnotation(annotationType);
+ if (annotation != null) {
+ return AnnotationUtils.synthesizeAnnotation(annotation, element);
+ }
+ }
+
+ // Exhaustive retrieval of merged annotation attributes...
+ AnnotationAttributes attributes = getMergedAnnotationAttributes(element, annotationType);
return AnnotationUtils.synthesizeAnnotation(attributes, annotationType, element);
}
/**
- * Find the first annotation of the specified {@code annotationName} within
- * the annotation hierarchy <em>above</em> the supplied {@code element},
- * merge that annotation's attributes with <em>matching</em> attributes from
- * annotations in lower levels of the annotation hierarchy, and synthesize
- * the result back into an annotation of the specified {@code annotationName}.
- * <p>{@link AliasFor @AliasFor} semantics are fully supported, both
- * within a single annotation and within the annotation hierarchy.
- * <p>This method delegates to {@link #findMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean)}
- * (supplying {@code false} for {@code classValuesAsString} and {@code nestedAnnotationsAsMap})
- * and {@link AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)}.
+ * Get <strong>all</strong> annotations of the specified {@code annotationType}
+ * within the annotation hierarchy <em>above</em> the supplied {@code element};
+ * and for each annotation found, merge that annotation's attributes with
+ * <em>matching</em> attributes from annotations in lower levels of the annotation
+ * hierarchy and synthesize the results back into an annotation of the specified
+ * {@code annotationType}.
+ * <p>{@link AliasFor @AliasFor} semantics are fully supported, both within a
+ * single annotation and within annotation hierarchies.
+ * <p>This method follows <em>get semantics</em> as described in the
+ * {@linkplain AnnotatedElementUtils class-level javadoc}.
+ * @param element the annotated element; never {@code null}
+ * @param annotationType the annotation type to find; never {@code null}
+ * @return the set of all merged, synthesized {@code Annotations} found, or an empty
+ * set if none were found
+ * @since 4.3
+ * @see #getMergedAnnotation(AnnotatedElement, Class)
+ * @see #getAllAnnotationAttributes(AnnotatedElement, String)
+ * @see #findAllMergedAnnotations(AnnotatedElement, Class)
+ */
+ public static <A extends Annotation> Set<A> getAllMergedAnnotations(AnnotatedElement element,
+ Class<A> annotationType) {
+
+ Assert.notNull(element, "AnnotatedElement must not be null");
+ Assert.notNull(annotationType, "annotationType must not be null");
+
+ MergedAnnotationAttributesProcessor processor = new MergedAnnotationAttributesProcessor(false, false, true);
+ searchWithGetSemantics(element, annotationType, null, processor);
+ return postProcessAndSynthesizeAggregatedResults(element, annotationType, processor.getAggregatedResults());
+ }
+
+ /**
+ * Get all <em>repeatable annotations</em> of the specified {@code annotationType}
+ * within the annotation hierarchy <em>above</em> the supplied {@code element};
+ * and for each annotation found, merge that annotation's attributes with
+ * <em>matching</em> attributes from annotations in lower levels of the annotation
+ * hierarchy and synthesize the results back into an annotation of the specified
+ * {@code annotationType}.
+ * <p>The container type that holds the repeatable annotations will be looked up
+ * via {@link java.lang.annotation.Repeatable}.
+ * <p>{@link AliasFor @AliasFor} semantics are fully supported, both within a
+ * single annotation and within annotation hierarchies.
+ * <p>This method follows <em>get semantics</em> as described in the
+ * {@linkplain AnnotatedElementUtils class-level javadoc}.
+ * @param element the annotated element; never {@code null}
+ * @param annotationType the annotation type to find; never {@code null}
+ * @return the set of all merged repeatable {@code Annotations} found, or an empty
+ * set if none were found
+ * @since 4.3
+ * @see #getMergedAnnotation(AnnotatedElement, Class)
+ * @see #getAllMergedAnnotations(AnnotatedElement, Class)
+ * @see #getMergedRepeatableAnnotations(AnnotatedElement, Class, Class)
+ * @throws IllegalArgumentException if the {@code element} or {@code annotationType}
+ * is {@code null}, or if the container type cannot be resolved
+ */
+ public static <A extends Annotation> Set<A> getMergedRepeatableAnnotations(AnnotatedElement element,
+ Class<A> annotationType) {
+
+ return getMergedRepeatableAnnotations(element, annotationType, null);
+ }
+
+ /**
+ * Get all <em>repeatable annotations</em> of the specified {@code annotationType}
+ * within the annotation hierarchy <em>above</em> the supplied {@code element};
+ * and for each annotation found, merge that annotation's attributes with
+ * <em>matching</em> attributes from annotations in lower levels of the annotation
+ * hierarchy and synthesize the results back into an annotation of the specified
+ * {@code annotationType}.
+ * <p>{@link AliasFor @AliasFor} semantics are fully supported, both within a
+ * single annotation and within annotation hierarchies.
+ * <p>This method follows <em>get semantics</em> as described in the
+ * {@linkplain AnnotatedElementUtils class-level javadoc}.
+ * @param element the annotated element; never {@code null}
+ * @param annotationType the annotation type to find; never {@code null}
+ * @param containerType the type of the container that holds the annotations;
+ * may be {@code null} if the container type should be looked up via
+ * {@link java.lang.annotation.Repeatable}
+ * @return the set of all merged repeatable {@code Annotations} found, or an empty
+ * set if none were found
+ * @since 4.3
+ * @see #getMergedAnnotation(AnnotatedElement, Class)
+ * @see #getAllMergedAnnotations(AnnotatedElement, Class)
+ * @throws IllegalArgumentException if the {@code element} or {@code annotationType}
+ * is {@code null}, or if the container type cannot be resolved
+ * @throws AnnotationConfigurationException if the supplied {@code containerType}
+ * is not a valid container annotation for the supplied {@code annotationType}
+ */
+ public static <A extends Annotation> Set<A> getMergedRepeatableAnnotations(AnnotatedElement element,
+ Class<A> annotationType, Class<? extends Annotation> containerType) {
+
+ Assert.notNull(element, "AnnotatedElement must not be null");
+ Assert.notNull(annotationType, "annotationType must not be null");
+
+ if (containerType == null) {
+ containerType = resolveContainerType(annotationType);
+ }
+ else {
+ validateContainerType(annotationType, containerType);
+ }
+
+ MergedAnnotationAttributesProcessor processor = new MergedAnnotationAttributesProcessor(false, false, true);
+ searchWithGetSemantics(element, annotationType, null, containerType, processor);
+ return postProcessAndSynthesizeAggregatedResults(element, annotationType, processor.getAggregatedResults());
+ }
+
+ /**
+ * Get the annotation attributes of <strong>all</strong> annotations of the specified
+ * {@code annotationName} in the annotation hierarchy above the supplied
+ * {@link AnnotatedElement} and store the results in a {@link MultiValueMap}.
+ * <p>Note: in contrast to {@link #getMergedAnnotationAttributes(AnnotatedElement, String)},
+ * this method does <em>not</em> support attribute overrides.
+ * <p>This method follows <em>get semantics</em> as described in the
+ * {@linkplain AnnotatedElementUtils class-level javadoc}.
* @param element the annotated element
* @param annotationName the fully qualified class name of the annotation type to find
- * @return the merged, synthesized {@code Annotation}, or {@code null} if not found
- * @since 4.2
- * @see #findMergedAnnotation(AnnotatedElement, Class)
- * @see #findMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
- * @see AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)
- * @deprecated As of Spring Framework 4.2.3, use {@link #findMergedAnnotation(AnnotatedElement, Class)} instead.
+ * @return a {@link MultiValueMap} keyed by attribute name, containing the annotation
+ * attributes from all annotations found, or {@code null} if not found
+ * @see #getAllAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
*/
- @Deprecated
- @SuppressWarnings("unchecked")
- public static <A extends Annotation> A findMergedAnnotation(AnnotatedElement element, String annotationName) {
- AnnotationAttributes attributes = findMergedAnnotationAttributes(element, annotationName, false, false);
- return AnnotationUtils.synthesizeAnnotation(attributes, (Class<A>) attributes.annotationType(), element);
+ public static MultiValueMap<String, Object> getAllAnnotationAttributes(AnnotatedElement element, String annotationName) {
+ return getAllAnnotationAttributes(element, annotationName, false, false);
}
/**
- * Find the first annotation of the specified {@code annotationName} within
+ * Get the annotation attributes of <strong>all</strong> annotations of
+ * the specified {@code annotationName} in the annotation hierarchy above
+ * the supplied {@link AnnotatedElement} and store the results in a
+ * {@link MultiValueMap}.
+ * <p>Note: in contrast to {@link #getMergedAnnotationAttributes(AnnotatedElement, String)},
+ * this method does <em>not</em> support attribute overrides.
+ * <p>This method follows <em>get semantics</em> as described in the
+ * {@linkplain AnnotatedElementUtils class-level javadoc}.
+ * @param element the annotated element
+ * @param annotationName the fully qualified class name of the annotation type to find
+ * @param classValuesAsString whether to convert Class references into Strings or to
+ * preserve them as Class references
+ * @param nestedAnnotationsAsMap whether to convert nested Annotation instances into
+ * {@code AnnotationAttributes} maps or to preserve them as Annotation instances
+ * @return a {@link MultiValueMap} keyed by attribute name, containing the annotation
+ * attributes from all annotations found, or {@code null} if not found
+ */
+ public static MultiValueMap<String, Object> getAllAnnotationAttributes(AnnotatedElement element,
+ String annotationName, final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) {
+
+ final MultiValueMap<String, Object> attributesMap = new LinkedMultiValueMap<String, Object>();
+
+ searchWithGetSemantics(element, null, annotationName, new SimpleAnnotationProcessor<Object>() {
+ @Override
+ public Object process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
+ AnnotationAttributes annotationAttributes = AnnotationUtils.getAnnotationAttributes(
+ annotation, classValuesAsString, nestedAnnotationsAsMap);
+ for (Map.Entry<String, Object> entry : annotationAttributes.entrySet()) {
+ attributesMap.add(entry.getKey(), entry.getValue());
+ }
+ return CONTINUE;
+ }
+ });
+
+ return (!attributesMap.isEmpty() ? attributesMap : null);
+ }
+
+ /**
+ * Determine if an annotation of the specified {@code annotationType}
+ * is <em>available</em> on the supplied {@link AnnotatedElement} or
+ * within the annotation hierarchy <em>above</em> the specified element.
+ * <p>If this method returns {@code true}, then {@link #findMergedAnnotationAttributes}
+ * will return a non-null value.
+ * <p>This method follows <em>find semantics</em> as described in the
+ * {@linkplain AnnotatedElementUtils class-level javadoc}.
+ * @param element the annotated element
+ * @param annotationType the annotation type to find
+ * @return {@code true} if a matching annotation is present
+ * @since 4.3
+ * @see #isAnnotated(AnnotatedElement, Class)
+ */
+ public static boolean hasAnnotation(AnnotatedElement element, Class<? extends Annotation> annotationType) {
+ Assert.notNull(element, "AnnotatedElement must not be null");
+ Assert.notNull(annotationType, "annotationType must not be null");
+
+ // Shortcut: directly present on the element, with no processing needed?
+ if (element.isAnnotationPresent(annotationType)) {
+ return true;
+ }
+
+ return Boolean.TRUE.equals(searchWithFindSemantics(element, annotationType, null, alwaysTrueAnnotationProcessor));
+ }
+
+ /**
+ * Find the first annotation of the specified {@code annotationType} within
* the annotation hierarchy <em>above</em> the supplied {@code element} and
* merge that annotation's attributes with <em>matching</em> attributes from
* annotations in lower levels of the annotation hierarchy.
@@ -443,8 +633,8 @@ public class AnnotatedElementUtils {
* <p>In contrast to {@link #getAllAnnotationAttributes}, the search
* algorithm used by this method will stop searching the annotation
* hierarchy once the first annotation of the specified
- * {@code annotationName} has been found. As a consequence, additional
- * annotations of the specified {@code annotationName} will be ignored.
+ * {@code annotationType} has been found. As a consequence, additional
+ * annotations of the specified {@code annotationType} will be ignored.
* <p>This method follows <em>find semantics</em> as described in the
* {@linkplain AnnotatedElementUtils class-level javadoc}.
* @param element the annotated element
@@ -463,8 +653,8 @@ public class AnnotatedElementUtils {
public static AnnotationAttributes findMergedAnnotationAttributes(AnnotatedElement element,
Class<? extends Annotation> annotationType, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
- AnnotationAttributes attributes = searchWithFindSemantics(element, annotationType, annotationType.getName(),
- new MergedAnnotationAttributesProcessor(annotationType, null, classValuesAsString, nestedAnnotationsAsMap));
+ AnnotationAttributes attributes = searchWithFindSemantics(element, annotationType, null,
+ new MergedAnnotationAttributesProcessor(classValuesAsString, nestedAnnotationsAsMap));
AnnotationUtils.postProcessAnnotationAttributes(element, attributes, classValuesAsString, nestedAnnotationsAsMap);
return attributes;
}
@@ -500,105 +690,217 @@ public class AnnotatedElementUtils {
String annotationName, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
AnnotationAttributes attributes = searchWithFindSemantics(element, null, annotationName,
- new MergedAnnotationAttributesProcessor(null, annotationName, classValuesAsString, nestedAnnotationsAsMap));
+ new MergedAnnotationAttributesProcessor(classValuesAsString, nestedAnnotationsAsMap));
AnnotationUtils.postProcessAnnotationAttributes(element, attributes, classValuesAsString, nestedAnnotationsAsMap);
return attributes;
}
/**
- * @deprecated As of Spring Framework 4.2, use {@link #getMergedAnnotationAttributes(AnnotatedElement, String)} instead.
+ * Find the first annotation of the specified {@code annotationType} within
+ * the annotation hierarchy <em>above</em> the supplied {@code element},
+ * merge that annotation's attributes with <em>matching</em> attributes from
+ * annotations in lower levels of the annotation hierarchy, and synthesize
+ * the result back into an annotation of the specified {@code annotationType}.
+ * <p>{@link AliasFor @AliasFor} semantics are fully supported, both
+ * within a single annotation and within the annotation hierarchy.
+ * <p>This method follows <em>find semantics</em> as described in the
+ * {@linkplain AnnotatedElementUtils class-level javadoc}.
+ * @param element the annotated element
+ * @param annotationType the annotation type to find
+ * @return the merged, synthesized {@code Annotation}, or {@code null} if not found
+ * @since 4.2
+ * @see #findAllMergedAnnotations(AnnotatedElement, Class)
+ * @see #findMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
+ * @see #getMergedAnnotationAttributes(AnnotatedElement, Class)
*/
- @Deprecated
- public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationName) {
- return getMergedAnnotationAttributes(element, annotationName);
+ public static <A extends Annotation> A findMergedAnnotation(AnnotatedElement element, Class<A> annotationType) {
+ Assert.notNull(annotationType, "annotationType must not be null");
+
+ // Shortcut: directly present on the element, with no merging needed?
+ if (!(element instanceof Class)) {
+ // Do not use this shortcut against a Class: Inherited annotations
+ // would get preferred over locally declared composed annotations.
+ A annotation = element.getAnnotation(annotationType);
+ if (annotation != null) {
+ return AnnotationUtils.synthesizeAnnotation(annotation, element);
+ }
+ }
+
+ // Exhaustive retrieval of merged annotation attributes...
+ AnnotationAttributes attributes = findMergedAnnotationAttributes(element, annotationType, false, false);
+ return AnnotationUtils.synthesizeAnnotation(attributes, annotationType, element);
}
/**
- * @deprecated As of Spring Framework 4.2, use {@link #getMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean)} instead.
+ * Find the first annotation of the specified {@code annotationName} within
+ * the annotation hierarchy <em>above</em> the supplied {@code element},
+ * merge that annotation's attributes with <em>matching</em> attributes from
+ * annotations in lower levels of the annotation hierarchy, and synthesize
+ * the result back into an annotation of the specified {@code annotationName}.
+ * <p>{@link AliasFor @AliasFor} semantics are fully supported, both
+ * within a single annotation and within the annotation hierarchy.
+ * <p>This method delegates to {@link #findMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean)}
+ * (supplying {@code false} for {@code classValuesAsString} and {@code nestedAnnotationsAsMap})
+ * and {@link AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)}.
+ * <p>This method follows <em>find semantics</em> as described in the
+ * {@linkplain AnnotatedElementUtils class-level javadoc}.
+ * @param element the annotated element
+ * @param annotationName the fully qualified class name of the annotation type to find
+ * @return the merged, synthesized {@code Annotation}, or {@code null} if not found
+ * @since 4.2
+ * @see #findMergedAnnotation(AnnotatedElement, Class)
+ * @see #findMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
+ * @see AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)
+ * @deprecated As of Spring Framework 4.2.3, use {@link #findMergedAnnotation(AnnotatedElement, Class)} instead.
*/
@Deprecated
- public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationName,
- boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
+ @SuppressWarnings("unchecked")
+ public static <A extends Annotation> A findMergedAnnotation(AnnotatedElement element, String annotationName) {
+ AnnotationAttributes attributes = findMergedAnnotationAttributes(element, annotationName, false, false);
+ return AnnotationUtils.synthesizeAnnotation(attributes, (Class<A>) attributes.annotationType(), element);
+ }
- return getMergedAnnotationAttributes(element, annotationName, classValuesAsString, nestedAnnotationsAsMap);
+ /**
+ * Find <strong>all</strong> annotations of the specified {@code annotationType}
+ * within the annotation hierarchy <em>above</em> the supplied {@code element};
+ * and for each annotation found, merge that annotation's attributes with
+ * <em>matching</em> attributes from annotations in lower levels of the annotation
+ * hierarchy and synthesize the results back into an annotation of the specified
+ * {@code annotationType}.
+ * <p>{@link AliasFor @AliasFor} semantics are fully supported, both within a
+ * single annotation and within annotation hierarchies.
+ * <p>This method follows <em>find semantics</em> as described in the
+ * {@linkplain AnnotatedElementUtils class-level javadoc}.
+ * @param element the annotated element; never {@code null}
+ * @param annotationType the annotation type to find; never {@code null}
+ * @return the set of all merged, synthesized {@code Annotations} found, or an empty
+ * set if none were found
+ * @since 4.3
+ * @see #findMergedAnnotation(AnnotatedElement, Class)
+ * @see #getAllMergedAnnotations(AnnotatedElement, Class)
+ */
+ public static <A extends Annotation> Set<A> findAllMergedAnnotations(AnnotatedElement element,
+ Class<A> annotationType) {
+
+ Assert.notNull(element, "AnnotatedElement must not be null");
+ Assert.notNull(annotationType, "annotationType must not be null");
+
+ MergedAnnotationAttributesProcessor processor = new MergedAnnotationAttributesProcessor(false, false, true);
+ searchWithFindSemantics(element, annotationType, null, processor);
+ return postProcessAndSynthesizeAggregatedResults(element, annotationType, processor.getAggregatedResults());
}
/**
- * Get the annotation attributes of <strong>all</strong> annotations of the specified
- * {@code annotationName} in the annotation hierarchy above the supplied
- * {@link AnnotatedElement} and store the results in a {@link MultiValueMap}.
- * <p>Note: in contrast to {@link #getMergedAnnotationAttributes(AnnotatedElement, String)},
- * this method does <em>not</em> support attribute overrides.
- * <p>This method follows <em>get semantics</em> as described in the
+ * Find all <em>repeatable annotations</em> of the specified {@code annotationType}
+ * within the annotation hierarchy <em>above</em> the supplied {@code element};
+ * and for each annotation found, merge that annotation's attributes with
+ * <em>matching</em> attributes from annotations in lower levels of the annotation
+ * hierarchy and synthesize the results back into an annotation of the specified
+ * {@code annotationType}.
+ * <p>The container type that holds the repeatable annotations will be looked up
+ * via {@link java.lang.annotation.Repeatable}.
+ * <p>{@link AliasFor @AliasFor} semantics are fully supported, both within a
+ * single annotation and within annotation hierarchies.
+ * <p>This method follows <em>find semantics</em> as described in the
* {@linkplain AnnotatedElementUtils class-level javadoc}.
- * @param element the annotated element
- * @param annotationName the fully qualified class name of the annotation type to find
- * @return a {@link MultiValueMap} keyed by attribute name, containing the annotation
- * attributes from all annotations found, or {@code null} if not found
- * @see #getAllAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
+ * @param element the annotated element; never {@code null}
+ * @param annotationType the annotation type to find; never {@code null}
+ * @return the set of all merged repeatable {@code Annotations} found, or an empty
+ * set if none were found
+ * @since 4.3
+ * @see #findMergedAnnotation(AnnotatedElement, Class)
+ * @see #findAllMergedAnnotations(AnnotatedElement, Class)
+ * @see #findMergedRepeatableAnnotations(AnnotatedElement, Class, Class)
+ * @throws IllegalArgumentException if the {@code element} or {@code annotationType}
+ * is {@code null}, or if the container type cannot be resolved
*/
- public static MultiValueMap<String, Object> getAllAnnotationAttributes(AnnotatedElement element, String annotationName) {
- return getAllAnnotationAttributes(element, annotationName, false, false);
+ public static <A extends Annotation> Set<A> findMergedRepeatableAnnotations(AnnotatedElement element,
+ Class<A> annotationType) {
+
+ return findMergedRepeatableAnnotations(element, annotationType, null);
}
/**
- * Get the annotation attributes of <strong>all</strong> annotations of
- * the specified {@code annotationName} in the annotation hierarchy above
- * the supplied {@link AnnotatedElement} and store the results in a
- * {@link MultiValueMap}.
- * <p>Note: in contrast to {@link #getMergedAnnotationAttributes(AnnotatedElement, String)},
- * this method does <em>not</em> support attribute overrides.
- * <p>This method follows <em>get semantics</em> as described in the
+ * Find all <em>repeatable annotations</em> of the specified {@code annotationType}
+ * within the annotation hierarchy <em>above</em> the supplied {@code element};
+ * and for each annotation found, merge that annotation's attributes with
+ * <em>matching</em> attributes from annotations in lower levels of the annotation
+ * hierarchy and synthesize the results back into an annotation of the specified
+ * {@code annotationType}.
+ * <p>{@link AliasFor @AliasFor} semantics are fully supported, both within a
+ * single annotation and within annotation hierarchies.
+ * <p>This method follows <em>find semantics</em> as described in the
* {@linkplain AnnotatedElementUtils class-level javadoc}.
- * @param element the annotated element
- * @param annotationName the fully qualified class name of the annotation type to find
- * @param classValuesAsString whether to convert Class references into Strings or to
- * preserve them as Class references
- * @param nestedAnnotationsAsMap whether to convert nested Annotation instances into
- * {@code AnnotationAttributes} maps or to preserve them as Annotation instances
- * @return a {@link MultiValueMap} keyed by attribute name, containing the annotation
- * attributes from all annotations found, or {@code null} if not found
+ * @param element the annotated element; never {@code null}
+ * @param annotationType the annotation type to find; never {@code null}
+ * @param containerType the type of the container that holds the annotations;
+ * may be {@code null} if the container type should be looked up via
+ * {@link java.lang.annotation.Repeatable}
+ * @return the set of all merged repeatable {@code Annotations} found, or an empty
+ * set if none were found
+ * @since 4.3
+ * @see #findMergedAnnotation(AnnotatedElement, Class)
+ * @see #findAllMergedAnnotations(AnnotatedElement, Class)
+ * @throws IllegalArgumentException if the {@code element} or {@code annotationType}
+ * is {@code null}, or if the container type cannot be resolved
+ * @throws AnnotationConfigurationException if the supplied {@code containerType}
+ * is not a valid container annotation for the supplied {@code annotationType}
*/
- public static MultiValueMap<String, Object> getAllAnnotationAttributes(AnnotatedElement element,
- final String annotationName, final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) {
+ public static <A extends Annotation> Set<A> findMergedRepeatableAnnotations(AnnotatedElement element,
+ Class<A> annotationType, Class<? extends Annotation> containerType) {
- final MultiValueMap<String, Object> attributesMap = new LinkedMultiValueMap<String, Object>();
+ Assert.notNull(element, "AnnotatedElement must not be null");
+ Assert.notNull(annotationType, "annotationType must not be null");
- searchWithGetSemantics(element, null, annotationName, new SimpleAnnotationProcessor<Void>() {
- @Override
- public Void process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
- boolean found = annotation.annotationType().getName().equals(annotationName);
- if (found) {
- AnnotationAttributes annotationAttributes = AnnotationUtils.getAnnotationAttributes(
- annotation, classValuesAsString, nestedAnnotationsAsMap);
- for (Map.Entry<String, Object> entry : annotationAttributes.entrySet()) {
- attributesMap.add(entry.getKey(), entry.getValue());
- }
- }
- // Continue searching...
- return null;
- }
- });
+ if (containerType == null) {
+ containerType = resolveContainerType(annotationType);
+ }
+ else {
+ validateContainerType(annotationType, containerType);
+ }
- return (!attributesMap.isEmpty() ? attributesMap : null);
+ MergedAnnotationAttributesProcessor processor = new MergedAnnotationAttributesProcessor(false, false, true);
+ searchWithFindSemantics(element, annotationType, null, containerType, processor);
+ return postProcessAndSynthesizeAggregatedResults(element, annotationType, processor.getAggregatedResults());
}
/**
- * Search for annotations of the specified {@code annotationName} on
- * the specified {@code element}, following <em>get semantics</em>.
+ * Search for annotations of the specified {@code annotationName} or
+ * {@code annotationType} on the specified {@code element}, following
+ * <em>get semantics</em>.
* @param element the annotated element
- * @param annotationType the annotation type on which to find meta-annotations
+ * @param annotationType the annotation type to find
+ * @param annotationName the fully qualified class name of the annotation
+ * type to find (as an alternative to {@code annotationType})
+ * @param processor the processor to delegate to
+ * @return the result of the processor, potentially {@code null}
+ */
+ private static <T> T searchWithGetSemantics(AnnotatedElement element, Class<? extends Annotation> annotationType,
+ String annotationName, Processor<T> processor) {
+
+ return searchWithGetSemantics(element, annotationType, annotationName, null, processor);
+ }
+
+ /**
+ * Search for annotations of the specified {@code annotationName} or
+ * {@code annotationType} on the specified {@code element}, following
+ * <em>get semantics</em>.
+ * @param element the annotated element
+ * @param annotationType the annotation type to find
* @param annotationName the fully qualified class name of the annotation
* type to find (as an alternative to {@code annotationType})
+ * @param containerType the type of the container that holds repeatable
+ * annotations, or {@code null} if the annotation is not repeatable
* @param processor the processor to delegate to
* @return the result of the processor, potentially {@code null}
+ * @since 4.3
*/
- private static <T> T searchWithGetSemantics(AnnotatedElement element,
- Class<? extends Annotation> annotationType, String annotationName, Processor<T> processor) {
+ private static <T> T searchWithGetSemantics(AnnotatedElement element, Class<? extends Annotation> annotationType,
+ String annotationName, Class<? extends Annotation> containerType, Processor<T> processor) {
try {
- return searchWithGetSemantics(
- element, annotationType, annotationName, processor, new HashSet<AnnotatedElement>(), 0);
+ return searchWithGetSemantics(element, annotationType, annotationName, containerType, processor,
+ new HashSet<AnnotatedElement>(), 0);
}
catch (Throwable ex) {
AnnotationUtils.rethrowAnnotationConfigurationException(ex);
@@ -613,17 +915,19 @@ public class AnnotatedElementUtils {
* <p>The {@code metaDepth} parameter is explained in the
* {@link Processor#process process()} method of the {@link Processor} API.
* @param element the annotated element
- * @param annotationType the annotation type on which to find meta-annotations
+ * @param annotationType the annotation type to find
* @param annotationName the fully qualified class name of the annotation
* type to find (as an alternative to {@code annotationType})
+ * @param containerType the type of the container that holds repeatable
+ * annotations, or {@code null} if the annotation is not repeatable
* @param processor the processor to delegate to
* @param visited the set of annotated elements that have already been visited
* @param metaDepth the meta-depth of the annotation
* @return the result of the processor, potentially {@code null}
*/
- private static <T> T searchWithGetSemantics(AnnotatedElement element,
- Class<? extends Annotation> annotationType, String annotationName,
- Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth) {
+ private static <T> T searchWithGetSemantics(AnnotatedElement element, Class<? extends Annotation> annotationType,
+ String annotationName, Class<? extends Annotation> containerType, Processor<T> processor,
+ Set<AnnotatedElement> visited, int metaDepth) {
Assert.notNull(element, "AnnotatedElement must not be null");
@@ -632,12 +936,12 @@ public class AnnotatedElementUtils {
// Start searching within locally declared annotations
List<Annotation> declaredAnnotations = Arrays.asList(element.getDeclaredAnnotations());
T result = searchWithGetSemanticsInAnnotations(element, declaredAnnotations,
- annotationType, annotationName, processor, visited, metaDepth);
+ annotationType, annotationName, containerType, processor, visited, metaDepth);
if (result != null) {
return result;
}
- if (element instanceof Class) { // otherwise getAnnotations doesn't return anything new
+ if (element instanceof Class) { // otherwise getAnnotations doesn't return anything new
List<Annotation> inheritedAnnotations = new ArrayList<Annotation>();
for (Annotation annotation : element.getAnnotations()) {
if (!declaredAnnotations.contains(annotation)) {
@@ -647,7 +951,7 @@ public class AnnotatedElementUtils {
// Continue searching within inherited annotations
result = searchWithGetSemanticsInAnnotations(element, inheritedAnnotations,
- annotationType, annotationName, processor, visited, metaDepth);
+ annotationType, annotationName, containerType, processor, visited, metaDepth);
if (result != null) {
return result;
}
@@ -662,39 +966,58 @@ public class AnnotatedElementUtils {
}
/**
- * This method is invoked by
- * {@link #searchWithGetSemantics(AnnotatedElement, Class, String, Processor, Set, int)}
- * to perform the actual search within the supplied list of annotations.
+ * This method is invoked by {@link #searchWithGetSemantics} to perform
+ * the actual search within the supplied list of annotations.
* <p>This method should be invoked first with locally declared annotations
* and then subsequently with inherited annotations, thereby allowing
* local annotations to take precedence over inherited annotations.
* <p>The {@code metaDepth} parameter is explained in the
* {@link Processor#process process()} method of the {@link Processor} API.
- * @param annotatedElement the element that is annotated with the supplied
+ * @param element the element that is annotated with the supplied
* annotations, used for contextual logging; may be {@code null} if unknown
* @param annotations the annotations to search in
- * @param annotationType the annotation type on which to find meta-annotations
+ * @param annotationType the annotation type to find
* @param annotationName the fully qualified class name of the annotation
* type to find (as an alternative to {@code annotationType})
+ * @param containerType the type of the container that holds repeatable
+ * annotations, or {@code null} if the annotation is not repeatable
* @param processor the processor to delegate to
* @param visited the set of annotated elements that have already been visited
* @param metaDepth the meta-depth of the annotation
* @return the result of the processor, potentially {@code null}
* @since 4.2
*/
- private static <T> T searchWithGetSemanticsInAnnotations(AnnotatedElement annotatedElement,
+ private static <T> T searchWithGetSemanticsInAnnotations(AnnotatedElement element,
List<Annotation> annotations, Class<? extends Annotation> annotationType, String annotationName,
- Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth) {
+ Class<? extends Annotation> containerType, Processor<T> processor, Set<AnnotatedElement> visited,
+ int metaDepth) {
// Search in annotations
for (Annotation annotation : annotations) {
- if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation) &&
- ((annotationType != null ? annotation.annotationType() == annotationType :
- annotation.annotationType().getName().equals(annotationName)) ||
- metaDepth > 0)) {
- T result = processor.process(annotatedElement, annotation, metaDepth);
- if (result != null) {
- return result;
+ if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) {
+ if (annotation.annotationType() == annotationType ||
+ annotation.annotationType().getName().equals(annotationName) ||
+ processor.alwaysProcesses()) {
+ T result = processor.process(element, annotation, metaDepth);
+ if (result != null) {
+ if (processor.aggregates() && metaDepth == 0) {
+ processor.getAggregatedResults().add(result);
+ }
+ else {
+ return result;
+ }
+ }
+ }
+ // Repeatable annotations in container?
+ else if (annotation.annotationType() == containerType) {
+ for (Annotation contained : getRawAnnotationsFromContainer(element, annotation)) {
+ T result = processor.process(element, contained, metaDepth);
+ if (result != null) {
+ // No need to post-process since repeatable annotations within a
+ // container cannot be composed annotations.
+ processor.getAggregatedResults().add(result);
+ }
+ }
}
}
}
@@ -703,10 +1026,15 @@ public class AnnotatedElementUtils {
for (Annotation annotation : annotations) {
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) {
T result = searchWithGetSemantics(annotation.annotationType(), annotationType,
- annotationName, processor, visited, metaDepth + 1);
+ annotationName, containerType, processor, visited, metaDepth + 1);
if (result != null) {
- processor.postProcess(annotatedElement, annotation, result);
- return result;
+ processor.postProcess(element, annotation, result);
+ if (processor.aggregates() && metaDepth == 0) {
+ processor.getAggregatedResults().add(result);
+ }
+ else {
+ return result;
+ }
}
}
}
@@ -715,22 +1043,48 @@ public class AnnotatedElementUtils {
}
/**
- * Search for annotations of the specified {@code annotationName} on
- * the specified {@code element}, following <em>find semantics</em>.
+ * Search for annotations of the specified {@code annotationName} or
+ * {@code annotationType} on the specified {@code element}, following
+ * <em>find semantics</em>.
* @param element the annotated element
- * @param annotationType the annotation type on which to find meta-annotations
+ * @param annotationType the annotation type to find
* @param annotationName the fully qualified class name of the annotation
* type to find (as an alternative to {@code annotationType})
* @param processor the processor to delegate to
* @return the result of the processor, potentially {@code null}
* @since 4.2
*/
- private static <T> T searchWithFindSemantics(
- AnnotatedElement element, Class<? extends Annotation> annotationType, String annotationName, Processor<T> processor) {
+ private static <T> T searchWithFindSemantics(AnnotatedElement element, Class<? extends Annotation> annotationType,
+ String annotationName, Processor<T> processor) {
+
+ return searchWithFindSemantics(element, annotationType, annotationName, null, processor);
+ }
+
+ /**
+ * Search for annotations of the specified {@code annotationName} or
+ * {@code annotationType} on the specified {@code element}, following
+ * <em>find semantics</em>.
+ * @param element the annotated element
+ * @param annotationType the annotation type to find
+ * @param annotationName the fully qualified class name of the annotation
+ * type to find (as an alternative to {@code annotationType})
+ * @param containerType the type of the container that holds repeatable
+ * annotations, or {@code null} if the annotation is not repeatable
+ * @param processor the processor to delegate to
+ * @return the result of the processor, potentially {@code null}
+ * @since 4.3
+ */
+ private static <T> T searchWithFindSemantics(AnnotatedElement element, Class<? extends Annotation> annotationType,
+ String annotationName, Class<? extends Annotation> containerType, Processor<T> processor) {
+
+ if (containerType != null && !processor.aggregates()) {
+ throw new IllegalArgumentException(
+ "Searches for repeatable annotations must supply an aggregating Processor");
+ }
try {
return searchWithFindSemantics(
- element, annotationType, annotationName, processor, new HashSet<AnnotatedElement>(), 0);
+ element, annotationType, annotationName, containerType, processor, new HashSet<AnnotatedElement>(), 0);
}
catch (Throwable ex) {
AnnotationUtils.rethrowAnnotationConfigurationException(ex);
@@ -744,10 +1098,12 @@ public class AnnotatedElementUtils {
* have already been <em>visited</em>.
* <p>The {@code metaDepth} parameter is explained in the
* {@link Processor#process process()} method of the {@link Processor} API.
- * @param element the annotated element
- * @param annotationType the annotation type on which to find meta-annotations
+ * @param element the annotated element; never {@code null}
+ * @param annotationType the annotation type to find
* @param annotationName the fully qualified class name of the annotation
* type to find (as an alternative to {@code annotationType})
+ * @param containerType the type of the container that holds repeatable
+ * annotations, or {@code null} if the annotation is not repeatable
* @param processor the processor to delegate to
* @param visited the set of annotated elements that have already been visited
* @param metaDepth the meta-depth of the annotation
@@ -755,25 +1111,44 @@ public class AnnotatedElementUtils {
* @since 4.2
*/
private static <T> T searchWithFindSemantics(AnnotatedElement element, Class<? extends Annotation> annotationType,
- String annotationName, Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth) {
+ String annotationName, Class<? extends Annotation> containerType, Processor<T> processor,
+ Set<AnnotatedElement> visited, int metaDepth) {
Assert.notNull(element, "AnnotatedElement must not be null");
- Assert.hasLength(annotationName, "annotationName must not be null or empty");
if (visited.add(element)) {
try {
// Locally declared annotations (ignoring @Inherited)
Annotation[] annotations = element.getDeclaredAnnotations();
+ List<T> aggregatedResults = (processor.aggregates() ? new ArrayList<T>() : null);
// Search in local annotations
for (Annotation annotation : annotations) {
- if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation) &&
- ((annotationType != null ? annotation.annotationType() == annotationType :
- annotation.annotationType().getName().equals(annotationName)) ||
- metaDepth > 0)) {
- T result = processor.process(element, annotation, metaDepth);
- if (result != null) {
- return result;
+ if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) {
+ if (annotation.annotationType() == annotationType
+ || annotation.annotationType().getName().equals(annotationName)
+ || processor.alwaysProcesses()) {
+
+ T result = processor.process(element, annotation, metaDepth);
+ if (result != null) {
+ if (processor.aggregates() && metaDepth == 0) {
+ aggregatedResults.add(result);
+ }
+ else {
+ return result;
+ }
+ }
+ }
+ // Repeatable annotations in container?
+ else if (annotation.annotationType() == containerType) {
+ for (Annotation contained : getRawAnnotationsFromContainer(element, annotation)) {
+ T result = processor.process(element, contained, metaDepth);
+ if (result != null) {
+ // No need to post-process since repeatable annotations within a
+ // container cannot be composed annotations.
+ aggregatedResults.add(result);
+ }
+ }
}
}
}
@@ -781,21 +1156,31 @@ public class AnnotatedElementUtils {
// Search in meta annotations on local annotations
for (Annotation annotation : annotations) {
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) {
- T result = searchWithFindSemantics(
- annotation.annotationType(), annotationType, annotationName, processor, visited, metaDepth + 1);
+ T result = searchWithFindSemantics(annotation.annotationType(), annotationType, annotationName,
+ containerType, processor, visited, metaDepth + 1);
if (result != null) {
processor.postProcess(annotation.annotationType(), annotation, result);
- return result;
+ if (processor.aggregates() && metaDepth == 0) {
+ aggregatedResults.add(result);
+ }
+ else {
+ return result;
+ }
}
}
}
+ if (processor.aggregates()) {
+ // Prepend to support top-down ordering within class hierarchies
+ processor.getAggregatedResults().addAll(0, aggregatedResults);
+ }
+
if (element instanceof Method) {
Method method = (Method) element;
// Search on possibly bridged method
Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method);
- T result = searchWithFindSemantics(resolvedMethod, annotationType, annotationName,
+ T result = searchWithFindSemantics(resolvedMethod, annotationType, annotationName, containerType,
processor, visited, metaDepth);
if (result != null) {
return result;
@@ -803,8 +1188,8 @@ public class AnnotatedElementUtils {
// Search on methods in interfaces declared locally
Class<?>[] ifcs = method.getDeclaringClass().getInterfaces();
- result = searchOnInterfaces(
- method, annotationType, annotationName, processor, visited, metaDepth, ifcs);
+ result = searchOnInterfaces(method, annotationType, annotationName, containerType, processor,
+ visited, metaDepth, ifcs);
if (result != null) {
return result;
}
@@ -821,7 +1206,7 @@ public class AnnotatedElementUtils {
Method equivalentMethod = clazz.getDeclaredMethod(method.getName(), method.getParameterTypes());
Method resolvedEquivalentMethod = BridgeMethodResolver.findBridgedMethod(equivalentMethod);
result = searchWithFindSemantics(resolvedEquivalentMethod, annotationType, annotationName,
- processor, visited, metaDepth);
+ containerType, processor, visited, metaDepth);
if (result != null) {
return result;
}
@@ -831,21 +1216,20 @@ public class AnnotatedElementUtils {
}
// Search on interfaces declared on superclass
- result = searchOnInterfaces(method, annotationType, annotationName, processor, visited,
- metaDepth, clazz.getInterfaces());
+ result = searchOnInterfaces(method, annotationType, annotationName, containerType, processor,
+ visited, metaDepth, clazz.getInterfaces());
if (result != null) {
return result;
}
}
}
-
- if (element instanceof Class) {
+ else if (element instanceof Class) {
Class<?> clazz = (Class<?>) element;
// Search on interfaces
for (Class<?> ifc : clazz.getInterfaces()) {
- T result = searchWithFindSemantics(
- ifc, annotationType, annotationName, processor, visited, metaDepth);
+ T result = searchWithFindSemantics(ifc, annotationType, annotationName, containerType,
+ processor, visited, metaDepth);
if (result != null) {
return result;
}
@@ -854,8 +1238,8 @@ public class AnnotatedElementUtils {
// Search on superclass
Class<?> superclass = clazz.getSuperclass();
if (superclass != null && Object.class != superclass) {
- T result = searchWithFindSemantics(
- superclass, annotationType, annotationName, processor, visited, metaDepth);
+ T result = searchWithFindSemantics(superclass, annotationType, annotationName, containerType,
+ processor, visited, metaDepth);
if (result != null) {
return result;
}
@@ -869,14 +1253,15 @@ public class AnnotatedElementUtils {
return null;
}
- private static <T> T searchOnInterfaces(Method method, Class<? extends Annotation> annotationType, String annotationName,
- Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth, Class<?>[] ifcs) {
+ private static <T> T searchOnInterfaces(Method method, Class<? extends Annotation> annotationType,
+ String annotationName, Class<? extends Annotation> containerType, Processor<T> processor,
+ Set<AnnotatedElement> visited, int metaDepth, Class<?>[] ifcs) {
for (Class<?> iface : ifcs) {
if (AnnotationUtils.isInterfaceWithAnnotatedMethods(iface)) {
try {
Method equivalentMethod = iface.getMethod(method.getName(), method.getParameterTypes());
- T result = searchWithFindSemantics(equivalentMethod, annotationType, annotationName,
+ T result = searchWithFindSemantics(equivalentMethod, annotationType, annotationName, containerType,
processor, visited, metaDepth);
if (result != null) {
return result;
@@ -891,6 +1276,87 @@ public class AnnotatedElementUtils {
return null;
}
+ /**
+ * Get the array of raw (unsynthesized) annotations from the {@code value}
+ * attribute of the supplied repeatable annotation {@code container}.
+ * @since 4.3
+ */
+ @SuppressWarnings("unchecked")
+ private static <A extends Annotation> A[] getRawAnnotationsFromContainer(AnnotatedElement element,
+ Annotation container) {
+
+ try {
+ return (A[]) AnnotationUtils.getValue(container);
+ }
+ catch (Exception ex) {
+ AnnotationUtils.handleIntrospectionFailure(element, ex);
+ }
+ // Unable to read value from repeating annotation container -> ignore it.
+ return (A[]) EMPTY_ANNOTATION_ARRAY;
+ }
+
+ /**
+ * Resolve the container type for the supplied repeatable {@code annotationType}.
+ * <p>Delegates to {@link AnnotationUtils#resolveContainerAnnotationType(Class)}.
+ * @param annotationType the annotation type to resolve the container for
+ * @return the container type; never {@code null}
+ * @throws IllegalArgumentException if the container type cannot be resolved
+ * @since 4.3
+ */
+ private static Class<? extends Annotation> resolveContainerType(Class<? extends Annotation> annotationType) {
+ Class<? extends Annotation> containerType = AnnotationUtils.resolveContainerAnnotationType(annotationType);
+ if (containerType == null) {
+ throw new IllegalArgumentException(
+ "annotationType must be a repeatable annotation: failed to resolve container type for "
+ + annotationType.getName());
+ }
+ return containerType;
+ }
+
+ /**
+ * Validate that the supplied {@code containerType} is a proper container
+ * annotation for the supplied repeatable {@code annotationType} (i.e.,
+ * that it declares a {@code value} attribute that holds an array of the
+ * {@code annotationType}).
+ * @since 4.3
+ * @throws AnnotationConfigurationException if the supplied {@code containerType}
+ * is not a valid container annotation for the supplied {@code annotationType}
+ */
+ private static void validateContainerType(Class<? extends Annotation> annotationType,
+ Class<? extends Annotation> containerType) {
+
+ try {
+ Method method = containerType.getDeclaredMethod(AnnotationUtils.VALUE);
+ Class<?> returnType = method.getReturnType();
+ if (!returnType.isArray() || returnType.getComponentType() != annotationType) {
+ String msg = String.format(
+ "Container type [%s] must declare a 'value' attribute for an array of type [%s]",
+ containerType.getName(), annotationType.getName());
+ throw new AnnotationConfigurationException(msg);
+ }
+ }
+ catch (Exception ex) {
+ AnnotationUtils.rethrowAnnotationConfigurationException(ex);
+ String msg = String.format("Invalid declaration of container type [%s] for repeatable annotation [%s]",
+ containerType.getName(), annotationType.getName());
+ throw new AnnotationConfigurationException(msg, ex);
+ }
+ }
+
+ /**
+ * @since 4.3
+ */
+ private static <A extends Annotation> Set<A> postProcessAndSynthesizeAggregatedResults(AnnotatedElement element,
+ Class<A> annotationType, List<AnnotationAttributes> aggregatedResults) {
+
+ Set<A> annotations = new LinkedHashSet<A>();
+ for (AnnotationAttributes attributes : aggregatedResults) {
+ AnnotationUtils.postProcessAnnotationAttributes(element, attributes, false, false);
+ annotations.add(AnnotationUtils.synthesizeAnnotation(attributes, annotationType, element));
+ }
+ return annotations;
+ }
+
/**
* Callback interface that is used to process annotations during a search.
@@ -899,11 +1365,16 @@ public class AnnotatedElementUtils {
* annotations, or all annotations discovered by the currently executing
* search. The term "target" in this context refers to a matching
* annotation (i.e., a specific annotation type that was found during
- * the search). Returning a non-null value from the {@link #process}
+ * the search).
+ * <p>Returning a non-null value from the {@link #process}
* method instructs the search algorithm to stop searching further;
* whereas, returning {@code null} from the {@link #process} method
* instructs the search algorithm to continue searching for additional
- * annotations.
+ * annotations. One exception to this rule applies to processors
+ * that {@linkplain #aggregates aggregate} results. If an aggregating
+ * processor returns a non-null value, that value will be added to the
+ * list of {@linkplain #getAggregatedResults aggregated results}
+ * and the search algorithm will continue.
* <p>Processors can optionally {@linkplain #postProcess post-process}
* the result of the {@link #process} method as the search algorithm
* goes back down the annotation hierarchy from an invocation of
@@ -916,11 +1387,12 @@ public class AnnotatedElementUtils {
/**
* Process the supplied annotation.
- * <p>Depending on the use case, the supplied annotation may be an
- * actual target annotation that has been found by the search
- * algorithm, or it may be some other annotation within the
+ * <p>The supplied annotation will be an actual target annotation
+ * that has been found by the search algorithm, unless this processor
+ * is configured to {@linkplain #alwaysProcesses always process}
+ * annotations in which case it may be some other annotation within an
* annotation hierarchy. In the latter case, the {@code metaDepth}
- * should have a value greater than {@code 0}. In any case, it is
+ * will have a value greater than {@code 0}. In any case, it is
* up to concrete implementations of this method to decide what to
* do with the supplied annotation.
* <p>The {@code metaDepth} parameter represents the depth of the
@@ -952,20 +1424,93 @@ public class AnnotatedElementUtils {
* @param result the result to post-process
*/
void postProcess(AnnotatedElement annotatedElement, Annotation annotation, T result);
+
+ /**
+ * Determine if this processor always processes annotations regardless of
+ * whether or not the target annotation has been found.
+ * @return {@code true} if this processor always processes annotations
+ * @since 4.3
+ */
+ boolean alwaysProcesses();
+
+ /**
+ * Determine if this processor aggregates the results returned by {@link #process}.
+ * <p>If this method returns {@code true}, then {@link #getAggregatedResults()}
+ * must return a non-null value.
+ * @return {@code true} if this processor supports aggregated results
+ * @see #getAggregatedResults
+ * @since 4.3
+ */
+ boolean aggregates();
+
+ /**
+ * Get the list of results aggregated by this processor.
+ * <p>NOTE: the processor does <strong>not</strong> aggregate the results
+ * itself. Rather, the search algorithm that uses this processor is
+ * responsible for asking this processor if it {@link #aggregates} results
+ * and then adding the post-processed results to the list returned by this
+ * method.
+ * @return the list of results aggregated by this processor; never
+ * {@code null} unless {@link #aggregates} returns {@code false}
+ * @see #aggregates
+ * @since 4.3
+ */
+ List<T> getAggregatedResults();
}
/**
- * {@link Processor} that {@linkplain #process processes} annotations
- * but does not {@linkplain #postProcess post-process} results.
+ * {@link Processor} that {@linkplain #process(AnnotatedElement, Annotation, int)
+ * processes} annotations but does not {@linkplain #postProcess post-process} or
+ * {@linkplain #aggregates aggregate} results.
* @since 4.2
*/
private abstract static class SimpleAnnotationProcessor<T> implements Processor<T> {
+ private final boolean alwaysProcesses;
+
+ public SimpleAnnotationProcessor() {
+ this(false);
+ }
+
+ public SimpleAnnotationProcessor(boolean alwaysProcesses) {
+ this.alwaysProcesses = alwaysProcesses;
+ }
+
+ @Override
+ public final boolean alwaysProcesses() {
+ return this.alwaysProcesses;
+ }
+
@Override
public final void postProcess(AnnotatedElement annotatedElement, Annotation annotation, T result) {
// no-op
}
+
+ @Override
+ public final boolean aggregates() {
+ return false;
+ }
+
+ @Override
+ public final List<T> getAggregatedResults() {
+ throw new UnsupportedOperationException("SimpleAnnotationProcessor does not support aggregated results");
+ }
+ }
+
+
+ /**
+ * {@link SimpleAnnotationProcessor} that always returns {@link Boolean#TRUE} when
+ * asked to {@linkplain #process(AnnotatedElement, Annotation, int) process} an
+ * annotation.
+ * @since 4.3
+ */
+ static class AlwaysTrueBooleanAnnotationProcessor extends SimpleAnnotationProcessor<Boolean> {
+
+ @Override
+ public final Boolean process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
+ return Boolean.TRUE;
+ }
}
@@ -974,35 +1519,58 @@ public class AnnotatedElementUtils {
* target annotation during the {@link #process} phase and then merges
* annotation attributes from lower levels in the annotation hierarchy
* during the {@link #postProcess} phase.
+ * <p>A {@code MergedAnnotationAttributesProcessor} may optionally be
+ * configured to {@linkplain #aggregates aggregate} results.
* @since 4.2
- * @see AnnotationUtils#retrieveAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean)
+ * @see AnnotationUtils#retrieveAnnotationAttributes
* @see AnnotationUtils#postProcessAnnotationAttributes
*/
private static class MergedAnnotationAttributesProcessor implements Processor<AnnotationAttributes> {
- private final Class<? extends Annotation> annotationType;
-
- private final String annotationName;
-
private final boolean classValuesAsString;
private final boolean nestedAnnotationsAsMap;
- MergedAnnotationAttributesProcessor(Class<? extends Annotation> annotationType, String annotationName,
- boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
+ private final boolean aggregates;
+
+ private final List<AnnotationAttributes> aggregatedResults;
+
+ MergedAnnotationAttributesProcessor() {
+ this(false, false, false);
+ }
+
+ MergedAnnotationAttributesProcessor(boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
+ this(classValuesAsString, nestedAnnotationsAsMap, false);
+ }
+
+ MergedAnnotationAttributesProcessor(boolean classValuesAsString, boolean nestedAnnotationsAsMap,
+ boolean aggregates) {
- this.annotationType = annotationType;
- this.annotationName = annotationName;
this.classValuesAsString = classValuesAsString;
this.nestedAnnotationsAsMap = nestedAnnotationsAsMap;
+ this.aggregates = aggregates;
+ this.aggregatedResults = (aggregates ? new ArrayList<AnnotationAttributes>() : null);
+ }
+
+ @Override
+ public boolean alwaysProcesses() {
+ return false;
+ }
+
+ @Override
+ public boolean aggregates() {
+ return this.aggregates;
+ }
+
+ @Override
+ public List<AnnotationAttributes> getAggregatedResults() {
+ return this.aggregatedResults;
}
@Override
public AnnotationAttributes process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
- boolean found = (this.annotationType != null ? annotation.annotationType() == this.annotationType :
- annotation.annotationType().getName().equals(this.annotationName));
- return (found ? AnnotationUtils.retrieveAnnotationAttributes(annotatedElement, annotation,
- this.classValuesAsString, this.nestedAnnotationsAsMap) : null);
+ return AnnotationUtils.retrieveAnnotationAttributes(annotatedElement, annotation,
+ this.classValuesAsString, this.nestedAnnotationsAsMap);
}
@Override
@@ -1010,15 +1578,36 @@ public class AnnotatedElementUtils {
annotation = AnnotationUtils.synthesizeAnnotation(annotation, element);
Class<? extends Annotation> targetAnnotationType = attributes.annotationType();
+ // Track which attribute values have already been replaced so that we can short
+ // circuit the search algorithms.
+ Set<String> valuesAlreadyReplaced = new HashSet<String>();
+
for (Method attributeMethod : AnnotationUtils.getAttributeMethods(annotation.annotationType())) {
String attributeName = attributeMethod.getName();
String attributeOverrideName = AnnotationUtils.getAttributeOverrideName(attributeMethod, targetAnnotationType);
// Explicit annotation attribute override declared via @AliasFor
if (attributeOverrideName != null) {
- if (attributes.containsKey(attributeOverrideName)) {
- overrideAttribute(element, annotation, attributes, attributeName, attributeOverrideName);
+ if (valuesAlreadyReplaced.contains(attributeOverrideName)) {
+ continue;
+ }
+
+ List<String> targetAttributeNames = new ArrayList<String>();
+ targetAttributeNames.add(attributeOverrideName);
+ valuesAlreadyReplaced.add(attributeOverrideName);
+
+ // Ensure all aliased attributes in the target annotation are overridden. (SPR-14069)
+ List<String> aliases = AnnotationUtils.getAttributeAliasMap(targetAnnotationType).get(attributeOverrideName);
+ if (aliases != null) {
+ for (String alias : aliases) {
+ if (!valuesAlreadyReplaced.contains(alias)) {
+ targetAttributeNames.add(alias);
+ valuesAlreadyReplaced.add(alias);
+ }
+ }
}
+
+ overrideAttributes(element, annotation, attributes, attributeName, targetAttributeNames);
}
// Implicit annotation attribute override based on convention
else if (!AnnotationUtils.VALUE.equals(attributeName) && attributes.containsKey(attributeName)) {
@@ -1027,13 +1616,25 @@ public class AnnotatedElementUtils {
}
}
- private void overrideAttribute(AnnotatedElement element, Annotation annotation,
- AnnotationAttributes attributes, String sourceAttributeName, String targetAttributeName) {
+ private void overrideAttributes(AnnotatedElement element, Annotation annotation,
+ AnnotationAttributes attributes, String sourceAttributeName, List<String> targetAttributeNames) {
+
+ Object adaptedValue = getAdaptedValue(element, annotation, sourceAttributeName);
+
+ for (String targetAttributeName : targetAttributeNames) {
+ attributes.put(targetAttributeName, adaptedValue);
+ }
+ }
+
+ private void overrideAttribute(AnnotatedElement element, Annotation annotation, AnnotationAttributes attributes,
+ String sourceAttributeName, String targetAttributeName) {
+
+ attributes.put(targetAttributeName, getAdaptedValue(element, annotation, sourceAttributeName));
+ }
+ private Object getAdaptedValue(AnnotatedElement element, Annotation annotation, String sourceAttributeName) {
Object value = AnnotationUtils.getValue(annotation, sourceAttributeName);
- Object adaptedValue = AnnotationUtils.adaptValue(
- element, value, this.classValuesAsString, this.nestedAnnotationsAsMap);
- attributes.put(targetAttributeName, adaptedValue);
+ return AnnotationUtils.adaptValue(element, value, this.classValuesAsString, this.nestedAnnotationsAsMap);
}
}
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributeExtractor.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributeExtractor.java
index 86ca194b..7fd8dd42 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributeExtractor.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributeExtractor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
-import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
/**
@@ -44,7 +43,7 @@ interface AnnotationAttributeExtractor<S> {
* type supported by this extractor.
* @return the annotated element, or {@code null} if unknown
*/
- AnnotatedElement getAnnotatedElement();
+ Object getAnnotatedElement();
/**
* Get the underlying source of annotation attributes.
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java
index 395d2dba..0cf257d6 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,8 +36,7 @@ import org.springframework.util.StringUtils;
*
* <p>Provides 'pseudo-reification' to avoid noisy Map generics in the calling
* code as well as convenience methods for looking up annotation attributes
- * in a type-safe fashion, including support for attribute aliases configured
- * via {@link AliasFor @AliasFor}.
+ * in a type-safe fashion.
*
* @author Chris Beams
* @author Sam Brannen
@@ -45,22 +44,36 @@ import org.springframework.util.StringUtils;
* @since 3.1.1
* @see AnnotationUtils#getAnnotationAttributes
* @see AnnotatedElementUtils
- * @see AliasFor
*/
@SuppressWarnings("serial")
public class AnnotationAttributes extends LinkedHashMap<String, Object> {
+ private static final String UNKNOWN = "unknown";
+
private final Class<? extends Annotation> annotationType;
private final String displayName;
+ boolean validated = false;
+
/**
* Create a new, empty {@link AnnotationAttributes} instance.
*/
public AnnotationAttributes() {
this.annotationType = null;
- this.displayName = "unknown";
+ this.displayName = UNKNOWN;
+ }
+
+ /**
+ * Create a new, empty {@link AnnotationAttributes} instance with the
+ * given initial capacity to optimize performance.
+ * @param initialCapacity initial size of the underlying map
+ */
+ public AnnotationAttributes(int initialCapacity) {
+ super(initialCapacity);
+ this.annotationType = null;
+ this.displayName = UNKNOWN;
}
/**
@@ -71,33 +84,62 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
* @since 4.2
*/
public AnnotationAttributes(Class<? extends Annotation> annotationType) {
- Assert.notNull(annotationType, "annotationType must not be null");
+ Assert.notNull(annotationType, "'annotationType' must not be null");
this.annotationType = annotationType;
this.displayName = annotationType.getName();
}
/**
- * Create a new, empty {@link AnnotationAttributes} instance with the
- * given initial capacity to optimize performance.
- * @param initialCapacity initial size of the underlying map
+ * Create a new, empty {@link AnnotationAttributes} instance for the
+ * specified {@code annotationType}.
+ * @param annotationType the annotation type name represented by this
+ * {@code AnnotationAttributes} instance; never {@code null}
+ * @param classLoader the ClassLoader to try to load the annotation type on,
+ * or {@code null} to just store the annotation type name
+ * @since 4.3.2
*/
- public AnnotationAttributes(int initialCapacity) {
- super(initialCapacity);
- this.annotationType = null;
- this.displayName = "unknown";
+ public AnnotationAttributes(String annotationType, ClassLoader classLoader) {
+ Assert.notNull(annotationType, "'annotationType' must not be null");
+ this.annotationType = getAnnotationType(annotationType, classLoader);
+ this.displayName = annotationType;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Class<? extends Annotation> getAnnotationType(String annotationType, ClassLoader classLoader) {
+ if (classLoader != null) {
+ try {
+ return (Class<? extends Annotation>) classLoader.loadClass(annotationType);
+ }
+ catch (ClassNotFoundException ex) {
+ // Annotation Class not resolvable
+ }
+ }
+ return null;
}
/**
- * Create a new {@link AnnotationAttributes} instance, wrapping the
- * provided map and all its <em>key-value</em> pairs.
- * @param map original source of annotation attribute <em>key-value</em>
- * pairs
+ * Create a new {@link AnnotationAttributes} instance, wrapping the provided
+ * map and all its <em>key-value</em> pairs.
+ * @param map original source of annotation attribute <em>key-value</em> pairs
* @see #fromMap(Map)
*/
public AnnotationAttributes(Map<String, Object> map) {
super(map);
this.annotationType = null;
- this.displayName = "unknown";
+ this.displayName = UNKNOWN;
+ }
+
+ /**
+ * Create a new {@link AnnotationAttributes} instance, wrapping the provided
+ * map and all its <em>key-value</em> pairs.
+ * @param other original source of annotation attribute <em>key-value</em> pairs
+ * @see #fromMap(Map)
+ */
+ public AnnotationAttributes(AnnotationAttributes other) {
+ super(other);
+ this.annotationType = other.annotationType;
+ this.displayName = other.displayName;
+ this.validated = other.validated;
}
@@ -144,8 +186,10 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
* @throws AnnotationConfigurationException if the attribute and its
* alias are both present with different non-empty values
* @since 4.2
- * @see ObjectUtils#isEmpty(Object)
+ * @deprecated as of Spring 4.3.2, in favor of built-in alias resolution
+ * in {@link #getString} itself
*/
+ @Deprecated
public String getAliasedString(String attributeName, Class<? extends Annotation> annotationType,
Object annotationSource) {
@@ -188,7 +232,10 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
* @throws AnnotationConfigurationException if the attribute and its
* alias are both present with different non-empty values
* @since 4.2
+ * @deprecated as of Spring 4.3.2, in favor of built-in alias resolution
+ * in {@link #getStringArray} itself
*/
+ @Deprecated
public String[] getAliasedStringArray(String attributeName, Class<? extends Annotation> annotationType,
Object annotationSource) {
@@ -286,7 +333,10 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
* @throws AnnotationConfigurationException if the attribute and its
* alias are both present with different non-empty values
* @since 4.2
+ * @deprecated as of Spring 4.3.2, in favor of built-in alias resolution
+ * in {@link #getClassArray} itself
*/
+ @Deprecated
public Class<?>[] getAliasedClassArray(String attributeName, Class<? extends Annotation> annotationType,
Object annotationSource) {
@@ -378,7 +428,7 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
*/
@SuppressWarnings("unchecked")
private <T> T getRequiredAttribute(String attributeName, Class<T> expectedType) {
- Assert.hasText(attributeName, "attributeName must not be null or empty");
+ Assert.hasText(attributeName, "'attributeName' must not be null or empty");
Object value = get(attributeName);
assertAttributePresence(attributeName, value);
assertNotException(attributeName, value);
@@ -418,9 +468,9 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
private <T> T getRequiredAttributeWithAlias(String attributeName, Class<? extends Annotation> annotationType,
Object annotationSource, Class<T> expectedType) {
- Assert.hasText(attributeName, "attributeName must not be null or empty");
- Assert.notNull(annotationType, "annotationType must not be null");
- Assert.notNull(expectedType, "expectedType must not be null");
+ Assert.hasText(attributeName, "'attributeName' must not be null or empty");
+ Assert.notNull(annotationType, "'annotationType' must not be null");
+ Assert.notNull(expectedType, "'expectedType' must not be null");
T attributeValue = getAttribute(attributeName, expectedType);
@@ -433,8 +483,8 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
if (!attributeEmpty && !aliasEmpty && !ObjectUtils.nullSafeEquals(attributeValue, aliasValue)) {
String elementName = (annotationSource == null ? "unknown element" : annotationSource.toString());
- String msg = String.format("In annotation [%s] declared on [%s], attribute [%s] and its alias [%s] " +
- "are present with values of [%s] and [%s], but only one is permitted.",
+ String msg = String.format("In annotation [%s] declared on [%s], attribute [%s] and its " +
+ "alias [%s] are present with values of [%s] and [%s], but only one is permitted.",
annotationType.getName(), elementName, attributeName, aliasName,
ObjectUtils.nullSafeToString(attributeValue), ObjectUtils.nullSafeToString(aliasValue));
throw new AnnotationConfigurationException(msg);
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java
index beecd919..01ddd5be 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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 java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import org.springframework.core.DecoratingProxy;
import org.springframework.core.OrderComparator;
/**
@@ -81,10 +82,13 @@ public class AnnotationAwareOrderComparator extends OrderComparator {
}
}
else if (obj != null) {
- return OrderUtils.getOrder(obj.getClass());
+ order = OrderUtils.getOrder(obj.getClass());
+ if (order == null && obj instanceof DecoratingProxy) {
+ order = OrderUtils.getOrder(((DecoratingProxy) obj).getDecoratedClass());
+ }
}
- return null;
+ return order;
}
/**
@@ -94,13 +98,17 @@ public class AnnotationAwareOrderComparator extends OrderComparator {
* multiple matches but only one object to be returned.
*/
public Integer getPriority(Object obj) {
+ Integer priority = null;
if (obj instanceof Class) {
- return OrderUtils.getPriority((Class<?>) obj);
+ priority = OrderUtils.getPriority((Class<?>) obj);
}
else if (obj != null) {
- return OrderUtils.getPriority(obj.getClass());
+ priority = OrderUtils.getPriority(obj.getClass());
+ if (priority == null && obj instanceof DecoratingProxy) {
+ priority = OrderUtils.getOrder(((DecoratingProxy) obj).getDecoratedClass());
+ }
}
- return null;
+ return priority;
}
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java
index 09eb167e..2fe059a5 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java
@@ -22,6 +22,7 @@ import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collections;
@@ -111,6 +112,7 @@ public abstract class AnnotationUtils {
*/
public static final String VALUE = "value";
+ private static final String REPEATABLE_CLASS_NAME = "java.lang.annotation.Repeatable";
private static final Map<AnnotationCacheKey, Annotation> findAnnotationCache =
new ConcurrentReferenceHashMap<AnnotationCacheKey, Annotation>(256);
@@ -308,6 +310,7 @@ public abstract class AnnotationUtils {
* @since 4.2
* @see #getRepeatableAnnotations(AnnotatedElement, Class, Class)
* @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class, Class)
+ * @see AnnotatedElementUtils#getMergedRepeatableAnnotations(AnnotatedElement, Class)
* @see org.springframework.core.BridgeMethodResolver#findBridgedMethod
* @see java.lang.annotation.Repeatable
* @see java.lang.reflect.AnnotatedElement#getAnnotationsByType
@@ -343,6 +346,7 @@ public abstract class AnnotationUtils {
* @see #getRepeatableAnnotations(AnnotatedElement, Class)
* @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class)
* @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class, Class)
+ * @see AnnotatedElementUtils#getMergedRepeatableAnnotations(AnnotatedElement, Class, Class)
* @see org.springframework.core.BridgeMethodResolver#findBridgedMethod
* @see java.lang.annotation.Repeatable
* @see java.lang.reflect.AnnotatedElement#getAnnotationsByType
@@ -388,6 +392,7 @@ public abstract class AnnotationUtils {
* @see #getRepeatableAnnotations(AnnotatedElement, Class)
* @see #getRepeatableAnnotations(AnnotatedElement, Class, Class)
* @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class, Class)
+ * @see AnnotatedElementUtils#getMergedRepeatableAnnotations(AnnotatedElement, Class)
* @see org.springframework.core.BridgeMethodResolver#findBridgedMethod
* @see java.lang.annotation.Repeatable
* @see java.lang.reflect.AnnotatedElement#getDeclaredAnnotationsByType
@@ -423,6 +428,7 @@ public abstract class AnnotationUtils {
* @see #getRepeatableAnnotations(AnnotatedElement, Class)
* @see #getRepeatableAnnotations(AnnotatedElement, Class, Class)
* @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class)
+ * @see AnnotatedElementUtils#getMergedRepeatableAnnotations(AnnotatedElement, Class, Class)
* @see org.springframework.core.BridgeMethodResolver#findBridgedMethod
* @see java.lang.annotation.Repeatable
* @see java.lang.reflect.AnnotatedElement#getDeclaredAnnotationsByType
@@ -616,7 +622,7 @@ public abstract class AnnotationUtils {
static boolean isInterfaceWithAnnotatedMethods(Class<?> iface) {
Boolean found = annotatedInterfaceCache.get(iface);
if (found != null) {
- return found.booleanValue();
+ return found;
}
found = Boolean.FALSE;
for (Method ifcMethod : iface.getMethods()) {
@@ -631,7 +637,7 @@ public abstract class AnnotationUtils {
}
}
annotatedInterfaceCache.put(iface, found);
- return found.booleanValue();
+ return found;
}
/**
@@ -883,14 +889,14 @@ public abstract class AnnotationUtils {
AnnotationCacheKey cacheKey = new AnnotationCacheKey(annotationType, metaAnnotationType);
Boolean metaPresent = metaPresentCache.get(cacheKey);
if (metaPresent != null) {
- return metaPresent.booleanValue();
+ return metaPresent;
}
metaPresent = Boolean.FALSE;
if (findAnnotation(annotationType, metaAnnotationType, false) != null) {
metaPresent = Boolean.TRUE;
}
metaPresentCache.put(cacheKey, metaPresent);
- return metaPresent.booleanValue();
+ return metaPresent;
}
/**
@@ -1013,6 +1019,13 @@ public abstract class AnnotationUtils {
public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement annotatedElement,
Annotation annotation, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
+ return getAnnotationAttributes(
+ (Object) annotatedElement, annotation, classValuesAsString, nestedAnnotationsAsMap);
+ }
+
+ private static AnnotationAttributes getAnnotationAttributes(Object annotatedElement,
+ Annotation annotation, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
+
AnnotationAttributes attributes =
retrieveAnnotationAttributes(annotatedElement, annotation, classValuesAsString, nestedAnnotationsAsMap);
postProcessAnnotationAttributes(annotatedElement, attributes, classValuesAsString, nestedAnnotationsAsMap);
@@ -1047,7 +1060,7 @@ public abstract class AnnotationUtils {
* @since 4.2
* @see #postProcessAnnotationAttributes
*/
- static AnnotationAttributes retrieveAnnotationAttributes(AnnotatedElement annotatedElement, Annotation annotation,
+ static AnnotationAttributes retrieveAnnotationAttributes(Object annotatedElement, Annotation annotation,
boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
Class<? extends Annotation> annotationType = annotation.annotationType();
@@ -1091,14 +1104,14 @@ public abstract class AnnotationUtils {
* {@code Annotation} instances
* @return the adapted value, or the original value if no adaptation is needed
*/
- static Object adaptValue(AnnotatedElement annotatedElement, Object value, boolean classValuesAsString,
+ static Object adaptValue(Object annotatedElement, Object value, boolean classValuesAsString,
boolean nestedAnnotationsAsMap) {
if (classValuesAsString) {
- if (value instanceof Class) {
+ if (value instanceof Class<?>) {
return ((Class<?>) value).getName();
}
- else if (value instanceof Class[]) {
+ else if (value instanceof Class<?>[]) {
Class<?>[] clazzArray = (Class<?>[]) value;
String[] classNames = new String[clazzArray.length];
for (int i = 0; i < clazzArray.length; i++) {
@@ -1138,6 +1151,63 @@ public abstract class AnnotationUtils {
}
/**
+ * Register the annotation-declared default values for the given attributes,
+ * if available.
+ * @param attributes the annotation attributes to process
+ * @since 4.3.2
+ */
+ public static void registerDefaultValues(AnnotationAttributes attributes) {
+ // Only do defaults scanning for public annotations; we'd run into
+ // IllegalAccessExceptions otherwise, and we don't want to mess with
+ // accessibility in a SecurityManager environment.
+ Class<? extends Annotation> annotationType = attributes.annotationType();
+ if (annotationType != null && Modifier.isPublic(annotationType.getModifiers())) {
+ // Check declared default values of attributes in the annotation type.
+ for (Method annotationAttribute : getAttributeMethods(annotationType)) {
+ String attributeName = annotationAttribute.getName();
+ Object defaultValue = annotationAttribute.getDefaultValue();
+ if (defaultValue != null && !attributes.containsKey(attributeName)) {
+ if (defaultValue instanceof Annotation) {
+ defaultValue = getAnnotationAttributes((Annotation) defaultValue, false, true);
+ }
+ else if (defaultValue instanceof Annotation[]) {
+ Annotation[] realAnnotations = (Annotation[]) defaultValue;
+ AnnotationAttributes[] mappedAnnotations = new AnnotationAttributes[realAnnotations.length];
+ for (int i = 0; i < realAnnotations.length; i++) {
+ mappedAnnotations[i] = getAnnotationAttributes(realAnnotations[i], false, true);
+ }
+ defaultValue = mappedAnnotations;
+ }
+ attributes.put(attributeName, new DefaultValueHolder(defaultValue));
+ }
+ }
+ }
+ }
+
+ /**
+ * Post-process the supplied {@link AnnotationAttributes}, preserving nested
+ * annotations as {@code Annotation} instances.
+ * <p>Specifically, this method enforces <em>attribute alias</em> semantics
+ * for annotation attributes that are annotated with {@link AliasFor @AliasFor}
+ * and replaces default value placeholders with their original default values.
+ * @param annotatedElement the element that is annotated with an annotation or
+ * annotation hierarchy from which the supplied attributes were created;
+ * may be {@code null} if unknown
+ * @param attributes the annotation attributes to post-process
+ * @param classValuesAsString whether to convert Class references into Strings (for
+ * compatibility with {@link org.springframework.core.type.AnnotationMetadata})
+ * or to preserve them as Class references
+ * @since 4.3.2
+ * @see #postProcessAnnotationAttributes(Object, AnnotationAttributes, boolean, boolean)
+ * @see #getDefaultValue(Class, String)
+ */
+ public static void postProcessAnnotationAttributes(Object annotatedElement,
+ AnnotationAttributes attributes, boolean classValuesAsString) {
+
+ postProcessAnnotationAttributes(annotatedElement, attributes, classValuesAsString, false);
+ }
+
+ /**
* Post-process the supplied {@link AnnotationAttributes}.
* <p>Specifically, this method enforces <em>attribute alias</em> semantics
* for annotation attributes that are annotated with {@link AliasFor @AliasFor}
@@ -1154,10 +1224,10 @@ public abstract class AnnotationUtils {
* {@link org.springframework.core.type.AnnotationMetadata}) or to preserve them as
* {@code Annotation} instances
* @since 4.2
- * @see #retrieveAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean)
+ * @see #retrieveAnnotationAttributes(Object, Annotation, boolean, boolean)
* @see #getDefaultValue(Class, String)
*/
- static void postProcessAnnotationAttributes(AnnotatedElement annotatedElement,
+ static void postProcessAnnotationAttributes(Object annotatedElement,
AnnotationAttributes attributes, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
// Abort?
@@ -1171,51 +1241,55 @@ public abstract class AnnotationUtils {
// circuit the search algorithms.
Set<String> valuesAlreadyReplaced = new HashSet<String>();
- // Validate @AliasFor configuration
- Map<String, List<String>> aliasMap = getAttributeAliasMap(annotationType);
- for (String attributeName : aliasMap.keySet()) {
- if (valuesAlreadyReplaced.contains(attributeName)) {
- continue;
- }
- Object value = attributes.get(attributeName);
- boolean valuePresent = (value != null && !(value instanceof DefaultValueHolder));
-
- for (String aliasedAttributeName : aliasMap.get(attributeName)) {
- if (valuesAlreadyReplaced.contains(aliasedAttributeName)) {
+ if (!attributes.validated) {
+ // Validate @AliasFor configuration
+ Map<String, List<String>> aliasMap = getAttributeAliasMap(annotationType);
+ for (String attributeName : aliasMap.keySet()) {
+ if (valuesAlreadyReplaced.contains(attributeName)) {
continue;
}
+ Object value = attributes.get(attributeName);
+ boolean valuePresent = (value != null && !(value instanceof DefaultValueHolder));
- Object aliasedValue = attributes.get(aliasedAttributeName);
- boolean aliasPresent = (aliasedValue != null && !(aliasedValue instanceof DefaultValueHolder));
-
- // Something to validate or replace with an alias?
- if (valuePresent || aliasPresent) {
- if (valuePresent && aliasPresent) {
- // Since annotation attributes can be arrays, we must use ObjectUtils.nullSafeEquals().
- if (!ObjectUtils.nullSafeEquals(value, aliasedValue)) {
- String elementAsString = (annotatedElement != null ? annotatedElement.toString() : "unknown element");
- throw new AnnotationConfigurationException(String.format(
- "In AnnotationAttributes for annotation [%s] declared on %s, " +
- "attribute '%s' and its alias '%s' are declared with values of [%s] and [%s], " +
- "but only one is permitted.", annotationType.getName(), elementAsString,
- attributeName, aliasedAttributeName, ObjectUtils.nullSafeToString(value),
- ObjectUtils.nullSafeToString(aliasedValue)));
- }
+ for (String aliasedAttributeName : aliasMap.get(attributeName)) {
+ if (valuesAlreadyReplaced.contains(aliasedAttributeName)) {
+ continue;
}
- else if (aliasPresent) {
- // Replace value with aliasedValue
- attributes.put(attributeName,
- adaptValue(annotatedElement, aliasedValue, classValuesAsString, nestedAnnotationsAsMap));
- valuesAlreadyReplaced.add(attributeName);
- }
- else {
- // Replace aliasedValue with value
- attributes.put(aliasedAttributeName,
- adaptValue(annotatedElement, value, classValuesAsString, nestedAnnotationsAsMap));
- valuesAlreadyReplaced.add(aliasedAttributeName);
+
+ Object aliasedValue = attributes.get(aliasedAttributeName);
+ boolean aliasPresent = (aliasedValue != null && !(aliasedValue instanceof DefaultValueHolder));
+
+ // Something to validate or replace with an alias?
+ if (valuePresent || aliasPresent) {
+ if (valuePresent && aliasPresent) {
+ // Since annotation attributes can be arrays, we must use ObjectUtils.nullSafeEquals().
+ if (!ObjectUtils.nullSafeEquals(value, aliasedValue)) {
+ String elementAsString =
+ (annotatedElement != null ? annotatedElement.toString() : "unknown element");
+ throw new AnnotationConfigurationException(String.format(
+ "In AnnotationAttributes for annotation [%s] declared on %s, " +
+ "attribute '%s' and its alias '%s' are declared with values of [%s] and [%s], " +
+ "but only one is permitted.", annotationType.getName(), elementAsString,
+ attributeName, aliasedAttributeName, ObjectUtils.nullSafeToString(value),
+ ObjectUtils.nullSafeToString(aliasedValue)));
+ }
+ }
+ else if (aliasPresent) {
+ // Replace value with aliasedValue
+ attributes.put(attributeName,
+ adaptValue(annotatedElement, aliasedValue, classValuesAsString, nestedAnnotationsAsMap));
+ valuesAlreadyReplaced.add(attributeName);
+ }
+ else {
+ // Replace aliasedValue with value
+ attributes.put(aliasedAttributeName,
+ adaptValue(annotatedElement, value, classValuesAsString, nestedAnnotationsAsMap));
+ valuesAlreadyReplaced.add(aliasedAttributeName);
+ }
}
}
}
+ attributes.validated = true;
}
// Replace any remaining placeholders with actual default values
@@ -1355,8 +1429,12 @@ public abstract class AnnotationUtils {
* @see #synthesizeAnnotation(Map, Class, AnnotatedElement)
* @see #synthesizeAnnotation(Class)
*/
- @SuppressWarnings("unchecked")
public static <A extends Annotation> A synthesizeAnnotation(A annotation, AnnotatedElement annotatedElement) {
+ return synthesizeAnnotation(annotation, (Object) annotatedElement);
+ }
+
+ @SuppressWarnings("unchecked")
+ static <A extends Annotation> A synthesizeAnnotation(A annotation, Object annotatedElement) {
if (annotation == null) {
return null;
}
@@ -1461,7 +1539,7 @@ public abstract class AnnotationUtils {
* @see #synthesizeAnnotation(Annotation, AnnotatedElement)
* @see #synthesizeAnnotation(Map, Class, AnnotatedElement)
*/
- public static Annotation[] synthesizeAnnotationArray(Annotation[] annotations, AnnotatedElement annotatedElement) {
+ static Annotation[] synthesizeAnnotationArray(Annotation[] annotations, Object annotatedElement) {
if (annotations == null) {
return null;
}
@@ -1489,7 +1567,7 @@ public abstract class AnnotationUtils {
* {@code @AliasFor} is detected
* @since 4.2.1
* @see #synthesizeAnnotation(Map, Class, AnnotatedElement)
- * @see #synthesizeAnnotationArray(Annotation[], AnnotatedElement)
+ * @see #synthesizeAnnotationArray(Annotation[], Object)
*/
@SuppressWarnings("unchecked")
static <A extends Annotation> A[] synthesizeAnnotationArray(Map<String, Object>[] maps, Class<A> annotationType) {
@@ -1577,7 +1655,7 @@ public abstract class AnnotationUtils {
private static boolean isSynthesizable(Class<? extends Annotation> annotationType) {
Boolean synthesizable = synthesizableCache.get(annotationType);
if (synthesizable != null) {
- return synthesizable.booleanValue();
+ return synthesizable;
}
synthesizable = Boolean.FALSE;
@@ -1605,7 +1683,7 @@ public abstract class AnnotationUtils {
}
synthesizableCache.put(annotationType, synthesizable);
- return synthesizable.booleanValue();
+ return synthesizable;
}
/**
@@ -1721,6 +1799,29 @@ public abstract class AnnotationUtils {
}
/**
+ * Resolve the container type for the supplied repeatable {@code annotationType}.
+ * <p>Automatically detects a <em>container annotation</em> declared via
+ * {@link java.lang.annotation.Repeatable}. If the supplied annotation type
+ * is not annotated with {@code @Repeatable}, this method simply returns
+ * {@code null}.
+ * @since 4.2
+ */
+ @SuppressWarnings("unchecked")
+ static Class<? extends Annotation> resolveContainerAnnotationType(Class<? extends Annotation> annotationType) {
+ try {
+ Annotation repeatable = getAnnotation(annotationType, REPEATABLE_CLASS_NAME);
+ if (repeatable != null) {
+ Object value = getValue(repeatable);
+ return (Class<? extends Annotation>) value;
+ }
+ }
+ catch (Exception ex) {
+ handleIntrospectionFailure(annotationType, ex);
+ }
+ return null;
+ }
+
+ /**
* If the supplied throwable is an {@link AnnotationConfigurationException},
* it will be cast to an {@code AnnotationConfigurationException} and thrown,
* allowing it to propagate to the caller.
@@ -1774,7 +1875,7 @@ public abstract class AnnotationUtils {
/**
* Cache key for the AnnotatedElement cache.
*/
- private static class AnnotationCacheKey {
+ private static final class AnnotationCacheKey implements Comparable<AnnotationCacheKey> {
private final AnnotatedElement element;
@@ -1801,13 +1902,25 @@ public abstract class AnnotationUtils {
public int hashCode() {
return (this.element.hashCode() * 29 + this.annotationType.hashCode());
}
+
+ @Override
+ public String toString() {
+ return "@" + this.annotationType + " on " + this.element;
+ }
+
+ @Override
+ public int compareTo(AnnotationCacheKey other) {
+ int result = this.element.toString().compareTo(other.element.toString());
+ if (result == 0) {
+ result = this.annotationType.getName().compareTo(other.annotationType.getName());
+ }
+ return result;
+ }
}
private static class AnnotationCollector<A extends Annotation> {
- private static final String REPEATABLE_CLASS_NAME = "java.lang.annotation.Repeatable";
-
private final Class<A> annotationType;
private final Class<? extends Annotation> containerAnnotationType;
@@ -1825,21 +1938,6 @@ public abstract class AnnotationUtils {
this.declaredMode = declaredMode;
}
- @SuppressWarnings("unchecked")
- static Class<? extends Annotation> resolveContainerAnnotationType(Class<? extends Annotation> annotationType) {
- try {
- Annotation repeatable = getAnnotation(annotationType, REPEATABLE_CLASS_NAME);
- if (repeatable != null) {
- Object value = AnnotationUtils.getValue(repeatable);
- return (Class<? extends Annotation>) value;
- }
- }
- catch (Exception ex) {
- handleIntrospectionFailure(annotationType, ex);
- }
- return null;
- }
-
Set<A> getResult(AnnotatedElement element) {
process(element);
return Collections.unmodifiableSet(this.result);
@@ -1951,12 +2049,19 @@ public abstract class AnnotationUtils {
this.aliasedAnnotationType = (Annotation.class == aliasFor.annotation() ?
this.sourceAnnotationType : aliasFor.annotation());
this.aliasedAttributeName = getAliasedAttributeName(aliasFor, sourceAttribute);
+ if (this.aliasedAnnotationType == this.sourceAnnotationType &&
+ this.aliasedAttributeName.equals(this.sourceAttributeName)) {
+ String msg = String.format("@AliasFor declaration on attribute '%s' in annotation [%s] points to " +
+ "itself. Specify 'annotation' to point to a same-named attribute on a meta-annotation.",
+ sourceAttribute.getName(), declaringClass.getName());
+ throw new AnnotationConfigurationException(msg);
+ }
try {
this.aliasedAttribute = this.aliasedAnnotationType.getDeclaredMethod(this.aliasedAttributeName);
}
catch (NoSuchMethodException ex) {
String msg = String.format(
- "Attribute [%s] in annotation [%s] is declared as an @AliasFor nonexistent attribute [%s] in annotation [%s].",
+ "Attribute '%s' in annotation [%s] is declared as an @AliasFor nonexistent attribute '%s' in annotation [%s].",
this.sourceAttributeName, this.sourceAnnotationType.getName(), this.aliasedAttributeName,
this.aliasedAnnotationType.getName());
throw new AnnotationConfigurationException(msg, ex);
@@ -1968,8 +2073,8 @@ public abstract class AnnotationUtils {
private void validate() {
// Target annotation is not meta-present?
if (!this.isAliasPair && !isAnnotationMetaPresent(this.sourceAnnotationType, this.aliasedAnnotationType)) {
- String msg = String.format("@AliasFor declaration on attribute [%s] in annotation [%s] declares " +
- "an alias for attribute [%s] in meta-annotation [%s] which is not meta-present.",
+ String msg = String.format("@AliasFor declaration on attribute '%s' in annotation [%s] declares " +
+ "an alias for attribute '%s' in meta-annotation [%s] which is not meta-present.",
this.sourceAttributeName, this.sourceAnnotationType.getName(), this.aliasedAttributeName,
this.aliasedAnnotationType.getName());
throw new AnnotationConfigurationException(msg);
@@ -1978,14 +2083,14 @@ public abstract class AnnotationUtils {
if (this.isAliasPair) {
AliasFor mirrorAliasFor = this.aliasedAttribute.getAnnotation(AliasFor.class);
if (mirrorAliasFor == null) {
- String msg = String.format("Attribute [%s] in annotation [%s] must be declared as an @AliasFor [%s].",
+ String msg = String.format("Attribute '%s' in annotation [%s] must be declared as an @AliasFor [%s].",
this.aliasedAttributeName, this.sourceAnnotationType.getName(), this.sourceAttributeName);
throw new AnnotationConfigurationException(msg);
}
String mirrorAliasedAttributeName = getAliasedAttributeName(mirrorAliasFor, this.aliasedAttribute);
if (!this.sourceAttributeName.equals(mirrorAliasedAttributeName)) {
- String msg = String.format("Attribute [%s] in annotation [%s] must be declared as an @AliasFor [%s], not [%s].",
+ String msg = String.format("Attribute '%s' in annotation [%s] must be declared as an @AliasFor [%s], not [%s].",
this.aliasedAttributeName, this.sourceAnnotationType.getName(), this.sourceAttributeName,
mirrorAliasedAttributeName);
throw new AnnotationConfigurationException(msg);
@@ -1994,9 +2099,10 @@ public abstract class AnnotationUtils {
Class<?> returnType = this.sourceAttribute.getReturnType();
Class<?> aliasedReturnType = this.aliasedAttribute.getReturnType();
- if (returnType != aliasedReturnType) {
- String msg = String.format("Misconfigured aliases: attribute [%s] in annotation [%s] " +
- "and attribute [%s] in annotation [%s] must declare the same return type.",
+ if (returnType != aliasedReturnType &&
+ (!aliasedReturnType.isArray() || returnType != aliasedReturnType.getComponentType())) {
+ String msg = String.format("Misconfigured aliases: attribute '%s' in annotation [%s] " +
+ "and attribute '%s' in annotation [%s] must declare the same return type.",
this.sourceAttributeName, this.sourceAnnotationType.getName(), this.aliasedAttributeName,
this.aliasedAnnotationType.getName());
throw new AnnotationConfigurationException(msg);
@@ -2013,16 +2119,16 @@ public abstract class AnnotationUtils {
Object aliasedDefaultValue = aliasedAttribute.getDefaultValue();
if (defaultValue == null || aliasedDefaultValue == null) {
- String msg = String.format("Misconfigured aliases: attribute [%s] in annotation [%s] " +
- "and attribute [%s] in annotation [%s] must declare default values.",
+ String msg = String.format("Misconfigured aliases: attribute '%s' in annotation [%s] " +
+ "and attribute '%s' in annotation [%s] must declare default values.",
this.sourceAttributeName, this.sourceAnnotationType.getName(), aliasedAttribute.getName(),
aliasedAttribute.getDeclaringClass().getName());
throw new AnnotationConfigurationException(msg);
}
if (!ObjectUtils.nullSafeEquals(defaultValue, aliasedDefaultValue)) {
- String msg = String.format("Misconfigured aliases: attribute [%s] in annotation [%s] " +
- "and attribute [%s] in annotation [%s] must declare the same default value.",
+ String msg = String.format("Misconfigured aliases: attribute '%s' in annotation [%s] " +
+ "and attribute '%s' in annotation [%s] must declare the same default value.",
this.sourceAttributeName, this.sourceAnnotationType.getName(), aliasedAttribute.getName(),
aliasedAttribute.getDeclaringClass().getName());
throw new AnnotationConfigurationException(msg);
@@ -2125,15 +2231,16 @@ public abstract class AnnotationUtils {
/**
* Get the name of the aliased attribute configured via the supplied
- * {@link AliasFor @AliasFor} annotation on the supplied {@code attribute}.
+ * {@link AliasFor @AliasFor} annotation on the supplied {@code attribute},
+ * or the original attribute if no aliased one specified (indicating that
+ * the reference goes to a same-named attribute on a meta-annotation).
* <p>This method returns the value of either the {@code attribute}
* or {@code value} attribute of {@code @AliasFor}, ensuring that only
* one of the attributes has been declared while simultaneously ensuring
* that at least one of the attributes has been declared.
* @param aliasFor the {@code @AliasFor} annotation from which to retrieve
* the aliased attribute name
- * @param attribute the attribute that is annotated with {@code @AliasFor},
- * used solely for building an exception message
+ * @param attribute the attribute that is annotated with {@code @AliasFor}
* @return the name of the aliased attribute (never {@code null} or empty)
* @throws AnnotationConfigurationException if invalid configuration of
* {@code @AliasFor} is detected
@@ -2146,23 +2253,15 @@ public abstract class AnnotationUtils {
// Ensure user did not declare both 'value' and 'attribute' in @AliasFor
if (attributeDeclared && valueDeclared) {
- String msg = String.format("In @AliasFor declared on attribute [%s] in annotation [%s], attribute 'attribute' " +
+ String msg = String.format("In @AliasFor declared on attribute '%s' in annotation [%s], attribute 'attribute' " +
"and its alias 'value' are present with values of [%s] and [%s], but only one is permitted.",
attribute.getName(), attribute.getDeclaringClass().getName(), attributeName, value);
throw new AnnotationConfigurationException(msg);
}
+ // Either explicit attribute name or pointing to same-named attribute by default
attributeName = (attributeDeclared ? attributeName : value);
-
- // Ensure user declared either 'value' or 'attribute' in @AliasFor
- if (!StringUtils.hasText(attributeName)) {
- String msg = String.format(
- "@AliasFor declaration on attribute [%s] in annotation [%s] is missing required 'attribute' value.",
- attribute.getName(), attribute.getDeclaringClass().getName());
- throw new AnnotationConfigurationException(msg);
- }
-
- return attributeName.trim();
+ return (StringUtils.hasText(attributeName) ? attributeName.trim() : attribute.getName());
}
@Override
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/DefaultAnnotationAttributeExtractor.java b/spring-core/src/main/java/org/springframework/core/annotation/DefaultAnnotationAttributeExtractor.java
index 805c769b..0ea2cf00 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/DefaultAnnotationAttributeExtractor.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/DefaultAnnotationAttributeExtractor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
-import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import org.springframework.util.ReflectionUtils;
@@ -32,7 +31,7 @@ import org.springframework.util.ReflectionUtils;
* @see AliasFor
* @see AbstractAliasAwareAnnotationAttributeExtractor
* @see MapAnnotationAttributeExtractor
- * @see AnnotationUtils#synthesizeAnnotation(Annotation, AnnotatedElement)
+ * @see AnnotationUtils#synthesizeAnnotation
*/
class DefaultAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttributeExtractor<Annotation> {
@@ -42,7 +41,7 @@ class DefaultAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAt
* @param annotatedElement the element that is annotated with the supplied
* annotation; may be {@code null} if unknown
*/
- DefaultAnnotationAttributeExtractor(Annotation annotation, AnnotatedElement annotatedElement) {
+ DefaultAnnotationAttributeExtractor(Annotation annotation, Object annotatedElement) {
super(annotation.annotationType(), annotatedElement, annotation);
}
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/MapAnnotationAttributeExtractor.java b/spring-core/src/main/java/org/springframework/core/annotation/MapAnnotationAttributeExtractor.java
index 005ae431..a2491fe5 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/MapAnnotationAttributeExtractor.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/MapAnnotationAttributeExtractor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,15 +18,15 @@ package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Array;
import java.lang.reflect.Method;
-import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
-import static org.springframework.core.annotation.AnnotationUtils.*;
-
/**
* Implementation of the {@link AnnotationAttributeExtractor} strategy that
* is backed by a {@link Map}.
@@ -87,10 +87,10 @@ class MapAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttrib
private static Map<String, Object> enrichAndValidateAttributes(
Map<String, Object> originalAttributes, Class<? extends Annotation> annotationType) {
- Map<String, Object> attributes = new HashMap<String, Object>(originalAttributes);
- Map<String, List<String>> attributeAliasMap = getAttributeAliasMap(annotationType);
+ Map<String, Object> attributes = new LinkedHashMap<String, Object>(originalAttributes);
+ Map<String, List<String>> attributeAliasMap = AnnotationUtils.getAttributeAliasMap(annotationType);
- for (Method attributeMethod : getAttributeMethods(annotationType)) {
+ for (Method attributeMethod : AnnotationUtils.getAttributeMethods(annotationType)) {
String attributeName = attributeMethod.getName();
Object attributeValue = attributes.get(attributeName);
@@ -111,7 +111,7 @@ class MapAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttrib
// if aliases not present, check default
if (attributeValue == null) {
- Object defaultValue = getDefaultValue(annotationType, attributeName);
+ Object defaultValue = AnnotationUtils.getDefaultValue(annotationType, attributeName);
if (defaultValue != null) {
attributeValue = defaultValue;
attributes.put(attributeName, attributeValue);
@@ -121,7 +121,7 @@ class MapAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttrib
// if still null
if (attributeValue == null) {
throw new IllegalArgumentException(String.format(
- "Attributes map [%s] returned null for required attribute [%s] defined by annotation type [%s].",
+ "Attributes map %s returned null for required attribute '%s' defined by annotation type [%s].",
attributes, attributeName, annotationType.getName()));
}
@@ -132,13 +132,21 @@ class MapAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttrib
if (!ClassUtils.isAssignable(requiredReturnType, actualReturnType)) {
boolean converted = false;
+ // Single element overriding an array of the same type?
+ if (requiredReturnType.isArray() && requiredReturnType.getComponentType() == actualReturnType) {
+ Object array = Array.newInstance(requiredReturnType.getComponentType(), 1);
+ Array.set(array, 0, attributeValue);
+ attributes.put(attributeName, array);
+ converted = true;
+ }
+
// Nested map representing a single annotation?
- if (Annotation.class.isAssignableFrom(requiredReturnType) &&
+ else if (Annotation.class.isAssignableFrom(requiredReturnType) &&
Map.class.isAssignableFrom(actualReturnType)) {
Class<? extends Annotation> nestedAnnotationType =
(Class<? extends Annotation>) requiredReturnType;
Map<String, Object> map = (Map<String, Object>) attributeValue;
- attributes.put(attributeName, synthesizeAnnotation(map, nestedAnnotationType, null));
+ attributes.put(attributeName, AnnotationUtils.synthesizeAnnotation(map, nestedAnnotationType, null));
converted = true;
}
@@ -149,14 +157,14 @@ class MapAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttrib
Class<? extends Annotation> nestedAnnotationType =
(Class<? extends Annotation>) requiredReturnType.getComponentType();
Map<String, Object>[] maps = (Map<String, Object>[]) attributeValue;
- attributes.put(attributeName, synthesizeAnnotationArray(maps, nestedAnnotationType));
+ attributes.put(attributeName, AnnotationUtils.synthesizeAnnotationArray(maps, nestedAnnotationType));
converted = true;
}
if (!converted) {
throw new IllegalArgumentException(String.format(
- "Attributes map [%s] returned a value of type [%s] for attribute [%s], "
- + "but a value of type [%s] is required as defined by annotation type [%s].",
+ "Attributes map %s returned a value of type [%s] for attribute '%s', " +
+ "but a value of type [%s] is required as defined by annotation type [%s].",
attributes, actualReturnType.getName(), attributeName, requiredReturnType.getName(),
annotationType.getName()));
}
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedAnnotationInvocationHandler.java b/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedAnnotationInvocationHandler.java
index c8614eb5..27e14c43 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedAnnotationInvocationHandler.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedAnnotationInvocationHandler.java
@@ -27,11 +27,9 @@ import java.util.concurrent.ConcurrentHashMap;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
+import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
-import static org.springframework.core.annotation.AnnotationUtils.*;
-import static org.springframework.util.ReflectionUtils.*;
-
/**
* {@link InvocationHandler} for an {@link Annotation} that Spring has
* <em>synthesized</em> (i.e., wrapped in a dynamic proxy) with additional
@@ -63,22 +61,21 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- if (isEqualsMethod(method)) {
+ if (ReflectionUtils.isEqualsMethod(method)) {
return annotationEquals(args[0]);
}
- if (isHashCodeMethod(method)) {
+ if (ReflectionUtils.isHashCodeMethod(method)) {
return annotationHashCode();
}
- if (isToStringMethod(method)) {
+ if (ReflectionUtils.isToStringMethod(method)) {
return annotationToString();
}
- if (isAnnotationTypeMethod(method)) {
+ if (AnnotationUtils.isAnnotationTypeMethod(method)) {
return annotationType();
}
- if (!isAttributeMethod(method)) {
- String msg = String.format("Method [%s] is unsupported for synthesized annotation type [%s]",
- method, annotationType());
- throw new AnnotationConfigurationException(msg);
+ if (!AnnotationUtils.isAttributeMethod(method)) {
+ throw new AnnotationConfigurationException(String.format(
+ "Method [%s] is unsupported for synthesized annotation type [%s]", method, annotationType()));
}
return getAttributeValue(method);
}
@@ -100,10 +97,10 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
// Synthesize nested annotations before returning them.
if (value instanceof Annotation) {
- value = synthesizeAnnotation((Annotation) value, this.attributeExtractor.getAnnotatedElement());
+ value = AnnotationUtils.synthesizeAnnotation((Annotation) value, this.attributeExtractor.getAnnotatedElement());
}
else if (value instanceof Annotation[]) {
- value = synthesizeAnnotationArray((Annotation[]) value, this.attributeExtractor.getAnnotatedElement());
+ value = AnnotationUtils.synthesizeAnnotationArray((Annotation[]) value, this.attributeExtractor.getAnnotatedElement());
}
this.valueCache.put(attributeName, value);
@@ -164,9 +161,9 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
return false;
}
- for (Method attributeMethod : getAttributeMethods(annotationType())) {
+ for (Method attributeMethod : AnnotationUtils.getAttributeMethods(annotationType())) {
Object thisValue = getAttributeValue(attributeMethod);
- Object otherValue = invokeMethod(attributeMethod, other);
+ Object otherValue = ReflectionUtils.invokeMethod(attributeMethod, other);
if (!ObjectUtils.nullSafeEquals(thisValue, otherValue)) {
return false;
}
@@ -181,7 +178,7 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
private int annotationHashCode() {
int result = 0;
- for (Method attributeMethod : getAttributeMethods(annotationType())) {
+ for (Method attributeMethod : AnnotationUtils.getAttributeMethods(annotationType())) {
Object value = getAttributeValue(attributeMethod);
int hashCode;
if (value.getClass().isArray()) {
@@ -239,7 +236,7 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
private String annotationToString() {
StringBuilder sb = new StringBuilder("@").append(annotationType().getName()).append("(");
- Iterator<Method> iterator = getAttributeMethods(annotationType()).iterator();
+ Iterator<Method> iterator = AnnotationUtils.getAttributeMethods(annotationType()).iterator();
while (iterator.hasNext()) {
Method attributeMethod = iterator.next();
sb.append(attributeMethod.getName());
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/SynthesizingMethodParameter.java b/spring-core/src/main/java/org/springframework/core/annotation/SynthesizingMethodParameter.java
index a6915e76..eb287c47 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/SynthesizingMethodParameter.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/SynthesizingMethodParameter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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.annotation;
import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import org.springframework.core.MethodParameter;
@@ -34,7 +35,8 @@ import org.springframework.core.MethodParameter;
public class SynthesizingMethodParameter extends MethodParameter {
/**
- * Create a new {@code SynthesizingMethodParameter} for the given method.
+ * Create a new {@code SynthesizingMethodParameter} for the given method,
+ * with nesting level 1.
* @param method the Method to specify a parameter for
* @param parameterIndex the index of the parameter: -1 for the method
* return type; 0 for the first method parameter; 1 for the second method
@@ -44,6 +46,51 @@ public class SynthesizingMethodParameter extends MethodParameter {
super(method, parameterIndex);
}
+ /**
+ * Create a new {@code SynthesizingMethodParameter} for the given method.
+ * @param method the Method to specify a parameter for
+ * @param parameterIndex the index of the parameter: -1 for the method
+ * return type; 0 for the first method parameter; 1 for the second method
+ * parameter, etc.
+ * @param nestingLevel the nesting level of the target type
+ * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
+ * nested List, whereas 2 would indicate the element of the nested List)
+ */
+ public SynthesizingMethodParameter(Method method, int parameterIndex, int nestingLevel) {
+ super(method, parameterIndex, nestingLevel);
+ }
+
+ /**
+ * Create a new {@code SynthesizingMethodParameter} for the given constructor,
+ * with nesting level 1.
+ * @param constructor the Constructor to specify a parameter for
+ * @param parameterIndex the index of the parameter
+ */
+ public SynthesizingMethodParameter(Constructor<?> constructor, int parameterIndex) {
+ super(constructor, parameterIndex);
+ }
+
+ /**
+ * Create a new {@code SynthesizingMethodParameter} for the given constructor.
+ * @param constructor the Constructor to specify a parameter for
+ * @param parameterIndex the index of the parameter
+ * @param nestingLevel the nesting level of the target type
+ * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
+ * nested List, whereas 2 would indicate the element of the nested List)
+ */
+ public SynthesizingMethodParameter(Constructor<?> constructor, int parameterIndex, int nestingLevel) {
+ super(constructor, parameterIndex, nestingLevel);
+ }
+
+ /**
+ * Copy constructor, resulting in an independent {@code SynthesizingMethodParameter}
+ * based on the same metadata and cache state that the original object was in.
+ * @param original the original SynthesizingMethodParameter object to copy from
+ */
+ protected SynthesizingMethodParameter(SynthesizingMethodParameter original) {
+ super(original);
+ }
+
@Override
protected <A extends Annotation> A adaptAnnotation(A annotation) {
@@ -55,4 +102,10 @@ public class SynthesizingMethodParameter extends MethodParameter {
return AnnotationUtils.synthesizeAnnotationArray(annotations, getAnnotatedElement());
}
+
+ @Override
+ public SynthesizingMethodParameter clone() {
+ return new SynthesizingMethodParameter(this);
+ }
+
}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java b/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java
index dde18ce8..ffbbd7ab 100644
--- a/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java
+++ b/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java
@@ -343,7 +343,7 @@ public class TypeDescriptor implements Serializable {
if (streamAvailable && StreamDelegate.isStream(this.type)) {
return StreamDelegate.getStreamElementType(this);
}
- return getRelatedIfResolvable(this, this.resolvableType.asCollection().getGeneric());
+ return getRelatedIfResolvable(this, this.resolvableType.asCollection().getGeneric(0));
}
/**
@@ -467,7 +467,7 @@ public class TypeDescriptor implements Serializable {
return false;
}
for (Annotation ann : getAnnotations()) {
- if (!other.hasAnnotation(ann.annotationType())) {
+ if (!ann.equals(other.getAnnotation(ann.annotationType()))) {
return false;
}
}
@@ -706,7 +706,7 @@ public class TypeDescriptor implements Serializable {
}
public static TypeDescriptor getStreamElementType(TypeDescriptor source) {
- return getRelatedIfResolvable(source, source.resolvableType.as(Stream.class).getGeneric());
+ return getRelatedIfResolvable(source, source.resolvableType.as(Stream.class).getGeneric(0));
}
}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/converter/ConverterRegistry.java b/spring-core/src/main/java/org/springframework/core/convert/converter/ConverterRegistry.java
index 9280c4ea..7541d550 100644
--- a/spring-core/src/main/java/org/springframework/core/convert/converter/ConverterRegistry.java
+++ b/spring-core/src/main/java/org/springframework/core/convert/converter/ConverterRegistry.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2009 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,18 +27,19 @@ public interface ConverterRegistry {
/**
* Add a plain converter to this registry.
- * The convertible sourceType/targetType pair is derived from the Converter's parameterized types.
+ * The convertible source/target type pair is derived from the Converter's parameterized types.
* @throws IllegalArgumentException if the parameterized types could not be resolved
*/
void addConverter(Converter<?, ?> converter);
/**
* Add a plain converter to this registry.
- * The convertible sourceType/targetType pair is specified explicitly.
- * Allows for a Converter to be reused for multiple distinct pairs without having to create a Converter class for each pair.
+ * The convertible source/target type pair is specified explicitly.
+ * <p>Allows for a Converter to be reused for multiple distinct pairs without
+ * having to create a Converter class for each pair.
* @since 3.1
*/
- void addConverter(Class<?> sourceType, Class<?> targetType, Converter<?, ?> converter);
+ <S, T> void addConverter(Class<S> sourceType, Class<T> targetType, Converter<? super S, ? extends T> converter);
/**
* Add a generic converter to this registry.
@@ -47,7 +48,7 @@ public interface ConverterRegistry {
/**
* Add a ranged converter factory to this registry.
- * The convertible sourceType/rangeType pair is derived from the ConverterFactory's parameterized types.
+ * The convertible source/target type pair is derived from the ConverterFactory's parameterized types.
* @throws IllegalArgumentException if the parameterized types could not be resolved.
*/
void addConverterFactory(ConverterFactory<?, ?> converterFactory);
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/AbstractConditionalEnumConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/AbstractConditionalEnumConverter.java
new file mode 100644
index 00000000..e6c94f65
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/AbstractConditionalEnumConverter.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.core.convert.converter.ConditionalConverter;
+import org.springframework.util.ClassUtils;
+
+/**
+ * A {@link ConditionalConverter} base implementation for enum-based converters.
+ *
+ * @author Stephane Nicoll
+ * @since 4.3
+ */
+abstract class AbstractConditionalEnumConverter implements ConditionalConverter {
+
+ private final ConversionService conversionService;
+
+
+ protected AbstractConditionalEnumConverter(ConversionService conversionService) {
+ this.conversionService = conversionService;
+ }
+
+
+ @Override
+ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ for (Class<?> interfaceType : ClassUtils.getAllInterfacesForClass(sourceType.getType())) {
+ if (this.conversionService.canConvert(TypeDescriptor.valueOf(interfaceType), targetType)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/ConversionUtils.java b/spring-core/src/main/java/org/springframework/core/convert/support/ConversionUtils.java
index e93eabe9..1806afd2 100644
--- a/spring-core/src/main/java/org/springframework/core/convert/support/ConversionUtils.java
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/ConversionUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +25,7 @@ import org.springframework.core.convert.converter.GenericConverter;
* Internal utilities for the conversion package.
*
* @author Keith Donald
+ * @author Stephane Nicoll
* @since 3.0
*/
abstract class ConversionUtils {
@@ -65,4 +66,16 @@ abstract class ConversionUtils {
}
}
+ public static Class<?> getEnumType(Class<?> targetType) {
+ Class<?> enumType = targetType;
+ while (enumType != null && !enumType.isEnum()) {
+ enumType = enumType.getSuperclass();
+ }
+ if (enumType == null) {
+ throw new IllegalArgumentException(
+ "The target type " + targetType.getName() + " does not refer to an enum");
+ }
+ return enumType;
+ }
+
}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java b/spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java
index 408d1367..86df6337 100644
--- a/spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -141,8 +141,10 @@ public class DefaultConversionService extends GenericConversionService {
converterRegistry.addConverter(Boolean.class, String.class, new ObjectToStringConverter());
converterRegistry.addConverterFactory(new StringToEnumConverterFactory());
- converterRegistry.addConverter(Enum.class, String.class,
- new EnumToStringConverter((ConversionService) converterRegistry));
+ converterRegistry.addConverter(new EnumToStringConverter((ConversionService) converterRegistry));
+
+ converterRegistry.addConverterFactory(new IntegerToEnumConverterFactory());
+ converterRegistry.addConverter(new EnumToIntegerConverter((ConversionService) converterRegistry));
converterRegistry.addConverter(new StringToLocaleConverter());
converterRegistry.addConverter(Locale.class, String.class, new ObjectToStringConverter());
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/EnumToIntegerConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/EnumToIntegerConverter.java
new file mode 100644
index 00000000..0028ef54
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/EnumToIntegerConverter.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.converter.Converter;
+
+/**
+ * Calls {@link Enum#ordinal()} to convert a source Enum to a Integer.
+ * This converter will not match enums with interfaces that can be converted.
+ *
+ * @author Yanming Zhou
+ * @since 4.3
+ */
+final class EnumToIntegerConverter extends AbstractConditionalEnumConverter implements Converter<Enum<?>, Integer> {
+
+ public EnumToIntegerConverter(ConversionService conversionService) {
+ super(conversionService);
+ }
+
+ @Override
+ public Integer convert(Enum<?> source) {
+ return source.ordinal();
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/EnumToStringConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/EnumToStringConverter.java
index 02a5f19f..ccb06f51 100644
--- a/spring-core/src/main/java/org/springframework/core/convert/support/EnumToStringConverter.java
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/EnumToStringConverter.java
@@ -17,10 +17,7 @@
package org.springframework.core.convert.support;
import org.springframework.core.convert.ConversionService;
-import org.springframework.core.convert.TypeDescriptor;
-import org.springframework.core.convert.converter.ConditionalConverter;
import org.springframework.core.convert.converter.Converter;
-import org.springframework.util.ClassUtils;
/**
* Calls {@link Enum#name()} to convert a source Enum to a String.
@@ -30,24 +27,10 @@ import org.springframework.util.ClassUtils;
* @author Phillip Webb
* @since 3.0
*/
-final class EnumToStringConverter implements Converter<Enum<?>, String>, ConditionalConverter {
-
- private final ConversionService conversionService;
-
+final class EnumToStringConverter extends AbstractConditionalEnumConverter implements Converter<Enum<?>, String> {
public EnumToStringConverter(ConversionService conversionService) {
- this.conversionService = conversionService;
- }
-
-
- @Override
- public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
- for (Class<?> interfaceType : ClassUtils.getAllInterfacesForClass(sourceType.getType())) {
- if (this.conversionService.canConvert(TypeDescriptor.valueOf(interfaceType), targetType)) {
- return false;
- }
- }
- return true;
+ super(conversionService);
}
@Override
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java b/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java
index 52df6a77..e051e6cc 100644
--- a/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -103,7 +103,7 @@ public class GenericConversionService implements ConfigurableConversionService {
}
@Override
- public void addConverter(Class<?> sourceType, Class<?> targetType, Converter<?, ?> converter) {
+ public <S, T> void addConverter(Class<S> sourceType, Class<T> targetType, Converter<? super S, ? extends T> converter) {
addConverter(new ConverterAdapter(
converter, ResolvableType.forClass(sourceType), ResolvableType.forClass(targetType)));
}
@@ -435,7 +435,7 @@ public class GenericConversionService implements ConfigurableConversionService {
/**
* Key for use with the converter cache.
*/
- private static final class ConverterCacheKey {
+ private static final class ConverterCacheKey implements Comparable<ConverterCacheKey> {
private final TypeDescriptor sourceType;
@@ -470,6 +470,17 @@ public class GenericConversionService implements ConfigurableConversionService {
return ("ConverterCacheKey [sourceType = " + this.sourceType +
", targetType = " + this.targetType + "]");
}
+
+ @Override
+ public int compareTo(ConverterCacheKey other) {
+ int result = this.sourceType.getResolvableType().toString().compareTo(
+ other.sourceType.getResolvableType().toString());
+ if (result == 0) {
+ result = this.targetType.getResolvableType().toString().compareTo(
+ other.targetType.getResolvableType().toString());
+ }
+ return result;
+ }
}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/IntegerToEnumConverterFactory.java b/spring-core/src/main/java/org/springframework/core/convert/support/IntegerToEnumConverterFactory.java
new file mode 100644
index 00000000..03204e73
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/IntegerToEnumConverterFactory.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.core.convert.converter.ConverterFactory;
+
+/**
+ * Converts from a Integer to a {@link java.lang.Enum} by calling {@link Class#getEnumConstants()}.
+ *
+ * @author Yanming Zhou
+ * @author Stephane Nicoll
+ * @since 4.3
+ */
+@SuppressWarnings({"unchecked", "rawtypes"})
+final class IntegerToEnumConverterFactory implements ConverterFactory<Integer, Enum> {
+
+ @Override
+ public <T extends Enum> Converter<Integer, T> getConverter(Class<T> targetType) {
+ return new IntegerToEnum(ConversionUtils.getEnumType(targetType));
+ }
+
+
+ private class IntegerToEnum<T extends Enum> implements Converter<Integer, T> {
+
+ private final Class<T> enumType;
+
+ public IntegerToEnum(Class<T> enumType) {
+ this.enumType = enumType;
+ }
+
+ @Override
+ public T convert(Integer source) {
+ return this.enumType.getEnumConstants()[source];
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java
index fed1e45d..5631f2ca 100644
--- a/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -155,7 +155,7 @@ final class ObjectToObjectConverter implements ConditionalGenericConverter {
method.getParameterTypes()[0] == sourceClass);
}
else if (member instanceof Constructor) {
- Constructor<?> ctor = (Constructor) member;
+ Constructor<?> ctor = (Constructor<?>) member;
return (ctor.getParameterTypes()[0] == sourceClass);
}
else {
@@ -185,9 +185,6 @@ final class ObjectToObjectConverter implements ConditionalGenericConverter {
method = ClassUtils.getStaticMethod(targetClass, "of", sourceClass);
if (method == null) {
method = ClassUtils.getStaticMethod(targetClass, "from", sourceClass);
- if (method == null) {
- return null;
- }
}
}
return method;
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/StringToEnumConverterFactory.java b/spring-core/src/main/java/org/springframework/core/convert/support/StringToEnumConverterFactory.java
index 923239b9..865d1e83 100644
--- a/spring-core/src/main/java/org/springframework/core/convert/support/StringToEnumConverterFactory.java
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/StringToEnumConverterFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@ import org.springframework.core.convert.converter.ConverterFactory;
* Converts from a String to a {@link java.lang.Enum} by calling {@link Enum#valueOf(Class, String)}.
*
* @author Keith Donald
+ * @author Stephane Nicoll
* @since 3.0
*/
@SuppressWarnings({"unchecked", "rawtypes"})
@@ -30,15 +31,7 @@ final class StringToEnumConverterFactory implements ConverterFactory<String, Enu
@Override
public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
- Class<?> enumType = targetType;
- while (enumType != null && !enumType.isEnum()) {
- enumType = enumType.getSuperclass();
- }
- if (enumType == null) {
- throw new IllegalArgumentException(
- "The target type " + targetType.getName() + " does not refer to an enum");
- }
- return new StringToEnum(enumType);
+ return new StringToEnum(ConversionUtils.getEnumType(targetType));
}
diff --git a/spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java b/spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java
index c44f5d62..25268f84 100644
--- a/spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java
+++ b/spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -547,6 +547,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
}
@Override
+ @Deprecated
public <T> Class<T> getPropertyAsClass(String key, Class<T> targetType) {
return this.propertyResolver.getPropertyAsClass(key, targetType);
}
diff --git a/spring-core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java b/spring-core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java
index b5f18271..1c001d96 100644
--- a/spring-core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java
+++ b/spring-core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -131,6 +131,15 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
}
}
+ @Override
+ public boolean containsProperty(String key) {
+ return (getProperty(key) != null);
+ }
+
+ @Override
+ public String getProperty(String key) {
+ return getProperty(key, String.class);
+ }
@Override
public String getProperty(String key, String defaultValue) {
@@ -145,6 +154,12 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
}
@Override
+ @Deprecated
+ public <T> Class<T> getPropertyAsClass(String key, Class<T> targetValueType) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public String getRequiredProperty(String key) throws IllegalStateException {
String value = getProperty(key);
if (value == null) {
diff --git a/spring-core/src/main/java/org/springframework/core/env/ConfigurablePropertyResolver.java b/spring-core/src/main/java/org/springframework/core/env/ConfigurablePropertyResolver.java
index e1ec8a27..bc626d64 100644
--- a/spring-core/src/main/java/org/springframework/core/env/ConfigurablePropertyResolver.java
+++ b/spring-core/src/main/java/org/springframework/core/env/ConfigurablePropertyResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,10 +19,10 @@ package org.springframework.core.env;
import org.springframework.core.convert.support.ConfigurableConversionService;
/**
- * Configuration interface to be implemented by most if not all {@link PropertyResolver
- * PropertyResolver} types. Provides facilities for accessing and customizing the
- * {@link org.springframework.core.convert.ConversionService ConversionService} used when
- * converting property values from one type to another.
+ * Configuration interface to be implemented by most if not all {@link PropertyResolver}
+ * types. Provides facilities for accessing and customizing the
+ * {@link org.springframework.core.convert.ConversionService ConversionService}
+ * used when converting property values from one type to another.
*
* @author Chris Beams
* @since 3.1
@@ -30,7 +30,7 @@ import org.springframework.core.convert.support.ConfigurableConversionService;
public interface ConfigurablePropertyResolver extends PropertyResolver {
/**
- * @return the {@link ConfigurableConversionService} used when performing type
+ * Return the {@link ConfigurableConversionService} used when performing type
* conversions on properties.
* <p>The configurable nature of the returned conversion service allows for
* the convenient addition and removal of individual {@code Converter} instances:
@@ -46,10 +46,10 @@ public interface ConfigurablePropertyResolver extends PropertyResolver {
/**
* Set the {@link ConfigurableConversionService} to be used when performing type
* conversions on properties.
- * <p><strong>Note:</strong> as an alternative to fully replacing the {@code
- * ConversionService}, consider adding or removing individual {@code Converter}
- * instances by drilling into {@link #getConversionService()} and calling methods
- * such as {@code #addConverter}.
+ * <p><strong>Note:</strong> as an alternative to fully replacing the
+ * {@code ConversionService}, consider adding or removing individual
+ * {@code Converter} instances by drilling into {@link #getConversionService()}
+ * and calling methods such as {@code #addConverter}.
* @see PropertyResolver#getProperty(String, Class)
* @see #getConversionService()
* @see org.springframework.core.convert.converter.ConverterRegistry#addConverter
diff --git a/spring-core/src/main/java/org/springframework/core/env/PropertyResolver.java b/spring-core/src/main/java/org/springframework/core/env/PropertyResolver.java
index 17570f77..b7b89930 100644
--- a/spring-core/src/main/java/org/springframework/core/env/PropertyResolver.java
+++ b/spring-core/src/main/java/org/springframework/core/env/PropertyResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ package org.springframework.core.env;
* Interface for resolving properties against any underlying source.
*
* @author Chris Beams
+ * @author Juergen Hoeller
* @since 3.1
* @see Environment
* @see PropertySourcesPropertyResolver
@@ -27,14 +28,14 @@ package org.springframework.core.env;
public interface PropertyResolver {
/**
- * Return whether the given property key is available for resolution, i.e.,
- * the value for the given key is not {@code null}.
+ * Return whether the given property key is available for resolution,
+ * i.e. if the value for the given key is not {@code null}.
*/
boolean containsProperty(String key);
/**
- * Return the property value associated with the given key, or {@code null}
- * if the key cannot be resolved.
+ * Return the property value associated with the given key,
+ * or {@code null} if the key cannot be resolved.
* @param key the property name to resolve
* @see #getProperty(String, String)
* @see #getProperty(String, Class)
@@ -53,8 +54,8 @@ public interface PropertyResolver {
String getProperty(String key, String defaultValue);
/**
- * Return the property value associated with the given key, or {@code null}
- * if the key cannot be resolved.
+ * Return the property value associated with the given key,
+ * or {@code null} if the key cannot be resolved.
* @param key the property name to resolve
* @param targetType the expected type of the property value
* @see #getRequiredProperty(String, Class)
@@ -62,8 +63,8 @@ public interface PropertyResolver {
<T> T getProperty(String key, Class<T> targetType);
/**
- * Return the property value associated with the given key, or
- * {@code defaultValue} if the key cannot be resolved.
+ * Return the property value associated with the given key,
+ * or {@code defaultValue} if the key cannot be resolved.
* @param key the property name to resolve
* @param targetType the expected type of the property value
* @param defaultValue the default value to return if no value is found
@@ -75,10 +76,13 @@ public interface PropertyResolver {
* Convert the property value associated with the given key to a {@code Class}
* of type {@code T} or {@code null} if the key cannot be resolved.
* @throws org.springframework.core.convert.ConversionException if class specified
- * by property value cannot be found or loaded or if targetType is not assignable
+ * by property value cannot be found or loaded or if targetType is not assignable
* from class specified by property value
* @see #getProperty(String, Class)
+ * @deprecated as of 4.3, in favor of {@link #getProperty} with manual conversion
+ * to {@code Class} via the application's {@code ClassLoader}
*/
+ @Deprecated
<T> Class<T> getPropertyAsClass(String key, Class<T> targetType);
/**
diff --git a/spring-core/src/main/java/org/springframework/core/env/PropertySourcesPropertyResolver.java b/spring-core/src/main/java/org/springframework/core/env/PropertySourcesPropertyResolver.java
index edf0b729..32462043 100644
--- a/spring-core/src/main/java/org/springframework/core/env/PropertySourcesPropertyResolver.java
+++ b/spring-core/src/main/java/org/springframework/core/env/PropertySourcesPropertyResolver.java
@@ -24,6 +24,7 @@ import org.springframework.util.ClassUtils;
* an underlying set of {@link PropertySources}.
*
* @author Chris Beams
+ * @author Juergen Hoeller
* @since 3.1
* @see PropertySource
* @see PropertySources
@@ -71,56 +72,38 @@ public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {
}
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
- boolean debugEnabled = logger.isDebugEnabled();
- if (logger.isTraceEnabled()) {
- logger.trace(String.format("getProperty(\"%s\", %s)", key, targetValueType.getSimpleName()));
- }
if (this.propertySources != null) {
for (PropertySource<?> propertySource : this.propertySources) {
- if (debugEnabled) {
- logger.debug(String.format("Searching for key '%s' in [%s]", key, propertySource.getName()));
+ if (logger.isTraceEnabled()) {
+ logger.trace(String.format("Searching for key '%s' in [%s]", key, propertySource.getName()));
}
Object value = propertySource.getProperty(key);
if (value != null) {
- Class<?> valueType = value.getClass();
if (resolveNestedPlaceholders && value instanceof String) {
value = resolveNestedPlaceholders((String) value);
}
- if (debugEnabled) {
- logger.debug(String.format("Found key '%s' in [%s] with type [%s] and value '%s'",
- key, propertySource.getName(), valueType.getSimpleName(), value));
- }
- if (!this.conversionService.canConvert(valueType, targetValueType)) {
- throw new IllegalArgumentException(String.format(
- "Cannot convert value [%s] from source type [%s] to target type [%s]",
- value, valueType.getSimpleName(), targetValueType.getSimpleName()));
- }
+ logKeyFound(key, propertySource, value);
return this.conversionService.convert(value, targetValueType);
}
}
}
- if (debugEnabled) {
- logger.debug(String.format("Could not find key '%s' in any property source. Returning [null]", key));
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Could not find key '%s' in any property source", key));
}
return null;
}
@Override
+ @Deprecated
public <T> Class<T> getPropertyAsClass(String key, Class<T> targetValueType) {
- boolean debugEnabled = logger.isDebugEnabled();
- if (logger.isTraceEnabled()) {
- logger.trace(String.format("getPropertyAsClass(\"%s\", %s)", key, targetValueType.getSimpleName()));
- }
if (this.propertySources != null) {
for (PropertySource<?> propertySource : this.propertySources) {
- if (debugEnabled) {
- logger.debug(String.format("Searching for key '%s' in [%s]", key, propertySource.getName()));
+ if (logger.isTraceEnabled()) {
+ logger.trace(String.format("Searching for key '%s' in [%s]", key, propertySource.getName()));
}
Object value = propertySource.getProperty(key);
if (value != null) {
- if (debugEnabled) {
- logger.debug(String.format("Found key '%s' in [%s] with value '%s'", key, propertySource.getName(), value));
- }
+ logKeyFound(key, propertySource, value);
Class<?> clazz;
if (value instanceof String) {
try {
@@ -131,7 +114,7 @@ public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {
}
}
else if (value instanceof Class) {
- clazz = (Class<?>)value;
+ clazz = (Class<?>) value;
}
else {
clazz = value.getClass();
@@ -145,22 +128,42 @@ public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {
}
}
}
- if (debugEnabled) {
- logger.debug(String.format("Could not find key '%s' in any property source. Returning [null]", key));
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Could not find key '%s' in any property source", key));
}
return null;
}
+ /**
+ * Log the given key as found in the given {@link PropertySource}, resulting in
+ * the given value.
+ * <p>The default implementation writes a debug log message, including the value.
+ * Subclasses may override this to change the log level and/or the log message.
+ * @param key the key found
+ * @param propertySource the {@code PropertySource} that the key has been found in
+ * @param value the corresponding value
+ * @since 4.3.1
+ */
+ protected void logKeyFound(String key, PropertySource<?> propertySource, Object value) {
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Found key '%s' in [%s] with type [%s] and value '%s'",
+ key, propertySource.getName(), value.getClass().getSimpleName(), value));
+ }
+ }
+
@SuppressWarnings("serial")
+ @Deprecated
private static class ClassConversionException extends ConversionException {
public ClassConversionException(Class<?> actual, Class<?> expected) {
- super(String.format("Actual type %s is not assignable to expected type %s", actual.getName(), expected.getName()));
+ super(String.format("Actual type %s is not assignable to expected type %s",
+ actual.getName(), expected.getName()));
}
public ClassConversionException(String actual, Class<?> expected, Exception ex) {
- super(String.format("Could not find/load class %s during attempt to convert to %s", actual, expected.getName()), ex);
+ super(String.format("Could not find/load class %s during attempt to convert to %s",
+ actual, expected.getName()), ex);
}
}
diff --git a/spring-core/src/main/java/org/springframework/core/env/SystemEnvironmentPropertySource.java b/spring-core/src/main/java/org/springframework/core/env/SystemEnvironmentPropertySource.java
index d60d3b50..d72a822e 100644
--- a/spring-core/src/main/java/org/springframework/core/env/SystemEnvironmentPropertySource.java
+++ b/spring-core/src/main/java/org/springframework/core/env/SystemEnvironmentPropertySource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,8 +24,8 @@ import org.springframework.util.Assert;
* Specialization of {@link MapPropertySource} designed for use with
* {@linkplain AbstractEnvironment#getSystemEnvironment() system environment variables}.
* Compensates for constraints in Bash and other shells that do not allow for variables
- * containing the period character; also allows for uppercase variations on property
- * names for more idiomatic shell use.
+ * containing the period character and/or hyphen character; also allows for uppercase
+ * variations on property names for more idiomatic shell use.
*
* <p>For example, a call to {@code getProperty("foo.bar")} will attempt to find a value
* for the original property or any 'equivalent' property, returning the first found:
@@ -35,8 +35,9 @@ import org.springframework.util.Assert;
* <li>{@code FOO.BAR} - original, with upper case</li>
* <li>{@code FOO_BAR} - with underscores and upper case</li>
* </ul>
+ * Any hyphen variant of the above would work as well, or even mix dot/hyphen variants.
*
- * The same applies for calls to {@link #containsProperty(String)}, which returns
+ * <p>The same applies for calls to {@link #containsProperty(String)}, which returns
* {@code true} if any of the above properties are present, otherwise {@code false}.
*
* <p>This feature is particularly useful when specifying active or default profiles as
@@ -102,29 +103,42 @@ public class SystemEnvironmentPropertySource extends MapPropertySource {
*/
private String resolvePropertyName(String name) {
Assert.notNull(name, "Property name must not be null");
+ String resolvedName = checkPropertyName(name);
+ if (resolvedName != null) {
+ return resolvedName;
+ }
+ String uppercasedName = name.toUpperCase();
+ if (!name.equals(uppercasedName)) {
+ resolvedName = checkPropertyName(uppercasedName);
+ if (resolvedName != null) {
+ return resolvedName;
+ }
+ }
+ return name;
+ }
+
+ private String checkPropertyName(String name) {
+ // Check name as-is
if (containsKey(name)) {
return name;
}
-
- String usName = name.replace('.', '_');
- if (!name.equals(usName) && containsKey(usName)) {
- return usName;
+ // Check name with just dots replaced
+ String noDotName = name.replace('.', '_');
+ if (!name.equals(noDotName) && containsKey(noDotName)) {
+ return noDotName;
}
-
- String ucName = name.toUpperCase();
- if (!name.equals(ucName)) {
- if (containsKey(ucName)) {
- return ucName;
- }
- else {
- String usUcName = ucName.replace('.', '_');
- if (!ucName.equals(usUcName) && containsKey(usUcName)) {
- return usUcName;
- }
- }
+ // Check name with just hyphens replaced
+ String noHyphenName = name.replace('-', '_');
+ if (!name.equals(noHyphenName) && containsKey(noHyphenName)) {
+ return noHyphenName;
}
-
- return name;
+ // Check name with dots and hyphens replaced
+ String noDotNoHyphenName = noDotName.replace('-', '_');
+ if (!noDotName.equals(noDotNoHyphenName) && containsKey(noDotNoHyphenName)) {
+ return noDotNoHyphenName;
+ }
+ // Give up
+ return null;
}
private boolean containsKey(String name) {
diff --git a/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java b/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java
index a81f6c38..8f61f36b 100644
--- a/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java
+++ b/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -72,7 +72,7 @@ public abstract class AbstractFileResolvingResource extends AbstractResource {
}
/**
- * This implementation returns a File reference for the underlying class path
+ * This implementation returns a File reference for the given URI-identified
* resource, provided that it refers to a file in the file system.
* @see org.springframework.util.ResourceUtils#getFile(java.net.URI, String)
*/
diff --git a/spring-core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java b/spring-core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java
index 2f22b14c..b7c5219f 100644
--- a/spring-core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java
+++ b/spring-core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,9 @@ package org.springframework.core.io;
import java.net.MalformedURLException;
import java.net.URL;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Set;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -42,6 +45,8 @@ public class DefaultResourceLoader implements ResourceLoader {
private ClassLoader classLoader;
+ private final Set<ProtocolResolver> protocolResolvers = new LinkedHashSet<ProtocolResolver>(4);
+
/**
* Create a new DefaultResourceLoader.
@@ -84,10 +89,40 @@ public class DefaultResourceLoader implements ResourceLoader {
return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());
}
+ /**
+ * Register the given resolver with this resource loader, allowing for
+ * additional protocols to be handled.
+ * <p>Any such resolver will be invoked ahead of this loader's standard
+ * resolution rules. It may therefore also override any default rules.
+ * @since 4.3
+ * @see #getProtocolResolvers()
+ */
+ public void addProtocolResolver(ProtocolResolver resolver) {
+ Assert.notNull(resolver, "ProtocolResolver must not be null");
+ this.protocolResolvers.add(resolver);
+ }
+
+ /**
+ * Return the collection of currently registered protocol resolvers,
+ * allowing for introspection as well as modification.
+ * @since 4.3
+ */
+ public Collection<ProtocolResolver> getProtocolResolvers() {
+ return this.protocolResolvers;
+ }
+
@Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
+
+ for (ProtocolResolver protocolResolver : this.protocolResolvers) {
+ Resource resource = protocolResolver.resolve(location, this);
+ if (resource != null) {
+ return resource;
+ }
+ }
+
if (location.startsWith("/")) {
return getResourceByPath(location);
}
diff --git a/spring-core/src/main/java/org/springframework/core/io/FileSystemResource.java b/spring-core/src/main/java/org/springframework/core/io/FileSystemResource.java
index 294a3e37..9179d921 100644
--- a/spring-core/src/main/java/org/springframework/core/io/FileSystemResource.java
+++ b/spring-core/src/main/java/org/springframework/core/io/FileSystemResource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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 @@ import org.springframework.util.StringUtils;
/**
* {@link Resource} implementation for {@code java.io.File} handles.
- * Obviously supports resolution as File, and also as URL.
+ * Supports resolution as a {@code File} and also as a {@code URL}.
* Implements the extended {@link WritableResource} interface.
*
* @author Juergen Hoeller
@@ -85,7 +85,6 @@ public class FileSystemResource extends AbstractResource implements WritableReso
return this.path;
}
-
/**
* This implementation returns whether the underlying file exists.
* @see java.io.File#exists()
diff --git a/spring-core/src/main/java/org/springframework/core/io/PathResource.java b/spring-core/src/main/java/org/springframework/core/io/PathResource.java
index f84ccfa9..af046049 100644
--- a/spring-core/src/main/java/org/springframework/core/io/PathResource.java
+++ b/spring-core/src/main/java/org/springframework/core/io/PathResource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -182,8 +182,8 @@ public class PathResource extends AbstractResource implements WritableResource {
return this.path.toFile();
}
catch (UnsupportedOperationException ex) {
- // only Paths on the default file system can be converted to a File
- // do exception translation for cases where conversion is not possible
+ // Only paths on the default file system can be converted to a File:
+ // Do exception translation for cases where conversion is not possible.
throw new FileNotFoundException(this.path + " cannot be resolved to " + "absolute file path");
}
}
diff --git a/spring-core/src/main/java/org/springframework/core/io/ProtocolResolver.java b/spring-core/src/main/java/org/springframework/core/io/ProtocolResolver.java
new file mode 100644
index 00000000..700c866e
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/ProtocolResolver.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.io;
+
+/**
+ * A resolution strategy for protocol-specific resource handles.
+ *
+ * <p>Used as an SPI for {@link DefaultResourceLoader}, allowing for
+ * custom protocols to be handled without subclassing the loader
+ * implementation (or application context implementation).
+ *
+ * @author Juergen Hoeller
+ * @since 4.3
+ * @see DefaultResourceLoader#addProtocolResolver
+ */
+public interface ProtocolResolver {
+
+ /**
+ * Resolve the given location against the given resource loader
+ * if this implementation's protocol matches.
+ * @param location the user-specified resource location
+ * @param resourceLoader the associated resource loader
+ * @return a corresponding {@code Resource} handle if the given location
+ * matches this resolver's protocol, or {@code null} otherwise
+ */
+ Resource resolve(String location, ResourceLoader resourceLoader);
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/Resource.java b/spring-core/src/main/java/org/springframework/core/io/Resource.java
index e3fc2a4e..ac4fcb29 100644
--- a/spring-core/src/main/java/org/springframework/core/io/Resource.java
+++ b/spring-core/src/main/java/org/springframework/core/io/Resource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author 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,26 +37,26 @@ import java.net.URL;
* @see #getFile()
* @see WritableResource
* @see ContextResource
- * @see FileSystemResource
- * @see ClassPathResource
* @see UrlResource
+ * @see ClassPathResource
+ * @see FileSystemResource
+ * @see PathResource
* @see ByteArrayResource
* @see InputStreamResource
- * @see PathResource
*/
public interface Resource extends InputStreamSource {
/**
- * Return whether this resource actually exists in physical form.
+ * Determine whether this resource actually exists in physical form.
* <p>This method performs a definitive existence check, whereas the
- * existence of a {@code Resource} handle only guarantees a
- * valid descriptor handle.
+ * existence of a {@code Resource} handle only guarantees a valid
+ * descriptor handle.
*/
boolean exists();
/**
- * Return whether the contents of this resource can be read,
- * e.g. via {@link #getInputStream()} or {@link #getFile()}.
+ * Indicate whether the contents of this resource can be read via
+ * {@link #getInputStream()}.
* <p>Will be {@code true} for typical resource descriptors;
* note that actual content reading may still fail when attempted.
* However, a value of {@code false} is a definitive indication
@@ -66,8 +66,8 @@ public interface Resource extends InputStreamSource {
boolean isReadable();
/**
- * Return whether this resource represents a handle with an open
- * stream. If true, the InputStream cannot be read multiple times,
+ * Indicate whether this resource represents a handle with an open stream.
+ * If {@code true}, the InputStream cannot be read multiple times,
* and must be read and closed to avoid resource leaks.
* <p>Will be {@code false} for typical resource descriptors.
*/
@@ -84,6 +84,7 @@ public interface Resource extends InputStreamSource {
* Return a URI handle for this resource.
* @throws IOException if the resource cannot be resolved as URI,
* i.e. if the resource is not available as descriptor
+ * @since 2.5
*/
URI getURI() throws IOException;
diff --git a/spring-core/src/main/java/org/springframework/core/io/UrlResource.java b/spring-core/src/main/java/org/springframework/core/io/UrlResource.java
index 41aa4a03..584b67f6 100644
--- a/spring-core/src/main/java/org/springframework/core/io/UrlResource.java
+++ b/spring-core/src/main/java/org/springframework/core/io/UrlResource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,7 +32,7 @@ import org.springframework.util.StringUtils;
/**
* {@link Resource} implementation for {@code java.net.URL} locators.
- * <p>Supports resolution as a {@code URL} and also as a {@code File} in
+ * Supports resolution as a {@code URL} and also as a {@code File} in
* case of the {@code "file:"} protocol.
*
* @author Juergen Hoeller
@@ -61,6 +61,7 @@ public class UrlResource extends AbstractFileResolvingResource {
* Create a new {@code UrlResource} based on the given URI object.
* @param uri a URI
* @throws MalformedURLException if the given URL path is not valid
+ * @since 2.5
*/
public UrlResource(URI uri) throws MalformedURLException {
Assert.notNull(uri, "URI must not be null");
@@ -133,6 +134,7 @@ public class UrlResource extends AbstractFileResolvingResource {
}
}
+
/**
* Determine a cleaned URL for the given original URL.
* @param originalUrl the original URL
@@ -151,7 +153,6 @@ public class UrlResource extends AbstractFileResolvingResource {
}
}
-
/**
* This implementation opens an InputStream for the given URL.
* <p>It sets the {@code useCaches} flag to {@code false},
diff --git a/spring-core/src/main/java/org/springframework/core/io/support/DefaultPropertySourceFactory.java b/spring-core/src/main/java/org/springframework/core/io/support/DefaultPropertySourceFactory.java
new file mode 100644
index 00000000..5ff4c9df
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/support/DefaultPropertySourceFactory.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.io.support;
+
+import java.io.IOException;
+
+import org.springframework.core.env.PropertySource;
+
+/**
+ * The default implementation for {@link PropertySourceFactory},
+ * wrapping every resource in a {@link ResourcePropertySource}.
+ *
+ * @author Juergen Hoeller
+ * @since 4.3
+ * @see PropertySourceFactory
+ * @see ResourcePropertySource
+ */
+public class DefaultPropertySourceFactory implements PropertySourceFactory {
+
+ @Override
+ public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
+ return (name != null ? new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource));
+ }
+
+}
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 d69433b1..0e049403 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
@@ -26,6 +26,7 @@ import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashSet;
@@ -144,6 +145,9 @@ import org.springframework.util.StringUtils;
* root of expanded directories. This originates from a limitation in the JDK's
* {@code ClassLoader.getResources()} method which only returns file system
* locations for a passed-in empty String (indicating potential roots to search).
+ * This {@code ResourcePatternResolver} implementation is trying to mitigate the
+ * jar root lookup limitation through {@link URLClassLoader} introspection and
+ * "java.class.path" manifest evaluation; however, without portability guarantees.
*
* <p><b>WARNING:</b> Ant-style patterns with "classpath:" resources are not
* guaranteed to find matching resources if the root package to search is available
@@ -166,6 +170,7 @@ import org.springframework.util.StringUtils;
* @author Colin Sampaleanu
* @author Marius Bogoevici
* @author Costin Leau
+ * @author Phil Webb
* @since 1.0.2
* @see #CLASSPATH_ALL_URL_PREFIX
* @see org.springframework.util.AntPathMatcher
@@ -384,6 +389,12 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
}
}
}
+
+ if (classLoader == ClassLoader.getSystemClassLoader()) {
+ // "java.class.path" manifest evaluation...
+ addClassPathManifestEntries(result);
+ }
+
if (classLoader != null) {
try {
// Hierarchy traversal...
@@ -399,6 +410,41 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
}
/**
+ * Determine jar file references from the "java.class.path." manifest property and add them
+ * to the given set of resources in the form of pointers to the root of the jar file content.
+ * @param result the set of resources to add jar roots to
+ * @since 4.3
+ */
+ protected void addClassPathManifestEntries(Set<Resource> result) {
+ try {
+ String javaClassPathProperty = System.getProperty("java.class.path");
+ for (String url : StringUtils.delimitedListToStringArray(
+ javaClassPathProperty, System.getProperty("path.separator"))) {
+ try {
+ if (url.endsWith(ResourceUtils.JAR_FILE_EXTENSION)) {
+ UrlResource jarResource = new UrlResource(ResourceUtils.JAR_URL_PREFIX +
+ ResourceUtils.FILE_URL_PREFIX + url + ResourceUtils.JAR_URL_SEPARATOR);
+ if (jarResource.exists()) {
+ result.add(jarResource);
+ }
+ }
+ }
+ catch (MalformedURLException ex) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Cannot search for matching files underneath [" + url +
+ "] because it cannot be converted to a valid 'jar:' URL: " + ex.getMessage());
+ }
+ }
+ }
+ }
+ catch (Exception ex) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Failed to evaluate 'java.class.path' manifest entries: " + ex);
+ }
+ }
+ }
+
+ /**
* Find all resources that match the given location pattern via the
* Ant-style PathMatcher. Supports resources in jar files and zip files
* and in the file system.
@@ -416,11 +462,18 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
Set<Resource> result = new LinkedHashSet<Resource>(16);
for (Resource rootDirResource : rootDirResources) {
rootDirResource = resolveRootDirResource(rootDirResource);
- if (rootDirResource.getURL().getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
- result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern, getPathMatcher()));
+ URL rootDirURL = rootDirResource.getURL();
+ if (equinoxResolveMethod != null) {
+ if (rootDirURL.getProtocol().startsWith("bundle")) {
+ rootDirURL = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirURL);
+ rootDirResource = new UrlResource(rootDirURL);
+ }
}
- else if (isJarResource(rootDirResource)) {
- result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern));
+ if (rootDirURL.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
+ result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirURL, subPattern, getPathMatcher()));
+ }
+ else if (ResourceUtils.isJarURL(rootDirURL) || isJarResource(rootDirResource)) {
+ result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirURL, subPattern));
}
else {
result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
@@ -458,56 +511,61 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
/**
* Resolve the specified resource for path matching.
- * <p>The default implementation detects an Equinox OSGi "bundleresource:"
- * / "bundleentry:" URL and resolves it into a standard jar file URL that
- * can be traversed using Spring's standard jar file traversal algorithm.
+ * <p>By default, Equinox OSGi "bundleresource:" / "bundleentry:" URL will be
+ * resolved into a standard jar file URL that be traversed using Spring's
+ * standard jar file traversal algorithm. For any preceding custom resolution,
+ * override this method and replace the resource handle accordingly.
* @param original the resource to resolve
* @return the resolved resource (may be identical to the passed-in resource)
* @throws IOException in case of resolution failure
*/
protected Resource resolveRootDirResource(Resource original) throws IOException {
- if (equinoxResolveMethod != null) {
- URL url = original.getURL();
- if (url.getProtocol().startsWith("bundle")) {
- return new UrlResource((URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, url));
- }
- }
return original;
}
/**
* Return whether the given resource handle indicates a jar resource
* that the {@code doFindPathMatchingJarResources} method can handle.
- * <p>The default implementation checks against the URL protocols
- * "jar", "zip" and "wsjar" (the latter are used by BEA WebLogic Server
- * and IBM WebSphere, respectively, but can be treated like jar files).
+ * <p>By default, the URL protocols "jar", "zip", "vfszip and "wsjar"
+ * will be treated as jar resources. This template method allows for
+ * detecting further kinds of jar-like resources, e.g. through
+ * {@code instanceof} checks on the resource handle type.
* @param resource the resource handle to check
* (usually the root directory to start path matching from)
* @see #doFindPathMatchingJarResources
* @see org.springframework.util.ResourceUtils#isJarURL
*/
protected boolean isJarResource(Resource resource) throws IOException {
- return ResourceUtils.isJarURL(resource.getURL());
+ return false;
}
/**
* Find all resources in jar files that match the given location pattern
* via the Ant-style PathMatcher.
* @param rootDirResource the root directory as Resource
+ * @param rootDirURL the pre-resolved root directory URL
* @param subPattern the sub pattern to match (below the root directory)
* @return a mutable Set of matching Resource instances
* @throws IOException in case of I/O errors
+ * @since 4.3
* @see java.net.JarURLConnection
* @see org.springframework.util.PathMatcher
*/
- protected Set<Resource> doFindPathMatchingJarResources(Resource rootDirResource, String subPattern)
+ @SuppressWarnings("deprecation")
+ protected Set<Resource> doFindPathMatchingJarResources(Resource rootDirResource, URL rootDirURL, String subPattern)
throws IOException {
- URLConnection con = rootDirResource.getURL().openConnection();
+ // Check deprecated variant for potential overriding first...
+ Set<Resource> result = doFindPathMatchingJarResources(rootDirResource, subPattern);
+ if (result != null) {
+ return result;
+ }
+
+ URLConnection con = rootDirURL.openConnection();
JarFile jarFile;
String jarFileUrl;
String rootEntryPath;
- boolean newJarFile = false;
+ boolean closeJarFile;
if (con instanceof JarURLConnection) {
// Should usually be the case for traditional JAR files.
@@ -517,13 +575,14 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
jarFileUrl = jarCon.getJarFileURL().toExternalForm();
JarEntry jarEntry = jarCon.getJarEntry();
rootEntryPath = (jarEntry != null ? jarEntry.getName() : "");
+ closeJarFile = !jarCon.getUseCaches();
}
else {
// No JarURLConnection -> need to resort to URL file parsing.
// We'll assume URLs of the format "jar:path!/entry", with the protocol
// being arbitrary as long as following the entry format.
// We'll also handle paths with and without leading "file:" prefix.
- String urlFile = rootDirResource.getURL().getFile();
+ String urlFile = rootDirURL.getFile();
try {
int separatorIndex = urlFile.indexOf(ResourceUtils.JAR_URL_SEPARATOR);
if (separatorIndex != -1) {
@@ -536,7 +595,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
jarFileUrl = urlFile;
rootEntryPath = "";
}
- newJarFile = true;
+ closeJarFile = true;
}
catch (ZipException ex) {
if (logger.isDebugEnabled()) {
@@ -555,7 +614,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
// The Sun JRE does not return a slash here, but BEA JRockit does.
rootEntryPath = rootEntryPath + "/";
}
- Set<Resource> result = new LinkedHashSet<Resource>(8);
+ result = new LinkedHashSet<Resource>(8);
for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) {
JarEntry entry = entries.nextElement();
String entryPath = entry.getName();
@@ -569,15 +628,30 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
return result;
}
finally {
- // Close jar file, but only if freshly obtained -
- // not from JarURLConnection, which might cache the file reference.
- if (newJarFile) {
+ if (closeJarFile) {
jarFile.close();
}
}
}
/**
+ * Find all resources in jar files that match the given location pattern
+ * via the Ant-style PathMatcher.
+ * @param rootDirResource the root directory as Resource
+ * @param subPattern the sub pattern to match (below the root directory)
+ * @return a mutable Set of matching Resource instances
+ * @throws IOException in case of I/O errors
+ * @deprecated as of Spring 4.3, in favor of
+ * {@link #doFindPathMatchingJarResources(Resource, URL, String)}
+ */
+ @Deprecated
+ protected Set<Resource> doFindPathMatchingJarResources(Resource rootDirResource, String subPattern)
+ throws IOException {
+
+ return null;
+ }
+
+ /**
* Resolve the given jar file URL into a JarFile object.
*/
protected JarFile getJarFile(String jarFileUrl) throws IOException {
@@ -706,6 +780,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
}
return;
}
+ Arrays.sort(dirContents);
for (File content : dirContents) {
String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");
if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {
@@ -732,8 +807,9 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
private static class VfsResourceMatchingDelegate {
public static Set<Resource> findMatchingResources(
- Resource rootResource, String locationPattern, PathMatcher pathMatcher) throws IOException {
- Object root = VfsPatternUtils.findRoot(rootResource.getURL());
+ URL rootDirURL, String locationPattern, PathMatcher pathMatcher) throws IOException {
+
+ Object root = VfsPatternUtils.findRoot(rootDirURL);
PatternVirtualFileVisitor visitor =
new PatternVirtualFileVisitor(VfsPatternUtils.getPath(root), locationPattern, pathMatcher);
VfsPatternUtils.visit(root, visitor);
diff --git a/spring-core/src/main/java/org/springframework/core/io/support/PropertySourceFactory.java b/spring-core/src/main/java/org/springframework/core/io/support/PropertySourceFactory.java
new file mode 100644
index 00000000..4ab2cfdf
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/support/PropertySourceFactory.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.io.support;
+
+import java.io.IOException;
+
+import org.springframework.core.env.PropertySource;
+
+/**
+ * Strategy interface for creating resource-based {@link PropertySource} wrappers.
+ *
+ * @author Juergen Hoeller
+ * @since 4.3
+ * @see DefaultPropertySourceFactory
+ */
+public interface PropertySourceFactory {
+
+ /**
+ * Create a {@link PropertySource} that wraps the given resource.
+ * @param name the name of the property source
+ * @param resource the resource (potentially encoded) to wrap
+ * @return the new {@link PropertySource} (never {@code null})
+ * @throws IOException if resource resolution failed
+ */
+ PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException;
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/support/ResourcePatternUtils.java b/spring-core/src/main/java/org/springframework/core/io/support/ResourcePatternUtils.java
index 9c0c12ee..e9721a95 100644
--- a/spring-core/src/main/java/org/springframework/core/io/support/ResourcePatternUtils.java
+++ b/spring-core/src/main/java/org/springframework/core/io/support/ResourcePatternUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,12 +17,11 @@
package org.springframework.core.io.support;
import org.springframework.core.io.ResourceLoader;
-import org.springframework.util.Assert;
import org.springframework.util.ResourceUtils;
/**
* Utility class for determining whether a given URL is a resource
- * location that can be loaded via a ResourcePatternResolver.
+ * location that can be loaded via a {@link ResourcePatternResolver}.
*
* <p>Callers will usually assume that a location is a relative path
* if the {@link #isUrl(String)} method returns {@code false}.
@@ -59,7 +58,6 @@ public abstract class ResourcePatternUtils {
* @see PathMatchingResourcePatternResolver
*/
public static ResourcePatternResolver getResourcePatternResolver(ResourceLoader resourceLoader) {
- Assert.notNull(resourceLoader, "ResourceLoader must not be null");
if (resourceLoader instanceof ResourcePatternResolver) {
return (ResourcePatternResolver) resourceLoader;
}
diff --git a/spring-core/src/main/java/org/springframework/core/io/support/ResourceRegion.java b/spring-core/src/main/java/org/springframework/core/io/support/ResourceRegion.java
new file mode 100644
index 00000000..a76be606
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/support/ResourceRegion.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.io.support;
+
+import org.springframework.core.io.Resource;
+import org.springframework.util.Assert;
+
+/**
+ * Region of a {@link Resource} implementation, materialized by a {@code position}
+ * within the {@link Resource} and a byte {@code count} for the length of that region.
+ *
+ * @author Arjen Poutsma
+ * @since 4.3
+ */
+public class ResourceRegion {
+
+ private final Resource resource;
+
+ private final long position;
+
+ private final long count;
+
+
+ /**
+ * Create a new {@code ResourceRegion} from a given {@link Resource}.
+ * This region of a resource is represented by a start {@code position}
+ * and a byte {@code count} within the given {@code Resource}.
+ * @param resource a Resource
+ * @param position the start position of the region in that resource
+ * @param count the byte count of the region in that resource
+ */
+ public ResourceRegion(Resource resource, long position, long count) {
+ Assert.notNull(resource, "Resource must not be null");
+ Assert.isTrue(position >= 0, "'position' must be larger than or equal to 0");
+ Assert.isTrue(count >= 0, "'count' must be larger than or equal to 0");
+ this.resource = resource;
+ this.position = position;
+ this.count = count;
+ }
+
+
+ /**
+ * Return the underlying {@link Resource} for this {@code ResourceRegion}
+ */
+ public Resource getResource() {
+ return this.resource;
+ }
+
+ /**
+ * Return the start position of this region in the underlying {@link Resource}
+ */
+ public long getPosition() {
+ return this.position;
+ }
+
+ /**
+ * Return the byte count of this region in the underlying {@link Resource}
+ */
+ public long getCount() {
+ return this.count;
+ }
+
+}
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 d4772499..908b54d3 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author 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.IOException;
+import java.lang.reflect.Constructor;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
@@ -31,6 +32,7 @@ import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.io.UrlResource;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
+import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
@@ -132,10 +134,12 @@ public abstract class SpringFactoriesLoader {
throw new IllegalArgumentException(
"Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]");
}
- return (T) instanceClass.newInstance();
+ Constructor<?> constructor = instanceClass.getDeclaredConstructor();
+ ReflectionUtils.makeAccessible(constructor);
+ return (T) constructor.newInstance();
}
catch (Throwable ex) {
- throw new IllegalArgumentException("Cannot instantiate factory class: " + factoryClass.getName(), ex);
+ throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), ex);
}
}
diff --git a/spring-core/src/main/java/org/springframework/core/serializer/support/SerializationDelegate.java b/spring-core/src/main/java/org/springframework/core/serializer/support/SerializationDelegate.java
new file mode 100644
index 00000000..1e661d6a
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/serializer/support/SerializationDelegate.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2002-2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.serializer.support;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.springframework.core.serializer.DefaultDeserializer;
+import org.springframework.core.serializer.DefaultSerializer;
+import org.springframework.core.serializer.Deserializer;
+import org.springframework.core.serializer.Serializer;
+import org.springframework.util.Assert;
+
+/**
+ * A convenient delegate with pre-arranged configuration state for common
+ * serialization needs. Implements {@link Serializer} and {@link Deserializer}
+ * itself, so can also be passed into such more specific callback methods.
+ *
+ * @author Juergen Hoeller
+ * @since 4.3
+ */
+public class SerializationDelegate implements Serializer<Object>, Deserializer<Object> {
+
+ private final Serializer<Object> serializer;
+
+ private final Deserializer<Object> deserializer;
+
+
+ /**
+ * Create a {@code SerializationDelegate} with a default serializer/deserializer
+ * for the given {@code ClassLoader}.
+ * @see DefaultDeserializer
+ * @see DefaultDeserializer#DefaultDeserializer(ClassLoader)
+ */
+ public SerializationDelegate(ClassLoader classLoader) {
+ this.serializer = new DefaultSerializer();
+ this.deserializer = new DefaultDeserializer(classLoader);
+ }
+
+ /**
+ * Create a {@code SerializationDelegate} with the given serializer/deserializer.
+ * @param serializer the {@link Serializer} to use (never {@code null)}
+ * @param deserializer the {@link Deserializer} to use (never {@code null)}
+ */
+ public SerializationDelegate(Serializer<Object> serializer, Deserializer<Object> deserializer) {
+ Assert.notNull(serializer, "Serializer must not be null");
+ Assert.notNull(deserializer, "Deserializer must not be null");
+ this.serializer = serializer;
+ this.deserializer = deserializer;
+ }
+
+
+ @Override
+ public void serialize(Object object, OutputStream outputStream) throws IOException {
+ this.serializer.serialize(object, outputStream);
+ }
+
+ @Override
+ public Object deserialize(InputStream inputStream) throws IOException {
+ return this.deserializer.deserialize(inputStream);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/style/ToStringCreator.java b/spring-core/src/main/java/org/springframework/core/style/ToStringCreator.java
index 6f682939..a7291f6f 100644
--- a/spring-core/src/main/java/org/springframework/core/style/ToStringCreator.java
+++ b/spring-core/src/main/java/org/springframework/core/style/ToStringCreator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -82,7 +82,7 @@ public class ToStringCreator {
* @return this, to support call-chaining
*/
public ToStringCreator append(String fieldName, byte value) {
- return append(fieldName, new Byte(value));
+ return append(fieldName, Byte.valueOf(value));
}
/**
@@ -92,7 +92,7 @@ public class ToStringCreator {
* @return this, to support call-chaining
*/
public ToStringCreator append(String fieldName, short value) {
- return append(fieldName, new Short(value));
+ return append(fieldName, Short.valueOf(value));
}
/**
@@ -102,7 +102,7 @@ public class ToStringCreator {
* @return this, to support call-chaining
*/
public ToStringCreator append(String fieldName, int value) {
- return append(fieldName, new Integer(value));
+ return append(fieldName, Integer.valueOf(value));
}
/**
@@ -112,7 +112,7 @@ public class ToStringCreator {
* @return this, to support call-chaining
*/
public ToStringCreator append(String fieldName, long value) {
- return append(fieldName, new Long(value));
+ return append(fieldName, Long.valueOf(value));
}
/**
@@ -122,7 +122,7 @@ public class ToStringCreator {
* @return this, to support call-chaining
*/
public ToStringCreator append(String fieldName, float value) {
- return append(fieldName, new Float(value));
+ return append(fieldName, Float.valueOf(value));
}
/**
@@ -132,7 +132,7 @@ public class ToStringCreator {
* @return this, to support call-chaining
*/
public ToStringCreator append(String fieldName, double value) {
- return append(fieldName, new Double(value));
+ return append(fieldName, Double.valueOf(value));
}
/**
diff --git a/spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java b/spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java
index 05e759ab..0483932a 100644
--- a/spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java
+++ b/spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -65,6 +65,8 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator implement
private ThreadFactory threadFactory;
+ private TaskDecorator taskDecorator;
+
/**
* Create a new SimpleAsyncTaskExecutor with default thread name prefix.
@@ -110,6 +112,20 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator implement
}
/**
+ * Specify a custom {@link TaskDecorator} to be applied to any {@link Runnable}
+ * about to be executed.
+ * <p>Note that such a decorator is not necessarily being applied to the
+ * user-supplied {@code Runnable}/{@code Callable} but rather to the actual
+ * execution callback (which may be a wrapper around the user-supplied task).
+ * <p>The primary use case is to set some execution context around the task's
+ * invocation, or to provide some monitoring/statistics for task execution.
+ * @since 4.3
+ */
+ public final void setTaskDecorator(TaskDecorator taskDecorator) {
+ this.taskDecorator = taskDecorator;
+ }
+
+ /**
* Set the maximum number of parallel accesses allowed.
* -1 indicates no concurrency limit at all.
* <p>In principle, this limit can be changed at runtime,
@@ -163,12 +179,13 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator implement
@Override
public void execute(Runnable task, long startTimeout) {
Assert.notNull(task, "Runnable must not be null");
+ Runnable taskToUse = (this.taskDecorator != null ? this.taskDecorator.decorate(task) : task);
if (isThrottleActive() && startTimeout > TIMEOUT_IMMEDIATE) {
this.concurrencyThrottle.beforeAccess();
- doExecute(new ConcurrencyThrottlingRunnable(task));
+ doExecute(new ConcurrencyThrottlingRunnable(taskToUse));
}
else {
- doExecute(task);
+ doExecute(taskToUse);
}
}
diff --git a/spring-core/src/main/java/org/springframework/core/task/TaskDecorator.java b/spring-core/src/main/java/org/springframework/core/task/TaskDecorator.java
new file mode 100644
index 00000000..d36a3ab3
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/task/TaskDecorator.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.task;
+
+/**
+ * A callback interface for a decorator to be applied to any {@link Runnable}
+ * about to be executed.
+ *
+ * <p>Note that such a decorator is not necessarily being applied to the
+ * user-supplied {@code Runnable}/{@code Callable} but rather to the actual
+ * execution callback (which may be a wrapper around the user-supplied task).
+ *
+ * <p>The primary use case is to set some execution context around the task's
+ * invocation, or to provide some monitoring/statistics for task execution.
+ *
+ * @author Juergen Hoeller
+ * @since 4.3
+ * @see TaskExecutor#execute(Runnable)
+ * @see SimpleAsyncTaskExecutor#setTaskDecorator
+ */
+public interface TaskDecorator {
+
+ /**
+ * Decorate the given {@code Runnable}, returning a potentially wrapped
+ * {@code Runnable} for actual execution.
+ * @param runnable the original {@code Runnable}
+ * @return the decorated {@code Runnable}
+ */
+ Runnable decorate(Runnable runnable);
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/task/support/TaskExecutorAdapter.java b/spring-core/src/main/java/org/springframework/core/task/support/TaskExecutorAdapter.java
index fad9ae09..165533df 100644
--- a/spring-core/src/main/java/org/springframework/core/task/support/TaskExecutorAdapter.java
+++ b/spring-core/src/main/java/org/springframework/core/task/support/TaskExecutorAdapter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@ import java.util.concurrent.FutureTask;
import java.util.concurrent.RejectedExecutionException;
import org.springframework.core.task.AsyncListenableTaskExecutor;
+import org.springframework.core.task.TaskDecorator;
import org.springframework.core.task.TaskRejectedException;
import org.springframework.util.Assert;
import org.springframework.util.concurrent.ListenableFuture;
@@ -45,6 +46,8 @@ public class TaskExecutorAdapter implements AsyncListenableTaskExecutor {
private final Executor concurrentExecutor;
+ private TaskDecorator taskDecorator;
+
/**
* Create a new TaskExecutorAdapter,
@@ -58,13 +61,28 @@ public class TaskExecutorAdapter implements AsyncListenableTaskExecutor {
/**
+ * Specify a custom {@link TaskDecorator} to be applied to any {@link Runnable}
+ * about to be executed.
+ * <p>Note that such a decorator is not necessarily being applied to the
+ * user-supplied {@code Runnable}/{@code Callable} but rather to the actual
+ * execution callback (which may be a wrapper around the user-supplied task).
+ * <p>The primary use case is to set some execution context around the task's
+ * invocation, or to provide some monitoring/statistics for task execution.
+ * @since 4.3
+ */
+ public final void setTaskDecorator(TaskDecorator taskDecorator) {
+ this.taskDecorator = taskDecorator;
+ }
+
+
+ /**
* Delegates to the specified JDK concurrent executor.
* @see java.util.concurrent.Executor#execute(Runnable)
*/
@Override
public void execute(Runnable task) {
try {
- this.concurrentExecutor.execute(task);
+ doExecute(this.concurrentExecutor, this.taskDecorator, task);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException(
@@ -80,12 +98,12 @@ public class TaskExecutorAdapter implements AsyncListenableTaskExecutor {
@Override
public Future<?> submit(Runnable task) {
try {
- if (this.concurrentExecutor instanceof ExecutorService) {
+ if (this.taskDecorator == null && this.concurrentExecutor instanceof ExecutorService) {
return ((ExecutorService) this.concurrentExecutor).submit(task);
}
else {
FutureTask<Object> future = new FutureTask<Object>(task, null);
- this.concurrentExecutor.execute(future);
+ doExecute(this.concurrentExecutor, this.taskDecorator, future);
return future;
}
}
@@ -98,12 +116,12 @@ public class TaskExecutorAdapter implements AsyncListenableTaskExecutor {
@Override
public <T> Future<T> submit(Callable<T> task) {
try {
- if (this.concurrentExecutor instanceof ExecutorService) {
+ if (this.taskDecorator == null && this.concurrentExecutor instanceof ExecutorService) {
return ((ExecutorService) this.concurrentExecutor).submit(task);
}
else {
FutureTask<T> future = new FutureTask<T>(task);
- this.concurrentExecutor.execute(future);
+ doExecute(this.concurrentExecutor, this.taskDecorator, future);
return future;
}
}
@@ -117,7 +135,7 @@ public class TaskExecutorAdapter implements AsyncListenableTaskExecutor {
public ListenableFuture<?> submitListenable(Runnable task) {
try {
ListenableFutureTask<Object> future = new ListenableFutureTask<Object>(task, null);
- this.concurrentExecutor.execute(future);
+ doExecute(this.concurrentExecutor, this.taskDecorator, future);
return future;
}
catch (RejectedExecutionException ex) {
@@ -130,7 +148,7 @@ public class TaskExecutorAdapter implements AsyncListenableTaskExecutor {
public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
try {
ListenableFutureTask<T> future = new ListenableFutureTask<T>(task);
- this.concurrentExecutor.execute(future);
+ doExecute(this.concurrentExecutor, this.taskDecorator, future);
return future;
}
catch (RejectedExecutionException ex) {
@@ -139,4 +157,20 @@ public class TaskExecutorAdapter implements AsyncListenableTaskExecutor {
}
}
+
+ /**
+ * Actually execute the given {@code Runnable} (which may be a user-supplied task
+ * or a wrapper around a user-supplied task) with the given executor.
+ * @param concurrentExecutor the underlying JDK concurrent executor to delegate to
+ * @param taskDecorator the specified decorator to be applied, if any
+ * @param runnable the runnable to execute
+ * @throws RejectedExecutionException if the given runnable cannot be accepted
+ * @since 4.3
+ */
+ protected void doExecute(Executor concurrentExecutor, TaskDecorator taskDecorator, Runnable runnable)
+ throws RejectedExecutionException{
+
+ concurrentExecutor.execute(taskDecorator != null ? taskDecorator.decorate(runnable) : runnable);
+ }
+
}
diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/AbstractRecursiveAnnotationVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/AbstractRecursiveAnnotationVisitor.java
index 2cc12717..bd42e059 100644
--- a/spring-core/src/main/java/org/springframework/core/type/classreading/AbstractRecursiveAnnotationVisitor.java
+++ b/spring-core/src/main/java/org/springframework/core/type/classreading/AbstractRecursiveAnnotationVisitor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -58,7 +58,7 @@ abstract class AbstractRecursiveAnnotationVisitor extends AnnotationVisitor {
@Override
public AnnotationVisitor visitAnnotation(String attributeName, String asmTypeDescriptor) {
String annotationType = Type.getType(asmTypeDescriptor).getClassName();
- AnnotationAttributes nestedAttributes = new AnnotationAttributes();
+ AnnotationAttributes nestedAttributes = new AnnotationAttributes(annotationType, this.classLoader);
this.attributes.put(attributeName, nestedAttributes);
return new RecursiveAnnotationAttributesVisitor(annotationType, nestedAttributes, this.classLoader);
}
diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java
index ab001b50..f35ecd68 100644
--- a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java
+++ b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java
@@ -44,8 +44,6 @@ import org.springframework.util.ObjectUtils;
*/
final class AnnotationAttributesReadingVisitor extends RecursiveAnnotationAttributesVisitor {
- private final String annotationType;
-
private final MultiValueMap<String, AnnotationAttributes> attributesMap;
private final Map<String, Set<String>> metaAnnotationMap;
@@ -55,38 +53,41 @@ final class AnnotationAttributesReadingVisitor extends RecursiveAnnotationAttrib
MultiValueMap<String, AnnotationAttributes> attributesMap, Map<String, Set<String>> metaAnnotationMap,
ClassLoader classLoader) {
- super(annotationType, new AnnotationAttributes(), classLoader);
- this.annotationType = annotationType;
+ super(annotationType, new AnnotationAttributes(annotationType, classLoader), classLoader);
this.attributesMap = attributesMap;
this.metaAnnotationMap = metaAnnotationMap;
}
@Override
- public void doVisitEnd(Class<?> annotationClass) {
- super.doVisitEnd(annotationClass);
- List<AnnotationAttributes> attributes = this.attributesMap.get(this.annotationType);
- if (attributes == null) {
- this.attributesMap.add(this.annotationType, this.attributes);
- }
- else {
- attributes.add(0, this.attributes);
- }
- Set<Annotation> visited = new LinkedHashSet<Annotation>();
- Annotation[] metaAnnotations = AnnotationUtils.getAnnotations(annotationClass);
- if (!ObjectUtils.isEmpty(metaAnnotations)) {
- for (Annotation metaAnnotation : metaAnnotations) {
- if (!AnnotationUtils.isInJavaLangAnnotationPackage(metaAnnotation)) {
- recursivelyCollectMetaAnnotations(visited, metaAnnotation);
+ public void visitEnd() {
+ super.visitEnd();
+
+ Class<?> annotationClass = this.attributes.annotationType();
+ if (annotationClass != null) {
+ List<AnnotationAttributes> attributeList = this.attributesMap.get(this.annotationType);
+ if (attributeList == null) {
+ this.attributesMap.add(this.annotationType, this.attributes);
+ }
+ else {
+ attributeList.add(0, this.attributes);
+ }
+ Set<Annotation> visited = new LinkedHashSet<Annotation>();
+ Annotation[] metaAnnotations = AnnotationUtils.getAnnotations(annotationClass);
+ if (!ObjectUtils.isEmpty(metaAnnotations)) {
+ for (Annotation metaAnnotation : metaAnnotations) {
+ if (!AnnotationUtils.isInJavaLangAnnotationPackage(metaAnnotation)) {
+ recursivelyCollectMetaAnnotations(visited, metaAnnotation);
+ }
}
}
- }
- if (this.metaAnnotationMap != null) {
- Set<String> metaAnnotationTypeNames = new LinkedHashSet<String>(visited.size());
- for (Annotation ann : visited) {
- metaAnnotationTypeNames.add(ann.annotationType().getName());
+ if (this.metaAnnotationMap != null) {
+ Set<String> metaAnnotationTypeNames = new LinkedHashSet<String>(visited.size());
+ for (Annotation ann : visited) {
+ metaAnnotationTypeNames.add(ann.annotationType().getName());
+ }
+ this.metaAnnotationMap.put(annotationClass.getName(), metaAnnotationTypeNames);
}
- this.metaAnnotationMap.put(annotationClass.getName(), metaAnnotationTypeNames);
}
}
diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java
index eea3d825..3b3a17dd 100644
--- a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java
+++ b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java
@@ -131,7 +131,8 @@ public class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisito
public AnnotationAttributes getAnnotationAttributes(String annotationName, boolean classValuesAsString) {
AnnotationAttributes raw = AnnotationReadingVisitorUtils.getMergedAnnotationAttributes(
this.attributesMap, this.metaAnnotationMap, annotationName);
- return AnnotationReadingVisitorUtils.convertClassValues(this.classLoader, raw, classValuesAsString);
+ return AnnotationReadingVisitorUtils.convertClassValues(
+ "class '" + getClassName() + "'", this.classLoader, raw, classValuesAsString);
}
@Override
@@ -148,7 +149,7 @@ public class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisito
}
for (AnnotationAttributes raw : attributes) {
for (Map.Entry<String, Object> entry : AnnotationReadingVisitorUtils.convertClassValues(
- this.classLoader, raw, classValuesAsString).entrySet()) {
+ "class '" + getClassName() + "'", this.classLoader, raw, classValuesAsString).entrySet()) {
allAttributes.add(entry.getKey(), entry.getValue());
}
}
diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationReadingVisitorUtils.java b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationReadingVisitorUtils.java
index 0d7ba35f..93c3e76d 100644
--- a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationReadingVisitorUtils.java
+++ b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationReadingVisitorUtils.java
@@ -41,25 +41,29 @@ import org.springframework.util.ObjectUtils;
*/
abstract class AnnotationReadingVisitorUtils {
- public static AnnotationAttributes convertClassValues(ClassLoader classLoader, AnnotationAttributes original,
- boolean classValuesAsString) {
+ public static AnnotationAttributes convertClassValues(Object annotatedElement,
+ ClassLoader classLoader, AnnotationAttributes original, boolean classValuesAsString) {
if (original == null) {
return null;
}
- AnnotationAttributes result = new AnnotationAttributes(original.size());
- for (Map.Entry<String, Object> entry : original.entrySet()) {
+ AnnotationAttributes result = new AnnotationAttributes(original);
+ AnnotationUtils.postProcessAnnotationAttributes(annotatedElement, result, classValuesAsString);
+
+ for (Map.Entry<String, Object> entry : result.entrySet()) {
try {
Object value = entry.getValue();
if (value instanceof AnnotationAttributes) {
- value = convertClassValues(classLoader, (AnnotationAttributes) value, classValuesAsString);
+ value = convertClassValues(
+ annotatedElement, classLoader, (AnnotationAttributes) value, classValuesAsString);
}
else if (value instanceof AnnotationAttributes[]) {
AnnotationAttributes[] values = (AnnotationAttributes[]) value;
for (int i = 0; i < values.length; i++) {
- values[i] = convertClassValues(classLoader, values[i], classValuesAsString);
+ values[i] = convertClassValues(annotatedElement, classLoader, values[i], classValuesAsString);
}
+ value = values;
}
else if (value instanceof Type) {
value = (classValuesAsString ? ((Type) value).getClassName() :
@@ -67,7 +71,8 @@ abstract class AnnotationReadingVisitorUtils {
}
else if (value instanceof Type[]) {
Type[] array = (Type[]) value;
- Object[] convArray = (classValuesAsString ? new String[array.length] : new Class<?>[array.length]);
+ Object[] convArray =
+ (classValuesAsString ? new String[array.length] : new Class<?>[array.length]);
for (int i = 0; i < array.length; i++) {
convArray[i] = (classValuesAsString ? array[i].getClassName() :
classLoader.loadClass(array[i].getClassName()));
@@ -75,11 +80,11 @@ abstract class AnnotationReadingVisitorUtils {
value = convArray;
}
else if (classValuesAsString) {
- if (value instanceof Class) {
+ if (value instanceof Class<?>) {
value = ((Class<?>) value).getName();
}
- else if (value instanceof Class[]) {
- Class<?>[] clazzArray = (Class[]) value;
+ else if (value instanceof Class<?>[]) {
+ Class<?>[] clazzArray = (Class<?>[]) value;
String[] newValue = new String[clazzArray.length];
for (int i = 0; i < clazzArray.length; i++) {
newValue[i] = clazzArray[i].getName();
@@ -87,13 +92,14 @@ abstract class AnnotationReadingVisitorUtils {
value = newValue;
}
}
- result.put(entry.getKey(), value);
+ entry.setValue(value);
}
catch (Exception ex) {
// Class not found - can't resolve class reference in annotation attribute.
result.put(entry.getKey(), ex);
}
}
+
return result;
}
@@ -123,13 +129,12 @@ abstract class AnnotationReadingVisitorUtils {
return null;
}
- // To start with, we populate the results with a copy of all attribute
- // values from the target annotation. A copy is necessary so that we do
- // not inadvertently mutate the state of the metadata passed to this
- // method.
- AnnotationAttributes results = new AnnotationAttributes(attributesList.get(0));
+ // To start with, we populate the result with a copy of all attribute values
+ // from the target annotation. A copy is necessary so that we do not
+ // inadvertently mutate the state of the metadata passed to this method.
+ AnnotationAttributes result = new AnnotationAttributes(attributesList.get(0));
- Set<String> overridableAttributeNames = new HashSet<String>(results.keySet());
+ Set<String> overridableAttributeNames = new HashSet<String>(result.keySet());
overridableAttributeNames.remove(AnnotationUtils.VALUE);
// Since the map is a LinkedMultiValueMap, we depend on the ordering of
@@ -152,14 +157,14 @@ abstract class AnnotationReadingVisitorUtils {
if (value != null) {
// Store the value, potentially overriding a value from an attribute
// of the same name found higher in the annotation hierarchy.
- results.put(overridableAttributeName, value);
+ result.put(overridableAttributeName, value);
}
}
}
}
}
- return results;
+ return result;
}
}
diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java
index 00bd9cb1..9dfcfd1a 100644
--- a/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java
+++ b/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java
@@ -122,7 +122,8 @@ public class MethodMetadataReadingVisitor extends MethodVisitor implements Metho
public AnnotationAttributes getAnnotationAttributes(String annotationName, boolean classValuesAsString) {
AnnotationAttributes raw = AnnotationReadingVisitorUtils.getMergedAnnotationAttributes(
this.attributesMap, this.metaAnnotationMap, annotationName);
- return AnnotationReadingVisitorUtils.convertClassValues(this.classLoader, raw, classValuesAsString);
+ return AnnotationReadingVisitorUtils.convertClassValues(
+ "method '" + getMethodName() + "'", this.classLoader, raw, classValuesAsString);
}
@Override
@@ -137,8 +138,9 @@ public class MethodMetadataReadingVisitor extends MethodVisitor implements Metho
}
MultiValueMap<String, Object> allAttributes = new LinkedMultiValueMap<String, Object>();
for (AnnotationAttributes annotationAttributes : this.attributesMap.get(annotationName)) {
- for (Map.Entry<String, Object> entry : AnnotationReadingVisitorUtils.convertClassValues(
- this.classLoader, annotationAttributes, classValuesAsString).entrySet()) {
+ AnnotationAttributes convertedAttributes = AnnotationReadingVisitorUtils.convertClassValues(
+ "method '" + getMethodName() + "'", this.classLoader, annotationAttributes, classValuesAsString);
+ for (Map.Entry<String, Object> entry : convertedAttributes.entrySet()) {
allAttributes.add(entry.getKey(), entry.getValue());
}
}
diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/RecursiveAnnotationArrayVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/RecursiveAnnotationArrayVisitor.java
index 3c5bd9a4..9c466b77 100644
--- a/spring-core/src/main/java/org/springframework/core/type/classreading/RecursiveAnnotationArrayVisitor.java
+++ b/spring-core/src/main/java/org/springframework/core/type/classreading/RecursiveAnnotationArrayVisitor.java
@@ -69,7 +69,7 @@ class RecursiveAnnotationArrayVisitor extends AbstractRecursiveAnnotationVisitor
@Override
public AnnotationVisitor visitAnnotation(String attributeName, String asmTypeDescriptor) {
String annotationType = Type.getType(asmTypeDescriptor).getClassName();
- AnnotationAttributes nestedAttributes = new AnnotationAttributes();
+ AnnotationAttributes nestedAttributes = new AnnotationAttributes(annotationType, this.classLoader);
this.allNestedAttributes.add(nestedAttributes);
return new RecursiveAnnotationAttributesVisitor(annotationType, nestedAttributes, this.classLoader);
}
diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/RecursiveAnnotationAttributesVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/RecursiveAnnotationAttributesVisitor.java
index 0d2176f0..ff7b92fb 100644
--- a/spring-core/src/main/java/org/springframework/core/type/classreading/RecursiveAnnotationAttributesVisitor.java
+++ b/spring-core/src/main/java/org/springframework/core/type/classreading/RecursiveAnnotationAttributesVisitor.java
@@ -16,10 +16,6 @@
package org.springframework.core.type.classreading;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
@@ -30,7 +26,7 @@ import org.springframework.core.annotation.AnnotationUtils;
*/
class RecursiveAnnotationAttributesVisitor extends AbstractRecursiveAnnotationVisitor {
- private final String annotationType;
+ protected final String annotationType;
public RecursiveAnnotationAttributesVisitor(
@@ -42,49 +38,8 @@ class RecursiveAnnotationAttributesVisitor extends AbstractRecursiveAnnotationVi
@Override
- public final void visitEnd() {
- try {
- Class<?> annotationClass = this.classLoader.loadClass(this.annotationType);
- doVisitEnd(annotationClass);
- }
- catch (ClassNotFoundException ex) {
- logger.debug("Failed to class-load type while reading annotation metadata. " +
- "This is a non-fatal error, but certain annotation metadata may be unavailable.", ex);
- }
- }
-
- protected void doVisitEnd(Class<?> annotationClass) {
- registerDefaultValues(annotationClass);
- }
-
- private void registerDefaultValues(Class<?> annotationClass) {
- // Only do defaults scanning for public annotations; we'd run into
- // IllegalAccessExceptions otherwise, and we don't want to mess with
- // accessibility in a SecurityManager environment.
- if (Modifier.isPublic(annotationClass.getModifiers())) {
- // Check declared default values of attributes in the annotation type.
- Method[] annotationAttributes = annotationClass.getMethods();
- for (Method annotationAttribute : annotationAttributes) {
- String attributeName = annotationAttribute.getName();
- Object defaultValue = annotationAttribute.getDefaultValue();
- if (defaultValue != null && !this.attributes.containsKey(attributeName)) {
- if (defaultValue instanceof Annotation) {
- defaultValue = AnnotationAttributes.fromMap(AnnotationUtils.getAnnotationAttributes(
- (Annotation) defaultValue, false, true));
- }
- else if (defaultValue instanceof Annotation[]) {
- Annotation[] realAnnotations = (Annotation[]) defaultValue;
- AnnotationAttributes[] mappedAnnotations = new AnnotationAttributes[realAnnotations.length];
- for (int i = 0; i < realAnnotations.length; i++) {
- mappedAnnotations[i] = AnnotationAttributes.fromMap(
- AnnotationUtils.getAnnotationAttributes(realAnnotations[i], false, true));
- }
- defaultValue = mappedAnnotations;
- }
- this.attributes.put(attributeName, defaultValue);
- }
- }
- }
+ public void visitEnd() {
+ AnnotationUtils.registerDefaultValues(this.attributes);
}
}
diff --git a/spring-core/src/main/java/org/springframework/lang/UsesSunMisc.java b/spring-core/src/main/java/org/springframework/lang/UsesSunMisc.java
new file mode 100644
index 00000000..5bcbd4a9
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/lang/UsesSunMisc.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.lang;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that the annotated element uses an API from the {@code sun.misc}
+ * package.
+ *
+ * @author Stephane Nicoll
+ * @since 4.3
+ */
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE})
+@Documented
+public @interface UsesSunMisc {
+}
diff --git a/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java b/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java
index 635f1234..5e91704b 100644
--- a/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java
+++ b/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -74,6 +74,8 @@ public class AntPathMatcher implements PathMatcher {
private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?\\}");
+ private static final char[] WILDCARD_CHARS = { '*', '?', '{' };
+
private String pathSeparator;
@@ -81,7 +83,7 @@ public class AntPathMatcher implements PathMatcher {
private boolean caseSensitive = true;
- private boolean trimTokens = true;
+ private boolean trimTokens = false;
private volatile Boolean cachePatterns;
@@ -130,7 +132,7 @@ public class AntPathMatcher implements PathMatcher {
/**
* Specify whether to trim tokenized paths and patterns.
- * <p>Default is {@code true}.
+ * <p>Default is {@code false}.
*/
public void setTrimTokens(boolean trimTokens) {
this.trimTokens = trimTokens;
@@ -188,6 +190,10 @@ public class AntPathMatcher implements PathMatcher {
}
String[] pattDirs = tokenizePattern(pattern);
+ if (fullMatch && this.caseSensitive && !isPotentialMatch(path, pattDirs)) {
+ return false;
+ }
+
String[] pathDirs = tokenizePath(path);
int pattIdxStart = 0;
@@ -307,6 +313,59 @@ public class AntPathMatcher implements PathMatcher {
return true;
}
+ private boolean isPotentialMatch(String path, String[] pattDirs) {
+ if (!this.trimTokens) {
+ char[] pathChars = path.toCharArray();
+ int pos = 0;
+ for (String pattDir : pattDirs) {
+ int skipped = skipSeparator(path, pos, this.pathSeparator);
+ pos += skipped;
+ skipped = skipSegment(pathChars, pos, pattDir);
+ if (skipped < pattDir.length()) {
+ if (skipped > 0) {
+ return true;
+ }
+ return (pattDir.length() > 0) && isWildcardChar(pattDir.charAt(0));
+ }
+ pos += skipped;
+ }
+ }
+ return true;
+ }
+
+ private int skipSegment(char[] chars, int pos, String prefix) {
+ int skipped = 0;
+ for (char c : prefix.toCharArray()) {
+ if (isWildcardChar(c)) {
+ return skipped;
+ }
+ else if (pos + skipped >= chars.length) {
+ return 0;
+ }
+ else if (chars[pos + skipped] == c) {
+ skipped++;
+ }
+ }
+ return skipped;
+ }
+
+ private int skipSeparator(String path, int pos, String separator) {
+ int skipped = 0;
+ while (path.startsWith(separator, pos + skipped)) {
+ skipped += separator.length();
+ }
+ return skipped;
+ }
+
+ private boolean isWildcardChar(char c) {
+ for (char candidate : WILDCARD_CHARS) {
+ if (c == candidate) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Tokenize the given path pattern into parts, based on this matcher's settings.
* <p>Performs caching based on {@link #setCachePatterns}, delegating to
diff --git a/spring-core/src/main/java/org/springframework/util/AutoPopulatingList.java b/spring-core/src/main/java/org/springframework/util/AutoPopulatingList.java
index 9cea1c99..4bea60c8 100644
--- a/spring-core/src/main/java/org/springframework/util/AutoPopulatingList.java
+++ b/spring-core/src/main/java/org/springframework/util/AutoPopulatingList.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -263,13 +263,16 @@ public class AutoPopulatingList<E> implements List<E>, Serializable {
public ElementInstantiationException(String msg) {
super(msg);
}
+
+ public ElementInstantiationException(String message, Throwable cause) {
+ super(message, cause);
+ }
}
/**
* Reflective implementation of the ElementFactory interface,
* using {@code Class.newInstance()} on a given element class.
- * @see Class#newInstance()
*/
private static class ReflectiveElementFactory<E> implements ElementFactory<E>, Serializable {
@@ -288,12 +291,12 @@ public class AutoPopulatingList<E> implements List<E>, Serializable {
return this.elementClass.newInstance();
}
catch (InstantiationException ex) {
- throw new ElementInstantiationException("Unable to instantiate element class [" +
- this.elementClass.getName() + "]. Root cause is " + ex);
+ throw new ElementInstantiationException(
+ "Unable to instantiate element class: " + this.elementClass.getName(), ex);
}
catch (IllegalAccessException ex) {
- throw new ElementInstantiationException("Cannot access element class [" +
- this.elementClass.getName() + "]. Root cause is " + ex);
+ throw new ElementInstantiationException(
+ "Could not access element constructor: " + this.elementClass.getName(), ex);
}
}
}
diff --git a/spring-core/src/main/java/org/springframework/util/Base64Utils.java b/spring-core/src/main/java/org/springframework/util/Base64Utils.java
index 17c48614..f10b60d6 100644
--- a/spring-core/src/main/java/org/springframework/util/Base64Utils.java
+++ b/spring-core/src/main/java/org/springframework/util/Base64Utils.java
@@ -30,11 +30,11 @@ import org.springframework.lang.UsesJava8;
* Codec present, {@link #encode}/{@link #decode} calls will throw an IllegalStateException.
* However, as of Spring 4.2, {@link #encodeToString} and {@link #decodeFromString} will
* nevertheless work since they can delegate to the JAXB DatatypeConverter as a fallback.
- * However, this does not apply when using the ...UrlSafe... methods for RFC 4648 "URL and
+ * However, this does not apply when using the "UrlSafe" methods for RFC 4648 "URL and
* Filename Safe Alphabet"; a delegate is required.
- * <p>
- * <em>Note:</em> Apache Commons Codec does not add padding ({@code =}) when encoding with
- * the URL and Filename Safe Alphabet.
+ *
+ * <p><em>Note:</em> Apache Commons Codec does not add padding ({@code =}) when encoding
+ * with the URL and Filename Safe Alphabet.
*
* @author Juergen Hoeller
* @author Gary Russell
diff --git a/spring-core/src/main/java/org/springframework/util/ClassUtils.java b/spring-core/src/main/java/org/springframework/util/ClassUtils.java
index 93dd3d61..6d152017 100644
--- a/spring-core/src/main/java/org/springframework/util/ClassUtils.java
+++ b/spring-core/src/main/java/org/springframework/util/ClassUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,16 +56,16 @@ public abstract class ClassUtils {
/** Prefix for internal non-primitive array class names: "[L" */
private static final String NON_PRIMITIVE_ARRAY_PREFIX = "[L";
- /** The package separator character '.' */
+ /** The package separator character: '.' */
private static final char PACKAGE_SEPARATOR = '.';
- /** The path separator character '/' */
+ /** The path separator character: '/' */
private static final char PATH_SEPARATOR = '/';
- /** The inner class separator character '$' */
+ /** The inner class separator character: '$' */
private static final char INNER_CLASS_SEPARATOR = '$';
- /** The CGLIB class separator character "$$" */
+ /** The CGLIB class separator: "$$" */
public static final String CGLIB_CLASS_SEPARATOR = "$$";
/** The ".class" file suffix */
@@ -1158,7 +1158,6 @@ public abstract class ClassUtils {
*/
public static Class<?> createCompositeInterface(Class<?>[] interfaces, ClassLoader classLoader) {
Assert.notEmpty(interfaces, "Interfaces must not be empty");
- Assert.notNull(classLoader, "ClassLoader must not be null");
return Proxy.getProxyClass(classLoader, interfaces);
}
diff --git a/spring-core/src/main/java/org/springframework/util/DigestUtils.java b/spring-core/src/main/java/org/springframework/util/DigestUtils.java
index 8de0b513..36b1a583 100644
--- a/spring-core/src/main/java/org/springframework/util/DigestUtils.java
+++ b/spring-core/src/main/java/org/springframework/util/DigestUtils.java
@@ -23,14 +23,15 @@ import java.security.NoSuchAlgorithmException;
/**
* Miscellaneous methods for calculating digests.
+ *
* <p>Mainly for internal use within the framework; consider
- * <a href="http://commons.apache.org/codec/">Apache Commons Codec</a> for a
- * more comprehensive suite of digest utilities.
+ * <a href="http://commons.apache.org/codec/">Apache Commons Codec</a>
+ * for a more comprehensive suite of digest utilities.
*
* @author Arjen Poutsma
+ * @author Juergen Hoeller
* @author Craig Andrews
* @since 3.0
- * @see org.apache.commons.codec.digest.DigestUtils
*/
public abstract class DigestUtils {
@@ -50,8 +51,8 @@ public abstract class DigestUtils {
}
/**
- * Calculate the MD5 digest of the given InputStream.
- * @param inputStream the inputStream to calculate the digest over
+ * Calculate the MD5 digest of the given stream.
+ * @param inputStream the InputStream to calculate the digest over
* @return the digest
* @since 4.2
*/
@@ -60,8 +61,7 @@ public abstract class DigestUtils {
}
/**
- * Return a hexadecimal string representation of the MD5 digest of the given
- * bytes.
+ * Return a hexadecimal string representation of the MD5 digest of the given bytes.
* @param bytes the bytes to calculate the digest over
* @return a hexadecimal digest string
*/
@@ -70,9 +70,8 @@ public abstract class DigestUtils {
}
/**
- * Return a hexadecimal string representation of the MD5 digest of the given
- * inputStream.
- * @param inputStream the inputStream to calculate the digest over
+ * Return a hexadecimal string representation of the MD5 digest of the given stream.
+ * @param inputStream the InputStream to calculate the digest over
* @return a hexadecimal digest string
* @since 4.2
*/
@@ -128,7 +127,12 @@ public abstract class DigestUtils {
return messageDigest.digest();
}
else {
- return messageDigest.digest(StreamUtils.copyToByteArray(inputStream));
+ final byte[] buffer = new byte[StreamUtils.BUFFER_SIZE];
+ int bytesRead = -1;
+ while ((bytesRead = inputStream.read(buffer)) != -1) {
+ messageDigest.update(buffer, 0, bytesRead);
+ }
+ return messageDigest.digest();
}
}
diff --git a/spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java b/spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java
index cde4e615..51dd02c6 100644
--- a/spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java
+++ b/spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java
@@ -36,7 +36,7 @@ import java.util.Map;
@SuppressWarnings("serial")
public class LinkedCaseInsensitiveMap<V> extends LinkedHashMap<String, V> {
- private final Map<String, String> caseInsensitiveKeys;
+ private Map<String, String> caseInsensitiveKeys;
private final Locale locale;
@@ -151,6 +151,14 @@ public class LinkedCaseInsensitiveMap<V> extends LinkedHashMap<String, V> {
super.clear();
}
+ @Override
+ @SuppressWarnings("unchecked")
+ public Object clone() {
+ LinkedCaseInsensitiveMap<V> copy = (LinkedCaseInsensitiveMap<V>) super.clone();
+ copy.caseInsensitiveKeys = new HashMap<String, String>(this.caseInsensitiveKeys);
+ return copy;
+ }
+
/**
* Convert the given key to a case-insensitive key.
diff --git a/spring-core/src/main/java/org/springframework/util/MimeType.java b/spring-core/src/main/java/org/springframework/util/MimeType.java
index b3e585fb..e73602d2 100644
--- a/spring-core/src/main/java/org/springframework/util/MimeType.java
+++ b/spring-core/src/main/java/org/springframework/util/MimeType.java
@@ -22,6 +22,7 @@ import java.util.BitSet;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -50,19 +51,12 @@ public class MimeType implements Comparable<MimeType>, Serializable {
private static final long serialVersionUID = 4085923477777865903L;
- protected static final String WILDCARD_TYPE = "*";
- private static final BitSet TOKEN;
+ protected static final String WILDCARD_TYPE = "*";
private static final String PARAM_CHARSET = "charset";
-
- private final String type;
-
- private final String subtype;
-
- private final Map<String, String> parameters;
-
+ private static final BitSet TOKEN;
static {
// variable names refer to RFC 2616, section 2.2
@@ -100,6 +94,13 @@ public class MimeType implements Comparable<MimeType>, Serializable {
}
+ private final String type;
+
+ private final String subtype;
+
+ private final Map<String, String> parameters;
+
+
/**
* Create a new {@code MimeType} for the given primary type.
* <p>The {@linkplain #getSubtype() subtype} is set to <code>"&#42;"</code>,
@@ -126,11 +127,23 @@ public class MimeType implements Comparable<MimeType>, Serializable {
* Create a new {@code MimeType} for the given type, subtype, and character set.
* @param type the primary type
* @param subtype the subtype
- * @param charSet the character set
+ * @param charset the character set
+ * @throws IllegalArgumentException if any of the parameters contains illegal characters
+ */
+ public MimeType(String type, String subtype, Charset charset) {
+ this(type, subtype, Collections.singletonMap(PARAM_CHARSET, charset.name()));
+ }
+
+ /**
+ * Copy-constructor that copies the type, subtype, parameters of the given {@code MimeType},
+ * and allows to set the specified character set.
+ * @param other the other media type
+ * @param charset the character set
* @throws IllegalArgumentException if any of the parameters contains illegal characters
+ * @since 4.3
*/
- public MimeType(String type, String subtype, Charset charSet) {
- this(type, subtype, Collections.singletonMap(PARAM_CHARSET, charSet.name()));
+ public MimeType(MimeType other, Charset charset) {
+ this(other.getType(), other.getSubtype(), addCharsetParameter(charset, other.getParameters()));
}
/**
@@ -261,13 +274,25 @@ public class MimeType implements Comparable<MimeType>, Serializable {
/**
* Return the character set, as indicated by a {@code charset} parameter, if any.
* @return the character set, or {@code null} if not available
+ * @since 4.3
*/
- public Charset getCharSet() {
+ public Charset getCharset() {
String charSet = getParameter(PARAM_CHARSET);
return (charSet != null ? Charset.forName(unquote(charSet)) : null);
}
/**
+ * Return the character set, as indicated by a {@code charset} parameter, if any.
+ * @return the character set, or {@code null} if not available
+ * @deprecated as of Spring 4.3, in favor of {@link #getCharset()} with its name
+ * aligned with the Java return type name
+ */
+ @Deprecated
+ public Charset getCharSet() {
+ return getCharset();
+ }
+
+ /**
* Return a generic parameter value, given a parameter name.
* @param name the parameter name
* @return the parameter value, or {@code null} if not present
@@ -374,50 +399,6 @@ public class MimeType implements Comparable<MimeType>, Serializable {
return false;
}
- /**
- * Compares this {@code MediaType} to another alphabetically.
- * @param other media type to compare to
- * @see MimeTypeUtils#sortBySpecificity(List)
- */
- @Override
- public int compareTo(MimeType other) {
- int comp = getType().compareToIgnoreCase(other.getType());
- if (comp != 0) {
- return comp;
- }
- comp = getSubtype().compareToIgnoreCase(other.getSubtype());
- if (comp != 0) {
- return comp;
- }
- comp = getParameters().size() - other.getParameters().size();
- if (comp != 0) {
- return comp;
- }
- TreeSet<String> thisAttributes = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
- thisAttributes.addAll(getParameters().keySet());
- TreeSet<String> otherAttributes = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
- otherAttributes.addAll(other.getParameters().keySet());
- Iterator<String> thisAttributesIterator = thisAttributes.iterator();
- Iterator<String> otherAttributesIterator = otherAttributes.iterator();
- while (thisAttributesIterator.hasNext()) {
- String thisAttribute = thisAttributesIterator.next();
- String otherAttribute = otherAttributesIterator.next();
- comp = thisAttribute.compareToIgnoreCase(otherAttribute);
- if (comp != 0) {
- return comp;
- }
- String thisValue = getParameters().get(thisAttribute);
- String otherValue = other.getParameters().get(otherAttribute);
- if (otherValue == null) {
- otherValue = "";
- }
- comp = thisValue.compareTo(otherValue);
- if (comp != 0) {
- return comp;
- }
- }
- return 0;
- }
@Override
public boolean equals(Object other) {
@@ -439,22 +420,22 @@ public class MimeType implements Comparable<MimeType>, Serializable {
* for {@link Charset}s.
* @since 4.2
*/
- private boolean parametersAreEqual(MimeType that) {
- if (this.parameters.size() != that.parameters.size()) {
+ private boolean parametersAreEqual(MimeType other) {
+ if (this.parameters.size() != other.parameters.size()) {
return false;
}
for (String key : this.parameters.keySet()) {
- if (!that.parameters.containsKey(key)) {
+ if (!other.parameters.containsKey(key)) {
return false;
}
if (PARAM_CHARSET.equals(key)) {
- if (!ObjectUtils.nullSafeEquals(this.getCharSet(), that.getCharSet())) {
+ if (!ObjectUtils.nullSafeEquals(getCharset(), other.getCharset())) {
return false;
}
}
- else if (!ObjectUtils.nullSafeEquals(this.parameters.get(key), that.parameters.get(key))) {
+ else if (!ObjectUtils.nullSafeEquals(this.parameters.get(key), other.parameters.get(key))) {
return false;
}
}
@@ -494,6 +475,52 @@ public class MimeType implements Comparable<MimeType>, Serializable {
}
/**
+ * Compares this {@code MediaType} to another alphabetically.
+ * @param other media type to compare to
+ * @see MimeTypeUtils#sortBySpecificity(List)
+ */
+ @Override
+ public int compareTo(MimeType other) {
+ int comp = getType().compareToIgnoreCase(other.getType());
+ if (comp != 0) {
+ return comp;
+ }
+ comp = getSubtype().compareToIgnoreCase(other.getSubtype());
+ if (comp != 0) {
+ return comp;
+ }
+ comp = getParameters().size() - other.getParameters().size();
+ if (comp != 0) {
+ return comp;
+ }
+ TreeSet<String> thisAttributes = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
+ thisAttributes.addAll(getParameters().keySet());
+ TreeSet<String> otherAttributes = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
+ otherAttributes.addAll(other.getParameters().keySet());
+ Iterator<String> thisAttributesIterator = thisAttributes.iterator();
+ Iterator<String> otherAttributesIterator = otherAttributes.iterator();
+ while (thisAttributesIterator.hasNext()) {
+ String thisAttribute = thisAttributesIterator.next();
+ String otherAttribute = otherAttributesIterator.next();
+ comp = thisAttribute.compareToIgnoreCase(otherAttribute);
+ if (comp != 0) {
+ return comp;
+ }
+ String thisValue = getParameters().get(thisAttribute);
+ String otherValue = other.getParameters().get(otherAttribute);
+ if (otherValue == null) {
+ otherValue = "";
+ }
+ comp = thisValue.compareTo(otherValue);
+ if (comp != 0) {
+ return comp;
+ }
+ }
+ return 0;
+ }
+
+
+ /**
* Parse the given String value into a {@code MimeType} object,
* with this method name following the 'valueOf' naming convention
* (as supported by {@link org.springframework.core.convert.ConversionService}.
@@ -503,6 +530,12 @@ public class MimeType implements Comparable<MimeType>, Serializable {
return MimeTypeUtils.parseMimeType(value);
}
+ private static Map<String, String> addCharsetParameter(Charset charset, Map<String, String> parameters) {
+ Map<String, String> map = new LinkedHashMap<String, String>(parameters);
+ map.put(PARAM_CHARSET, charset.name());
+ return map;
+ }
+
public static class SpecificityComparator<T extends MimeType> implements Comparator<T> {
diff --git a/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java b/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java
index 0bf62d58..a0ad5cc0 100644
--- a/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java
+++ b/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -220,6 +220,9 @@ public abstract class MimeTypeUtils {
throw new InvalidMimeTypeException(mimeType, "'mimeType' must not be empty");
}
String[] parts = StringUtils.tokenizeToStringArray(mimeType, ";");
+ if (parts.length == 0) {
+ throw new InvalidMimeTypeException(mimeType, "'mimeType' must not be empty");
+ }
String fullType = parts[0].trim();
// java.net.HttpURLConnection returns a *; q=.2 Accept header
diff --git a/spring-core/src/main/java/org/springframework/util/NumberUtils.java b/spring-core/src/main/java/org/springframework/util/NumberUtils.java
index 9df023c3..46b3f868 100644
--- a/spring-core/src/main/java/org/springframework/util/NumberUtils.java
+++ b/spring-core/src/main/java/org/springframework/util/NumberUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -87,39 +87,29 @@ public abstract class NumberUtils {
return (T) number;
}
else if (Byte.class == targetClass) {
- long value = number.longValue();
+ long value = checkedLongValue(number, targetClass);
if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
raiseOverflowException(number, targetClass);
}
return (T) Byte.valueOf(number.byteValue());
}
else if (Short.class == targetClass) {
- long value = number.longValue();
+ long value = checkedLongValue(number, targetClass);
if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
raiseOverflowException(number, targetClass);
}
return (T) Short.valueOf(number.shortValue());
}
else if (Integer.class == targetClass) {
- long value = number.longValue();
+ long value = checkedLongValue(number, targetClass);
if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) {
raiseOverflowException(number, targetClass);
}
return (T) Integer.valueOf(number.intValue());
}
else if (Long.class == targetClass) {
- BigInteger bigInt = null;
- if (number instanceof BigInteger) {
- bigInt = (BigInteger) number;
- }
- else if (number instanceof BigDecimal) {
- bigInt = ((BigDecimal) number).toBigInteger();
- }
- // Effectively analogous to JDK 8's BigInteger.longValueExact()
- if (bigInt != null && (bigInt.compareTo(LONG_MIN) < 0 || bigInt.compareTo(LONG_MAX) > 0)) {
- raiseOverflowException(number, targetClass);
- }
- return (T) Long.valueOf(number.longValue());
+ long value = checkedLongValue(number, targetClass);
+ return (T) Long.valueOf(value);
}
else if (BigInteger.class == targetClass) {
if (number instanceof BigDecimal) {
@@ -149,10 +139,34 @@ public abstract class NumberUtils {
}
/**
+ * Check for a {@code BigInteger}/{@code BigDecimal} long overflow
+ * before returning the given number as a long value.
+ * @param number the number to convert
+ * @param targetClass the target class to convert to
+ * @return the long value, if convertible without overflow
+ * @throws IllegalArgumentException if there is an overflow
+ * @see #raiseOverflowException
+ */
+ private static long checkedLongValue(Number number, Class<? extends Number> targetClass) {
+ BigInteger bigInt = null;
+ if (number instanceof BigInteger) {
+ bigInt = (BigInteger) number;
+ }
+ else if (number instanceof BigDecimal) {
+ bigInt = ((BigDecimal) number).toBigInteger();
+ }
+ // Effectively analogous to JDK 8's BigInteger.longValueExact()
+ if (bigInt != null && (bigInt.compareTo(LONG_MIN) < 0 || bigInt.compareTo(LONG_MAX) > 0)) {
+ raiseOverflowException(number, targetClass);
+ }
+ return number.longValue();
+ }
+
+ /**
* Raise an <em>overflow</em> exception for the given number and target class.
* @param number the number we tried to convert
* @param targetClass the target class we tried to convert to
- * @throws IllegalArgumentException
+ * @throws IllegalArgumentException if there is an overflow
*/
private static void raiseOverflowException(Number number, Class<?> targetClass) {
throw new IllegalArgumentException("Could not convert number [" + number + "] of type [" +
diff --git a/spring-core/src/main/java/org/springframework/util/ResizableByteArrayOutputStream.java b/spring-core/src/main/java/org/springframework/util/ResizableByteArrayOutputStream.java
index cb2cdfa2..c4487ccf 100644
--- a/spring-core/src/main/java/org/springframework/util/ResizableByteArrayOutputStream.java
+++ b/spring-core/src/main/java/org/springframework/util/ResizableByteArrayOutputStream.java
@@ -23,7 +23,7 @@ import java.io.ByteArrayOutputStream;
* <ul>
* <li>has public {@link org.springframework.util.ResizableByteArrayOutputStream#grow(int)}
* and {@link org.springframework.util.ResizableByteArrayOutputStream#resize(int)} methods
- * to get more control over the the size of the internal buffer</li>
+ * to get more control over the size of the internal buffer</li>
* <li>has a higher initial capacity (256) by default</li>
* </ul>
*
diff --git a/spring-core/src/main/java/org/springframework/util/ResourceUtils.java b/spring-core/src/main/java/org/springframework/util/ResourceUtils.java
index 3ea92707..9108053f 100644
--- a/spring-core/src/main/java/org/springframework/util/ResourceUtils.java
+++ b/spring-core/src/main/java/org/springframework/util/ResourceUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -235,6 +235,7 @@ public abstract class ResourceUtils {
* @return a corresponding File object
* @throws FileNotFoundException if the URL cannot be resolved to
* a file in the file system
+ * @since 2.5
*/
public static File getFile(URI resourceUri) throws FileNotFoundException {
return getFile(resourceUri, "URI");
@@ -249,6 +250,7 @@ public abstract class ResourceUtils {
* @return a corresponding File object
* @throws FileNotFoundException if the URL cannot be resolved to
* a file in the file system
+ * @since 2.5
*/
public static File getFile(URI resourceUri, String description) throws FileNotFoundException {
Assert.notNull(resourceUri, "Resource URI must not be null");
diff --git a/spring-core/src/main/java/org/springframework/util/StopWatch.java b/spring-core/src/main/java/org/springframework/util/StopWatch.java
index 1ba39880..b4701164 100644
--- a/spring-core/src/main/java/org/springframework/util/StopWatch.java
+++ b/spring-core/src/main/java/org/springframework/util/StopWatch.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -134,7 +134,7 @@ public class StopWatch {
/**
* Stop the current task. The results are undefined if timing
* methods are called without invoking at least one pair
- * {@code #start()} / {@code #stop()} methods.
+ * {@code start()} / {@code stop()} methods.
* @see #start()
*/
public void stop() throws IllegalStateException {
diff --git a/spring-core/src/main/java/org/springframework/util/StreamUtils.java b/spring-core/src/main/java/org/springframework/util/StreamUtils.java
index 1bc2170c..c04ce058 100644
--- a/spring-core/src/main/java/org/springframework/util/StreamUtils.java
+++ b/spring-core/src/main/java/org/springframework/util/StreamUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +37,7 @@ import java.nio.charset.Charset;
*
* @author Juergen Hoeller
* @author Phillip Webb
+ * @author Brian Clozel
* @since 3.2.2
* @see FileCopyUtils
*/
@@ -132,6 +133,62 @@ public abstract class StreamUtils {
}
/**
+ * Copy a range of content of the given InputStream to the given OutputStream.
+ * <p>If the specified range exceeds the length of the InputStream, this copies
+ * up to the end of the stream and returns the actual number of copied bytes.
+ * <p>Leaves both streams open when done.
+ * @param in the InputStream to copy from
+ * @param out the OutputStream to copy to
+ * @param start the position to start copying from
+ * @param end the position to end copying
+ * @return the number of bytes copied
+ * @throws IOException in case of I/O errors
+ * @since 4.3
+ */
+ public static long copyRange(InputStream in, OutputStream out, long start, long end) throws IOException {
+ long skipped = in.skip(start);
+ if (skipped < start) {
+ throw new IOException("Skipped only " + skipped + " bytes out of " + start + " required.");
+ }
+ long bytesToCopy = end - start + 1;
+ byte buffer[] = new byte[StreamUtils.BUFFER_SIZE];
+ while (bytesToCopy > 0) {
+ int bytesRead = in.read(buffer);
+ if (bytesRead == -1) {
+ break;
+ }
+ else if (bytesRead <= bytesToCopy) {
+ out.write(buffer, 0, bytesRead);
+ bytesToCopy -= bytesRead;
+ }
+ else {
+ out.write(buffer, 0, (int) bytesToCopy);
+ bytesToCopy = 0;
+ }
+ }
+ return end - start + 1 - bytesToCopy;
+ }
+
+ /**
+ * Drain the remaining content of the given InputStream.
+ * Leaves the InputStream open when done.
+ * @param in the InputStream to drain
+ * @return the number of bytes read
+ * @throws IOException in case of I/O errors
+ * @since 4.3
+ */
+ public static int drain(InputStream in) throws IOException {
+ Assert.notNull(in, "No InputStream specified");
+ byte[] buffer = new byte[BUFFER_SIZE];
+ int bytesRead = -1;
+ int byteCount = 0;
+ while ((bytesRead = in.read(buffer)) != -1) {
+ byteCount += bytesRead;
+ }
+ return byteCount;
+ }
+
+ /**
* Return an efficient empty {@link InputStream}.
* @return a {@link ByteArrayInputStream} based on an empty byte array
* @since 4.2.2
diff --git a/spring-core/src/main/java/org/springframework/util/UpdateMessageDigestInputStream.java b/spring-core/src/main/java/org/springframework/util/UpdateMessageDigestInputStream.java
index 37890be9..90bfa4a7 100644
--- a/spring-core/src/main/java/org/springframework/util/UpdateMessageDigestInputStream.java
+++ b/spring-core/src/main/java/org/springframework/util/UpdateMessageDigestInputStream.java
@@ -33,7 +33,7 @@ abstract class UpdateMessageDigestInputStream extends InputStream {
* Update the message digest with the rest of the bytes in this stream.
* <p>Using this method is more optimized since it avoids creating new
* byte arrays for each call.
- * @param messageDigest The message digest to update
+ * @param messageDigest the message digest to update
* @throws IOException when propagated from {@link #read()}
*/
public void updateMessageDigest(MessageDigest messageDigest) throws IOException {
@@ -47,7 +47,7 @@ abstract class UpdateMessageDigestInputStream extends InputStream {
* Update the message digest with the next len bytes in this stream.
* <p>Using this method is more optimized since it avoids creating new
* byte arrays for each call.
- * @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
* @throws IOException when propagated from {@link #read()}
*/
diff --git a/spring-core/src/main/java/org/springframework/util/backoff/BackOff.java b/spring-core/src/main/java/org/springframework/util/backoff/BackOff.java
index 0543ac91..2eb4b3ce 100644
--- a/spring-core/src/main/java/org/springframework/util/backoff/BackOff.java
+++ b/spring-core/src/main/java/org/springframework/util/backoff/BackOff.java
@@ -26,7 +26,7 @@ package org.springframework.util.backoff;
* BackOffExecution exec = backOff.start();
*
* // In the operation recovery/retry loop:
- * long waitInterval = exec.nextBackOffMillis();
+ * long waitInterval = exec.nextBackOff();
* if (waitInterval == BackOffExecution.STOP) {
* // do not retry operation
* }
diff --git a/spring-core/src/main/java/org/springframework/util/comparator/CompoundComparator.java b/spring-core/src/main/java/org/springframework/util/comparator/CompoundComparator.java
index a1051404..c04735c8 100644
--- a/spring-core/src/main/java/org/springframework/util/comparator/CompoundComparator.java
+++ b/spring-core/src/main/java/org/springframework/util/comparator/CompoundComparator.java
@@ -24,7 +24,7 @@ import java.util.List;
import org.springframework.util.Assert;
/**
- * A comparator that chains a sequence of one or more more Comparators.
+ * A comparator that chains a sequence of one or more Comparators.
*
* <p>A compound comparator calls each Comparator in sequence until a single
* Comparator returns a non-zero result, or the comparators are exhausted and
diff --git a/spring-core/src/main/java/org/springframework/util/comparator/InstanceComparator.java b/spring-core/src/main/java/org/springframework/util/comparator/InstanceComparator.java
index 19d9d9bf..8f033bad 100644
--- a/spring-core/src/main/java/org/springframework/util/comparator/InstanceComparator.java
+++ b/spring-core/src/main/java/org/springframework/util/comparator/InstanceComparator.java
@@ -43,7 +43,7 @@ public class InstanceComparator<T> implements Comparator<T> {
/**
* Create a new {@link InstanceComparator} instance.
* @param instanceOrder the ordered list of classes that should be used when comparing
- * objects. Classes earlier in the list will be be given a higher priority.
+ * objects. Classes earlier in the list will be given a higher priority.
*/
public InstanceComparator(Class<?>... instanceOrder) {
Assert.notNull(instanceOrder, "'instanceOrder' must not be null");
diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/CompletableToListenableFutureAdapter.java b/spring-core/src/main/java/org/springframework/util/concurrent/CompletableToListenableFutureAdapter.java
index f42a88f4..1a6cd14f 100644
--- a/spring-core/src/main/java/org/springframework/util/concurrent/CompletableToListenableFutureAdapter.java
+++ b/spring-core/src/main/java/org/springframework/util/concurrent/CompletableToListenableFutureAdapter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,7 +24,6 @@ import java.util.function.BiFunction;
import org.springframework.lang.UsesJava8;
-
/**
* Adapts a {@link CompletableFuture} into a {@link ListenableFuture}.
*
@@ -38,6 +37,7 @@ public class CompletableToListenableFutureAdapter<T> implements ListenableFuture
private final ListenableFutureCallbackRegistry<T> callbacks = new ListenableFutureCallbackRegistry<T>();
+
public CompletableToListenableFutureAdapter(CompletableFuture<T> completableFuture) {
this.completableFuture = completableFuture;
this.completableFuture.handle(new BiFunction<T, Throwable, Object>() {
@@ -54,6 +54,7 @@ public class CompletableToListenableFutureAdapter<T> implements ListenableFuture
});
}
+
@Override
public void addCallback(ListenableFutureCallback<? super T> callback) {
this.callbacks.addCallback(callback);
diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/FailureCallback.java b/spring-core/src/main/java/org/springframework/util/concurrent/FailureCallback.java
index caac2874..20f21420 100644
--- a/spring-core/src/main/java/org/springframework/util/concurrent/FailureCallback.java
+++ b/spring-core/src/main/java/org/springframework/util/concurrent/FailureCallback.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author 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,8 +17,7 @@
package org.springframework.util.concurrent;
/**
- * Defines the contract for failure callbacks that accept the result of a
- * {@link ListenableFuture}.
+ * Failure callback for a {@link ListenableFuture}.
*
* @author Sebastien Deleuze
* @since 4.1
@@ -26,8 +25,9 @@ package org.springframework.util.concurrent;
public interface FailureCallback {
/**
- * Called when the {@link ListenableFuture} fails to complete.
- * @param ex the exception that triggered the failure
+ * Called when the {@link ListenableFuture} completes with failure.
+ * <p>Note that Exceptions raised by this method are ignored.
+ * @param ex the failure
*/
void onFailure(Throwable ex);
diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/FutureAdapter.java b/spring-core/src/main/java/org/springframework/util/concurrent/FutureAdapter.java
index 37aac931..2a36c9e6 100644
--- a/spring-core/src/main/java/org/springframework/util/concurrent/FutureAdapter.java
+++ b/spring-core/src/main/java/org/springframework/util/concurrent/FutureAdapter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,10 +24,9 @@ import java.util.concurrent.TimeoutException;
import org.springframework.util.Assert;
/**
- * Abstract class that adapts a {@link Future} parameterized over S into a {@code
- * Future} parameterized over T. All methods are delegated to the adaptee, where {@link
- * #get()} and {@link #get(long, TimeUnit)} call {@link #adapt(Object)} on the adaptee's
- * result.
+ * Abstract class that adapts a {@link Future} parameterized over S into a {@code Future}
+ * parameterized over T. All methods are delegated to the adaptee, where {@link #get()}
+ * and {@link #get(long, TimeUnit)} call {@link #adapt(Object)} on the adaptee's result.
*
* @author Arjen Poutsma
* @since 4.0
diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFuture.java b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFuture.java
index 6250b08f..5b842973 100644
--- a/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFuture.java
+++ b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFuture.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,11 +19,11 @@ package org.springframework.util.concurrent;
import java.util.concurrent.Future;
/**
- * Extends the {@link Future} interface with the capability to accept completion
- * callbacks. If the future has already completed when the callback is added, the
- * callback will be triggered immediately.
+ * Extend {@link Future} with the capability to accept completion callbacks.
+ * If the future has completed when the callback is added, the callback is
+ * triggered immediately.
* <p>Inspired by {@code com.google.common.util.concurrent.ListenableFuture}.
-
+ *
* @author Arjen Poutsma
* @author Sebastien Deleuze
* @since 4.0
@@ -31,20 +31,15 @@ import java.util.concurrent.Future;
public interface ListenableFuture<T> extends Future<T> {
/**
- * Registers the given callback to this {@code ListenableFuture}. The callback will
- * be triggered when this {@code Future} is complete or, if it is already complete,
- * immediately.
+ * Register the given {@code ListenableFutureCallback}.
* @param callback the callback to register
*/
void addCallback(ListenableFutureCallback<? super T> callback);
/**
- * Registers the given success and failure callbacks to this {@code ListenableFuture}.
- * The callback will be triggered when this {@code Future} is complete or, if it is
- * already complete immediately. This is a Java 8 lambdas compliant alternative to
- * {@link #addCallback(ListenableFutureCallback)}.
- * @param successCallback the success callback to register
- * @param failureCallback the failure callback to register
+ * Java 8 lambda-friendly alternative with success and failure callbacks.
+ * @param successCallback the success callback
+ * @param failureCallback the failure callback
* @since 4.1
*/
void addCallback(SuccessCallback<? super T> successCallback, FailureCallback failureCallback);
diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureAdapter.java b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureAdapter.java
index 268a6496..0f1624aa 100644
--- a/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureAdapter.java
+++ b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureAdapter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -52,16 +52,20 @@ public abstract class ListenableFutureAdapter<T, S> extends FutureAdapter<T, S>
listenableAdaptee.addCallback(new ListenableFutureCallback<S>() {
@Override
public void onSuccess(S result) {
+ T adapted;
try {
- successCallback.onSuccess(adaptInternal(result));
+ adapted = adaptInternal(result);
}
catch (ExecutionException ex) {
Throwable cause = ex.getCause();
onFailure(cause != null ? cause : ex);
+ return;
}
catch (Throwable ex) {
onFailure(ex);
+ return;
}
+ successCallback.onSuccess(adapted);
}
@Override
public void onFailure(Throwable ex) {
diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallback.java b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallback.java
index 63379445..4ce858c1 100644
--- a/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallback.java
+++ b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallback.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,7 @@
package org.springframework.util.concurrent;
/**
- * Defines the contract for callbacks that accept the result of a
+ * Callback mechanism for the outcome, success or failure, from a
* {@link ListenableFuture}.
*
* @author Arjen Poutsma
diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallbackRegistry.java b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallbackRegistry.java
index 1c4be730..46eb746b 100644
--- a/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallbackRegistry.java
+++ b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallbackRegistry.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author 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,12 +22,14 @@ import java.util.Queue;
import org.springframework.util.Assert;
/**
- * Registry for {@link ListenableFutureCallback} instances.
+ * Helper class for {@link ListenableFuture} implementations that maintains a
+ * of success and failure callbacks and helps to notify them.
*
* <p>Inspired by {@code com.google.common.util.concurrent.ExecutionList}.
*
* @author Arjen Poutsma
* @author Sebastien Deleuze
+ * @author Rossen Stoyanchev
* @since 4.0
*/
public class ListenableFutureCallbackRegistry<T> {
@@ -47,7 +49,6 @@ public class ListenableFutureCallbackRegistry<T> {
* Add the given callback to this registry.
* @param callback the callback to add
*/
- @SuppressWarnings("unchecked")
public void addCallback(ListenableFutureCallback<? super T> callback) {
Assert.notNull(callback, "'callback' must not be null");
synchronized (this.mutex) {
@@ -57,15 +58,34 @@ public class ListenableFutureCallbackRegistry<T> {
this.failureCallbacks.add(callback);
break;
case SUCCESS:
- callback.onSuccess((T) this.result);
+ notifySuccess(callback);
break;
case FAILURE:
- callback.onFailure((Throwable) this.result);
+ notifyFailure(callback);
break;
}
}
}
+ @SuppressWarnings("unchecked")
+ private void notifySuccess(SuccessCallback<? super T> callback) {
+ try {
+ callback.onSuccess((T) this.result);
+ }
+ catch (Throwable ex) {
+ // Ignore
+ }
+ }
+
+ private void notifyFailure(FailureCallback callback) {
+ try {
+ callback.onFailure((Throwable) this.result);
+ }
+ catch (Throwable ex) {
+ // Ignore
+ }
+ }
+
/**
* Add the given success callback to this registry.
* @param callback the success callback to add
@@ -80,7 +100,7 @@ public class ListenableFutureCallbackRegistry<T> {
this.successCallbacks.add(callback);
break;
case SUCCESS:
- callback.onSuccess((T) this.result);
+ notifySuccess(callback);
break;
}
}
@@ -99,7 +119,7 @@ public class ListenableFutureCallbackRegistry<T> {
this.failureCallbacks.add(callback);
break;
case FAILURE:
- callback.onFailure((Throwable) this.result);
+ notifyFailure(callback);
break;
}
}
@@ -115,7 +135,7 @@ public class ListenableFutureCallbackRegistry<T> {
this.state = State.SUCCESS;
this.result = result;
while (!this.successCallbacks.isEmpty()) {
- this.successCallbacks.poll().onSuccess(result);
+ notifySuccess(this.successCallbacks.poll());
}
}
}
@@ -130,7 +150,7 @@ public class ListenableFutureCallbackRegistry<T> {
this.state = State.FAILURE;
this.result = ex;
while (!this.failureCallbacks.isEmpty()) {
- this.failureCallbacks.poll().onFailure(ex);
+ notifyFailure(this.failureCallbacks.poll());
}
}
}
diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/SuccessCallback.java b/spring-core/src/main/java/org/springframework/util/concurrent/SuccessCallback.java
index 65f30411..b20bcad1 100644
--- a/spring-core/src/main/java/org/springframework/util/concurrent/SuccessCallback.java
+++ b/spring-core/src/main/java/org/springframework/util/concurrent/SuccessCallback.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author 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,8 +17,7 @@
package org.springframework.util.concurrent;
/**
- * Defines the contract for success callbacks that accept the result of a
- * {@link ListenableFuture}.
+ * Success callback for a {@link ListenableFuture}.
*
* @author Sebastien Deleuze
* @since 4.1
@@ -26,7 +25,8 @@ package org.springframework.util.concurrent;
public interface SuccessCallback<T> {
/**
- * Called when the {@link ListenableFuture} successfully completes.
+ * Called when the {@link ListenableFuture} completes with success.
+ * <p>Note that Exceptions raised by this method are ignored.
* @param result the result
*/
void onSuccess(T result);
diff --git a/spring-core/src/main/java/org/springframework/util/xml/XmlValidationModeDetector.java b/spring-core/src/main/java/org/springframework/util/xml/XmlValidationModeDetector.java
index d924fd15..227b216c 100644
--- a/spring-core/src/main/java/org/springframework/util/xml/XmlValidationModeDetector.java
+++ b/spring-core/src/main/java/org/springframework/util/xml/XmlValidationModeDetector.java
@@ -120,7 +120,7 @@ public class XmlValidationModeDetector {
/**
- * Does the content contain the the DTD DOCTYPE declaration?
+ * Does the content contain the DTD DOCTYPE declaration?
*/
private boolean hasDoctype(String content) {
return content.contains(DOCTYPE);
diff --git a/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java b/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java
index 1fed0c5b..7e7c3d5b 100644
--- a/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java
+++ b/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java
@@ -130,7 +130,7 @@ public class GenericTypeResolverTests {
Type t = null;
Type x = null;
for (Map.Entry<TypeVariable, Type> entry : map.entrySet()) {
- if(entry.getKey().toString().equals("T")) {
+ if (entry.getKey().toString().equals("T")) {
t = entry.getValue();
}
else {
diff --git a/spring-core/src/test/java/org/springframework/core/SerializableTypeWrapperTests.java b/spring-core/src/test/java/org/springframework/core/SerializableTypeWrapperTests.java
index ac68dace..ba669e0b 100644
--- a/spring-core/src/test/java/org/springframework/core/SerializableTypeWrapperTests.java
+++ b/spring-core/src/test/java/org/springframework/core/SerializableTypeWrapperTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -46,7 +46,7 @@ public class SerializableTypeWrapperTests {
public void forField() throws Exception {
Type type = SerializableTypeWrapper.forField(Fields.class.getField("parameterizedType"));
assertThat(type.toString(), equalTo("java.util.List<java.lang.String>"));
- assertSerialzable(type);
+ assertSerializable(type);
}
@Test
@@ -54,7 +54,7 @@ public class SerializableTypeWrapperTests {
Method method = Methods.class.getDeclaredMethod("method", Class.class, Object.class);
Type type = SerializableTypeWrapper.forMethodParameter(MethodParameter.forMethodOrConstructor(method, 0));
assertThat(type.toString(), equalTo("java.lang.Class<T>"));
- assertSerialzable(type);
+ assertSerializable(type);
}
@Test
@@ -62,62 +62,62 @@ public class SerializableTypeWrapperTests {
Constructor<?> constructor = Constructors.class.getDeclaredConstructor(List.class);
Type type = SerializableTypeWrapper.forMethodParameter(MethodParameter.forMethodOrConstructor(constructor, 0));
assertThat(type.toString(), equalTo("java.util.List<java.lang.String>"));
- assertSerialzable(type);
+ assertSerializable(type);
}
@Test
public void forGenericSuperClass() throws Exception {
Type type = SerializableTypeWrapper.forGenericSuperclass(ArrayList.class);
assertThat(type.toString(), equalTo("java.util.AbstractList<E>"));
- assertSerialzable(type);
+ assertSerializable(type);
}
@Test
public void forGenericInterfaces() throws Exception {
Type type = SerializableTypeWrapper.forGenericInterfaces(List.class)[0];
assertThat(type.toString(), equalTo("java.util.Collection<E>"));
- assertSerialzable(type);
+ assertSerializable(type);
}
@Test
- public void forTypeParamters() throws Exception {
+ public void forTypeParameters() throws Exception {
Type type = SerializableTypeWrapper.forTypeParameters(List.class)[0];
assertThat(type.toString(), equalTo("E"));
- assertSerialzable(type);
+ assertSerializable(type);
}
@Test
public void classType() throws Exception {
Type type = SerializableTypeWrapper.forField(Fields.class.getField("classType"));
assertThat(type.toString(), equalTo("class java.lang.String"));
- assertSerialzable(type);
+ assertSerializable(type);
}
@Test
public void genericArrayType() throws Exception {
GenericArrayType type = (GenericArrayType) SerializableTypeWrapper.forField(Fields.class.getField("genericArrayType"));
assertThat(type.toString(), equalTo("java.util.List<java.lang.String>[]"));
- assertSerialzable(type);
- assertSerialzable(type.getGenericComponentType());
+ assertSerializable(type);
+ assertSerializable(type.getGenericComponentType());
}
@Test
public void parameterizedType() throws Exception {
ParameterizedType type = (ParameterizedType) SerializableTypeWrapper.forField(Fields.class.getField("parameterizedType"));
assertThat(type.toString(), equalTo("java.util.List<java.lang.String>"));
- assertSerialzable(type);
- assertSerialzable(type.getOwnerType());
- assertSerialzable(type.getRawType());
- assertSerialzable(type.getActualTypeArguments());
- assertSerialzable(type.getActualTypeArguments()[0]);
+ assertSerializable(type);
+ assertSerializable(type.getOwnerType());
+ assertSerializable(type.getRawType());
+ assertSerializable(type.getActualTypeArguments());
+ assertSerializable(type.getActualTypeArguments()[0]);
}
@Test
public void typeVariableType() throws Exception {
TypeVariable<?> type = (TypeVariable<?>) SerializableTypeWrapper.forField(Fields.class.getField("typeVariableType"));
assertThat(type.toString(), equalTo("T"));
- assertSerialzable(type);
- assertSerialzable(type.getBounds());
+ assertSerializable(type);
+ assertSerializable(type.getBounds());
}
@Test
@@ -125,13 +125,13 @@ public class SerializableTypeWrapperTests {
ParameterizedType typeSource = (ParameterizedType) SerializableTypeWrapper.forField(Fields.class.getField("wildcardType"));
WildcardType type = (WildcardType) typeSource.getActualTypeArguments()[0];
assertThat(type.toString(), equalTo("? extends java.lang.CharSequence"));
- assertSerialzable(type);
- assertSerialzable(type.getLowerBounds());
- assertSerialzable(type.getUpperBounds());
+ assertSerializable(type);
+ assertSerializable(type.getLowerBounds());
+ assertSerializable(type.getUpperBounds());
}
- private void assertSerialzable(Object source) throws Exception {
+ private void assertSerializable(Object source) throws Exception {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(source);
@@ -152,19 +152,19 @@ public class SerializableTypeWrapperTests {
public T typeVariableType;
public List<? extends CharSequence> wildcardType;
-
}
- static interface Methods {
- <T> List<T> method(Class<T> p1, T p2);
+ interface Methods {
+ <T> List<T> method(Class<T> p1, T p2);
}
+
static class Constructors {
public Constructors(List<String> p) {
}
-
}
+
}
diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java
index 57dd9847..8230dd59 100644
--- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java
+++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,11 @@ import javax.annotation.Resource;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.internal.ArrayComparisonFailure;
import org.junit.rules.ExpectedException;
+import org.springframework.core.annotation.AnnotationUtilsTests.WebController;
+import org.springframework.core.annotation.AnnotationUtilsTests.WebMapping;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
@@ -44,6 +47,7 @@ import static java.util.stream.Collectors.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.springframework.core.annotation.AnnotatedElementUtils.*;
+import static org.springframework.core.annotation.AnnotationUtilsTests.*;
/**
* Unit tests for {@link AnnotatedElementUtils}.
@@ -51,6 +55,9 @@ import static org.springframework.core.annotation.AnnotatedElementUtils.*;
* @author Sam Brannen
* @author Rossen Stoyanchev
* @since 4.0.3
+ * @see AnnotationUtilsTests
+ * @see MultipleComposedAnnotationsOnSingleAnnotatedElementTests
+ * @see ComposedRepeatableAnnotationsTests
*/
public class AnnotatedElementUtilsTests {
@@ -63,18 +70,25 @@ public class AnnotatedElementUtilsTests {
@Test
public void getMetaAnnotationTypesOnNonAnnotatedClass() {
assertNull(getMetaAnnotationTypes(NonAnnotatedClass.class, TransactionalComponent.class));
+ assertNull(getMetaAnnotationTypes(NonAnnotatedClass.class, TransactionalComponent.class.getName()));
}
@Test
public void getMetaAnnotationTypesOnClassWithMetaDepth1() {
Set<String> names = getMetaAnnotationTypes(TransactionalComponentClass.class, TransactionalComponent.class);
assertEquals(names(Transactional.class, Component.class), names);
+
+ names = getMetaAnnotationTypes(TransactionalComponentClass.class, TransactionalComponent.class.getName());
+ assertEquals(names(Transactional.class, Component.class), names);
}
@Test
public void getMetaAnnotationTypesOnClassWithMetaDepth2() {
Set<String> names = getMetaAnnotationTypes(ComposedTransactionalComponentClass.class, ComposedTransactionalComponent.class);
assertEquals(names(TransactionalComponent.class, Transactional.class, Component.class), names);
+
+ names = getMetaAnnotationTypes(ComposedTransactionalComponentClass.class, ComposedTransactionalComponent.class.getName());
+ assertEquals(names(TransactionalComponent.class, Transactional.class, Component.class), names);
}
@Test
@@ -294,7 +308,15 @@ public class AnnotatedElementUtilsTests {
assertTrue(isAnnotated(element, name));
}
- @Ignore("Disabled until SPR-13554 is addressed")
+ /**
+ * This test should never pass, simply because Spring does not support a hybrid
+ * approach for annotation attribute overrides with transitive implicit aliases.
+ * See SPR-13554 for details.
+ * <p>Furthermore, if you choose to execute this test, it can fail for either
+ * the first test class or the second one (with different exceptions), depending
+ * on the order in which the JVM returns the attribute methods via reflection.
+ */
+ @Ignore("Permanently disabled but left in place for illustrative purposes")
@Test
public void getMergedAnnotationAttributesWithHalfConventionBasedAndHalfAliasedComposedAnnotation() {
for (Class<?> clazz : asList(HalfConventionBasedAndHalfAliasedComposedContextConfigClassV1.class,
@@ -380,10 +402,20 @@ public class AnnotatedElementUtilsTests {
}
@Test
+ public void getMergedAnnotationWithTransitiveImplicitAliasesWithSingleElementOverridingAnArrayViaAliasFor() {
+ assertGetMergedAnnotation(SingleLocationTransitiveImplicitAliasesContextConfigClass.class, "test.groovy");
+ }
+
+ @Test
public void getMergedAnnotationWithTransitiveImplicitAliasesWithSkippedLevel() {
assertGetMergedAnnotation(TransitiveImplicitAliasesWithSkippedLevelContextConfigClass.class, "test.xml");
}
+ @Test
+ public void getMergedAnnotationWithTransitiveImplicitAliasesWithSkippedLevelWithSingleElementOverridingAnArrayViaAliasFor() {
+ assertGetMergedAnnotation(SingleLocationTransitiveImplicitAliasesWithSkippedLevelContextConfigClass.class, "test.xml");
+ }
+
private void assertGetMergedAnnotation(Class<?> element, String... expected) {
String name = ContextConfig.class.getName();
ContextConfig contextConfig = getMergedAnnotation(element, ContextConfig.class);
@@ -428,15 +460,15 @@ public class AnnotatedElementUtilsTests {
}
@Test
- public void getMergedAnnotationAttributesWithInvalidAliasedComposedAnnotation() {
- Class<?> element = InvalidAliasedComposedContextConfigClass.class;
- exception.expect(AnnotationConfigurationException.class);
- exception.expectMessage(either(containsString("attribute 'value' and its alias 'locations'")).or(
- containsString("attribute 'locations' and its alias 'value'")));
- exception.expectMessage(either(containsString("values of [{duplicateDeclaration}] and [{test.xml}]")).or(
- containsString("values of [{test.xml}] and [{duplicateDeclaration}]")));
- exception.expectMessage(containsString("but only one is permitted"));
- getMergedAnnotationAttributes(element, ContextConfig.class);
+ public void getMergedAnnotationAttributesWithShadowedAliasComposedAnnotation() {
+ Class<?> element = ShadowedAliasComposedContextConfigClass.class;
+ AnnotationAttributes attributes = getMergedAnnotationAttributes(element, ContextConfig.class);
+
+ String[] expected = asArray("test.xml");
+
+ assertNotNull("Should find @ContextConfig on " + element.getSimpleName(), attributes);
+ assertArrayEquals("locations", expected, attributes.getStringArray("locations"));
+ assertArrayEquals("value", expected, attributes.getStringArray("value"));
}
@Test
@@ -561,6 +593,52 @@ public class AnnotatedElementUtilsTests {
}
@Test
+ public void findMergedAnnotationAttributesOnClassWithAttributeAliasInComposedAnnotationAndNestedAnnotationsInTargetAnnotation() {
+ AnnotationAttributes attributes = assertComponentScanAttributes(TestComponentScanClass.class, "com.example.app.test");
+
+ Filter[] excludeFilters = attributes.getAnnotationArray("excludeFilters", Filter.class);
+ assertNotNull(excludeFilters);
+
+ List<String> patterns = stream(excludeFilters).map(Filter::pattern).collect(toList());
+ assertEquals(asList("*Test", "*Tests"), patterns);
+ }
+
+ /**
+ * This test ensures that {@link AnnotationUtils#postProcessAnnotationAttributes}
+ * uses {@code ObjectUtils.nullSafeEquals()} to check for equality between annotation
+ * attributes since attributes may be arrays.
+ */
+ @Test
+ public void findMergedAnnotationAttributesOnClassWithBothAttributesOfAnAliasPairDeclared() {
+ assertComponentScanAttributes(ComponentScanWithBasePackagesAndValueAliasClass.class, "com.example.app.test");
+ }
+
+ @Test
+ public void findMergedAnnotationAttributesWithSingleElementOverridingAnArrayViaConvention() {
+ assertComponentScanAttributes(ConventionBasedSinglePackageComponentScanClass.class, "com.example.app.test");
+ }
+
+ @Test
+ public void findMergedAnnotationAttributesWithSingleElementOverridingAnArrayViaAliasFor() {
+ assertComponentScanAttributes(AliasForBasedSinglePackageComponentScanClass.class, "com.example.app.test");
+ }
+
+ private AnnotationAttributes assertComponentScanAttributes(Class<?> element, String... expected) {
+ AnnotationAttributes attributes = findMergedAnnotationAttributes(element, ComponentScan.class);
+
+ assertNotNull("Should find @ComponentScan on " + element, attributes);
+ assertArrayEquals("value: ", expected, attributes.getStringArray("value"));
+ assertArrayEquals("basePackages: ", expected, attributes.getStringArray("basePackages"));
+
+ return attributes;
+ }
+
+ private AnnotationAttributes findMergedAnnotationAttributes(AnnotatedElement element, Class<? extends Annotation> annotationType) {
+ Assert.notNull(annotationType, "annotationType must not be null");
+ return AnnotatedElementUtils.findMergedAnnotationAttributes(element, annotationType.getName(), false, false);
+ }
+
+ @Test
public void findMergedAnnotationWithAttributeAliasesInTargetAnnotation() {
Class<?> element = AliasedTransactionalComponentClass.class;
AliasedTransactional annotation = findMergedAnnotation(element, AliasedTransactional.class);
@@ -594,38 +672,6 @@ public class AnnotatedElementUtilsTests {
}
@Test
- public void findMergedAnnotationAttributesOnClassWithAttributeAliasInComposedAnnotationAndNestedAnnotationsInTargetAnnotation() {
- String[] expected = asArray("com.example.app.test");
- Class<?> element = TestComponentScanClass.class;
- AnnotationAttributes attributes = findMergedAnnotationAttributes(element, ComponentScan.class);
-
- assertNotNull("Should find @ComponentScan on " + element, attributes);
- assertArrayEquals("basePackages for " + element, expected, attributes.getStringArray("basePackages"));
-
- Filter[] excludeFilters = attributes.getAnnotationArray("excludeFilters", Filter.class);
- assertNotNull(excludeFilters);
-
- List<String> patterns = stream(excludeFilters).map(Filter::pattern).collect(toList());
- assertEquals(asList("*Test", "*Tests"), patterns);
- }
-
- /**
- * This test ensures that {@link AnnotationUtils#postProcessAnnotationAttributes}
- * uses {@code ObjectUtils.nullSafeEquals()} to check for equality between annotation
- * attributes since attributes may be arrays.
- */
- @Test
- public void findMergedAnnotationAttributesOnClassWithBothAttributesOfAnAliasPairDeclared() {
- String[] expected = asArray("com.example.app.test");
- Class<?> element = ComponentScanWithBasePackagesAndValueAliasClass.class;
- AnnotationAttributes attributes = findMergedAnnotationAttributes(element, ComponentScan.class);
-
- assertNotNull("Should find @ComponentScan on " + element, attributes);
- assertArrayEquals("value: ", expected, attributes.getStringArray("value"));
- assertArrayEquals("basePackages: ", expected, attributes.getStringArray("basePackages"));
- }
-
- @Test
public void findMergedAnnotationWithLocalAliasesThatConflictWithAttributesInMetaAnnotationByConvention() {
final String[] EMPTY = new String[0];
Class<?> element = SpringAppConfigClass.class;
@@ -639,6 +685,24 @@ public class AnnotatedElementUtilsTests {
}
@Test
+ public void findMergedAnnotationWithSingleElementOverridingAnArrayViaConvention() throws Exception {
+ assertWebMapping(WebController.class.getMethod("postMappedWithPathAttribute"));
+ }
+
+ @Test
+ public void findMergedAnnotationWithSingleElementOverridingAnArrayViaAliasFor() throws Exception {
+ assertWebMapping(WebController.class.getMethod("getMappedWithValueAttribute"));
+ assertWebMapping(WebController.class.getMethod("getMappedWithPathAttribute"));
+ }
+
+ private void assertWebMapping(AnnotatedElement element) throws ArrayComparisonFailure {
+ WebMapping webMapping = findMergedAnnotation(element, WebMapping.class);
+ assertNotNull(webMapping);
+ assertArrayEquals("value attribute: ", asArray("/test"), webMapping.value());
+ assertArrayEquals("path attribute: ", asArray("/test"), webMapping.path());
+ }
+
+ @Test
public void javaLangAnnotationTypeViaFindMergedAnnotation() throws Exception {
Constructor<?> deprecatedCtor = Date.class.getConstructor(String.class);
assertEquals(deprecatedCtor.getAnnotation(Deprecated.class), findMergedAnnotation(deprecatedCtor, Deprecated.class));
@@ -651,28 +715,10 @@ public class AnnotatedElementUtilsTests {
assertEquals(SpringAppConfigClass.class.getAnnotation(Resource.class), findMergedAnnotation(SpringAppConfigClass.class, Resource.class));
}
-
private Set<String> names(Class<?>... classes) {
return stream(classes).map(Class::getName).collect(toSet());
}
- @SafeVarargs
- // The following "varargs" suppression is necessary for javac from OpenJDK
- // (1.8.0_60-b27); however, Eclipse warns that it's unnecessary. See the following
- // Eclipse issues for details.
- //
- // https://bugs.eclipse.org/bugs/show_bug.cgi?id=344783
- // https://bugs.eclipse.org/bugs/show_bug.cgi?id=349669#c10
- // @SuppressWarnings("varargs")
- private static <T> T[] asArray(T... arr) {
- return arr;
- }
-
- private static AnnotationAttributes findMergedAnnotationAttributes(AnnotatedElement element, Class<? extends Annotation> annotationType) {
- Assert.notNull(annotationType, "annotationType must not be null");
- return AnnotatedElementUtils.findMergedAnnotationAttributes(element, annotationType.getName(), false, false);
- }
-
// -------------------------------------------------------------------------
@@ -821,6 +867,10 @@ public class AnnotatedElementUtilsTests {
String[] locations();
}
+ /**
+ * This hybrid approach for annotation attribute overrides with transitive implicit
+ * aliases is unsupported. See SPR-13554 for details.
+ */
@ContextConfig
@Retention(RetentionPolicy.RUNTIME)
@interface HalfConventionBasedAndHalfAliasedComposedContextConfig {
@@ -858,10 +908,12 @@ public class AnnotatedElementUtilsTests {
@AliasFor(annotation = ContextConfig.class, attribute = "locations")
String[] xmlFiles() default {};
- @AliasFor(annotation = ContextConfig.class, attribute = "locations")
+ // intentionally omitted: attribute = "locations"
+ @AliasFor(annotation = ContextConfig.class)
String[] locations() default {};
- @AliasFor(annotation = ContextConfig.class, attribute = "locations")
+ // intentionally omitted: attribute = "locations" (SPR-14069)
+ @AliasFor(annotation = ContextConfig.class)
String[] value() default {};
}
@@ -883,6 +935,17 @@ public class AnnotatedElementUtilsTests {
@ImplicitAliasesContextConfig
@Retention(RetentionPolicy.RUNTIME)
+ @interface SingleLocationTransitiveImplicitAliasesContextConfig {
+
+ @AliasFor(annotation = ImplicitAliasesContextConfig.class, attribute = "xmlFiles")
+ String xml() default "";
+
+ @AliasFor(annotation = ImplicitAliasesContextConfig.class, attribute = "groovyScripts")
+ String groovy() default "";
+ }
+
+ @ImplicitAliasesContextConfig
+ @Retention(RetentionPolicy.RUNTIME)
@interface TransitiveImplicitAliasesWithSkippedLevelContextConfig {
@AliasFor(annotation = ContextConfig.class, attribute = "locations")
@@ -892,15 +955,28 @@ public class AnnotatedElementUtilsTests {
String[] groovy() default {};
}
+ @ImplicitAliasesContextConfig
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface SingleLocationTransitiveImplicitAliasesWithSkippedLevelContextConfig {
+
+ @AliasFor(annotation = ContextConfig.class, attribute = "locations")
+ String xml() default "";
+
+ @AliasFor(annotation = ImplicitAliasesContextConfig.class, attribute = "groovyScripts")
+ String groovy() default "";
+ }
+
/**
- * Invalid because the configuration declares a value for 'value' and
- * requires a value for the aliased 'locations'. So we likely end up with
- * both 'value' and 'locations' being present in {@link AnnotationAttributes}
- * but with different values, which violates the contract of {@code @AliasFor}.
+ * Although the configuration declares an explicit value for 'value' and
+ * requires a value for the aliased 'locations', this does not result in
+ * an error since 'locations' effectively <em>shadows</em> the 'value'
+ * attribute (which cannot be set via the composed annotation anyway).
+ *
+ * If 'value' were not shadowed, such a declaration would not make sense.
*/
@ContextConfig(value = "duplicateDeclaration")
@Retention(RetentionPolicy.RUNTIME)
- @interface InvalidAliasedComposedContextConfig {
+ @interface ShadowedAliasComposedContextConfig {
@AliasFor(annotation = ContextConfig.class, attribute = "locations")
String[] xmlConfigFiles();
@@ -962,6 +1038,21 @@ public class AnnotatedElementUtilsTests {
String[] packages();
}
+ @ComponentScan
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface ConventionBasedSinglePackageComponentScan {
+
+ String basePackages();
+ }
+
+ @ComponentScan
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface AliasForBasedSinglePackageComponentScan {
+
+ @AliasFor(attribute = "basePackages", annotation = ComponentScan.class)
+ String pkg();
+ }
+
// -------------------------------------------------------------------------
static class NonAnnotatedClass {
@@ -1132,16 +1223,24 @@ public class AnnotatedElementUtilsTests {
static class TransitiveImplicitAliasesContextConfigClass {
}
+ @SingleLocationTransitiveImplicitAliasesContextConfig(groovy = "test.groovy")
+ static class SingleLocationTransitiveImplicitAliasesContextConfigClass {
+ }
+
@TransitiveImplicitAliasesWithSkippedLevelContextConfig(xml = "test.xml")
static class TransitiveImplicitAliasesWithSkippedLevelContextConfigClass {
}
+ @SingleLocationTransitiveImplicitAliasesWithSkippedLevelContextConfig(xml = "test.xml")
+ static class SingleLocationTransitiveImplicitAliasesWithSkippedLevelContextConfigClass {
+ }
+
@ComposedImplicitAliasesContextConfig
static class ComposedImplicitAliasesContextConfigClass {
}
- @InvalidAliasedComposedContextConfig(xmlConfigFiles = "test.xml")
- static class InvalidAliasedComposedContextConfigClass {
+ @ShadowedAliasComposedContextConfig(xmlConfigFiles = "test.xml")
+ static class ShadowedAliasComposedContextConfigClass {
}
@AliasedComposedContextConfigAndTestPropSource(xmlConfigFiles = "test.xml")
@@ -1156,6 +1255,14 @@ public class AnnotatedElementUtilsTests {
static class TestComponentScanClass {
}
+ @ConventionBasedSinglePackageComponentScan(basePackages = "com.example.app.test")
+ static class ConventionBasedSinglePackageComponentScanClass {
+ }
+
+ @AliasForBasedSinglePackageComponentScan(pkg = "com.example.app.test")
+ static class AliasForBasedSinglePackageComponentScanClass {
+ }
+
@SpringAppConfig(Number.class)
static class SpringAppConfigClass {
}
diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationAttributesTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationAttributesTests.java
index 57cdd7ab..5908bf36 100644
--- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationAttributesTests.java
+++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationAttributesTests.java
@@ -25,7 +25,6 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
-import org.springframework.core.annotation.AnnotationUtilsTests.ContextConfig;
import org.springframework.core.annotation.AnnotationUtilsTests.ImplicitAliasesContextConfig;
import static org.hamcrest.CoreMatchers.*;
@@ -133,21 +132,21 @@ public class AnnotationAttributesTests {
@Test
public void getEnumWithNullAttributeName() {
exception.expect(IllegalArgumentException.class);
- exception.expectMessage(containsString("attributeName must not be null or empty"));
+ exception.expectMessage("must not be null or empty");
attributes.getEnum(null);
}
@Test
public void getEnumWithEmptyAttributeName() {
exception.expect(IllegalArgumentException.class);
- exception.expectMessage(containsString("attributeName must not be null or empty"));
+ exception.expectMessage("must not be null or empty");
attributes.getEnum("");
}
@Test
public void getEnumWithUnknownAttributeName() {
exception.expect(IllegalArgumentException.class);
- exception.expectMessage(containsString("Attribute 'bogus' not found"));
+ exception.expectMessage("Attribute 'bogus' not found");
attributes.getEnum("bogus");
}
@@ -160,334 +159,66 @@ public class AnnotationAttributesTests {
}
@Test
- public void getAliasedString() {
- final String value = "metaverse";
-
- attributes.clear();
- attributes.put("name", value);
- assertEquals(value, getAliasedString("name"));
- assertEquals(value, getAliasedString("value"));
-
- attributes.clear();
- attributes.put("value", value);
- assertEquals(value, getAliasedString("name"));
- assertEquals(value, getAliasedString("value"));
-
- attributes.clear();
- attributes.put("name", value);
- attributes.put("value", value);
- assertEquals(value, getAliasedString("name"));
- assertEquals(value, getAliasedString("value"));
- }
-
- @Test
public void getAliasedStringWithImplicitAliases() {
- final String value = "metaverse";
- final List<String> aliases = Arrays.asList("value", "location1", "location2", "location3", "xmlFile", "groovyScript");
+ String value = "metaverse";
+ List<String> aliases = Arrays.asList("value", "location1", "location2", "location3", "xmlFile", "groovyScript");
attributes = new AnnotationAttributes(ImplicitAliasesContextConfig.class);
attributes.put("value", value);
- aliases.stream().forEach(alias -> assertEquals(value, getAliasedStringWithImplicitAliases(alias)));
+ AnnotationUtils.postProcessAnnotationAttributes(null, attributes, false);
+ aliases.stream().forEach(alias -> assertEquals(value, attributes.getString(alias)));
- attributes.clear();
+ attributes = new AnnotationAttributes(ImplicitAliasesContextConfig.class);
attributes.put("location1", value);
- aliases.stream().forEach(alias -> assertEquals(value, getAliasedStringWithImplicitAliases(alias)));
+ AnnotationUtils.postProcessAnnotationAttributes(null, attributes, false);
+ aliases.stream().forEach(alias -> assertEquals(value, attributes.getString(alias)));
- attributes.clear();
+ attributes = new AnnotationAttributes(ImplicitAliasesContextConfig.class);
attributes.put("value", value);
attributes.put("location1", value);
attributes.put("xmlFile", value);
attributes.put("groovyScript", value);
- aliases.stream().forEach(alias -> assertEquals(value, getAliasedStringWithImplicitAliases(alias)));
- }
-
- @Test
- public void getAliasedStringWithImplicitAliasesWithMissingAliasedAttributes() {
- final List<String> aliases = Arrays.asList("value", "location1", "location2", "location3", "xmlFile", "groovyScript");
- attributes = new AnnotationAttributes(ImplicitAliasesContextConfig.class);
-
- exception.expect(IllegalArgumentException.class);
- exception.expectMessage(startsWith("Neither attribute 'value' nor one of its aliases ["));
- aliases.stream().forEach(alias -> exception.expectMessage(containsString(alias)));
- exception.expectMessage(endsWith("] was found in attributes for annotation [" + ImplicitAliasesContextConfig.class.getName() + "]"));
- getAliasedStringWithImplicitAliases("value");
- }
-
- @Test
- public void getAliasedStringFromSynthesizedAnnotationAttributes() {
- Scope scope = ScopedComponent.class.getAnnotation(Scope.class);
- AnnotationAttributes scopeAttributes = AnnotationUtils.getAnnotationAttributes(ScopedComponent.class, scope);
-
- assertEquals("custom", getAliasedString(scopeAttributes, "name"));
- assertEquals("custom", getAliasedString(scopeAttributes, "value"));
- }
-
- @Test
- public void getAliasedStringWithMissingAliasedAttributes() {
- exception.expect(IllegalArgumentException.class);
- exception.expectMessage(equalTo("Neither attribute 'name' nor one of its aliases [value] was found in attributes for annotation [unknown]"));
- getAliasedString("name");
- }
-
- @Test
- public void getAliasedStringWithDifferentAliasedValues() {
- attributes.put("name", "request");
- attributes.put("value", "session");
-
- exception.expect(AnnotationConfigurationException.class);
- exception.expectMessage(containsString("In annotation [" + Scope.class.getName() + "]"));
- exception.expectMessage(containsString("attribute [name] and its alias [value]"));
- exception.expectMessage(containsString("[request] and [session]"));
- exception.expectMessage(containsString("but only one is permitted"));
-
- getAliasedString("name");
- }
-
- private String getAliasedString(String attributeName) {
- return getAliasedString(this.attributes, attributeName);
- }
-
- private String getAliasedString(AnnotationAttributes attrs, String attributeName) {
- return attrs.getAliasedString(attributeName, Scope.class, null);
- }
-
- private String getAliasedStringWithImplicitAliases(String attributeName) {
- return this.attributes.getAliasedString(attributeName, ImplicitAliasesContextConfig.class, null);
- }
-
- @Test
- public void getAliasedStringArray() {
- final String[] INPUT = new String[] {"test.xml"};
- final String[] EMPTY = new String[0];
-
- attributes.clear();
- attributes.put("location", INPUT);
- assertArrayEquals(INPUT, getAliasedStringArray("location"));
- assertArrayEquals(INPUT, getAliasedStringArray("value"));
-
- attributes.clear();
- attributes.put("value", INPUT);
- assertArrayEquals(INPUT, getAliasedStringArray("location"));
- assertArrayEquals(INPUT, getAliasedStringArray("value"));
-
- attributes.clear();
- attributes.put("location", INPUT);
- attributes.put("value", INPUT);
- assertArrayEquals(INPUT, getAliasedStringArray("location"));
- assertArrayEquals(INPUT, getAliasedStringArray("value"));
-
- attributes.clear();
- attributes.put("location", INPUT);
- attributes.put("value", EMPTY);
- assertArrayEquals(INPUT, getAliasedStringArray("location"));
- assertArrayEquals(INPUT, getAliasedStringArray("value"));
-
- attributes.clear();
- attributes.put("location", EMPTY);
- attributes.put("value", INPUT);
- assertArrayEquals(INPUT, getAliasedStringArray("location"));
- assertArrayEquals(INPUT, getAliasedStringArray("value"));
-
- attributes.clear();
- attributes.put("location", EMPTY);
- attributes.put("value", EMPTY);
- assertArrayEquals(EMPTY, getAliasedStringArray("location"));
- assertArrayEquals(EMPTY, getAliasedStringArray("value"));
+ AnnotationUtils.postProcessAnnotationAttributes(null, attributes, false);
+ aliases.stream().forEach(alias -> assertEquals(value, attributes.getString(alias)));
}
@Test
public void getAliasedStringArrayWithImplicitAliases() {
- final String[] INPUT = new String[] {"test.xml"};
- final String[] EMPTY = new String[0];
- final List<String> aliases = Arrays.asList("value", "location1", "location2", "location3", "xmlFile", "groovyScript");
+ String[] value = new String[] {"test.xml"};
+ List<String> aliases = Arrays.asList("value", "location1", "location2", "location3", "xmlFile", "groovyScript");
attributes = new AnnotationAttributes(ImplicitAliasesContextConfig.class);
+ attributes.put("location1", value);
+ AnnotationUtils.postProcessAnnotationAttributes(null, attributes, false);
+ aliases.stream().forEach(alias -> assertArrayEquals(value, attributes.getStringArray(alias)));
- attributes.put("location1", INPUT);
- aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedStringArrayWithImplicitAliases(alias)));
-
- attributes.clear();
- attributes.put("value", INPUT);
- aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedStringArrayWithImplicitAliases(alias)));
-
- attributes.clear();
- attributes.put("location1", INPUT);
- attributes.put("value", INPUT);
- aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedStringArrayWithImplicitAliases(alias)));
-
- attributes.clear();
- attributes.put("location1", INPUT);
- attributes.put("value", EMPTY);
- aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedStringArrayWithImplicitAliases(alias)));
-
- attributes.clear();
- attributes.put("location1", EMPTY);
- attributes.put("value", INPUT);
- aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedStringArrayWithImplicitAliases(alias)));
-
- attributes.clear();
- attributes.put("location1", EMPTY);
- attributes.put("value", EMPTY);
- aliases.stream().forEach(alias -> assertArrayEquals(EMPTY, getAliasedStringArrayWithImplicitAliases(alias)));
- }
-
- @Test
- public void getAliasedStringArrayWithImplicitAliasesWithMissingAliasedAttributes() {
- final List<String> aliases = Arrays.asList("value", "location1", "location2", "location3", "xmlFile", "groovyScript");
attributes = new AnnotationAttributes(ImplicitAliasesContextConfig.class);
-
- exception.expect(IllegalArgumentException.class);
- exception.expectMessage(startsWith("Neither attribute 'value' nor one of its aliases ["));
- aliases.stream().forEach(alias -> exception.expectMessage(containsString(alias)));
- exception.expectMessage(endsWith("] was found in attributes for annotation [" + ImplicitAliasesContextConfig.class.getName() + "]"));
- getAliasedStringArrayWithImplicitAliases("value");
- }
-
- @Test
- public void getAliasedStringArrayWithMissingAliasedAttributes() {
- exception.expect(IllegalArgumentException.class);
- exception.expectMessage(equalTo("Neither attribute 'location' nor one of its aliases [value] was found in attributes for annotation [unknown]"));
- getAliasedStringArray("location");
- }
-
- @Test
- public void getAliasedStringArrayWithDifferentAliasedValues() {
- attributes.put("location", new String[] {"1.xml"});
- attributes.put("value", new String[] {"2.xml"});
-
- exception.expect(AnnotationConfigurationException.class);
- exception.expectMessage(containsString("In annotation [" + ContextConfig.class.getName() + "]"));
- exception.expectMessage(containsString("attribute [location] and its alias [value]"));
- exception.expectMessage(containsString("[{1.xml}] and [{2.xml}]"));
- exception.expectMessage(containsString("but only one is permitted"));
-
- getAliasedStringArray("location");
- }
-
- private String[] getAliasedStringArray(String attributeName) {
- // Note: even though the attributes we test against here are of type
- // String instead of String[], it doesn't matter... since
- // AnnotationAttributes does not validate the actual return type of
- // attributes in the annotation.
- return attributes.getAliasedStringArray(attributeName, ContextConfig.class, null);
- }
-
- private String[] getAliasedStringArrayWithImplicitAliases(String attributeName) {
- // Note: even though the attributes we test against here are of type
- // String instead of String[], it doesn't matter... since
- // AnnotationAttributes does not validate the actual return type of
- // attributes in the annotation.
- return this.attributes.getAliasedStringArray(attributeName, ImplicitAliasesContextConfig.class, null);
- }
-
- @Test
- public void getAliasedClassArray() {
- final Class<?>[] INPUT = new Class<?>[] {String.class};
- final Class<?>[] EMPTY = new Class<?>[0];
-
- attributes.clear();
- attributes.put("classes", INPUT);
- assertArrayEquals(INPUT, getAliasedClassArray("classes"));
- assertArrayEquals(INPUT, getAliasedClassArray("value"));
-
- attributes.clear();
- attributes.put("value", INPUT);
- assertArrayEquals(INPUT, getAliasedClassArray("classes"));
- assertArrayEquals(INPUT, getAliasedClassArray("value"));
-
- attributes.clear();
- attributes.put("classes", INPUT);
- attributes.put("value", INPUT);
- assertArrayEquals(INPUT, getAliasedClassArray("classes"));
- assertArrayEquals(INPUT, getAliasedClassArray("value"));
-
- attributes.clear();
- attributes.put("classes", INPUT);
- attributes.put("value", EMPTY);
- assertArrayEquals(INPUT, getAliasedClassArray("classes"));
- assertArrayEquals(INPUT, getAliasedClassArray("value"));
-
- attributes.clear();
- attributes.put("classes", EMPTY);
- attributes.put("value", INPUT);
- assertArrayEquals(INPUT, getAliasedClassArray("classes"));
- assertArrayEquals(INPUT, getAliasedClassArray("value"));
-
- attributes.clear();
- attributes.put("classes", EMPTY);
- attributes.put("value", EMPTY);
- assertArrayEquals(EMPTY, getAliasedClassArray("classes"));
- assertArrayEquals(EMPTY, getAliasedClassArray("value"));
- }
-
- @Test
- public void getAliasedClassArrayWithImplicitAliases() {
- final Class<?>[] INPUT = new Class<?>[] {String.class};
- final Class<?>[] EMPTY = new Class<?>[0];
- final List<String> aliases = Arrays.asList("value", "location1", "location2", "location3", "xmlFile", "groovyScript");
+ attributes.put("value", value);
+ AnnotationUtils.postProcessAnnotationAttributes(null, attributes, false);
+ aliases.stream().forEach(alias -> assertArrayEquals(value, attributes.getStringArray(alias)));
attributes = new AnnotationAttributes(ImplicitAliasesContextConfig.class);
+ attributes.put("location1", value);
+ attributes.put("value", value);
+ AnnotationUtils.postProcessAnnotationAttributes(null, attributes, false);
+ aliases.stream().forEach(alias -> assertArrayEquals(value, attributes.getStringArray(alias)));
- attributes.put("location1", INPUT);
- aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedClassArrayWithImplicitAliases(alias)));
-
- attributes.clear();
- attributes.put("value", INPUT);
- aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedClassArrayWithImplicitAliases(alias)));
-
- attributes.clear();
- attributes.put("location1", INPUT);
- attributes.put("value", INPUT);
- aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedClassArrayWithImplicitAliases(alias)));
-
- attributes.clear();
- attributes.put("location1", INPUT);
- attributes.put("value", EMPTY);
- aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedClassArrayWithImplicitAliases(alias)));
-
- attributes.clear();
- attributes.put("location1", EMPTY);
- attributes.put("value", INPUT);
- aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedClassArrayWithImplicitAliases(alias)));
-
- attributes.clear();
- attributes.put("location1", EMPTY);
- attributes.put("value", EMPTY);
- aliases.stream().forEach(alias -> assertArrayEquals(EMPTY, getAliasedClassArrayWithImplicitAliases(alias)));
- }
-
- @Test
- public void getAliasedClassArrayWithMissingAliasedAttributes() {
- exception.expect(IllegalArgumentException.class);
- exception.expectMessage(equalTo("Neither attribute 'classes' nor one of its aliases [value] was found in attributes for annotation [unknown]"));
- getAliasedClassArray("classes");
- }
-
- @Test
- public void getAliasedClassArrayWithDifferentAliasedValues() {
- attributes.put("classes", new Class<?>[] {String.class});
- attributes.put("value", new Class<?>[] {Number.class});
-
- exception.expect(AnnotationConfigurationException.class);
- exception.expectMessage(containsString("In annotation [" + Filter.class.getName() + "]"));
- exception.expectMessage(containsString("attribute [classes] and its alias [value]"));
- exception.expectMessage(containsString("[{class java.lang.String}] and [{class java.lang.Number}]"));
- exception.expectMessage(containsString("but only one is permitted"));
-
- getAliasedClassArray("classes");
- }
-
+ attributes = new AnnotationAttributes(ImplicitAliasesContextConfig.class);
+ attributes.put("location1", value);
+ AnnotationUtils.registerDefaultValues(attributes);
+ AnnotationUtils.postProcessAnnotationAttributes(null, attributes, false);
+ aliases.stream().forEach(alias -> assertArrayEquals(value, attributes.getStringArray(alias)));
- private Class<?>[] getAliasedClassArray(String attributeName) {
- return attributes.getAliasedClassArray(attributeName, Filter.class, null);
- }
+ attributes = new AnnotationAttributes(ImplicitAliasesContextConfig.class);
+ attributes.put("value", value);
+ AnnotationUtils.registerDefaultValues(attributes);
+ AnnotationUtils.postProcessAnnotationAttributes(null, attributes, false);
+ aliases.stream().forEach(alias -> assertArrayEquals(value, attributes.getStringArray(alias)));
- private Class<?>[] getAliasedClassArrayWithImplicitAliases(String attributeName) {
- // Note: even though the attributes we test against here are of type
- // String instead of Class<?>[], it doesn't matter... since
- // AnnotationAttributes does not validate the actual return type of
- // attributes in the annotation.
- return this.attributes.getAliasedClassArray(attributeName, ImplicitAliasesContextConfig.class, null);
+ attributes = new AnnotationAttributes(ImplicitAliasesContextConfig.class);
+ AnnotationUtils.registerDefaultValues(attributes);
+ AnnotationUtils.postProcessAnnotationAttributes(null, attributes, false);
+ aliases.stream().forEach(alias -> assertArrayEquals(new String[] {""}, attributes.getStringArray(alias)));
}
@@ -514,23 +245,4 @@ public class AnnotationAttributesTests {
static class FilteredClass {
}
-
- /**
- * Mock of {@code org.springframework.context.annotation.Scope}.
- */
- @Retention(RetentionPolicy.RUNTIME)
- @interface Scope {
-
- @AliasFor(attribute = "name")
- String value() default "singleton";
-
- @AliasFor(attribute = "value")
- String name() default "singleton";
- }
-
-
- @Scope(name = "custom")
- static class ScopedComponent {
- }
-
}
diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java
index cbf9e974..c8d5170c 100644
--- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java
+++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -210,8 +210,7 @@ public class AnnotationUtilsTests {
/** @since 4.1.2 */
@Test
public void findClassAnnotationFavorsMoreLocallyDeclaredComposedAnnotationsOverAnnotationsOnInterfaces() {
- Component component = findAnnotation(ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class,
- Component.class);
+ Component component = findAnnotation(ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class, Component.class);
assertNotNull(component);
assertEquals("meta2", component.value());
}
@@ -330,7 +329,7 @@ public class AnnotationUtilsTests {
@Test
public void findAnnotationDeclaringClassForTypesWithSingleCandidateType() {
// no class-level annotation
- List<Class<? extends Annotation>> transactionalCandidateList = asList(Transactional.class);
+ List<Class<? extends Annotation>> transactionalCandidateList = Collections.singletonList(Transactional.class);
assertNull(findAnnotationDeclaringClassForTypes(transactionalCandidateList, NonAnnotatedInterface.class));
assertNull(findAnnotationDeclaringClassForTypes(transactionalCandidateList, NonAnnotatedClass.class));
@@ -345,7 +344,7 @@ public class AnnotationUtilsTests {
// non-inherited class-level annotation; note: @Order is not inherited,
// but findAnnotationDeclaringClassForTypes() should still find it on classes.
- List<Class<? extends Annotation>> orderCandidateList = asList(Order.class);
+ List<Class<? extends Annotation>> orderCandidateList = Collections.singletonList(Order.class);
assertEquals(NonInheritedAnnotationInterface.class,
findAnnotationDeclaringClassForTypes(orderCandidateList, NonInheritedAnnotationInterface.class));
assertNull(findAnnotationDeclaringClassForTypes(orderCandidateList, SubNonInheritedAnnotationInterface.class));
@@ -466,8 +465,8 @@ public class AnnotationUtilsTests {
assertNotNull(attributes);
assertEquals(WebMapping.class, attributes.annotationType());
assertEquals("name attribute: ", "foo", attributes.getString("name"));
- assertEquals("value attribute: ", "/test", attributes.getString(VALUE));
- assertEquals("path attribute: ", "/test", attributes.getString("path"));
+ assertArrayEquals("value attribute: ", asArray("/test"), attributes.getStringArray(VALUE));
+ assertArrayEquals("path attribute: ", asArray("/test"), attributes.getStringArray("path"));
method = WebController.class.getMethod("handleMappedWithPathAttribute");
webMapping = method.getAnnotation(WebMapping.class);
@@ -475,15 +474,18 @@ public class AnnotationUtilsTests {
assertNotNull(attributes);
assertEquals(WebMapping.class, attributes.annotationType());
assertEquals("name attribute: ", "bar", attributes.getString("name"));
- assertEquals("value attribute: ", "/test", attributes.getString(VALUE));
- assertEquals("path attribute: ", "/test", attributes.getString("path"));
+ assertArrayEquals("value attribute: ", asArray("/test"), attributes.getStringArray(VALUE));
+ assertArrayEquals("path attribute: ", asArray("/test"), attributes.getStringArray("path"));
+ }
- method = WebController.class.getMethod("handleMappedWithDifferentPathAndValueAttributes");
- webMapping = method.getAnnotation(WebMapping.class);
+ @Test
+ public void getAnnotationAttributesWithAttributeAliasesWithDifferentValues() throws Exception {
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(containsString("attribute 'value' and its alias 'path'"));
- exception.expectMessage(containsString("values of [/enigma] and [/test]"));
- exception.expectMessage(endsWith("but only one is permitted."));
+ exception.expectMessage(containsString("values of [{/enigma}] and [{/test}]"));
+
+ Method method = WebController.class.getMethod("handleMappedWithDifferentPathAndValueAttributes");
+ WebMapping webMapping = method.getAnnotation(WebMapping.class);
getAnnotationAttributes(webMapping);
}
@@ -552,9 +554,10 @@ public class AnnotationUtilsTests {
@Test
public void getRepeatableAnnotationsDeclaredOnClassWithMissingAttributeAliasDeclaration() throws Exception {
exception.expect(AnnotationConfigurationException.class);
- exception.expectMessage(startsWith("Attribute [value] in"));
+ exception.expectMessage(startsWith("Attribute 'value' in"));
exception.expectMessage(containsString(BrokenContextConfig.class.getName()));
- exception.expectMessage(endsWith("must be declared as an @AliasFor [location]."));
+ exception.expectMessage(containsString("@AliasFor [location]"));
+
getRepeatableAnnotations(BrokenConfigHierarchyTestCase.class, BrokenContextConfig.class, BrokenHierarchy.class);
}
@@ -776,6 +779,25 @@ public class AnnotationUtilsTests {
}
@Test
+ public void getAttributeAliasNamesFromComposedAnnotationWithImplicitAliasesWithImpliedAliasNamesOmitted()
+ throws Exception {
+
+ Method value = ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class.getDeclaredMethod("value");
+ Method location = ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class.getDeclaredMethod("location");
+ Method xmlFile = ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class.getDeclaredMethod("xmlFile");
+
+ // Meta-annotation attribute overrides
+ assertEquals("value", getAttributeOverrideName(value, ContextConfig.class));
+ assertEquals("location", getAttributeOverrideName(location, ContextConfig.class));
+ assertEquals("location", getAttributeOverrideName(xmlFile, ContextConfig.class));
+
+ // Implicit aliases
+ assertThat(getAttributeAliasNames(value), containsInAnyOrder("location", "xmlFile"));
+ assertThat(getAttributeAliasNames(location), containsInAnyOrder("value", "xmlFile"));
+ assertThat(getAttributeAliasNames(xmlFile), containsInAnyOrder("value", "location"));
+ }
+
+ @Test
public void getAttributeAliasNamesFromComposedAnnotationWithTransitiveImplicitAliases() throws Exception {
Method xml = TransitiveImplicitAliasesContextConfig.class.getDeclaredMethod("xml");
Method groovy = TransitiveImplicitAliasesContextConfig.class.getDeclaredMethod("groovy");
@@ -808,6 +830,26 @@ public class AnnotationUtilsTests {
}
@Test
+ public void getAttributeAliasNamesFromComposedAnnotationWithTransitiveImplicitAliasesWithImpliedAliasNamesOmitted()
+ throws Exception {
+
+ Method xml = TransitiveImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class.getDeclaredMethod("xml");
+ Method groovy = TransitiveImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class.getDeclaredMethod("groovy");
+
+ // Meta-annotation attribute overrides
+ assertEquals("location", getAttributeOverrideName(xml, ContextConfig.class));
+ assertEquals("location", getAttributeOverrideName(groovy, ContextConfig.class));
+
+ // Explicit meta-annotation attribute overrides
+ assertEquals("xmlFile", getAttributeOverrideName(xml, ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class));
+ assertEquals("location", getAttributeOverrideName(groovy, ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class));
+
+ // Transitive implicit aliases
+ assertThat(getAttributeAliasNames(groovy), containsInAnyOrder("xml"));
+ assertThat(getAttributeAliasNames(xml), containsInAnyOrder("groovy"));
+ }
+
+ @Test
public void synthesizeAnnotationWithoutAttributeAliases() throws Exception {
Component component = WebController.class.getAnnotation(Component.class);
assertNotNull(component);
@@ -835,17 +877,17 @@ public class AnnotationUtilsTests {
assertSame(synthesizedWebMapping, synthesizedAgainWebMapping);
assertEquals("name attribute: ", "foo", synthesizedAgainWebMapping.name());
- assertEquals("aliased path attribute: ", "/test", synthesizedAgainWebMapping.path());
- assertEquals("actual value attribute: ", "/test", synthesizedAgainWebMapping.value());
+ assertArrayEquals("aliased path attribute: ", asArray("/test"), synthesizedAgainWebMapping.path());
+ assertArrayEquals("actual value attribute: ", asArray("/test"), synthesizedAgainWebMapping.value());
}
@Test
public void synthesizeAnnotationWhereAliasForIsMissingAttributeDeclaration() throws Exception {
AliasForWithMissingAttributeDeclaration annotation = AliasForWithMissingAttributeDeclarationClass.class.getAnnotation(AliasForWithMissingAttributeDeclaration.class);
exception.expect(AnnotationConfigurationException.class);
- exception.expectMessage(startsWith("@AliasFor declaration on attribute [foo] in annotation"));
+ exception.expectMessage(startsWith("@AliasFor declaration on attribute 'foo' in annotation"));
exception.expectMessage(containsString(AliasForWithMissingAttributeDeclaration.class.getName()));
- exception.expectMessage(endsWith("is missing required 'attribute' value."));
+ exception.expectMessage(containsString("points to itself"));
synthesizeAnnotation(annotation);
}
@@ -853,10 +895,9 @@ public class AnnotationUtilsTests {
public void synthesizeAnnotationWhereAliasForHasDuplicateAttributeDeclaration() throws Exception {
AliasForWithDuplicateAttributeDeclaration annotation = AliasForWithDuplicateAttributeDeclarationClass.class.getAnnotation(AliasForWithDuplicateAttributeDeclaration.class);
exception.expect(AnnotationConfigurationException.class);
- exception.expectMessage(startsWith("In @AliasFor declared on attribute [foo] in annotation"));
+ exception.expectMessage(startsWith("In @AliasFor declared on attribute 'foo' in annotation"));
exception.expectMessage(containsString(AliasForWithDuplicateAttributeDeclaration.class.getName()));
exception.expectMessage(containsString("attribute 'attribute' and its alias 'value' are present with values of [baz] and [bar]"));
- exception.expectMessage(endsWith("but only one is permitted."));
synthesizeAnnotation(annotation);
}
@@ -864,9 +905,9 @@ public class AnnotationUtilsTests {
public void synthesizeAnnotationWithAttributeAliasForNonexistentAttribute() throws Exception {
AliasForNonexistentAttribute annotation = AliasForNonexistentAttributeClass.class.getAnnotation(AliasForNonexistentAttribute.class);
exception.expect(AnnotationConfigurationException.class);
- exception.expectMessage(startsWith("Attribute [foo] in"));
+ exception.expectMessage(startsWith("Attribute 'foo' in"));
exception.expectMessage(containsString(AliasForNonexistentAttribute.class.getName()));
- exception.expectMessage(containsString("is declared as an @AliasFor nonexistent attribute [bar]"));
+ exception.expectMessage(containsString("is declared as an @AliasFor nonexistent attribute 'bar'"));
synthesizeAnnotation(annotation);
}
@@ -875,9 +916,9 @@ public class AnnotationUtilsTests {
AliasForWithoutMirroredAliasFor annotation =
AliasForWithoutMirroredAliasForClass.class.getAnnotation(AliasForWithoutMirroredAliasFor.class);
exception.expect(AnnotationConfigurationException.class);
- exception.expectMessage(startsWith("Attribute [bar] in"));
+ exception.expectMessage(startsWith("Attribute 'bar' in"));
exception.expectMessage(containsString(AliasForWithoutMirroredAliasFor.class.getName()));
- exception.expectMessage(endsWith("must be declared as an @AliasFor [foo]."));
+ exception.expectMessage(containsString("@AliasFor [foo]"));
synthesizeAnnotation(annotation);
}
@@ -887,10 +928,10 @@ public class AnnotationUtilsTests {
AliasForWithMirroredAliasForWrongAttributeClass.class.getAnnotation(AliasForWithMirroredAliasForWrongAttribute.class);
exception.expect(AnnotationConfigurationException.class);
- exception.expectMessage(startsWith("Attribute [bar] in"));
+ exception.expectMessage(startsWith("Attribute 'bar' in"));
exception.expectMessage(containsString(AliasForWithMirroredAliasForWrongAttribute.class.getName()));
exception.expectMessage(either(containsString("must be declared as an @AliasFor [foo], not [quux]")).
- or(containsString("is declared as an @AliasFor nonexistent attribute [quux]")));
+ or(containsString("is declared as an @AliasFor nonexistent attribute 'quux'")));
synthesizeAnnotation(annotation);
}
@@ -901,9 +942,9 @@ public class AnnotationUtilsTests {
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("Misconfigured aliases"));
exception.expectMessage(containsString(AliasForAttributeOfDifferentType.class.getName()));
- exception.expectMessage(containsString("attribute [foo]"));
- exception.expectMessage(containsString("attribute [bar]"));
- exception.expectMessage(endsWith("must declare the same return type."));
+ exception.expectMessage(containsString("attribute 'foo'"));
+ exception.expectMessage(containsString("attribute 'bar'"));
+ exception.expectMessage(containsString("same return type"));
synthesizeAnnotation(annotation);
}
@@ -914,9 +955,9 @@ public class AnnotationUtilsTests {
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("Misconfigured aliases"));
exception.expectMessage(containsString(AliasForWithMissingDefaultValues.class.getName()));
- exception.expectMessage(containsString("attribute [foo] in annotation"));
- exception.expectMessage(containsString("attribute [bar] in annotation"));
- exception.expectMessage(endsWith("must declare default values."));
+ exception.expectMessage(containsString("attribute 'foo' in annotation"));
+ exception.expectMessage(containsString("attribute 'bar' in annotation"));
+ exception.expectMessage(containsString("default values"));
synthesizeAnnotation(annotation);
}
@@ -927,21 +968,22 @@ public class AnnotationUtilsTests {
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("Misconfigured aliases"));
exception.expectMessage(containsString(AliasForAttributeWithDifferentDefaultValue.class.getName()));
- exception.expectMessage(containsString("attribute [foo] in annotation"));
- exception.expectMessage(containsString("attribute [bar] in annotation"));
- exception.expectMessage(endsWith("must declare the same default value."));
+ exception.expectMessage(containsString("attribute 'foo' in annotation"));
+ exception.expectMessage(containsString("attribute 'bar' in annotation"));
+ exception.expectMessage(containsString("same default value"));
synthesizeAnnotation(annotation);
}
@Test
public void synthesizeAnnotationWithAttributeAliasForMetaAnnotationThatIsNotMetaPresent() throws Exception {
- AliasedComposedContextConfigNotMetaPresent annotation = AliasedComposedContextConfigNotMetaPresentClass.class.getAnnotation(AliasedComposedContextConfigNotMetaPresent.class);
+ AliasedComposedContextConfigNotMetaPresent annotation =
+ AliasedComposedContextConfigNotMetaPresentClass.class.getAnnotation(AliasedComposedContextConfigNotMetaPresent.class);
exception.expect(AnnotationConfigurationException.class);
- exception.expectMessage(startsWith("@AliasFor declaration on attribute [xmlConfigFile] in annotation"));
+ exception.expectMessage(startsWith("@AliasFor declaration on attribute 'xmlConfigFile' in annotation"));
exception.expectMessage(containsString(AliasedComposedContextConfigNotMetaPresent.class.getName()));
- exception.expectMessage(containsString("declares an alias for attribute [location] in meta-annotation"));
+ exception.expectMessage(containsString("declares an alias for attribute 'location' in meta-annotation"));
exception.expectMessage(containsString(ContextConfig.class.getName()));
- exception.expectMessage(endsWith("which is not meta-present."));
+ exception.expectMessage(containsString("not meta-present"));
synthesizeAnnotation(annotation);
}
@@ -956,16 +998,16 @@ public class AnnotationUtilsTests {
assertNotSame(webMapping, synthesizedWebMapping1);
assertEquals("name attribute: ", "foo", synthesizedWebMapping1.name());
- assertEquals("aliased path attribute: ", "/test", synthesizedWebMapping1.path());
- assertEquals("actual value attribute: ", "/test", synthesizedWebMapping1.value());
+ assertArrayEquals("aliased path attribute: ", asArray("/test"), synthesizedWebMapping1.path());
+ assertArrayEquals("actual value attribute: ", asArray("/test"), synthesizedWebMapping1.value());
WebMapping synthesizedWebMapping2 = synthesizeAnnotation(webMapping);
assertThat(synthesizedWebMapping2, instanceOf(SynthesizedAnnotation.class));
assertNotSame(webMapping, synthesizedWebMapping2);
assertEquals("name attribute: ", "foo", synthesizedWebMapping2.name());
- assertEquals("aliased path attribute: ", "/test", synthesizedWebMapping2.path());
- assertEquals("actual value attribute: ", "/test", synthesizedWebMapping2.value());
+ assertArrayEquals("aliased path attribute: ", asArray("/test"), synthesizedWebMapping2.path());
+ assertArrayEquals("actual value attribute: ", asArray("/test"), synthesizedWebMapping2.value());
}
@Test
@@ -990,6 +1032,31 @@ public class AnnotationUtilsTests {
}
@Test
+ public void synthesizeAnnotationWithImplicitAliasesWithImpliedAliasNamesOmitted() throws Exception {
+ assertAnnotationSynthesisWithImplicitAliasesWithImpliedAliasNamesOmitted(
+ ValueImplicitAliasesWithImpliedAliasNamesOmittedContextConfigClass.class, "value");
+ assertAnnotationSynthesisWithImplicitAliasesWithImpliedAliasNamesOmitted(
+ LocationsImplicitAliasesWithImpliedAliasNamesOmittedContextConfigClass.class, "location");
+ assertAnnotationSynthesisWithImplicitAliasesWithImpliedAliasNamesOmitted(
+ XmlFilesImplicitAliasesWithImpliedAliasNamesOmittedContextConfigClass.class, "xmlFile");
+ }
+
+ private void assertAnnotationSynthesisWithImplicitAliasesWithImpliedAliasNamesOmitted(Class<?> clazz,
+ String expected) throws Exception {
+
+ ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig config = clazz.getAnnotation(
+ ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class);
+ assertNotNull(config);
+
+ ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig synthesizedConfig = synthesizeAnnotation(config);
+ assertThat(synthesizedConfig, instanceOf(SynthesizedAnnotation.class));
+
+ assertEquals("value: ", expected, synthesizedConfig.value());
+ assertEquals("locations: ", expected, synthesizedConfig.location());
+ assertEquals("xmlFiles: ", expected, synthesizedConfig.xmlFile());
+ }
+
+ @Test
public void synthesizeAnnotationWithImplicitAliasesForAliasPair() throws Exception {
Class<?> clazz = ImplicitAliasesForAliasPairContextConfigClass.class;
ImplicitAliasesForAliasPairContextConfig config = clazz.getAnnotation(ImplicitAliasesForAliasPairContextConfig.class);
@@ -1037,9 +1104,9 @@ public class AnnotationUtilsTests {
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("Misconfigured aliases:"));
- exception.expectMessage(containsString("attribute [location1] in annotation [" + annotationType.getName() + "]"));
- exception.expectMessage(containsString("attribute [location2] in annotation [" + annotationType.getName() + "]"));
- exception.expectMessage(endsWith("must declare default values."));
+ exception.expectMessage(containsString("attribute 'location1' in annotation [" + annotationType.getName() + "]"));
+ exception.expectMessage(containsString("attribute 'location2' in annotation [" + annotationType.getName() + "]"));
+ exception.expectMessage(containsString("default values"));
synthesizeAnnotation(config, clazz);
}
@@ -1053,9 +1120,9 @@ public class AnnotationUtilsTests {
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("Misconfigured aliases:"));
- exception.expectMessage(containsString("attribute [location1] in annotation [" + annotationType.getName() + "]"));
- exception.expectMessage(containsString("attribute [location2] in annotation [" + annotationType.getName() + "]"));
- exception.expectMessage(endsWith("must declare the same default value."));
+ exception.expectMessage(containsString("attribute 'location1' in annotation [" + annotationType.getName() + "]"));
+ exception.expectMessage(containsString("attribute 'location2' in annotation [" + annotationType.getName() + "]"));
+ exception.expectMessage(containsString("same default value"));
synthesizeAnnotation(config, clazz);
}
@@ -1077,10 +1144,9 @@ public class AnnotationUtilsTests {
exception.expectMessage(containsString(clazz.getName()));
exception.expectMessage(containsString("and synthesized from"));
exception.expectMessage(either(containsString("attribute 'location1' and its alias 'location2'")).or(
- containsString("attribute 'location2' and its alias 'location1'")));
+ containsString("attribute 'location2' and its alias 'location1'")));
exception.expectMessage(either(containsString("are present with values of [1] and [2]")).or(
- containsString("are present with values of [2] and [1]")));
- exception.expectMessage(endsWith("but only one is permitted."));
+ containsString("are present with values of [2] and [1]")));
synthesizedConfig.location1();
}
@@ -1106,8 +1172,8 @@ public class AnnotationUtilsTests {
assertNotNull(componentScan);
assertEquals("value from ComponentScan: ", "*Foo", componentScan.value().pattern());
- AnnotationAttributes attributes = getAnnotationAttributes(ComponentScanSingleFilterClass.class, componentScan,
- false, true);
+ AnnotationAttributes attributes = getAnnotationAttributes(
+ ComponentScanSingleFilterClass.class, componentScan, false, true);
assertNotNull(attributes);
assertEquals(ComponentScanSingleFilter.class, attributes.annotationType());
@@ -1119,8 +1185,8 @@ public class AnnotationUtilsTests {
filterMap.put("pattern", "newFoo");
filterMap.put("enigma", 42);
- ComponentScanSingleFilter synthesizedComponentScan = synthesizeAnnotation(attributes,
- ComponentScanSingleFilter.class, ComponentScanSingleFilterClass.class);
+ ComponentScanSingleFilter synthesizedComponentScan = synthesizeAnnotation(
+ attributes, ComponentScanSingleFilter.class, ComponentScanSingleFilterClass.class);
assertNotNull(synthesizedComponentScan);
assertNotSame(componentScan, synthesizedComponentScan);
@@ -1184,6 +1250,21 @@ public class AnnotationUtilsTests {
}
@Test
+ public void synthesizeAnnotationFromMapWithAttributeAliasesThatOverrideArraysWithSingleElements() throws Exception {
+ Map<String, Object> map = Collections.singletonMap("value", "/foo");
+ Get get = synthesizeAnnotation(map, Get.class, null);
+ assertNotNull(get);
+ assertEquals("value: ", "/foo", get.value());
+ assertEquals("path: ", "/foo", get.path());
+
+ map = Collections.singletonMap("path", "/foo");
+ get = synthesizeAnnotation(map, Get.class, null);
+ assertNotNull(get);
+ assertEquals("value: ", "/foo", get.value());
+ assertEquals("path: ", "/foo", get.path());
+ }
+
+ @Test
public void synthesizeAnnotationFromMapWithImplicitAttributeAliases() throws Exception {
assertAnnotationSynthesisFromMapWithImplicitAliases("value");
assertAnnotationSynthesisFromMapWithImplicitAliases("location1");
@@ -1220,7 +1301,7 @@ public class AnnotationUtilsTests {
private void assertMissingTextAttribute(Map<String, Object> attributes) {
exception.expect(IllegalArgumentException.class);
exception.expectMessage(startsWith("Attributes map"));
- exception.expectMessage(containsString("returned null for required attribute [text]"));
+ exception.expectMessage(containsString("returned null for required attribute 'text'"));
exception.expectMessage(containsString("defined by annotation type [" + AnnotationWithoutDefaults.class.getName() + "]"));
synthesizeAnnotation(attributes, AnnotationWithoutDefaults.class, null);
}
@@ -1232,15 +1313,15 @@ public class AnnotationUtilsTests {
exception.expect(IllegalArgumentException.class);
exception.expectMessage(startsWith("Attributes map"));
exception.expectMessage(containsString("returned a value of type [java.lang.Long]"));
- exception.expectMessage(containsString("for attribute [value]"));
+ exception.expectMessage(containsString("for attribute 'value'"));
exception.expectMessage(containsString("but a value of type [java.lang.String] is required"));
exception.expectMessage(containsString("as defined by annotation type [" + Component.class.getName() + "]"));
+
synthesizeAnnotation(map, Component.class, null);
}
@Test
public void synthesizeAnnotationFromAnnotationAttributesWithoutAttributeAliases() throws Exception {
-
// 1) Get an annotation
Component component = WebController.class.getAnnotation(Component.class);
assertNotNull(component);
@@ -1288,8 +1369,8 @@ public class AnnotationUtilsTests {
private void assertToStringForWebMappingWithPathAndValue(WebMapping webMapping) {
String string = webMapping.toString();
assertThat(string, startsWith("@" + WebMapping.class.getName() + "("));
- assertThat(string, containsString("value=/test"));
- assertThat(string, containsString("path=/test"));
+ assertThat(string, containsString("value=[/test]"));
+ assertThat(string, containsString("path=[/test]"));
assertThat(string, containsString("name=bar"));
assertThat(string, containsString("method="));
assertThat(string, containsString("[GET, POST]"));
@@ -1410,7 +1491,7 @@ public class AnnotationUtilsTests {
ContextConfig[] configs = synthesizedHierarchy.value();
assertNotNull(configs);
assertTrue("nested annotations must be synthesized",
- stream(configs).allMatch(c -> c instanceof SynthesizedAnnotation));
+ stream(configs).allMatch(c -> c instanceof SynthesizedAnnotation));
List<String> locations = stream(configs).map(ContextConfig::location).collect(toList());
assertThat(locations, is(expectedLocations));
@@ -1462,6 +1543,18 @@ public class AnnotationUtilsTests {
assertArrayEquals(new char[] { 'x', 'y', 'z' }, chars);
}
+ @SafeVarargs
+ // The following "varargs" suppression is necessary for javac from OpenJDK
+ // (1.8.0_60-b27); however, Eclipse warns that it's unnecessary. See the following
+ // Eclipse issues for details.
+ //
+ // https://bugs.eclipse.org/bugs/show_bug.cgi?id=344783
+ // https://bugs.eclipse.org/bugs/show_bug.cgi?id=349669#c10
+ // @SuppressWarnings("varargs")
+ static <T> T[] asArray(T... arr) {
+ return arr;
+ }
+
@Component("meta1")
@Order
@@ -1769,14 +1862,40 @@ public class AnnotationUtilsTests {
String name();
@AliasFor("path")
- String value() default "";
+ String[] value() default "";
@AliasFor(attribute = "value")
- String path() default "";
+ String[] path() default "";
RequestMethod[] method() default {};
}
+ /**
+ * Mock of {@code org.springframework.web.bind.annotation.GetMapping}, except
+ * that the String arrays are overridden with single String elements.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @WebMapping(method = RequestMethod.GET, name = "")
+ @interface Get {
+
+ @AliasFor(annotation = WebMapping.class)
+ String value() default "";
+
+ @AliasFor(annotation = WebMapping.class)
+ String path() default "";
+ }
+
+ /**
+ * Mock of {@code org.springframework.web.bind.annotation.PostMapping}, except
+ * that the path is overridden by convention with single String element.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @WebMapping(method = RequestMethod.POST, name = "")
+ @interface Post {
+
+ String path() default "";
+ }
+
@Component("webController")
static class WebController {
@@ -1788,6 +1907,18 @@ public class AnnotationUtilsTests {
public void handleMappedWithPathAttribute() {
}
+ @Get("/test")
+ public void getMappedWithValueAttribute() {
+ }
+
+ @Get(path = "/test")
+ public void getMappedWithPathAttribute() {
+ }
+
+ @Post(path = "/test")
+ public void postMappedWithPathAttribute() {
+ }
+
/**
* mapping is logically "equal" to handleMappedWithPathAttribute().
*/
@@ -1990,12 +2121,12 @@ public class AnnotationUtilsTests {
@ContextConfig
@Retention(RetentionPolicy.RUNTIME)
- @interface ImplicitAliasesContextConfig {
+ public @interface ImplicitAliasesContextConfig {
@AliasFor(annotation = ContextConfig.class, attribute = "location")
String xmlFile() default "";
- @AliasFor(annotation = ContextConfig.class, value = "location")
+ @AliasFor(annotation = ContextConfig.class, attribute = "location")
String groovyScript() default "";
@AliasFor(annotation = ContextConfig.class, attribute = "location")
@@ -2048,6 +2179,48 @@ public class AnnotationUtilsTests {
@ContextConfig
@Retention(RetentionPolicy.RUNTIME)
+ @interface ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig {
+
+ // intentionally omitted: attribute = "value"
+ @AliasFor(annotation = ContextConfig.class)
+ String value() default "";
+
+ // intentionally omitted: attribute = "locations"
+ @AliasFor(annotation = ContextConfig.class)
+ String location() default "";
+
+ @AliasFor(annotation = ContextConfig.class, attribute = "location")
+ String xmlFile() default "";
+ }
+
+ @ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface TransitiveImplicitAliasesWithImpliedAliasNamesOmittedContextConfig {
+
+ @AliasFor(annotation = ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class, attribute = "xmlFile")
+ String xml() default "";
+
+ @AliasFor(annotation = ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class, attribute = "location")
+ String groovy() default "";
+ }
+
+ // Attribute value intentionally matches attribute name:
+ @ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig("value")
+ static class ValueImplicitAliasesWithImpliedAliasNamesOmittedContextConfigClass {
+ }
+
+ // Attribute value intentionally matches attribute name:
+ @ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig(location = "location")
+ static class LocationsImplicitAliasesWithImpliedAliasNamesOmittedContextConfigClass {
+ }
+
+ // Attribute value intentionally matches attribute name:
+ @ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig(xmlFile = "xmlFile")
+ static class XmlFilesImplicitAliasesWithImpliedAliasNamesOmittedContextConfigClass {
+ }
+
+ @ContextConfig
+ @Retention(RetentionPolicy.RUNTIME)
@interface ImplicitAliasesWithMissingDefaultValuesContextConfig {
@AliasFor(annotation = ContextConfig.class, attribute = "location")
diff --git a/spring-core/src/test/java/org/springframework/core/annotation/ComposedRepeatableAnnotationsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/ComposedRepeatableAnnotationsTests.java
new file mode 100644
index 00000000..5cbc10d9
--- /dev/null
+++ b/spring-core/src/test/java/org/springframework/core/annotation/ComposedRepeatableAnnotationsTests.java
@@ -0,0 +1,386 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.AnnotatedElement;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.hamcrest.CoreMatchers.isA;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.startsWith;
+import static org.junit.Assert.*;
+import static org.springframework.core.annotation.AnnotatedElementUtils.*;
+
+/**
+ * Unit tests that verify support for getting and finding all composed, repeatable
+ * annotations on a single annotated element.
+ *
+ * <p>See <a href="https://jira.spring.io/browse/SPR-13973">SPR-13973</a>.
+ *
+ * @author Sam Brannen
+ * @since 4.3
+ * @see AnnotatedElementUtils#getMergedRepeatableAnnotations
+ * @see AnnotatedElementUtils#findMergedRepeatableAnnotations
+ * @see AnnotatedElementUtilsTests
+ * @see MultipleComposedAnnotationsOnSingleAnnotatedElementTests
+ */
+public class ComposedRepeatableAnnotationsTests {
+
+ @Rule
+ public final ExpectedException exception = ExpectedException.none();
+
+
+ @Test
+ public void getNonRepeatableAnnotation() {
+ expectNonRepeatableAnnotation();
+ getMergedRepeatableAnnotations(getClass(), NonRepeatable.class);
+ }
+
+ @Test
+ public void getInvalidRepeatableAnnotationContainerMissingValueAttribute() {
+ expectContainerMissingValueAttribute();
+ getMergedRepeatableAnnotations(getClass(), InvalidRepeatable.class, ContainerMissingValueAttribute.class);
+ }
+
+ @Test
+ public void getInvalidRepeatableAnnotationContainerWithNonArrayValueAttribute() {
+ expectContainerWithNonArrayValueAttribute();
+ getMergedRepeatableAnnotations(getClass(), InvalidRepeatable.class, ContainerWithNonArrayValueAttribute.class);
+ }
+
+ @Test
+ public void getInvalidRepeatableAnnotationContainerWithArrayValueAttributeButWrongComponentType() {
+ expectContainerWithArrayValueAttributeButWrongComponentType();
+ getMergedRepeatableAnnotations(getClass(), InvalidRepeatable.class,
+ ContainerWithArrayValueAttributeButWrongComponentType.class);
+ }
+
+ @Test
+ public void getRepeatableAnnotationsOnClass() {
+ assertGetRepeatableAnnotations(RepeatableClass.class);
+ }
+
+ @Test
+ public void getRepeatableAnnotationsOnSuperclass() {
+ assertGetRepeatableAnnotations(SubRepeatableClass.class);
+ }
+
+ @Test
+ public void getComposedRepeatableAnnotationsOnClass() {
+ assertGetRepeatableAnnotations(ComposedRepeatableClass.class);
+ }
+
+ @Test
+ public void getComposedRepeatableAnnotationsMixedWithContainerOnClass() {
+ assertGetRepeatableAnnotations(ComposedRepeatableMixedWithContainerClass.class);
+ }
+
+ @Test
+ public void getComposedContainerForRepeatableAnnotationsOnClass() {
+ assertGetRepeatableAnnotations(ComposedContainerClass.class);
+ }
+
+ @Test
+ public void getNoninheritedComposedRepeatableAnnotationsOnClass() {
+ Class<?> element = NoninheritedRepeatableClass.class;
+ Set<Noninherited> annotations = getMergedRepeatableAnnotations(element, Noninherited.class);
+ assertNoninheritedRepeatableAnnotations(annotations);
+ }
+
+ @Test
+ public void getNoninheritedComposedRepeatableAnnotationsOnSuperclass() {
+ Class<?> element = SubNoninheritedRepeatableClass.class;
+ Set<Noninherited> annotations = getMergedRepeatableAnnotations(element, Noninherited.class);
+ assertNotNull(annotations);
+ assertEquals(0, annotations.size());
+ }
+
+ @Test
+ public void findNonRepeatableAnnotation() {
+ expectNonRepeatableAnnotation();
+ findMergedRepeatableAnnotations(getClass(), NonRepeatable.class);
+ }
+
+ @Test
+ public void findInvalidRepeatableAnnotationContainerMissingValueAttribute() {
+ expectContainerMissingValueAttribute();
+ findMergedRepeatableAnnotations(getClass(), InvalidRepeatable.class, ContainerMissingValueAttribute.class);
+ }
+
+ @Test
+ public void findInvalidRepeatableAnnotationContainerWithNonArrayValueAttribute() {
+ expectContainerWithNonArrayValueAttribute();
+ findMergedRepeatableAnnotations(getClass(), InvalidRepeatable.class, ContainerWithNonArrayValueAttribute.class);
+ }
+
+ @Test
+ public void findInvalidRepeatableAnnotationContainerWithArrayValueAttributeButWrongComponentType() {
+ expectContainerWithArrayValueAttributeButWrongComponentType();
+ findMergedRepeatableAnnotations(getClass(), InvalidRepeatable.class,
+ ContainerWithArrayValueAttributeButWrongComponentType.class);
+ }
+
+ @Test
+ public void findRepeatableAnnotationsOnClass() {
+ assertFindRepeatableAnnotations(RepeatableClass.class);
+ }
+
+ @Test
+ public void findRepeatableAnnotationsOnSuperclass() {
+ assertFindRepeatableAnnotations(SubRepeatableClass.class);
+ }
+
+ @Test
+ public void findComposedRepeatableAnnotationsOnClass() {
+ assertFindRepeatableAnnotations(ComposedRepeatableClass.class);
+ }
+
+ @Test
+ public void findComposedRepeatableAnnotationsMixedWithContainerOnClass() {
+ assertFindRepeatableAnnotations(ComposedRepeatableMixedWithContainerClass.class);
+ }
+
+ @Test
+ public void findNoninheritedComposedRepeatableAnnotationsOnClass() {
+ Class<?> element = NoninheritedRepeatableClass.class;
+ Set<Noninherited> annotations = findMergedRepeatableAnnotations(element, Noninherited.class);
+ assertNoninheritedRepeatableAnnotations(annotations);
+ }
+
+ @Test
+ public void findNoninheritedComposedRepeatableAnnotationsOnSuperclass() {
+ Class<?> element = SubNoninheritedRepeatableClass.class;
+ Set<Noninherited> annotations = findMergedRepeatableAnnotations(element, Noninherited.class);
+ assertNoninheritedRepeatableAnnotations(annotations);
+ }
+
+ @Test
+ public void findComposedContainerForRepeatableAnnotationsOnClass() {
+ assertFindRepeatableAnnotations(ComposedContainerClass.class);
+ }
+
+ private void expectNonRepeatableAnnotation() {
+ exception.expect(IllegalArgumentException.class);
+ exception.expectMessage(startsWith("annotationType must be a repeatable annotation"));
+ exception.expectMessage(containsString("failed to resolve container type for"));
+ exception.expectMessage(containsString(NonRepeatable.class.getName()));
+ }
+
+ private void expectContainerMissingValueAttribute() {
+ exception.expect(AnnotationConfigurationException.class);
+ exception.expectMessage(startsWith("Invalid declaration of container type"));
+ exception.expectMessage(containsString(ContainerMissingValueAttribute.class.getName()));
+ exception.expectMessage(containsString("for repeatable annotation"));
+ exception.expectMessage(containsString(InvalidRepeatable.class.getName()));
+ exception.expectCause(isA(NoSuchMethodException.class));
+ }
+
+ private void expectContainerWithNonArrayValueAttribute() {
+ exception.expect(AnnotationConfigurationException.class);
+ exception.expectMessage(startsWith("Container type"));
+ exception.expectMessage(containsString(ContainerWithNonArrayValueAttribute.class.getName()));
+ exception.expectMessage(containsString("must declare a 'value' attribute for an array of type"));
+ exception.expectMessage(containsString(InvalidRepeatable.class.getName()));
+ }
+
+ private void expectContainerWithArrayValueAttributeButWrongComponentType() {
+ exception.expect(AnnotationConfigurationException.class);
+ exception.expectMessage(startsWith("Container type"));
+ exception.expectMessage(containsString(ContainerWithArrayValueAttributeButWrongComponentType.class.getName()));
+ exception.expectMessage(containsString("must declare a 'value' attribute for an array of type"));
+ exception.expectMessage(containsString(InvalidRepeatable.class.getName()));
+ }
+
+ private void assertGetRepeatableAnnotations(AnnotatedElement element) {
+ assertNotNull(element);
+
+ Set<PeteRepeat> peteRepeats = getMergedRepeatableAnnotations(element, PeteRepeat.class);
+ assertNotNull(peteRepeats);
+ assertEquals(3, peteRepeats.size());
+
+ Iterator<PeteRepeat> iterator = peteRepeats.iterator();
+ assertEquals("A", iterator.next().value());
+ assertEquals("B", iterator.next().value());
+ assertEquals("C", iterator.next().value());
+ }
+
+ private void assertFindRepeatableAnnotations(AnnotatedElement element) {
+ assertNotNull(element);
+
+ Set<PeteRepeat> peteRepeats = findMergedRepeatableAnnotations(element, PeteRepeat.class);
+ assertNotNull(peteRepeats);
+ assertEquals(3, peteRepeats.size());
+
+ Iterator<PeteRepeat> iterator = peteRepeats.iterator();
+ assertEquals("A", iterator.next().value());
+ assertEquals("B", iterator.next().value());
+ assertEquals("C", iterator.next().value());
+ }
+
+ private void assertNoninheritedRepeatableAnnotations(Set<Noninherited> annotations) {
+ assertNotNull(annotations);
+ assertEquals(3, annotations.size());
+
+ Iterator<Noninherited> iterator = annotations.iterator();
+ assertEquals("A", iterator.next().value());
+ assertEquals("B", iterator.next().value());
+ assertEquals("C", iterator.next().value());
+ }
+
+
+ // -------------------------------------------------------------------------
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface NonRepeatable {
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface ContainerMissingValueAttribute {
+ // InvalidRepeatable[] value();
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface ContainerWithNonArrayValueAttribute {
+
+ InvalidRepeatable value();
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface ContainerWithArrayValueAttributeButWrongComponentType {
+
+ String[] value();
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface InvalidRepeatable {
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Inherited
+ @interface PeteRepeats {
+
+ PeteRepeat[] value();
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Inherited
+ @Repeatable(PeteRepeats.class)
+ @interface PeteRepeat {
+
+ String value();
+ }
+
+ @PeteRepeat("shadowed")
+ @Target({ ElementType.METHOD, ElementType.TYPE })
+ @Retention(RetentionPolicy.RUNTIME)
+ @Inherited
+ @interface ForPetesSake {
+
+ @AliasFor(annotation = PeteRepeat.class)
+ String value();
+ }
+
+ @PeteRepeat("shadowed")
+ @Target({ ElementType.METHOD, ElementType.TYPE })
+ @Retention(RetentionPolicy.RUNTIME)
+ @Inherited
+ @interface ForTheLoveOfFoo {
+
+ @AliasFor(annotation = PeteRepeat.class)
+ String value();
+ }
+
+ @PeteRepeats({ @PeteRepeat("B"), @PeteRepeat("C") })
+ @Target({ ElementType.METHOD, ElementType.TYPE })
+ @Retention(RetentionPolicy.RUNTIME)
+ @Inherited
+ @interface ComposedContainer {
+ }
+
+ @PeteRepeat("A")
+ @PeteRepeats({ @PeteRepeat("B"), @PeteRepeat("C") })
+ static class RepeatableClass {
+ }
+
+ static class SubRepeatableClass extends RepeatableClass {
+ }
+
+ @ForPetesSake("B")
+ @ForTheLoveOfFoo("C")
+ @PeteRepeat("A")
+ static class ComposedRepeatableClass {
+ }
+
+ @ForPetesSake("C")
+ @PeteRepeats(@PeteRepeat("A"))
+ @PeteRepeat("B")
+ static class ComposedRepeatableMixedWithContainerClass {
+ }
+
+ @PeteRepeat("A")
+ @ComposedContainer
+ static class ComposedContainerClass {
+ }
+
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface Noninheriteds {
+
+ Noninherited[] value();
+ }
+
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.RUNTIME)
+ @Repeatable(Noninheriteds.class)
+ @interface Noninherited {
+
+ @AliasFor("name")
+ String value() default "";
+
+ @AliasFor("value")
+ String name() default "";
+ }
+
+ @Noninherited(name = "shadowed")
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface ComposedNoninherited {
+
+ @AliasFor(annotation = Noninherited.class)
+ String name() default "";
+ }
+
+ @ComposedNoninherited(name = "C")
+ @Noninheriteds({ @Noninherited(value = "A"), @Noninherited(name = "B") })
+ static class NoninheritedRepeatableClass {
+ }
+
+ static class SubNoninheritedRepeatableClass extends NoninheritedRepeatableClass {
+ }
+
+}
diff --git a/spring-core/src/test/java/org/springframework/core/annotation/MapAnnotationAttributeExtractorTests.java b/spring-core/src/test/java/org/springframework/core/annotation/MapAnnotationAttributeExtractorTests.java
index a3811527..d3b45602 100644
--- a/spring-core/src/test/java/org/springframework/core/annotation/MapAnnotationAttributeExtractorTests.java
+++ b/spring-core/src/test/java/org/springframework/core/annotation/MapAnnotationAttributeExtractorTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,11 +26,14 @@ import org.junit.Before;
import org.junit.Test;
import org.springframework.core.annotation.AnnotationUtilsTests.ImplicitAliasesContextConfig;
+import org.springframework.core.annotation.AnnotationUtilsTests.RequestMethod;
+import org.springframework.core.annotation.AnnotationUtilsTests.WebMapping;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
+import static org.springframework.core.annotation.AnnotationUtilsTests.*;
/**
* Unit tests for {@link MapAnnotationAttributeExtractor}.
@@ -38,6 +41,7 @@ import static org.junit.Assert.*;
* @author Sam Brannen
* @since 4.2.1
*/
+@SuppressWarnings("serial")
public class MapAnnotationAttributeExtractorTests extends AbstractAliasAwareAnnotationAttributeExtractorTestCase {
@Before
@@ -46,7 +50,6 @@ public class MapAnnotationAttributeExtractorTests extends AbstractAliasAwareAnno
}
@Test
- @SuppressWarnings("serial")
public void enrichAndValidateAttributesWithImplicitAliasesAndMinimalAttributes() {
Map<String, Object> attributes = new HashMap<String, Object>();
Map<String, Object> expectedAttributes = new HashMap<String, Object>() {{
@@ -64,7 +67,6 @@ public class MapAnnotationAttributeExtractorTests extends AbstractAliasAwareAnno
}
@Test
- @SuppressWarnings("serial")
public void enrichAndValidateAttributesWithImplicitAliases() {
Map<String, Object> attributes = new HashMap<String, Object>() {{
put("groovyScript", "groovy!");
@@ -84,6 +86,31 @@ public class MapAnnotationAttributeExtractorTests extends AbstractAliasAwareAnno
assertEnrichAndValidateAttributes(attributes, expectedAttributes);
}
+ @Test
+ public void enrichAndValidateAttributesWithSingleElementThatOverridesAnArray() {
+ // @formatter:off
+ Map<String, Object> attributes = new HashMap<String, Object>() {{
+ // Intentionally storing 'value' as a single String instead of an array.
+ // put("value", asArray("/foo"));
+ put("value", "/foo");
+ put("name", "test");
+ }};
+
+ Map<String, Object> expected = new HashMap<String, Object>() {{
+ put("value", asArray("/foo"));
+ put("path", asArray("/foo"));
+ put("name", "test");
+ put("method", new RequestMethod[0]);
+ }};
+ // @formatter:on
+
+ MapAnnotationAttributeExtractor extractor = new MapAnnotationAttributeExtractor(attributes, WebMapping.class, null);
+ Map<String, Object> enriched = extractor.getSource();
+
+ assertEquals("attribute map size", expected.size(), enriched.size());
+ expected.forEach((attr, expectedValue) -> assertThat("for attribute '" + attr + "'", enriched.get(attr), is(expectedValue)));
+ }
+
@SuppressWarnings("unchecked")
private void assertEnrichAndValidateAttributes(Map<String, Object> sourceAttributes, Map<String, Object> expected) {
Class<? extends Annotation> annotationType = ImplicitAliasesContextConfig.class;
@@ -114,8 +141,7 @@ public class MapAnnotationAttributeExtractorTests extends AbstractAliasAwareAnno
Map<String, Object> enriched = extractor.getSource();
assertEquals("attribute map size", expected.size(), enriched.size());
- expected.keySet().stream().forEach( attr ->
- assertThat("for attribute '" + attr + "'", enriched.get(attr), is(expected.get(attr))));
+ expected.forEach((attr, expectedValue) -> assertThat("for attribute '" + attr + "'", enriched.get(attr), is(expectedValue)));
}
@Override
diff --git a/spring-core/src/test/java/org/springframework/core/annotation/MultipleComposedAnnotationsOnSingleAnnotatedElementTests.java b/spring-core/src/test/java/org/springframework/core/annotation/MultipleComposedAnnotationsOnSingleAnnotatedElementTests.java
new file mode 100644
index 00000000..fc2259ac
--- /dev/null
+++ b/spring-core/src/test/java/org/springframework/core/annotation/MultipleComposedAnnotationsOnSingleAnnotatedElementTests.java
@@ -0,0 +1,368 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Method;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+import static org.springframework.core.annotation.AnnotatedElementUtils.*;
+
+/**
+ * Unit tests that verify support for finding multiple composed annotations on
+ * a single annotated element.
+ *
+ * <p>See <a href="https://jira.spring.io/browse/SPR-13486">SPR-13486</a>.
+ *
+ * @author Sam Brannen
+ * @since 4.3
+ * @see AnnotatedElementUtils
+ * @see AnnotatedElementUtilsTests
+ * @see ComposedRepeatableAnnotationsTests
+ */
+public class MultipleComposedAnnotationsOnSingleAnnotatedElementTests {
+
+ @Test
+ public void getMultipleComposedAnnotationsOnClass() {
+ assertGetAllMergedAnnotationsBehavior(MultipleComposedCachesClass.class);
+ }
+
+ @Test
+ public void getMultipleInheritedComposedAnnotationsOnSuperclass() {
+ assertGetAllMergedAnnotationsBehavior(SubMultipleComposedCachesClass.class);
+ }
+
+ @Test
+ public void getMultipleNoninheritedComposedAnnotationsOnClass() {
+ Class<?> element = MultipleNoninheritedComposedCachesClass.class;
+ Set<Cacheable> cacheables = getAllMergedAnnotations(element, Cacheable.class);
+ assertNotNull(cacheables);
+ assertEquals(2, cacheables.size());
+
+ Iterator<Cacheable> iterator = cacheables.iterator();
+ Cacheable cacheable1 = iterator.next();
+ Cacheable cacheable2 = iterator.next();
+ assertEquals("noninheritedCache1", cacheable1.value());
+ assertEquals("noninheritedCache2", cacheable2.value());
+ }
+
+ @Test
+ public void getMultipleNoninheritedComposedAnnotationsOnSuperclass() {
+ Class<?> element = SubMultipleNoninheritedComposedCachesClass.class;
+ Set<Cacheable> cacheables = getAllMergedAnnotations(element, Cacheable.class);
+ assertNotNull(cacheables);
+ assertEquals(0, cacheables.size());
+ }
+
+ @Test
+ public void getComposedPlusLocalAnnotationsOnClass() {
+ assertGetAllMergedAnnotationsBehavior(ComposedPlusLocalCachesClass.class);
+ }
+
+ @Test
+ public void getMultipleComposedAnnotationsOnInterface() {
+ Class<MultipleComposedCachesOnInterfaceClass> element = MultipleComposedCachesOnInterfaceClass.class;
+ Set<Cacheable> cacheables = getAllMergedAnnotations(element, Cacheable.class);
+ assertNotNull(cacheables);
+ assertEquals(0, cacheables.size());
+ }
+
+ @Test
+ public void getMultipleComposedAnnotationsOnMethod() throws Exception {
+ AnnotatedElement element = getClass().getDeclaredMethod("multipleComposedCachesMethod");
+ assertGetAllMergedAnnotationsBehavior(element);
+ }
+
+ @Test
+ public void getComposedPlusLocalAnnotationsOnMethod() throws Exception {
+ AnnotatedElement element = getClass().getDeclaredMethod("composedPlusLocalCachesMethod");
+ assertGetAllMergedAnnotationsBehavior(element);
+ }
+
+ @Test
+ @Ignore("Disabled since some Java 8 updates handle the bridge method differently")
+ public void getMultipleComposedAnnotationsOnBridgeMethod() throws Exception {
+ Set<Cacheable> cacheables = getAllMergedAnnotations(getBridgeMethod(), Cacheable.class);
+ assertNotNull(cacheables);
+ assertEquals(0, cacheables.size());
+ }
+
+ @Test
+ public void findMultipleComposedAnnotationsOnClass() {
+ assertFindAllMergedAnnotationsBehavior(MultipleComposedCachesClass.class);
+ }
+
+ @Test
+ public void findMultipleInheritedComposedAnnotationsOnSuperclass() {
+ assertFindAllMergedAnnotationsBehavior(SubMultipleComposedCachesClass.class);
+ }
+
+ @Test
+ public void findMultipleNoninheritedComposedAnnotationsOnClass() {
+ Class<?> element = MultipleNoninheritedComposedCachesClass.class;
+ Set<Cacheable> cacheables = findAllMergedAnnotations(element, Cacheable.class);
+ assertNotNull(cacheables);
+ assertEquals(2, cacheables.size());
+
+ Iterator<Cacheable> iterator = cacheables.iterator();
+ Cacheable cacheable1 = iterator.next();
+ Cacheable cacheable2 = iterator.next();
+ assertEquals("noninheritedCache1", cacheable1.value());
+ assertEquals("noninheritedCache2", cacheable2.value());
+ }
+
+ @Test
+ public void findMultipleNoninheritedComposedAnnotationsOnSuperclass() {
+ Class<?> element = SubMultipleNoninheritedComposedCachesClass.class;
+ Set<Cacheable> cacheables = findAllMergedAnnotations(element, Cacheable.class);
+ assertNotNull(cacheables);
+ assertEquals(2, cacheables.size());
+
+ Iterator<Cacheable> iterator = cacheables.iterator();
+ Cacheable cacheable1 = iterator.next();
+ Cacheable cacheable2 = iterator.next();
+ assertEquals("noninheritedCache1", cacheable1.value());
+ assertEquals("noninheritedCache2", cacheable2.value());
+ }
+
+ @Test
+ public void findComposedPlusLocalAnnotationsOnClass() {
+ assertFindAllMergedAnnotationsBehavior(ComposedPlusLocalCachesClass.class);
+ }
+
+ @Test
+ public void findMultipleComposedAnnotationsOnInterface() {
+ assertFindAllMergedAnnotationsBehavior(MultipleComposedCachesOnInterfaceClass.class);
+ }
+
+ @Test
+ public void findComposedCacheOnInterfaceAndLocalCacheOnClass() {
+ assertFindAllMergedAnnotationsBehavior(ComposedCacheOnInterfaceAndLocalCacheClass.class);
+ }
+
+ @Test
+ public void findMultipleComposedAnnotationsOnMethod() throws Exception {
+ AnnotatedElement element = getClass().getDeclaredMethod("multipleComposedCachesMethod");
+ assertFindAllMergedAnnotationsBehavior(element);
+ }
+
+ @Test
+ public void findComposedPlusLocalAnnotationsOnMethod() throws Exception {
+ AnnotatedElement element = getClass().getDeclaredMethod("composedPlusLocalCachesMethod");
+ assertFindAllMergedAnnotationsBehavior(element);
+ }
+
+ @Test
+ public void findMultipleComposedAnnotationsOnBridgeMethod() throws Exception {
+ assertFindAllMergedAnnotationsBehavior(getBridgeMethod());
+ }
+
+ /**
+ * Bridge/bridged method setup code copied from
+ * {@link org.springframework.core.BridgeMethodResolverTests#testWithGenericParameter()}.
+ */
+ public Method getBridgeMethod() throws NoSuchMethodException {
+ Method[] methods = StringGenericParameter.class.getMethods();
+ Method bridgeMethod = null;
+ Method bridgedMethod = null;
+
+ for (Method method : methods) {
+ if ("getFor".equals(method.getName()) && !method.getParameterTypes()[0].equals(Integer.class)) {
+ if (method.getReturnType().equals(Object.class)) {
+ bridgeMethod = method;
+ }
+ else {
+ bridgedMethod = method;
+ }
+ }
+ }
+ assertTrue(bridgeMethod != null && bridgeMethod.isBridge());
+ assertTrue(bridgedMethod != null && !bridgedMethod.isBridge());
+
+ return bridgeMethod;
+ }
+
+ private void assertGetAllMergedAnnotationsBehavior(AnnotatedElement element) {
+ assertNotNull(element);
+
+ Set<Cacheable> cacheables = getAllMergedAnnotations(element, Cacheable.class);
+ assertNotNull(cacheables);
+ assertEquals(2, cacheables.size());
+
+ Iterator<Cacheable> iterator = cacheables.iterator();
+ Cacheable fooCacheable = iterator.next();
+ Cacheable barCacheable = iterator.next();
+ assertEquals("fooKey", fooCacheable.key());
+ assertEquals("fooCache", fooCacheable.value());
+ assertEquals("barKey", barCacheable.key());
+ assertEquals("barCache", barCacheable.value());
+ }
+
+ private void assertFindAllMergedAnnotationsBehavior(AnnotatedElement element) {
+ assertNotNull(element);
+
+ Set<Cacheable> cacheables = findAllMergedAnnotations(element, Cacheable.class);
+ assertNotNull(cacheables);
+ assertEquals(2, cacheables.size());
+
+ Iterator<Cacheable> iterator = cacheables.iterator();
+ Cacheable fooCacheable = iterator.next();
+ Cacheable barCacheable = iterator.next();
+ assertEquals("fooKey", fooCacheable.key());
+ assertEquals("fooCache", fooCacheable.value());
+ assertEquals("barKey", barCacheable.key());
+ assertEquals("barCache", barCacheable.value());
+ }
+
+
+ // -------------------------------------------------------------------------
+
+ /**
+ * Mock of {@code org.springframework.cache.annotation.Cacheable}.
+ */
+ @Target({ ElementType.METHOD, ElementType.TYPE })
+ @Retention(RetentionPolicy.RUNTIME)
+ @Inherited
+ @interface Cacheable {
+
+ @AliasFor("cacheName")
+ String value() default "";
+
+ @AliasFor("value")
+ String cacheName() default "";
+
+ String key() default "";
+ }
+
+ @Cacheable("fooCache")
+ @Target({ ElementType.METHOD, ElementType.TYPE })
+ @Retention(RetentionPolicy.RUNTIME)
+ @Inherited
+ @interface FooCache {
+
+ @AliasFor(annotation = Cacheable.class)
+ String key() default "";
+ }
+
+ @Cacheable("barCache")
+ @Target({ ElementType.METHOD, ElementType.TYPE })
+ @Retention(RetentionPolicy.RUNTIME)
+ @Inherited
+ @interface BarCache {
+
+ @AliasFor(annotation = Cacheable.class)
+ String key();
+ }
+
+ @Cacheable("noninheritedCache1")
+ @Target({ ElementType.METHOD, ElementType.TYPE })
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface NoninheritedCache1 {
+
+ @AliasFor(annotation = Cacheable.class)
+ String key() default "";
+ }
+
+ @Cacheable("noninheritedCache2")
+ @Target({ ElementType.METHOD, ElementType.TYPE })
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface NoninheritedCache2 {
+
+ @AliasFor(annotation = Cacheable.class)
+ String key() default "";
+ }
+
+ @FooCache(key = "fooKey")
+ @BarCache(key = "barKey")
+ private static class MultipleComposedCachesClass {
+ }
+
+ private static class SubMultipleComposedCachesClass extends MultipleComposedCachesClass {
+ }
+
+ @NoninheritedCache1
+ @NoninheritedCache2
+ private static class MultipleNoninheritedComposedCachesClass {
+ }
+
+ private static class SubMultipleNoninheritedComposedCachesClass extends MultipleNoninheritedComposedCachesClass {
+ }
+
+ @Cacheable(cacheName = "fooCache", key = "fooKey")
+ @BarCache(key = "barKey")
+ private static class ComposedPlusLocalCachesClass {
+ }
+
+ @FooCache(key = "fooKey")
+ @BarCache(key = "barKey")
+ private interface MultipleComposedCachesInterface {
+ }
+
+ private static class MultipleComposedCachesOnInterfaceClass implements MultipleComposedCachesInterface {
+ }
+
+ @Cacheable(cacheName = "fooCache", key = "fooKey")
+ private interface ComposedCacheInterface {
+ }
+
+ @BarCache(key = "barKey")
+ private static class ComposedCacheOnInterfaceAndLocalCacheClass implements ComposedCacheInterface {
+ }
+
+
+ @FooCache(key = "fooKey")
+ @BarCache(key = "barKey")
+ private void multipleComposedCachesMethod() {
+ }
+
+ @Cacheable(cacheName = "fooCache", key = "fooKey")
+ @BarCache(key = "barKey")
+ private void composedPlusLocalCachesMethod() {
+ }
+
+
+ public interface GenericParameter<T> {
+
+ T getFor(Class<T> cls);
+ }
+
+ @SuppressWarnings("unused")
+ private static class StringGenericParameter implements GenericParameter<String> {
+
+ @FooCache(key = "fooKey")
+ @BarCache(key = "barKey")
+ @Override
+ public String getFor(Class<String> cls) {
+ return "foo";
+ }
+
+ public String getFor(Integer integer) {
+ return "foo";
+ }
+ }
+
+}
diff --git a/spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java b/spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java
index 61f30c31..39184c2c 100644
--- a/spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java
+++ b/spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java
@@ -52,27 +52,11 @@ import static org.junit.Assert.*;
* @author Andy Clement
* @author Phillip Webb
* @author Sam Brannen
+ * @author Nathan Piper
*/
@SuppressWarnings("rawtypes")
public class TypeDescriptorTests {
- public List<String> listOfString;
-
- public List<List<String>> listOfListOfString = new ArrayList<List<String>>();
-
- public List<List> listOfListOfUnknown = new ArrayList<List>();
-
- public int[] intArray;
-
- public List<String>[] arrayOfListOfString;
-
- public List<Integer> listField = new ArrayList<Integer>();
-
- public Map<String, Integer> mapField = new HashMap<String, Integer>();
-
- public Map<String, List<Integer>> nestedMapField = new HashMap<String, List<Integer>>();
-
-
@Test
public void parameterPrimitive() throws Exception {
TypeDescriptor desc = new TypeDescriptor(new MethodParameter(getClass().getMethod("testParameterPrimitive", int.class), 0));
@@ -86,10 +70,6 @@ public class TypeDescriptorTests {
assertFalse(desc.isMap());
}
- public void testParameterPrimitive(int primitive) {
-
- }
-
@Test
public void parameterScalar() throws Exception {
TypeDescriptor desc = new TypeDescriptor(new MethodParameter(getClass().getMethod("testParameterScalar", String.class), 0));
@@ -104,10 +84,6 @@ public class TypeDescriptorTests {
assertFalse(desc.isMap());
}
- public void testParameterScalar(String value) {
-
- }
-
@Test
public void parameterList() throws Exception {
MethodParameter methodParameter = new MethodParameter(getClass().getMethod("testParameterList", List.class), 0);
@@ -129,10 +105,6 @@ public class TypeDescriptorTests {
assertFalse(desc.isMap());
}
- public void testParameterList(List<List<Map<Integer, Enum<?>>>> list) {
-
- }
-
@Test
public void parameterListNoParamTypes() throws Exception {
MethodParameter methodParameter = new MethodParameter(getClass().getMethod("testParameterListNoParamTypes", List.class), 0);
@@ -149,10 +121,6 @@ public class TypeDescriptorTests {
assertFalse(desc.isMap());
}
- public void testParameterListNoParamTypes(List list) {
-
- }
-
@Test
public void parameterArray() throws Exception {
MethodParameter methodParameter = new MethodParameter(getClass().getMethod("testParameterArray", Integer[].class), 0);
@@ -170,10 +138,6 @@ public class TypeDescriptorTests {
assertFalse(desc.isMap());
}
- public void testParameterArray(Integer[] array) {
-
- }
-
@Test
public void parameterMap() throws Exception {
MethodParameter methodParameter = new MethodParameter(getClass().getMethod("testParameterMap", Map.class), 0);
@@ -194,10 +158,6 @@ public class TypeDescriptorTests {
assertEquals(String.class, desc.getMapValueTypeDescriptor().getElementTypeDescriptor().getType());
}
- public void testParameterMap(Map<Integer, List<String>> map) {
-
- }
-
@Test
public void parameterAnnotated() throws Exception {
TypeDescriptor t1 = new TypeDescriptor(new MethodParameter(getClass().getMethod("testAnnotatedMethod", String.class), 0));
@@ -208,36 +168,20 @@ public class TypeDescriptorTests {
assertEquals(123, t1.getAnnotation(ParameterAnnotation.class).value());
}
- @Target({ElementType.PARAMETER})
- @Retention(RetentionPolicy.RUNTIME)
- public @interface ParameterAnnotation {
- int value();
- }
-
- public void testAnnotatedMethod(@ParameterAnnotation(123) String parameter) {
-
- }
-
@Test
public void propertyComplex() throws Exception {
- Property property = new Property(getClass(), getClass().getMethod("getComplexProperty"), getClass().getMethod("setComplexProperty", Map.class));
+ Property property = new Property(getClass(), getClass().getMethod("getComplexProperty"),
+ getClass().getMethod("setComplexProperty", Map.class));
TypeDescriptor desc = new TypeDescriptor(property);
assertEquals(String.class, desc.getMapKeyTypeDescriptor().getType());
assertEquals(Integer.class, desc.getMapValueTypeDescriptor().getElementTypeDescriptor().getElementTypeDescriptor().getType());
}
- public Map<String, List<List<Integer>>> getComplexProperty() {
- return null;
- }
-
- public void setComplexProperty(Map<String, List<List<Integer>>> complexProperty) {
-
- }
-
@Test
public void propertyGenericType() throws Exception {
GenericType<Integer> genericBean = new IntegerType();
- Property property = new Property(getClass(), genericBean.getClass().getMethod("getProperty"), genericBean.getClass().getMethod("setProperty", Integer.class));
+ Property property = new Property(getClass(), genericBean.getClass().getMethod("getProperty"),
+ genericBean.getClass().getMethod("setProperty", Integer.class));
TypeDescriptor desc = new TypeDescriptor(property);
assertEquals(Integer.class, desc.getType());
}
@@ -245,7 +189,8 @@ public class TypeDescriptorTests {
@Test
public void propertyTypeCovariance() throws Exception {
GenericType<Number> genericBean = new NumberType();
- Property property = new Property(getClass(), genericBean.getClass().getMethod("getProperty"), genericBean.getClass().getMethod("setProperty", Number.class));
+ Property property = new Property(getClass(), genericBean.getClass().getMethod("getProperty"),
+ genericBean.getClass().getMethod("setProperty", Number.class));
TypeDescriptor desc = new TypeDescriptor(property);
assertEquals(Integer.class, desc.getType());
}
@@ -253,69 +198,18 @@ public class TypeDescriptorTests {
@Test
public void propertyGenericTypeList() throws Exception {
GenericType<Integer> genericBean = new IntegerType();
- Property property = new Property(getClass(), genericBean.getClass().getMethod("getListProperty"), genericBean.getClass().getMethod("setListProperty", List.class));
+ Property property = new Property(getClass(), genericBean.getClass().getMethod("getListProperty"),
+ genericBean.getClass().getMethod("setListProperty", List.class));
TypeDescriptor desc = new TypeDescriptor(property);
assertEquals(List.class, desc.getType());
assertEquals(Integer.class, desc.getElementTypeDescriptor().getType());
}
- public interface GenericType<T> {
- T getProperty();
-
- void setProperty(T t);
-
- List<T> getListProperty();
-
- void setListProperty(List<T> t);
-
- }
-
- public class IntegerType implements GenericType<Integer> {
-
- @Override
- public Integer getProperty() {
- return null;
- }
-
- @Override
- public void setProperty(Integer t) {
- }
-
- @Override
- public List<Integer> getListProperty() {
- return null;
- }
-
- @Override
- public void setListProperty(List<Integer> t) {
- }
- }
-
- public class NumberType implements GenericType<Number> {
-
- @Override
- public Integer getProperty() {
- return null;
- }
-
- @Override
- public void setProperty(Number t) {
- }
-
- @Override
- public List<Number> getListProperty() {
- return null;
- }
-
- @Override
- public void setListProperty(List<Number> t) {
- }
- }
-
@Test
public void propertyGenericClassList() throws Exception {
IntegerClass genericBean = new IntegerClass();
- Property property = new Property(genericBean.getClass(), genericBean.getClass().getMethod("getListProperty"), genericBean.getClass().getMethod("setListProperty", List.class));
+ Property property = new Property(genericBean.getClass(), genericBean.getClass().getMethod("getListProperty"),
+ genericBean.getClass().getMethod("setListProperty", List.class));
TypeDescriptor desc = new TypeDescriptor(property);
assertEquals(List.class, desc.getType());
assertEquals(Integer.class, desc.getElementTypeDescriptor().getType());
@@ -323,32 +217,10 @@ public class TypeDescriptorTests {
assertTrue(desc.hasAnnotation(MethodAnnotation1.class));
}
- public static class GenericClass<T> {
-
- public T getProperty() {
- return null;
- }
-
- public void setProperty(T t) {
- }
-
- @MethodAnnotation1
- public List<T> getListProperty() {
- return null;
- }
-
- public void setListProperty(List<T> t) {
- }
-
- }
-
- public static class IntegerClass extends GenericClass<Integer> {
-
- }
-
@Test
public void property() throws Exception {
- Property property = new Property(getClass(), getClass().getMethod("getProperty"), getClass().getMethod("setProperty", Map.class));
+ Property property = new Property(
+ getClass(), getClass().getMethod("getProperty"), getClass().getMethod("setProperty", Map.class));
TypeDescriptor desc = new TypeDescriptor(property);
assertEquals(Map.class, desc.getType());
assertEquals(Integer.class, desc.getMapKeyTypeDescriptor().getElementTypeDescriptor().getType());
@@ -358,60 +230,6 @@ public class TypeDescriptorTests {
assertNotNull(desc.getAnnotation(MethodAnnotation3.class));
}
- @MethodAnnotation1
- public Map<List<Integer>, List<Long>> getProperty() {
- return property;
- }
-
- @MethodAnnotation2
- public void setProperty(Map<List<Integer>, List<Long>> property) {
- this.property = property;
- }
-
- @MethodAnnotation3
- private Map<List<Integer>, List<Long>> property;
-
-
- @Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
- @Retention(RetentionPolicy.RUNTIME)
- public @interface MethodAnnotation1 {
- }
-
- @Target({ElementType.METHOD})
- @Retention(RetentionPolicy.RUNTIME)
- public @interface MethodAnnotation2 {
- }
-
- @Target({ElementType.FIELD})
- @Retention(RetentionPolicy.RUNTIME)
- public @interface MethodAnnotation3 {
- }
-
- @MethodAnnotation1
- @Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
- @Retention(RetentionPolicy.RUNTIME)
- public @interface ComposedMethodAnnotation1 {}
-
- @ComposedMethodAnnotation1
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface ComposedComposedMethodAnnotation1 {}
-
- @MethodAnnotation1
- public void methodWithLocalAnnotation() {}
-
- @ComposedMethodAnnotation1
- public void methodWithComposedAnnotation() {}
-
- @ComposedComposedMethodAnnotation1
- public void methodWithComposedComposedAnnotation() {}
-
- private void assertAnnotationFoundOnMethod(Class<? extends Annotation> annotationType, String methodName) throws Exception {
- TypeDescriptor typeDescriptor = new TypeDescriptor(new MethodParameter(getClass().getMethod(methodName), -1));
- assertNotNull("Should have found @" + annotationType.getSimpleName() + " on " + methodName + ".",
- typeDescriptor.getAnnotation(annotationType));
- }
-
@Test
public void getAnnotationOnMethodThatIsLocallyAnnotated() throws Exception {
assertAnnotationFoundOnMethod(MethodAnnotation1.class, "methodWithLocalAnnotation");
@@ -427,6 +245,12 @@ public class TypeDescriptorTests {
assertAnnotationFoundOnMethod(MethodAnnotation1.class, "methodWithComposedComposedAnnotation");
}
+ private void assertAnnotationFoundOnMethod(Class<? extends Annotation> annotationType, String methodName) throws Exception {
+ TypeDescriptor typeDescriptor = new TypeDescriptor(new MethodParameter(getClass().getMethod(methodName), -1));
+ assertNotNull("Should have found @" + annotationType.getSimpleName() + " on " + methodName + ".",
+ typeDescriptor.getAnnotation(annotationType));
+ }
+
@Test
public void fieldScalar() throws Exception {
TypeDescriptor typeDescriptor = new TypeDescriptor(getClass().getField("fieldScalar"));
@@ -438,8 +262,6 @@ public class TypeDescriptorTests {
assertEquals(Integer.class, typeDescriptor.getObjectType());
}
- public Integer fieldScalar;
-
@Test
public void fieldList() throws Exception {
TypeDescriptor typeDescriptor = new TypeDescriptor(TypeDescriptorTests.class.getDeclaredField("listOfString"));
@@ -504,8 +326,6 @@ public class TypeDescriptorTests {
assertEquals(Long.class, desc.getMapValueTypeDescriptor().getElementTypeDescriptor().getType());
}
- public Map<List<Integer>, List<Long>> fieldMap;
-
@Test
public void fieldAnnotated() throws Exception {
TypeDescriptor typeDescriptor = new TypeDescriptor(getClass().getField("fieldAnnotated"));
@@ -513,15 +333,6 @@ public class TypeDescriptorTests {
assertNotNull(typeDescriptor.getAnnotation(FieldAnnotation.class));
}
- @FieldAnnotation
- public List<String> fieldAnnotated;
-
- @Target({ElementType.FIELD})
- @Retention(RetentionPolicy.RUNTIME)
- public @interface FieldAnnotation {
-
- }
-
@Test
public void valueOfScalar() {
TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(Integer.class);
@@ -592,7 +403,7 @@ public class TypeDescriptorTests {
assertEquals(String.class, t1.getType());
}
- @Test(expected=IllegalArgumentException.class)
+ @Test(expected = IllegalArgumentException.class)
public void nestedMethodParameterNot1NestedLevel() throws Exception {
TypeDescriptor.nested(new MethodParameter(getClass().getMethod("test4", List.class), 0, 2), 2);
}
@@ -609,31 +420,11 @@ public class TypeDescriptorTests {
assertNull(t1);
}
- @Test(expected=IllegalArgumentException.class)
+ @Test(expected = IllegalArgumentException.class)
public void nestedMethodParameterTypeInvalidNestingLevel() throws Exception {
TypeDescriptor.nested(new MethodParameter(getClass().getMethod("test5", String.class), 0, 2), 2);
}
- public void test1(List<String> param1) {
-
- }
-
- public void test2(List<List<String>> param1) {
-
- }
-
- public void test3(Map<Integer, String> param1) {
-
- }
-
- public void test4(List<Map<Integer, String>> param1) {
-
- }
-
- public void test5(String param1) {
-
- }
-
@Test
public void nestedNotParameterized() throws Exception {
TypeDescriptor t1 = TypeDescriptor.nested(new MethodParameter(getClass().getMethod("test6", List.class), 0), 1);
@@ -643,18 +434,12 @@ public class TypeDescriptorTests {
assertNull(t2);
}
- public void test6(List<List> param1) {
-
- }
-
@Test
public void nestedFieldTypeMapTwoLevels() throws Exception {
TypeDescriptor t1 = TypeDescriptor.nested(getClass().getField("test4"), 2);
assertEquals(String.class, t1.getType());
}
- public List<Map<Integer, String>> test4;
-
@Test
public void nestedPropertyTypeMapTwoLevels() throws Exception {
Property property = new Property(getClass(), getClass().getMethod("getTest4"), getClass().getMethod("setTest4", List.class));
@@ -662,14 +447,6 @@ public class TypeDescriptorTests {
assertEquals(String.class, t1.getType());
}
- public List<Map<Integer, String>> getTest4() {
- return null;
- }
-
- public void setTest4(List<Map<Integer, String>> test4) {
-
- }
-
@Test
public void collection() {
TypeDescriptor desc = TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(Integer.class));
@@ -762,9 +539,6 @@ public class TypeDescriptorTests {
assertNotNull(desc.getAnnotation(FieldAnnotation.class));
}
- @FieldAnnotation
- public List<List<Integer>> listPreserveContext;
-
@Test
public void mapKeyType() {
TypeDescriptor desc = TypeDescriptor.valueOf(Map.class);
@@ -783,9 +557,6 @@ public class TypeDescriptorTests {
assertNotNull(desc.getAnnotation(FieldAnnotation.class));
}
- @FieldAnnotation
- public Map<List<Integer>, List<Integer>> mapPreserveContext;
-
@Test
public void mapValueType() {
TypeDescriptor desc = TypeDescriptor.valueOf(Map.class);
@@ -805,7 +576,7 @@ public class TypeDescriptorTests {
}
@Test
- public void equals() throws Exception {
+ public void equality() throws Exception {
TypeDescriptor t1 = TypeDescriptor.valueOf(String.class);
TypeDescriptor t2 = TypeDescriptor.valueOf(String.class);
TypeDescriptor t3 = TypeDescriptor.valueOf(Date.class);
@@ -826,6 +597,18 @@ public class TypeDescriptorTests {
TypeDescriptor t11 = new TypeDescriptor(getClass().getField("mapField"));
TypeDescriptor t12 = new TypeDescriptor(getClass().getField("mapField"));
assertEquals(t11, t12);
+
+ TypeDescriptor t13 = new TypeDescriptor(new MethodParameter(getClass().getMethod("testAnnotatedMethod", String.class), 0));
+ TypeDescriptor t14 = new TypeDescriptor(new MethodParameter(getClass().getMethod("testAnnotatedMethod", String.class), 0));
+ assertEquals(t13, t14);
+
+ TypeDescriptor t15 = new TypeDescriptor(new MethodParameter(getClass().getMethod("testAnnotatedMethod", String.class), 0));
+ TypeDescriptor t16 = new TypeDescriptor(new MethodParameter(getClass().getMethod("testAnnotatedMethodDifferentAnnotationValue", String.class), 0));
+ assertNotEquals(t15, t16);
+
+ TypeDescriptor t17 = new TypeDescriptor(new MethodParameter(getClass().getMethod("testAnnotatedMethod", String.class), 0));
+ TypeDescriptor t18 = new TypeDescriptor(new MethodParameter(getClass().getMethod("test5", String.class), 0));
+ assertNotEquals(t17, t18);
}
@Test
@@ -844,10 +627,6 @@ public class TypeDescriptorTests {
assertTrue(TypeDescriptor.valueOf(List.class).isAssignableTo(new TypeDescriptor(getClass().getField("listField"))));
}
- public List notGenericList;
-
- public List<Number> isAssignableElementTypes;
-
@Test
public void isAssignableMapKeyValueTypes() throws Exception {
assertTrue(new TypeDescriptor(getClass().getField("mapField")).isAssignableTo(new TypeDescriptor(getClass().getField("mapField"))));
@@ -857,10 +636,6 @@ public class TypeDescriptorTests {
assertTrue(TypeDescriptor.valueOf(Map.class).isAssignableTo(new TypeDescriptor(getClass().getField("mapField"))));
}
- public Map notGenericMap;
-
- public Map<CharSequence, Number> isAssignableMapKeyValueTypes;
-
@Test
public void multiValueMap() throws Exception {
TypeDescriptor td = new TypeDescriptor(getClass().getField("multiValueMap"));
@@ -871,8 +646,6 @@ public class TypeDescriptorTests {
td.getMapValueTypeDescriptor().getElementTypeDescriptor().getType());
}
- public MultiValueMap<String, Integer> multiValueMap = new LinkedMultiValueMap<String, Integer>();
-
@Test
public void passDownGeneric() throws Exception {
TypeDescriptor td = new TypeDescriptor(getClass().getField("passDownGeneric"));
@@ -881,12 +654,6 @@ public class TypeDescriptorTests {
assertEquals(Integer.class, td.getElementTypeDescriptor().getElementTypeDescriptor().getElementTypeDescriptor().getType());
}
- public PassDownGeneric<Integer> passDownGeneric = new PassDownGeneric<Integer>();
-
- @SuppressWarnings("serial")
- public static class PassDownGeneric<T> extends ArrayList<List<Set<T>>> {
- }
-
@Test
public void testUpCast() throws Exception {
Property property = new Property(getClass(), getClass().getMethod("getProperty"),
@@ -904,8 +671,9 @@ public class TypeDescriptorTests {
try {
typeDescriptor.upcast(Collection.class);
fail("Did not throw");
- } catch(IllegalArgumentException e) {
- assertEquals("interface java.util.Map is not assignable to interface java.util.Collection", e.getMessage());
+ }
+ catch (IllegalArgumentException ex) {
+ assertEquals("interface java.util.Map is not assignable to interface java.util.Collection", ex.getMessage());
}
}
@@ -933,13 +701,13 @@ public class TypeDescriptorTests {
@Test
public void createMapArray() throws Exception {
- TypeDescriptor mapType = TypeDescriptor.map(LinkedHashMap.class, TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Integer.class));
+ TypeDescriptor mapType = TypeDescriptor.map(
+ LinkedHashMap.class, TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Integer.class));
TypeDescriptor arrayType = TypeDescriptor.array(mapType);
assertEquals(arrayType.getType(), LinkedHashMap[].class);
assertEquals(arrayType.getElementTypeDescriptor(), mapType);
}
-
@Test
public void createStringArray() throws Exception {
TypeDescriptor arrayType = TypeDescriptor.array(TypeDescriptor.valueOf(String.class));
@@ -985,4 +753,268 @@ public class TypeDescriptorTests {
assertThat(TypeDescriptor.valueOf(Integer.class).getSource(), equalTo((Object) Integer.class));
}
+
+ // Methods designed for test introspection
+
+ public void testParameterPrimitive(int primitive) {
+ }
+
+ public void testParameterScalar(String value) {
+ }
+
+ public void testParameterList(List<List<Map<Integer, Enum<?>>>> list) {
+ }
+
+ public void testParameterListNoParamTypes(List list) {
+ }
+
+ public void testParameterArray(Integer[] array) {
+ }
+
+ public void testParameterMap(Map<Integer, List<String>> map) {
+ }
+
+ public void test1(List<String> param1) {
+ }
+
+ public void test2(List<List<String>> param1) {
+ }
+
+ public void test3(Map<Integer, String> param1) {
+ }
+
+ public void test4(List<Map<Integer, String>> param1) {
+ }
+
+ public void test5(String param1) {
+ }
+
+ public void test6(List<List> param1) {
+ }
+
+ public List<Map<Integer, String>> getTest4() {
+ return null;
+ }
+
+ public void setTest4(List<Map<Integer, String>> test4) {
+ }
+
+ public Map<String, List<List<Integer>>> getComplexProperty() {
+ return null;
+ }
+
+ @MethodAnnotation1
+ public Map<List<Integer>, List<Long>> getProperty() {
+ return property;
+ }
+
+ @MethodAnnotation2
+ public void setProperty(Map<List<Integer>, List<Long>> property) {
+ this.property = property;
+ }
+
+ @MethodAnnotation1
+ public void methodWithLocalAnnotation() {
+ }
+
+ @ComposedMethodAnnotation1
+ public void methodWithComposedAnnotation() {
+ }
+
+ @ComposedComposedMethodAnnotation1
+ public void methodWithComposedComposedAnnotation() {
+ }
+
+ public void setComplexProperty(Map<String, List<List<Integer>>> complexProperty) {
+ }
+
+ public void testAnnotatedMethod(@ParameterAnnotation(123) String parameter) {
+ }
+
+ public void testAnnotatedMethodDifferentAnnotationValue(@ParameterAnnotation(567) String parameter) {
+ }
+
+
+ // Fields designed for test introspection
+
+ public Integer fieldScalar;
+
+ public List<String> listOfString;
+
+ public List<List<String>> listOfListOfString = new ArrayList<List<String>>();
+
+ public List<List> listOfListOfUnknown = new ArrayList<List>();
+
+ public int[] intArray;
+
+ public List<String>[] arrayOfListOfString;
+
+ public List<Integer> listField = new ArrayList<Integer>();
+
+ public Map<String, Integer> mapField = new HashMap<String, Integer>();
+
+ public Map<String, List<Integer>> nestedMapField = new HashMap<String, List<Integer>>();
+
+ public Map<List<Integer>, List<Long>> fieldMap;
+
+ public List<Map<Integer, String>> test4;
+
+ @FieldAnnotation
+ public List<String> fieldAnnotated;
+
+ @FieldAnnotation
+ public List<List<Integer>> listPreserveContext;
+
+ @FieldAnnotation
+ public Map<List<Integer>, List<Integer>> mapPreserveContext;
+
+ @MethodAnnotation3
+ private Map<List<Integer>, List<Long>> property;
+
+ public List notGenericList;
+
+ public List<Number> isAssignableElementTypes;
+
+ public Map notGenericMap;
+
+ public Map<CharSequence, Number> isAssignableMapKeyValueTypes;
+
+ public MultiValueMap<String, Integer> multiValueMap = new LinkedMultiValueMap<String, Integer>();
+
+ public PassDownGeneric<Integer> passDownGeneric = new PassDownGeneric<Integer>();
+
+
+ // Classes designed for test introspection
+
+ @SuppressWarnings("serial")
+ public static class PassDownGeneric<T> extends ArrayList<List<Set<T>>> {
+ }
+
+
+ public static class GenericClass<T> {
+
+ public T getProperty() {
+ return null;
+ }
+
+ public void setProperty(T t) {
+ }
+
+ @MethodAnnotation1
+ public List<T> getListProperty() {
+ return null;
+ }
+
+ public void setListProperty(List<T> t) {
+ }
+ }
+
+
+ public static class IntegerClass extends GenericClass<Integer> {
+ }
+
+
+ public interface GenericType<T> {
+
+ T getProperty();
+
+ void setProperty(T t);
+
+ List<T> getListProperty();
+
+ void setListProperty(List<T> t);
+ }
+
+
+ public class IntegerType implements GenericType<Integer> {
+
+ @Override
+ public Integer getProperty() {
+ return null;
+ }
+
+ @Override
+ public void setProperty(Integer t) {
+ }
+
+ @Override
+ public List<Integer> getListProperty() {
+ return null;
+ }
+
+ @Override
+ public void setListProperty(List<Integer> t) {
+ }
+ }
+
+
+ public class NumberType implements GenericType<Number> {
+
+ @Override
+ public Integer getProperty() {
+ return null;
+ }
+
+ @Override
+ public void setProperty(Number t) {
+ }
+
+ @Override
+ public List<Number> getListProperty() {
+ return null;
+ }
+
+ @Override
+ public void setListProperty(List<Number> t) {
+ }
+ }
+
+
+ // Annotations used on tested elements
+
+ @Target({ElementType.PARAMETER})
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface ParameterAnnotation {
+
+ int value();
+ }
+
+
+ @Target({ElementType.FIELD})
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface FieldAnnotation {
+ }
+
+
+ @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface MethodAnnotation1 {
+ }
+
+
+ @Target({ElementType.METHOD})
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface MethodAnnotation2 {
+ }
+
+
+ @Target({ElementType.FIELD})
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface MethodAnnotation3 {
+ }
+
+
+ @MethodAnnotation1
+ @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface ComposedMethodAnnotation1 {
+ }
+
+
+ @ComposedMethodAnnotation1
+ @Target(ElementType.METHOD)
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface ComposedComposedMethodAnnotation1 {
+ }
+
}
diff --git a/spring-core/src/test/java/org/springframework/core/convert/support/CollectionToCollectionConverterTests.java b/spring-core/src/test/java/org/springframework/core/convert/support/CollectionToCollectionConverterTests.java
index c65ba7d3..131e739e 100644
--- a/spring-core/src/test/java/org/springframework/core/convert/support/CollectionToCollectionConverterTests.java
+++ b/spring-core/src/test/java/org/springframework/core/convert/support/CollectionToCollectionConverterTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -75,10 +75,10 @@ public class CollectionToCollectionConverterTests {
conversionService.addConverterFactory(new StringToNumberConverterFactory());
assertTrue(conversionService.canConvert(sourceType, targetType));
@SuppressWarnings("unchecked")
- List<String> result = (List<String>) conversionService.convert(list, sourceType, targetType);
+ List<Integer> result = (List<Integer>) conversionService.convert(list, sourceType, targetType);
assertFalse(list.equals(result));
- assertEquals(9, result.get(0));
- assertEquals(37, result.get(1));
+ assertEquals(9, result.get(0).intValue());
+ assertEquals(37, result.get(1).intValue());
}
@Test
@@ -262,6 +262,25 @@ public class CollectionToCollectionConverterTests {
}
+ public ArrayList<Integer> scalarListTarget;
+
+ public List<Integer> emptyListTarget;
+
+ public LinkedList<Integer> emptyListDifferentTarget;
+
+ public List<List<List<Integer>>> objectToCollection;
+
+ public List<String> strings;
+
+ public List<?> list = Collections.emptyList();
+
+ public Collection<?> wildcardCollection = Collections.emptyList();
+
+ public List<Resource> resources;
+
+ public EnumSet<MyEnum> enumSet;
+
+
public static abstract class BaseResource implements Resource {
@Override
@@ -330,25 +349,6 @@ public class CollectionToCollectionConverterTests {
}
- public static enum MyEnum {A, B, C}
-
-
- public ArrayList<Integer> scalarListTarget;
-
- public List<Integer> emptyListTarget;
-
- public LinkedList<Integer> emptyListDifferentTarget;
-
- public List<List<List<Integer>>> objectToCollection;
-
- public List<String> strings;
-
- public List<?> list = Collections.emptyList();
-
- public Collection<?> wildcardCollection = Collections.emptyList();
-
- public List<Resource> resources;
-
- public EnumSet<MyEnum> enumSet;
+ public enum MyEnum {A, B, C}
}
diff --git a/spring-core/src/test/java/org/springframework/core/convert/support/DefaultConversionServiceTests.java b/spring-core/src/test/java/org/springframework/core/convert/support/DefaultConversionServiceTests.java
index 5059738c..e7caf78b 100644
--- a/spring-core/src/test/java/org/springframework/core/convert/support/DefaultConversionServiceTests.java
+++ b/spring-core/src/test/java/org/springframework/core/convert/support/DefaultConversionServiceTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +243,26 @@ public class DefaultConversionServiceTests {
public void testEnumToString() {
assertEquals("BAR", conversionService.convert(Foo.BAR, String.class));
}
+
+ @Test
+ public void testIntegerToEnum() throws Exception {
+ assertEquals(Foo.BAR, conversionService.convert(0, Foo.class));
+ }
+
+ @Test
+ public void testIntegerToEnumWithSubclass() throws Exception {
+ assertEquals(SubFoo.BAZ, conversionService.convert(1, SubFoo.BAR.getClass()));
+ }
+
+ @Test
+ public void testIntegerToEnumNull() {
+ assertEquals(null, conversionService.convert(null, Foo.class));
+ }
+
+ @Test
+ public void testEnumToInteger() {
+ assertEquals(Integer.valueOf(0), conversionService.convert(Foo.BAR, Integer.class));
+ }
@Test
public void testStringToEnumSet() throws Exception {
@@ -313,7 +333,7 @@ public class DefaultConversionServiceTests {
@Test
public void convertArrayToCollectionInterface() {
- List<?> result = conversionService.convert(new String[] { "1", "2", "3" }, List.class);
+ List<?> result = conversionService.convert(new String[] {"1", "2", "3"}, List.class);
assertEquals("1", result.get(0));
assertEquals("2", result.get(1));
assertEquals("3", result.get(2));
@@ -322,7 +342,7 @@ public class DefaultConversionServiceTests {
@Test
public void convertArrayToCollectionGenericTypeConversion() throws Exception {
@SuppressWarnings("unchecked")
- List<Integer> result = (List<Integer>) conversionService.convert(new String[] { "1", "2", "3" }, TypeDescriptor
+ List<Integer> result = (List<Integer>) conversionService.convert(new String[] {"1", "2", "3"}, TypeDescriptor
.valueOf(String[].class), new TypeDescriptor(getClass().getDeclaredField("genericList")));
assertEquals(new Integer("1"), result.get(0));
assertEquals(new Integer("2"), result.get(1));
@@ -344,7 +364,7 @@ public class DefaultConversionServiceTests {
ConverterRegistry registry = (conversionService);
registry.addConverter(new ColorConverter());
@SuppressWarnings("unchecked")
- List<Color> colors = (List<Color>) conversionService.convert(new String[] { "ffffff", "#000000" },
+ List<Color> colors = (List<Color>) conversionService.convert(new String[] {"ffffff", "#000000"},
TypeDescriptor.valueOf(String[].class),
new TypeDescriptor(new MethodParameter(getClass().getMethod("handlerMethod", List.class), 0)));
assertEquals(2, colors.size());
@@ -354,7 +374,7 @@ public class DefaultConversionServiceTests {
@Test
public void convertArrayToCollectionImpl() {
- LinkedList<?> result = conversionService.convert(new String[] { "1", "2", "3" }, LinkedList.class);
+ LinkedList<?> result = conversionService.convert(new String[] {"1", "2", "3"}, LinkedList.class);
assertEquals("1", result.get(0));
assertEquals("2", result.get(1));
assertEquals("3", result.get(2));
@@ -371,13 +391,13 @@ public class DefaultConversionServiceTests {
@Test
public void convertArrayToString() {
- String result = conversionService.convert(new String[] { "1", "2", "3" }, String.class);
+ String result = conversionService.convert(new String[] {"1", "2", "3"}, String.class);
assertEquals("1,2,3", result);
}
@Test
public void convertArrayToStringWithElementConversion() {
- String result = conversionService.convert(new Integer[] { 1, 2, 3 }, String.class);
+ String result = conversionService.convert(new Integer[] {1, 2, 3}, String.class);
assertEquals("1,2,3", result);
}
@@ -422,21 +442,21 @@ public class DefaultConversionServiceTests {
@Test
public void convertArrayToObject() {
- Object[] array = new Object[] { 3L };
+ Object[] array = new Object[] {3L};
Object result = conversionService.convert(array, Long.class);
assertEquals(3L, result);
}
@Test
public void convertArrayToObjectWithElementConversion() {
- String[] array = new String[] { "3" };
+ String[] array = new String[] {"3"};
Integer result = conversionService.convert(array, Integer.class);
assertEquals(new Integer(3), result);
}
@Test
public void convertArrayToObjectAssignableTargetType() {
- Long[] array = new Long[] { 3L };
+ Long[] array = new Long[] {3L};
Long[] result = (Long[]) conversionService.convert(array, Object.class);
assertArrayEquals(array, result);
}
@@ -561,9 +581,9 @@ public class DefaultConversionServiceTests {
}
@Test
- @SuppressWarnings("unchecked")
+ @SuppressWarnings("rawtypes")
public void convertObjectToCollection() {
- List<String> result = (List<String>) conversionService.convert(3L, List.class);
+ List result = conversionService.convert(3L, List.class);
assertEquals(1, result.size());
assertEquals(3L, result.get(0));
}
@@ -849,14 +869,14 @@ public class DefaultConversionServiceTests {
}
});
char[] converted = conversionService.convert("abc", char[].class);
- assertThat(converted, equalTo(new char[] { 'a', 'b', 'c' }));
+ assertThat(converted, equalTo(new char[] {'a', 'b', 'c'}));
}
@Test
@SuppressWarnings("unchecked")
public void multidimensionalArrayToListConversionShouldConvertEntriesCorrectly() {
- String[][] grid = new String[][] { new String[] { "1", "2", "3", "4" }, new String[] { "5", "6", "7", "8" },
- new String[] { "9", "10", "11", "12" } };
+ String[][] grid = new String[][] {new String[] {"1", "2", "3", "4"}, new String[] {"5", "6", "7", "8"},
+ new String[] {"9", "10", "11", "12"}};
List<String[]> converted = conversionService.convert(grid, List.class);
String[][] convertedBack = conversionService.convert(converted, String[][].class);
assertArrayEquals(grid, convertedBack);
@@ -865,16 +885,15 @@ public class DefaultConversionServiceTests {
@Test
public void convertCannotOptimizeArray() {
conversionService.addConverter(new Converter<Byte, Byte>() {
-
@Override
public Byte convert(Byte source) {
return (byte) (source + 1);
}
});
- byte[] byteArray = new byte[] { 1, 2, 3 };
+ byte[] byteArray = new byte[] {1, 2, 3};
byte[] converted = conversionService.convert(byteArray, byte[].class);
assertNotSame(byteArray, converted);
- assertTrue(Arrays.equals(new byte[] { 2, 3, 4 }, converted));
+ assertTrue(Arrays.equals(new byte[] {2, 3, 4}, converted));
}
@Test
@@ -938,6 +957,7 @@ public class DefaultConversionServiceTests {
public enum Foo {
+
BAR, BAZ
}
@@ -964,7 +984,12 @@ public class DefaultConversionServiceTests {
public class ColorConverter implements Converter<String, Color> {
@Override
- public Color convert(String source) { if (!source.startsWith("#")) source = "#" + source; return Color.decode(source); }
+ public Color convert(String source) {
+ if (!source.startsWith("#")) {
+ source = "#" + source;
+ }
+ return Color.decode(source);
+ }
}
@@ -1031,8 +1056,8 @@ public class DefaultConversionServiceTests {
private static class SSN {
static int constructorCount = 0;
- static int toStringCount = 0;
+ static int toStringCount = 0;
static void reset() {
constructorCount = 0;
diff --git a/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java b/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java
index 6b16aefe..4b3183b6 100644
--- a/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java
+++ b/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java
@@ -479,6 +479,21 @@ public class GenericConversionServiceTests {
}
@Test
+ public void conditionalConverterCachingForDifferentAnnotationAttributes() throws Exception {
+ conversionService.addConverter(new ColorConverter());
+ conversionService.addConverter(new MyConditionalColorConverter());
+
+ assertEquals(Color.BLACK, conversionService.convert("000000xxxx",
+ new TypeDescriptor(getClass().getField("activeColor"))));
+ assertEquals(Color.BLACK, conversionService.convert(" #000000 ",
+ new TypeDescriptor(getClass().getField("inactiveColor"))));
+ assertEquals(Color.BLACK, conversionService.convert("000000yyyy",
+ new TypeDescriptor(getClass().getField("activeColor"))));
+ assertEquals(Color.BLACK, conversionService.convert(" #000000 ",
+ new TypeDescriptor(getClass().getField("inactiveColor"))));
+ }
+
+ @Test
public void shouldNotSupportNullConvertibleTypesFromNonConditionalGenericConverter() {
GenericConverter converter = new NonConditionalGenericConverter();
try {
@@ -629,14 +644,49 @@ public class GenericConversionServiceTests {
}
+ @ExampleAnnotation(active = true)
+ public String annotatedString;
+
+ @ExampleAnnotation(active = true)
+ public Color activeColor;
+
+ @ExampleAnnotation(active = false)
+ public Color inactiveColor;
+
+ public List<Integer> list;
+
+ public Map<String, Integer> map;
+
+ public Map<String, ?> wildcardMap;
+
+ @SuppressWarnings("rawtypes")
+ public Collection rawCollection;
+
+ public Collection<?> genericCollection;
+
+ public Collection<String> stringCollection;
+
+ public Collection<Integer> integerCollection;
+
+
@Retention(RetentionPolicy.RUNTIME)
- private static @interface ExampleAnnotation {}
+ private @interface ExampleAnnotation {
+
+ boolean active();
+ }
+
+
+ private interface MyBaseInterface {
+ }
- private static interface MyBaseInterface {}
- private static interface MyInterface extends MyBaseInterface {}
+ private interface MyInterface extends MyBaseInterface {
+ }
+
+
+ private static class MyInterfaceImplementer implements MyInterface {
+ }
- private static class MyInterfaceImplementer implements MyInterface {}
private static class MyBaseInterfaceToStringConverter implements Converter<MyBaseInterface, String> {
@@ -646,6 +696,7 @@ public class GenericConversionServiceTests {
}
}
+
private static class MyStringArrayToResourceArrayConverter implements Converter<String[], Resource[]> {
@Override
@@ -654,6 +705,7 @@ public class GenericConversionServiceTests {
}
}
+
private static class MyStringArrayToIntegerArrayConverter implements Converter<String[], Integer[]> {
@Override
@@ -662,6 +714,7 @@ public class GenericConversionServiceTests {
}
}
+
private static class MyStringToIntegerArrayConverter implements Converter<String, Integer[]> {
@Override
@@ -671,6 +724,7 @@ public class GenericConversionServiceTests {
}
}
+
private static class WithCopyConstructor {
WithCopyConstructor() {}
@@ -679,6 +733,7 @@ public class GenericConversionServiceTests {
WithCopyConstructor(WithCopyConstructor value) {}
}
+
private static class MyConditionalConverter implements Converter<String, Color>, ConditionalConverter {
private int matchAttempts = 0;
@@ -699,6 +754,7 @@ public class GenericConversionServiceTests {
}
}
+
private static class NonConditionalGenericConverter implements GenericConverter {
@Override
@@ -712,6 +768,7 @@ public class GenericConversionServiceTests {
}
}
+
private static class MyConditionalGenericConverter implements GenericConverter, ConditionalConverter {
private final List<TypeDescriptor> sourceTypes = new ArrayList<TypeDescriptor>();
@@ -737,6 +794,7 @@ public class GenericConversionServiceTests {
}
}
+
private static class MyConditionalConverterFactory implements ConverterFactory<String, Color>, ConditionalConverter {
private MyConditionalConverter converter = new MyConditionalConverter();
@@ -768,11 +826,13 @@ public class GenericConversionServiceTests {
String getBaseCode();
}
- private static interface MyEnumInterface extends MyEnumBaseInterface {
+
+ private interface MyEnumInterface extends MyEnumBaseInterface {
String getCode();
}
- private static enum MyEnum implements MyEnumInterface {
+
+ private enum MyEnum implements MyEnumInterface {
A("1"),
B("2"),
@@ -795,7 +855,8 @@ public class GenericConversionServiceTests {
}
}
- private static enum EnumWithSubclass {
+
+ private enum EnumWithSubclass {
FIRST {
@Override
@@ -805,6 +866,7 @@ public class GenericConversionServiceTests {
}
}
+
@SuppressWarnings("rawtypes")
private static class MyStringToRawCollectionConverter implements Converter<String, Collection> {
@@ -814,6 +876,7 @@ public class GenericConversionServiceTests {
}
}
+
private static class MyStringToGenericCollectionConverter implements Converter<String, Collection<?>> {
@Override
@@ -822,6 +885,7 @@ public class GenericConversionServiceTests {
}
}
+
private static class MyEnumInterfaceToStringConverter<T extends MyEnumInterface> implements Converter<T, String> {
@Override
@@ -830,14 +894,16 @@ public class GenericConversionServiceTests {
}
}
+
private static class StringToMyEnumInterfaceConverterFactory implements ConverterFactory<String, MyEnumInterface> {
- @SuppressWarnings({ "unchecked", "rawtypes" })
+ @SuppressWarnings({"unchecked", "rawtypes"})
public <T extends MyEnumInterface> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToMyEnumInterfaceConverter(targetType);
}
private static class StringToMyEnumInterfaceConverter<T extends Enum<?> & MyEnumInterface> implements Converter<String, T> {
+
private final Class<T> enumType;
public StringToMyEnumInterfaceConverter(Class<T> enumType) {
@@ -855,9 +921,10 @@ public class GenericConversionServiceTests {
}
}
+
private static class StringToMyEnumBaseInterfaceConverterFactory implements ConverterFactory<String, MyEnumBaseInterface> {
- @SuppressWarnings({ "unchecked", "rawtypes" })
+ @SuppressWarnings({"unchecked", "rawtypes"})
public <T extends MyEnumBaseInterface> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToMyEnumBaseInterfaceConverter(targetType);
}
@@ -881,6 +948,7 @@ public class GenericConversionServiceTests {
}
}
+
private static class MyStringToStringCollectionConverter implements Converter<String, Collection<String>> {
@Override
@@ -889,6 +957,7 @@ public class GenericConversionServiceTests {
}
}
+
private static class MyStringToIntegerCollectionConverter implements Converter<String, Collection<Integer>> {
@Override
@@ -897,6 +966,7 @@ public class GenericConversionServiceTests {
}
}
+
@SuppressWarnings("rawtypes")
private static class UntypedConverter implements Converter {
@@ -906,28 +976,27 @@ public class GenericConversionServiceTests {
}
}
+
private static class ColorConverter implements Converter<String, Color> {
+
@Override
- public Color convert(String source) { if (!source.startsWith("#")) source = "#" + source; return Color.decode(source); }
+ public Color convert(String source) {
+ return Color.decode(source.trim());
+ }
}
- @ExampleAnnotation
- public String annotatedString;
-
- public List<Integer> list;
-
- public Map<String, Integer> map;
-
- public Map<String, ?> wildcardMap;
-
- @SuppressWarnings("rawtypes")
- public Collection rawCollection;
-
- public Collection<?> genericCollection;
-
- public Collection<String> stringCollection;
+ private static class MyConditionalColorConverter implements Converter<String, Color>, ConditionalConverter {
- public Collection<Integer> integerCollection;
+ @Override
+ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ ExampleAnnotation ann = targetType.getAnnotation(ExampleAnnotation.class);
+ return (ann != null && ann.active());
+ }
+ @Override
+ public Color convert(String source) {
+ return Color.decode(source.substring(0, 6));
+ }
+ }
}
diff --git a/spring-core/src/test/java/org/springframework/core/convert/support/MapToMapConverterTests.java b/spring-core/src/test/java/org/springframework/core/convert/support/MapToMapConverterTests.java
index 510f4f53..3757f554 100644
--- a/spring-core/src/test/java/org/springframework/core/convert/support/MapToMapConverterTests.java
+++ b/spring-core/src/test/java/org/springframework/core/convert/support/MapToMapConverterTests.java
@@ -36,9 +36,14 @@ import org.springframework.util.MultiValueMap;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
+/**
+ * @author Keith Donald
+ * @author Phil Webb
+ * @author Juergen Hoeller
+ */
public class MapToMapConverterTests {
- private GenericConversionService conversionService = new GenericConversionService();
+ private final GenericConversionService conversionService = new GenericConversionService();
@Before
@@ -54,12 +59,15 @@ public class MapToMapConverterTests {
map.put("2", "37");
TypeDescriptor sourceType = TypeDescriptor.forObject(map);
TypeDescriptor targetType = new TypeDescriptor(getClass().getField("scalarMapTarget"));
+
assertTrue(conversionService.canConvert(sourceType, targetType));
try {
conversionService.convert(map, sourceType, targetType);
- } catch (ConversionFailedException e) {
- assertTrue(e.getCause() instanceof ConverterNotFoundException);
}
+ catch (ConversionFailedException ex) {
+ assertTrue(ex.getCause() instanceof ConverterNotFoundException);
+ }
+
conversionService.addConverterFactory(new StringToNumberConverterFactory());
assertTrue(conversionService.canConvert(sourceType, targetType));
@SuppressWarnings("unchecked")
@@ -74,6 +82,7 @@ public class MapToMapConverterTests {
Map<String, String> map = new HashMap<String, String>();
map.put("1", "9");
map.put("2", "37");
+
assertTrue(conversionService.canConvert(Map.class, Map.class));
assertSame(map, conversionService.convert(map, Map.class));
}
@@ -85,12 +94,15 @@ public class MapToMapConverterTests {
map.put("2", "37");
TypeDescriptor sourceType = new TypeDescriptor(getClass().getField("notGenericMapSource"));
TypeDescriptor targetType = new TypeDescriptor(getClass().getField("scalarMapTarget"));
+
assertTrue(conversionService.canConvert(sourceType, targetType));
try {
conversionService.convert(map, sourceType, targetType);
- } catch (ConversionFailedException e) {
- assertTrue(e.getCause() instanceof ConverterNotFoundException);
}
+ catch (ConversionFailedException ex) {
+ assertTrue(ex.getCause() instanceof ConverterNotFoundException);
+ }
+
conversionService.addConverterFactory(new StringToNumberConverterFactory());
assertTrue(conversionService.canConvert(sourceType, targetType));
@SuppressWarnings("unchecked")
@@ -107,12 +119,15 @@ public class MapToMapConverterTests {
map.put("2", Arrays.asList("37", "23"));
TypeDescriptor sourceType = TypeDescriptor.forObject(map);
TypeDescriptor targetType = new TypeDescriptor(getClass().getField("collectionMapTarget"));
+
assertTrue(conversionService.canConvert(sourceType, targetType));
try {
conversionService.convert(map, sourceType, targetType);
- } catch (ConversionFailedException e) {
- assertTrue(e.getCause() instanceof ConverterNotFoundException);
}
+ catch (ConversionFailedException ex) {
+ assertTrue(ex.getCause() instanceof ConverterNotFoundException);
+ }
+
conversionService.addConverter(new CollectionToCollectionConverter(conversionService));
conversionService.addConverterFactory(new StringToNumberConverterFactory());
assertTrue(conversionService.canConvert(sourceType, targetType));
@@ -130,6 +145,7 @@ public class MapToMapConverterTests {
map.put("2", Arrays.asList("37", "23"));
TypeDescriptor sourceType = new TypeDescriptor(getClass().getField("sourceCollectionMapTarget"));
TypeDescriptor targetType = new TypeDescriptor(getClass().getField("collectionMapTarget"));
+
assertFalse(conversionService.canConvert(sourceType, targetType));
try {
conversionService.convert(map, sourceType, targetType);
@@ -138,6 +154,7 @@ public class MapToMapConverterTests {
catch (ConverterNotFoundException ex) {
// expected
}
+
conversionService.addConverter(new CollectionToCollectionConverter(conversionService));
conversionService.addConverterFactory(new StringToNumberConverterFactory());
assertTrue(conversionService.canConvert(sourceType, targetType));
@@ -153,6 +170,7 @@ public class MapToMapConverterTests {
Map<String, List<String>> map = new HashMap<String, List<String>>();
map.put("1", Arrays.asList("9", "12"));
map.put("2", Arrays.asList("37", "23"));
+
assertTrue(conversionService.canConvert(Map.class, Map.class));
assertSame(map, conversionService.convert(map, Map.class));
}
@@ -164,6 +182,7 @@ public class MapToMapConverterTests {
map.put("2", Arrays.asList("37", "23"));
conversionService.addConverter(new CollectionToCollectionConverter(conversionService));
conversionService.addConverter(new CollectionToObjectConverter(conversionService));
+
assertTrue(conversionService.canConvert(Map.class, Map.class));
assertSame(map, conversionService.convert(map, Map.class));
}
@@ -173,6 +192,7 @@ public class MapToMapConverterTests {
Map<String, String> map = new HashMap<String, String>();
TypeDescriptor sourceType = TypeDescriptor.forObject(map);
TypeDescriptor targetType = new TypeDescriptor(getClass().getField("emptyMapTarget"));
+
assertTrue(conversionService.canConvert(sourceType, targetType));
assertSame(map, conversionService.convert(map, sourceType, targetType));
}
@@ -180,6 +200,7 @@ public class MapToMapConverterTests {
@Test
public void emptyMapNoTargetGenericInfo() throws Exception {
Map<String, String> map = new HashMap<String, String>();
+
assertTrue(conversionService.canConvert(Map.class, Map.class));
assertSame(map, conversionService.convert(map, Map.class));
}
@@ -189,6 +210,7 @@ public class MapToMapConverterTests {
Map<String, String> map = new HashMap<String, String>();
TypeDescriptor sourceType = TypeDescriptor.forObject(map);
TypeDescriptor targetType = new TypeDescriptor(getClass().getField("emptyMapDifferentTarget"));
+
assertTrue(conversionService.canConvert(sourceType, targetType));
@SuppressWarnings("unchecked")
LinkedHashMap<String, String> result = (LinkedHashMap<String, String>) conversionService.convert(map, sourceType, targetType);
@@ -205,6 +227,7 @@ public class MapToMapConverterTests {
TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Integer.class));
TypeDescriptor targetType = TypeDescriptor.map(NoDefaultConstructorMap.class,
TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Integer.class));
+
assertTrue(conversionService.canConvert(sourceType, targetType));
@SuppressWarnings("unchecked")
Map<String, Integer> result = (Map<String, Integer>) conversionService.convert(map, sourceType, targetType);
@@ -220,6 +243,7 @@ public class MapToMapConverterTests {
source.put("a", Arrays.asList(1, 2, 3));
source.put("b", Arrays.asList(4, 5, 6));
TypeDescriptor targetType = new TypeDescriptor(getClass().getField("multiValueMapTarget"));
+
MultiValueMap<String, String> converted = (MultiValueMap<String, String>) conversionService.convert(source, targetType);
assertThat(converted.size(), equalTo(2));
assertThat(converted.get("a"), equalTo(Arrays.asList("1", "2", "3")));
@@ -234,6 +258,7 @@ public class MapToMapConverterTests {
source.put("a", 1);
source.put("b", 2);
TypeDescriptor targetType = new TypeDescriptor(getClass().getField("multiValueMapTarget"));
+
MultiValueMap<String, String> converted = (MultiValueMap<String, String>) conversionService.convert(source, targetType);
assertThat(converted.size(), equalTo(2));
assertThat(converted.get("a"), equalTo(Arrays.asList("1")));
@@ -249,23 +274,12 @@ public class MapToMapConverterTests {
EnumMap<MyEnum, Integer> result = new EnumMap<MyEnum, Integer>(MyEnum.class);
result.put(MyEnum.A, 1);
result.put(MyEnum.C, 2);
- assertEquals(result,
- conversionService.convert(source, TypeDescriptor.forObject(source), new TypeDescriptor(getClass().getField("enumMap"))));
- }
-
-
- @SuppressWarnings("serial")
- public static class NoDefaultConstructorMap<K, V> extends HashMap<K, V> {
- public NoDefaultConstructorMap(Map<? extends K, ? extends V> map) {
- super(map);
- }
+ assertEquals(result, conversionService.convert(source,
+ TypeDescriptor.forObject(source), new TypeDescriptor(getClass().getField("enumMap"))));
}
- public static enum MyEnum {A, B, C}
-
-
public Map<Integer, Integer> scalarMapTarget;
public Map<Integer, List<Integer>> collectionMapTarget;
@@ -283,4 +297,16 @@ public class MapToMapConverterTests {
public EnumMap<MyEnum, Integer> enumMap;
+
+ @SuppressWarnings("serial")
+ public static class NoDefaultConstructorMap<K, V> extends HashMap<K, V> {
+
+ public NoDefaultConstructorMap(Map<? extends K, ? extends V> map) {
+ super(map);
+ }
+ }
+
+
+ public enum MyEnum {A, B, C}
+
}
diff --git a/spring-core/src/test/java/org/springframework/core/convert/support/StreamConverterTests.java b/spring-core/src/test/java/org/springframework/core/convert/support/StreamConverterTests.java
index 6b09204e..2da31bf1 100644
--- a/spring-core/src/test/java/org/springframework/core/convert/support/StreamConverterTests.java
+++ b/spring-core/src/test/java/org/springframework/core/convert/support/StreamConverterTests.java
@@ -49,6 +49,7 @@ public class StreamConverterTests {
private final StreamConverter streamConverter = new StreamConverter(this.conversionService);
+
@Before
public void setup() {
this.conversionService.addConverter(new CollectionToCollectionConverter(this.conversionService));
@@ -57,13 +58,15 @@ public class StreamConverterTests {
this.conversionService.addConverter(this.streamConverter);
}
+
@Test
public void convertFromStreamToList() throws NoSuchFieldException {
this.conversionService.addConverter(Number.class, String.class, new ObjectToStringConverter());
Stream<Integer> stream = Arrays.asList(1, 2, 3).stream();
TypeDescriptor listOfStrings = new TypeDescriptor(Types.class.getField("listOfStrings")); ;
Object result = this.conversionService.convert(stream, listOfStrings);
- assertNotNull("converted object must not be null", result);
+
+ assertNotNull("Converted object must not be null", result);
assertTrue("Converted object must be a list", result instanceof List);
@SuppressWarnings("unchecked")
List<String> content = (List<String>) result;
@@ -79,7 +82,8 @@ public class StreamConverterTests {
Stream<Integer> stream = Arrays.asList(1, 2, 3).stream();
TypeDescriptor arrayOfLongs = new TypeDescriptor(Types.class.getField("arrayOfLongs")); ;
Object result = this.conversionService.convert(stream, arrayOfLongs);
- assertNotNull("converted object must not be null", result);
+
+ assertNotNull("Converted object must not be null", result);
assertTrue("Converted object must be an array", result.getClass().isArray());
Long[] content = (Long[]) result;
assertEquals(Long.valueOf(1L), content[0]);
@@ -93,7 +97,8 @@ public class StreamConverterTests {
Stream<Integer> stream = Arrays.asList(1, 2, 3).stream();
TypeDescriptor listOfStrings = new TypeDescriptor(Types.class.getField("rawList")); ;
Object result = this.conversionService.convert(stream, listOfStrings);
- assertNotNull("converted object must not be null", result);
+
+ assertNotNull("Converted object must not be null", result);
assertTrue("Converted object must be a list", result instanceof List);
@SuppressWarnings("unchecked")
List<Object> content = (List<Object>) result;
@@ -120,7 +125,8 @@ public class StreamConverterTests {
List<String> stream = Arrays.asList("1", "2", "3");
TypeDescriptor streamOfInteger = new TypeDescriptor(Types.class.getField("streamOfIntegers")); ;
Object result = this.conversionService.convert(stream, streamOfInteger);
- assertNotNull("converted object must not be null", result);
+
+ assertNotNull("Converted object must not be null", result);
assertTrue("Converted object must be a stream", result instanceof Stream);
@SuppressWarnings("unchecked")
Stream<Integer> content = (Stream<Integer>) result;
@@ -139,7 +145,8 @@ public class StreamConverterTests {
});
TypeDescriptor streamOfBoolean = new TypeDescriptor(Types.class.getField("streamOfBooleans")); ;
Object result = this.conversionService.convert(stream, streamOfBoolean);
- assertNotNull("converted object must not be null", result);
+
+ assertNotNull("Converted object must not be null", result);
assertTrue("Converted object must be a stream", result instanceof Stream);
@SuppressWarnings("unchecked")
Stream<Boolean> content = (Stream<Boolean>) result;
@@ -152,7 +159,8 @@ public class StreamConverterTests {
List<String> stream = Arrays.asList("1", "2", "3");
TypeDescriptor streamOfInteger = new TypeDescriptor(Types.class.getField("rawStream")); ;
Object result = this.conversionService.convert(stream, streamOfInteger);
- assertNotNull("converted object must not be null", result);
+
+ assertNotNull("Converted object must not be null", result);
assertTrue("Converted object must be a stream", result instanceof Stream);
@SuppressWarnings("unchecked")
Stream<Object> content = (Stream<Object>) result;
@@ -175,6 +183,7 @@ public class StreamConverterTests {
new TypeDescriptor(Types.class.getField("arrayOfLongs")));
}
+
@SuppressWarnings({ "rawtypes" })
static class Types {
diff --git a/spring-core/src/test/java/org/springframework/core/env/DummyEnvironment.java b/spring-core/src/test/java/org/springframework/core/env/DummyEnvironment.java
index bcb3d225..756da6a9 100644
--- a/spring-core/src/test/java/org/springframework/core/env/DummyEnvironment.java
+++ b/spring-core/src/test/java/org/springframework/core/env/DummyEnvironment.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +44,7 @@ public class DummyEnvironment implements Environment {
}
@Override
+ @Deprecated
public <T> Class<T> getPropertyAsClass(String key, Class<T> targetType) {
return null;
}
@@ -54,8 +55,7 @@ public class DummyEnvironment implements Environment {
}
@Override
- public <T> T getRequiredProperty(String key, Class<T> targetType)
- throws IllegalStateException {
+ public <T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException {
return null;
}
@@ -65,8 +65,7 @@ public class DummyEnvironment implements Environment {
}
@Override
- public String resolveRequiredPlaceholders(String text)
- throws IllegalArgumentException {
+ public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
return null;
}
diff --git a/spring-core/src/test/java/org/springframework/core/env/PropertySourceTests.java b/spring-core/src/test/java/org/springframework/core/env/PropertySourceTests.java
index 28a51d05..fda1f06d 100644
--- a/spring-core/src/test/java/org/springframework/core/env/PropertySourceTests.java
+++ b/spring-core/src/test/java/org/springframework/core/env/PropertySourceTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author 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,13 +30,15 @@ import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
/**
- * Unit tests for {@link AbstractPropertySource} implementations.
+ * Unit tests for {@link PropertySource} implementations.
*
* @author Chris Beams
* @since 3.1
*/
public class PropertySourceTests {
- @Test @SuppressWarnings("serial")
+
+ @Test
+ @SuppressWarnings("serial")
public void equals() {
Map<String, Object> map1 = new HashMap<String, Object>() {{ put("a", "b"); }};
Map<String, Object> map2 = new HashMap<String, Object>() {{ put("c", "d"); }};
@@ -59,14 +61,15 @@ public class PropertySourceTests {
assertThat(new MapPropertySource("x", map1).equals(new PropertiesPropertySource("y", props2)), is(false));
}
- @Test @SuppressWarnings("serial")
+ @Test
+ @SuppressWarnings("serial")
public void collectionsOperations() {
Map<String, Object> map1 = new HashMap<String, Object>() {{ put("a", "b"); }};
Map<String, Object> map2 = new HashMap<String, Object>() {{ put("c", "d"); }};
PropertySource<?> ps1 = new MapPropertySource("ps1", map1);
ps1.getSource();
- List<PropertySource<?>> propertySources = new ArrayList<PropertySource<?>>();
+ List<PropertySource<?>> propertySources = new ArrayList<>();
assertThat(propertySources.add(ps1), equalTo(true));
assertThat(propertySources.contains(ps1), is(true));
assertThat(propertySources.contains(PropertySource.named("ps1")), is(true));
@@ -110,10 +113,11 @@ public class PropertySourceTests {
ps.toString(),
equalTo(String.format("%s [name='%s']",
ps.getClass().getSimpleName(),
- name,
- map.size())));
- } finally {
+ name)));
+ }
+ finally {
logger.setLevel(original);
}
}
+
}
diff --git a/spring-core/src/test/java/org/springframework/core/env/PropertySourcesPropertyResolverTests.java b/spring-core/src/test/java/org/springframework/core/env/PropertySourcesPropertyResolverTests.java
index 84f6fce0..529bcd41 100644
--- a/spring-core/src/test/java/org/springframework/core/env/PropertySourcesPropertyResolverTests.java
+++ b/spring-core/src/test/java/org/springframework/core/env/PropertySourcesPropertyResolverTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@ import org.junit.Before;
import org.junit.Test;
import org.springframework.core.convert.ConversionException;
+import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.mock.env.MockPropertySource;
import static org.hamcrest.Matchers.*;
@@ -114,9 +115,9 @@ public class PropertySourcesPropertyResolverTests {
try {
propertyResolver.getProperty("foo", TestType.class);
- fail("Expected IllegalArgumentException due to non-convertible types");
+ fail("Expected ConverterNotFoundException due to non-convertible types");
}
- catch (IllegalArgumentException ex) {
+ catch (ConverterNotFoundException ex) {
// expected
}
}
@@ -257,6 +258,7 @@ public class PropertySourcesPropertyResolverTests {
}
@Test
+ @SuppressWarnings("deprecation")
public void getPropertyAsClass() throws ClassNotFoundException, LinkageError {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new MockPropertySource().withProperty("some.class", SpecificType.class.getName()));
@@ -265,6 +267,7 @@ public class PropertySourcesPropertyResolverTests {
}
@Test
+ @SuppressWarnings("deprecation")
public void getPropertyAsClass_withInterfaceAsTarget() throws ClassNotFoundException, LinkageError {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new MockPropertySource().withProperty("some.class", SomeType.class.getName()));
@@ -272,7 +275,8 @@ public class PropertySourcesPropertyResolverTests {
assertTrue(resolver.getPropertyAsClass("some.class", SomeType.class).equals(SomeType.class));
}
- @Test(expected=ConversionException.class)
+ @Test(expected = ConversionException.class)
+ @SuppressWarnings("deprecation")
public void getPropertyAsClass_withMismatchedTypeForValue() {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new MockPropertySource().withProperty("some.class", "java.lang.String"));
@@ -280,7 +284,8 @@ public class PropertySourcesPropertyResolverTests {
resolver.getPropertyAsClass("some.class", SomeType.class);
}
- @Test(expected=ConversionException.class)
+ @Test(expected = ConversionException.class)
+ @SuppressWarnings("deprecation")
public void getPropertyAsClass_withNonExistentClassForValue() {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new MockPropertySource().withProperty("some.class", "some.bogus.Class"));
@@ -289,6 +294,7 @@ public class PropertySourcesPropertyResolverTests {
}
@Test
+ @SuppressWarnings("deprecation")
public void getPropertyAsClass_withObjectForValue() {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new MockPropertySource().withProperty("some.class", new SpecificType()));
@@ -296,7 +302,8 @@ public class PropertySourcesPropertyResolverTests {
assertTrue(resolver.getPropertyAsClass("some.class", SomeType.class).equals(SpecificType.class));
}
- @Test(expected=ConversionException.class)
+ @Test(expected = ConversionException.class)
+ @SuppressWarnings("deprecation")
public void getPropertyAsClass_withMismatchedObjectForValue() {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new MockPropertySource().withProperty("some.class", new Integer(42)));
@@ -305,6 +312,7 @@ public class PropertySourcesPropertyResolverTests {
}
@Test
+ @SuppressWarnings("deprecation")
public void getPropertyAsClass_withRealClassForValue() {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new MockPropertySource().withProperty("some.class", SpecificType.class));
@@ -312,7 +320,8 @@ public class PropertySourcesPropertyResolverTests {
assertTrue(resolver.getPropertyAsClass("some.class", SomeType.class).equals(SpecificType.class));
}
- @Test(expected=ConversionException.class)
+ @Test(expected = ConversionException.class)
+ @SuppressWarnings("deprecation")
public void getPropertyAsClass_withMismatchedRealClassForValue() {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new MockPropertySource().withProperty("some.class", Integer.class));
diff --git a/spring-core/src/test/java/org/springframework/core/env/SystemEnvironmentPropertySourceTests.java b/spring-core/src/test/java/org/springframework/core/env/SystemEnvironmentPropertySourceTests.java
index 8926161a..935862b0 100644
--- a/spring-core/src/test/java/org/springframework/core/env/SystemEnvironmentPropertySourceTests.java
+++ b/spring-core/src/test/java/org/springframework/core/env/SystemEnvironmentPropertySourceTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -93,16 +93,59 @@ public class SystemEnvironmentPropertySourceTests {
@Test
public void withUppercase() {
envMap.put("A_KEY", "a_value");
+ envMap.put("A_LONG_KEY", "a_long_value");
+ envMap.put("A_DOT.KEY", "a_dot_value");
+ envMap.put("A_HYPHEN-KEY", "a_hyphen_value");
assertThat(ps.containsProperty("A_KEY"), equalTo(true));
assertThat(ps.containsProperty("A.KEY"), equalTo(true));
+ assertThat(ps.containsProperty("A-KEY"), equalTo(true));
assertThat(ps.containsProperty("a_key"), equalTo(true));
assertThat(ps.containsProperty("a.key"), equalTo(true));
-
- assertThat(ps.getProperty("A_KEY"), equalTo((Object)"a_value"));
- assertThat(ps.getProperty("A.KEY"), equalTo((Object)"a_value"));
- assertThat(ps.getProperty("a_key"), equalTo((Object)"a_value"));
- assertThat(ps.getProperty("a.key"), equalTo((Object)"a_value"));
+ assertThat(ps.containsProperty("a-key"), equalTo(true));
+ assertThat(ps.containsProperty("A_LONG_KEY"), equalTo(true));
+ assertThat(ps.containsProperty("A.LONG.KEY"), equalTo(true));
+ assertThat(ps.containsProperty("A-LONG-KEY"), equalTo(true));
+ assertThat(ps.containsProperty("A.LONG-KEY"), equalTo(true));
+ assertThat(ps.containsProperty("A-LONG.KEY"), equalTo(true));
+ assertThat(ps.containsProperty("A_long_KEY"), equalTo(true));
+ assertThat(ps.containsProperty("A.long.KEY"), equalTo(true));
+ assertThat(ps.containsProperty("A-long-KEY"), equalTo(true));
+ assertThat(ps.containsProperty("A.long-KEY"), equalTo(true));
+ assertThat(ps.containsProperty("A-long.KEY"), equalTo(true));
+ assertThat(ps.containsProperty("A_DOT.KEY"), equalTo(true));
+ assertThat(ps.containsProperty("A-DOT.KEY"), equalTo(true));
+ assertThat(ps.containsProperty("A_dot.KEY"), equalTo(true));
+ assertThat(ps.containsProperty("A-dot.KEY"), equalTo(true));
+ assertThat(ps.containsProperty("A_HYPHEN-KEY"), equalTo(true));
+ assertThat(ps.containsProperty("A.HYPHEN-KEY"), equalTo(true));
+ assertThat(ps.containsProperty("A_hyphen-KEY"), equalTo(true));
+ assertThat(ps.containsProperty("A.hyphen-KEY"), equalTo(true));
+
+ assertThat(ps.getProperty("A_KEY"), equalTo("a_value"));
+ assertThat(ps.getProperty("A.KEY"), equalTo("a_value"));
+ assertThat(ps.getProperty("A-KEY"), equalTo("a_value"));
+ assertThat(ps.getProperty("a_key"), equalTo("a_value"));
+ assertThat(ps.getProperty("a.key"), equalTo("a_value"));
+ assertThat(ps.getProperty("a-key"), equalTo("a_value"));
+ assertThat(ps.getProperty("A_LONG_KEY"), equalTo("a_long_value"));
+ assertThat(ps.getProperty("A.LONG.KEY"), equalTo("a_long_value"));
+ assertThat(ps.getProperty("A-LONG-KEY"), equalTo("a_long_value"));
+ assertThat(ps.getProperty("A.LONG-KEY"), equalTo("a_long_value"));
+ assertThat(ps.getProperty("A-LONG.KEY"), equalTo("a_long_value"));
+ assertThat(ps.getProperty("A_long_KEY"), equalTo("a_long_value"));
+ assertThat(ps.getProperty("A.long.KEY"), equalTo("a_long_value"));
+ assertThat(ps.getProperty("A-long-KEY"), equalTo("a_long_value"));
+ assertThat(ps.getProperty("A.long-KEY"), equalTo("a_long_value"));
+ assertThat(ps.getProperty("A-long.KEY"), equalTo("a_long_value"));
+ assertThat(ps.getProperty("A_DOT.KEY"), equalTo("a_dot_value"));
+ assertThat(ps.getProperty("A-DOT.KEY"), equalTo("a_dot_value"));
+ assertThat(ps.getProperty("A_dot.KEY"), equalTo("a_dot_value"));
+ assertThat(ps.getProperty("A-dot.KEY"), equalTo("a_dot_value"));
+ assertThat(ps.getProperty("A_HYPHEN-KEY"), equalTo("a_hyphen_value"));
+ assertThat(ps.getProperty("A.HYPHEN-KEY"), equalTo("a_hyphen_value"));
+ assertThat(ps.getProperty("A_hyphen-KEY"), equalTo("a_hyphen_value"));
+ assertThat(ps.getProperty("A.hyphen-KEY"), equalTo("a_hyphen_value"));
}
@Test
diff --git a/spring-core/src/test/java/org/springframework/core/io/support/DummyPackagePrivateFactory.java b/spring-core/src/test/java/org/springframework/core/io/support/DummyPackagePrivateFactory.java
new file mode 100644
index 00000000..4c83bfab
--- /dev/null
+++ b/spring-core/src/test/java/org/springframework/core/io/support/DummyPackagePrivateFactory.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.io.support;
+
+/**
+ * Used by {@link SpringFactoriesLoaderTests}
+
+ * @author Phillip Webb
+ */
+class DummyPackagePrivateFactory {
+
+}
diff --git a/spring-core/src/test/java/org/springframework/core/io/support/ResourceRegionTests.java b/spring-core/src/test/java/org/springframework/core/io/support/ResourceRegionTests.java
new file mode 100644
index 00000000..8ee0fef2
--- /dev/null
+++ b/spring-core/src/test/java/org/springframework/core/io/support/ResourceRegionTests.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.io.support;
+
+import org.junit.Test;
+
+import org.springframework.core.io.Resource;
+
+import static org.mockito.Mockito.*;
+
+/**
+ * Unit tests for the {@link ResourceRegion} class.
+ *
+ * @author Brian Clozel
+ */
+public class ResourceRegionTests {
+
+ @Test(expected = IllegalArgumentException.class)
+ public void shouldThrowExceptionWithNullResource() {
+ new ResourceRegion(null, 0, 1);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void shouldThrowExceptionForNegativePosition() {
+ new ResourceRegion(mock(Resource.class), -1, 1);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void shouldThrowExceptionForNegativeCount() {
+ new ResourceRegion(mock(Resource.class), 0, -1);
+ }
+
+}
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
new file mode 100644
index 00000000..4de87d2c
--- /dev/null
+++ b/spring-core/src/test/java/org/springframework/core/io/support/SpringFactoriesLoaderTests.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.io.support;
+
+import java.lang.reflect.Modifier;
+import java.util.List;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Tests for {@link SpringFactoriesLoader}.
+ *
+ * @author Arjen Poutsma
+ * @author Phillip Webb
+ */
+public class SpringFactoriesLoaderTests {
+
+ @Test
+ public void loadFactoriesInCorrectOrder() {
+ List<DummyFactory> factories = SpringFactoriesLoader
+ .loadFactories(DummyFactory.class, null);
+ assertEquals(2, factories.size());
+ assertTrue(factories.get(0) instanceof MyDummyFactory1);
+ assertTrue(factories.get(1) instanceof MyDummyFactory2);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void loadInvalid() {
+ SpringFactoriesLoader.loadFactories(String.class, null);
+ }
+
+ @Test
+ public void loadPackagePrivateFactory() throws Exception {
+ 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/core/type/AnnotationMetadataTests.java b/spring-core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java
index 5e4083e0..2f377fc0 100644
--- a/spring-core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java
+++ b/spring-core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java
@@ -30,6 +30,7 @@ import java.util.Set;
import org.junit.Test;
+import org.springframework.core.annotation.AliasFor;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
@@ -291,6 +292,7 @@ public class AnnotationMetadataTests {
Set<MethodMetadata> methods = metadata.getAnnotatedMethods(DirectAnnotation.class.getName());
MethodMetadata method = methods.iterator().next();
assertEquals("direct", method.getAnnotationAttributes(DirectAnnotation.class.getName()).get("value"));
+ assertEquals("direct", method.getAnnotationAttributes(DirectAnnotation.class.getName()).get("myValue"));
List<Object> allMeta = method.getAllAnnotationAttributes(DirectAnnotation.class.getName()).get("value");
assertThat(new HashSet<Object>(allMeta), is(equalTo(new HashSet<Object>(Arrays.asList("direct", "meta")))));
allMeta = method.getAllAnnotationAttributes(DirectAnnotation.class.getName()).get("additional");
@@ -416,7 +418,11 @@ public class AnnotationMetadataTests {
@Retention(RetentionPolicy.RUNTIME)
public @interface DirectAnnotation {
- String value();
+ @AliasFor("myValue")
+ String value() default "";
+
+ @AliasFor("value")
+ String myValue() default "";
String additional() default "direct";
}
@@ -449,7 +455,7 @@ public class AnnotationMetadataTests {
}
// SPR-10914
- public static enum SubclassEnum {
+ public enum SubclassEnum {
FOO {
/* Do not delete! This subclassing is intentional. */
},
@@ -489,14 +495,14 @@ public class AnnotationMetadataTests {
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
- public static @interface TestConfiguration {
+ public @interface TestConfiguration {
String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
- public static @interface TestComponentScan {
+ public @interface TestComponentScan {
String[] value() default {};
@@ -509,7 +515,7 @@ public class AnnotationMetadataTests {
@TestComponentScan(basePackages = "bogus")
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
- public static @interface ComposedConfigurationWithAttributeOverrides {
+ public @interface ComposedConfigurationWithAttributeOverrides {
String[] basePackages() default {};
}
@@ -520,19 +526,19 @@ public class AnnotationMetadataTests {
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
- public static @interface NamedAnnotation1 {
+ public @interface NamedAnnotation1 {
String name() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
- public static @interface NamedAnnotation2 {
+ public @interface NamedAnnotation2 {
String name() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
- public static @interface NamedAnnotation3 {
+ public @interface NamedAnnotation3 {
String name() default "";
}
@@ -547,7 +553,7 @@ public class AnnotationMetadataTests {
@NamedAnnotation3(name = "name 3")
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
- public static @interface NamedComposedAnnotation {
+ public @interface NamedComposedAnnotation {
}
@NamedComposedAnnotation
diff --git a/spring-core/src/test/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitorMemberClassTests.java b/spring-core/src/test/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitorMemberClassTests.java
index cc4ca072..5553f170 100644
--- a/spring-core/src/test/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitorMemberClassTests.java
+++ b/spring-core/src/test/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitorMemberClassTests.java
@@ -35,7 +35,8 @@ public class ClassMetadataReadingVisitorMemberClassTests
MetadataReader reader =
new SimpleMetadataReaderFactory().getMetadataReader(clazz.getName());
return reader.getAnnotationMetadata();
- } catch (IOException ex) {
+ }
+ catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
diff --git a/spring-core/src/test/java/org/springframework/tests/Assume.java b/spring-core/src/test/java/org/springframework/tests/Assume.java
index e7923851..cd26d368 100644
--- a/spring-core/src/test/java/org/springframework/tests/Assume.java
+++ b/spring-core/src/test/java/org/springframework/tests/Assume.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,6 @@ import java.lang.reflect.Method;
import java.util.Set;
import org.apache.commons.logging.Log;
-
import org.junit.AssumptionViolatedException;
import org.springframework.util.ClassUtils;
@@ -33,38 +32,8 @@ import static org.junit.Assume.*;
* conditions hold {@code true}. If the assumption fails, it means the test should be
* skipped.
*
- * <p>For example, if a set of tests require at least JDK 1.7 it can use
- * {@code Assume#atLeast(JavaVersion.JAVA_17)} as shown below:
- *
- * <pre class="code">
- * public void MyTests {
- *
- * &#064;BeforeClass
- * public static void assumptions() {
- * Assume.atLeast(JavaVersion.JAVA_17);
- * }
- *
- * // ... all the test methods that require at least JDK 1.7
- * }
- * </pre>
- *
- * If only a single test requires at least JDK 1.7 it can use the
- * {@code Assume#atLeast(JavaVersion.JAVA_17)} as shown below:
- *
- * <pre class="code">
- * public void MyTests {
- *
- * &#064;Test
- * public void requiresJdk17 {
- * Assume.atLeast(JavaVersion.JAVA_17);
- * // ... perform the actual test
- * }
- * }
- * </pre>
- *
- * In addition to assumptions based on the JDK version, tests can be categorized into
- * {@link TestGroup}s. Active groups are enabled using the 'testGroups' system property,
- * usually activated from the gradle command line:
+ * Tests can be categorized into {@link TestGroup}s. Active groups are enabled using
+ * the 'testGroups' system property, usually activated from the gradle command line:
* <pre>
* gradle test -PtestGroups="performance"
* </pre>
@@ -76,7 +45,6 @@ import static org.junit.Assume.*;
* @author Phillip Webb
* @author Sam Brannen
* @since 3.2
- * @see #atLeast(JavaVersion)
* @see #group(TestGroup)
* @see #group(TestGroup, Executable)
*/
@@ -86,26 +54,13 @@ public abstract class Assume {
/**
- * Assume that a minimum {@link JavaVersion} is running.
- * @param version the minimum version for the test to run
- * @throws AssumptionViolatedException if the assumption fails
- */
- public static void atLeast(JavaVersion version) {
- if (!JavaVersion.runningVersion().isAtLeast(version)) {
- throw new AssumptionViolatedException("Requires JDK " + version + " but running "
- + JavaVersion.runningVersion());
- }
- }
-
- /**
* Assume that a particular {@link TestGroup} has been specified.
* @param group the group that must be specified
* @throws AssumptionViolatedException if the assumption fails
*/
public static void group(TestGroup group) {
if (!GROUPS.contains(group)) {
- throw new AssumptionViolatedException("Requires unspecified group " + group
- + " from " + GROUPS);
+ throw new AssumptionViolatedException("Requires unspecified group " + group + " from " + GROUPS);
}
}
@@ -154,11 +109,12 @@ public abstract class Assume {
}
}
+
/**
* @since 4.2
*/
- @FunctionalInterface
- public static interface Executable {
+ public interface Executable {
+
void execute() throws Exception;
}
diff --git a/spring-core/src/test/java/org/springframework/tests/JavaVersion.java b/spring-core/src/test/java/org/springframework/tests/JavaVersion.java
deleted file mode 100644
index ac4e78ad..00000000
--- a/spring-core/src/test/java/org/springframework/tests/JavaVersion.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright 2002-2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.tests;
-
-/**
- * Enumeration of known JDK versions.
- *
- * @author Phillip Webb
- * @see #runningVersion()
- */
-public enum JavaVersion {
-
- /**
- * Java 1.6
- */
- JAVA_16("1.6", 16),
-
- /**
- * Java 1.7
- */
- JAVA_17("1.7", 17),
-
- /**
- * Java 1.8
- */
- JAVA_18("1.8", 18),
-
- /**
- * Java 1.9
- */
- JAVA_19("1.9", 19);
-
-
- private static final JavaVersion runningVersion = findRunningVersion();
-
- private static JavaVersion findRunningVersion() {
- String version = System.getProperty("java.version");
- for (JavaVersion candidate : values()) {
- if (version.startsWith(candidate.version)) {
- return candidate;
- }
- }
- return JavaVersion.JAVA_16;
- }
-
-
- private String version;
-
- private int value;
-
-
- private JavaVersion(String version, int value) {
- this.version = version;
- this.value = value;
- }
-
-
- @Override
- public String toString() {
- return version;
- }
-
- /**
- * Determines if the specified version is the same as or greater than this version.
- * @param version the version to check
- * @return {@code true} if the specified version is at least this version
- */
- public boolean isAtLeast(JavaVersion version) {
- return (this.value >= version.value);
- }
-
- /**
- * Returns the current running JDK version. If the current version cannot be
- * determined {@link #JAVA_16} will be returned.
- * @return the JDK version
- */
- public static JavaVersion runningVersion() {
- return runningVersion;
- }
-
-}
diff --git a/spring-core/src/test/java/org/springframework/tests/TestGroup.java b/spring-core/src/test/java/org/springframework/tests/TestGroup.java
index f7b8525b..9a10ce12 100644
--- a/spring-core/src/test/java/org/springframework/tests/TestGroup.java
+++ b/spring-core/src/test/java/org/springframework/tests/TestGroup.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -58,14 +58,7 @@ public enum TestGroup {
/**
* Tests that should only be run on the continuous integration server.
*/
- CI,
-
- /**
- * Tests that require custom compilation beyond that of the standard JDK. This helps to
- * allow running tests that will otherwise fail when using JDK > 1.8 b88. See
- * <a href="https://jira.spring.io/browse/SPR-10558">SPR-10558</a>
- */
- CUSTOM_COMPILATION;
+ CI;
/**
diff --git a/spring-core/src/test/java/org/springframework/tests/TestGroupTests.java b/spring-core/src/test/java/org/springframework/tests/TestGroupTests.java
index f756bb31..291fba3a 100644
--- a/spring-core/src/test/java/org/springframework/tests/TestGroupTests.java
+++ b/spring-core/src/test/java/org/springframework/tests/TestGroupTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -38,6 +38,7 @@ public class TestGroupTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
+
@Test
public void parseNull() throws Exception {
assertThat(TestGroup.parse(null), equalTo(Collections.<TestGroup> emptySet()));
@@ -65,7 +66,7 @@ public class TestGroupTests {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Unable to find test group 'missing' when parsing " +
"testGroups value: 'performance, missing'. Available groups include: " +
- "[LONG_RUNNING,PERFORMANCE,JMXMP,CI,CUSTOM_COMPILATION]");
+ "[LONG_RUNNING,PERFORMANCE,JMXMP,CI]");
TestGroup.parse("performance, missing");
}
@@ -77,9 +78,8 @@ public class TestGroupTests {
@Test
public void parseAllExcept() throws Exception {
Set<TestGroup> expected = new HashSet<TestGroup>(EnumSet.allOf(TestGroup.class));
- expected.remove(TestGroup.CUSTOM_COMPILATION);
expected.remove(TestGroup.PERFORMANCE);
- assertThat(TestGroup.parse("all-custom_compilation,performance"), equalTo(expected));
+ assertThat(TestGroup.parse("all-performance"), equalTo(expected));
}
}
diff --git a/spring-core/src/test/java/org/springframework/util/AntPathMatcherTests.java b/spring-core/src/test/java/org/springframework/util/AntPathMatcherTests.java
index 29a2486a..d810c330 100644
--- a/spring-core/src/test/java/org/springframework/util/AntPathMatcherTests.java
+++ b/spring-core/src/test/java/org/springframework/util/AntPathMatcherTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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 AntPathMatcherTests {
// test exact matching
assertTrue(pathMatcher.match("test", "test"));
assertTrue(pathMatcher.match("/test", "/test"));
+ assertTrue(pathMatcher.match("http://example.org", "http://example.org")); // SPR-14141
assertFalse(pathMatcher.match("/test.jpg", "test.jpg"));
assertFalse(pathMatcher.match("test", "/test"));
assertFalse(pathMatcher.match("/test", "test"));
@@ -128,11 +129,21 @@ public class AntPathMatcherTests {
assertFalse(pathMatcher.match("/x/x/**/bla", "/x/x/x/"));
+ assertTrue(pathMatcher.match("/foo/bar/**", "/foo/bar")) ;
+
assertTrue(pathMatcher.match("", ""));
assertTrue(pathMatcher.match("/{bla}.*", "/testing.html"));
}
+ // SPR-14247
+ @Test
+ public void matchWithTrimTokensEnabled() throws Exception {
+ pathMatcher.setTrimTokens(true);
+
+ assertTrue(pathMatcher.match("/foo/bar", "/foo /bar"));
+ }
+
@Test
public void withMatchStart() {
// test exact matching
@@ -418,8 +429,8 @@ public class AntPathMatcherTests {
assertEquals("/*.html", pathMatcher.combine("/**", "/*.html"));
assertEquals("/*.html", pathMatcher.combine("/*", "/*.html"));
assertEquals("/*.html", pathMatcher.combine("/*.*", "/*.html"));
- assertEquals("/{foo}/bar", pathMatcher.combine("/{foo}", "/bar")); // SPR-8858
- assertEquals("/user/user", pathMatcher.combine("/user", "/user")); // SPR-7970
+ assertEquals("/{foo}/bar", pathMatcher.combine("/{foo}", "/bar")); // SPR-8858
+ assertEquals("/user/user", pathMatcher.combine("/user", "/user")); // SPR-7970
assertEquals("/{foo:.*[^0-9].*}/edit/", pathMatcher.combine("/{foo:.*[^0-9].*}", "/edit/")); // SPR-10062
assertEquals("/1.0/foo/test", pathMatcher.combine("/1.0", "/foo/test")); // SPR-10554
assertEquals("/hotel", pathMatcher.combine("/", "/hotel")); // SPR-12975
@@ -454,8 +465,8 @@ public class AntPathMatcherTests {
// SPR-10550
assertEquals(-1, comparator.compare("/hotels/{hotel}/bookings/{booking}/cutomers/{customer}", "/**"));
- assertEquals(1, comparator.compare("/**","/hotels/{hotel}/bookings/{booking}/cutomers/{customer}"));
- assertEquals(0, comparator.compare("/**","/**"));
+ assertEquals(1, comparator.compare("/**", "/hotels/{hotel}/bookings/{booking}/cutomers/{customer}"));
+ assertEquals(0, comparator.compare("/**", "/**"));
assertEquals(-1, comparator.compare("/hotels/{hotel}", "/hotels/*"));
assertEquals(1, comparator.compare("/hotels/*", "/hotels/{hotel}"));
@@ -618,13 +629,45 @@ public class AntPathMatcherTests {
assertTrue(pathMatcher.stringMatcherCache.size() > 20);
for (int i = 0; i < 65536; i++) {
- pathMatcher.match("test" + i, "test");
+ pathMatcher.match("test" + i, "test" + i);
}
// Cache keeps being alive due to the explicit cache setting
assertTrue(pathMatcher.stringMatcherCache.size() > 65536);
}
@Test
+ public void preventCreatingStringMatchersIfPathDoesNotStartsWithPatternPrefix() {
+ pathMatcher.setCachePatterns(true);
+ assertEquals(0, pathMatcher.stringMatcherCache.size());
+
+ pathMatcher.match("test?", "test");
+ assertEquals(1, pathMatcher.stringMatcherCache.size());
+
+ pathMatcher.match("test?", "best");
+ pathMatcher.match("test/*", "view/test.jpg");
+ pathMatcher.match("test/**/test.jpg", "view/test.jpg");
+ pathMatcher.match("test/{name}.jpg", "view/test.jpg");
+ assertEquals(1, pathMatcher.stringMatcherCache.size());
+ }
+
+ @Test
+ public void creatingStringMatchersIfPatternPrefixCannotDetermineIfPathMatch() {
+ pathMatcher.setCachePatterns(true);
+ assertEquals(0, pathMatcher.stringMatcherCache.size());
+
+ pathMatcher.match("test", "testian");
+ pathMatcher.match("test?", "testFf");
+ pathMatcher.match("test/*", "test/dir/name.jpg");
+ pathMatcher.match("test/{name}.jpg", "test/lorem.jpg");
+ pathMatcher.match("bla/**/test.jpg", "bla/test.jpg");
+ pathMatcher.match("**/{name}.jpg", "test/lorem.jpg");
+ pathMatcher.match("/**/{name}.jpg", "/test/lorem.jpg");
+ pathMatcher.match("/*/dir/{name}.jpg", "/*/dir/lorem.jpg");
+
+ assertEquals(7, pathMatcher.stringMatcherCache.size());
+ }
+
+ @Test
public void cachePatternsSetToFalse() {
pathMatcher.setCachePatterns(false);
match();
diff --git a/spring-core/src/test/java/org/springframework/util/AutoPopulatingListTests.java b/spring-core/src/test/java/org/springframework/util/AutoPopulatingListTests.java
index ba0980b6..21ce9562 100644
--- a/spring-core/src/test/java/org/springframework/util/AutoPopulatingListTests.java
+++ b/spring-core/src/test/java/org/springframework/util/AutoPopulatingListTests.java
@@ -74,9 +74,9 @@ public class AutoPopulatingListTests {
private void doTestWithElementFactory(AutoPopulatingList<Object> list) {
doTestWithClass(list);
- for(int x = 0; x < list.size(); x++) {
+ for (int x = 0; x < list.size(); x++) {
Object element = list.get(x);
- if(element instanceof TestObject) {
+ if (element instanceof TestObject) {
assertEquals(x, ((TestObject) element).getAge());
}
}
diff --git a/spring-core/src/test/java/org/springframework/util/DigestUtilsTests.java b/spring-core/src/test/java/org/springframework/util/DigestUtilsTests.java
index 6842bfee..5db3ea51 100644
--- a/spring-core/src/test/java/org/springframework/util/DigestUtilsTests.java
+++ b/spring-core/src/test/java/org/springframework/util/DigestUtilsTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
package org.springframework.util;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
import java.io.UnsupportedEncodingException;
import org.junit.Before;
@@ -25,6 +27,7 @@ import static org.junit.Assert.*;
/**
* @author Arjen Poutsma
+ * @author Juergen Hoeller
*/
public class DigestUtilsTests {
@@ -38,24 +41,39 @@ public class DigestUtilsTests {
@Test
- public void md5() {
- byte[] result = DigestUtils.md5Digest(bytes);
+ public void md5() throws IOException {
byte[] expected = new byte[]
{-0x4f, 0xa, -0x73, -0x4f, 0x64, -0x20, 0x75, 0x41, 0x5, -0x49, -0x57, -0x65, -0x19, 0x2e, 0x3f, -0x1b};
+
+ byte[] result = DigestUtils.md5Digest(bytes);
+ assertArrayEquals("Invalid hash", expected, result);
+
+ result = DigestUtils.md5Digest(new ByteArrayInputStream(bytes));
assertArrayEquals("Invalid hash", expected, result);
}
@Test
- public void md5Hex() throws UnsupportedEncodingException {
+ public void md5Hex() throws IOException {
+ String expected = "b10a8db164e0754105b7a99be72e3fe5";
+
String hash = DigestUtils.md5DigestAsHex(bytes);
- assertEquals("Invalid hash", "b10a8db164e0754105b7a99be72e3fe5", hash);
+ assertEquals("Invalid hash", expected, hash);
+
+ hash = DigestUtils.md5DigestAsHex(new ByteArrayInputStream(bytes));
+ assertEquals("Invalid hash", expected, hash);
}
@Test
- public void md5StringBuilder() throws UnsupportedEncodingException {
+ public void md5StringBuilder() throws IOException {
+ String expected = "b10a8db164e0754105b7a99be72e3fe5";
+
StringBuilder builder = new StringBuilder();
DigestUtils.appendMd5DigestAsHex(bytes, builder);
- assertEquals("Invalid hash", "b10a8db164e0754105b7a99be72e3fe5", builder.toString());
+ assertEquals("Invalid hash", expected, builder.toString());
+
+ builder = new StringBuilder();
+ DigestUtils.appendMd5DigestAsHex(new ByteArrayInputStream(bytes), builder);
+ assertEquals("Invalid hash", expected, builder.toString());
}
}
diff --git a/spring-core/src/test/java/org/springframework/util/LinkedCaseInsensitiveMapTests.java b/spring-core/src/test/java/org/springframework/util/LinkedCaseInsensitiveMapTests.java
index 4f4492ef..16e3e29c 100644
--- a/spring-core/src/test/java/org/springframework/util/LinkedCaseInsensitiveMapTests.java
+++ b/spring-core/src/test/java/org/springframework/util/LinkedCaseInsensitiveMapTests.java
@@ -74,4 +74,26 @@ public class LinkedCaseInsensitiveMapTests {
assertEquals("N", map.getOrDefault(new Object(), "N"));
}
+ @Test
+ @SuppressWarnings("unchecked")
+ public void mapClone() {
+ map.put("key", "value1");
+ LinkedCaseInsensitiveMap<String> copy = (LinkedCaseInsensitiveMap<String>) map.clone();
+ assertEquals("value1", map.get("key"));
+ assertEquals("value1", map.get("KEY"));
+ assertEquals("value1", map.get("Key"));
+ assertEquals("value1", copy.get("key"));
+ assertEquals("value1", copy.get("KEY"));
+ assertEquals("value1", copy.get("Key"));
+ copy.put("Key", "value2");
+ assertEquals(1, map.size());
+ assertEquals(1, copy.size());
+ assertEquals("value1", map.get("key"));
+ assertEquals("value1", map.get("KEY"));
+ assertEquals("value1", map.get("Key"));
+ assertEquals("value2", copy.get("key"));
+ assertEquals("value2", copy.get("KEY"));
+ assertEquals("value2", copy.get("Key"));
+ }
+
}
diff --git a/spring-core/src/test/java/org/springframework/util/MimeTypeTests.java b/spring-core/src/test/java/org/springframework/util/MimeTypeTests.java
index b9d3480f..99c5a826 100644
--- a/spring-core/src/test/java/org/springframework/util/MimeTypeTests.java
+++ b/spring-core/src/test/java/org/springframework/util/MimeTypeTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -75,7 +75,7 @@ public class MimeTypeTests {
MimeType mimeType = MimeType.valueOf(s);
assertEquals("Invalid type", "text", mimeType.getType());
assertEquals("Invalid subtype", "html", mimeType.getSubtype());
- assertEquals("Invalid charset", Charset.forName("ISO-8859-1"), mimeType.getCharSet());
+ assertEquals("Invalid charset", Charset.forName("ISO-8859-1"), mimeType.getCharset());
}
@Test
@@ -84,7 +84,7 @@ public class MimeTypeTests {
MimeType mimeType = MimeType.valueOf(s);
assertEquals("Invalid type", "application", mimeType.getType());
assertEquals("Invalid subtype", "xml", mimeType.getSubtype());
- assertEquals("Invalid charset", Charset.forName("UTF-8"), mimeType.getCharSet());
+ assertEquals("Invalid charset", Charset.forName("UTF-8"), mimeType.getCharset());
}
@Test
@@ -190,6 +190,11 @@ public class MimeTypeTests {
}
@Test(expected = InvalidMimeTypeException.class)
+ public void parseMimeTypeMissingTypeAndSubtype() throws Exception {
+ MimeTypeUtils.parseMimeType(" ;a=b");
+ }
+
+ @Test(expected = InvalidMimeTypeException.class)
public void parseMimeTypeEmptyParameterAttribute() {
MimeTypeUtils.parseMimeType("audio/*;=value");
}
@@ -263,13 +268,13 @@ public class MimeTypeTests {
assertTrue("Invalid comparison result", audioBasicLevel.compareTo(audio) > 0);
- List<MimeType> expected = new ArrayList<MimeType>();
+ List<MimeType> expected = new ArrayList<>();
expected.add(audio);
expected.add(audioBasic);
expected.add(audioBasicLevel);
expected.add(audioWave);
- List<MimeType> result = new ArrayList<MimeType>(expected);
+ List<MimeType> result = new ArrayList<>(expected);
Random rnd = new Random();
// shuffle & sort 10 times
for (int i = 0; i < 10; i++) {
diff --git a/spring-core/src/test/java/org/springframework/util/NumberUtilsTests.java b/spring-core/src/test/java/org/springframework/util/NumberUtilsTests.java
index d6fbc9b0..2584cf42 100644
--- a/spring-core/src/test/java/org/springframework/util/NumberUtilsTests.java
+++ b/spring-core/src/test/java/org/springframework/util/NumberUtilsTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -328,8 +328,6 @@ public class NumberUtilsTests {
assertEquals(Integer.valueOf(Integer.MIN_VALUE), NumberUtils.convertNumberToTargetClass(BigInteger.valueOf(Integer.MAX_VALUE + 1), Integer.class));
assertEquals(Integer.valueOf(Integer.MIN_VALUE), NumberUtils.convertNumberToTargetClass(BigInteger.valueOf(Integer.MIN_VALUE), Integer.class));
assertEquals(Integer.valueOf(Integer.MAX_VALUE), NumberUtils.convertNumberToTargetClass(BigInteger.valueOf(Integer.MIN_VALUE - 1), Integer.class));
- assertToNumberOverflow(BigInteger.valueOf(Integer.MAX_VALUE).add(BigInteger.ONE), Integer.class);
- assertToNumberOverflow(BigInteger.valueOf(Integer.MIN_VALUE).subtract(BigInteger.ONE), Integer.class);
assertEquals(Integer.valueOf(Integer.valueOf(-1)), NumberUtils.convertNumberToTargetClass(Long.valueOf(-1), Integer.class));
assertEquals(Integer.valueOf(Integer.valueOf(0)), NumberUtils.convertNumberToTargetClass(Long.valueOf(0), Integer.class));
@@ -338,8 +336,6 @@ public class NumberUtilsTests {
assertEquals(Integer.valueOf(Integer.MIN_VALUE), NumberUtils.convertNumberToTargetClass(Long.valueOf(Integer.MAX_VALUE + 1), Integer.class));
assertEquals(Integer.valueOf(Integer.MIN_VALUE), NumberUtils.convertNumberToTargetClass(Long.valueOf(Integer.MIN_VALUE), Integer.class));
assertEquals(Integer.valueOf(Integer.MAX_VALUE), NumberUtils.convertNumberToTargetClass(Long.valueOf(Integer.MIN_VALUE - 1), Integer.class));
- assertToNumberOverflow(Long.valueOf(Long.MAX_VALUE + 1), Integer.class);
- assertToNumberOverflow(Long.valueOf(Long.MIN_VALUE - 1), Integer.class);
assertEquals(Integer.valueOf(Integer.valueOf(-1)), NumberUtils.convertNumberToTargetClass(Integer.valueOf(-1), Integer.class));
assertEquals(Integer.valueOf(Integer.valueOf(0)), NumberUtils.convertNumberToTargetClass(Integer.valueOf(0), Integer.class));
@@ -364,6 +360,12 @@ public class NumberUtilsTests {
assertEquals(Integer.valueOf(Byte.MIN_VALUE), NumberUtils.convertNumberToTargetClass(Byte.valueOf((byte) (Byte.MAX_VALUE + 1)), Integer.class));
assertEquals(Integer.valueOf(Byte.MIN_VALUE), NumberUtils.convertNumberToTargetClass(Byte.valueOf(Byte.MIN_VALUE), Integer.class));
assertEquals(Integer.valueOf(Byte.MAX_VALUE), NumberUtils.convertNumberToTargetClass(Byte.valueOf((byte) (Byte.MIN_VALUE - 1)), Integer.class));
+
+ assertToNumberOverflow(Long.valueOf(Long.MAX_VALUE + 1), Integer.class);
+ assertToNumberOverflow(Long.valueOf(Long.MIN_VALUE - 1), Integer.class);
+ assertToNumberOverflow(BigInteger.valueOf(Integer.MAX_VALUE).add(BigInteger.ONE), Integer.class);
+ assertToNumberOverflow(BigInteger.valueOf(Integer.MIN_VALUE).subtract(BigInteger.ONE), Integer.class);
+ assertToNumberOverflow(new BigDecimal("18446744073709551611"), Integer.class);
}
@Test
@@ -376,9 +378,6 @@ public class NumberUtilsTests {
assertEquals(Long.valueOf(Long.MIN_VALUE), NumberUtils.convertNumberToTargetClass(BigInteger.valueOf(Long.MIN_VALUE), Long.class));
assertEquals(Long.valueOf(Long.MAX_VALUE), NumberUtils.convertNumberToTargetClass(BigInteger.valueOf(Long.MIN_VALUE - 1), Long.class));
- assertToNumberOverflow(BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE), Long.class);
- assertToNumberOverflow(BigInteger.valueOf(Long.MIN_VALUE).subtract(BigInteger.ONE), Long.class);
-
assertEquals(Long.valueOf(Long.valueOf(-1)), NumberUtils.convertNumberToTargetClass(Long.valueOf(-1), Long.class));
assertEquals(Long.valueOf(Long.valueOf(0)), NumberUtils.convertNumberToTargetClass(Long.valueOf(0), Long.class));
assertEquals(Long.valueOf(Long.valueOf(1)), NumberUtils.convertNumberToTargetClass(Long.valueOf(1), Long.class));
@@ -410,6 +409,10 @@ public class NumberUtilsTests {
assertEquals(Long.valueOf(Byte.MIN_VALUE), NumberUtils.convertNumberToTargetClass(Byte.valueOf((byte) (Byte.MAX_VALUE + 1)), Long.class));
assertEquals(Long.valueOf(Byte.MIN_VALUE), NumberUtils.convertNumberToTargetClass(Byte.valueOf(Byte.MIN_VALUE), Long.class));
assertEquals(Long.valueOf(Byte.MAX_VALUE), NumberUtils.convertNumberToTargetClass(Byte.valueOf((byte) (Byte.MIN_VALUE - 1)), Long.class));
+
+ assertToNumberOverflow(BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE), Long.class);
+ assertToNumberOverflow(BigInteger.valueOf(Long.MIN_VALUE).subtract(BigInteger.ONE), Long.class);
+ assertToNumberOverflow(new BigDecimal("18446744073709551611"), Long.class);
}
diff --git a/spring-core/src/test/java/org/springframework/util/ResizableByteArrayOutputStreamTests.java b/spring-core/src/test/java/org/springframework/util/ResizableByteArrayOutputStreamTests.java
index ec41e815..709b3fb0 100644
--- a/spring-core/src/test/java/org/springframework/util/ResizableByteArrayOutputStreamTests.java
+++ b/spring-core/src/test/java/org/springframework/util/ResizableByteArrayOutputStreamTests.java
@@ -54,7 +54,7 @@ public class ResizableByteArrayOutputStreamTests {
@Test
public void autoGrow() {
assertEquals(INITIAL_CAPACITY, this.baos.capacity());
- for(int i = 0; i < 129; i++) {
+ for (int i = 0; i < 129; i++) {
this.baos.write(0);
}
assertEquals(256, this.baos.capacity());
diff --git a/spring-core/src/test/java/org/springframework/util/StreamUtilsTests.java b/spring-core/src/test/java/org/springframework/util/StreamUtilsTests.java
index 93295cad..48f173f2 100644
--- a/spring-core/src/test/java/org/springframework/util/StreamUtilsTests.java
+++ b/spring-core/src/test/java/org/springframework/util/StreamUtilsTests.java
@@ -21,6 +21,7 @@ import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
+import java.util.Arrays;
import java.util.Random;
import java.util.UUID;
@@ -94,6 +95,15 @@ public class StreamUtilsTests {
}
@Test
+ public void copyRange() throws Exception {
+ ByteArrayOutputStream out = spy(new ByteArrayOutputStream());
+ StreamUtils.copyRange(new ByteArrayInputStream(bytes), out, 0, 100);
+ byte[] range = Arrays.copyOfRange(bytes, 0, 101);
+ assertThat(out.toByteArray(), equalTo(range));
+ verify(out, never()).close();
+ }
+
+ @Test
public void nonClosingInputStream() throws Exception {
InputStream source = mock(InputStream.class);
InputStream nonClosing = StreamUtils.nonClosing(source);
diff --git a/spring-core/src/test/java/org/springframework/util/xml/AbstractStaxHandlerTestCase.java b/spring-core/src/test/java/org/springframework/util/xml/AbstractStaxHandlerTestCase.java
index 8dd82430..e23c92cc 100644
--- a/spring-core/src/test/java/org/springframework/util/xml/AbstractStaxHandlerTestCase.java
+++ b/spring-core/src/test/java/org/springframework/util/xml/AbstractStaxHandlerTestCase.java
@@ -82,7 +82,7 @@ public abstract class AbstractStaxHandlerTestCase {
private static boolean wwwSpringframeworkOrgIsAccessible() {
try {
- new Socket("www.springframework.org", 80);
+ new Socket("www.springframework.org", 80).close();
}
catch (Exception e) {
return false;
diff --git a/spring-core/src/test/java/org/springframework/util/xml/AbstractStaxXMLReaderTestCase.java b/spring-core/src/test/java/org/springframework/util/xml/AbstractStaxXMLReaderTestCase.java
index 9dec693d..41aa5f25 100644
--- a/spring-core/src/test/java/org/springframework/util/xml/AbstractStaxXMLReaderTestCase.java
+++ b/spring-core/src/test/java/org/springframework/util/xml/AbstractStaxXMLReaderTestCase.java
@@ -194,8 +194,8 @@ public abstract class AbstractStaxXMLReaderTestCase {
private static class SkipLocatorArgumentsAdapter implements InvocationArgumentsAdapter {
@Override
public Object[] adaptArguments(Object[] arguments) {
- for(int i=0; i<arguments.length; i++) {
- if(arguments[i] instanceof Locator) {
+ for (int i = 0; i < arguments.length; i++) {
+ if (arguments[i] instanceof Locator) {
arguments[i] = null;
}
}
@@ -206,7 +206,7 @@ public abstract class AbstractStaxXMLReaderTestCase {
private static class CharArrayToStringAdapter implements InvocationArgumentsAdapter {
@Override
public Object[] adaptArguments(Object[] arguments) {
- if(arguments.length == 3 && arguments[0] instanceof char[]
+ if (arguments.length == 3 && arguments[0] instanceof char[]
&& arguments[1] instanceof Integer && arguments[2] instanceof Integer) {
return new Object[] {new String((char[]) arguments[0], (Integer) arguments[1], (Integer) arguments[2])};
}
@@ -218,7 +218,7 @@ public abstract class AbstractStaxXMLReaderTestCase {
@Override
public Object[] adaptArguments(Object[] arguments) {
for (int i = 0; i < arguments.length; i++) {
- if(arguments[i] instanceof Attributes) {
+ if (arguments[i] instanceof Attributes) {
arguments[i] = new PartialAttributes((Attributes) arguments[i]);
}
};
diff --git a/spring-core/src/test/resources/META-INF/spring.factories b/spring-core/src/test/resources/META-INF/spring.factories
new file mode 100644
index 00000000..ab64ffe1
--- /dev/null
+++ b/spring-core/src/test/resources/META-INF/spring.factories
@@ -0,0 +1,9 @@
+org.springframework.core.io.support.DummyFactory=\
+org.springframework.core.io.support.MyDummyFactory2,\
+org.springframework.core.io.support.MyDummyFactory1
+
+java.lang.String=\
+org.springframework.core.io.support.MyDummyFactory1
+
+org.springframework.core.io.support.DummyPackagePrivateFactory=\
+org.springframework.core.io.support.DummyPackagePrivateFactory
diff --git a/spring-core/src/test/resources/org/springframework/core/io/support/springFactoriesLoaderTests.properties b/spring-core/src/test/resources/org/springframework/core/io/support/springFactoriesLoaderTests.properties
deleted file mode 100644
index d3e700fe..00000000
--- a/spring-core/src/test/resources/org/springframework/core/io/support/springFactoriesLoaderTests.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-org.springframework.core.io.support.DummyFactory=org.springframework.core.io.support.MyDummyFactory2,org.springframework.core.io.support.MyDummyFactory1
-java.lang.String=org.springframework.core.io.support.MyDummyFactory1 \ No newline at end of file
diff --git a/spring-expression/src/main/java/org/springframework/expression/BeanResolver.java b/spring-expression/src/main/java/org/springframework/expression/BeanResolver.java
index c95501ba..1f6726f1 100644
--- a/spring-expression/src/main/java/org/springframework/expression/BeanResolver.java
+++ b/spring-expression/src/main/java/org/springframework/expression/BeanResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,9 @@ package org.springframework.expression;
/**
* A bean resolver can be registered with the evaluation context
- * and will kick in for {@code @myBeanName} still expressions.
+ * and will kick in for {@code @myBeanName} and {@code &myBeanName} expressions.
+ * The <tt>&</tt> variant syntax allows access to the factory bean where
+ * relevant.
*
* @author Andy Clement
* @since 3.0.3
@@ -26,7 +28,8 @@ package org.springframework.expression;
public interface BeanResolver {
/**
- * Look up the named bean and return it.
+ * Look up the named bean and return it. If attempting to access a factory
+ * bean the name will have a <tt>&</tt> prefix.
* @param context the current evaluation context
* @param beanName the name of the bean to lookup
* @return an object representing the bean
diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/CodeFlow.java b/spring-expression/src/main/java/org/springframework/expression/spel/CodeFlow.java
index 2d20e195..decb2f1c 100644
--- a/spring-expression/src/main/java/org/springframework/expression/spel/CodeFlow.java
+++ b/spring-expression/src/main/java/org/springframework/expression/spel/CodeFlow.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -148,6 +148,72 @@ public class CodeFlow implements Opcodes {
}
/**
+ * Called after the main expression evaluation method has been generated, this
+ * method will callback any registered FieldAdders or ClinitAdders to add any
+ * extra information to the class representing the compiled expression.
+ */
+ public void finish() {
+ if (this.fieldAdders != null) {
+ for (FieldAdder fieldAdder : this.fieldAdders) {
+ fieldAdder.generateField(cw,this);
+ }
+ }
+ if (this.clinitAdders != null) {
+ MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "<clinit>", "()V", null, null);
+ mv.visitCode();
+ this.nextFreeVariableId = 0; // To 0 because there is no 'this' in a clinit
+ for (ClinitAdder clinitAdder : this.clinitAdders) {
+ clinitAdder.generateCode(mv, this);
+ }
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0,0); // not supplied due to COMPUTE_MAXS
+ mv.visitEnd();
+ }
+ }
+
+ /**
+ * Register a FieldAdder which will add a new field to the generated
+ * class to support the code produced by an ast nodes primary
+ * generateCode() method.
+ */
+ public void registerNewField(FieldAdder fieldAdder) {
+ if (this.fieldAdders == null) {
+ this.fieldAdders = new ArrayList<FieldAdder>();
+ }
+ this.fieldAdders.add(fieldAdder);
+ }
+
+ /**
+ * Register a ClinitAdder which will add code to the static
+ * initializer in the generated class to support the code
+ * produced by an ast nodes primary generateCode() method.
+ */
+ public void registerNewClinit(ClinitAdder clinitAdder) {
+ if (this.clinitAdders == null) {
+ this.clinitAdders = new ArrayList<ClinitAdder>();
+ }
+ this.clinitAdders.add(clinitAdder);
+ }
+
+ public int nextFieldId() {
+ return this.nextFieldId++;
+ }
+
+ public int nextFreeVariableId() {
+ return this.nextFreeVariableId++;
+ }
+
+ public String getClassName() {
+ return this.clazzName;
+ }
+
+ @Deprecated
+ public String getClassname() {
+ return this.clazzName;
+ }
+
+
+ /**
* Insert any necessary cast and value call to convert from a boxed type to a
* primitive value
* @param mv the method visitor into which instructions should be inserted
@@ -779,74 +845,6 @@ public class CodeFlow implements Opcodes {
}
/**
- * Called after the main expression evaluation method has been generated, this
- * method will callback any registered FieldAdders or ClinitAdders to add any
- * extra information to the class representing the compiled expression.
- */
- public void finish() {
- if (fieldAdders != null) {
- for (FieldAdder fieldAdder: fieldAdders) {
- fieldAdder.generateField(cw,this);
- }
- }
- if (clinitAdders != null) {
- MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "<clinit>", "()V", null, null);
- mv.visitCode();
- nextFreeVariableId = 0; // To 0 because there is no 'this' in a clinit
- for (ClinitAdder clinitAdder: clinitAdders) {
- clinitAdder.generateCode(mv, this);
- }
- mv.visitInsn(RETURN);
- mv.visitMaxs(0,0); // not supplied due to COMPUTE_MAXS
- mv.visitEnd();
- }
- }
-
- /**
- * Register a FieldAdder which will add a new field to the generated
- * class to support the code produced by an ast nodes primary
- * generateCode() method.
- */
- public void registerNewField(FieldAdder fieldAdder) {
- if (fieldAdders == null) {
- fieldAdders = new ArrayList<FieldAdder>();
- }
- fieldAdders.add(fieldAdder);
- }
-
- /**
- * Register a ClinitAdder which will add code to the static
- * initializer in the generated class to support the code
- * produced by an ast nodes primary generateCode() method.
- */
- public void registerNewClinit(ClinitAdder clinitAdder) {
- if (clinitAdders == null) {
- clinitAdders = new ArrayList<ClinitAdder>();
- }
- clinitAdders.add(clinitAdder);
- }
-
- public int nextFieldId() {
- return nextFieldId++;
- }
-
- public int nextFreeVariableId() {
- return nextFreeVariableId++;
- }
-
- public String getClassname() {
- return clazzName;
- }
-
- public interface FieldAdder {
- public void generateField(ClassWriter cw, CodeFlow codeflow);
- }
-
- public interface ClinitAdder {
- public void generateCode(MethodVisitor mv, CodeFlow codeflow);
- }
-
- /**
* Create the optimal instruction for loading a number on the stack.
* @param mv where to insert the bytecode
* @param value the value to be loaded
@@ -977,4 +975,15 @@ public class CodeFlow implements Opcodes {
}
+ public interface FieldAdder {
+
+ void generateField(ClassWriter cw, CodeFlow codeflow);
+ }
+
+
+ public interface ClinitAdder {
+
+ void generateCode(MethodVisitor mv, CodeFlow codeflow);
+ }
+
}
diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java b/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java
index 1431554a..40c7054c 100644
--- a/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java
+++ b/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -214,7 +214,7 @@ public enum SpelMessage {
"A problem occurred when trying to resolve bean ''{0}'':''{1}''"),
INVALID_BEAN_REFERENCE(Kind.ERROR, 1059,
- "@ can only be followed by an identifier or a quoted name"),
+ "@ or & can only be followed by an identifier or a quoted name"),
TYPE_NAME_EXPECTED_FOR_ARRAY_CONSTRUCTION(Kind.ERROR, 1060,
"Expected the type of the new array to be specified as a String but found ''{0}''"),
diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/BeanReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/BeanReference.java
index 5f0c8404..67021482 100644
--- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/BeanReference.java
+++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/BeanReference.java
@@ -25,12 +25,15 @@ import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
/**
- * Represents a bean reference to a type, for example "@foo" or "@'foo.bar'"
- *
+ * Represents a bean reference to a type, for example <tt>@foo</tt> or <tt>@'foo.bar'</tt>.
+ * For a FactoryBean the syntax <tt>&foo</tt> can be used to access the factory itself.
+ *
* @author Andy Clement
*/
public class BeanReference extends SpelNodeImpl {
+ private final static String FACTORY_BEAN_PREFIX = "&";
+
private final String beanName;
@@ -59,7 +62,10 @@ public class BeanReference extends SpelNodeImpl {
@Override
public String toStringAST() {
- StringBuilder sb = new StringBuilder("@");
+ StringBuilder sb = new StringBuilder();
+ if (!this.beanName.startsWith(FACTORY_BEAN_PREFIX)) {
+ sb.append("@");
+ }
if (!this.beanName.contains(".")) {
sb.append(this.beanName);
}
diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java
index ae15a6f1..32a837ea 100644
--- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java
+++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -694,7 +694,7 @@ public class Indexer extends SpelNodeImpl {
newElements--;
}
}
- catch (Exception ex) {
+ catch (Throwable ex) {
throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.UNABLE_TO_GROW_COLLECTION);
}
}
diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/InlineList.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/InlineList.java
index b4134b63..1a435b86 100644
--- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/InlineList.java
+++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/InlineList.java
@@ -132,8 +132,8 @@ public class InlineList extends SpelNodeImpl {
@Override
public void generateCode(MethodVisitor mv, CodeFlow codeflow) {
- final String constantFieldName = "inlineList$"+codeflow.nextFieldId();
- final String clazzname = codeflow.getClassname();
+ final String constantFieldName = "inlineList$" + codeflow.nextFieldId();
+ final String className = codeflow.getClassName();
codeflow.registerNewField(new CodeFlow.FieldAdder() {
public void generateField(ClassWriter cw, CodeFlow codeflow) {
@@ -143,11 +143,11 @@ public class InlineList extends SpelNodeImpl {
codeflow.registerNewClinit(new CodeFlow.ClinitAdder() {
public void generateCode(MethodVisitor mv, CodeFlow codeflow) {
- generateClinitCode(clazzname,constantFieldName, mv,codeflow,false);
+ generateClinitCode(className, constantFieldName, mv, codeflow, false);
}
});
- mv.visitFieldInsn(GETSTATIC, clazzname, constantFieldName, "Ljava/util/List;");
+ mv.visitFieldInsn(GETSTATIC, className, constantFieldName, "Ljava/util/List;");
codeflow.pushDescriptor("Ljava/util/List");
}
@@ -158,8 +158,8 @@ public class InlineList extends SpelNodeImpl {
if (!nested) {
mv.visitFieldInsn(PUTSTATIC, clazzname, constantFieldName, "Ljava/util/List;");
}
- int childcount = getChildCount();
- for (int c=0; c < childcount; c++) {
+ int childCount = getChildCount();
+ for (int c = 0; c < childCount; c++) {
if (!nested) {
mv.visitFieldInsn(GETSTATIC, clazzname, constantFieldName, "Ljava/util/List;");
}
diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorInstanceof.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorInstanceof.java
index 5a3063b6..6c880bdd 100644
--- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorInstanceof.java
+++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorInstanceof.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author 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,8 +53,9 @@ public class OperatorInstanceof extends Operator {
*/
@Override
public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException {
+ SpelNodeImpl rightOperand = getRightOperand();
TypedValue left = getLeftOperand().getValueInternal(state);
- TypedValue right = getRightOperand().getValueInternal(state);
+ TypedValue right = rightOperand.getValueInternal(state);
Object leftValue = left.getValue();
Object rightValue = right.getValue();
BooleanTypedValue result = null;
@@ -71,7 +72,11 @@ public class OperatorInstanceof extends Operator {
result = BooleanTypedValue.forValue(rightClass.isAssignableFrom(leftValue.getClass()));
}
this.type = rightClass;
- this.exitTypeDescriptor = "Z";
+ if (rightOperand instanceof TypeReference) {
+ // Can only generate bytecode where the right operand is a direct type reference,
+ // not if it is indirect (for example when right operand is a variable reference)
+ this.exitTypeDescriptor = "Z";
+ }
return result;
}
@@ -83,7 +88,16 @@ public class OperatorInstanceof extends Operator {
@Override
public void generateCode(MethodVisitor mv, CodeFlow cf) {
getLeftOperand().generateCode(mv, cf);
- mv.visitTypeInsn(INSTANCEOF,Type.getInternalName(this.type));
+ CodeFlow.insertBoxIfNecessary(mv, cf.lastDescriptor());
+ if (this.type.isPrimitive()) {
+ // always false - but left operand code always driven
+ // in case it had side effects
+ mv.visitInsn(POP);
+ mv.visitInsn(ICONST_0); // value of false
+ }
+ else {
+ mv.visitTypeInsn(INSTANCEOF,Type.getInternalName(this.type));
+ }
cf.pushDescriptor(this.exitTypeDescriptor);
}
diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java
index 8531aa0d..600ffda6 100644
--- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java
+++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -525,7 +525,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
// parse: @beanname @'bean.name'
// quoted if dotted
private boolean maybeEatBeanReference() {
- if (peekToken(TokenKind.BEAN_REF)) {
+ if (peekToken(TokenKind.BEAN_REF) || peekToken(TokenKind.FACTORY_BEAN_REF)) {
Token beanRefToken = nextToken();
Token beanNameToken = null;
String beanName = null;
@@ -543,7 +543,14 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
SpelMessage.INVALID_BEAN_REFERENCE);
}
- BeanReference beanReference = new BeanReference(toPos(beanNameToken) ,beanName);
+ BeanReference beanReference = null;
+ if (beanRefToken.getKind() == TokenKind.FACTORY_BEAN_REF) {
+ String beanNameString = new StringBuilder().append(TokenKind.FACTORY_BEAN_REF.tokenChars).append(beanName).toString();
+ beanReference = new BeanReference(toPos(beanRefToken.startPos,beanNameToken.endPos),beanNameString);
+ }
+ else {
+ beanReference = new BeanReference(toPos(beanNameToken) ,beanName);
+ }
this.constructedNodes.push(beanReference);
return true;
}
diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/TokenKind.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/TokenKind.java
index 4a31941e..0b577554 100644
--- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/TokenKind.java
+++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/TokenKind.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -112,6 +112,8 @@ enum TokenKind {
BEAN_REF("@"),
+ FACTORY_BEAN_REF("&"),
+
SYMBOLIC_OR("||"),
SYMBOLIC_AND("&&"),
diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/Tokenizer.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/Tokenizer.java
index fb912228..e0408f10 100644
--- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/Tokenizer.java
+++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/Tokenizer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -182,12 +182,12 @@ class Tokenizer {
}
break;
case '&':
- if (!isTwoCharToken(TokenKind.SYMBOLIC_AND)) {
- throw new InternalParseException(new SpelParseException(
- this.expressionString, this.pos, SpelMessage.MISSING_CHARACTER,
- "&"));
+ if (isTwoCharToken(TokenKind.SYMBOLIC_AND)) {
+ pushPairToken(TokenKind.SYMBOLIC_AND);
+ }
+ else {
+ pushCharToken(TokenKind.FACTORY_BEAN_REF);
}
- pushPairToken(TokenKind.SYMBOLIC_AND);
break;
case '|':
if (!isTwoCharToken(TokenKind.SYMBOLIC_OR)) {
diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java
index 4a3a5cfe..8cb54e8f 100644
--- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java
+++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -33,7 +33,6 @@ import org.springframework.asm.MethodVisitor;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.Property;
import org.springframework.core.convert.TypeDescriptor;
-import org.springframework.core.style.ToStringCreator;
import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
@@ -70,11 +69,14 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
}
- private final Map<CacheKey, InvokerPair> readerCache = new ConcurrentHashMap<CacheKey, InvokerPair>(64);
+ private final Map<PropertyCacheKey, InvokerPair> readerCache =
+ new ConcurrentHashMap<PropertyCacheKey, InvokerPair>(64);
- private final Map<CacheKey, Member> writerCache = new ConcurrentHashMap<CacheKey, Member>(64);
+ private final Map<PropertyCacheKey, Member> writerCache =
+ new ConcurrentHashMap<PropertyCacheKey, Member>(64);
- private final Map<CacheKey, TypeDescriptor> typeDescriptorCache = new ConcurrentHashMap<CacheKey, TypeDescriptor>(64);
+ private final Map<PropertyCacheKey, TypeDescriptor> typeDescriptorCache =
+ new ConcurrentHashMap<PropertyCacheKey, TypeDescriptor>(64);
private InvokerPair lastReadInvokerPair;
@@ -96,7 +98,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
if (type.isArray() && name.equals("length")) {
return true;
}
- CacheKey cacheKey = new CacheKey(type, name, target instanceof Class);
+ PropertyCacheKey cacheKey = new PropertyCacheKey(type, name, target instanceof Class);
if (this.readerCache.containsKey(cacheKey)) {
return true;
}
@@ -140,7 +142,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
return new TypedValue(Array.getLength(target));
}
- CacheKey cacheKey = new CacheKey(type, name, target instanceof Class);
+ PropertyCacheKey cacheKey = new PropertyCacheKey(type, name, target instanceof Class);
InvokerPair invoker = this.readerCache.get(cacheKey);
lastReadInvokerPair = invoker;
@@ -202,7 +204,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
return false;
}
Class<?> type = (target instanceof Class ? (Class<?>) target : target.getClass());
- CacheKey cacheKey = new CacheKey(type, name, target instanceof Class);
+ PropertyCacheKey cacheKey = new PropertyCacheKey(type, name, target instanceof Class);
if (this.writerCache.containsKey(cacheKey)) {
return true;
}
@@ -244,7 +246,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
throw new AccessException("Type conversion failure", evaluationException);
}
}
- CacheKey cacheKey = new CacheKey(type, name, target instanceof Class);
+ PropertyCacheKey cacheKey = new PropertyCacheKey(type, name, target instanceof Class);
Member cachedMember = this.writerCache.get(cacheKey);
if (cachedMember == null || cachedMember instanceof Method) {
@@ -301,7 +303,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
if (type.isArray() && name.equals("length")) {
return TypeDescriptor.valueOf(Integer.TYPE);
}
- CacheKey cacheKey = new CacheKey(type, name, target instanceof Class);
+ PropertyCacheKey cacheKey = new PropertyCacheKey(type, name, target instanceof Class);
TypeDescriptor typeDescriptor = this.typeDescriptorCache.get(cacheKey);
if (typeDescriptor == null) {
// attempt to populate the cache entry
@@ -466,7 +468,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
return this;
}
- CacheKey cacheKey = new CacheKey(type, name, target instanceof Class);
+ PropertyCacheKey cacheKey = new PropertyCacheKey(type, name, target instanceof Class);
InvokerPair invocationTarget = this.readerCache.get(cacheKey);
if (invocationTarget == null || invocationTarget.member instanceof Method) {
@@ -520,17 +522,17 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
}
- private static class CacheKey {
+ private static final class PropertyCacheKey implements Comparable<PropertyCacheKey> {
private final Class<?> clazz;
- private final String name;
+ private final String property;
private boolean targetIsClass;
- public CacheKey(Class<?> clazz, String name, boolean targetIsClass) {
+ public PropertyCacheKey(Class<?> clazz, String name, boolean targetIsClass) {
this.clazz = clazz;
- this.name = name;
+ this.property = name;
this.targetIsClass = targetIsClass;
}
@@ -539,23 +541,32 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
if (this == other) {
return true;
}
- if (!(other instanceof CacheKey)) {
+ if (!(other instanceof PropertyCacheKey)) {
return false;
}
- CacheKey otherKey = (CacheKey) other;
- return (this.clazz.equals(otherKey.clazz) && this.name.equals(otherKey.name) &&
+ PropertyCacheKey otherKey = (PropertyCacheKey) other;
+ return (this.clazz == otherKey.clazz && this.property.equals(otherKey.property) &&
this.targetIsClass == otherKey.targetIsClass);
}
@Override
public int hashCode() {
- return (this.clazz.hashCode() * 29 + this.name.hashCode());
+ return (this.clazz.hashCode() * 29 + this.property.hashCode());
}
@Override
public String toString() {
- return new ToStringCreator(this).append("clazz", this.clazz).append("name",
- this.name).append("targetIsClass", this.targetIsClass).toString();
+ return "CacheKey [clazz=" + this.clazz.getName() + ", property=" + this.property + ", " +
+ this.property + ", targetIsClass=" + this.targetIsClass + "]";
+ }
+
+ @Override
+ public int compareTo(PropertyCacheKey other) {
+ int result = this.clazz.getName().compareTo(other.clazz.getName());
+ if (result == 0) {
+ result = this.property.compareTo(other.property);
+ }
+ return result;
}
}
diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java
index 690a7e57..253e4bb3 100644
--- a/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java
+++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,7 +36,7 @@ import org.springframework.util.Assert;
*/
public class StandardTypeConverter implements TypeConverter {
- private static ConversionService defaultConversionService;
+ private static volatile ConversionService defaultConversionService;
private final ConversionService conversionService;
@@ -45,10 +45,8 @@ public class StandardTypeConverter implements TypeConverter {
* Create a StandardTypeConverter for the default ConversionService.
*/
public StandardTypeConverter() {
- synchronized (this) {
- if (defaultConversionService == null) {
- defaultConversionService = new DefaultConversionService();
- }
+ if (defaultConversionService == null) {
+ defaultConversionService = new DefaultConversionService();
}
this.conversionService = defaultConversionService;
}
diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java
index de17ed7c..fe583605 100644
--- a/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java
+++ b/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java
@@ -71,7 +71,8 @@ public class EvaluationTests extends AbstractExpressionTests {
try {
o = parser.parseExpression("list2[3]").getValue(new StandardEvaluationContext(testClass));
fail();
- } catch (EvaluationException ee) {
+ }
+ catch (EvaluationException ee) {
ee.printStackTrace();
// success!
}
@@ -241,7 +242,8 @@ public class EvaluationTests extends AbstractExpressionTests {
try {
new SpelExpressionParser().parseExpression("placeOfBirth.foo.");
fail("Should have failed to parse");
- } catch (ParseException e) {
+ }
+ catch (ParseException e) {
assertTrue(e instanceof SpelParseException);
SpelParseException spe = (SpelParseException) e;
assertEquals(SpelMessage.OOD, spe.getMessageCode());
@@ -265,7 +267,8 @@ public class EvaluationTests extends AbstractExpressionTests {
try {
new SpelExpressionParser().parseRaw("placeOfBirth.23");
fail();
- } catch (SpelParseException spe) {
+ }
+ catch (SpelParseException spe) {
assertEquals(spe.getMessageCode(), SpelMessage.UNEXPECTED_DATA_AFTER_DOT);
assertEquals("23", spe.getInserts()[0]);
}
@@ -554,7 +557,8 @@ public class EvaluationTests extends AbstractExpressionTests {
try {
assertFalse(parser.parseExpression("T(List)!=null").getValue(context, Boolean.class));
fail("should have failed to find List");
- } catch (EvaluationException ee) {
+ }
+ catch (EvaluationException ee) {
// success - List not found
}
((StandardTypeLocator) context.getTypeLocator()).registerImport("java.util");
@@ -633,7 +637,8 @@ public class EvaluationTests extends AbstractExpressionTests {
try {
context.registerMethodFilter(String.class, filter);
fail("should have failed");
- } catch (IllegalStateException ise) {
+ }
+ catch (IllegalStateException ise) {
assertEquals(
"Method filter cannot be set as the reflective method resolver is not in use",
ise.getMessage());
@@ -738,7 +743,8 @@ public class EvaluationTests extends AbstractExpressionTests {
try {
e.getValue(ctx,String.class);
fail();
- } catch (SpelEvaluationException see) {
+ }
+ catch (SpelEvaluationException see) {
assertEquals(SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS,see.getMessageCode());
}
}
@@ -754,7 +760,8 @@ public class EvaluationTests extends AbstractExpressionTests {
expression = parser.parseExpression("foo[3]");
try {
expression.setValue(ctx, "3");
- } catch(SpelEvaluationException see) {
+ }
+ catch (SpelEvaluationException see) {
assertEquals(SpelMessage.UNABLE_TO_GROW_COLLECTION, see.getMessageCode());
assertThat(instance.getFoo().size(), equalTo(3));
}
@@ -771,7 +778,8 @@ public class EvaluationTests extends AbstractExpressionTests {
try {
e.getValue(ctx,Integer.class);
fail();
- } catch (SpelEvaluationException see) {
+ }
+ catch (SpelEvaluationException see) {
assertEquals(SpelMessage.NOT_ASSIGNABLE,see.getMessageCode());
}
}
@@ -894,7 +902,8 @@ public class EvaluationTests extends AbstractExpressionTests {
try {
e.getValue(ctx,Double.TYPE);
fail();
- } catch (SpelEvaluationException see) {
+ }
+ catch (SpelEvaluationException see) {
assertEquals(SpelMessage.OPERAND_NOT_INCREMENTABLE,see.getMessageCode());
}
@@ -902,7 +911,8 @@ public class EvaluationTests extends AbstractExpressionTests {
try {
e.getValue(ctx,Double.TYPE);
fail();
- } catch (SpelEvaluationException see) {
+ }
+ catch (SpelEvaluationException see) {
assertEquals(SpelMessage.OPERAND_NOT_INCREMENTABLE,see.getMessageCode());
}
}
@@ -917,14 +927,16 @@ public class EvaluationTests extends AbstractExpressionTests {
Expression e = parser.parseExpression("++1");
e.getValue(ctx,Integer.class);
fail();
- } catch (SpelEvaluationException see) {
+ }
+ catch (SpelEvaluationException see) {
assertEquals(SpelMessage.NOT_ASSIGNABLE,see.getMessageCode());
}
try {
Expression e = parser.parseExpression("1++");
e.getValue(ctx,Integer.class);
fail();
- } catch (SpelEvaluationException see) {
+ }
+ catch (SpelEvaluationException see) {
assertEquals(SpelMessage.NOT_ASSIGNABLE,see.getMessageCode());
}
}
@@ -938,7 +950,8 @@ public class EvaluationTests extends AbstractExpressionTests {
try {
e.getValue(ctx,Integer.class);
fail();
- } catch (SpelEvaluationException see) {
+ }
+ catch (SpelEvaluationException see) {
assertEquals(SpelMessage.NOT_ASSIGNABLE,see.getMessageCode());
}
}
@@ -1060,7 +1073,8 @@ public class EvaluationTests extends AbstractExpressionTests {
try {
e.getValue(ctx,Double.TYPE);
fail();
- } catch (SpelEvaluationException see) {
+ }
+ catch (SpelEvaluationException see) {
assertEquals(SpelMessage.OPERAND_NOT_DECREMENTABLE,see.getMessageCode());
}
@@ -1068,7 +1082,8 @@ public class EvaluationTests extends AbstractExpressionTests {
try {
e.getValue(ctx,Double.TYPE);
fail();
- } catch (SpelEvaluationException see) {
+ }
+ catch (SpelEvaluationException see) {
assertEquals(SpelMessage.OPERAND_NOT_DECREMENTABLE,see.getMessageCode());
}
}
@@ -1083,14 +1098,16 @@ public class EvaluationTests extends AbstractExpressionTests {
Expression e = parser.parseExpression("--1");
e.getValue(ctx,Integer.class);
fail();
- } catch (SpelEvaluationException see) {
+ }
+ catch (SpelEvaluationException see) {
assertEquals(SpelMessage.NOT_ASSIGNABLE,see.getMessageCode());
}
try {
Expression e = parser.parseExpression("1--");
e.getValue(ctx,Integer.class);
fail();
- } catch (SpelEvaluationException see) {
+ }
+ catch (SpelEvaluationException see) {
assertEquals(SpelMessage.NOT_ASSIGNABLE,see.getMessageCode());
}
}
@@ -1123,36 +1140,6 @@ public class EvaluationTests extends AbstractExpressionTests {
}
-
-
- private void expectFail(ExpressionParser parser, EvaluationContext eContext, String expressionString, SpelMessage messageCode) {
- try {
- Expression e = parser.parseExpression(expressionString);
- SpelUtilities.printAbstractSyntaxTree(System.out, e);
- e.getValue(eContext);
- fail();
- } catch (SpelEvaluationException see) {
- see.printStackTrace();
- assertEquals(messageCode,see.getMessageCode());
- }
- }
-
- private void expectFailNotAssignable(ExpressionParser parser, EvaluationContext eContext, String expressionString) {
- expectFail(parser,eContext,expressionString,SpelMessage.NOT_ASSIGNABLE);
- }
-
- private void expectFailSetValueNotSupported(ExpressionParser parser, EvaluationContext eContext, String expressionString) {
- expectFail(parser,eContext,expressionString,SpelMessage.SETVALUE_NOT_SUPPORTED);
- }
-
- private void expectFailNotIncrementable(ExpressionParser parser, EvaluationContext eContext, String expressionString) {
- expectFail(parser,eContext,expressionString,SpelMessage.OPERAND_NOT_INCREMENTABLE);
- }
-
- private void expectFailNotDecrementable(ExpressionParser parser, EvaluationContext eContext, String expressionString) {
- expectFail(parser,eContext,expressionString,SpelMessage.OPERAND_NOT_DECREMENTABLE);
- }
-
// Verify how all the nodes behave with assignment (++, --, =)
@Test
public void incrementAllNodeTypes() throws SecurityException, NoSuchMethodException {
@@ -1456,9 +1443,39 @@ public class EvaluationTests extends AbstractExpressionTests {
r = e.getValue(ctx,Integer.TYPE);
assertEquals(100,r);
assertEquals(100,helper.iii);
+ }
+
+
+ private void expectFail(ExpressionParser parser, EvaluationContext eContext, String expressionString, SpelMessage messageCode) {
+ try {
+ Expression e = parser.parseExpression(expressionString);
+ SpelUtilities.printAbstractSyntaxTree(System.out, e);
+ e.getValue(eContext);
+ fail();
+ }
+ catch (SpelEvaluationException see) {
+ see.printStackTrace();
+ assertEquals(messageCode,see.getMessageCode());
+ }
+ }
+
+ private void expectFailNotAssignable(ExpressionParser parser, EvaluationContext eContext, String expressionString) {
+ expectFail(parser,eContext,expressionString,SpelMessage.NOT_ASSIGNABLE);
+ }
+ private void expectFailSetValueNotSupported(ExpressionParser parser, EvaluationContext eContext, String expressionString) {
+ expectFail(parser,eContext,expressionString,SpelMessage.SETVALUE_NOT_SUPPORTED);
}
+ private void expectFailNotIncrementable(ExpressionParser parser, EvaluationContext eContext, String expressionString) {
+ expectFail(parser,eContext,expressionString,SpelMessage.OPERAND_NOT_INCREMENTABLE);
+ }
+
+ private void expectFailNotDecrementable(ExpressionParser parser, EvaluationContext eContext, String expressionString) {
+ expectFail(parser,eContext,expressionString,SpelMessage.OPERAND_NOT_DECREMENTABLE);
+ }
+
+
static class MyBeanResolver implements BeanResolver {
@Override
@@ -1472,5 +1489,4 @@ public class EvaluationTests extends AbstractExpressionTests {
}
-
}
diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/ExpressionLanguageScenarioTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/ExpressionLanguageScenarioTests.java
index 8d18b60c..87530cd5 100644
--- a/spring-expression/src/test/java/org/springframework/expression/spel/ExpressionLanguageScenarioTests.java
+++ b/spring-expression/src/test/java/org/springframework/expression/spel/ExpressionLanguageScenarioTests.java
@@ -47,7 +47,7 @@ import static org.junit.Assert.*;
* <li>Some basic type converters are included
* <li>properties/methods/constructors are discovered and invoked using reflection
* </ul>
- * The scenarios after that then how how to plug in extensions:<br>
+ * The scenarios after that then how to plug in extensions:<br>
* <ul>
* <li>Adding entries to the classpath that will be used to load types and define well known 'imports'
* <li>Defining variables that are then accessible in the expression
@@ -79,10 +79,12 @@ public class ExpressionLanguageScenarioTests extends AbstractExpressionTests {
assertEquals("hello world", value);
assertEquals(String.class, value.getClass());
- } catch (EvaluationException ee) {
+ }
+ catch (EvaluationException ee) {
ee.printStackTrace();
fail("Unexpected Exception: " + ee.getMessage());
- } catch (ParseException pe) {
+ }
+ catch (ParseException pe) {
pe.printStackTrace();
fail("Unexpected Exception: " + pe.getMessage());
}
@@ -186,10 +188,12 @@ public class ExpressionLanguageScenarioTests extends AbstractExpressionTests {
Object value = expr.getValue(ctx);
assertEquals("hellohello", value);
- } catch (EvaluationException ee) {
+ }
+ catch (EvaluationException ee) {
ee.printStackTrace();
fail("Unexpected Exception: " + ee.getMessage());
- } catch (ParseException pe) {
+ }
+ catch (ParseException pe) {
pe.printStackTrace();
fail("Unexpected Exception: " + pe.getMessage());
}
@@ -213,7 +217,8 @@ public class ExpressionLanguageScenarioTests extends AbstractExpressionTests {
try {
expr.setValue(ctx, Color.blue);
fail("Should not be allowed to set oranges to be blue !");
- } catch (SpelEvaluationException ee) {
+ }
+ catch (SpelEvaluationException ee) {
assertEquals(ee.getMessageCode(), SpelMessage.PROPERTY_OR_FIELD_NOT_WRITABLE_ON_NULL);
}
}
diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/ExpressionStateTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/ExpressionStateTests.java
index 68debbf8..8b2e6afa 100644
--- a/spring-expression/src/test/java/org/springframework/expression/spel/ExpressionStateTests.java
+++ b/spring-expression/src/test/java/org/springframework/expression/spel/ExpressionStateTests.java
@@ -140,7 +140,8 @@ public class ExpressionStateTests extends AbstractExpressionTests {
try {
state.popActiveContextObject();
fail("stack should be empty...");
- } catch (EmptyStackException ese) {
+ }
+ catch (EmptyStackException ese) {
// success
}
@@ -221,7 +222,8 @@ public class ExpressionStateTests extends AbstractExpressionTests {
try {
state.operate(Operation.ADD,1,2);
fail("should have failed");
- } catch (EvaluationException ee) {
+ }
+ catch (EvaluationException ee) {
SpelEvaluationException sEx = (SpelEvaluationException)ee;
assertEquals(SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES,sEx.getMessageCode());
}
@@ -229,7 +231,8 @@ public class ExpressionStateTests extends AbstractExpressionTests {
try {
state.operate(Operation.ADD,null,null);
fail("should have failed");
- } catch (EvaluationException ee) {
+ }
+ catch (EvaluationException ee) {
SpelEvaluationException sEx = (SpelEvaluationException)ee;
assertEquals(SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES,sEx.getMessageCode());
}
@@ -249,7 +252,8 @@ public class ExpressionStateTests extends AbstractExpressionTests {
try {
state.findType("someMadeUpName");
fail("Should have failed to find it");
- } catch (EvaluationException ee) {
+ }
+ catch (EvaluationException ee) {
SpelEvaluationException sEx = (SpelEvaluationException)ee;
assertEquals(SpelMessage.TYPE_NOT_FOUND,sEx.getMessageCode());
}
diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/IndexingTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/IndexingTests.java
index d88760df..f8281212 100644
--- a/spring-expression/src/test/java/org/springframework/expression/spel/IndexingTests.java
+++ b/spring-expression/src/test/java/org/springframework/expression/spel/IndexingTests.java
@@ -191,8 +191,9 @@ public class IndexingTests {
expression = parser.parseExpression("property[0]");
try {
expression.setValue(this, "4");
- } catch (EvaluationException e) {
- assertTrue(e.getMessage().startsWith("EL1053E"));
+ }
+ catch (EvaluationException ex) {
+ assertTrue(ex.getMessage().startsWith("EL1053E"));
}
}
@@ -251,8 +252,9 @@ public class IndexingTests {
expression = parser.parseExpression("property[0]");
try {
assertEquals("bar", expression.getValue(this));
- } catch (EvaluationException e) {
- assertTrue(e.getMessage().startsWith("EL1027E"));
+ }
+ catch (EvaluationException ex) {
+ assertTrue(ex.getMessage().startsWith("EL1027E"));
}
}
@@ -268,8 +270,9 @@ public class IndexingTests {
expression = parser.parseExpression("property[0]");
try {
assertEquals("bar", expression.getValue(this));
- } catch (EvaluationException e) {
- assertTrue(e.getMessage().startsWith("EL1053E"));
+ }
+ catch (EvaluationException ex) {
+ assertTrue(ex.getMessage().startsWith("EL1053E"));
}
}
@@ -285,8 +288,9 @@ public class IndexingTests {
expression = parser.parseExpression("property2[0]");
try {
assertEquals("bar", expression.getValue(this));
- } catch (EvaluationException e) {
- assertTrue(e.getMessage().startsWith("EL1053E"));
+ }
+ catch (EvaluationException ex) {
+ assertTrue(ex.getMessage().startsWith("EL1053E"));
}
}
diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/OperatorOverloaderTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/OperatorOverloaderTests.java
index f792611a..805fdc13 100644
--- a/spring-expression/src/test/java/org/springframework/expression/spel/OperatorOverloaderTests.java
+++ b/spring-expression/src/test/java/org/springframework/expression/spel/OperatorOverloaderTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author 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,44 +33,45 @@ import static org.junit.Assert.*;
*/
public class OperatorOverloaderTests extends AbstractExpressionTests {
+ @Test
+ public void testSimpleOperations() throws Exception {
+ // no built in support for this:
+ evaluateAndCheckError("'abc'-true",SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
+
+ StandardEvaluationContext eContext = TestScenarioCreator.getTestEvaluationContext();
+ eContext.setOperatorOverloader(new StringAndBooleanAddition());
+
+ SpelExpression expr = (SpelExpression)parser.parseExpression("'abc'+true");
+ assertEquals("abctrue",expr.getValue(eContext));
+
+ expr = (SpelExpression)parser.parseExpression("'abc'-true");
+ assertEquals("abc",expr.getValue(eContext));
+
+ expr = (SpelExpression)parser.parseExpression("'abc'+null");
+ assertEquals("abcnull",expr.getValue(eContext));
+ }
+
+
static class StringAndBooleanAddition implements OperatorOverloader {
@Override
public Object operate(Operation operation, Object leftOperand, Object rightOperand) throws EvaluationException {
if (operation==Operation.ADD) {
return ((String)leftOperand)+((Boolean)rightOperand).toString();
- } else {
+ }
+ else {
return leftOperand;
}
}
@Override
- public boolean overridesOperation(Operation operation, Object leftOperand, Object rightOperand)
- throws EvaluationException {
+ public boolean overridesOperation(Operation operation, Object leftOperand, Object rightOperand) throws EvaluationException {
if (leftOperand instanceof String && rightOperand instanceof Boolean) {
return true;
}
return false;
}
-
}
- @Test
- public void testSimpleOperations() throws Exception {
- // no built in support for this:
- evaluateAndCheckError("'abc'-true",SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
-
- StandardEvaluationContext eContext = TestScenarioCreator.getTestEvaluationContext();
- eContext.setOperatorOverloader(new StringAndBooleanAddition());
-
- SpelExpression expr = (SpelExpression)parser.parseExpression("'abc'+true");
- assertEquals("abctrue",expr.getValue(eContext));
-
- expr = (SpelExpression)parser.parseExpression("'abc'-true");
- assertEquals("abc",expr.getValue(eContext));
-
- expr = (SpelExpression)parser.parseExpression("'abc'+null");
- assertEquals("abcnull",expr.getValue(eContext));
- }
}
diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/ParsingTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/ParsingTests.java
index 9368c253..38fbc369 100644
--- a/spring-expression/src/test/java/org/springframework/expression/spel/ParsingTests.java
+++ b/spring-expression/src/test/java/org/springframework/expression/spel/ParsingTests.java
@@ -462,7 +462,8 @@ public class ParsingTests {
fail("Parsed exception was null");
}
assertEquals("String form of AST does not match expected output", expectedStringFormOfAST, e.toStringAST());
- } catch (ParseException ee) {
+ }
+ catch (ParseException ee) {
ee.printStackTrace();
fail("Unexpected Exception: " + ee.getMessage());
}
diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java
index 15ac4bd2..63919fce 100644
--- a/spring-expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java
+++ b/spring-expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java
@@ -78,15 +78,17 @@ public class PropertyAccessTests extends AbstractExpressionTests {
try {
expr.getValue(context);
fail("Should have failed - default property resolver cannot resolve on null");
- } catch (Exception e) {
- checkException(e,SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE_ON_NULL);
+ }
+ catch (Exception ex) {
+ checkException(ex, SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE_ON_NULL);
}
assertFalse(expr.isWritable(context));
try {
expr.setValue(context,"abc");
fail("Should have failed - default property resolver cannot resolve on null");
- } catch (Exception e) {
- checkException(e,SpelMessage.PROPERTY_OR_FIELD_NOT_WRITABLE_ON_NULL);
+ }
+ catch (Exception ex) {
+ checkException(ex, SpelMessage.PROPERTY_OR_FIELD_NOT_WRITABLE_ON_NULL);
}
}
@@ -94,7 +96,8 @@ public class PropertyAccessTests extends AbstractExpressionTests {
if (e instanceof SpelEvaluationException) {
SpelMessage sm = ((SpelEvaluationException)e).getMessageCode();
assertEquals("Expected exception type did not occur",expectedMessage,sm);
- } else {
+ }
+ else {
fail("Should be a SpelException "+e);
}
}
@@ -127,7 +130,8 @@ public class PropertyAccessTests extends AbstractExpressionTests {
try {
expr.setValue(ctx, "not allowed");
fail("Should not have been allowed");
- } catch (EvaluationException e) {
+ }
+ catch (EvaluationException ex) {
// success - message will be: EL1063E:(pos 20): A problem occurred whilst attempting to set the property
// 'flibbles': 'Cannot set flibbles to an object of type 'class java.lang.String''
// System.out.println(e.getMessage());
@@ -171,38 +175,42 @@ public class PropertyAccessTests extends AbstractExpressionTests {
@Override
public Class<?>[] getSpecificTargetClasses() {
- return new Class[] { String.class };
+ return new Class[] {String.class};
}
@Override
public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
- if (!(target instanceof String))
+ if (!(target instanceof String)) {
throw new RuntimeException("Assertion Failed! target should be String");
+ }
return (name.equals("flibbles"));
}
@Override
public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
- if (!(target instanceof String))
+ if (!(target instanceof String)) {
throw new RuntimeException("Assertion Failed! target should be String");
+ }
return (name.equals("flibbles"));
}
@Override
public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
- if (!name.equals("flibbles"))
+ if (!name.equals("flibbles")) {
throw new RuntimeException("Assertion Failed! name should be flibbles");
+ }
return new TypedValue(flibbles);
}
@Override
- public void write(EvaluationContext context, Object target, String name, Object newValue)
- throws AccessException {
- if (!name.equals("flibbles"))
+ public void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException {
+ if (!name.equals("flibbles")) {
throw new RuntimeException("Assertion Failed! name should be flibbles");
+ }
try {
flibbles = (Integer) context.getTypeConverter().convertValue(newValue, TypeDescriptor.forObject(newValue), TypeDescriptor.valueOf(Integer.class));
- }catch (EvaluationException e) {
+ }
+ catch (EvaluationException ex) {
throw new AccessException("Cannot set flibbles to an object of type '" + newValue.getClass() + "'");
}
}
diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/ScenariosForSpringSecurity.java b/spring-expression/src/test/java/org/springframework/expression/spel/ScenariosForSpringSecurity.java
index 2457a802..ef753aa4 100644
--- a/spring-expression/src/test/java/org/springframework/expression/spel/ScenariosForSpringSecurity.java
+++ b/spring-expression/src/test/java/org/springframework/expression/spel/ScenariosForSpringSecurity.java
@@ -61,7 +61,8 @@ public class ScenariosForSpringSecurity extends AbstractExpressionTests {
value = expr.getValue(ctx,Boolean.class);
assertTrue(value);
- } catch (EvaluationException ee) {
+ }
+ catch (EvaluationException ee) {
ee.printStackTrace();
fail("Unexpected SpelException: " + ee.getMessage());
}
@@ -160,10 +161,10 @@ public class ScenariosForSpringSecurity extends AbstractExpressionTests {
public String[] getRoles() { return new String[]{"NONE"}; }
public boolean hasAnyRole(String... roles) {
- if (roles==null) return true;
+ if (roles == null) return true;
String[] myRoles = getRoles();
- for (int i=0;i<myRoles.length;i++) {
- for (int j=0;j<roles.length;j++) {
+ for (int i = 0; i < myRoles.length; i++) {
+ for (int j = 0; j < roles.length; j++) {
if (myRoles[i].equals(roles[j])) return true;
}
}
diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SelectionAndProjectionTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SelectionAndProjectionTests.java
index eb318762..3c8ec310 100644
--- a/spring-expression/src/test/java/org/springframework/expression/spel/SelectionAndProjectionTests.java
+++ b/spring-expression/src/test/java/org/springframework/expression/spel/SelectionAndProjectionTests.java
@@ -428,7 +428,8 @@ public class SelectionAndProjectionTests {
for (int i = 0; i < 3; i++) {
if (i == 1) {
array[i] = new IntegerTestBean(5.9f);
- } else {
+ }
+ else {
array[i] = new IntegerTestBean(i + 5);
}
}
diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SetValueTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SetValueTests.java
index 34954ae5..7d770ee0 100644
--- a/spring-expression/src/test/java/org/springframework/expression/spel/SetValueTests.java
+++ b/spring-expression/src/test/java/org/springframework/expression/spel/SetValueTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +39,7 @@ public class SetValueTests extends AbstractExpressionTests {
private final static boolean DEBUG = false;
+
@Test
public void testSetProperty() {
setValue("wonNobelPrize", true);
@@ -90,7 +91,8 @@ public class SetValueTests extends AbstractExpressionTests {
try {
assertFalse("Should not be writable!",e.isWritable(lContext));
fail("Should have had an error because wibble does not really exist");
- } catch (SpelEvaluationException see) {
+ }
+ catch (SpelEvaluationException see) {
// org.springframework.expression.spel.SpelEvaluationException: EL1008E:(pos 15): Property or field 'wibble' cannot be found on object of type 'org.springframework.expression.spel.testresources.ArrayContainer' - maybe not public?
// at org.springframework.expression.spel.ast.PropertyOrFieldReference.readProperty(PropertyOrFieldReference.java:225)
// success!
@@ -110,7 +112,8 @@ public class SetValueTests extends AbstractExpressionTests {
try {
assertFalse("Should not be writable!",e.isWritable(lContext));
fail("Should have had an error because wibble does not really exist");
- } catch (SpelEvaluationException see) {
+ }
+ catch (SpelEvaluationException see) {
// success!
}
@@ -119,7 +122,8 @@ public class SetValueTests extends AbstractExpressionTests {
try {
assertFalse("Should not be writable!",e.isWritable(lContext));
fail("Should have had an error because wibble does not really exist");
- } catch (SpelEvaluationException see) {
+ }
+ catch (SpelEvaluationException see) {
// success!
}
}
@@ -247,10 +251,12 @@ public class SetValueTests extends AbstractExpressionTests {
StandardEvaluationContext lContext = TestScenarioCreator.getTestEvaluationContext();
e.setValue(lContext, value);
fail("expected an error");
- } catch (ParseException pe) {
+ }
+ catch (ParseException pe) {
pe.printStackTrace();
fail("Unexpected Exception: " + pe.getMessage());
- } catch (EvaluationException ee) {
+ }
+ catch (EvaluationException ee) {
// success!
}
}
@@ -268,10 +274,12 @@ public class SetValueTests extends AbstractExpressionTests {
assertTrue("Expression is not writeable but should be", e.isWritable(lContext));
e.setValue(lContext, value);
assertEquals("Retrieved value was not equal to set value", value, e.getValue(lContext,value.getClass()));
- } catch (EvaluationException ee) {
+ }
+ catch (EvaluationException ee) {
ee.printStackTrace();
fail("Unexpected Exception: " + ee.getMessage());
- } catch (ParseException pe) {
+ }
+ catch (ParseException pe) {
pe.printStackTrace();
fail("Unexpected Exception: " + pe.getMessage());
}
@@ -299,12 +307,15 @@ public class SetValueTests extends AbstractExpressionTests {
fail("Not the same: ["+a+"] type="+a.getClass()+" ["+b+"] type="+b.getClass());
// assertEquals("Retrieved value was not equal to set value", expectedValue, e.getValue(lContext));
}
- } catch (EvaluationException ee) {
+ }
+ catch (EvaluationException ee) {
ee.printStackTrace();
fail("Unexpected Exception: " + ee.getMessage());
- } catch (ParseException pe) {
+ }
+ catch (ParseException pe) {
pe.printStackTrace();
fail("Unexpected Exception: " + pe.getMessage());
}
}
+
}
diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java
index 542e3215..cfde694a 100644
--- a/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java
+++ b/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -236,6 +236,53 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
}
@Test
+ public void operatorInstanceOf_SPR14250() throws Exception {
+ // primitive left operand - should get boxed, return true
+ expression = parse("3 instanceof T(Integer)");
+ assertEquals(true,expression.getValue());
+ assertCanCompile(expression);
+ assertEquals(true,expression.getValue());
+
+ // primitive left operand - should get boxed, return false
+ expression = parse("3 instanceof T(String)");
+ assertEquals(false,expression.getValue());
+ assertCanCompile(expression);
+ assertEquals(false,expression.getValue());
+
+ // double slot left operand - should get boxed, return false
+ expression = parse("3.0d instanceof T(Integer)");
+ assertEquals(false,expression.getValue());
+ assertCanCompile(expression);
+ assertEquals(false,expression.getValue());
+
+ // double slot left operand - should get boxed, return true
+ expression = parse("3.0d instanceof T(Double)");
+ assertEquals(true,expression.getValue());
+ assertCanCompile(expression);
+ assertEquals(true,expression.getValue());
+
+ // Only when the right hand operand is a direct type reference
+ // will it be compilable.
+ StandardEvaluationContext ctx = new StandardEvaluationContext();
+ ctx.setVariable("foo", String.class);
+ expression = parse("3 instanceof #foo");
+ assertEquals(false,expression.getValue(ctx));
+ assertCantCompile(expression);
+
+ // use of primitive as type for instanceof check - compilable
+ // but always false
+ expression = parse("3 instanceof T(int)");
+ assertEquals(false,expression.getValue());
+ assertCanCompile(expression);
+ assertEquals(false,expression.getValue());
+
+ expression = parse("3 instanceof T(long)");
+ assertEquals(false,expression.getValue());
+ assertCanCompile(expression);
+ assertEquals(false,expression.getValue());
+ }
+
+ @Test
public void stringLiteral() throws Exception {
expression = parser.parseExpression("'abcde'");
assertEquals("abcde",expression.getValue(new TestClass1(),String.class));
@@ -3000,11 +3047,11 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
assertEquals("java.lang.String",expression.getValue());
assertCanCompile(expression);
assertEquals("java.lang.String",expression.getValue());
-
+
// These tests below verify that the chain of static accesses (either method/property or field)
// leave the right thing on top of the stack for processing by any outer consuming code.
// Here the consuming code is the String.valueOf() function. If the wrong thing were on
- // the stack (for example if the compiled code for static methods wasn't popping the
+ // the stack (for example if the compiled code for static methods wasn't popping the
// previous thing off the stack) the valueOf() would operate on the wrong value.
String shclass = StaticsHelper.class.getName();
@@ -3042,7 +3089,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
assertEquals("fb",expression.getValue(StaticsHelper.sh));
assertCanCompile(expression);
assertEquals("fb",expression.getValue(StaticsHelper.sh));
-
+
expression = parser.parseExpression("T(String).valueOf(propertya.propertyb)");
assertEquals("pb",expression.getValue(StaticsHelper.sh));
assertCanCompile(expression);
@@ -3052,9 +3099,9 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
assertEquals("mb",expression.getValue(StaticsHelper.sh));
assertCanCompile(expression);
assertEquals("mb",expression.getValue(StaticsHelper.sh));
-
- }
+ }
+
@Test
public void constructorReference_SPR12326() {
String type = this.getClass().getName();
@@ -4267,7 +4314,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
// // time it interpreted
// long stime = System.currentTimeMillis();
-// for (int i=0;i<100000;i++) {
+// for (int i = 0;i<100000;i++) {
// v = expression.getValue(ctx,holder);
// }
// System.out.println((System.currentTimeMillis()-stime));
@@ -4278,7 +4325,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
//
// // time it compiled
// stime = System.currentTimeMillis();
-// for (int i=0;i<100000;i++) {
+// for (int i = 0;i<100000;i++) {
// v = expression.getValue(ctx,holder);
// }
// System.out.println((System.currentTimeMillis()-stime));
@@ -5014,7 +5061,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
public void reset() {
i = 0;
- _i=0;
+ _i = 0;
s = null;
_s = null;
field = null;
@@ -5446,7 +5493,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
public static String methodb() {
return "mb";
}
-
+
public static StaticsHelper getPropertya() {
return sh;
}
@@ -5454,11 +5501,11 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
public static String getPropertyb() {
return "pb";
}
-
+
public static StaticsHelper fielda = sh;
public static String fieldb = "fb";
-
+
public String toString() {
return "sh";
}
diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationPerformanceTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationPerformanceTests.java
index 58403d6b..3aad8c6f 100644
--- a/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationPerformanceTests.java
+++ b/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationPerformanceTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -79,80 +79,80 @@ public class SpelCompilationPerformanceTests extends AbstractExpressionTests {
assertEquals(2d,o);
System.out.println("Performance check for SpEL expression: '(T(Integer).valueOf(payload).doubleValue())/18D'");
long stime = System.currentTimeMillis();
- for (int i=0;i<1000000;i++) {
+ for (int i = 0; i < 1000000; i++) {
o = expression.getValue(nh);
}
- System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms");
+ System.out.println("One million iterations: " + (System.currentTimeMillis()-stime) + "ms");
stime = System.currentTimeMillis();
- for (int i=0;i<1000000;i++) {
+ for (int i = 0; i < 1000000; i++) {
o = expression.getValue(nh);
}
- System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms");
+ System.out.println("One million iterations: " + (System.currentTimeMillis()-stime) + "ms");
stime = System.currentTimeMillis();
- for (int i=0;i<1000000;i++) {
+ for (int i = 0; i < 1000000; i++) {
o = expression.getValue(nh);
}
- System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms");
+ System.out.println("One million iterations: " + (System.currentTimeMillis()-stime) + "ms");
compile(expression);
System.out.println("Now compiled:");
o = expression.getValue(nh);
assertEquals(2d, o);
stime = System.currentTimeMillis();
- for (int i=0;i<1000000;i++) {
+ for (int i = 0; i < 1000000; i++) {
o = expression.getValue(nh);
}
- System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms");
+ System.out.println("One million iterations: " + (System.currentTimeMillis()-stime) + "ms");
stime = System.currentTimeMillis();
- for (int i=0;i<1000000;i++) {
+ for (int i = 0; i < 1000000; i++) {
o = expression.getValue(nh);
}
- System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms");
+ System.out.println("One million iterations: " + (System.currentTimeMillis()-stime) + "ms");
stime = System.currentTimeMillis();
- for (int i=0;i<1000000;i++) {
+ for (int i = 0; i < 1000000; i++) {
o = expression.getValue(nh);
}
- System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms");
+ System.out.println("One million iterations: " + (System.currentTimeMillis()-stime) + "ms");
expression = parser.parseExpression("payload/18D");
o = expression.getValue(nh);
assertEquals(2d,o);
System.out.println("Performance check for SpEL expression: 'payload/18D'");
stime = System.currentTimeMillis();
- for (int i=0;i<1000000;i++) {
+ for (int i = 0; i < 1000000; i++) {
o = expression.getValue(nh);
}
- System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms");
+ System.out.println("One million iterations: " + (System.currentTimeMillis()-stime) + "ms");
stime = System.currentTimeMillis();
- for (int i=0;i<1000000;i++) {
+ for (int i = 0; i < 1000000; i++) {
o = expression.getValue(nh);
}
- System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms");
+ System.out.println("One million iterations: " + (System.currentTimeMillis()-stime) + "ms");
stime = System.currentTimeMillis();
- for (int i=0;i<1000000;i++) {
+ for (int i = 0; i < 1000000; i++) {
o = expression.getValue(nh);
}
- System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms");
+ System.out.println("One million iterations: " + (System.currentTimeMillis()-stime) + "ms");
compile(expression);
System.out.println("Now compiled:");
o = expression.getValue(nh);
assertEquals(2d, o);
stime = System.currentTimeMillis();
- for (int i=0;i<1000000;i++) {
+ for (int i = 0; i < 1000000; i++) {
o = expression.getValue(nh);
}
- System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms");
+ System.out.println("One million iterations: " + (System.currentTimeMillis()-stime) + "ms");
stime = System.currentTimeMillis();
- for (int i=0;i<1000000;i++) {
+ for (int i = 0; i < 1000000; i++) {
o = expression.getValue(nh);
}
- System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms");
+ System.out.println("One million iterations: " + (System.currentTimeMillis()-stime) + "ms");
stime = System.currentTimeMillis();
- for (int i=0;i<1000000;i++) {
+ for (int i = 0; i < 1000000; i++) {
o = expression.getValue(nh);
}
- System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms");
+ System.out.println("One million iterations: " + (System.currentTimeMillis()-stime) + "ms");
}
@Test
@@ -162,40 +162,40 @@ public class SpelCompilationPerformanceTests extends AbstractExpressionTests {
assertEquals("bc",o);
System.out.println("Performance check for SpEL expression: '{'abcde','ijklm'}[0].substring({1,3,4}[0],{1,3,4}[1])'");
long stime = System.currentTimeMillis();
- for (int i=0;i<1000000;i++) {
+ for (int i = 0; i < 1000000; i++) {
o = expression.getValue();
}
- System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms");
+ System.out.println("One million iterations: " + (System.currentTimeMillis()-stime) + "ms");
stime = System.currentTimeMillis();
- for (int i=0;i<1000000;i++) {
+ for (int i = 0; i < 1000000; i++) {
o = expression.getValue();
}
- System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms");
+ System.out.println("One million iterations: " + (System.currentTimeMillis()-stime) + "ms");
stime = System.currentTimeMillis();
- for (int i=0;i<1000000;i++) {
+ for (int i = 0; i < 1000000; i++) {
o = expression.getValue();
}
- System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms");
+ System.out.println("One million iterations: " + (System.currentTimeMillis()-stime) + "ms");
compile(expression);
System.out.println("Now compiled:");
o = expression.getValue();
assertEquals("bc", o);
stime = System.currentTimeMillis();
- for (int i=0;i<1000000;i++) {
+ for (int i = 0; i < 1000000; i++) {
o = expression.getValue();
}
- System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms");
+ System.out.println("One million iterations: " + (System.currentTimeMillis()-stime) + "ms");
stime = System.currentTimeMillis();
- for (int i=0;i<1000000;i++) {
+ for (int i = 0; i < 1000000; i++) {
o = expression.getValue();
}
- System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms");
+ System.out.println("One million iterations: " + (System.currentTimeMillis()-stime) + "ms");
stime = System.currentTimeMillis();
- for (int i=0;i<1000000;i++) {
+ for (int i = 0; i < 1000000; i++) {
o = expression.getValue();
}
- System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms");
+ System.out.println("One million iterations: " + (System.currentTimeMillis()-stime) + "ms");
}
@Test
@@ -205,40 +205,40 @@ public class SpelCompilationPerformanceTests extends AbstractExpressionTests {
assertEquals("jk",o);
System.out.println("Performance check for SpEL expression: '{'abcde','ijklm'}[0].substring({1,3,4}[0],{1,3,4}[1])'");
long stime = System.currentTimeMillis();
- for (int i=0;i<1000000;i++) {
+ for (int i = 0; i < 1000000; i++) {
o = expression.getValue();
}
- System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms");
+ System.out.println("One million iterations: " + (System.currentTimeMillis()-stime) + "ms");
stime = System.currentTimeMillis();
- for (int i=0;i<1000000;i++) {
+ for (int i = 0; i < 1000000; i++) {
o = expression.getValue();
}
- System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms");
+ System.out.println("One million iterations: " + (System.currentTimeMillis()-stime) + "ms");
stime = System.currentTimeMillis();
- for (int i=0;i<1000000;i++) {
+ for (int i = 0; i < 1000000; i++) {
o = expression.getValue();
}
- System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms");
+ System.out.println("One million iterations: " + (System.currentTimeMillis()-stime) + "ms");
compile(expression);
System.out.println("Now compiled:");
o = expression.getValue();
assertEquals("jk", o);
stime = System.currentTimeMillis();
- for (int i=0;i<1000000;i++) {
+ for (int i = 0; i < 1000000; i++) {
o = expression.getValue();
}
- System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms");
+ System.out.println("One million iterations: " + (System.currentTimeMillis()-stime) + "ms");
stime = System.currentTimeMillis();
- for (int i=0;i<1000000;i++) {
+ for (int i = 0; i < 1000000; i++) {
o = expression.getValue();
}
- System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms");
+ System.out.println("One million iterations: " + (System.currentTimeMillis()-stime) + "ms");
stime = System.currentTimeMillis();
- for (int i=0;i<1000000;i++) {
+ for (int i = 0; i < 1000000; i++) {
o = expression.getValue();
}
- System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms");
+ System.out.println("One million iterations: " + (System.currentTimeMillis()-stime) + "ms");
}
@@ -251,40 +251,40 @@ public class SpelCompilationPerformanceTests extends AbstractExpressionTests {
System.out.println("Performance check for SpEL expression: 'hello' + getWorld() + ' spring'");
long stime = System.currentTimeMillis();
- for (int i=0;i<1000000;i++) {
+ for (int i = 0; i < 1000000; i++) {
o = expression.getValue(g);
}
- System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms");
+ System.out.println("One million iterations: " + (System.currentTimeMillis()-stime) + "ms");
stime = System.currentTimeMillis();
- for (int i=0;i<1000000;i++) {
+ for (int i = 0; i < 1000000; i++) {
o = expression.getValue(g);
}
- System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms");
+ System.out.println("One million iterations: " + (System.currentTimeMillis()-stime) + "ms");
stime = System.currentTimeMillis();
- for (int i=0;i<1000000;i++) {
+ for (int i = 0; i < 1000000; i++) {
o = expression.getValue(g);
}
- System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms");
+ System.out.println("One million iterations: " + (System.currentTimeMillis()-stime) + "ms");
compile(expression);
System.out.println("Now compiled:");
o = expression.getValue(g);
assertEquals("helloworld spring", o);
stime = System.currentTimeMillis();
- for (int i=0;i<1000000;i++) {
+ for (int i = 0; i < 1000000; i++) {
o = expression.getValue(g);
}
- System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms");
+ System.out.println("One million iterations: " + (System.currentTimeMillis()-stime) + "ms");
stime = System.currentTimeMillis();
- for (int i=0;i<1000000;i++) {
+ for (int i = 0; i < 1000000; i++) {
o = expression.getValue(g);
}
- System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms");
+ System.out.println("One million iterations: " + (System.currentTimeMillis()-stime) + "ms");
stime = System.currentTimeMillis();
- for (int i=0;i<1000000;i++) {
+ for (int i = 0; i < 1000000; i++) {
o = expression.getValue(g);
}
- System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms");
+ System.out.println("One million iterations: " + (System.currentTimeMillis()-stime) + "ms");
}
public static class Greeter {
@@ -301,15 +301,15 @@ public class SpelCompilationPerformanceTests extends AbstractExpressionTests {
long iTotal = 0,cTotal = 0;
// warmup
- for (int i=0;i<count;i++) {
- b = expression.getValue(payload,Boolean.TYPE);
+ for (int i = 0; i < count; i++) {
+ b = expression.getValue(payload, Boolean.TYPE);
}
log("timing interpreted: ");
- for (int iter=0;iter<iterations;iter++) {
+ for (int i = 0; i < iterations; i++) {
long stime = System.currentTimeMillis();
- for (int i=0;i<count;i++) {
- b = expression.getValue(payload,Boolean.TYPE);
+ for (int j = 0; j < count; j++) {
+ b = expression.getValue(payload, Boolean.TYPE);
}
long etime = System.currentTimeMillis();
long interpretedSpeed = (etime - stime);
@@ -320,12 +320,12 @@ public class SpelCompilationPerformanceTests extends AbstractExpressionTests {
compile(expression);
boolean bc = false;
- expression.getValue(payload,Boolean.TYPE);
+ expression.getValue(payload, Boolean.TYPE);
log("timing compiled: ");
- for (int iter=0;iter<iterations;iter++) {
+ for (int i = 0; i < iterations; i++) {
long stime = System.currentTimeMillis();
- for (int i=0;i<count;i++) {
- bc = expression.getValue(payload,Boolean.TYPE);
+ for (int j = 0; j < count; j++) {
+ bc = expression.getValue(payload, Boolean.TYPE);
}
long etime = System.currentTimeMillis();
long compiledSpeed = (etime - stime);
@@ -340,11 +340,11 @@ public class SpelCompilationPerformanceTests extends AbstractExpressionTests {
assertFalse(b);
// Verify the same result for compiled vs interpreted
- assertEquals(b,bc);
+ assertEquals(b, bc);
// Verify if the input changes, the result changes
payload.DR[0].DRFixedSection.duration = 0.04d;
- bc = expression.getValue(payload,Boolean.TYPE);
+ bc = expression.getValue(payload, Boolean.TYPE);
assertTrue(bc);
}
@@ -364,15 +364,15 @@ public class SpelCompilationPerformanceTests extends AbstractExpressionTests {
Expression expression = parser.parseExpression("hello()");
// warmup
- for (int i=0;i<count;i++) {
- interpretedResult = expression.getValue(testdata,String.class);
+ for (int i = 0; i < count; i++) {
+ interpretedResult = expression.getValue(testdata, String.class);
}
log("timing interpreted: ");
- for (int iter=0;iter<iterations;iter++) {
+ for (int i = 0; i < iterations; i++) {
stime = System.currentTimeMillis();
- for (int i=0;i<count;i++) {
- interpretedResult = expression.getValue(testdata,String.class);
+ for (int j = 0; j < count; j++) {
+ interpretedResult = expression.getValue(testdata, String.class);
}
etime = System.currentTimeMillis();
long interpretedSpeed = (etime - stime);
@@ -384,11 +384,11 @@ public class SpelCompilationPerformanceTests extends AbstractExpressionTests {
compile(expression);
log("timing compiled: ");
- expression.getValue(testdata,String.class);
- for (int iter=0;iter<iterations;iter++) {
+ expression.getValue(testdata, String.class);
+ for (int i = 0; i < iterations; i++) {
stime = System.currentTimeMillis();
- for (int i=0;i<count;i++) {
- compiledResult = expression.getValue(testdata,String.class);
+ for (int j = 0; j < count; j++) {
+ compiledResult = expression.getValue(testdata, String.class);
}
etime = System.currentTimeMillis();
long compiledSpeed = (etime - stime);
@@ -438,15 +438,15 @@ public class SpelCompilationPerformanceTests extends AbstractExpressionTests {
Expression expression = parser.parseExpression("name");
// warmup
- for (int i=0;i<count;i++) {
- expression.getValue(testdata,String.class);
+ for (int i = 0; i < count; i++) {
+ expression.getValue(testdata, String.class);
}
log("timing interpreted: ");
- for (int iter=0;iter<iterations;iter++) {
+ for (int i = 0; i < iterations; i++) {
stime = System.currentTimeMillis();
- for (int i=0;i<count;i++) {
- interpretedResult = expression.getValue(testdata,String.class);
+ for (int j = 0; j < count; j++) {
+ interpretedResult = expression.getValue(testdata, String.class);
}
etime = System.currentTimeMillis();
long interpretedSpeed = (etime - stime);
@@ -458,11 +458,11 @@ public class SpelCompilationPerformanceTests extends AbstractExpressionTests {
compile(expression);
log("timing compiled: ");
- expression.getValue(testdata,String.class);
- for (int iter=0;iter<iterations;iter++) {
+ expression.getValue(testdata, String.class);
+ for (int i = 0; i < iterations; i++) {
stime = System.currentTimeMillis();
- for (int i=0;i<count;i++) {
- compiledResult = expression.getValue(testdata,String.class);
+ for (int j = 0; j < count; j++) {
+ compiledResult = expression.getValue(testdata, String.class);
}
etime = System.currentTimeMillis();
long compiledSpeed = (etime - stime);
@@ -485,15 +485,15 @@ public class SpelCompilationPerformanceTests extends AbstractExpressionTests {
Expression expression = parser.parseExpression("foo.bar.boo");
// warmup
- for (int i=0;i<count;i++) {
- expression.getValue(testdata,String.class);
+ for (int i = 0; i < count; i++) {
+ expression.getValue(testdata, String.class);
}
log("timing interpreted: ");
- for (int iter=0;iter<iterations;iter++) {
+ for (int i = 0; i < iterations; i++) {
stime = System.currentTimeMillis();
- for (int i=0;i<count;i++) {
- interpretedResult = expression.getValue(testdata,String.class);
+ for (int j = 0; j < count; j++) {
+ interpretedResult = expression.getValue(testdata, String.class);
}
etime = System.currentTimeMillis();
long interpretedSpeed = (etime - stime);
@@ -505,11 +505,11 @@ public class SpelCompilationPerformanceTests extends AbstractExpressionTests {
compile(expression);
log("timing compiled: ");
- expression.getValue(testdata,String.class);
- for (int iter=0;iter<iterations;iter++) {
+ expression.getValue(testdata, String.class);
+ for (int i = 0; i < iterations; i++) {
stime = System.currentTimeMillis();
- for (int i=0;i<count;i++) {
- compiledResult = expression.getValue(testdata,String.class);
+ for (int j = 0; j < count; j++) {
+ compiledResult = expression.getValue(testdata, String.class);
}
etime = System.currentTimeMillis();
long compiledSpeed = (etime - stime);
@@ -531,14 +531,14 @@ public class SpelCompilationPerformanceTests extends AbstractExpressionTests {
Expression expression = parser.parseExpression("foo.baz.boo");
// warmup
- for (int i=0;i<count;i++) {
- expression.getValue(testdata,String.class);
+ for (int i = 0; i < count; i++) {
+ expression.getValue(testdata, String.class);
}
log("timing interpreted: ");
- for (int iter=0;iter<iterations;iter++) {
+ for (int i = 0; i < iterations; i++) {
stime = System.currentTimeMillis();
- for (int i=0;i<count;i++) {
- interpretedResult = expression.getValue(testdata,String.class);
+ for (int j = 0; j < count; j++) {
+ interpretedResult = expression.getValue(testdata, String.class);
}
etime = System.currentTimeMillis();
long interpretedSpeed = (etime - stime);
@@ -550,11 +550,11 @@ public class SpelCompilationPerformanceTests extends AbstractExpressionTests {
compile(expression);
log("timing compiled: ");
- expression.getValue(testdata,String.class);
- for (int iter=0;iter<iterations;iter++) {
+ expression.getValue(testdata, String.class);
+ for (int i = 0; i < iterations; i++) {
stime = System.currentTimeMillis();
- for (int i=0;i<count;i++) {
- compiledResult = expression.getValue(testdata,String.class);
+ for (int j = 0; j < count; j++) {
+ compiledResult = expression.getValue(testdata, String.class);
}
etime = System.currentTimeMillis();
long compiledSpeed = (etime - stime);
@@ -576,42 +576,42 @@ public class SpelCompilationPerformanceTests extends AbstractExpressionTests {
Expression expression = parser.parseExpression("foo.bay().boo");
// warmup
- for (int i=0;i<count;i++) {
- expression.getValue(testdata,String.class);
+ for (int i = 0; i < count; i++) {
+ expression.getValue(testdata, String.class);
}
log("timing interpreted: ");
- for (int iter=0;iter<iterations;iter++) {
+ for (int i = 0; i < iterations; i++) {
stime = System.currentTimeMillis();
- for (int i=0;i<count;i++) {
- interpretedResult = expression.getValue(testdata,String.class);
+ for (int j = 0; j < count; j++) {
+ interpretedResult = expression.getValue(testdata, String.class);
}
etime = System.currentTimeMillis();
long interpretedSpeed = (etime - stime);
- interpretedTotal+=interpretedSpeed;
- log(interpretedSpeed+"ms ");
+ interpretedTotal += interpretedSpeed;
+ log(interpretedSpeed + "ms ");
}
logln();
compile(expression);
log("timing compiled: ");
- expression.getValue(testdata,String.class);
- for (int iter=0;iter<iterations;iter++) {
+ expression.getValue(testdata, String.class);
+ for (int i = 0; i < iterations; i++) {
stime = System.currentTimeMillis();
- for (int i=0;i<count;i++) {
- compiledResult = expression.getValue(testdata,String.class);
+ for (int j = 0; j < count; j++) {
+ compiledResult = expression.getValue(testdata, String.class);
}
etime = System.currentTimeMillis();
long compiledSpeed = (etime - stime);
- compiledTotal+=compiledSpeed;
- log(compiledSpeed+"ms ");
+ compiledTotal += compiledSpeed;
+ log(compiledSpeed + "ms ");
}
logln();
assertEquals(interpretedResult,compiledResult);
- reportPerformance("nested reference (mixed field/method)",interpretedTotal, compiledTotal);
+ reportPerformance("nested reference (mixed field/method)", interpretedTotal, compiledTotal);
}
@Test
@@ -623,15 +623,15 @@ public class SpelCompilationPerformanceTests extends AbstractExpressionTests {
Expression expression = parser.parseExpression("name2");
// warmup
- for (int i=0;i<count;i++) {
- expression.getValue(testdata,String.class);
+ for (int i = 0;i < count; i++) {
+ expression.getValue(testdata, String.class);
}
log("timing interpreted: ");
- for (int iter=0;iter<iterations;iter++) {
+ for (int i = 0; i < iterations; i++) {
stime = System.currentTimeMillis();
- for (int i=0;i<count;i++) {
- interpretedResult = expression.getValue(testdata,String.class);
+ for (int j = 0; j < count; j++) {
+ interpretedResult = expression.getValue(testdata, String.class);
}
etime = System.currentTimeMillis();
long interpretedSpeed = (etime - stime);
@@ -644,11 +644,11 @@ public class SpelCompilationPerformanceTests extends AbstractExpressionTests {
compile(expression);
log("timing compiled: ");
- expression.getValue(testdata,String.class);
- for (int iter=0;iter<iterations;iter++) {
+ expression.getValue(testdata, String.class);
+ for (int i = 0; i < iterations; i++) {
stime = System.currentTimeMillis();
- for (int i=0;i<count;i++) {
- compiledResult = expression.getValue(testdata,String.class);
+ for (int j = 0; j < count; j++) {
+ compiledResult = expression.getValue(testdata, String.class);
}
etime = System.currentTimeMillis();
long compiledSpeed = (etime - stime);
@@ -672,7 +672,7 @@ public class SpelCompilationPerformanceTests extends AbstractExpressionTests {
double averageInterpreted = interpretedTotal/(iterations);
double averageCompiled = compiledTotal/(iterations);
double ratio = (averageCompiled/averageInterpreted)*100.0d;
- logln(">>"+title+": average for "+count+": compiled="+averageCompiled+"ms interpreted="+averageInterpreted+"ms: compiled takes "+((int)ratio)+"% of the interpreted time");
+ logln(">>"+title+": average for "+count+": compiled="+averageCompiled+"ms interpreted="+averageInterpreted+"ms: compiled takes " + ((int)ratio)+"% of the interpreted time");
if (averageCompiled>averageInterpreted) {
fail("Compiled version took longer than interpreted! CompiledSpeed=~"+averageCompiled+
"ms InterpretedSpeed="+averageInterpreted+"ms");
@@ -690,7 +690,8 @@ public class SpelCompilationPerformanceTests extends AbstractExpressionTests {
if (noisyTests) {
if (message!=null && message.length>0) {
System.out.println(message[0]);
- } else {
+ }
+ else {
System.out.println();
}
}
diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java
index e089bb5f..81e7e2ad 100644
--- a/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java
+++ b/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -720,6 +720,9 @@ public class SpelReproTests extends AbstractExpressionTests {
else if (beanName.equals("foo.bar")) {
return "trouble";
}
+ else if (beanName.equals("&foo")) {
+ return "foo factory";
+ }
else if (beanName.equals("goo")) {
throw new AccessException("DONT ASK ME ABOUT GOO");
}
@@ -1954,6 +1957,34 @@ public class SpelReproTests extends AbstractExpressionTests {
}
@Test
+ public void AccessingFactoryBean_spr9511() {
+ StandardEvaluationContext context = new StandardEvaluationContext();
+ context.setBeanResolver(new MyBeanResolver());
+ Expression expr = new SpelExpressionParser().parseRaw("@foo");
+ assertEquals("custard", expr.getValue(context));
+ expr = new SpelExpressionParser().parseRaw("&foo");
+ assertEquals("foo factory",expr.getValue(context));
+
+ try {
+ expr = new SpelExpressionParser().parseRaw("&@foo");
+ fail("Illegal syntax, error expected");
+ }
+ catch (SpelParseException spe) {
+ assertEquals(SpelMessage.INVALID_BEAN_REFERENCE,spe.getMessageCode());
+ assertEquals(0,spe.getPosition());
+ }
+
+ try {
+ expr = new SpelExpressionParser().parseRaw("@&foo");
+ fail("Illegal syntax, error expected");
+ }
+ catch (SpelParseException spe) {
+ assertEquals(SpelMessage.INVALID_BEAN_REFERENCE,spe.getMessageCode());
+ assertEquals(0,spe.getPosition());
+ }
+ }
+
+ @Test
public void SPR12035() {
ExpressionParser parser = new SpelExpressionParser();
diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/StandardTypeLocatorTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/StandardTypeLocatorTests.java
index 33c5437e..556cbc65 100644
--- a/spring-expression/src/test/java/org/springframework/expression/spel/StandardTypeLocatorTests.java
+++ b/spring-expression/src/test/java/org/springframework/expression/spel/StandardTypeLocatorTests.java
@@ -49,7 +49,8 @@ public class StandardTypeLocatorTests {
try {
locator.findType("URL");
fail("Should have failed");
- } catch (EvaluationException ee) {
+ }
+ catch (EvaluationException ee) {
SpelEvaluationException sEx = (SpelEvaluationException)ee;
assertEquals(SpelMessage.TYPE_NOT_FOUND,sEx.getMessageCode());
}
diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/VariableAndFunctionTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/VariableAndFunctionTests.java
index 982c3c2b..db6f4fa5 100644
--- a/spring-expression/src/test/java/org/springframework/expression/spel/VariableAndFunctionTests.java
+++ b/spring-expression/src/test/java/org/springframework/expression/spel/VariableAndFunctionTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -78,7 +78,8 @@ public class VariableAndFunctionTests extends AbstractExpressionTests {
@SuppressWarnings("unused")
Object v = parser.parseRaw("#notStatic()").getValue(ctx);
fail("Should have failed with exception - cannot call non static method that way");
- } catch (SpelEvaluationException se) {
+ }
+ catch (SpelEvaluationException se) {
if (se.getMessageCode() != SpelMessage.FUNCTION_MUST_BE_STATIC) {
se.printStackTrace();
fail("Should have failed a message about the function needing to be static, not: "
@@ -86,6 +87,8 @@ public class VariableAndFunctionTests extends AbstractExpressionTests {
}
}
}
+
+
// this method is used by the test above
public void nonStatic() {
}
diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelParserTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelParserTests.java
index 248dd1c3..6993bfc7 100644
--- a/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelParserTests.java
+++ b/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelParserTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -116,9 +116,10 @@ public class SpelParserTests {
SpelExpressionParser parser = new SpelExpressionParser();
parser.parseRaw("new String");
fail();
- } catch (ParseException e) {
- assertTrue(e instanceof SpelParseException);
- SpelParseException spe = (SpelParseException) e;
+ }
+ catch (ParseException ex) {
+ assertTrue(ex instanceof SpelParseException);
+ SpelParseException spe = (SpelParseException) ex;
assertEquals(SpelMessage.MISSING_CONSTRUCTOR_ARGS, spe.getMessageCode());
assertEquals(10, spe.getPosition());
}
@@ -127,9 +128,10 @@ public class SpelParserTests {
SpelExpressionParser parser = new SpelExpressionParser();
parser.parseRaw("new String(3,");
fail();
- } catch (ParseException e) {
- assertTrue(e instanceof SpelParseException);
- SpelParseException spe = (SpelParseException) e;
+ }
+ catch (ParseException ex) {
+ assertTrue(ex instanceof SpelParseException);
+ SpelParseException spe = (SpelParseException) ex;
assertEquals(SpelMessage.RUN_OUT_OF_ARGUMENTS, spe.getMessageCode());
assertEquals(10, spe.getPosition());
}
@@ -138,9 +140,10 @@ public class SpelParserTests {
SpelExpressionParser parser = new SpelExpressionParser();
parser.parseRaw("new String(3");
fail();
- } catch (ParseException e) {
- assertTrue(e instanceof SpelParseException);
- SpelParseException spe = (SpelParseException) e;
+ }
+ catch (ParseException ex) {
+ assertTrue(ex instanceof SpelParseException);
+ SpelParseException spe = (SpelParseException) ex;
assertEquals(SpelMessage.RUN_OUT_OF_ARGUMENTS, spe.getMessageCode());
assertEquals(10, spe.getPosition());
}
@@ -149,9 +152,10 @@ public class SpelParserTests {
SpelExpressionParser parser = new SpelExpressionParser();
parser.parseRaw("new String(");
fail();
- } catch (ParseException e) {
- assertTrue(e instanceof SpelParseException);
- SpelParseException spe = (SpelParseException) e;
+ }
+ catch (ParseException ex) {
+ assertTrue(ex instanceof SpelParseException);
+ SpelParseException spe = (SpelParseException) ex;
assertEquals(SpelMessage.RUN_OUT_OF_ARGUMENTS, spe.getMessageCode());
assertEquals(10, spe.getPosition());
}
@@ -160,9 +164,10 @@ public class SpelParserTests {
SpelExpressionParser parser = new SpelExpressionParser();
parser.parseRaw("\"abc");
fail();
- } catch (ParseException e) {
- assertTrue(e instanceof SpelParseException);
- SpelParseException spe = (SpelParseException) e;
+ }
+ catch (ParseException ex) {
+ assertTrue(ex instanceof SpelParseException);
+ SpelParseException spe = (SpelParseException) ex;
assertEquals(SpelMessage.NON_TERMINATING_DOUBLE_QUOTED_STRING, spe.getMessageCode());
assertEquals(0, spe.getPosition());
}
@@ -171,9 +176,10 @@ public class SpelParserTests {
SpelExpressionParser parser = new SpelExpressionParser();
parser.parseRaw("'abc");
fail();
- } catch (ParseException e) {
- assertTrue(e instanceof SpelParseException);
- SpelParseException spe = (SpelParseException) e;
+ }
+ catch (ParseException ex) {
+ assertTrue(ex instanceof SpelParseException);
+ SpelParseException spe = (SpelParseException) ex;
assertEquals(SpelMessage.NON_TERMINATING_QUOTED_STRING, spe.getMessageCode());
assertEquals(0, spe.getPosition());
}
@@ -274,7 +280,8 @@ public class SpelParserTests {
try {
new SpelExpressionParser().parseRaw("\"double quote: \\\"\\\".\"");
fail("Should have failed");
- } catch (SpelParseException spe) {
+ }
+ catch (SpelParseException spe) {
assertEquals(17, spe.getPosition());
assertEquals(SpelMessage.UNEXPECTED_ESCAPE_CHAR, spe.getMessageCode());
}
@@ -401,9 +408,10 @@ public class SpelParserTests {
Object o = expr.getValue();
assertEquals(value, o);
assertEquals(type, o.getClass());
- } catch (Exception e) {
- e.printStackTrace();
- fail(e.getMessage());
+ }
+ catch (Exception ex) {
+ ex.printStackTrace();
+ fail(ex.getMessage());
}
}
@@ -412,9 +420,10 @@ public class SpelParserTests {
SpelExpressionParser parser = new SpelExpressionParser();
parser.parseRaw(expression);
fail();
- } catch (ParseException e) {
- assertTrue(e instanceof SpelParseException);
- SpelParseException spe = (SpelParseException) e;
+ }
+ catch (ParseException ex) {
+ assertTrue(ex instanceof SpelParseException);
+ SpelParseException spe = (SpelParseException) ex;
assertEquals(expectedMessage, spe.getMessageCode());
}
}
diff --git a/spring-instrument-tomcat/src/main/java/org/springframework/instrument/classloading/tomcat/TomcatInstrumentableClassLoader.java b/spring-instrument-tomcat/src/main/java/org/springframework/instrument/classloading/tomcat/TomcatInstrumentableClassLoader.java
index 55452217..58723872 100644
--- a/spring-instrument-tomcat/src/main/java/org/springframework/instrument/classloading/tomcat/TomcatInstrumentableClassLoader.java
+++ b/spring-instrument-tomcat/src/main/java/org/springframework/instrument/classloading/tomcat/TomcatInstrumentableClassLoader.java
@@ -30,8 +30,8 @@ import org.springframework.instrument.classloading.WeavingTransformer;
* to loaded classes without the need to use a VM-wide agent.
*
* <p>To be registered using a
- * {@code <a href="http://tomcat.apache.org/tomcat-6.0-doc/config/loader.html">Loader</a>} tag
- * in Tomcat's {@code <a href="http://tomcat.apache.org/tomcat-6.0-doc/config/context.html">Context</a>}
+ * <a href="http://tomcat.apache.org/tomcat-6.0-doc/config/loader.html">{@code Loader}</a> tag
+ * in Tomcat's <a href="http://tomcat.apache.org/tomcat-6.0-doc/config/context.html">{@code Context}</a>
* definition in the {@code server.xml} file, with the Spring-provided "spring-instrument-tomcat.jar"
* file deployed into Tomcat's "lib" directory. The required configuration tag looks as follows:
*
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/config/DatabasePopulatorConfigUtils.java b/spring-jdbc/src/main/java/org/springframework/jdbc/config/DatabasePopulatorConfigUtils.java
index eca9a2fd..384cb0c8 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/config/DatabasePopulatorConfigUtils.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/config/DatabasePopulatorConfigUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,6 +32,7 @@ import org.springframework.util.xml.DomUtils;
/**
* @author Juergen Hoeller
+ * @author Stephane Nicoll
* @since 3.1
*/
class DatabasePopulatorConfigUtils {
@@ -70,8 +71,9 @@ class DatabasePopulatorConfigUtils {
if (StringUtils.hasLength(scriptElement.getAttribute("encoding"))) {
delegate.addPropertyValue("sqlScriptEncoding", new TypedStringValue(scriptElement.getAttribute("encoding")));
}
- if (StringUtils.hasLength(scriptElement.getAttribute("separator"))) {
- delegate.addPropertyValue("separator", new TypedStringValue(scriptElement.getAttribute("separator")));
+ String separator = getSeparator(element, scriptElement);
+ if (separator != null) {
+ delegate.addPropertyValue("separator", new TypedStringValue(separator));
}
delegates.add(delegate.getBeanDefinition());
}
@@ -80,4 +82,16 @@ class DatabasePopulatorConfigUtils {
return builder.getBeanDefinition();
}
+ private static String getSeparator(Element element, Element scriptElement) {
+ String scriptSeparator = scriptElement.getAttribute("separator");
+ if (StringUtils.hasLength(scriptSeparator)) {
+ return scriptSeparator;
+ }
+ String elementSeparator = element.getAttribute("separator");
+ if (StringUtils.hasLength(elementSeparator)) {
+ return elementSeparator;
+ }
+ return null;
+ }
+
}
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java
index 88baff51..fb280e86 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +34,8 @@ import org.springframework.beans.BeanWrapper;
import org.springframework.beans.NotWritablePropertyException;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.beans.TypeMismatchException;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.jdbc.support.JdbcUtils;
@@ -58,7 +60,7 @@ import org.springframework.util.StringUtils;
* <p>To facilitate mapping between columns and fields that don't have matching names,
* try using column aliases in the SQL statement like "select fname as first_name from customer".
*
- * <p>For 'null' values read from the databasem, we will attempt to call the setter, but in the case of
+ * <p>For 'null' values read from the database, we will attempt to call the setter, but in the case of
* Java primitives, this causes a TypeMismatchException. This class can be configured (using the
* primitivesDefaultedForNullValue property) to trap this exception and use the primitives default value.
* Be aware that if you use the values from the generated bean to update the database the primitive value
@@ -85,6 +87,9 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
/** Whether we're defaulting primitives when mapping a null value */
private boolean primitivesDefaultedForNullValue = false;
+ /** ConversionService for binding JDBC values to bean properties */
+ private ConversionService conversionService = new DefaultConversionService();
+
/** Map of the fields we provide mapping for */
private Map<String, PropertyDescriptor> mappedFields;
@@ -179,6 +184,27 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
return this.primitivesDefaultedForNullValue;
}
+ /**
+ * Set a {@link ConversionService} for binding JDBC values to bean properties,
+ * or {@code null} for none.
+ * <p>Default is a {@link DefaultConversionService}, as of Spring 4.3. This
+ * provides support for {@code java.time} conversion and other special types.
+ * @since 4.3
+ * @see #initBeanWrapper(BeanWrapper)
+ */
+ public void setConversionService(ConversionService conversionService) {
+ this.conversionService = conversionService;
+ }
+
+ /**
+ * Return a {@link ConversionService} for binding JDBC values to bean properties,
+ * or {@code null} if none.
+ * @since 4.3
+ */
+ public ConversionService getConversionService() {
+ return this.conversionService;
+ }
+
/**
* Initialize the mapping metadata for the given class.
@@ -248,7 +274,7 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
@Override
public T mapRow(ResultSet rs, int rowNumber) throws SQLException {
Assert.state(this.mappedClass != null, "Mapped class was not specified");
- T mappedObject = BeanUtils.instantiate(this.mappedClass);
+ T mappedObject = BeanUtils.instantiateClass(this.mappedClass);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(mappedObject);
initBeanWrapper(bw);
@@ -313,10 +339,17 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
/**
* Initialize the given BeanWrapper to be used for row mapping.
* To be called for each row.
- * <p>The default implementation is empty. Can be overridden in subclasses.
+ * <p>The default implementation applies the configured {@link ConversionService},
+ * if any. Can be overridden in subclasses.
* @param bw the BeanWrapper to initialize
+ * @see #getConversionService()
+ * @see BeanWrapper#setConversionService
*/
protected void initBeanWrapper(BeanWrapper bw) {
+ ConversionService cs = getConversionService();
+ if (cs != null) {
+ bw.setConversionService(cs);
+ }
}
/**
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 6d236c02..ec716dd5 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
@@ -219,12 +219,14 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
}
/**
- * Set the fetch size for this JdbcTemplate. This is important for processing
- * large result sets: Setting this higher than the default value will increase
- * processing speed at the cost of memory consumption; setting this lower can
- * avoid transferring row data that will never be read by the application.
- * <p>Default is -1, indicating to use the JDBC driver's default
- * (i.e. to not pass a specific fetch size setting on the driver).
+ * Set the fetch size for this JdbcTemplate. This is important for processing large
+ * result sets: Setting this higher than the default value will increase processing
+ * speed at the cost of memory consumption; setting this lower can avoid transferring
+ * row data that will never be read by the application.
+ * <p>Default is -1, indicating to use the JDBC driver's default configuration
+ * (i.e. to not pass a specific fetch size setting on to the driver).
+ * <p>Note: As of 4.3, negative values other than -1 will get passed on to the
+ * driver, since e.g. MySQL supports special behavior for {@code Integer.MIN_VALUE}.
* @see java.sql.Statement#setFetchSize
*/
public void setFetchSize(int fetchSize) {
@@ -239,13 +241,15 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
}
/**
- * Set the maximum number of rows for this JdbcTemplate. This is important
- * for processing subsets of large result sets, avoiding to read and hold
- * the entire result set in the database or in the JDBC driver if we're
- * never interested in the entire result in the first place (for example,
- * when performing searches that might return a large number of matches).
- * <p>Default is -1, indicating to use the JDBC driver's default
- * (i.e. to not pass a specific max rows setting on the driver).
+ * Set the maximum number of rows for this JdbcTemplate. This is important for
+ * processing subsets of large result sets, avoiding to read and hold the entire
+ * result set in the database or in the JDBC driver if we're never interested in
+ * the entire result in the first place (for example, when performing searches
+ * that might return a large number of matches).
+ * <p>Default is -1, indicating to use the JDBC driver's default configuration
+ * (i.e. to not pass a specific max rows setting on to the driver).
+ * <p>Note: As of 4.3, negative values other than -1 will get passed on to the
+ * driver, in sync with {@link #setFetchSize}'s support for special MySQL values.
* @see java.sql.Statement#setMaxRows
*/
public void setMaxRows(int maxRows) {
@@ -1349,11 +1353,11 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
*/
protected void applyStatementSettings(Statement stmt) throws SQLException {
int fetchSize = getFetchSize();
- if (fetchSize >= 0) {
+ if (fetchSize != -1) {
stmt.setFetchSize(fetchSize);
}
int maxRows = getMaxRows();
- if (maxRows >= 0) {
+ if (maxRows != -1) {
stmt.setMaxRows(maxRows);
}
DataSourceUtils.applyTimeout(stmt, getDataSource(), getQueryTimeout());
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/TableMetaDataContext.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/TableMetaDataContext.java
index 63ea39ec..0a2e6d63 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/TableMetaDataContext.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/TableMetaDataContext.java
@@ -47,13 +47,13 @@ public class TableMetaDataContext {
/** Logger available to subclasses */
protected final Log logger = LogFactory.getLog(getClass());
- /** name of procedure to call **/
+ /** name of table for this context **/
private String tableName;
- /** name of catalog for call **/
+ /** name of catalog for this context **/
private String catalogName;
- /** name of schema for call **/
+ /** name of schema for this context **/
private String schemaName;
/** List of columns objects to be used in this context */
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcInsert.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcInsert.java
index 3af9f463..3d7c25f1 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcInsert.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcInsert.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -542,6 +542,7 @@ public abstract class AbstractJdbcInsert {
* @param batch array of Maps with parameter names and values to be used in batch insert
* @return array of number of rows affected
*/
+ @SuppressWarnings("unchecked")
protected int[] doExecuteBatch(Map<String, ?>... batch) {
checkCompiled();
List<List<Object>> batchValues = new ArrayList<List<Object>>(batch.length);
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcInsert.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcInsert.java
index d178cc61..41742350 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcInsert.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcInsert.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,7 +32,7 @@ import org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor;
* name of the table and a Map containing the column names and the column values.
*
* <p>The meta data processing is based on the DatabaseMetaData provided by the
- * JDBC driver. As long as the JBDC driver can provide the names of the columns
+ * JDBC driver. As long as the JDBC driver can provide the names of the columns
* for a specified table than we can rely on this auto-detection feature. If that
* is not the case, then the column names must be specified explicitly.
*
@@ -148,6 +148,7 @@ public class SimpleJdbcInsert extends AbstractJdbcInsert implements SimpleJdbcIn
}
@Override
+ @SuppressWarnings("unchecked")
public int[] executeBatch(Map<String, ?>... batch) {
return doExecuteBatch(batch);
}
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcInsertOperations.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcInsertOperations.java
index 0e1dd15c..645a2a97 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcInsertOperations.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcInsertOperations.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -152,6 +152,7 @@ public interface SimpleJdbcInsertOperations {
* @param batch an array of Maps containing a batch of column names and corresponding value
* @return the array of number of rows affected as returned by the JDBC driver
*/
+ @SuppressWarnings("unchecked")
int[] executeBatch(Map<String, ?>... batch);
/**
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/AbstractDriverBasedDataSource.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/AbstractDriverBasedDataSource.java
index 0a2808fe..e5be5182 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/AbstractDriverBasedDataSource.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/AbstractDriverBasedDataSource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
+import org.springframework.lang.UsesJava7;
import org.springframework.util.Assert;
/**
@@ -39,6 +40,10 @@ public abstract class AbstractDriverBasedDataSource extends AbstractDataSource {
private String password;
+ private String catalog;
+
+ private String schema;
+
private Properties connectionProperties;
@@ -89,6 +94,40 @@ public abstract class AbstractDriverBasedDataSource extends AbstractDataSource {
}
/**
+ * Specify a database catalog to be applied to each Connection.
+ * @since 4.3.2
+ * @see Connection#setCatalog
+ */
+ public void setCatalog(String catalog) {
+ this.catalog = catalog;
+ }
+
+ /**
+ * Return the database catalog to be applied to each Connection, if any.
+ * @since 4.3.2
+ */
+ public String getCatalog() {
+ return this.catalog;
+ }
+
+ /**
+ * Specify a database schema to be applied to each Connection.
+ * @since 4.3.2
+ * @see Connection#setSchema
+ */
+ public void setSchema(String schema) {
+ this.schema = schema;
+ }
+
+ /**
+ * Return the database schema to be applied to each Connection, if any.
+ * @since 4.3.2
+ */
+ public String getSchema() {
+ return this.schema;
+ }
+
+ /**
* Specify arbitrary connection properties as key/value pairs,
* to be passed to the Driver.
* <p>Can also contain "user" and "password" properties. However,
@@ -140,6 +179,7 @@ public abstract class AbstractDriverBasedDataSource extends AbstractDataSource {
* @throws SQLException in case of failure
* @see java.sql.Driver#connect(String, java.util.Properties)
*/
+ @UsesJava7
protected Connection getConnectionFromDriver(String username, String password) throws SQLException {
Properties mergedProps = new Properties();
Properties connProps = getConnectionProperties();
@@ -152,7 +192,15 @@ public abstract class AbstractDriverBasedDataSource extends AbstractDataSource {
if (password != null) {
mergedProps.setProperty("password", password);
}
- return getConnectionFromDriver(mergedProps);
+
+ Connection con = getConnectionFromDriver(mergedProps);
+ if (this.catalog != null) {
+ con.setCatalog(this.catalog);
+ }
+ if (this.schema != null) {
+ con.setSchema(this.schema);
+ }
+ return con;
}
/**
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/ConnectionHandle.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/ConnectionHandle.java
index 2343aef4..7b361385 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/ConnectionHandle.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/ConnectionHandle.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author 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,7 +26,7 @@ import java.sql.Connection;
* @since 1.1
* @see SimpleConnectionHandle
* @see ConnectionHolder
- * @see org.springframework.orm.jdo.JpaDialect#getJdbcConnection
+ * @see org.springframework.orm.jpa.JpaDialect#getJdbcConnection
* @see org.springframework.orm.jdo.JdoDialect#getJdbcConnection
*/
public interface ConnectionHandle {
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/ConnectionHolder.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/ConnectionHolder.java
index 6ed77ef0..95447490 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/ConnectionHolder.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/ConnectionHolder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -161,9 +161,9 @@ public class ConnectionHolder extends ResourceHolderSupport {
*/
public boolean supportsSavepoints() throws SQLException {
if (this.savepointsSupported == null) {
- this.savepointsSupported = new Boolean(getConnection().getMetaData().supportsSavepoints());
+ this.savepointsSupported = getConnection().getMetaData().supportsSavepoints();
}
- return this.savepointsSupported.booleanValue();
+ return this.savepointsSupported;
}
/**
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/UserCredentialsDataSourceAdapter.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/UserCredentialsDataSourceAdapter.java
index cfcaca4d..14021771 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/UserCredentialsDataSourceAdapter.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/UserCredentialsDataSourceAdapter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ import java.sql.Connection;
import java.sql.SQLException;
import org.springframework.core.NamedThreadLocal;
+import org.springframework.lang.UsesJava7;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -65,6 +66,10 @@ public class UserCredentialsDataSourceAdapter extends DelegatingDataSource {
private String password;
+ private String catalog;
+
+ private String schema;
+
private final ThreadLocal<JdbcUserCredentials> threadBoundCredentials =
new NamedThreadLocal<JdbcUserCredentials>("Current JDBC user credentials");
@@ -93,6 +98,24 @@ public class UserCredentialsDataSourceAdapter extends DelegatingDataSource {
this.password = password;
}
+ /**
+ * Specify a database catalog to be applied to each retrieved Connection.
+ * @since 4.3.2
+ * @see Connection#setCatalog
+ */
+ public void setCatalog(String catalog) {
+ this.catalog = catalog;
+ }
+
+ /**
+ * Specify a database schema to be applied to each retrieved Connection.
+ * @since 4.3.2
+ * @see Connection#setSchema
+ */
+ public void setSchema(String schema) {
+ this.schema = schema;
+ }
+
/**
* Set user credententials for this proxy and the current thread.
@@ -126,14 +149,20 @@ public class UserCredentialsDataSourceAdapter extends DelegatingDataSource {
* determined credentials as parameters.
*/
@Override
+ @UsesJava7
public Connection getConnection() throws SQLException {
JdbcUserCredentials threadCredentials = this.threadBoundCredentials.get();
- if (threadCredentials != null) {
- return doGetConnection(threadCredentials.username, threadCredentials.password);
+ Connection con = (threadCredentials != null ?
+ doGetConnection(threadCredentials.username, threadCredentials.password) :
+ doGetConnection(this.username, this.password));
+
+ if (this.catalog != null) {
+ con.setCatalog(this.catalog);
}
- else {
- return doGetConnection(this.username, this.password);
+ if (this.schema != null) {
+ con.setSchema(this.schema);
}
+ return con;
}
/**
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/CompositeDatabasePopulator.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/CompositeDatabasePopulator.java
index b8924624..0ae78488 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/CompositeDatabasePopulator.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/CompositeDatabasePopulator.java
@@ -20,6 +20,7 @@ import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
/**
@@ -29,6 +30,7 @@ import java.util.List;
* @author Dave Syer
* @author Juergen Hoeller
* @author Sam Brannen
+ * @author Kazuki Shimizu
* @since 3.1
*/
public class CompositeDatabasePopulator implements DatabasePopulator {
@@ -37,6 +39,33 @@ public class CompositeDatabasePopulator implements DatabasePopulator {
/**
+ * Create an empty {@code CompositeDatabasePopulator}.
+ * @see #setPopulators
+ * @see #addPopulators
+ */
+ public CompositeDatabasePopulator() {
+ }
+
+ /**
+ * Create a {@code CompositeDatabasePopulator} with the given populators.
+ * @param populators one or more populators to delegate to
+ * @since 4.3
+ */
+ public CompositeDatabasePopulator(Collection<DatabasePopulator> populators) {
+ this.populators.addAll(populators);
+ }
+
+ /**
+ * Create a {@code CompositeDatabasePopulator} with the given populators.
+ * @param populators one or more populators to delegate to
+ * @since 4.3
+ */
+ public CompositeDatabasePopulator(DatabasePopulator... populators) {
+ this.populators.addAll(Arrays.asList(populators));
+ }
+
+
+ /**
* Specify one or more populators to delegate to.
*/
public void setPopulators(DatabasePopulator... populators) {
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/object/GenericSqlQuery.java b/spring-jdbc/src/main/java/org/springframework/jdbc/object/GenericSqlQuery.java
index d2af6281..dbbfd5c1 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/object/GenericSqlQuery.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/object/GenericSqlQuery.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,43 +18,57 @@ package org.springframework.jdbc.object;
import java.util.Map;
-import org.springframework.dao.InvalidDataAccessResourceUsageException;
+import org.springframework.beans.BeanUtils;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.util.Assert;
+/**
+ * A concrete variant of {@link SqlQuery} which can be configured
+ * with a {@link RowMapper}.
+ *
+ * @author Thomas Risberg
+ * @author Juergen Hoeller
+ * @since 3.0
+ * @see #setRowMapper
+ * @see #setRowMapperClass
+ */
public class GenericSqlQuery<T> extends SqlQuery<T> {
- Class<?> rowMapperClass;
+ private RowMapper<T> rowMapper;
+
+ @SuppressWarnings("rawtypes")
+ private Class<? extends RowMapper> rowMapperClass;
+
- RowMapper<?> rowMapper;
+ /**
+ * Set a specific {@link RowMapper} instance to use for this query.
+ * @since 4.3.2
+ */
+ public void setRowMapper(RowMapper<T> rowMapper) {
+ this.rowMapper = rowMapper;
+ }
+ /**
+ * Set a {@link RowMapper} class for this query, creating a fresh
+ * {@link RowMapper} instance per execution.
+ */
@SuppressWarnings("rawtypes")
- public void setRowMapperClass(Class<? extends RowMapper> rowMapperClass)
- throws IllegalAccessException, InstantiationException {
+ public void setRowMapperClass(Class<? extends RowMapper> rowMapperClass) {
this.rowMapperClass = rowMapperClass;
- if (!RowMapper.class.isAssignableFrom(rowMapperClass))
- throw new IllegalStateException("The specified class '" +
- rowMapperClass.getName() + " is not a sub class of " +
- "'org.springframework.jdbc.core.RowMapper'");
}
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
- Assert.notNull(rowMapperClass, "The 'rowMapperClass' property is required");
+ Assert.isTrue(this.rowMapper != null || this.rowMapperClass != null,
+ "'rowMapper' or 'rowMapperClass' is required");
}
+
@Override
@SuppressWarnings("unchecked")
protected RowMapper<T> newRowMapper(Object[] parameters, Map<?, ?> context) {
- try {
- return (RowMapper<T>) rowMapperClass.newInstance();
- }
- catch (InstantiationException e) {
- throw new InvalidDataAccessResourceUsageException("Unable to instantiate RowMapper", e);
- }
- catch (IllegalAccessException e) {
- throw new InvalidDataAccessResourceUsageException("Unable to instantiate RowMapper", e);
- }
+ return (this.rowMapper != null ? this.rowMapper : BeanUtils.instantiateClass(this.rowMapperClass));
}
+
}
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodes.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodes.java
index 7a9c1399..826b9db6 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodes.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodes.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -192,7 +192,7 @@ public class SQLErrorCodes {
try {
this.customSqlExceptionTranslator = customTranslatorClass.newInstance();
}
- catch (Exception ex) {
+ catch (Throwable ex) {
throw new IllegalStateException("Unable to instantiate custom translator", ex);
}
}
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/DefaultLobHandler.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/DefaultLobHandler.java
index 57aad967..b63c4e2b 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/DefaultLobHandler.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/DefaultLobHandler.java
@@ -207,7 +207,6 @@ public class DefaultLobHandler extends AbstractLobHandler {
}
@Override
- @SuppressWarnings("resource")
public LobCreator getLobCreator() {
return (this.createTemporaryLob ? new TemporaryLobCreator() : new DefaultLobCreator());
}
diff --git a/spring-jdbc/src/main/resources/META-INF/spring.schemas b/spring-jdbc/src/main/resources/META-INF/spring.schemas
index 44835b63..d0396fe1 100644
--- a/spring-jdbc/src/main/resources/META-INF/spring.schemas
+++ b/spring-jdbc/src/main/resources/META-INF/spring.schemas
@@ -4,4 +4,5 @@ http\://www.springframework.org/schema/jdbc/spring-jdbc-3.2.xsd=org/springframew
http\://www.springframework.org/schema/jdbc/spring-jdbc-4.0.xsd=org/springframework/jdbc/config/spring-jdbc-4.0.xsd
http\://www.springframework.org/schema/jdbc/spring-jdbc-4.1.xsd=org/springframework/jdbc/config/spring-jdbc-4.1.xsd
http\://www.springframework.org/schema/jdbc/spring-jdbc-4.2.xsd=org/springframework/jdbc/config/spring-jdbc-4.2.xsd
-http\://www.springframework.org/schema/jdbc/spring-jdbc.xsd=org/springframework/jdbc/config/spring-jdbc-4.2.xsd
+http\://www.springframework.org/schema/jdbc/spring-jdbc-4.3.xsd=org/springframework/jdbc/config/spring-jdbc-4.3.xsd
+http\://www.springframework.org/schema/jdbc/spring-jdbc.xsd=org/springframework/jdbc/config/spring-jdbc-4.3.xsd
diff --git a/spring-jdbc/src/main/resources/org/springframework/jdbc/config/spring-jdbc-4.3.xsd b/spring-jdbc/src/main/resources/org/springframework/jdbc/config/spring-jdbc-4.3.xsd
new file mode 100644
index 00000000..ab76e90f
--- /dev/null
+++ b/spring-jdbc/src/main/resources/org/springframework/jdbc/config/spring-jdbc-4.3.xsd
@@ -0,0 +1,223 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<xsd:schema xmlns="http://www.springframework.org/schema/jdbc"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xmlns:tool="http://www.springframework.org/schema/tool"
+ targetNamespace="http://www.springframework.org/schema/jdbc"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified">
+
+ <xsd:import namespace="http://www.springframework.org/schema/beans" schemaLocation="http://www.springframework.org/schema/beans/spring-beans-4.3.xsd"/>
+ <xsd:import namespace="http://www.springframework.org/schema/tool" schemaLocation="http://www.springframework.org/schema/tool/spring-tool-4.3.xsd"/>
+
+ <xsd:element name="embedded-database">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactoryBean"><![CDATA[
+ Creates an embedded database instance and makes it available to other beans as a javax.sql.DataSource.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation>
+ <tool:exports type="javax.sql.DataSource"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="beans:identifiedType">
+ <xsd:sequence>
+ <xsd:element name="script" type="scriptType" minOccurs="0" maxOccurs="unbounded">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ An SQL script to execute to populate, initialize, or clean up an embedded database.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="database-name" type="xsd:string" default="">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The name to assign to the embedded database. Note that this is not the
+ bean name but rather the name of the embedded database as used in the JDBC
+ connection URL for the database. Defaults to "testdb" if an explicit bean
+ 'id' has not been provided.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="generate-name" type="xsd:string" use="optional" default="false">
+ <xsd:annotation>
+ <xsd:documentation>
+ If set to "true", a pseudo-random unique name will be generated for the embedded
+ database, overriding any implicit name provided via the 'id' attribute or any
+ explicit name provided via the 'database-name' attribute.
+ Note that this is not the bean name but rather the name of the embedded database
+ as used in the JDBC connection URL for the database.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="separator" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The default statement separator to use (the default is to use ';' if it is present
+ in the script, or '\n' otherwise).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="type" type="databaseType" default="HSQL">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The type of embedded database to create, such as HSQL, H2 or Derby. Defaults to HSQL.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="initialize-database">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.jdbc.datasource.init.DataSourceInitializer"><![CDATA[
+ Initializes a database instance with SQL scripts provided in nested <script/> elements.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="script" type="scriptType" minOccurs="1" maxOccurs="unbounded">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ An SQL script to execute to populate, initialize, or clean up a database.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="data-source" type="xsd:string" default="dataSource">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A reference to a data source that should be initialized. Defaults to "dataSource".
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref"/>
+ <tool:expected-type type="javax.sql.DataSource"/>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="enabled" type="xsd:string" use="optional" default="true">
+ <xsd:annotation>
+ <xsd:documentation>
+ Is this bean "enabled", meaning the scripts will be executed?
+ Defaults to true but can be used to switch on and off script execution
+ depending on the environment.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="ignore-failures" use="optional" default="NONE">
+ <xsd:annotation>
+ <xsd:documentation>
+ Should failed SQL statements be ignored during execution?
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="NONE">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Do not ignore failures (the default)
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="DROPS">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Ignore failed DROP statements
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="ALL">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Ignore all failures
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="separator" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The default statement separator to use (the default is to use ';' if it is present
+ in the script, or '\n' otherwise).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:complexType name="scriptType">
+ <xsd:attribute name="location" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The resource location of an SQL script to execute. Can be a single script location
+ or a pattern (e.g. classpath:/com/foo/sql/*-data.sql).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="encoding" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The encoding for SQL scripts, if different from the platform encoding.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="separator" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The statement separator in the script (the default is to use ';' if it is present
+ in the script, or '\n' otherwise).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="execution">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Indicate the execution phase of this script. Use INIT to execute on startup (as a
+ bean initialization) or DESTROY to execute on shutdown (as a bean destruction callback).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="INIT"/>
+ <xsd:enumeration value="DESTROY"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:complexType>
+
+ <xsd:simpleType name="databaseType">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="HSQL">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ HyperSQL DataBase Engine
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="H2">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ H2 Database Engine
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="DERBY">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Apache Derby Database Engine
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+</xsd:schema>
diff --git a/spring-jdbc/src/main/resources/org/springframework/jdbc/support/sql-error-codes.xml b/spring-jdbc/src/main/resources/org/springframework/jdbc/support/sql-error-codes.xml
index 3fe34efe..c0278d59 100644
--- a/spring-jdbc/src/main/resources/org/springframework/jdbc/support/sql-error-codes.xml
+++ b/spring-jdbc/src/main/resources/org/springframework/jdbc/support/sql-error-codes.xml
@@ -250,8 +250,11 @@
<!-- http://help.sap.com/saphelp_hanaplatform/helpdata/en/20/a78d3275191014b41bae7c4a46d835/content.htm -->
<bean id="Hana" class="org.springframework.jdbc.support.SQLErrorCodes">
- <property name="databaseProductName">
- <value>SAP DB</value>
+ <property name="databaseProductNames">
+ <list>
+ <value>SAP DB</value>
+ <value>HDB</value>
+ </list>
</property>
<property name="badSqlGrammarCodes">
<value>
diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/config/JdbcNamespaceIntegrationTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/config/JdbcNamespaceIntegrationTests.java
index 6615c885..d15f94b4 100644
--- a/spring-jdbc/src/test/java/org/springframework/jdbc/config/JdbcNamespaceIntegrationTests.java
+++ b/spring-jdbc/src/test/java/org/springframework/jdbc/config/JdbcNamespaceIntegrationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -48,6 +48,7 @@ import static org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFacto
* @author Juergen Hoeller
* @author Chris Beams
* @author Sam Brannen
+ * @author Stephane Nicoll
*/
public class JdbcNamespaceIntegrationTests {
@@ -165,6 +166,16 @@ public class JdbcNamespaceIntegrationTests {
assertBeanPropertyValueOf("databaseName", "secondDataSource", factory);
}
+ @Test
+ public void initializeWithCustomSeparator() throws Exception {
+ assertCorrectSetupAndCloseContext("jdbc-initialize-custom-separator.xml", 2, "dataSource");
+ }
+
+ @Test
+ public void embeddedWithCustomSeparator() throws Exception {
+ assertCorrectSetupAndCloseContext("jdbc-config-custom-separator.xml", 2, "dataSource");
+ }
+
private ClassPathXmlApplicationContext context(String file) {
return new ClassPathXmlApplicationContext(file, getClass());
}
diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateQueryTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateQueryTests.java
index bbcc5657..f8fc598b 100644
--- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateQueryTests.java
+++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateQueryTests.java
@@ -150,7 +150,8 @@ public class JdbcTemplateQueryTests {
this.thrown.expect(IncorrectResultSizeDataAccessException.class);
try {
this.template.queryForObject(sql, String.class);
- } finally {
+ }
+ finally {
verify(this.resultSet).close();
verify(this.statement).close();
}
diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/DataSourceJtaTransactionTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/DataSourceJtaTransactionTests.java
index c5247749..3cda75ea 100644
--- a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/DataSourceJtaTransactionTests.java
+++ b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/DataSourceJtaTransactionTests.java
@@ -292,13 +292,14 @@ public class DataSourceJtaTransactionTests {
assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive());
verify(userTransaction, times(6)).begin();
verify(transactionManager, times(5)).resume(transaction);
- if(rollback) {
+ if (rollback) {
verify(userTransaction, times(5)).commit();
verify(userTransaction).rollback();
- } else {
+ }
+ else {
verify(userTransaction, times(6)).commit();
}
- if(accessAfterResume && !openOuterConnection) {
+ if (accessAfterResume && !openOuterConnection) {
verify(connection, times(7)).close();
}
else {
@@ -528,7 +529,7 @@ public class DataSourceJtaTransactionTests {
assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive());
verify(userTransaction).begin();
- if(suspendException) {
+ if (suspendException) {
verify(userTransaction).rollback();
}
diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/DataSourceTransactionManagerTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/DataSourceTransactionManagerTests.java
index e4ecd2fc..211445a1 100644
--- a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/DataSourceTransactionManagerTests.java
+++ b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/DataSourceTransactionManagerTests.java
@@ -152,7 +152,7 @@ public class DataSourceTransactionManagerTests {
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(dsToUse));
assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
- if(autoCommit && (!lazyConnection || createStatement)) {
+ if (autoCommit && (!lazyConnection || createStatement)) {
InOrder ordered = inOrder(con);
ordered.verify(con).setAutoCommit(false);
ordered.verify(con).commit();
@@ -244,7 +244,7 @@ public class DataSourceTransactionManagerTests {
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
- if(autoCommit && (!lazyConnection || createStatement)) {
+ if (autoCommit && (!lazyConnection || createStatement)) {
InOrder ordered = inOrder(con);
ordered.verify(con).setAutoCommit(false);
ordered.verify(con).rollback();
diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/CompositeDatabasePopulatorTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/CompositeDatabasePopulatorTests.java
new file mode 100644
index 00000000..d3c3b7f7
--- /dev/null
+++ b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/CompositeDatabasePopulatorTests.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.datasource.init;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.junit.Test;
+
+import static org.mockito.Mockito.*;
+
+/**
+ * Unit tests for {@link CompositeDatabasePopulator}.
+ *
+ * @author Kazuki Shimizu
+ * @author Juergen Hoeller
+ * @since 4.3
+ */
+public class CompositeDatabasePopulatorTests {
+
+ private final Connection mockedConnection = mock(Connection.class);
+
+ private final DatabasePopulator mockedDatabasePopulator1 = mock(DatabasePopulator.class);
+
+ private final DatabasePopulator mockedDatabasePopulator2 = mock(DatabasePopulator.class);
+
+
+ @Test
+ public void addPopulators() throws SQLException {
+ CompositeDatabasePopulator populator = new CompositeDatabasePopulator();
+ populator.addPopulators(mockedDatabasePopulator1, mockedDatabasePopulator2);
+ populator.populate(mockedConnection);
+ verify(mockedDatabasePopulator1,times(1)).populate(mockedConnection);
+ verify(mockedDatabasePopulator2, times(1)).populate(mockedConnection);
+ }
+
+ @Test
+ public void setPopulatorsWithMultiple() throws SQLException {
+ CompositeDatabasePopulator populator = new CompositeDatabasePopulator();
+ populator.setPopulators(mockedDatabasePopulator1, mockedDatabasePopulator2); // multiple
+ populator.populate(mockedConnection);
+ verify(mockedDatabasePopulator1, times(1)).populate(mockedConnection);
+ verify(mockedDatabasePopulator2, times(1)).populate(mockedConnection);
+ }
+
+ @Test
+ public void setPopulatorsForOverride() throws SQLException {
+ CompositeDatabasePopulator populator = new CompositeDatabasePopulator();
+ populator.setPopulators(mockedDatabasePopulator1);
+ populator.setPopulators(mockedDatabasePopulator2); // override
+ populator.populate(mockedConnection);
+ verify(mockedDatabasePopulator1, times(0)).populate(mockedConnection);
+ verify(mockedDatabasePopulator2, times(1)).populate(mockedConnection);
+ }
+
+ @Test
+ public void constructWithVarargs() throws SQLException {
+ CompositeDatabasePopulator populator =
+ new CompositeDatabasePopulator(mockedDatabasePopulator1, mockedDatabasePopulator2);
+ populator.populate(mockedConnection);
+ verify(mockedDatabasePopulator1, times(1)).populate(mockedConnection);
+ verify(mockedDatabasePopulator2, times(1)).populate(mockedConnection);
+ }
+
+ @Test
+ public void constructWithCollection() throws SQLException {
+ Set<DatabasePopulator> populators = new LinkedHashSet<>();
+ populators.add(mockedDatabasePopulator1);
+ populators.add(mockedDatabasePopulator2);
+ CompositeDatabasePopulator populator = new CompositeDatabasePopulator(populators);
+ populator.populate(mockedConnection);
+ verify(mockedDatabasePopulator1, times(1)).populate(mockedConnection);
+ verify(mockedDatabasePopulator2, times(1)).populate(mockedConnection);
+ }
+
+}
diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/lookup/BeanFactoryDataSourceLookupTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/lookup/BeanFactoryDataSourceLookupTests.java
index a1b19afa..3cef22b8 100644
--- a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/lookup/BeanFactoryDataSourceLookupTests.java
+++ b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/lookup/BeanFactoryDataSourceLookupTests.java
@@ -63,10 +63,11 @@ public class BeanFactoryDataSourceLookupTests {
BeanFactoryDataSourceLookup lookup = new BeanFactoryDataSourceLookup(beanFactory);
lookup.getDataSource(DATASOURCE_BEAN_NAME);
fail("should have thrown DataSourceLookupFailureException");
- } catch (DataSourceLookupFailureException ex) { /* expected */ }
+ }
+ catch (DataSourceLookupFailureException ex) { /* expected */ }
}
- @Test(expected=IllegalStateException.class)
+ @Test(expected = IllegalStateException.class)
public void testLookupWhereBeanFactoryHasNotBeenSupplied() throws Exception {
BeanFactoryDataSourceLookup lookup = new BeanFactoryDataSourceLookup();
lookup.getDataSource(DATASOURCE_BEAN_NAME);
diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/object/GenericSqlQueryTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/object/GenericSqlQueryTests.java
index ed32b628..5a90e703 100644
--- a/spring-jdbc/src/test/java/org/springframework/jdbc/object/GenericSqlQueryTests.java
+++ b/spring-jdbc/src/test/java/org/springframework/jdbc/object/GenericSqlQueryTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@
package org.springframework.jdbc.object;
-
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
@@ -43,11 +42,12 @@ import static org.mockito.BDDMockito.*;
/**
* @author Thomas Risberg
+ * @author Juergen Hoeller
*/
public class GenericSqlQueryTests {
private static final String SELECT_ID_FORENAME_NAMED_PARAMETERS_PARSED =
- "select id, forename from custmr where id = ? and country = ?";
+ "select id, forename from custmr where id = ? and country = ?";
private BeanFactory beanFactory;
@@ -57,6 +57,7 @@ public class GenericSqlQueryTests {
private ResultSet resultSet;
+
@Before
public void setUp() throws Exception {
this.beanFactory = new DefaultListableBeanFactory();
@@ -72,17 +73,23 @@ public class GenericSqlQueryTests {
}
@Test
- public void testPlaceHoldersCustomerQuery() throws SQLException {
- SqlQuery<?> query = (SqlQuery<?>) beanFactory.getBean("queryWithPlaceHolders");
+ public void testCustomerQueryWithPlaceholders() throws SQLException {
+ SqlQuery<?> query = (SqlQuery<?>) beanFactory.getBean("queryWithPlaceholders");
doTestCustomerQuery(query, false);
}
@Test
- public void testNamedParameterCustomerQuery() throws SQLException {
+ public void testCustomerQueryWithNamedParameters() throws SQLException {
SqlQuery<?> query = (SqlQuery<?>) beanFactory.getBean("queryWithNamedParameters");
doTestCustomerQuery(query, true);
}
+ @Test
+ public void testCustomerQueryWithRowMapperInstance() throws SQLException {
+ SqlQuery<?> query = (SqlQuery<?>) beanFactory.getBean("queryWithRowMapperBean");
+ doTestCustomerQuery(query, true);
+ }
+
private void doTestCustomerQuery(SqlQuery<?> query, boolean namedParameters) throws SQLException {
given(resultSet.next()).willReturn(true);
given(resultSet.getInt("id")).willReturn(1);
diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/object/StoredProcedureTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/object/StoredProcedureTests.java
index 73427778..4c40aa99 100644
--- a/spring-jdbc/src/test/java/org/springframework/jdbc/object/StoredProcedureTests.java
+++ b/spring-jdbc/src/test/java/org/springframework/jdbc/object/StoredProcedureTests.java
@@ -81,7 +81,7 @@ public class StoredProcedureTests {
@After
public void verifyClosed() throws Exception {
- if(verifyClosedAfter) {
+ if (verifyClosedAfter) {
verify(callableStatement).close();
verify(connection, atLeastOnce()).close();
}
@@ -189,7 +189,7 @@ public class StoredProcedureTests {
TestJdbcTemplate t = new TestJdbcTemplate();
t.setDataSource(dataSource);
// Will fail without the following, because we're not able to get a connection
- // from the DataSource here if we need to to create an ExceptionTranslator
+ // from the DataSource here if we need to create an ExceptionTranslator
t.setExceptionTranslator(new SQLStateSQLExceptionTranslator());
StoredProcedureConfiguredViaJdbcTemplate sp = new StoredProcedureConfiguredViaJdbcTemplate(t);
diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/support/rowset/ResultSetWrappingRowSetTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/support/rowset/ResultSetWrappingRowSetTests.java
index 1637764d..0b048d08 100644
--- a/spring-jdbc/src/test/java/org/springframework/jdbc/support/rowset/ResultSetWrappingRowSetTests.java
+++ b/spring-jdbc/src/test/java/org/springframework/jdbc/support/rowset/ResultSetWrappingRowSetTests.java
@@ -205,7 +205,8 @@ public class ResultSetWrappingRowSetTests {
if (arg instanceof String) {
given(rset.findColumn((String) arg)).willReturn(1);
given(rsetMethod.invoke(rset, 1)).willReturn(ret).willThrow(new SQLException("test"));
- } else {
+ }
+ else {
given(rsetMethod.invoke(rset, arg)).willReturn(ret).willThrow(new SQLException("test"));
}
rowsetMethod.invoke(rowset, arg);
diff --git a/spring-jdbc/src/test/resources/org/springframework/jdbc/config/jdbc-config-custom-separator.xml b/spring-jdbc/src/test/resources/org/springframework/jdbc/config/jdbc-config-custom-separator.xml
new file mode 100644
index 00000000..7f9479e3
--- /dev/null
+++ b/spring-jdbc/src/test/resources/org/springframework/jdbc/config/jdbc-config-custom-separator.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:jdbc="http://www.springframework.org/schema/jdbc"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.3.xsd">
+
+ <jdbc:embedded-database id="dataSource" type="HSQL" separator="@@">
+ <jdbc:script location="classpath:org/springframework/jdbc/config/db-schema.sql" separator=";"/>
+ <jdbc:script location="classpath:org/springframework/jdbc/config/db-test-data-endings.sql"/>
+ </jdbc:embedded-database>
+
+</beans>
diff --git a/spring-jdbc/src/test/resources/org/springframework/jdbc/config/jdbc-initialize-custom-separator.xml b/spring-jdbc/src/test/resources/org/springframework/jdbc/config/jdbc-initialize-custom-separator.xml
new file mode 100644
index 00000000..8a2ea731
--- /dev/null
+++ b/spring-jdbc/src/test/resources/org/springframework/jdbc/config/jdbc-initialize-custom-separator.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:jdbc="http://www.springframework.org/schema/jdbc"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.3.xsd">
+
+ <jdbc:embedded-database id="dataSource" type="HSQL"/>
+
+ <jdbc:initialize-database data-source="dataSource" separator="@@">
+ <jdbc:script location="classpath:org/springframework/jdbc/config/db-schema.sql" separator=";" encoding="ISO-8859-1"/>
+ <jdbc:script location="classpath:org/springframework/jdbc/config/db-test-data-endings.sql"/>
+ </jdbc:initialize-database>
+
+</beans>
diff --git a/spring-jdbc/src/test/resources/org/springframework/jdbc/object/GenericSqlQueryTests-context.xml b/spring-jdbc/src/test/resources/org/springframework/jdbc/object/GenericSqlQueryTests-context.xml
index 908653e3..71950206 100644
--- a/spring-jdbc/src/test/resources/org/springframework/jdbc/object/GenericSqlQueryTests-context.xml
+++ b/spring-jdbc/src/test/resources/org/springframework/jdbc/object/GenericSqlQueryTests-context.xml
@@ -6,7 +6,7 @@
<bean id="dataSource" class="org.springframework.jdbc.datasource.TestDataSourceWrapper"/>
- <bean id="queryWithPlaceHolders" class="org.springframework.jdbc.object.GenericSqlQuery">
+ <bean id="queryWithPlaceholders" class="org.springframework.jdbc.object.GenericSqlQuery">
<property name="dataSource" ref="dataSource"/>
<property name="sql" value="select id, forename from custmr where id = ? and country = ?"/>
<property name="parameters">
@@ -50,4 +50,28 @@
<property name="rowMapperClass" value="org.springframework.jdbc.object.CustomerMapper"/>
</bean>
+ <bean id="queryWithRowMapperBean" class="org.springframework.jdbc.object.GenericSqlQuery">
+ <property name="dataSource" ref="dataSource"/>
+ <property name="sql" value="select id, forename from custmr where id = :id and country = :country"/>
+ <property name="parameters">
+ <list>
+ <bean class="org.springframework.jdbc.core.SqlParameter">
+ <constructor-arg index="0" value="id"/>
+ <constructor-arg index="1">
+ <util:constant static-field="java.sql.Types.INTEGER"/>
+ </constructor-arg>
+ </bean>
+ <bean class="org.springframework.jdbc.core.SqlParameter">
+ <constructor-arg index="0" value="country"/>
+ <constructor-arg index="1">
+ <util:constant static-field="java.sql.Types.VARCHAR"/>
+ </constructor-arg>
+ </bean>
+ </list>
+ </property>
+ <property name="rowMapper">
+ <bean class="org.springframework.jdbc.object.CustomerMapper"/>
+ </property>
+ </bean>
+
</beans> \ No newline at end of file
diff --git a/spring-jms/src/main/java/org/springframework/jms/UncategorizedJmsException.java b/spring-jms/src/main/java/org/springframework/jms/UncategorizedJmsException.java
index b9531906..e55ba0d6 100644
--- a/spring-jms/src/main/java/org/springframework/jms/UncategorizedJmsException.java
+++ b/spring-jms/src/main/java/org/springframework/jms/UncategorizedJmsException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -51,7 +51,7 @@ public class UncategorizedJmsException extends JmsException {
* but can also be a JNDI NamingException or the like.
*/
public UncategorizedJmsException(Throwable cause) {
- super("Uncategorized exception occured during JMS processing", cause);
+ super("Uncategorized exception occurred during JMS processing", cause);
}
}
diff --git a/spring-jms/src/main/java/org/springframework/jms/annotation/EnableJms.java b/spring-jms/src/main/java/org/springframework/jms/annotation/EnableJms.java
index a8767010..0d908450 100644
--- a/spring-jms/src/main/java/org/springframework/jms/annotation/EnableJms.java
+++ b/spring-jms/src/main/java/org/springframework/jms/annotation/EnableJms.java
@@ -108,7 +108,7 @@ import org.springframework.context.annotation.Import;
* <p>Annotated methods can use flexible signature; in particular, it is possible to use
* the {@link org.springframework.messaging.Message Message} abstraction and related annotations,
* see {@link JmsListener} Javadoc for more details. For instance, the following would
- * inject the content of the message and a a custom "myCounter" JMS header:
+ * inject the content of the message and a custom "myCounter" JMS header:
*
* <pre class="code">
* &#064;JmsListener(containerFactory = "myJmsListenerContainerFactory", destination="myQueue")
diff --git a/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListener.java b/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListener.java
index bf62c493..78329881 100644
--- a/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListener.java
+++ b/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -66,6 +66,9 @@ import org.springframework.messaging.handler.annotation.MessageMapping;
* {@link org.springframework.messaging.handler.annotation.SendTo @SendTo} to the
* method declaration.
*
+ * <p>This annotation may be used as a <em>meta-annotation</em> to create custom
+ * <em>composed annotations</em> with attribute overrides.
+ *
* @author Stephane Nicoll
* @since 4.1
* @see EnableJms
@@ -111,7 +114,8 @@ public @interface JmsListener {
String selector() default "";
/**
- * The concurrency limits for the listener, if any.
+ * The concurrency limits for the listener, if any. Overrides the value defined
+ * by the container factory used to create the listener container.
* <p>The concurrency limits can be a "lower-upper" String &mdash; for example,
* "5-10" &mdash; or a simple upper limit String &mdash; for example, "10", in
* which case the lower limit will be 1.
diff --git a/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListenerAnnotationBeanPostProcessor.java b/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListenerAnnotationBeanPostProcessor.java
index 10b35b7e..dad78130 100644
--- a/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListenerAnnotationBeanPostProcessor.java
+++ b/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListenerAnnotationBeanPostProcessor.java
@@ -36,9 +36,10 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.beans.factory.config.EmbeddedValueResolver;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.Ordered;
-import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.jms.config.JmsListenerConfigUtils;
import org.springframework.jms.config.JmsListenerContainerFactory;
import org.springframework.jms.config.JmsListenerEndpointRegistrar;
@@ -49,6 +50,7 @@ import org.springframework.messaging.handler.annotation.support.MessageHandlerMe
import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
+import org.springframework.util.StringValueResolver;
/**
* Bean post-processor that registers methods annotated with {@link JmsListener}
@@ -95,6 +97,8 @@ public class JmsListenerAnnotationBeanPostProcessor
private BeanFactory beanFactory;
+ private StringValueResolver embeddedValueResolver;
+
private final MessageHandlerMethodFactoryAdapter messageHandlerMethodFactory = new MessageHandlerMethodFactoryAdapter();
private final JmsListenerEndpointRegistrar registrar = new JmsListenerEndpointRegistrar();
@@ -146,6 +150,9 @@ public class JmsListenerAnnotationBeanPostProcessor
@Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
+ if (beanFactory instanceof ConfigurableBeanFactory) {
+ this.embeddedValueResolver = new EmbeddedValueResolver((ConfigurableBeanFactory) beanFactory);
+ }
}
@@ -198,7 +205,7 @@ public class JmsListenerAnnotationBeanPostProcessor
new MethodIntrospector.MetadataLookup<Set<JmsListener>>() {
@Override
public Set<JmsListener> inspect(Method method) {
- Set<JmsListener> listenerMethods = AnnotationUtils.getRepeatableAnnotations(
+ Set<JmsListener> listenerMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations(
method, JmsListener.class, JmsListeners.class);
return (!listenerMethods.isEmpty() ? listenerMethods : null);
}
@@ -236,13 +243,14 @@ public class JmsListenerAnnotationBeanPostProcessor
* @see JmsListenerEndpointRegistrar#registerEndpoint
*/
protected void processJmsListener(JmsListener jmsListener, Method mostSpecificMethod, Object bean) {
- Method invocableMethod = MethodIntrospector.selectInvocableMethod(mostSpecificMethod, bean.getClass());
+ Method invocableMethod = AopUtils.selectInvocableMethod(mostSpecificMethod, bean.getClass());
MethodJmsListenerEndpoint endpoint = createMethodJmsListenerEndpoint();
endpoint.setBean(bean);
endpoint.setMethod(invocableMethod);
endpoint.setMostSpecificMethod(mostSpecificMethod);
endpoint.setMessageHandlerMethodFactory(this.messageHandlerMethodFactory);
+ endpoint.setEmbeddedValueResolver(this.embeddedValueResolver);
endpoint.setBeanFactory(this.beanFactory);
endpoint.setId(getEndpointId(jmsListener));
endpoint.setDestination(resolve(jmsListener.destination()));
@@ -294,8 +302,7 @@ public class JmsListenerAnnotationBeanPostProcessor
}
private String resolve(String value) {
- return (this.beanFactory instanceof ConfigurableBeanFactory ?
- ((ConfigurableBeanFactory) this.beanFactory).resolveEmbeddedValue(value) : value);
+ return (this.embeddedValueResolver != null ? this.embeddedValueResolver.resolveStringValue(value) : value);
}
diff --git a/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListeners.java b/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListeners.java
index f3d5266e..255f6457 100644
--- a/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListeners.java
+++ b/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListeners.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +30,9 @@ import java.lang.annotation.Target;
* where {@link JmsListener} can simply be declared several times on the same method,
* implicitly generating this container annotation.
*
+ * <p>This annotation may be used as a <em>meta-annotation</em> to create custom
+ * <em>composed annotations</em>.
+ *
* @author Stephane Nicoll
* @since 4.2
* @see JmsListener
diff --git a/spring-jms/src/main/java/org/springframework/jms/config/JcaListenerContainerParser.java b/spring-jms/src/main/java/org/springframework/jms/config/JcaListenerContainerParser.java
index ef03eda9..9f005d06 100644
--- a/spring-jms/src/main/java/org/springframework/jms/config/JcaListenerContainerParser.java
+++ b/spring-jms/src/main/java/org/springframework/jms/config/JcaListenerContainerParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -88,7 +88,7 @@ class JcaListenerContainerParser extends AbstractListenerContainerParser {
String prefetch = containerEle.getAttribute(PREFETCH_ATTRIBUTE);
if (StringUtils.hasText(prefetch)) {
- properties.add("prefetchSize", new Integer(prefetch));
+ properties.add("prefetchSize", Integer.valueOf(prefetch));
}
return properties;
diff --git a/spring-jms/src/main/java/org/springframework/jms/config/MethodJmsListenerEndpoint.java b/spring-jms/src/main/java/org/springframework/jms/config/MethodJmsListenerEndpoint.java
index 8bde24d0..b7379d20 100644
--- a/spring-jms/src/main/java/org/springframework/jms/config/MethodJmsListenerEndpoint.java
+++ b/spring-jms/src/main/java/org/springframework/jms/config/MethodJmsListenerEndpoint.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,8 +22,10 @@ import java.util.Arrays;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
-import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.beans.factory.config.EmbeddedValueResolver;
+import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.jms.listener.MessageListenerContainer;
import org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter;
import org.springframework.jms.support.converter.MessageConverter;
@@ -33,6 +35,7 @@ import org.springframework.messaging.handler.annotation.support.MessageHandlerMe
import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
+import org.springframework.util.StringValueResolver;
/**
* A {@link JmsListenerEndpoint} providing the method to invoke to process
@@ -42,7 +45,7 @@ import org.springframework.util.StringUtils;
* @author Juergen Hoeller
* @since 4.1
*/
-public class MethodJmsListenerEndpoint extends AbstractJmsListenerEndpoint {
+public class MethodJmsListenerEndpoint extends AbstractJmsListenerEndpoint implements BeanFactoryAware {
private Object bean;
@@ -52,6 +55,8 @@ public class MethodJmsListenerEndpoint extends AbstractJmsListenerEndpoint {
private MessageHandlerMethodFactory messageHandlerMethodFactory;
+ private StringValueResolver embeddedValueResolver;
+
private BeanFactory beanFactory;
@@ -110,12 +115,24 @@ public class MethodJmsListenerEndpoint extends AbstractJmsListenerEndpoint {
}
/**
- * Set the {@link BeanFactory} to use to resolve expressions (can be null).
+ * Set a value resolver for embedded placeholders and expressions.
*/
+ public void setEmbeddedValueResolver(StringValueResolver embeddedValueResolver) {
+ this.embeddedValueResolver = embeddedValueResolver;
+ }
+
+ /**
+ * Set the {@link BeanFactory} to use to resolve expressions (can be {@code null}).
+ */
+ @Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
+ if (this.embeddedValueResolver == null && beanFactory instanceof ConfigurableBeanFactory) {
+ this.embeddedValueResolver = new EmbeddedValueResolver((ConfigurableBeanFactory) beanFactory);
+ }
}
+
@Override
protected MessagingMessageListenerAdapter createMessageListener(MessageListenerContainer container) {
Assert.state(this.messageHandlerMethodFactory != null,
@@ -157,7 +174,7 @@ public class MethodJmsListenerEndpoint extends AbstractJmsListenerEndpoint {
*/
protected String getDefaultResponseDestination() {
Method specificMethod = getMostSpecificMethod();
- SendTo ann = AnnotationUtils.getAnnotation(specificMethod, SendTo.class);
+ SendTo ann = getSendTo(specificMethod);
if (ann != null) {
Object[] destinations = ann.value();
if (destinations.length != 1) {
@@ -169,15 +186,16 @@ public class MethodJmsListenerEndpoint extends AbstractJmsListenerEndpoint {
return null;
}
- /**
- * Resolve the specified value if possible.
- * @see ConfigurableBeanFactory#resolveEmbeddedValue
- */
- private String resolve(String value) {
- if (this.beanFactory instanceof ConfigurableBeanFactory) {
- return ((ConfigurableBeanFactory) this.beanFactory).resolveEmbeddedValue(value);
+ private SendTo getSendTo(Method specificMethod) {
+ SendTo ann = AnnotatedElementUtils.findMergedAnnotation(specificMethod, SendTo.class);
+ if (ann == null) {
+ ann = AnnotatedElementUtils.findMergedAnnotation(specificMethod.getDeclaringClass(), SendTo.class);
}
- return value;
+ return ann;
+ }
+
+ private String resolve(String value) {
+ return (this.embeddedValueResolver != null ? this.embeddedValueResolver.resolveStringValue(value) : value);
}
diff --git a/spring-jms/src/main/java/org/springframework/jms/connection/CachingConnectionFactory.java b/spring-jms/src/main/java/org/springframework/jms/connection/CachingConnectionFactory.java
index f3ec7bee..14eb99ff 100644
--- a/spring-jms/src/main/java/org/springframework/jms/connection/CachingConnectionFactory.java
+++ b/spring-jms/src/main/java/org/springframework/jms/connection/CachingConnectionFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -509,7 +509,7 @@ public class CachingConnectionFactory extends SingleConnectionFactory {
* Simple wrapper class around a Destination reference.
* Used as the cache key when caching MessageProducer objects.
*/
- private static class DestinationCacheKey {
+ private static class DestinationCacheKey implements Comparable<DestinationCacheKey> {
private final Destination destination;
@@ -537,7 +537,7 @@ public class CachingConnectionFactory extends SingleConnectionFactory {
public boolean equals(Object other) {
// Effectively checking object equality as well as toString equality.
// On WebSphere MQ, Destination objects do not implement equals...
- return (other == this || destinationEquals((DestinationCacheKey) other));
+ return (this == other || destinationEquals((DestinationCacheKey) other));
}
@Override
@@ -547,6 +547,16 @@ public class CachingConnectionFactory extends SingleConnectionFactory {
// for equivalent destinations... Thanks a lot, WebSphere MQ!
return this.destination.getClass().hashCode();
}
+
+ @Override
+ public String toString() {
+ return getDestinationString();
+ }
+
+ @Override
+ public int compareTo(DestinationCacheKey other) {
+ return getDestinationString().compareTo(other.getDestinationString());
+ }
}
@@ -584,6 +594,12 @@ public class CachingConnectionFactory extends SingleConnectionFactory {
ObjectUtils.nullSafeEquals(this.subscription, otherKey.subscription) &&
this.durable == otherKey.durable);
}
+
+ @Override
+ public String toString() {
+ return super.toString() + " [selector=" + this.selector + ", noLocal=" + this.noLocal +
+ ", subscription=" + this.subscription + ", durable=" + this.durable + "]";
+ }
}
}
diff --git a/spring-jms/src/main/java/org/springframework/jms/connection/SessionProxy.java b/spring-jms/src/main/java/org/springframework/jms/connection/SessionProxy.java
index bf439aee..e866b9ff 100644
--- a/spring-jms/src/main/java/org/springframework/jms/connection/SessionProxy.java
+++ b/spring-jms/src/main/java/org/springframework/jms/connection/SessionProxy.java
@@ -20,7 +20,7 @@ import javax.jms.Session;
/**
* Subinterface of {@link javax.jms.Session} to be implemented by
- * Session proxies. Allows access to the the underlying target Session.
+ * Session proxies. Allows access to the underlying target Session.
*
* @author Juergen Hoeller
* @since 2.0.4
diff --git a/spring-jms/src/main/java/org/springframework/jms/connection/TransactionAwareConnectionFactoryProxy.java b/spring-jms/src/main/java/org/springframework/jms/connection/TransactionAwareConnectionFactoryProxy.java
index 50c61409..19a9ffc0 100644
--- a/spring-jms/src/main/java/org/springframework/jms/connection/TransactionAwareConnectionFactoryProxy.java
+++ b/spring-jms/src/main/java/org/springframework/jms/connection/TransactionAwareConnectionFactoryProxy.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -101,7 +101,7 @@ public class TransactionAwareConnectionFactoryProxy
* Set the target ConnectionFactory that this ConnectionFactory should delegate to.
*/
public final void setTargetConnectionFactory(ConnectionFactory targetConnectionFactory) {
- Assert.notNull(targetConnectionFactory, "targetConnectionFactory must not be nul");
+ Assert.notNull(targetConnectionFactory, "'targetConnectionFactory' must not be null");
this.targetConnectionFactory = targetConnectionFactory;
}
diff --git a/spring-jms/src/main/java/org/springframework/jms/core/JmsTemplate.java b/spring-jms/src/main/java/org/springframework/jms/core/JmsTemplate.java
index 4407c000..d20fb208 100644
--- a/spring-jms/src/main/java/org/springframework/jms/core/JmsTemplate.java
+++ b/spring-jms/src/main/java/org/springframework/jms/core/JmsTemplate.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -89,18 +89,6 @@ import org.springframework.util.ReflectionUtils;
*/
public class JmsTemplate extends JmsDestinationAccessor implements JmsOperations {
- /**
- * Timeout value indicating that a receive operation should
- * check if a message is immediately available without blocking.
- */
- public static final long RECEIVE_TIMEOUT_NO_WAIT = -1;
-
- /**
- * Timeout value indicating a blocking receive without timeout.
- */
- public static final long RECEIVE_TIMEOUT_INDEFINITE_WAIT = 0;
-
-
/** The JMS 2.0 MessageProducer.setDeliveryDelay method, if available */
private static final Method setDeliveryDelayMethod =
ClassUtils.getMethodIfAvailable(MessageProducer.class, "setDeliveryDelay", long.class);
@@ -315,11 +303,13 @@ public class JmsTemplate extends JmsDestinationAccessor implements JmsOperations
* Set the timeout to use for receive calls (in milliseconds).
* <p>The default is {@link #RECEIVE_TIMEOUT_INDEFINITE_WAIT}, which indicates
* a blocking receive without timeout.
- * <p>Specify {@link #RECEIVE_TIMEOUT_NO_WAIT} to inidicate that a receive operation
- * should check if a message is immediately available without blocking.
+ * <p>Specify {@link #RECEIVE_TIMEOUT_NO_WAIT} (or any other negative value)
+ * to indicate that a receive operation should check if a message is
+ * immediately available without blocking.
+ * @see #receiveFromConsumer(MessageConsumer, long)
* @see javax.jms.MessageConsumer#receive(long)
- * @see javax.jms.MessageConsumer#receive()
* @see javax.jms.MessageConsumer#receiveNoWait()
+ * @see javax.jms.MessageConsumer#receive()
*/
public void setReceiveTimeout(long receiveTimeout) {
this.receiveTimeout = receiveTimeout;
@@ -800,7 +790,7 @@ public class JmsTemplate extends JmsDestinationAccessor implements JmsOperations
if (resourceHolder != null && resourceHolder.hasTimeout()) {
timeout = Math.min(timeout, resourceHolder.getTimeToLiveInMillis());
}
- Message message = doReceive(consumer, timeout);
+ Message message = receiveFromConsumer(consumer, timeout);
if (session.getTransacted()) {
// Commit necessary - but avoid commit call within a JTA transaction.
if (isSessionLocallyTransacted(session)) {
@@ -821,25 +811,6 @@ public class JmsTemplate extends JmsDestinationAccessor implements JmsOperations
}
}
- /**
- * Actually receive a message from the given consumer.
- * @param consumer the JMS MessageConsumer to receive with
- * @param timeout the receive timeout
- * @return the JMS Message received, or {@code null} if none
- * @throws JMSException if thrown by JMS API methods
- */
- private Message doReceive(MessageConsumer consumer, long timeout) throws JMSException {
- if (timeout == RECEIVE_TIMEOUT_NO_WAIT) {
- return consumer.receiveNoWait();
- }
- else if (timeout > 0) {
- return consumer.receive(timeout);
- }
- else {
- return consumer.receive();
- }
- }
-
//---------------------------------------------------------------------------------------
// Convenience methods for receiving auto-converted messages
@@ -952,7 +923,7 @@ public class JmsTemplate extends JmsDestinationAccessor implements JmsOperations
logger.debug("Sending created message: " + requestMessage);
}
doSend(producer, requestMessage);
- return doReceive(consumer, getReceiveTimeout());
+ return receiveFromConsumer(consumer, getReceiveTimeout());
}
finally {
JmsUtils.closeMessageConsumer(consumer);
diff --git a/spring-jms/src/main/java/org/springframework/jms/listener/AbstractPollingMessageListenerContainer.java b/spring-jms/src/main/java/org/springframework/jms/listener/AbstractPollingMessageListenerContainer.java
index 4c1a6011..555cd6b8 100644
--- a/spring-jms/src/main/java/org/springframework/jms/listener/AbstractPollingMessageListenerContainer.java
+++ b/spring-jms/src/main/java/org/springframework/jms/listener/AbstractPollingMessageListenerContainer.java
@@ -158,9 +158,13 @@ public abstract class AbstractPollingMessageListenerContainer extends AbstractMe
* The default is 1000 ms, that is, 1 second.
* <p><b>NOTE:</b> This value needs to be smaller than the transaction
* timeout used by the transaction manager (in the appropriate unit,
- * of course). -1 indicates no timeout at all; however, this is only
- * feasible if not running within a transaction manager.
+ * of course). 0 indicates no timeout at all; however, this is only
+ * feasible if not running within a transaction manager and generally
+ * discouraged since such a listener container cannot cleanly shut down.
+ * A negative value such as -1 indicates a no-wait receive operation.
+ * @see #receiveFromConsumer(MessageConsumer, long)
* @see javax.jms.MessageConsumer#receive(long)
+ * @see javax.jms.MessageConsumer#receiveNoWait()
* @see javax.jms.MessageConsumer#receive()
* @see #setTransactionTimeout
*/
@@ -417,7 +421,7 @@ public abstract class AbstractPollingMessageListenerContainer extends AbstractMe
* @throws JMSException if thrown by JMS methods
*/
protected Message receiveMessage(MessageConsumer consumer) throws JMSException {
- return (this.receiveTimeout < 0 ? consumer.receive() : consumer.receive(this.receiveTimeout));
+ return receiveFromConsumer(consumer, getReceiveTimeout());
}
/**
diff --git a/spring-jms/src/main/java/org/springframework/jms/listener/DefaultMessageListenerContainer.java b/spring-jms/src/main/java/org/springframework/jms/listener/DefaultMessageListenerContainer.java
index 5fe49eb4..f9f6479f 100644
--- a/spring-jms/src/main/java/org/springframework/jms/listener/DefaultMessageListenerContainer.java
+++ b/spring-jms/src/main/java/org/springframework/jms/listener/DefaultMessageListenerContainer.java
@@ -995,7 +995,9 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe
}
else {
try {
- Thread.sleep(interval);
+ synchronized (this.lifecycleMonitor) {
+ this.lifecycleMonitor.wait(interval);
+ }
}
catch (InterruptedException interEx) {
// Re-interrupt current thread, to allow other threads to react.
@@ -1063,9 +1065,9 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe
catch (Throwable ex) {
clearResources();
if (!this.lastMessageSucceeded) {
- // We failed more than once in a row or on startup - sleep before
- // first recovery attempt.
- sleepBeforeRecoveryAttempt();
+ // We failed more than once in a row or on startup -
+ // wait before first recovery attempt.
+ waitBeforeRecoveryAttempt();
}
this.lastMessageSucceeded = false;
boolean alreadyRecovered = false;
@@ -1220,11 +1222,11 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe
/**
* Apply the back-off time once. In a regular scenario, the back-off is only applied if we
- * failed to recover with the broker. This additional sleep period avoids a burst retry
+ * failed to recover with the broker. This additional wait period avoids a burst retry
* scenario when the broker is actually up but something else if failing (i.e. listener
* specific).
*/
- private void sleepBeforeRecoveryAttempt() {
+ private void waitBeforeRecoveryAttempt() {
BackOffExecution execution = DefaultMessageListenerContainer.this.backOff.start();
applyBackOffTime(execution);
}
diff --git a/spring-jms/src/main/java/org/springframework/jms/listener/adapter/AbstractAdaptableMessageListener.java b/spring-jms/src/main/java/org/springframework/jms/listener/adapter/AbstractAdaptableMessageListener.java
index 10ae1cf8..2f53f57e 100644
--- a/spring-jms/src/main/java/org/springframework/jms/listener/adapter/AbstractAdaptableMessageListener.java
+++ b/spring-jms/src/main/java/org/springframework/jms/listener/adapter/AbstractAdaptableMessageListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,8 +36,10 @@ import org.springframework.jms.support.converter.MessageConversionException;
import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.jms.support.converter.MessagingMessageConverter;
import org.springframework.jms.support.converter.SimpleMessageConverter;
+import org.springframework.jms.support.converter.SmartMessageConverter;
import org.springframework.jms.support.destination.DestinationResolver;
import org.springframework.jms.support.destination.DynamicDestinationResolver;
+import org.springframework.messaging.MessageHeaders;
import org.springframework.util.Assert;
/**
@@ -269,14 +271,17 @@ public abstract class AbstractAdaptableMessageListener
* @see #setMessageConverter
*/
protected Message buildMessage(Session session, Object result) throws JMSException {
- Object content = (result instanceof JmsResponse ? ((JmsResponse<?>) result).getResponse() : result);
- if (content instanceof org.springframework.messaging.Message) {
- return this.messagingMessageConverter.toMessage(content, session);
- }
+ Object content = preProcessResponse(result instanceof JmsResponse
+ ? ((JmsResponse<?>) result).getResponse() : result);
MessageConverter converter = getMessageConverter();
if (converter != null) {
- return converter.toMessage(content, session);
+ if (content instanceof org.springframework.messaging.Message) {
+ return this.messagingMessageConverter.toMessage(content, session);
+ }
+ else {
+ return converter.toMessage(content, session);
+ }
}
if (!(content instanceof Message)) {
@@ -287,6 +292,17 @@ public abstract class AbstractAdaptableMessageListener
}
/**
+ * Pre-process the given result before it is converted to a {@link Message}.
+ * @param result the result of the invocation
+ * @return the payload response to handle, either the {@code result} argument
+ * or any other object (for instance wrapping the result).
+ * @since 4.3
+ */
+ protected Object preProcessResponse(Object result) {
+ return result;
+ }
+
+ /**
* Post-process the given response message before it will be sent.
* <p>The default implementation sets the response's correlation id
* to the request message's correlation id, if any; otherwise to the
@@ -402,11 +418,21 @@ public abstract class AbstractAdaptableMessageListener
/**
- * Delegates payload extraction to {@link #extractMessage(javax.jms.Message)} to
- * enforce backward compatibility.
+ * A {@link MessagingMessageConverter} that lazily invoke payload extraction and
+ * delegate it to {@link #extractMessage(javax.jms.Message)} in order to enforce
+ * backward compatibility.
*/
private class MessagingMessageConverterAdapter extends MessagingMessageConverter {
+ @SuppressWarnings("unchecked")
+ @Override
+ public Object fromMessage(javax.jms.Message message) throws JMSException, MessageConversionException {
+ if (message == null) {
+ return null;
+ }
+ return new LazyResolutionMessage(message);
+ }
+
@Override
protected Object extractPayload(Message message) throws JMSException {
Object payload = extractMessage(message);
@@ -425,13 +451,69 @@ public abstract class AbstractAdaptableMessageListener
}
@Override
- protected Message createMessageForPayload(Object payload, Session session) throws JMSException {
+ protected Message createMessageForPayload(Object payload, Session session, Object conversionHint)
+ throws JMSException {
MessageConverter converter = getMessageConverter();
- if (converter != null) {
- return converter.toMessage(payload, session);
+ if (converter == null) {
+ throw new IllegalStateException("No message converter, cannot handle '" + payload + "'");
}
- throw new IllegalStateException("No message converter - cannot handle [" + payload + "]");
+ if (converter instanceof SmartMessageConverter) {
+ return ((SmartMessageConverter) converter).toMessage(payload, session, conversionHint);
+
+ }
+ return converter.toMessage(payload, session);
}
+
+ protected class LazyResolutionMessage implements org.springframework.messaging.Message<Object> {
+
+ private final javax.jms.Message message;
+
+ private Object payload;
+
+ private MessageHeaders headers;
+
+ public LazyResolutionMessage(javax.jms.Message message) {
+ this.message = message;
+ }
+
+ @Override
+ public Object getPayload() {
+ if (this.payload == null) {
+ try {
+ this.payload = unwrapPayload();
+ }
+ catch (JMSException ex) {
+ throw new MessageConversionException(
+ "Failed to extract payload from [" + this.message + "]", ex);
+ }
+ }
+ //
+ return this.payload;
+ }
+
+ /**
+ * Extract the payload of the current message. Since we deferred the resolution
+ * of the payload, a custom converter may still return a full message for it. In
+ * this case, its payload is returned.
+ * @return the payload of the message
+ */
+ private Object unwrapPayload() throws JMSException {
+ Object payload = extractPayload(this.message);
+ if (payload instanceof org.springframework.messaging.Message) {
+ return ((org.springframework.messaging.Message) payload).getPayload();
+ }
+ return payload;
+ }
+
+ @Override
+ public MessageHeaders getHeaders() {
+ if (this.headers == null) {
+ this.headers = extractHeaders(this.message);
+ }
+ return this.headers;
+ }
+ }
+
}
diff --git a/spring-jms/src/main/java/org/springframework/jms/listener/adapter/MessagingMessageListenerAdapter.java b/spring-jms/src/main/java/org/springframework/jms/listener/adapter/MessagingMessageListenerAdapter.java
index 0791f4c4..b87dd6b2 100644
--- a/spring-jms/src/main/java/org/springframework/jms/listener/adapter/MessagingMessageListenerAdapter.java
+++ b/spring-jms/src/main/java/org/springframework/jms/listener/adapter/MessagingMessageListenerAdapter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,11 +19,14 @@ package org.springframework.jms.listener.adapter;
import javax.jms.JMSException;
import javax.jms.Session;
+import org.springframework.core.MethodParameter;
import org.springframework.jms.support.JmsHeaderMapper;
import org.springframework.jms.support.converter.MessageConversionException;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException;
+import org.springframework.messaging.core.AbstractMessageSendingTemplate;
import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
+import org.springframework.messaging.support.MessageBuilder;
/**
* A {@link javax.jms.MessageListener} adapter that invokes a configurable
@@ -72,6 +75,17 @@ public class MessagingMessageListenerAdapter extends AbstractAdaptableMessageLis
}
}
+ @Override
+ protected Object preProcessResponse(Object result) {
+ MethodParameter returnType = this.handlerMethod.getReturnType();
+ if (result instanceof Message) {
+ return MessageBuilder.fromMessage((Message<?>) result)
+ .setHeader(AbstractMessageSendingTemplate.CONVERSION_HINT_HEADER, returnType).build();
+ }
+ return MessageBuilder.withPayload(result).setHeader(
+ AbstractMessageSendingTemplate.CONVERSION_HINT_HEADER, returnType).build();
+ }
+
protected Message<?> toMessagingMessage(javax.jms.Message jmsMessage) {
try {
return (Message<?>) getMessagingMessageConverter().fromMessage(jmsMessage);
diff --git a/spring-jms/src/main/java/org/springframework/jms/listener/endpoint/JmsMessageEndpointFactory.java b/spring-jms/src/main/java/org/springframework/jms/listener/endpoint/JmsMessageEndpointFactory.java
index fe448aa2..5beb8733 100644
--- a/spring-jms/src/main/java/org/springframework/jms/listener/endpoint/JmsMessageEndpointFactory.java
+++ b/spring-jms/src/main/java/org/springframework/jms/listener/endpoint/JmsMessageEndpointFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -61,7 +61,7 @@ public class JmsMessageEndpointFactory extends AbstractMessageEndpointFactory {
* Return the JMS MessageListener for this endpoint.
*/
protected MessageListener getMessageListener() {
- return messageListener;
+ return this.messageListener;
}
/**
diff --git a/spring-jms/src/main/java/org/springframework/jms/support/JmsAccessor.java b/spring-jms/src/main/java/org/springframework/jms/support/JmsAccessor.java
index 38538c73..05352cf1 100644
--- a/spring-jms/src/main/java/org/springframework/jms/support/JmsAccessor.java
+++ b/spring-jms/src/main/java/org/springframework/jms/support/JmsAccessor.java
@@ -125,7 +125,7 @@ public abstract class JmsAccessor implements InitializingBean {
* {@link Session} to send a message.
* <p>Default is {@link Session#AUTO_ACKNOWLEDGE}.
* <p>Vendor-specific extensions to the acknowledgment mode can be set here as well.
- * <p>Note that that inside an EJB, the parameters to the
+ * <p>Note that inside an EJB, the parameters to the
* {@code create(Queue/Topic)Session(boolean transacted, int acknowledgeMode)} method
* are not taken into account. Depending on the transaction context in the EJB,
* the container makes its own decisions on these values. See section 17.3.5
diff --git a/spring-jms/src/main/java/org/springframework/jms/support/converter/MappingJackson2MessageConverter.java b/spring-jms/src/main/java/org/springframework/jms/support/converter/MappingJackson2MessageConverter.java
index b64abf2f..5d9bc44b 100644
--- a/spring-jms/src/main/java/org/springframework/jms/support/converter/MappingJackson2MessageConverter.java
+++ b/spring-jms/src/main/java/org/springframework/jms/support/converter/MappingJackson2MessageConverter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,12 +29,15 @@ import javax.jms.Message;
import javax.jms.Session;
import javax.jms.TextMessage;
+import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
import org.springframework.beans.factory.BeanClassLoaderAware;
+import org.springframework.core.MethodParameter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -50,14 +53,15 @@ import org.springframework.util.ClassUtils;
* <li>{@link DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES} is disabled</li>
* </ul>
*
- * <p>Tested against Jackson 2.2; compatible with Jackson 2.0 and higher.
+ * <p>Compatible with Jackson 2.6 and higher, as of Spring 4.3.
*
* @author Mark Pollack
* @author Dave Syer
* @author Juergen Hoeller
+ * @author Stephane Nicoll
* @since 3.1.4
*/
-public class MappingJackson2MessageConverter implements MessageConverter, BeanClassLoaderAware {
+public class MappingJackson2MessageConverter implements SmartMessageConverter, BeanClassLoaderAware {
/**
* The default encoding used for writing to text messages: UTF-8.
@@ -190,6 +194,35 @@ public class MappingJackson2MessageConverter implements MessageConverter, BeanCl
}
@Override
+ public Message toMessage(Object object, Session session, Object conversionHint)
+ throws JMSException, MessageConversionException {
+
+ return toMessage(object, session, getSerializationView(conversionHint));
+ }
+
+ /**
+ * Convert a Java object to a JMS Message using the specified json view
+ * and the supplied session to create the message object.
+ * @param object the object to convert
+ * @param session the Session to use for creating a JMS Message
+ * @param jsonView the view to use to filter the content
+ * @return the JMS Message
+ * @throws javax.jms.JMSException if thrown by JMS API methods
+ * @throws MessageConversionException in case of conversion failure
+ * @since 4.3
+ */
+ public Message toMessage(Object object, Session session, Class<?> jsonView)
+ throws JMSException, MessageConversionException {
+
+ if (jsonView != null) {
+ return toMessage(object, session, this.objectMapper.writerWithView(jsonView));
+ }
+ else {
+ return toMessage(object, session, this.objectMapper.writer());
+ }
+ }
+
+ @Override
public Object fromMessage(Message message) throws JMSException, MessageConversionException {
try {
JavaType targetJavaType = getJavaTypeForMessage(message);
@@ -200,6 +233,29 @@ public class MappingJackson2MessageConverter implements MessageConverter, BeanCl
}
}
+ protected Message toMessage(Object object, Session session, ObjectWriter objectWriter)
+ throws JMSException, MessageConversionException {
+
+ Message message;
+ try {
+ switch (this.targetType) {
+ case TEXT:
+ message = mapToTextMessage(object, session, objectWriter);
+ break;
+ case BYTES:
+ message = mapToBytesMessage(object, session, objectWriter);
+ break;
+ default:
+ message = mapToMessage(object, session, objectWriter, this.targetType);
+ }
+ }
+ catch (IOException ex) {
+ throw new MessageConversionException("Could not map JSON object [" + object + "]", ex);
+ }
+ setTypeIdOnMessage(object, message);
+ return message;
+ }
+
/**
* Map the given object to a {@link TextMessage}.
@@ -210,12 +266,31 @@ public class MappingJackson2MessageConverter implements MessageConverter, BeanCl
* @throws JMSException if thrown by JMS methods
* @throws IOException in case of I/O errors
* @see Session#createBytesMessage
+ * @deprecated as of 4.3, use {@link #mapToTextMessage(Object, Session, ObjectWriter)}
*/
+ @Deprecated
protected TextMessage mapToTextMessage(Object object, Session session, ObjectMapper objectMapper)
throws JMSException, IOException {
+ return mapToTextMessage(object, session, objectMapper.writer());
+ }
+
+ /**
+ * Map the given object to a {@link TextMessage}.
+ * @param object the object to be mapped
+ * @param session current JMS session
+ * @param objectWriter the writer to use
+ * @return the resulting message
+ * @throws JMSException if thrown by JMS methods
+ * @throws IOException in case of I/O errors
+ * @see Session#createBytesMessage
+ * @since 4.3
+ */
+ protected TextMessage mapToTextMessage(Object object, Session session, ObjectWriter objectWriter)
+ throws JMSException, IOException {
+
StringWriter writer = new StringWriter();
- objectMapper.writeValue(writer, object);
+ objectWriter.writeValue(writer, object);
return session.createTextMessage(writer.toString());
}
@@ -228,13 +303,33 @@ public class MappingJackson2MessageConverter implements MessageConverter, BeanCl
* @throws JMSException if thrown by JMS methods
* @throws IOException in case of I/O errors
* @see Session#createBytesMessage
+ * @deprecated as of 4.3, use {@link #mapToBytesMessage(Object, Session, ObjectWriter)}
*/
+ @Deprecated
protected BytesMessage mapToBytesMessage(Object object, Session session, ObjectMapper objectMapper)
throws JMSException, IOException {
+ return mapToBytesMessage(object, session, objectMapper.writer());
+ }
+
+
+ /**
+ * Map the given object to a {@link BytesMessage}.
+ * @param object the object to be mapped
+ * @param session current JMS session
+ * @param objectWriter the writer to use
+ * @return the resulting message
+ * @throws JMSException if thrown by JMS methods
+ * @throws IOException in case of I/O errors
+ * @since 4.3
+ * @see Session#createBytesMessage
+ */
+ protected BytesMessage mapToBytesMessage(Object object, Session session, ObjectWriter objectWriter)
+ throws JMSException, IOException {
+
ByteArrayOutputStream bos = new ByteArrayOutputStream(1024);
OutputStreamWriter writer = new OutputStreamWriter(bos, this.encoding);
- objectMapper.writeValue(writer, object);
+ objectWriter.writeValue(writer, object);
BytesMessage message = session.createBytesMessage();
message.writeBytes(bos.toByteArray());
@@ -256,10 +351,31 @@ public class MappingJackson2MessageConverter implements MessageConverter, BeanCl
* @return the resulting message
* @throws JMSException if thrown by JMS methods
* @throws IOException in case of I/O errors
+ * @deprecated as of 4.3, use {@link #mapToMessage(Object, Session, ObjectWriter, MessageType)}
*/
+ @Deprecated
protected Message mapToMessage(Object object, Session session, ObjectMapper objectMapper, MessageType targetType)
throws JMSException, IOException {
+ return mapToMessage(object, session, objectMapper.writer(), targetType);
+ }
+
+ /**
+ * Template method that allows for custom message mapping.
+ * Invoked when {@link #setTargetType} is not {@link MessageType#TEXT} or
+ * {@link MessageType#BYTES}.
+ * <p>The default implementation throws an {@link IllegalArgumentException}.
+ * @param object the object to marshal
+ * @param session the JMS Session
+ * @param objectWriter the writer to use
+ * @param targetType the target message type (other than TEXT or BYTES)
+ * @return the resulting message
+ * @throws JMSException if thrown by JMS methods
+ * @throws IOException in case of I/O errors
+ */
+ protected Message mapToMessage(Object object, Session session, ObjectWriter objectWriter, MessageType targetType)
+ throws JMSException, IOException {
+
throw new IllegalArgumentException("Unsupported message type [" + targetType +
"]. MappingJackson2MessageConverter by default only supports TextMessages and BytesMessages.");
}
@@ -286,7 +402,6 @@ public class MappingJackson2MessageConverter implements MessageConverter, BeanCl
}
}
-
/**
* Convenience method to dispatch to converters for individual message types.
*/
@@ -391,4 +506,42 @@ public class MappingJackson2MessageConverter implements MessageConverter, BeanCl
}
}
+ /**
+ * Determine a Jackson serialization view based on the given conversion hint.
+ * @param conversionHint the conversion hint Object as passed into the
+ * converter for the current conversion attempt
+ * @return the serialization view class, or {@code null} if none
+ */
+ protected Class<?> getSerializationView(Object conversionHint) {
+ if (conversionHint instanceof MethodParameter) {
+ MethodParameter methodParam = (MethodParameter) conversionHint;
+ JsonView annotation = methodParam.getParameterAnnotation(JsonView.class);
+ if (annotation == null) {
+ annotation = methodParam.getMethodAnnotation(JsonView.class);
+ if (annotation == null) {
+ return null;
+ }
+ }
+ return extractViewClass(annotation, conversionHint);
+ }
+ else if (conversionHint instanceof JsonView) {
+ return extractViewClass((JsonView) conversionHint, conversionHint);
+ }
+ else if (conversionHint instanceof Class) {
+ return (Class) conversionHint;
+ }
+ else {
+ return null;
+ }
+ }
+
+ private Class<?> extractViewClass(JsonView annotation, Object conversionHint) {
+ Class<?>[] classes = annotation.value();
+ if (classes.length != 1) {
+ throw new IllegalArgumentException(
+ "@JsonView only supported for handler methods with exactly 1 class argument: " + conversionHint);
+ }
+ return classes[0];
+ }
+
}
diff --git a/spring-jms/src/main/java/org/springframework/jms/support/converter/MessagingMessageConverter.java b/spring-jms/src/main/java/org/springframework/jms/support/converter/MessagingMessageConverter.java
index a4db7431..fbd1d5ad 100644
--- a/spring-jms/src/main/java/org/springframework/jms/support/converter/MessagingMessageConverter.java
+++ b/spring-jms/src/main/java/org/springframework/jms/support/converter/MessagingMessageConverter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,6 +24,8 @@ import org.springframework.beans.factory.InitializingBean;
import org.springframework.jms.support.JmsHeaderMapper;
import org.springframework.jms.support.SimpleJmsHeaderMapper;
import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageHeaders;
+import org.springframework.messaging.core.AbstractMessagingTemplate;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.Assert;
@@ -93,8 +95,11 @@ public class MessagingMessageConverter implements MessageConverter, Initializing
Message.class.getName() + "] is handled by this converter");
}
Message<?> input = (Message<?>) object;
- javax.jms.Message reply = createMessageForPayload(input.getPayload(), session);
- this.headerMapper.fromHeaders(input.getHeaders(), reply);
+ MessageHeaders headers = input.getHeaders();
+ Object conversionHint = (headers != null ? headers.get(
+ AbstractMessagingTemplate.CONVERSION_HINT_HEADER) : null);
+ javax.jms.Message reply = createMessageForPayload(input.getPayload(), session, conversionHint);
+ this.headerMapper.fromHeaders(headers, reply);
return reply;
}
@@ -104,7 +109,7 @@ public class MessagingMessageConverter implements MessageConverter, Initializing
if (message == null) {
return null;
}
- Map<String, Object> mappedHeaders = this.headerMapper.toHeaders(message);
+ Map<String, Object> mappedHeaders = extractHeaders(message);
Object convertedObject = extractPayload(message);
MessageBuilder<Object> builder = (convertedObject instanceof org.springframework.messaging.Message) ?
MessageBuilder.fromMessage((org.springframework.messaging.Message<Object>) convertedObject) :
@@ -122,9 +127,29 @@ public class MessagingMessageConverter implements MessageConverter, Initializing
/**
* Create a JMS message for the specified payload.
* @see MessageConverter#toMessage(Object, Session)
+ * @deprecated as of 4.3, use {@link #createMessageForPayload(Object, Session, Object)}
*/
+ @Deprecated
protected javax.jms.Message createMessageForPayload(Object payload, Session session) throws JMSException {
return this.payloadConverter.toMessage(payload, session);
}
+ /**
+ * Create a JMS message for the specified payload and conversionHint.
+ * The conversion hint is an extra object passed to the {@link MessageConverter},
+ * e.g. the associated {@code MethodParameter} (may be {@code null}}.
+ * @see MessageConverter#toMessage(Object, Session)
+ * @since 4.3
+ */
+ @SuppressWarnings("deprecation")
+ protected javax.jms.Message createMessageForPayload(Object payload, Session session, Object conversionHint)
+ throws JMSException {
+
+ return createMessageForPayload(payload, session);
+ }
+
+ protected final MessageHeaders extractHeaders(javax.jms.Message message) {
+ return this.headerMapper.toHeaders(message);
+ }
+
}
diff --git a/spring-jms/src/main/java/org/springframework/jms/support/converter/SmartMessageConverter.java b/spring-jms/src/main/java/org/springframework/jms/support/converter/SmartMessageConverter.java
new file mode 100644
index 00000000..2fb5cd73
--- /dev/null
+++ b/spring-jms/src/main/java/org/springframework/jms/support/converter/SmartMessageConverter.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jms.support.converter;
+
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.Session;
+
+/**
+ * An extended {@link MessageConverter} SPI with conversion hint support.
+ *
+ * <p>In case of a conversion hint being provided, the framework will call
+ * the extended method if a converter implements this interface, instead
+ * of calling the regular {@code toMessage} variant.
+ *
+ * @author Stephane Nicoll
+ * @since 4.3
+ */
+public interface SmartMessageConverter extends MessageConverter {
+
+ /**
+ * A variant of {@link #toMessage(Object, Session)} which takes an extra conversion
+ * context as an argument, allowing to take e.g. annotations on a payload parameter
+ * into account.
+ * @param object the object to convert
+ * @param session the Session to use for creating a JMS Message
+ * @param conversionHint an extra object passed to the {@link MessageConverter},
+ * e.g. the associated {@code MethodParameter} (may be {@code null}}
+ * @return the JMS Message
+ * @throws javax.jms.JMSException if thrown by JMS API methods
+ * @throws MessageConversionException in case of conversion failure
+ * @see #toMessage(Object, Session)
+ */
+ Message toMessage(Object object, Session session, Object conversionHint)
+ throws JMSException, MessageConversionException;
+
+}
diff --git a/spring-jms/src/main/java/org/springframework/jms/support/destination/JmsDestinationAccessor.java b/spring-jms/src/main/java/org/springframework/jms/support/destination/JmsDestinationAccessor.java
index 46c0bd4c..0093f86f 100644
--- a/spring-jms/src/main/java/org/springframework/jms/support/destination/JmsDestinationAccessor.java
+++ b/spring-jms/src/main/java/org/springframework/jms/support/destination/JmsDestinationAccessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,8 @@ package org.springframework.jms.support.destination;
import javax.jms.Destination;
import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
import javax.jms.Session;
import org.springframework.jms.support.JmsAccessor;
@@ -38,6 +40,20 @@ import org.springframework.util.Assert;
*/
public abstract class JmsDestinationAccessor extends JmsAccessor {
+ /**
+ * Timeout value indicating that a receive operation should
+ * check if a message is immediately available without blocking.
+ * @since 4.3
+ */
+ public static final long RECEIVE_TIMEOUT_NO_WAIT = -1;
+
+ /**
+ * Timeout value indicating a blocking receive without timeout.
+ * @since 4.3
+ */
+ public static final long RECEIVE_TIMEOUT_INDEFINITE_WAIT = 0;
+
+
private DestinationResolver destinationResolver = new DynamicDestinationResolver();
private boolean pubSubDomain = false;
@@ -98,4 +114,27 @@ public abstract class JmsDestinationAccessor extends JmsAccessor {
return getDestinationResolver().resolveDestinationName(session, destinationName, isPubSubDomain());
}
+ /**
+ * Actually receive a message from the given consumer.
+ * @param consumer the JMS MessageConsumer to receive with
+ * @param timeout the receive timeout (a negative value indicates
+ * a no-wait receive; 0 indicates an indefinite wait attempt)
+ * @return the JMS Message received, or {@code null} if none
+ * @throws JMSException if thrown by JMS API methods
+ * @since 4.3
+ * @see #RECEIVE_TIMEOUT_NO_WAIT
+ * @see #RECEIVE_TIMEOUT_INDEFINITE_WAIT
+ */
+ protected Message receiveFromConsumer(MessageConsumer consumer, long timeout) throws JMSException {
+ if (timeout > 0) {
+ return consumer.receive(timeout);
+ }
+ else if (timeout < 0) {
+ return consumer.receiveNoWait();
+ }
+ else {
+ return consumer.receive();
+ }
+ }
+
}
diff --git a/spring-jms/src/main/resources/META-INF/spring.schemas b/spring-jms/src/main/resources/META-INF/spring.schemas
index a6ee238a..ffcc14f6 100644
--- a/spring-jms/src/main/resources/META-INF/spring.schemas
+++ b/spring-jms/src/main/resources/META-INF/spring.schemas
@@ -5,4 +5,5 @@ http\://www.springframework.org/schema/jms/spring-jms-3.2.xsd=org/springframewor
http\://www.springframework.org/schema/jms/spring-jms-4.0.xsd=org/springframework/jms/config/spring-jms-4.0.xsd
http\://www.springframework.org/schema/jms/spring-jms-4.1.xsd=org/springframework/jms/config/spring-jms-4.1.xsd
http\://www.springframework.org/schema/jms/spring-jms-4.2.xsd=org/springframework/jms/config/spring-jms-4.2.xsd
-http\://www.springframework.org/schema/jms/spring-jms.xsd=org/springframework/jms/config/spring-jms-4.2.xsd
+http\://www.springframework.org/schema/jms/spring-jms-4.3.xsd=org/springframework/jms/config/spring-jms-4.3.xsd
+http\://www.springframework.org/schema/jms/spring-jms.xsd=org/springframework/jms/config/spring-jms-4.3.xsd
diff --git a/spring-jms/src/main/resources/org/springframework/jms/config/spring-jms-4.3.xsd b/spring-jms/src/main/resources/org/springframework/jms/config/spring-jms-4.3.xsd
new file mode 100644
index 00000000..223c63bf
--- /dev/null
+++ b/spring-jms/src/main/resources/org/springframework/jms/config/spring-jms-4.3.xsd
@@ -0,0 +1,638 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<xsd:schema xmlns="http://www.springframework.org/schema/jms"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:tool="http://www.springframework.org/schema/tool"
+ targetNamespace="http://www.springframework.org/schema/jms"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified">
+
+ <xsd:import namespace="http://www.springframework.org/schema/tool" schemaLocation="http://www.springframework.org/schema/tool/spring-tool-4.3.xsd"/>
+
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Defines the configuration elements for the Spring Framework's JMS support.
+ Allows for configuring JMS listener containers in XML 'shortcut' style as
+ well as through annotation.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+
+ <xsd:element name="annotation-driven">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Enables the detection of @JmsListener annotation on any Spring-managed object. If
+ present, a message listener container will be created to receive the relevant
+ messages and invoke the annotated method accordingly.
+
+ See Javadoc for the org.springframework.jms.annotation.EnableJms annotation for
+ information on code-based alternatives to this XML element.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:attribute name="registry" type="xsd:string" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Specifies the org.springframework.jms.config.JmsListenerEndpointRegistry instance to
+ use to register annotated jms listener endpoints. If not provided, a default instance
+ will be used by default.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="org.springframework.jms.config.JmsListenerEndpointRegistry"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="container-factory" type="xsd:string" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Specifies the org.springframework.jms.config.JmsListenerContainerFactory instance to
+ use to create the container for a jms listener endpoint that does not define a specific
+ factory. This permits in practice to omit the "containerFactory" attribute of the JmsListener
+ annotation. This attribute is not required as each endpoint may define the factory to use and,
+ as a convenience, the JmsListenerContainerFactory with name 'jmsListenerContainerFactory' is
+ looked up by default.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="org.springframework.jms.config.JmsListenerContainerFactory"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="handler-method-factory" type="xsd:string" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Specifies a custom org.springframework.messaging.handler.annotation.support.MessageHandlerMethodFactory
+ instance to use to configure the message listener responsible to serve an endpoint detected by this
+ processor. By default, DefaultMessageHandlerMethodFactory is used and it can be configured
+ further to support additional method arguments or to customize conversion and validation
+ support. See org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory
+ Javadoc for more details.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="org.springframework.messaging.handler.annotation.support.MessageHandlerMethodFactory"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+
+
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="listener-container">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Each listener child element will be hosted by a container whose configuration
+ is determined by this parent element. This variant builds standard JMS
+ listener containers, operating against a specified JMS ConnectionFactory. When
+ a factory-id attribute is present, the configuration defined by this element is
+ exposed as a org.springframework.jms.config.JmsListenerContainerFactory. It is
+ therefore possible to only define this element without any child to just expose
+ a container factory.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation>
+ <tool:exports type="org.springframework.jms.listener.AbstractMessageListenerContainer"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="listener" type="listenerType" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ <xsd:attribute name="factory-id" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Expose the settings defined by this element as a org.springframework.jms.config.JmsListenerContainerFactory
+ so that they can be reused with other endpoints.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="container-type" default="default">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The type of this listener container: "default" or "simple", choosing
+ between DefaultMessageListenerContainer and SimpleMessageListenerContainer.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="default"/>
+ <xsd:enumeration value="simple"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="container-class" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A custom listener container implementation class as fully qualified class name.
+ Default is Spring's standard DefaultMessageListenerContainer or
+ SimpleMessageListenerContainer, according to the "container-type" attribute.
+ Note that a custom container class will typically be a subclass of either of
+ those two Spring-provided standard container classes: Nake sure that the
+ "container-type" attribute matches the actual base type that the custom class
+ derives from ("default" will usually be fine anyway, since most custom classes
+ will derive from DefaultMessageListenerContainer).
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation>
+ <tool:expected-type type="java.lang.Class"/>
+ <tool:assignable-to type="org.springframework.jms.listener.AbstractMessageListenerContainer"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="connection-factory" type="xsd:string" default="connectionFactory">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A reference to the JMS ConnectionFactory bean.
+ Default is "connectionFactory".
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="javax.jms.ConnectionFactory"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="task-executor" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A reference to a Spring TaskExecutor (or standard JDK 1.5 Executor) for executing
+ JMS listener invokers. Default is a SimpleAsyncTaskExecutor in case of a
+ DefaultMessageListenerContainer, using internally managed threads. For a
+ SimpleMessageListenerContainer, listeners will always get invoked within the
+ JMS provider's receive thread by default.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="java.util.concurrent.Executor"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="destination-resolver" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A reference to the DestinationResolver strategy for resolving destination names.
+ Default is a DynamicDestinationResolver, using the JMS provider's queue/topic
+ name resolution. Alternatively, specify a reference to a JndiDestinationResolver
+ (typically in a J2EE environment).
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="org.springframework.jms.support.destination.DestinationResolver"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="message-converter" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A reference to the MessageConverter strategy for converting JMS Messages to
+ listener method arguments. Default is a SimpleMessageConverter.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="org.springframework.jms.support.converter.MessageConverter"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="error-handler" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A reference to an ErrorHandler strategy for handling any uncaught Exceptions
+ that may occur during the execution of the MessageListener.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="org.springframework.util.ErrorHandler"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="destination-type" default="queue">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The JMS destination type for this listener: "queue", "topic", "durableTopic",
+ "sharedTopic", "sharedDurableTopic". This enables potentially the "pubSubDomain",
+ "subscriptionDurable" and "subscriptionShared" properties of the container. The
+ default is "queue" (i.e. disabling those 3 properties).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="queue"/>
+ <xsd:enumeration value="topic"/>
+ <xsd:enumeration value="durableTopic"/>
+ <xsd:enumeration value="sharedTopic"/>
+ <xsd:enumeration value="sharedDurableTopic"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="response-destination-type" default="queue">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The JMS destination type for responses: "queue", "topic". Default
+ is the value of the "destination-type" attribute.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="queue"/>
+ <xsd:enumeration value="topic"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="client-id" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The JMS client id for this listener container.
+ Needs to be specified when using subscriptions.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="cache" default="auto">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The cache level for JMS resources: "none", "connection", "session", "consumer"
+ or "auto". By default ("auto"), the cache level will effectively be "consumer",
+ unless an external transaction manager has been specified - in which case the
+ effective default will be "none" (assuming J2EE-style transaction management
+ where the given ConnectionFactory is an XA-aware pool).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="none"/>
+ <xsd:enumeration value="connection"/>
+ <xsd:enumeration value="session"/>
+ <xsd:enumeration value="consumer"/>
+ <xsd:enumeration value="auto"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="acknowledge" default="auto">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The native JMS acknowledge mode: "auto", "client", "dups-ok" or "transacted".
+ A value of "transacted" effectively activates a locally transacted Session;
+ as alternative, specify an external "transaction-manager" via the corresponding
+ attribute. Default is "auto".
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="auto"/>
+ <xsd:enumeration value="client"/>
+ <xsd:enumeration value="dups-ok"/>
+ <xsd:enumeration value="transacted"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="transaction-manager" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A reference to an external PlatformTransactionManager (typically an
+ XA-based transaction coordinator, e.g. Spring's JtaTransactionManager).
+ If not specified, native acknowledging will be used (see "acknowledge" attribute).
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="org.springframework.transaction.PlatformTransactionManager"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="concurrency" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The number of concurrent sessions/consumers to start for each listener.
+ Can either be a simple number indicating the maximum number (e.g. "5")
+ or a range indicating the lower as well as the upper limit (e.g. "3-5").
+ Note that a specified minimum is just a hint and might be ignored at runtime.
+ Default is 1; keep concurrency limited to 1 in case of a topic listener
+ or if message ordering is important; consider raising it for general queues.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="prefetch" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The maximum number of messages to load into a single session.
+ Note that raising this number might lead to starvation of concurrent consumers!
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="receive-timeout" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The timeout to use for receive calls (in milliseconds).
+ The default is 1000 ms (1 sec); -1 indicates no timeout at all.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="back-off" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Specify the BackOff instance to use to compute the interval between recovery
+ attempts. If the BackOff implementation returns "BackOffExecution#STOP", the listener
+ container will not further attempt to recover. The recovery-interval value is
+ ignored when this property is set. The default is a FixedBackOff with an
+ interval of 5000 ms, that is 5 seconds.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="org.springframework.util.backoff.BackOff"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="recovery-interval" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Specify the interval between recovery attempts, in milliseconds. Convenience
+ way to create a FixedBackOff with the specified interval. For more recovery
+ options, consider specifying a BackOff instance instead. The default is
+ 5000 ms, that is 5 seconds.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="phase" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The lifecycle phase within which this container should start and stop. The lower
+ the value the earlier this container will start and the later it will stop. The
+ default is Integer.MAX_VALUE meaning the container will start as late as possible
+ and stop as soon as possible.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="jca-listener-container">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Each listener child element will be hosted by a container whose configuration
+ is determined by this parent element. This variant builds standard JCA-based
+ listener containers, operating against a specified JCA ResourceAdapter
+ (which needs to be provided by the JMS message broker, e.g. ActiveMQ). When
+ a factory-id attribute is present, the configuration defined by this element is
+ exposed as a org.springframework.jms.config.JmsListenerContainerFactory. It is
+ therefore possible to only define this element without any child to just expose
+ a container factory.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation>
+ <tool:exports type="org.springframework.jms.listener.endpoint.JmsMessageEndpointManager"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="listener" type="listenerType" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ <xsd:attribute name="factory-id" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Expose the settings defined by this element as a org.springframework.jms.config.JmsListenerContainerFactory
+ so that they can be reused with other endpoints.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="resource-adapter" type="xsd:string" default="resourceAdapter">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A reference to the JCA ResourceAdapter bean for the JMS provider.
+ Default is "resourceAdapter".
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="javax.resource.spi.ResourceAdapter"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="activation-spec-factory" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A reference to the JmsActivationSpecFactory.
+ Default is to autodetect the JMS provider and its ActivationSpec class
+ (see DefaultJmsActivationSpecFactory).
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="org.springframework.jms.listener.endpoint.JmsActivationSpecFactory"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="destination-resolver" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A reference to the DestinationResolver strategy for resolving destination names.
+ Default is to pass in the destination name Strings into the JCA ActivationSpec as-is.
+ Alternatively, specify a reference to a JndiDestinationResolver (typically in a J2EE
+ environment, in particular if the server insists on receiving Destination objects).
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="org.springframework.jms.support.destination.DestinationResolver"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="message-converter" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A reference to the MessageConverter strategy for converting JMS Messages to
+ listener method arguments. Default is a SimpleMessageConverter.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="org.springframework.jms.support.converter.MessageConverter"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="destination-type" default="queue">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The JMS destination type for this listener: "queue", "topic" or "durableTopic".
+ Default is "queue".
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="queue"/>
+ <xsd:enumeration value="topic"/>
+ <xsd:enumeration value="durableTopic"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="response-destination-type" default="queue">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The JMS destination type for responses: "queue", "topic". Default
+ is the value of the "destination-type" attribute.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="queue"/>
+ <xsd:enumeration value="topic"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="client-id" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The JMS client id for this listener container.
+ Needs to be specified when using durable subscriptions.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="acknowledge" default="auto">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The native JMS acknowledge mode: "auto", "client", "dups-ok" or "transacted".
+ A value of "transacted" effectively activates a locally transacted Session;
+ as alternative, specify an external "transaction-manager" via the corresponding
+ attribute. Default is "auto".
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="auto"/>
+ <xsd:enumeration value="client"/>
+ <xsd:enumeration value="dups-ok"/>
+ <xsd:enumeration value="transacted"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="transaction-manager" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A reference to the Spring JtaTransactionManager or [javax.transaction.TransactionManager],
+ for kicking off an XA transaction for each incoming message.
+ If not specified, native acknowledging will be used (see "acknowledge" attribute).
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref"/>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="concurrency" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The number of concurrent sessions/consumers to start for each listener.
+ Can either be a simple number indicating the maximum number (e.g. "5")
+ or a range indicating the lower as well as the upper limit (e.g. "3-5").
+ Note that a specified minimum is just a hint and will typically be ignored
+ at runtime when using a JCA listener container. Default is 1.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="prefetch" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The maximum number of messages to load into a single session.
+ Note that raising this number might lead to starvation of concurrent consumers!
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="phase" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The lifecycle phase within which this container should start and stop. The lower
+ the value the earlier this container will start and the later it will stop. The
+ default is Integer.MAX_VALUE meaning the container will start as late as possible
+ and stop as soon as possible.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:complexType name="listenerType">
+ <xsd:attribute name="id" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The unique identifier for a listener.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="destination" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The destination name for this listener, resolved through the
+ container-wide DestinationResolver strategy (if any). Required.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="subscription" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The name for the durable subscription, if any.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="selector" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The JMS message selector for this listener.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="ref" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The bean name of the listener object, implementing
+ the MessageListener/SessionAwareMessageListener interface
+ or defining the specified listener method. Required.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref"/>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="method" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The name of the listener method to invoke. If not specified,
+ the target bean is supposed to implement the MessageListener
+ or SessionAwareMessageListener interface.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="response-destination" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The name of the default response destination to send response messages to.
+ This will be applied in case of a request message that does not carry
+ a "JMSReplyTo" field. The type of this destination will be determined
+ by the listener-container's "response-destination-type" attribute.
+ Note: This only applies to a listener method with a return value,
+ for which each result object will be converted into a response message.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="concurrency" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The number of concurrent sessions/consumers to start for this listener.
+ Can either be a simple number indicating the maximum number (e.g. "5")
+ or a range indicating the lower as well as the upper limit (e.g. "3-5").
+ Note that a specified minimum is just a hint and might be ignored at runtime.
+ Default is the value provided by the container.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+
+</xsd:schema>
diff --git a/spring-jms/src/test/java/org/springframework/jms/annotation/EnableJmsTests.java b/spring-jms/src/test/java/org/springframework/jms/annotation/EnableJmsTests.java
index a48e401c..5351ba06 100644
--- a/spring-jms/src/test/java/org/springframework/jms/annotation/EnableJmsTests.java
+++ b/spring-jms/src/test/java/org/springframework/jms/annotation/EnableJmsTests.java
@@ -16,10 +16,14 @@
package org.springframework.jms.annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
import javax.jms.JMSException;
import javax.jms.MessageListener;
import org.hamcrest.core.Is;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
@@ -32,10 +36,12 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
+import org.springframework.core.annotation.AliasFor;
import org.springframework.jms.config.JmsListenerContainerTestFactory;
import org.springframework.jms.config.JmsListenerEndpointRegistrar;
import org.springframework.jms.config.JmsListenerEndpointRegistry;
import org.springframework.jms.config.MessageListenerTestContainer;
+import org.springframework.jms.config.MethodJmsListenerEndpoint;
import org.springframework.jms.config.SimpleJmsListenerEndpoint;
import org.springframework.jms.listener.adapter.ListenerExecutionFailedException;
import org.springframework.jms.listener.adapter.MessageListenerAdapter;
@@ -48,6 +54,7 @@ import static org.junit.Assert.*;
/**
* @author Stephane Nicoll
+ * @author Sam Brannen
*/
public class EnableJmsTests extends AbstractJmsAnnotationDrivenTests {
@@ -155,6 +162,29 @@ public class EnableJmsTests extends AbstractJmsAnnotationDrivenTests {
}
@Test
+ public void composedJmsListeners() {
+ try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
+ EnableJmsDefaultContainerFactoryConfig.class, ComposedJmsListenersBean.class)) {
+ JmsListenerContainerTestFactory simpleFactory = context.getBean("jmsListenerContainerFactory",
+ JmsListenerContainerTestFactory.class);
+ assertEquals(2, simpleFactory.getListenerContainers().size());
+
+ MethodJmsListenerEndpoint first = (MethodJmsListenerEndpoint) simpleFactory.getListenerContainer(
+ "first").getEndpoint();
+ assertEquals("first", first.getId());
+ assertEquals("orderQueue", first.getDestination());
+ assertNull(first.getConcurrency());
+
+ MethodJmsListenerEndpoint second = (MethodJmsListenerEndpoint) simpleFactory.getListenerContainer(
+ "second").getEndpoint();
+ assertEquals("second", second.getId());
+ assertEquals("billingQueue", second.getDestination());
+ assertEquals("2-10", second.getConcurrency());
+ }
+ }
+
+ @Test
+ @SuppressWarnings("resource")
public void unknownFactory() {
thrown.expect(BeanCreationException.class);
thrown.expectMessage("customFactory"); // not found
@@ -337,4 +367,38 @@ public class EnableJmsTests extends AbstractJmsAnnotationDrivenTests {
}
}
+
+ @JmsListener(destination = "orderQueue")
+ @Retention(RetentionPolicy.RUNTIME)
+ private @interface OrderQueueListener {
+
+ @AliasFor(annotation = JmsListener.class)
+ String id() default "";
+
+ @AliasFor(annotation = JmsListener.class)
+ String concurrency() default "";
+ }
+
+
+ @JmsListener(destination = "billingQueue")
+ @Retention(RetentionPolicy.RUNTIME)
+ private @interface BillingQueueListener {
+
+ @AliasFor(annotation = JmsListener.class)
+ String id() default "";
+
+ @AliasFor(annotation = JmsListener.class)
+ String concurrency() default "";
+ }
+
+
+ @Component
+ static class ComposedJmsListenersBean {
+
+ @OrderQueueListener(id = "first")
+ @BillingQueueListener(id = "second", concurrency = "2-10")
+ public void repeatableHandle(String msg) {
+ }
+ }
+
}
diff --git a/spring-jms/src/test/java/org/springframework/jms/annotation/JmsListenerAnnotationBeanPostProcessorTests.java b/spring-jms/src/test/java/org/springframework/jms/annotation/JmsListenerAnnotationBeanPostProcessorTests.java
index ea1dbb70..46a8f9fe 100644
--- a/spring-jms/src/test/java/org/springframework/jms/annotation/JmsListenerAnnotationBeanPostProcessorTests.java
+++ b/spring-jms/src/test/java/org/springframework/jms/annotation/JmsListenerAnnotationBeanPostProcessorTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -134,12 +134,12 @@ public class JmsListenerAnnotationBeanPostProcessorTests {
}
@Test
+ @SuppressWarnings("resource")
public void invalidProxy() {
thrown.expect(BeanCreationException.class);
thrown.expectCause(is(instanceOf(IllegalStateException.class)));
thrown.expectMessage("handleIt2");
- new AnnotationConfigApplicationContext(
- Config.class, ProxyConfig.class, InvalidProxyTestBean.class);
+ new AnnotationConfigApplicationContext(Config.class, ProxyConfig.class, InvalidProxyTestBean.class);
}
diff --git a/spring-jms/src/test/java/org/springframework/jms/config/JmsListenerContainerFactoryIntegrationTests.java b/spring-jms/src/test/java/org/springframework/jms/config/JmsListenerContainerFactoryIntegrationTests.java
index da2a90c1..dcfceb82 100644
--- a/spring-jms/src/test/java/org/springframework/jms/config/JmsListenerContainerFactoryIntegrationTests.java
+++ b/spring-jms/src/test/java/org/springframework/jms/config/JmsListenerContainerFactoryIntegrationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@
package org.springframework.jms.config;
import java.lang.reflect.Method;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import javax.jms.JMSException;
@@ -36,6 +35,7 @@ import org.springframework.jms.listener.DefaultMessageListenerContainer;
import org.springframework.jms.listener.SessionAwareMessageListener;
import org.springframework.jms.support.converter.MessageConversionException;
import org.springframework.jms.support.converter.MessageConverter;
+import org.springframework.jms.support.converter.MessagingMessageConverter;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory;
@@ -66,10 +66,21 @@ public class JmsListenerContainerFactoryIntegrationTests {
@Test
public void messageConverterUsedIfSet() throws JMSException {
- containerFactory.setMessageConverter(new UpperCaseMessageConverter());
+ this.containerFactory.setMessageConverter(new UpperCaseMessageConverter());
+ testMessageConverterIsUsed();
+ }
+
+ @Test
+ public void messagingMessageConverterCanBeUsed() throws JMSException {
+ MessagingMessageConverter converter = new MessagingMessageConverter();
+ converter.setPayloadConverter(new UpperCaseMessageConverter());
+ this.containerFactory.setMessageConverter(converter);
+ testMessageConverterIsUsed();
+ }
+ private void testMessageConverterIsUsed() throws JMSException {
MethodJmsListenerEndpoint endpoint = createDefaultMethodJmsEndpoint(
- listener.getClass(), "handleIt", String.class, String.class);
+ this.listener.getClass(), "handleIt", String.class, String.class);
Message message = new StubTextMessage("foo-bar");
message.setStringProperty("my-header", "my-value");
diff --git a/spring-jms/src/test/java/org/springframework/jms/config/MethodJmsListenerEndpointTests.java b/spring-jms/src/test/java/org/springframework/jms/config/MethodJmsListenerEndpointTests.java
index c8771107..fb16b199 100644
--- a/spring-jms/src/test/java/org/springframework/jms/config/MethodJmsListenerEndpointTests.java
+++ b/spring-jms/src/test/java/org/springframework/jms/config/MethodJmsListenerEndpointTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -57,14 +57,18 @@ import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory;
-import org.springframework.messaging.handler.annotation.support.MethodArgumentTypeMismatchException;
import org.springframework.util.ReflectionUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.springframework.validation.annotation.Validated;
-import static org.junit.Assert.*;
-import static org.mockito.BDDMockito.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.BDDMockito.mock;
+import static org.mockito.BDDMockito.verify;
/**
* @author Stephane Nicoll
@@ -266,7 +270,7 @@ public class MethodJmsListenerEndpointTests {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
MessagingMessageListenerAdapter listener = createInstance(this.factory,
getListenerMethod(methodName, String.class), container);
- processAndReplyWithSendTo(listener, false);
+ processAndReplyWithSendTo(listener, "replyDestination", false);
assertListenerMethodInvocation(sample, methodName);
}
@@ -278,7 +282,7 @@ public class MethodJmsListenerEndpointTests {
container.setReplyPubSubDomain(false);
MessagingMessageListenerAdapter listener = createInstance(this.factory,
getListenerMethod(methodName, String.class), container);
- processAndReplyWithSendTo(listener, false);
+ processAndReplyWithSendTo(listener, "replyDestination", false);
assertListenerMethodInvocation(sample, methodName);
}
@@ -289,7 +293,7 @@ public class MethodJmsListenerEndpointTests {
container.setPubSubDomain(true);
MessagingMessageListenerAdapter listener = createInstance(this.factory,
getListenerMethod(methodName, String.class), container);
- processAndReplyWithSendTo(listener, true);
+ processAndReplyWithSendTo(listener, "replyDestination", true);
assertListenerMethodInvocation(sample, methodName);
}
@@ -300,11 +304,19 @@ public class MethodJmsListenerEndpointTests {
container.setReplyPubSubDomain(true);
MessagingMessageListenerAdapter listener = createInstance(this.factory,
getListenerMethod(methodName, String.class), container);
- processAndReplyWithSendTo(listener, true);
+ processAndReplyWithSendTo(listener, "replyDestination", true);
assertListenerMethodInvocation(sample, methodName);
}
- private void processAndReplyWithSendTo(MessagingMessageListenerAdapter listener, boolean pubSubDomain) throws JMSException {
+ @Test
+ public void processAndReplyWithDefaultSendTo() throws JMSException {
+ MessagingMessageListenerAdapter listener = createDefaultInstance(String.class);
+ processAndReplyWithSendTo(listener, "defaultReply", false);
+ assertDefaultListenerMethodInvocation();
+ }
+
+ private void processAndReplyWithSendTo(MessagingMessageListenerAdapter listener,
+ String replyDestinationName, boolean pubSubDomain) throws JMSException {
String body = "echo text";
String correlationId = "link-1234";
Destination replyDestination = new Destination() {};
@@ -314,7 +326,7 @@ public class MethodJmsListenerEndpointTests {
QueueSender queueSender = mock(QueueSender.class);
Session session = mock(Session.class);
- given(destinationResolver.resolveDestinationName(session, "replyDestination", pubSubDomain))
+ given(destinationResolver.resolveDestinationName(session, replyDestinationName, pubSubDomain))
.willReturn(replyDestination);
given(session.createTextMessage(body)).willReturn(reply);
given(session.createProducer(replyDestination)).willReturn(queueSender);
@@ -324,7 +336,7 @@ public class MethodJmsListenerEndpointTests {
inputMessage.setJMSCorrelationID(correlationId);
listener.onMessage(inputMessage, session);
- verify(destinationResolver).resolveDestinationName(session, "replyDestination", pubSubDomain);
+ verify(destinationResolver).resolveDestinationName(session, replyDestinationName, pubSubDomain);
verify(reply).setJMSCorrelationID(correlationId);
verify(queueSender).send(reply);
verify(queueSender).close();
@@ -399,7 +411,7 @@ public class MethodJmsListenerEndpointTests {
Session session = mock(Session.class);
thrown.expect(ListenerExecutionFailedException.class);
- thrown.expectCause(Matchers.isA(MethodArgumentTypeMismatchException.class));
+ thrown.expectCause(Matchers.isA(MessageConversionException.class));
listener.onMessage(createSimpleJmsTextMessage("test"), session); // Message<String> as Message<Integer>
}
@@ -470,9 +482,10 @@ public class MethodJmsListenerEndpointTests {
}
+ @SendTo("defaultReply") @SuppressWarnings("unused")
static class JmsEndpointSampleBean {
- private final Map<String, Boolean> invocations = new HashMap<String, Boolean>();
+ private final Map<String, Boolean> invocations = new HashMap<>();
public void resolveMessageAndSession(javax.jms.Message message, Session session) {
invocations.put("resolveMessageAndSession", true);
@@ -549,6 +562,11 @@ public class MethodJmsListenerEndpointTests {
return content;
}
+ public String processAndReplyWithDefaultSendTo(String content) {
+ invocations.put("processAndReplyWithDefaultSendTo", true);
+ return content;
+ }
+
@SendTo("")
public String emptySendTo(String content) {
invocations.put("emptySendTo", true);
diff --git a/spring-jms/src/test/java/org/springframework/jms/listener/adapter/MessageListenerAdapterTests.java b/spring-jms/src/test/java/org/springframework/jms/listener/adapter/MessageListenerAdapterTests.java
index 3049c791..d085341e 100644
--- a/spring-jms/src/test/java/org/springframework/jms/listener/adapter/MessageListenerAdapterTests.java
+++ b/spring-jms/src/test/java/org/springframework/jms/listener/adapter/MessageListenerAdapterTests.java
@@ -307,7 +307,8 @@ public class MessageListenerAdapterTests {
try {
adapter.onMessage(sentTextMessage, session);
fail("expected CouldNotSendReplyException with InvalidDestinationException");
- } catch(ReplyFailureException ex) {
+ }
+ catch (ReplyFailureException ex) {
assertEquals(InvalidDestinationException.class, ex.getCause().getClass());
}
@@ -345,7 +346,8 @@ public class MessageListenerAdapterTests {
try {
adapter.onMessage(sentTextMessage, session);
fail("expected CouldNotSendReplyException with JMSException");
- } catch(ReplyFailureException ex) {
+ }
+ catch (ReplyFailureException ex) {
assertEquals(JMSException.class, ex.getCause().getClass());
}
@@ -371,7 +373,8 @@ public class MessageListenerAdapterTests {
try {
adapter.onMessage(message, session);
fail("expected ListenerExecutionFailedException");
- } catch(ListenerExecutionFailedException ex) { /* expected */ }
+ }
+ catch (ListenerExecutionFailedException ex) { /* expected */ }
}
@Test
@@ -425,7 +428,8 @@ public class MessageListenerAdapterTests {
try {
adapter.onMessage(sentTextMessage, session);
fail("expected CouldNotSendReplyException with MessageConversionException");
- } catch(ReplyFailureException ex) {
+ }
+ catch (ReplyFailureException ex) {
assertEquals(MessageConversionException.class, ex.getCause().getClass());
}
}
diff --git a/spring-jms/src/test/java/org/springframework/jms/listener/adapter/MessagingMessageListenerAdapterTests.java b/spring-jms/src/test/java/org/springframework/jms/listener/adapter/MessagingMessageListenerAdapterTests.java
index d4b98753..0daf92c0 100644
--- a/spring-jms/src/test/java/org/springframework/jms/listener/adapter/MessagingMessageListenerAdapterTests.java
+++ b/spring-jms/src/test/java/org/springframework/jms/listener/adapter/MessagingMessageListenerAdapterTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,8 +27,11 @@ import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;
+import com.fasterxml.jackson.annotation.JsonView;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.support.StaticListableBeanFactory;
import org.springframework.jms.StubTextMessage;
@@ -50,6 +53,11 @@ import static org.mockito.BDDMockito.*;
*/
public class MessagingMessageListenerAdapterTests {
+ @Rule
+ public final ExpectedException thrown = ExpectedException.none();
+
+ private static final Destination sharedReplyDestination = mock(Destination.class);
+
private final DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
private final SampleBean sample = new SampleBean();
@@ -120,6 +128,31 @@ public class MessagingMessageListenerAdapterTests {
}
@Test
+ public void payloadConversionLazilyInvoked() throws JMSException {
+ javax.jms.Message jmsMessage = mock(javax.jms.Message.class);
+ MessageConverter messageConverter = mock(MessageConverter.class);
+ given(messageConverter.fromMessage(jmsMessage)).willReturn("FooBar");
+ MessagingMessageListenerAdapter listener = getSimpleInstance("simple", Message.class);
+ listener.setMessageConverter(messageConverter);
+ Message<?> message = listener.toMessagingMessage(jmsMessage);
+ verify(messageConverter, never()).fromMessage(jmsMessage);
+ assertEquals("FooBar", message.getPayload());
+ verify(messageConverter, times(1)).fromMessage(jmsMessage);
+ }
+
+ @Test
+ public void headerConversionLazilyInvoked() throws JMSException {
+ javax.jms.Message jmsMessage = mock(javax.jms.Message.class);
+ when(jmsMessage.getPropertyNames()).thenThrow(new IllegalArgumentException("Header failure"));
+ MessagingMessageListenerAdapter listener = getSimpleInstance("simple", Message.class);
+ Message<?> message = listener.toMessagingMessage(jmsMessage);
+
+ this.thrown.expect(IllegalArgumentException.class);
+ this.thrown.expectMessage("Header failure");
+ message.getHeaders(); // Triggers headers resolution
+ }
+
+ @Test
public void incomingMessageUsesMessageConverter() throws JMSException {
javax.jms.Message jmsMessage = mock(javax.jms.Message.class);
Session session = mock(Session.class);
@@ -151,7 +184,6 @@ public class MessagingMessageListenerAdapterTests {
@Test
public void replyPayloadToQueue() throws JMSException {
- Message<String> request = MessageBuilder.withPayload("Response").build();
Session session = mock(Session.class);
Queue replyDestination = mock(Queue.class);
given(session.createQueue("queueOut")).willReturn(replyDestination);
@@ -161,7 +193,7 @@ public class MessagingMessageListenerAdapterTests {
given(session.createTextMessage("Response")).willReturn(responseMessage);
given(session.createProducer(replyDestination)).willReturn(messageProducer);
- MessagingMessageListenerAdapter listener = getPayloadInstance(request, "replyPayloadToQueue", Message.class);
+ MessagingMessageListenerAdapter listener = getPayloadInstance("Response", "replyPayloadToQueue", Message.class);
listener.onMessage(mock(javax.jms.Message.class), session);
verify(session).createQueue("queueOut");
@@ -172,7 +204,6 @@ public class MessagingMessageListenerAdapterTests {
@Test
public void replyPayloadToTopic() throws JMSException {
- Message<String> request = MessageBuilder.withPayload("Response").build();
Session session = mock(Session.class);
Topic replyDestination = mock(Topic.class);
given(session.createTopic("topicOut")).willReturn(replyDestination);
@@ -182,7 +213,7 @@ public class MessagingMessageListenerAdapterTests {
given(session.createTextMessage("Response")).willReturn(responseMessage);
given(session.createProducer(replyDestination)).willReturn(messageProducer);
- MessagingMessageListenerAdapter listener = getPayloadInstance(request, "replyPayloadToTopic", Message.class);
+ MessagingMessageListenerAdapter listener = getPayloadInstance("Response", "replyPayloadToTopic", Message.class);
listener.onMessage(mock(javax.jms.Message.class), session);
verify(session).createTopic("topicOut");
@@ -193,17 +224,13 @@ public class MessagingMessageListenerAdapterTests {
@Test
public void replyPayloadToDestination() throws JMSException {
- Queue replyDestination = mock(Queue.class);
- Message<String> request = MessageBuilder.withPayload("Response")
- .setHeader("destination", replyDestination).build();
-
Session session = mock(Session.class);
MessageProducer messageProducer = mock(MessageProducer.class);
TextMessage responseMessage = mock(TextMessage.class);
given(session.createTextMessage("Response")).willReturn(responseMessage);
- given(session.createProducer(replyDestination)).willReturn(messageProducer);
+ given(session.createProducer(sharedReplyDestination)).willReturn(messageProducer);
- MessagingMessageListenerAdapter listener = getPayloadInstance(request, "replyPayloadToDestination", Message.class);
+ MessagingMessageListenerAdapter listener = getPayloadInstance("Response", "replyPayloadToDestination", Message.class);
listener.onMessage(mock(javax.jms.Message.class), session);
verify(session, times(0)).createQueue(anyString());
@@ -215,7 +242,6 @@ public class MessagingMessageListenerAdapterTests {
@Test
public void replyPayloadNoDestination() throws JMSException {
Queue replyDestination = mock(Queue.class);
- Message<String> request = MessageBuilder.withPayload("Response").build();
Session session = mock(Session.class);
MessageProducer messageProducer = mock(MessageProducer.class);
@@ -224,7 +250,7 @@ public class MessagingMessageListenerAdapterTests {
given(session.createProducer(replyDestination)).willReturn(messageProducer);
MessagingMessageListenerAdapter listener =
- getPayloadInstance(request, "replyPayloadNoDestination", Message.class);
+ getPayloadInstance("Response", "replyPayloadNoDestination", Message.class);
listener.setDefaultResponseDestination(replyDestination);
listener.onMessage(mock(javax.jms.Message.class), session);
@@ -241,9 +267,22 @@ public class MessagingMessageListenerAdapterTests {
verify(reply).setObjectProperty("foo", "bar");
}
- private TextMessage testReplyWithJackson(String methodName, String replyContent) throws JMSException {
+ @Test
+ public void replyJacksonMessageAndJsonView() throws JMSException {
+ TextMessage reply = testReplyWithJackson("replyJacksonMessageAndJsonView",
+ "{\"name\":\"Response\"}");
+ verify(reply).setObjectProperty("foo", "bar");
+ }
+
+ @Test
+ public void replyJacksonPojoAndJsonView() throws JMSException {
+ TextMessage reply = testReplyWithJackson("replyJacksonPojoAndJsonView",
+ "{\"name\":\"Response\"}");
+ verify(reply, never()).setObjectProperty("foo", "bar");
+ }
+
+ public TextMessage testReplyWithJackson(String methodName, String replyContent) throws JMSException {
Queue replyDestination = mock(Queue.class);
- Message<String> request = MessageBuilder.withPayload("Response").build();
Session session = mock(Session.class);
MessageProducer messageProducer = mock(MessageProducer.class);
@@ -251,7 +290,7 @@ public class MessagingMessageListenerAdapterTests {
given(session.createTextMessage(replyContent)).willReturn(responseMessage);
given(session.createProducer(replyDestination)).willReturn(messageProducer);
- MessagingMessageListenerAdapter listener = getPayloadInstance(request, methodName, Message.class);
+ MessagingMessageListenerAdapter listener = getPayloadInstance("Response", methodName, Message.class);
MappingJackson2MessageConverter messageConverter = new MappingJackson2MessageConverter();
messageConverter.setTargetType(MessageType.TEXT);
listener.setMessageConverter(messageConverter);
@@ -321,8 +360,7 @@ public class MessagingMessageListenerAdapterTests {
}
public JmsResponse<String> replyPayloadToDestination(Message<String> input) {
- return JmsResponse.forDestination(input.getPayload(),
- input.getHeaders().get("destination", Destination.class));
+ return JmsResponse.forDestination(input.getPayload(), sharedReplyDestination);
}
public JmsResponse<String> replyPayloadNoDestination(Message<String> input) {
@@ -334,6 +372,17 @@ public class MessagingMessageListenerAdapterTests {
.setHeader("foo", "bar").build();
}
+ @JsonView(Summary.class)
+ public Message<SampleResponse> replyJacksonMessageAndJsonView(Message<String> input) {
+ return MessageBuilder.withPayload(createSampleResponse(input.getPayload()))
+ .setHeader("foo", "bar").build();
+ }
+
+ @JsonView(Summary.class)
+ public SampleResponse replyJacksonPojoAndJsonView(Message<String> input) {
+ return createSampleResponse(input.getPayload());
+ }
+
private SampleResponse createSampleResponse(String name) {
return new SampleResponse(name, "lengthy description");
}
@@ -347,15 +396,22 @@ public class MessagingMessageListenerAdapterTests {
}
}
+ interface Summary {};
+ interface Full extends Summary {};
private static class SampleResponse {
private int counter = 42;
+ @JsonView(Summary.class)
private String name;
+ @JsonView(Full.class)
private String description;
+ SampleResponse() {
+ }
+
public SampleResponse(String name, String description) {
this.name = name;
this.description = description;
diff --git a/spring-jms/src/test/java/org/springframework/jms/support/SimpleMessageConverterTests.java b/spring-jms/src/test/java/org/springframework/jms/support/SimpleMessageConverterTests.java
index 1f2fd20d..91bafe01 100644
--- a/spring-jms/src/test/java/org/springframework/jms/support/SimpleMessageConverterTests.java
+++ b/spring-jms/src/test/java/org/springframework/jms/support/SimpleMessageConverterTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,7 +45,7 @@ import static org.mockito.BDDMockito.*;
* @author Rick Evans
* @since 18.09.2004
*/
-public final class SimpleMessageConverterTests {
+public class SimpleMessageConverterTests {
@Test
public void testStringConversion() throws JMSException {
@@ -124,19 +124,18 @@ public final class SimpleMessageConverterTests {
assertEquals(content, converter.fromMessage(msg));
}
- @Test(expected=MessageConversionException.class)
+ @Test(expected = MessageConversionException.class)
public void testToMessageThrowsExceptionIfGivenNullObjectToConvert() throws Exception {
new SimpleMessageConverter().toMessage(null, null);
}
- @Test(expected=MessageConversionException.class)
+ @Test(expected = MessageConversionException.class)
public void testToMessageThrowsExceptionIfGivenIncompatibleObjectToConvert() throws Exception {
new SimpleMessageConverter().toMessage(new Object(), null);
}
@Test
public void testToMessageSimplyReturnsMessageAsIsIfSuppliedWithMessage() throws JMSException {
-
Session session = mock(Session.class);
ObjectMessage message = mock(ObjectMessage.class);
@@ -147,7 +146,6 @@ public final class SimpleMessageConverterTests {
@Test
public void testFromMessageSimplyReturnsMessageAsIsIfSuppliedWithMessage() throws JMSException {
-
Message message = mock(Message.class);
SimpleMessageConverter converter = new SimpleMessageConverter();
@@ -157,36 +155,36 @@ public final class SimpleMessageConverterTests {
@Test
public void testMapConversionWhereMapHasNonStringTypesForKeys() throws JMSException {
-
MapMessage message = mock(MapMessage.class);
- final Session session = mock(Session.class);
+ Session session = mock(Session.class);
given(session.createMapMessage()).willReturn(message);
- final Map<Integer, String> content = new HashMap<Integer, String>(1);
+ Map<Integer, String> content = new HashMap<Integer, String>(1);
content.put(1, "value1");
- final SimpleMessageConverter converter = new SimpleMessageConverter();
+ SimpleMessageConverter converter = new SimpleMessageConverter();
try {
converter.toMessage(content, session);
fail("expected MessageConversionException");
- } catch (MessageConversionException ex) { /* expected */ }
+ }
+ catch (MessageConversionException ex) { /* expected */ }
}
@Test
public void testMapConversionWhereMapHasNNullForKey() throws JMSException {
-
MapMessage message = mock(MapMessage.class);
- final Session session = mock(Session.class);
+ Session session = mock(Session.class);
given(session.createMapMessage()).willReturn(message);
- final Map<Object, String> content = new HashMap<Object, String>(1);
+ Map<Object, String> content = new HashMap<Object, String>(1);
content.put(null, "value1");
- final SimpleMessageConverter converter = new SimpleMessageConverter();
+ SimpleMessageConverter converter = new SimpleMessageConverter();
try {
converter.toMessage(content, session);
fail("expected MessageConversionException");
- } catch (MessageConversionException ex) { /* expected */ }
+ }
+ catch (MessageConversionException ex) { /* expected */ }
}
}
diff --git a/spring-jms/src/test/java/org/springframework/jms/support/converter/MappingJackson2MessageConverterTests.java b/spring-jms/src/test/java/org/springframework/jms/support/converter/MappingJackson2MessageConverterTests.java
index af9e2c75..fcf8ed16 100644
--- a/spring-jms/src/test/java/org/springframework/jms/support/converter/MappingJackson2MessageConverterTests.java
+++ b/spring-jms/src/test/java/org/springframework/jms/support/converter/MappingJackson2MessageConverterTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author 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,28 +17,39 @@
package org.springframework.jms.support.converter;
import java.io.ByteArrayInputStream;
+import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.jms.BytesMessage;
+import javax.jms.JMSException;
import javax.jms.Session;
import javax.jms.TextMessage;
+import com.fasterxml.jackson.annotation.JsonView;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
+import org.springframework.core.MethodParameter;
+
import static org.junit.Assert.*;
import static org.mockito.BDDMockito.*;
/**
* @author Arjen Poutsma
* @author Dave Syer
+ * @author Stephane Nicoll
*/
public class MappingJackson2MessageConverterTests {
+ @Rule
+ public final ExpectedException thrown = ExpectedException.none();
+
private MappingJackson2MessageConverter converter;
private Session sessionMock;
@@ -167,6 +178,91 @@ public class MappingJackson2MessageConverterTests {
assertEquals("Invalid result", result, unmarshalled);
}
+ @Test
+ public void toTextMessageWithReturnType() throws JMSException, NoSuchMethodException {
+ Method method = this.getClass().getDeclaredMethod("summary");
+ MethodParameter returnType = new MethodParameter(method, -1);
+ testToTextMessageWithReturnType(returnType);
+ verify(sessionMock).createTextMessage("{\"name\":\"test\"}");
+ }
+
+ @Test
+ public void toTextMessageWithNullReturnType() throws JMSException, NoSuchMethodException {
+ testToTextMessageWithReturnType(null);
+ verify(sessionMock).createTextMessage("{\"name\":\"test\",\"description\":\"lengthy description\"}");
+ }
+
+ @Test
+ public void toTextMessageWithReturnTypeAndNoJsonView() throws JMSException, NoSuchMethodException {
+ Method method = this.getClass().getDeclaredMethod("none");
+ MethodParameter returnType = new MethodParameter(method, -1);
+
+ testToTextMessageWithReturnType(returnType);
+ verify(sessionMock).createTextMessage("{\"name\":\"test\",\"description\":\"lengthy description\"}");
+ }
+
+ @Test
+ public void toTextMessageWithReturnTypeAndMultipleJsonViews() throws JMSException, NoSuchMethodException {
+ Method method = this.getClass().getDeclaredMethod("invalid");
+ MethodParameter returnType = new MethodParameter(method, -1);
+
+ thrown.expect(IllegalArgumentException.class);
+ testToTextMessageWithReturnType(returnType);
+ }
+
+ private void testToTextMessageWithReturnType(MethodParameter returnType) throws JMSException, NoSuchMethodException {
+ converter.setTargetType(MessageType.TEXT);
+ TextMessage textMessageMock = mock(TextMessage.class);
+
+ MyAnotherBean bean = new MyAnotherBean("test", "lengthy description");
+ given(sessionMock.createTextMessage(isA(String.class))).willReturn(textMessageMock);
+ converter.toMessage(bean, sessionMock, returnType);
+ verify(textMessageMock).setStringProperty("__typeid__", MyAnotherBean.class.getName());
+ }
+
+ @Test
+ public void toTextMessageWithJsonViewClass() throws JMSException {
+ converter.setTargetType(MessageType.TEXT);
+ TextMessage textMessageMock = mock(TextMessage.class);
+
+ MyAnotherBean bean = new MyAnotherBean("test", "lengthy description");
+ given(sessionMock.createTextMessage(isA(String.class))).willReturn(textMessageMock);
+
+
+ converter.toMessage(bean, sessionMock, Summary.class);
+ verify(textMessageMock).setStringProperty("__typeid__", MyAnotherBean.class.getName());
+ verify(sessionMock).createTextMessage("{\"name\":\"test\"}");
+ }
+
+ @Test
+ public void toTextMessageWithAnotherJsonViewClass() throws JMSException {
+ converter.setTargetType(MessageType.TEXT);
+ TextMessage textMessageMock = mock(TextMessage.class);
+
+ MyAnotherBean bean = new MyAnotherBean("test", "lengthy description");
+ given(sessionMock.createTextMessage(isA(String.class))).willReturn(textMessageMock);
+
+
+ converter.toMessage(bean, sessionMock, Full.class);
+ verify(textMessageMock).setStringProperty("__typeid__", MyAnotherBean.class.getName());
+ verify(sessionMock).createTextMessage("{\"name\":\"test\",\"description\":\"lengthy description\"}");
+ }
+
+
+ @JsonView(Summary.class)
+ public MyAnotherBean summary() {
+ return new MyAnotherBean();
+ }
+
+ public MyAnotherBean none() {
+ return new MyAnotherBean();
+ }
+
+ @JsonView({Summary.class, Full.class})
+ public MyAnotherBean invalid() {
+ return new MyAnotherBean();
+ }
+
public static class MyBean {
public MyBean() {
@@ -210,4 +306,40 @@ public class MappingJackson2MessageConverterTests {
}
}
+ private interface Summary {};
+ private interface Full extends Summary {};
+
+ private static class MyAnotherBean {
+
+ @JsonView(Summary.class)
+ private String name;
+
+ @JsonView(Full.class)
+ private String description;
+
+ private MyAnotherBean() {
+ }
+
+ public MyAnotherBean(String name, String description) {
+ this.name = name;
+ this.description = description;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+ }
+
}
diff --git a/spring-jms/src/test/java/org/springframework/jms/support/converter/MessagingMessageConverterTests.java b/spring-jms/src/test/java/org/springframework/jms/support/converter/MessagingMessageConverterTests.java
index 4841d039..ab023afd 100644
--- a/spring-jms/src/test/java/org/springframework/jms/support/converter/MessagingMessageConverterTests.java
+++ b/spring-jms/src/test/java/org/springframework/jms/support/converter/MessagingMessageConverterTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 @@ import static org.junit.Assert.*;
import static org.mockito.BDDMockito.*;
/**
- *
* @author Stephane Nicoll
*/
public class MessagingMessageConverterTests {
@@ -46,8 +45,8 @@ public class MessagingMessageConverterTests {
@Test
public void onlyHandlesMessage() throws JMSException {
- thrown.expect(IllegalArgumentException.class);
- converter.toMessage(new Object(), mock(Session.class));
+ this.thrown.expect(IllegalArgumentException.class);
+ this.converter.toMessage(new Object(), mock(Session.class));
}
@Test
@@ -57,43 +56,38 @@ public class MessagingMessageConverterTests {
ObjectMessage jmsMessage = mock(ObjectMessage.class);
given(session.createObjectMessage(payload)).willReturn(jmsMessage);
- converter.toMessage(MessageBuilder.withPayload(payload).build(), session);
+ this.converter.toMessage(MessageBuilder.withPayload(payload).build(), session);
verify(session).createObjectMessage(payload);
}
@Test
public void fromNull() throws JMSException {
- assertNull(converter.fromMessage(null));
+ assertNull(this.converter.fromMessage(null));
}
@Test
public void customPayloadConverter() throws JMSException {
TextMessage jmsMsg = new StubTextMessage("1224");
- converter.setPayloadConverter(new SimpleMessageConverter() {
- @Override
- public Object fromMessage(javax.jms.Message message) throws JMSException, MessageConversionException {
- TextMessage textMessage = (TextMessage) message;
- return Long.parseLong(textMessage.getText());
- }
- });
-
- Message<?> msg = (Message<?>) converter.fromMessage(jmsMsg);
+ this.converter.setPayloadConverter(new TestMessageConverter());
+ Message<?> msg = (Message<?>) this.converter.fromMessage(jmsMsg);
assertEquals(1224L, msg.getPayload());
}
- @Test
- public void payloadIsAMessage() throws JMSException {
- final Message<String> message = MessageBuilder.withPayload("Test").setHeader("inside", true).build();
- converter.setPayloadConverter(new SimpleMessageConverter() {
- @Override
- public Object fromMessage(javax.jms.Message jmsMessage) throws JMSException, MessageConversionException {
- return message;
+ static class TestMessageConverter extends SimpleMessageConverter {
+
+ private boolean called;
+
+ @Override
+ public Object fromMessage(javax.jms.Message message) throws JMSException, MessageConversionException {
+ if (this.called) {
+ throw new java.lang.IllegalStateException("Converter called twice");
}
- });
- Message<?> msg = (Message<?>) converter.fromMessage(new StubTextMessage());
- assertEquals(message.getPayload(), msg.getPayload());
- assertEquals(true, msg.getHeaders().get("inside"));
+ this.called = true;
+ TextMessage textMessage = (TextMessage) message;
+ return Long.parseLong(textMessage.getText());
+ }
+
}
}
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/converter/MappingJackson2MessageConverter.java b/spring-messaging/src/main/java/org/springframework/messaging/converter/MappingJackson2MessageConverter.java
index f549f5e3..490b6f6a 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/converter/MappingJackson2MessageConverter.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/converter/MappingJackson2MessageConverter.java
@@ -20,6 +20,7 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
+import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicReference;
@@ -30,6 +31,7 @@ import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
@@ -38,7 +40,6 @@ import org.springframework.core.MethodParameter;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.util.Assert;
-import org.springframework.util.ClassUtils;
import org.springframework.util.MimeType;
/**
@@ -50,7 +51,7 @@ import org.springframework.util.MimeType;
* <li>{@link DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES} is disabled</li>
* </ul>
*
- * <p>Compatible with Jackson 2.1 and higher.
+ * <p>Compatible with Jackson 2.6 and higher, as of Spring 4.3.
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
@@ -59,11 +60,6 @@ import org.springframework.util.MimeType;
*/
public class MappingJackson2MessageConverter extends AbstractMessageConverter {
- // Check for Jackson 2.3's overloaded canDeserialize/canSerialize variants with cause reference
- private static final boolean jackson23Available =
- ClassUtils.hasMethod(ObjectMapper.class, "canDeserialize", JavaType.class, AtomicReference.class);
-
-
private ObjectMapper objectMapper;
private Boolean prettyPrint;
@@ -146,23 +142,14 @@ public class MappingJackson2MessageConverter extends AbstractMessageConverter {
return false;
}
JavaType javaType = this.objectMapper.constructType(targetClass);
- if (!jackson23Available || !logger.isWarnEnabled()) {
+ if (!logger.isWarnEnabled()) {
return this.objectMapper.canDeserialize(javaType);
}
AtomicReference<Throwable> causeRef = new AtomicReference<Throwable>();
if (this.objectMapper.canDeserialize(javaType, causeRef)) {
return true;
}
- Throwable cause = causeRef.get();
- if (cause != null) {
- String msg = "Failed to evaluate Jackson deserialization for type " + javaType;
- if (logger.isDebugEnabled()) {
- logger.warn(msg, cause);
- }
- else {
- logger.warn(msg + ": " + cause);
- }
- }
+ logWarningIfNecessary(javaType, causeRef.get());
return false;
}
@@ -171,16 +158,29 @@ public class MappingJackson2MessageConverter extends AbstractMessageConverter {
if (payload == null || !supportsMimeType(headers)) {
return false;
}
- if (!jackson23Available || !logger.isWarnEnabled()) {
+ if (!logger.isWarnEnabled()) {
return this.objectMapper.canSerialize(payload.getClass());
}
AtomicReference<Throwable> causeRef = new AtomicReference<Throwable>();
if (this.objectMapper.canSerialize(payload.getClass(), causeRef)) {
return true;
}
- Throwable cause = causeRef.get();
- if (cause != null) {
- String msg = "Failed to evaluate Jackson serialization for type [" + payload.getClass() + "]";
+ logWarningIfNecessary(payload.getClass(), causeRef.get());
+ return false;
+ }
+
+ /**
+ * Determine whether to log the given exception coming from a
+ * {@link ObjectMapper#canDeserialize} / {@link ObjectMapper#canSerialize} check.
+ * @param type the class that Jackson tested for (de-)serializability
+ * @param cause the Jackson-thrown exception to evaluate
+ * (typically a {@link JsonMappingException})
+ * @since 4.3
+ */
+ protected void logWarningIfNecessary(Type type, Throwable cause) {
+ if (cause != null && !(cause instanceof JsonMappingException && cause.getMessage().startsWith("Can not find"))) {
+ String msg = "Failed to evaluate Jackson " + (type instanceof JavaType ? "de" : "") +
+ "serialization for type [" + type + "]";
if (logger.isDebugEnabled()) {
logger.warn(msg, cause);
}
@@ -188,7 +188,6 @@ public class MappingJackson2MessageConverter extends AbstractMessageConverter {
logger.warn(msg + ": " + cause);
}
}
- return false;
}
@Override
@@ -198,7 +197,6 @@ public class MappingJackson2MessageConverter extends AbstractMessageConverter {
}
@Override
- @SuppressWarnings("deprecation")
protected Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
JavaType javaType = this.objectMapper.constructType(targetClass);
Object payload = message.getPayload();
@@ -207,7 +205,7 @@ public class MappingJackson2MessageConverter extends AbstractMessageConverter {
try {
if (payload instanceof byte[]) {
if (view != null) {
- return this.objectMapper.readerWithView(view).withType(javaType).readValue((byte[]) payload);
+ return this.objectMapper.readerWithView(view).forType(javaType).readValue((byte[]) payload);
}
else {
return this.objectMapper.readValue((byte[]) payload, javaType);
@@ -215,7 +213,7 @@ public class MappingJackson2MessageConverter extends AbstractMessageConverter {
}
else {
if (view != null) {
- return this.objectMapper.readerWithView(view).withType(javaType).readValue(payload.toString());
+ return this.objectMapper.readerWithView(view).forType(javaType).readValue(payload.toString());
}
else {
return this.objectMapper.readValue(payload.toString(), javaType);
@@ -302,8 +300,8 @@ public class MappingJackson2MessageConverter extends AbstractMessageConverter {
* @return the JSON encoding to use (never {@code null})
*/
protected JsonEncoding getJsonEncoding(MimeType contentType) {
- if ((contentType != null) && (contentType.getCharSet() != null)) {
- Charset charset = contentType.getCharSet();
+ if ((contentType != null) && (contentType.getCharset() != null)) {
+ Charset charset = contentType.getCharset();
for (JsonEncoding encoding : JsonEncoding.values()) {
if (charset.name().equals(encoding.getJavaName())) {
return encoding;
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/converter/MarshallingMessageConverter.java b/spring-messaging/src/main/java/org/springframework/messaging/converter/MarshallingMessageConverter.java
index 12d9d8d8..965af158 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/converter/MarshallingMessageConverter.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/converter/MarshallingMessageConverter.java
@@ -40,7 +40,7 @@ import org.springframework.util.MimeType;
* {@link Marshaller} and {@link Unmarshaller} abstractions.
*
* <p>This converter requires a {@code Marshaller} and {@code Unmarshaller} before it can
- * be used. These can be injected by the {@linkplain MarshallingMessageConverter(Marshaller)
+ * be used. These can be injected by the {@linkplain #MarshallingMessageConverter(Marshaller)
* constructor} or {@linkplain #setMarshaller(Marshaller) bean properties}.
*
* @author Arjen Poutsma
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/converter/StringMessageConverter.java b/spring-messaging/src/main/java/org/springframework/messaging/converter/StringMessageConverter.java
index a0d094aa..f511f77f 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/converter/StringMessageConverter.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/converter/StringMessageConverter.java
@@ -66,8 +66,8 @@ public class StringMessageConverter extends AbstractMessageConverter {
}
private Charset getContentTypeCharset(MimeType mimeType) {
- if (mimeType != null && mimeType.getCharSet() != null) {
- return mimeType.getCharSet();
+ if (mimeType != null && mimeType.getCharset() != null) {
+ return mimeType.getCharset();
}
else {
return this.defaultCharset;
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/HandlerMethod.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/HandlerMethod.java
index b7898d3e..bde12d08 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/handler/HandlerMethod.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/HandlerMethod.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,7 +25,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.MethodParameter;
-import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.SynthesizingMethodParameter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -33,10 +33,11 @@ import org.springframework.util.ClassUtils;
/**
* Encapsulates information about a handler method consisting of a
* {@linkplain #getMethod() method} and a {@linkplain #getBean() bean}.
- * Provides convenient access to method parameters, method return value, method annotations.
+ * Provides convenient access to method parameters, the method return value,
+ * method annotations, etc.
*
* <p>The class may be created with a bean instance or with a bean name (e.g. lazy-init bean,
- * prototype bean). Use {@link #createWithResolvedBean()} to obtain a {@link HandlerMethod}
+ * prototype bean). Use {@link #createWithResolvedBean()} to obtain a {@code HandlerMethod}
* instance with a bean instance resolved through the associated {@link BeanFactory}.
*
* @author Arjen Poutsma
@@ -61,6 +62,8 @@ public class HandlerMethod {
private final MethodParameter[] parameters;
+ private final HandlerMethod resolvedFromHandlerMethod;
+
/**
* Create an instance from a bean instance and a method.
@@ -74,6 +77,7 @@ public class HandlerMethod {
this.method = method;
this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
this.parameters = initMethodParameters();
+ this.resolvedFromHandlerMethod = null;
}
/**
@@ -89,12 +93,13 @@ public class HandlerMethod {
this.method = bean.getClass().getMethod(methodName, parameterTypes);
this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(this.method);
this.parameters = initMethodParameters();
+ this.resolvedFromHandlerMethod = null;
}
/**
* Create an instance from a bean name, a method, and a {@code BeanFactory}.
* The method {@link #createWithResolvedBean()} may be used later to
- * re-create the {@code HandlerMethod} with an initialized the bean.
+ * re-create the {@code HandlerMethod} with an initialized bean.
*/
public HandlerMethod(String beanName, BeanFactory beanFactory, Method method) {
Assert.hasText(beanName, "Bean name is required");
@@ -106,6 +111,7 @@ public class HandlerMethod {
this.method = method;
this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
this.parameters = initMethodParameters();
+ this.resolvedFromHandlerMethod = null;
}
/**
@@ -119,6 +125,7 @@ public class HandlerMethod {
this.method = handlerMethod.method;
this.bridgedMethod = handlerMethod.bridgedMethod;
this.parameters = handlerMethod.parameters;
+ this.resolvedFromHandlerMethod = handlerMethod.resolvedFromHandlerMethod;
}
/**
@@ -133,6 +140,7 @@ public class HandlerMethod {
this.method = handlerMethod.method;
this.bridgedMethod = handlerMethod.bridgedMethod;
this.parameters = handlerMethod.parameters;
+ this.resolvedFromHandlerMethod = handlerMethod;
}
@@ -184,6 +192,15 @@ public class HandlerMethod {
}
/**
+ * Return the HandlerMethod from which this HandlerMethod instance was
+ * resolved via {@link #createWithResolvedBean()}.
+ * @since 4.3
+ */
+ public HandlerMethod getResolvedFromHandlerMethod() {
+ return this.resolvedFromHandlerMethod;
+ }
+
+ /**
* Return the HandlerMethod return type.
*/
public MethodParameter getReturnType() {
@@ -207,11 +224,24 @@ public class HandlerMethod {
/**
* Returns a single annotation on the underlying method traversing its super methods
* if no annotation can be found on the given method itself.
- * @param annotationType the type of annotation to introspect the method for.
+ * <p>Also supports <em>merged</em> composed annotations with attribute
+ * overrides as of Spring Framework 4.3.
+ * @param annotationType the type of annotation to introspect the method for
* @return the annotation, or {@code null} if none found
+ * @see AnnotatedElementUtils#findMergedAnnotation
*/
public <A extends Annotation> A getMethodAnnotation(Class<A> annotationType) {
- return AnnotationUtils.findAnnotation(this.method, annotationType);
+ return AnnotatedElementUtils.findMergedAnnotation(this.method, annotationType);
+ }
+
+ /**
+ * Return whether the parameter is declared with the given annotation type.
+ * @param annotationType the annotation type to look for
+ * @since 4.3
+ * @see AnnotatedElementUtils#hasAnnotation
+ */
+ public <A extends Annotation> boolean hasMethodAnnotation(Class<A> annotationType) {
+ return AnnotatedElementUtils.hasAnnotation(this.method, annotationType);
}
/**
@@ -227,6 +257,9 @@ public class HandlerMethod {
return new HandlerMethod(this, handler);
}
+ /**
+ * Return a short representation of this handler method for log message purposes.
+ */
public String getShortLogMessage() {
int args = this.method.getParameterTypes().length;
return getBeanType().getName() + "#" + this.method.getName() + "[" + args + " args]";
@@ -265,6 +298,10 @@ public class HandlerMethod {
super(HandlerMethod.this.bridgedMethod, index);
}
+ protected HandlerMethodParameter(HandlerMethodParameter original) {
+ super(original);
+ }
+
@Override
public Class<?> getContainingClass() {
return HandlerMethod.this.getBeanType();
@@ -274,6 +311,16 @@ public class HandlerMethod {
public <T extends Annotation> T getMethodAnnotation(Class<T> annotationType) {
return HandlerMethod.this.getMethodAnnotation(annotationType);
}
+
+ @Override
+ public <T extends Annotation> boolean hasMethodAnnotation(Class<T> annotationType) {
+ return HandlerMethod.this.hasMethodAnnotation(annotationType);
+ }
+
+ @Override
+ public HandlerMethodParameter clone() {
+ return new HandlerMethodParameter(this);
+ }
}
@@ -289,10 +336,20 @@ public class HandlerMethod {
this.returnValue = returnValue;
}
+ protected ReturnValueMethodParameter(ReturnValueMethodParameter original) {
+ super(original);
+ this.returnValue = original.returnValue;
+ }
+
@Override
public Class<?> getParameterType() {
return (this.returnValue != null ? this.returnValue.getClass() : super.getParameterType());
}
+
+ @Override
+ public ReturnValueMethodParameter clone() {
+ return new ReturnValueMethodParameter(this);
+ }
}
}
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/HandlerMethodSelector.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/HandlerMethodSelector.java
index 69bd0c1a..3d438200 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/handler/HandlerMethodSelector.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/HandlerMethodSelector.java
@@ -39,7 +39,7 @@ public abstract class HandlerMethodSelector {
* @param handlerType the handler type to search handler methods on
* @param handlerMethodFilter a {@link MethodFilter} to help recognize handler methods of interest
* @return the selected methods, or an empty set
- * @see MethodIntrospector#selectMethods(Class, MethodFilter)
+ * @see MethodIntrospector#selectMethods
*/
public static Set<Method> selectMethods(Class<?> handlerType, MethodFilter handlerMethodFilter) {
return MethodIntrospector.selectMethods(handlerType, handlerMethodFilter);
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/SendTo.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/SendTo.java
index ee4ce415..0a5a3f27 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/SendTo.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/SendTo.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,10 +32,15 @@ import org.springframework.messaging.Message;
* convey the destination to use for the reply. In that case, that destination
* should take precedence.
*
+ * <p>The annotation may also be placed at class-level if the provider supports
+ * it to indicate that all related methods should use this destination if none
+ * is specified otherwise.
+ *
* @author Rossen Stoyanchev
+ * @author Stephane Nicoll
* @since 4.0
*/
-@Target(ElementType.METHOD)
+@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SendTo {
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/AbstractNamedValueMethodArgumentResolver.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/AbstractNamedValueMethodArgumentResolver.java
index f9b9444d..dcf20f83 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/AbstractNamedValueMethodArgumentResolver.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/AbstractNamedValueMethodArgumentResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -84,24 +84,30 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle
@Override
public Object resolveArgument(MethodParameter parameter, Message<?> message) throws Exception {
- Class<?> paramType = parameter.getParameterType();
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
+ MethodParameter nestedParameter = parameter.nestedIfOptional();
- Object arg = resolveArgumentInternal(parameter, message, namedValueInfo.name);
+ Object resolvedName = resolveStringValue(namedValueInfo.name);
+ if (resolvedName == null) {
+ throw new IllegalArgumentException(
+ "Specified name must not resolve to null: [" + namedValueInfo.name + "]");
+ }
+
+ Object arg = resolveArgumentInternal(nestedParameter, message, resolvedName.toString());
if (arg == null) {
if (namedValueInfo.defaultValue != null) {
- arg = resolveDefaultValue(namedValueInfo.defaultValue);
+ arg = resolveStringValue(namedValueInfo.defaultValue);
}
- else if (namedValueInfo.required && !parameter.getParameterType().getName().equals("java.util.Optional")) {
- handleMissingValue(namedValueInfo.name, parameter, message);
+ else if (namedValueInfo.required && !nestedParameter.isOptional()) {
+ handleMissingValue(namedValueInfo.name, nestedParameter, message);
}
- arg = handleNullValue(namedValueInfo.name, arg, paramType);
+ arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
- arg = resolveDefaultValue(namedValueInfo.defaultValue);
+ arg = resolveStringValue(namedValueInfo.defaultValue);
}
- if (!ClassUtils.isAssignableValue(paramType, arg)) {
+ if (!ClassUtils.isAssignableValue(parameter.getParameterType(), arg)) {
arg = this.conversionService.convert(
arg, TypeDescriptor.valueOf(arg.getClass()), new TypeDescriptor(parameter));
}
@@ -149,6 +155,22 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle
}
/**
+ * Resolve the given annotation-specified value,
+ * potentially containing placeholders and expressions.
+ */
+ private Object resolveStringValue(String value) {
+ if (this.configurableBeanFactory == null) {
+ return value;
+ }
+ String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(value);
+ BeanExpressionResolver exprResolver = this.configurableBeanFactory.getBeanExpressionResolver();
+ if (exprResolver == null) {
+ return value;
+ }
+ return exprResolver.evaluate(placeholdersResolved, this.expressionContext);
+ }
+
+ /**
* Resolves the given parameter type and value name into an argument value.
* @param parameter the method parameter to resolve to an argument value
* @param message the current request
@@ -160,21 +182,6 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle
throws Exception;
/**
- * Resolves the given default value into an argument value.
- */
- private Object resolveDefaultValue(String defaultValue) {
- if (this.configurableBeanFactory == null) {
- return defaultValue;
- }
- String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(defaultValue);
- BeanExpressionResolver exprResolver = this.configurableBeanFactory.getBeanExpressionResolver();
- if (exprResolver == null) {
- return defaultValue;
- }
- return exprResolver.evaluate(placeholdersResolved, this.expressionContext);
- }
-
- /**
* Invoked when a named value is required, but
* {@link #resolveArgumentInternal(MethodParameter, Message, String)} returned {@code null} and
* there is no default value. Subclasses typically throw an exception in this case.
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/DefaultMessageHandlerMethodFactory.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/DefaultMessageHandlerMethodFactory.java
index 8c1cbdea..8d83f5ed 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/DefaultMessageHandlerMethodFactory.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/DefaultMessageHandlerMethodFactory.java
@@ -158,7 +158,7 @@ public class DefaultMessageHandlerMethodFactory implements MessageHandlerMethodF
resolvers.add(new HeadersMethodArgumentResolver());
// Type-based argument resolution
- resolvers.add(new MessageMethodArgumentResolver());
+ resolvers.add(new MessageMethodArgumentResolver(this.messageConverter));
if (this.customArgumentResolvers != null) {
resolvers.addAll(this.customArgumentResolvers);
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MessageMethodArgumentResolver.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MessageMethodArgumentResolver.java
index d13658cc..03b9ba06 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MessageMethodArgumentResolver.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MessageMethodArgumentResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,12 +21,20 @@ import java.lang.reflect.Type;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.messaging.Message;
+import org.springframework.messaging.converter.MessageConversionException;
+import org.springframework.messaging.converter.MessageConverter;
+import org.springframework.messaging.converter.SmartMessageConverter;
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
+import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
+import org.springframework.util.StringUtils;
/**
- * A {@link HandlerMethodArgumentResolver} for {@link Message} parameters.
- * Validates that the generic type of the payload matches with the message value.
+ * {@code HandlerMethodArgumentResolver} for {@link Message} method arguments.
+ * Validates that the generic type of the payload matches to the message value
+ * or otherwise applies {@link MessageConverter} to convert to the expected
+ * payload type.
*
* @author Rossen Stoyanchev
* @author Stephane Nicoll
@@ -34,6 +42,20 @@ import org.springframework.util.ClassUtils;
*/
public class MessageMethodArgumentResolver implements HandlerMethodArgumentResolver {
+ private final MessageConverter converter;
+
+
+ /**
+ * Create a new instance with the given {@link MessageConverter}.
+ * @param converter the MessageConverter to use (required)
+ * @since 4.1
+ */
+ public MessageMethodArgumentResolver(MessageConverter converter) {
+ Assert.notNull(converter, "MessageConverter must not be null");
+ this.converter = converter;
+ }
+
+
@Override
public boolean supportsParameter(MethodParameter parameter) {
return Message.class.isAssignableFrom(parameter.getParameterType());
@@ -41,22 +63,32 @@ public class MessageMethodArgumentResolver implements HandlerMethodArgumentResol
@Override
public Object resolveArgument(MethodParameter parameter, Message<?> message) throws Exception {
- Class<?> paramType = parameter.getParameterType();
- if (!paramType.isAssignableFrom(message.getClass())) {
- throw new MethodArgumentTypeMismatchException(message, parameter,
- "The actual message type [" + ClassUtils.getQualifiedName(message.getClass()) + "] " +
- "does not match the expected type [" + ClassUtils.getQualifiedName(paramType) + "]");
+
+ Class<?> targetMessageType = parameter.getParameterType();
+ Class<?> targetPayloadType = getPayloadType(parameter);
+
+ if (!targetMessageType.isAssignableFrom(message.getClass())) {
+ String actual = ClassUtils.getQualifiedName(message.getClass());
+ String expected = ClassUtils.getQualifiedName(targetMessageType);
+ throw new MethodArgumentTypeMismatchException(message, parameter, "The actual message type " +
+ "[" + actual + "] does not match the expected type [" + expected + "]");
}
- Class<?> expectedPayloadType = getPayloadType(parameter);
Object payload = message.getPayload();
- if (payload != null && expectedPayloadType != null && !expectedPayloadType.isInstance(payload)) {
- throw new MethodArgumentTypeMismatchException(message, parameter,
- "The expected Message<?> payload type [" + ClassUtils.getQualifiedName(expectedPayloadType) +
- "] does not match the actual payload type [" + ClassUtils.getQualifiedName(payload.getClass()) + "]");
+ if (payload == null || targetPayloadType.isInstance(payload)) {
+ return message;
+ }
+
+ if (isEmptyPayload(payload)) {
+ String actual = ClassUtils.getQualifiedName(payload.getClass());
+ String expected = ClassUtils.getQualifiedName(targetPayloadType);
+ throw new MessageConversionException(message, "Cannot convert from the " +
+ "expected payload type [" + expected + "] to the " +
+ "actual payload type [" + actual + "] when the payload is empty.");
}
- return message;
+ payload = convertPayload(message, parameter, targetPayloadType);
+ return MessageBuilder.createMessage(payload, message.getHeaders());
}
private Class<?> getPayloadType(MethodParameter parameter) {
@@ -65,4 +97,42 @@ public class MessageMethodArgumentResolver implements HandlerMethodArgumentResol
return resolvableType.getGeneric(0).resolve(Object.class);
}
+ /**
+ * Check if the given {@code payload} is empty.
+ * @param payload the payload to check (can be {@code null})
+ */
+ protected boolean isEmptyPayload(Object payload) {
+ if (payload == null) {
+ return true;
+ }
+ else if (payload instanceof byte[]) {
+ return ((byte[]) payload).length == 0;
+ }
+ else if (payload instanceof String) {
+ return !StringUtils.hasText((String) payload);
+ }
+ else {
+ return false;
+ }
+ }
+
+ private Object convertPayload(Message<?> message, MethodParameter parameter, Class<?> targetPayloadType) {
+ Object result;
+ if (this.converter instanceof SmartMessageConverter) {
+ SmartMessageConverter smartConverter = (SmartMessageConverter) this.converter;
+ result = smartConverter.fromMessage(message, targetPayloadType, parameter);
+ }
+ else {
+ result = this.converter.fromMessage(message, targetPayloadType);
+ }
+
+ if (result == null) {
+ String actual = ClassUtils.getQualifiedName(targetPayloadType);
+ String expected = ClassUtils.getQualifiedName(message.getPayload().getClass());
+ throw new MessageConversionException(message, "No converter found to convert payload " +
+ "type [" + actual + "] to expected payload type [" + expected + "].");
+ }
+ return result;
+ }
+
}
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/AbstractExceptionHandlerMethodResolver.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/AbstractExceptionHandlerMethodResolver.java
index b2851730..a7f7cf9f 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/AbstractExceptionHandlerMethodResolver.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/AbstractExceptionHandlerMethodResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +33,7 @@ import org.springframework.util.ClassUtils;
* {@link ExceptionDepthComparator} and the top match is returned.
*
* @author Rossen Stoyanchev
+ * @author Juergen Hoeller
* @since 4.0
*/
public abstract class AbstractExceptionHandlerMethodResolver {
@@ -69,6 +70,7 @@ public abstract class AbstractExceptionHandlerMethodResolver {
return result;
}
+
/**
* Whether the contained type has any exception mappings.
*/
@@ -77,13 +79,30 @@ public abstract class AbstractExceptionHandlerMethodResolver {
}
/**
- * Find a method to handle the given exception.
- * Use {@link org.springframework.core.ExceptionDepthComparator} if more than one match is found.
+ * Find a {@link Method} to handle the given exception.
+ * Use {@link ExceptionDepthComparator} if more than one match is found.
* @param exception the exception
- * @return a method to handle the exception or {@code null}
+ * @return a Method to handle the exception, or {@code null} if none found
*/
public Method resolveMethod(Exception exception) {
- Class<? extends Exception> exceptionType = exception.getClass();
+ Method method = resolveMethodByExceptionType(exception.getClass());
+ if (method == null) {
+ Throwable cause = exception.getCause();
+ if (cause != null) {
+ method = resolveMethodByExceptionType(cause.getClass());
+ }
+ }
+ return method;
+ }
+
+ /**
+ * Find a {@link Method} to handle the given exception type. This can be
+ * useful if an {@link Exception} instance is not available (e.g. for tools).
+ * @param exceptionType the exception type
+ * @return a Method to handle the exception, or {@code null} if none found
+ * @since 4.3.1
+ */
+ public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {
Method method = this.exceptionLookupCache.get(exceptionType);
if (method == null) {
method = getMappedMethod(exceptionType);
@@ -93,9 +112,9 @@ public abstract class AbstractExceptionHandlerMethodResolver {
}
/**
- * Return the method mapped to the given exception type or {@code null}.
+ * Return the {@link Method} mapped to the given exception type, or {@code null} if none.
*/
- private Method getMappedMethod(Class<? extends Exception> exceptionType) {
+ private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
List<Class<? extends Throwable>> matches = new ArrayList<Class<? extends Throwable>>();
for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
if (mappedException.isAssignableFrom(exceptionType)) {
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/HandlerMethodArgumentResolverComposite.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/HandlerMethodArgumentResolverComposite.java
index 1536b372..1d55191f 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/HandlerMethodArgumentResolverComposite.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/HandlerMethodArgumentResolverComposite.java
@@ -32,6 +32,7 @@ import org.springframework.util.Assert;
* for faster lookups.
*
* @author Rossen Stoyanchev
+ * @author Juergen Hoeller
* @since 4.0
*/
public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
@@ -52,6 +53,19 @@ public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgu
/**
* Add the given {@link HandlerMethodArgumentResolver}s.
+ * @since 4.3
+ */
+ public HandlerMethodArgumentResolverComposite addResolvers(HandlerMethodArgumentResolver... resolvers) {
+ if (resolvers != null) {
+ for (HandlerMethodArgumentResolver resolver : resolvers) {
+ this.argumentResolvers.add(resolver);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Add the given {@link HandlerMethodArgumentResolver}s.
*/
public HandlerMethodArgumentResolverComposite addResolvers(List<? extends HandlerMethodArgumentResolver> argumentResolvers) {
if (argumentResolvers != null) {
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 99fb8592..82932ca8 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
@@ -42,7 +42,7 @@ import org.springframework.util.ReflectionUtils;
*/
public class InvocableHandlerMethod extends HandlerMethod {
- private HandlerMethodArgumentResolver argumentResolvers = new HandlerMethodArgumentResolverComposite();
+ private HandlerMethodArgumentResolverComposite argumentResolvers = new HandlerMethodArgumentResolverComposite();
private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
@@ -78,7 +78,7 @@ public class InvocableHandlerMethod extends HandlerMethod {
/**
* Set {@link HandlerMethodArgumentResolver}s to use to use for resolving method argument values.
*/
- public void setMessageMethodArgumentResolvers(HandlerMethodArgumentResolver argumentResolvers) {
+ public void setMessageMethodArgumentResolvers(HandlerMethodArgumentResolverComposite argumentResolvers) {
this.argumentResolvers = argumentResolvers;
}
@@ -272,6 +272,12 @@ public class InvocableHandlerMethod extends HandlerMethod {
this.returnType = ResolvableType.forType(super.getGenericParameterType()).getGeneric(0);
}
+ protected AsyncResultMethodParameter(AsyncResultMethodParameter original) {
+ super(original);
+ this.returnValue = original.returnValue;
+ this.returnType = original.returnType;
+ }
+
@Override
public Class<?> getParameterType() {
if (this.returnValue != null) {
@@ -287,6 +293,11 @@ public class InvocableHandlerMethod extends HandlerMethod {
public Type getGenericParameterType() {
return this.returnType.getType();
}
+
+ @Override
+ public AsyncResultMethodParameter clone() {
+ return new AsyncResultMethodParameter(this);
+ }
}
}
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/SendToUser.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/SendToUser.java
index 06f2133e..255e1238 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/SendToUser.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/SendToUser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +30,9 @@ import org.springframework.core.annotation.AliasFor;
* destination(s) prepended with <code>"/user/{username}"</code> where the user name
* is extracted from the headers of the input message being handled.
*
+ * <p>The annotation may also be placed at class-level in which case all methods
+ * in the class where the annotation applies will inherit it.
+
* @author Rossen Stoyanchev
* @author Sam Brannen
* @since 4.0
@@ -37,7 +40,7 @@ import org.springframework.core.annotation.AliasFor;
* @see org.springframework.messaging.simp.user.UserDestinationMessageHandler
* @see org.springframework.messaging.simp.SimpMessageHeaderAccessor#getUser()
*/
-@Target(ElementType.METHOD)
+@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SendToUser {
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SendToMethodReturnValueHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SendToMethodReturnValueHandler.java
index 0f18a567..dd473a26 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SendToMethodReturnValueHandler.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SendToMethodReturnValueHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@ import java.security.Principal;
import java.util.Map;
import org.springframework.core.MethodParameter;
+import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
@@ -132,11 +133,13 @@ public class SendToMethodReturnValueHandler implements HandlerMethodReturnValueH
@Override
public boolean supportsReturnType(MethodParameter returnType) {
- if (returnType.getMethodAnnotation(SendTo.class) != null ||
- returnType.getMethodAnnotation(SendToUser.class) != null) {
+ if (returnType.hasMethodAnnotation(SendTo.class) ||
+ AnnotatedElementUtils.hasAnnotation(returnType.getDeclaringClass(), SendTo.class) ||
+ returnType.hasMethodAnnotation(SendToUser.class) ||
+ AnnotatedElementUtils.hasAnnotation(returnType.getDeclaringClass(), SendToUser.class)) {
return true;
}
- return (!this.annotationRequired);
+ return !this.annotationRequired;
}
@Override
@@ -148,9 +151,10 @@ public class SendToMethodReturnValueHandler implements HandlerMethodReturnValueH
MessageHeaders headers = message.getHeaders();
String sessionId = SimpMessageHeaderAccessor.getSessionId(headers);
PlaceholderResolver varResolver = initVarResolver(headers);
- SendToUser sendToUser = returnType.getMethodAnnotation(SendToUser.class);
+ Object annotation = findAnnotation(returnType);
- if (sendToUser != null) {
+ if (annotation != null && annotation instanceof SendToUser) {
+ SendToUser sendToUser = (SendToUser) annotation;
boolean broadcast = sendToUser.broadcast();
String user = getUserName(message, headers);
if (user == null) {
@@ -174,7 +178,7 @@ public class SendToMethodReturnValueHandler implements HandlerMethodReturnValueH
}
}
else {
- SendTo sendTo = returnType.getMethodAnnotation(SendTo.class);
+ SendTo sendTo = (SendTo) annotation;
String[] destinations = getTargetDestinations(sendTo, message, this.defaultDestinationPrefix);
for (String destination : destinations) {
destination = this.placeholderHelper.replacePlaceholders(destination, varResolver);
@@ -183,6 +187,57 @@ public class SendToMethodReturnValueHandler implements HandlerMethodReturnValueH
}
}
+ private Object findAnnotation(MethodParameter returnType) {
+ Annotation[] annot = new Annotation[4];
+ annot[0] = AnnotatedElementUtils.findMergedAnnotation(returnType.getMethod(), SendToUser.class);
+ annot[1] = AnnotatedElementUtils.findMergedAnnotation(returnType.getMethod(), SendTo.class);
+ annot[2] = AnnotatedElementUtils.findMergedAnnotation(returnType.getDeclaringClass(), SendToUser.class);
+ annot[3] = AnnotatedElementUtils.findMergedAnnotation(returnType.getDeclaringClass(), SendTo.class);
+
+ if (annot[0] != null && !ObjectUtils.isEmpty(((SendToUser) annot[0]).value())) {
+ return annot[0];
+ }
+ if (annot[1] != null && !ObjectUtils.isEmpty(((SendTo) annot[1]).value())) {
+ return annot[1];
+ }
+ if (annot[2] != null && !ObjectUtils.isEmpty(((SendToUser) annot[2]).value())) {
+ return annot[2];
+ }
+ if (annot[3] != null && !ObjectUtils.isEmpty(((SendTo) annot[3]).value())) {
+ return annot[3];
+ }
+
+ for (int i=0; i < 4; i++) {
+ if (annot[i] != null) {
+ return annot[i];
+ }
+ }
+
+ return null;
+ }
+
+ private SendToUser getSendToUser(MethodParameter returnType) {
+ SendToUser annot = AnnotatedElementUtils.findMergedAnnotation(returnType.getMethod(), SendToUser.class);
+ if (annot != null && !ObjectUtils.isEmpty(annot.value())) {
+ return annot;
+ }
+ SendToUser typeAnnot = AnnotatedElementUtils.findMergedAnnotation(returnType.getDeclaringClass(), SendToUser.class);
+ if (typeAnnot != null && !ObjectUtils.isEmpty(typeAnnot.value())) {
+ return typeAnnot;
+ }
+ return (annot != null ? annot : typeAnnot);
+ }
+
+ private SendTo getSendTo(MethodParameter returnType) {
+ SendTo sendTo = AnnotatedElementUtils.findMergedAnnotation(returnType.getMethod(), SendTo.class);
+ if (sendTo != null && !ObjectUtils.isEmpty(sendTo.value())) {
+ return sendTo;
+ }
+ else {
+ return AnnotatedElementUtils.findMergedAnnotation(returnType.getDeclaringClass(), SendTo.class);
+ }
+ }
+
@SuppressWarnings("unchecked")
private PlaceholderResolver initVarResolver(MessageHeaders headers) {
String name = DestinationVariableMethodArgumentResolver.DESTINATION_TEMPLATE_VARIABLES_HEADER;
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandler.java
index ccadb980..81165402 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandler.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandler.java
@@ -29,7 +29,7 @@ import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.context.SmartLifecycle;
-import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.convert.ConversionService;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.messaging.Message;
@@ -317,7 +317,7 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
// Type-based argument resolution
resolvers.add(new PrincipalMethodArgumentResolver());
- resolvers.add(new MessageMethodArgumentResolver());
+ resolvers.add(new MessageMethodArgumentResolver(this.messageConverter));
resolvers.addAll(getCustomArgumentResolvers());
resolvers.add(new PayloadArgumentResolver(this.messageConverter, this.validator));
@@ -336,23 +336,23 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
}
// Annotation-based return value types
- SendToMethodReturnValueHandler sth =
+ SendToMethodReturnValueHandler sendToHandler =
new SendToMethodReturnValueHandler(this.brokerTemplate, true);
- sth.setHeaderInitializer(this.headerInitializer);
- handlers.add(sth);
+ sendToHandler.setHeaderInitializer(this.headerInitializer);
+ handlers.add(sendToHandler);
- SubscriptionMethodReturnValueHandler sh =
+ SubscriptionMethodReturnValueHandler subscriptionHandler =
new SubscriptionMethodReturnValueHandler(this.clientMessagingTemplate);
- sh.setHeaderInitializer(this.headerInitializer);
- handlers.add(sh);
+ subscriptionHandler.setHeaderInitializer(this.headerInitializer);
+ handlers.add(subscriptionHandler);
// custom return value types
handlers.addAll(getCustomReturnValueHandlers());
// catch-all
- sth = new SendToMethodReturnValueHandler(this.brokerTemplate, false);
- sth.setHeaderInitializer(this.headerInitializer);
- handlers.add(sth);
+ sendToHandler = new SendToMethodReturnValueHandler(this.brokerTemplate, false);
+ sendToHandler.setHeaderInitializer(this.headerInitializer);
+ handlers.add(sendToHandler);
return handlers;
}
@@ -360,14 +360,14 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
@Override
protected boolean isHandler(Class<?> beanType) {
- return (AnnotationUtils.findAnnotation(beanType, Controller.class) != null);
+ return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class);
}
@Override
protected SimpMessageMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
- MessageMapping messageAnn = AnnotationUtils.findAnnotation(method, MessageMapping.class);
+ MessageMapping messageAnn = AnnotatedElementUtils.findMergedAnnotation(method, MessageMapping.class);
if (messageAnn != null) {
- MessageMapping typeAnn = AnnotationUtils.findAnnotation(handlerType, MessageMapping.class);
+ MessageMapping typeAnn = AnnotatedElementUtils.findMergedAnnotation(handlerType, MessageMapping.class);
// Only actually register it if there are destinations specified;
// otherwise @MessageMapping is just being used as a (meta-annotation) marker.
if (messageAnn.value().length > 0 || (typeAnn != null && typeAnn.value().length > 0)) {
@@ -379,9 +379,9 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
}
}
- SubscribeMapping subscribeAnn = AnnotationUtils.findAnnotation(method, SubscribeMapping.class);
+ SubscribeMapping subscribeAnn = AnnotatedElementUtils.findMergedAnnotation(method, SubscribeMapping.class);
if (subscribeAnn != null) {
- MessageMapping typeAnn = AnnotationUtils.findAnnotation(handlerType, MessageMapping.class);
+ MessageMapping typeAnn = AnnotatedElementUtils.findMergedAnnotation(handlerType, MessageMapping.class);
// Only actually register it if there are destinations specified;
// otherwise @SubscribeMapping is just being used as a (meta-annotation) marker.
if (subscribeAnn.value().length > 0 || (typeAnn != null && typeAnn.value().length > 0)) {
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SubscriptionMethodReturnValueHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SubscriptionMethodReturnValueHandler.java
index c762a130..e18af8df 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SubscriptionMethodReturnValueHandler.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SubscriptionMethodReturnValueHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,15 +34,22 @@ import org.springframework.messaging.support.MessageHeaderInitializer;
import org.springframework.util.Assert;
/**
- * A {@link HandlerMethodReturnValueHandler} for replying directly to a subscription.
- * It is supported on methods annotated with
- * {@link org.springframework.messaging.simp.annotation.SubscribeMapping}
- * unless they're also annotated with {@link SendTo} or {@link SendToUser} in
- * which case a message is sent to the broker instead.
+ * {@code HandlerMethodReturnValueHandler} for replying directly to a
+ * subscription. It is supported on methods annotated with
+ * {@link org.springframework.messaging.simp.annotation.SubscribeMapping
+ * SubscribeMapping} such that the return value is treated as a response to be
+ * sent directly back on the session. This allows a client to implement
+ * a request-response pattern and use it for example to obtain some data upon
+ * initialization.
*
- * <p>The value returned from the method is converted, and turned to a {@link Message}
- * and then enriched with the sessionId, subscriptionId, and destination of the
- * input message. The message is then sent directly back to the connected client.
+ * <p>The value returned from the method is converted and turned into a
+ * {@link Message} that is then enriched with the sessionId, subscriptionId, and
+ * destination of the input message.
+ *
+ * <p><strong>Note:</strong> this default behavior for interpreting the return
+ * value from an {@code @SubscribeMapping} method can be overridden through use
+ * of the {@link SendTo} or {@link SendToUser} annotations in which case a
+ * message is prepared and sent to the broker instead.
*
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
@@ -60,12 +67,12 @@ public class SubscriptionMethodReturnValueHandler implements HandlerMethodReturn
/**
* Construct a new SubscriptionMethodReturnValueHandler.
- * @param messagingTemplate a messaging template to send messages to,
+ * @param template a messaging template to send messages to,
* most likely the "clientOutboundChannel" (must not be {@code null})
*/
- public SubscriptionMethodReturnValueHandler(MessageSendingOperations<String> messagingTemplate) {
- Assert.notNull(messagingTemplate, "messagingTemplate must not be null");
- this.messagingTemplate = messagingTemplate;
+ public SubscriptionMethodReturnValueHandler(MessageSendingOperations<String> template) {
+ Assert.notNull(template, "messagingTemplate must not be null");
+ this.messagingTemplate = template;
}
@@ -88,13 +95,15 @@ public class SubscriptionMethodReturnValueHandler implements HandlerMethodReturn
@Override
public boolean supportsReturnType(MethodParameter returnType) {
- return (returnType.getMethodAnnotation(SubscribeMapping.class) != null &&
- returnType.getMethodAnnotation(SendTo.class) == null &&
- returnType.getMethodAnnotation(SendToUser.class) == null);
+ return (returnType.hasMethodAnnotation(SubscribeMapping.class) &&
+ !returnType.hasMethodAnnotation(SendTo.class) &&
+ !returnType.hasMethodAnnotation(SendToUser.class));
}
@Override
- public void handleReturnValue(Object returnValue, MethodParameter returnType, Message<?> message) throws Exception {
+ public void handleReturnValue(Object returnValue, MethodParameter returnType, Message<?> message)
+ throws Exception {
+
if (returnValue == null) {
return;
}
@@ -105,27 +114,27 @@ public class SubscriptionMethodReturnValueHandler implements HandlerMethodReturn
String subscriptionId = SimpMessageHeaderAccessor.getSubscriptionId(headers);
if (subscriptionId == null) {
- throw new IllegalStateException(
- "No subscriptionId in " + message + " returned by: " + returnType.getMethod());
+ throw new IllegalStateException("No subscriptionId in " + message +
+ " returned by: " + returnType.getMethod());
}
if (logger.isDebugEnabled()) {
logger.debug("Reply to @SubscribeMapping: " + returnValue);
}
- this.messagingTemplate.convertAndSend(
- destination, returnValue, createHeaders(sessionId, subscriptionId, returnType));
+ MessageHeaders headersToSend = createHeaders(sessionId, subscriptionId, returnType);
+ this.messagingTemplate.convertAndSend(destination, returnValue, headersToSend);
}
private MessageHeaders createHeaders(String sessionId, String subscriptionId, MethodParameter returnType) {
- SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
+ SimpMessageHeaderAccessor accessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
if (getHeaderInitializer() != null) {
- getHeaderInitializer().initHeaders(headerAccessor);
+ getHeaderInitializer().initHeaders(accessor);
}
- headerAccessor.setSessionId(sessionId);
- headerAccessor.setSubscriptionId(subscriptionId);
- headerAccessor.setHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER, returnType);
- headerAccessor.setLeaveMutable(true);
- return headerAccessor.getMessageHeaders();
+ accessor.setSessionId(sessionId);
+ accessor.setSubscriptionId(subscriptionId);
+ accessor.setHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER, returnType);
+ accessor.setLeaveMutable(true);
+ return accessor.getMessageHeaders();
}
}
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 97afc43f..9dace2c1 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -50,7 +50,7 @@ import org.springframework.util.PathMatcher;
* in memory and uses a {@link org.springframework.util.PathMatcher PathMatcher}
* for matching destinations.
*
- * <p>As of 4.2 this class supports a {@link #setSelectorHeaderName selector}
+ * <p>As of 4.2, this class supports a {@link #setSelectorHeaderName selector}
* header on subscription messages with Spring EL expressions evaluated against
* the headers to filter out messages in addition to destination matching.
*
@@ -65,11 +65,10 @@ public class DefaultSubscriptionRegistry extends AbstractSubscriptionRegistry {
public static final int DEFAULT_CACHE_LIMIT = 1024;
- /** The maximum number of entries in the cache */
- private volatile int cacheLimit = DEFAULT_CACHE_LIMIT;
-
private PathMatcher pathMatcher = new AntPathMatcher();
+ private volatile int cacheLimit = DEFAULT_CACHE_LIMIT;
+
private String selectorHeaderName = "selector";
private volatile boolean selectorHeaderInUse = false;
@@ -82,32 +81,32 @@ public class DefaultSubscriptionRegistry extends AbstractSubscriptionRegistry {
/**
- * Specify the maximum number of entries for the resolved destination cache.
- * Default is 1024.
+ * Specify the {@link PathMatcher} to use.
*/
- public void setCacheLimit(int cacheLimit) {
- this.cacheLimit = cacheLimit;
+ public void setPathMatcher(PathMatcher pathMatcher) {
+ this.pathMatcher = pathMatcher;
}
/**
- * Return the maximum number of entries for the resolved destination cache.
+ * Return the configured {@link PathMatcher}.
*/
- public int getCacheLimit() {
- return this.cacheLimit;
+ public PathMatcher getPathMatcher() {
+ return this.pathMatcher;
}
/**
- * Specify the {@link PathMatcher} to use.
+ * Specify the maximum number of entries for the resolved destination cache.
+ * Default is 1024.
*/
- public void setPathMatcher(PathMatcher pathMatcher) {
- this.pathMatcher = pathMatcher;
+ public void setCacheLimit(int cacheLimit) {
+ this.cacheLimit = cacheLimit;
}
/**
- * Return the configured {@link PathMatcher}.
+ * Return the maximum number of entries for the resolved destination cache.
*/
- public PathMatcher getPathMatcher() {
- return this.pathMatcher;
+ public int getCacheLimit() {
+ return this.cacheLimit;
}
/**
@@ -123,12 +122,13 @@ public class DefaultSubscriptionRegistry extends AbstractSubscriptionRegistry {
* @since 4.2
*/
public void setSelectorHeaderName(String selectorHeaderName) {
- Assert.notNull(selectorHeaderName);
+ Assert.notNull(selectorHeaderName, "'selectorHeaderName' must not be null");
this.selectorHeaderName = selectorHeaderName;
}
/**
* Return the name for the selector header.
+ * @since 4.2
*/
public String getSelectorHeaderName() {
return this.selectorHeaderName;
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/broker/SimpleBrokerMessageHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/broker/SimpleBrokerMessageHandler.java
index b78ab54a..f58d7e7f 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/simp/broker/SimpleBrokerMessageHandler.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/broker/SimpleBrokerMessageHandler.java
@@ -42,6 +42,7 @@ import org.springframework.util.PathMatcher;
* {@link SubscriptionRegistry} and sends messages to subscribers.
*
* @author Rossen Stoyanchev
+ * @author Juergen Hoeller
* @since 4.0
*/
public class SimpleBrokerMessageHandler extends AbstractBrokerMessageHandler {
@@ -54,6 +55,8 @@ public class SimpleBrokerMessageHandler extends AbstractBrokerMessageHandler {
private PathMatcher pathMatcher;
+ private Integer cacheLimit;
+
private TaskScheduler taskScheduler;
private long[] heartbeatValue;
@@ -90,14 +93,7 @@ public class SimpleBrokerMessageHandler extends AbstractBrokerMessageHandler {
Assert.notNull(subscriptionRegistry, "SubscriptionRegistry must not be null");
this.subscriptionRegistry = subscriptionRegistry;
initPathMatcherToUse();
- }
-
- private void initPathMatcherToUse() {
- if (this.pathMatcher != null) {
- if (this.subscriptionRegistry instanceof DefaultSubscriptionRegistry) {
- ((DefaultSubscriptionRegistry) this.subscriptionRegistry).setPathMatcher(this.pathMatcher);
- }
- }
+ initCacheLimitToUse();
}
public SubscriptionRegistry getSubscriptionRegistry() {
@@ -105,14 +101,46 @@ public class SimpleBrokerMessageHandler extends AbstractBrokerMessageHandler {
}
/**
- * When configured, the given PathMatcher is passed down to the
+ * When configured, the given PathMatcher is passed down to the underlying
* SubscriptionRegistry to use for matching destination to subscriptions.
+ * <p>Default is a standard {@link org.springframework.util.AntPathMatcher}.
+ * @since 4.1
+ * @see #setSubscriptionRegistry
+ * @see DefaultSubscriptionRegistry#setPathMatcher
+ * @see org.springframework.util.AntPathMatcher
*/
public void setPathMatcher(PathMatcher pathMatcher) {
this.pathMatcher = pathMatcher;
initPathMatcherToUse();
}
+ private void initPathMatcherToUse() {
+ if (this.pathMatcher != null && this.subscriptionRegistry instanceof DefaultSubscriptionRegistry) {
+ ((DefaultSubscriptionRegistry) this.subscriptionRegistry).setPathMatcher(this.pathMatcher);
+ }
+ }
+
+ /**
+ * When configured, the specified cache limit is passed down to the
+ * underlying SubscriptionRegistry, overriding any default there.
+ * <p>With a standard {@link DefaultSubscriptionRegistry}, the default
+ * cache limit is 1024.
+ * @since 4.3.2
+ * @see #setSubscriptionRegistry
+ * @see DefaultSubscriptionRegistry#setCacheLimit
+ * @see DefaultSubscriptionRegistry#DEFAULT_CACHE_LIMIT
+ */
+ public void setCacheLimit(Integer cacheLimit) {
+ this.cacheLimit = cacheLimit;
+ initCacheLimitToUse();
+ }
+
+ private void initCacheLimitToUse() {
+ if (this.cacheLimit != null && this.subscriptionRegistry instanceof DefaultSubscriptionRegistry) {
+ ((DefaultSubscriptionRegistry) this.subscriptionRegistry).setCacheLimit(this.cacheLimit);
+ }
+ }
+
/**
* Configure the {@link org.springframework.scheduling.TaskScheduler} to
* use for providing heartbeat support. Setting this property also sets the
@@ -130,6 +158,7 @@ public class SimpleBrokerMessageHandler extends AbstractBrokerMessageHandler {
/**
* Return the configured TaskScheduler.
+ * @since 4.2
*/
public TaskScheduler getTaskScheduler() {
return this.taskScheduler;
@@ -151,6 +180,7 @@ public class SimpleBrokerMessageHandler extends AbstractBrokerMessageHandler {
/**
* The configured value for the heart-beat settings.
+ * @since 4.2
*/
public long[] getHeartbeatValue() {
return this.heartbeatValue;
@@ -160,6 +190,7 @@ public class SimpleBrokerMessageHandler extends AbstractBrokerMessageHandler {
* Configure a {@link MessageHeaderInitializer} to apply to the headers
* of all messages sent to the client outbound channel.
* <p>By default this property is not set.
+ * @since 4.1
*/
public void setHeaderInitializer(MessageHeaderInitializer headerInitializer) {
this.headerInitializer = headerInitializer;
@@ -167,6 +198,7 @@ public class SimpleBrokerMessageHandler extends AbstractBrokerMessageHandler {
/**
* Return the configured header initializer.
+ * @since 4.1
*/
public MessageHeaderInitializer getHeaderInitializer() {
return this.headerInitializer;
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/config/AbstractMessageBrokerConfiguration.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/config/AbstractMessageBrokerConfiguration.java
index fa475b68..c9c9be60 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/simp/config/AbstractMessageBrokerConfiguration.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/config/AbstractMessageBrokerConfiguration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -52,6 +52,7 @@ import org.springframework.messaging.support.ExecutorSubscribableChannel;
import org.springframework.messaging.support.ImmutableMessageChannelInterceptor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.MimeTypeUtils;
import org.springframework.util.PathMatcher;
@@ -85,7 +86,7 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
private static final String MVC_VALIDATOR_NAME = "mvcValidator";
- private static final boolean jackson2Present= ClassUtils.isPresent(
+ private static final boolean jackson2Present = ClassUtils.isPresent(
"com.fasterxml.jackson.databind.ObjectMapper", AbstractMessageBrokerConfiguration.class.getClassLoader());
@@ -142,7 +143,7 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
}
/**
- * A hook for sub-classes to customize the message channel for inbound messages
+ * A hook for subclasses to customize the message channel for inbound messages
* from WebSocket clients.
*/
protected void configureClientInboundChannel(ChannelRegistration registration) {
@@ -175,7 +176,7 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
}
/**
- * A hook for sub-classes to customize the message channel for messages from
+ * A hook for subclasses to customize the message channel for messages from
* the application or message broker to WebSocket clients.
*/
protected void configureClientOutboundChannel(ChannelRegistration registration) {
@@ -223,7 +224,7 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
}
/**
- * A hook for sub-classes to customize message broker configuration through the
+ * A hook for subclasses to customize message broker configuration through the
* provided {@link MessageBrokerRegistry} instance.
*/
protected void configureMessageBroker(MessageBrokerRegistry registry) {
@@ -252,7 +253,7 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
addReturnValueHandlers(returnValueHandlers);
handler.setCustomReturnValueHandlers(returnValueHandlers);
- PathMatcher pathMatcher = this.getBrokerRegistry().getPathMatcher();
+ PathMatcher pathMatcher = getBrokerRegistry().getPathMatcher();
if (pathMatcher != null) {
handler.setPathMatcher(pathMatcher);
}
@@ -260,7 +261,7 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
}
/**
- * Protected method for plugging in a custom sub-class of
+ * Protected method for plugging in a custom subclass of
* {@link org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler
* SimpAnnotationMethodMessageHandler}.
* @since 4.2
@@ -315,12 +316,14 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
if (getBrokerRegistry().getUserRegistryBroadcast() == null) {
return new NoOpMessageHandler();
}
- return new UserRegistryMessageHandler(userRegistry(), brokerMessagingTemplate(),
- getBrokerRegistry().getUserRegistryBroadcast(), messageBrokerTaskScheduler());
+ SimpUserRegistry userRegistry = userRegistry();
+ Assert.isInstanceOf(MultiServerUserRegistry.class, userRegistry);
+ return new UserRegistryMessageHandler((MultiServerUserRegistry) userRegistry,
+ brokerMessagingTemplate(), getBrokerRegistry().getUserRegistryBroadcast(),
+ messageBrokerTaskScheduler());
}
// Expose alias for 4.1 compatibility
-
@Bean(name={"messageBrokerTaskScheduler", "messageBrokerSockJsTaskScheduler"})
public ThreadPoolTaskScheduler messageBrokerTaskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
@@ -380,6 +383,7 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
if (prefix != null) {
resolver.setUserDestinationPrefix(prefix);
}
+ resolver.setPathMatcher(getBrokerRegistry().getPathMatcher());
return resolver;
}
@@ -395,9 +399,9 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
protected abstract SimpUserRegistry createLocalUserRegistry();
/**
- * As of 4.2, UserSessionRegistry is deprecated in favor of SimpUserRegistry
- * exposing information about all connected users. The MultiServerUserRegistry
- * implementation in combination with UserRegistryMessageHandler can be used
+ * As of 4.2, {@code UserSessionRegistry} is deprecated in favor of {@link SimpUserRegistry}
+ * exposing information about all connected users. The {@link MultiServerUserRegistry}
+ * implementation in combination with {@link UserRegistryMessageHandler} can be used
* to share user registries across multiple servers.
*/
@Deprecated
@@ -465,9 +469,9 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
@Override
public void handleMessage(Message<?> message) {
}
-
}
+
private class NoOpBrokerMessageHandler extends AbstractBrokerMessageHandler {
public NoOpBrokerMessageHandler() {
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/config/MessageBrokerRegistry.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/config/MessageBrokerRegistry.java
index 1eabf8fd..3c8dd759 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/simp/config/MessageBrokerRegistry.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/config/MessageBrokerRegistry.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -51,6 +51,8 @@ public class MessageBrokerRegistry {
private PathMatcher pathMatcher;
+ private Integer cacheLimit;
+
public MessageBrokerRegistry(SubscribableChannel clientInboundChannel, MessageChannel clientOutboundChannel) {
Assert.notNull(clientInboundChannel);
@@ -96,6 +98,16 @@ public class MessageBrokerRegistry {
return this.brokerChannelRegistration;
}
+ protected String getUserDestinationBroadcast() {
+ return (this.brokerRelayRegistration != null ?
+ this.brokerRelayRegistration.getUserDestinationBroadcast() : null);
+ }
+
+ protected String getUserRegistryBroadcast() {
+ return (this.brokerRelayRegistration != null ?
+ this.brokerRelayRegistration.getUserRegistryBroadcast() : null);
+ }
+
/**
* Configure one or more prefixes to filter destinations targeting application
* annotated methods. For example destinations prefixed with "/app" may be
@@ -137,16 +149,6 @@ public class MessageBrokerRegistry {
return this.userDestinationPrefix;
}
- protected String getUserDestinationBroadcast() {
- return (this.brokerRelayRegistration != null ?
- this.brokerRelayRegistration.getUserDestinationBroadcast() : null);
- }
-
- protected String getUserRegistryBroadcast() {
- return (this.brokerRelayRegistration != null ?
- this.brokerRelayRegistration.getUserRegistryBroadcast() : null);
- }
-
/**
* Configure the PathMatcher to use to match the destinations of incoming
* messages to {@code @MessageMapping} and {@code @SubscribeMapping} methods.
@@ -162,6 +164,7 @@ public class MessageBrokerRegistry {
* <p>When the simple broker is enabled, the PathMatcher configured here is
* also used to match message destinations when brokering messages.
* @since 4.1
+ * @see org.springframework.messaging.simp.broker.DefaultSubscriptionRegistry#setPathMatcher
*/
public MessageBrokerRegistry setPathMatcher(PathMatcher pathMatcher) {
this.pathMatcher = pathMatcher;
@@ -172,6 +175,18 @@ public class MessageBrokerRegistry {
return this.pathMatcher;
}
+ /**
+ * Configure the cache limit to apply for registrations with the broker.
+ * <p>This is currently only applied for the destination cache in the
+ * subscription registry. The default cache limit there is 1024.
+ * @since 4.3.2
+ * @see org.springframework.messaging.simp.broker.DefaultSubscriptionRegistry#setCacheLimit
+ */
+ public MessageBrokerRegistry setCacheLimit(int cacheLimit) {
+ this.cacheLimit = cacheLimit;
+ return this;
+ }
+
protected SimpleBrokerMessageHandler getSimpleBroker(SubscribableChannel brokerChannel) {
if (this.simpleBrokerRegistration == null && this.brokerRelayRegistration == null) {
@@ -180,6 +195,7 @@ public class MessageBrokerRegistry {
if (this.simpleBrokerRegistration != null) {
SimpleBrokerMessageHandler handler = this.simpleBrokerRegistration.getMessageHandler(brokerChannel);
handler.setPathMatcher(this.pathMatcher);
+ handler.setCacheLimit(this.cacheLimit);
return handler;
}
return null;
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/config/TaskExecutorRegistration.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/config/TaskExecutorRegistration.java
index 80e9843f..39ce0802 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/simp/config/TaskExecutorRegistration.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/config/TaskExecutorRegistration.java
@@ -52,7 +52,7 @@ public class TaskExecutorRegistration {
* in {@link java.util.concurrent.ThreadPoolExecutor ThreadPoolExecutor}. When
* this strategy is used, the {@link #maxPoolSize(int) maxPoolSize} is ignored.
* <p>By default this is set to twice the value of
- * {@link Runtime#availableProcessors()}. In an an application where tasks do not
+ * {@link Runtime#availableProcessors()}. In an application where tasks do not
* block frequently, the number should be closer to or equal to the number of
* available CPUs/cores.
*/
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/DefaultStompSession.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/DefaultStompSession.java
index 71160780..44cb5f8e 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/DefaultStompSession.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/DefaultStompSession.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -67,7 +67,6 @@ public class DefaultStompSession implements ConnectionHandlingStompSession {
private static final Message<byte[]> HEARTBEAT;
-
static {
StompHeaderAccessor accessor = StompHeaderAccessor.createForHeartbeat();
HEARTBEAT = MessageBuilder.createMessage(StompDecoder.HEARTBEAT_PAYLOAD, accessor.getMessageHeaders());
@@ -93,6 +92,8 @@ public class DefaultStompSession implements ConnectionHandlingStompSession {
private volatile TcpConnection<byte[]> connection;
+ private volatile String version;
+
private final AtomicInteger subscriptionIndex = new AtomicInteger();
private final Map<String, DefaultSubscription> subscriptions = new ConcurrentHashMap<String, DefaultSubscription>(4);
@@ -310,6 +311,28 @@ public class DefaultStompSession implements ConnectionHandlingStompSession {
return subscription;
}
+ @Override
+ public Receiptable acknowledge(String messageId, boolean consumed) {
+ StompHeaders stompHeaders = new StompHeaders();
+ if ("1.1".equals(this.version)) {
+ stompHeaders.setMessageId(messageId);
+ }
+ else {
+ stompHeaders.setId(messageId);
+ }
+
+ String receiptId = checkOrAddReceipt(stompHeaders);
+ Receiptable receiptable = new ReceiptHandler(receiptId);
+
+ StompCommand command = (consumed ? StompCommand.ACK : StompCommand.NACK);
+ StompHeaderAccessor accessor = createHeaderAccessor(command);
+ accessor.addNativeHeaders(stompHeaders);
+ Message<byte[]> message = createMessage(accessor, null);
+ execute(message);
+
+ return receiptable;
+ }
+
private void unsubscribe(String id) {
StompHeaderAccessor accessor = createHeaderAccessor(StompCommand.UNSUBSCRIBE);
accessor.setSubscriptionId(id);
@@ -390,6 +413,7 @@ public class DefaultStompSession implements ConnectionHandlingStompSession {
}
else if (StompCommand.CONNECTED.equals(command)) {
initHeartbeatTasks(stompHeaders);
+ this.version = stompHeaders.getFirst("version");
this.sessionFuture.set(this);
this.sessionHandler.afterConnected(this, stompHeaders);
}
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandler.java
index 18ab77f6..f0383acc 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandler.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandler.java
@@ -60,7 +60,7 @@ import org.springframework.util.concurrent.ListenableFutureTask;
*
* <p>This class also automatically opens a default "system" TCP connection to the message
* broker that is used for sending messages that originate from the server application (as
- * opposed to from a client). Such messages are are not associated with any client and
+ * opposed to from a client). Such messages are not associated with any client and
* therefore do not have a session id header. The "system" connection is effectively
* shared and cannot be used to receive messages. Several properties are provided to
* configure the "system" connection including:
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompHeaderAccessor.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompHeaderAccessor.java
index 78db72b1..76971b26 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompHeaderAccessor.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompHeaderAccessor.java
@@ -440,7 +440,7 @@ public class StompHeaderAccessor extends SimpMessageHeaderAccessor {
if (bytes.length == 0 || getContentType() == null || !isReadableContentType()) {
return contentType;
}
- Charset charset = getContentType().getCharSet();
+ Charset charset = getContentType().getCharset();
charset = (charset != null ? charset : StompDecoder.UTF8_CHARSET);
return (bytes.length < 80) ?
contentType + " payload=" + new String(bytes, charset) :
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompSession.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompSession.java
index ec46f518..c8f2f5ee 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompSession.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompSession.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -87,6 +87,19 @@ public interface StompSession {
Subscription subscribe(StompHeaders headers, StompFrameHandler handler);
/**
+ * Send an acknowledgement whether a message was consumed or not resulting
+ * in an ACK or NACK frame respectively.
+ * <p><strong>Note:</strong> to use this when subscribing you must set the
+ * {@link StompHeaders#setAck(String) ack} header to "client" or
+ * "client-individual" in order ot use this.
+ * @param messageId the id of the message
+ * @param consumed whether the message was consumed or not
+ * @return a Receiptable for tracking receipts
+ * @since 4.3
+ */
+ Receiptable acknowledge(String messageId, boolean consumed);
+
+ /**
* Disconnect the session by sending a DISCONNECT frame.
*/
void disconnect();
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/user/DefaultUserDestinationResolver.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/user/DefaultUserDestinationResolver.java
index d8593c71..67a77f0a 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/simp/user/DefaultUserDestinationResolver.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/user/DefaultUserDestinationResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,6 +29,7 @@ import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessageType;
import org.springframework.util.Assert;
+import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
/**
@@ -57,6 +58,8 @@ public class DefaultUserDestinationResolver implements UserDestinationResolver {
private String prefix = "/user/";
+ private boolean keepLeadingSlash = true;
+
/**
* Create an instance that will access user session id information through
@@ -94,6 +97,26 @@ public class DefaultUserDestinationResolver implements UserDestinationResolver {
return this.prefix;
}
+ /**
+ * Provide the {@code PathMatcher} in use for working with destinations
+ * which in turn helps to determine whether the leading slash should be
+ * kept in actual destinations after removing the
+ * {@link #setUserDestinationPrefix userDestinationPrefix}.
+ * <p>By default actual destinations have a leading slash, e.g.
+ * {@code /queue/position-updates} which makes sense with brokers that
+ * support destinations with slash as separator. When a {@code PathMatcher}
+ * is provided that supports an alternative separator, then resulting
+ * destinations won't have a leading slash, e.g. {@code
+ * jms.queue.position-updates}.
+ * @param pathMatcher the PathMatcher used to work with destinations
+ * @since 4.3
+ */
+ public void setPathMatcher(PathMatcher pathMatcher) {
+ if (pathMatcher != null) {
+ this.keepLeadingSlash = pathMatcher.combine("1", "2").equals("1/2");
+ }
+ }
+
@Override
public UserDestinationResult resolveDestination(Message<?> message) {
@@ -131,6 +154,9 @@ public class DefaultUserDestinationResolver implements UserDestinationResolver {
}
int prefixEnd = this.prefix.length() - 1;
String actualDestination = destination.substring(prefixEnd);
+ if (!this.keepLeadingSlash) {
+ actualDestination = actualDestination.substring(1);
+ }
String user = (principal != null ? principal.getName() : null);
return new ParseResult(actualDestination, destination, Collections.singleton(sessionId), user);
}
@@ -165,6 +191,9 @@ public class DefaultUserDestinationResolver implements UserDestinationResolver {
sessionIds = Collections.<String>emptySet();
}
}
+ if (!this.keepLeadingSlash) {
+ actualDestination = actualDestination.substring(1);
+ }
return new ParseResult(actualDestination, subscribeDestination, sessionIds, userName);
}
else {
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/user/MultiServerUserRegistry.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/user/MultiServerUserRegistry.java
index 386797c6..144d7f6c 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/simp/user/MultiServerUserRegistry.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/user/MultiServerUserRegistry.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,10 +35,11 @@ import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
- * A user registry that is a composite of the "local" user registry as well as
- * snapshots of remote user registries. For use with
- * {@link UserRegistryMessageHandler} which broadcasts periodically the content
- * of the local registry and receives updates from other servers.
+ * {@code SimpUserRegistry} that looks up users in a "local" user registry as
+ * well as a set of "remote" user registries. The local registry is provided as
+ * a constructor argument while remote registries are updated via broadcasts
+ * handled by {@link UserRegistryMessageHandler} which in turn notifies this
+ * registry when updates are received.
*
* @author Rossen Stoyanchev
* @since 4.2
@@ -50,10 +51,12 @@ public class MultiServerUserRegistry implements SimpUserRegistry, SmartApplicati
private final SimpUserRegistry localRegistry;
- private final SmartApplicationListener listener;
+ private final Map<String, UserRegistrySnapshot> remoteRegistries = new ConcurrentHashMap<String, UserRegistrySnapshot>();
- private final Map<String, UserRegistryDto> remoteRegistries =
- new ConcurrentHashMap<String, UserRegistryDto>();
+ private final boolean delegateApplicationEvents;
+
+ /* Cross-server session lookup (e.g. same user connected to multiple servers) */
+ private final SessionLookup sessionLookup = new SessionLookup();
/**
@@ -61,13 +64,11 @@ public class MultiServerUserRegistry implements SimpUserRegistry, SmartApplicati
*/
public MultiServerUserRegistry(SimpUserRegistry localRegistry) {
Assert.notNull(localRegistry, "'localRegistry' is required.");
- this.localRegistry = localRegistry;
- this.listener = (this.localRegistry instanceof SmartApplicationListener ?
- (SmartApplicationListener) this.localRegistry : new NoOpSmartApplicationListener());
this.id = generateId();
+ this.localRegistry = localRegistry;
+ this.delegateApplicationEvents = this.localRegistry instanceof SmartApplicationListener;
}
-
private static String generateId() {
String host;
try {
@@ -81,110 +82,132 @@ public class MultiServerUserRegistry implements SimpUserRegistry, SmartApplicati
@Override
- public SimpUser getUser(String userName) {
- SimpUser user = this.localRegistry.getUser(userName);
- if (user != null) {
- return user;
+ public int getOrder() {
+ return (this.delegateApplicationEvents ?
+ ((SmartApplicationListener) this.localRegistry).getOrder() : Ordered.LOWEST_PRECEDENCE);
+ }
+
+ // SmartApplicationListener methods
+
+ @Override
+ public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
+ return (this.delegateApplicationEvents &&
+ ((SmartApplicationListener) this.localRegistry).supportsEventType(eventType));
+ }
+
+ @Override
+ public boolean supportsSourceType(Class<?> sourceType) {
+ return (this.delegateApplicationEvents &&
+ ((SmartApplicationListener) this.localRegistry).supportsSourceType(sourceType));
+ }
+
+ @Override
+ public void onApplicationEvent(ApplicationEvent event) {
+ if (this.delegateApplicationEvents) {
+ ((SmartApplicationListener) this.localRegistry).onApplicationEvent(event);
}
- for (UserRegistryDto registry : this.remoteRegistries.values()) {
- user = registry.getUsers().get(userName);
+ }
+
+ // SimpUserRegistry methods
+
+ @Override
+ public SimpUser getUser(String userName) {
+ // Prefer remote registries due to cross-server SessionLookup
+ for (UserRegistrySnapshot registry : this.remoteRegistries.values()) {
+ SimpUser user = registry.getUserMap().get(userName);
if (user != null) {
return user;
}
}
- return null;
+ return this.localRegistry.getUser(userName);
}
@Override
public Set<SimpUser> getUsers() {
- Set<SimpUser> result = new HashSet<SimpUser>(this.localRegistry.getUsers());
- for (UserRegistryDto registry : this.remoteRegistries.values()) {
- result.addAll(registry.getUsers().values());
+ // Prefer remote registries due to cross-server SessionLookup
+ Set<SimpUser> result = new HashSet<SimpUser>();
+ for (UserRegistrySnapshot registry : this.remoteRegistries.values()) {
+ result.addAll(registry.getUserMap().values());
}
+ result.addAll(this.localRegistry.getUsers());
return result;
}
@Override
public Set<SimpSubscription> findSubscriptions(SimpSubscriptionMatcher matcher) {
- Set<SimpSubscription> result = new HashSet<SimpSubscription>(this.localRegistry.findSubscriptions(matcher));
- for (UserRegistryDto registry : this.remoteRegistries.values()) {
+ Set<SimpSubscription> result = new HashSet<SimpSubscription>();
+ for (UserRegistrySnapshot registry : this.remoteRegistries.values()) {
result.addAll(registry.findSubscriptions(matcher));
}
+ result.addAll(this.localRegistry.findSubscriptions(matcher));
return result;
}
- @Override
- public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
- return this.listener.supportsEventType(eventType);
- }
-
- @Override
- public boolean supportsSourceType(Class<?> sourceType) {
- return this.listener.supportsSourceType(sourceType);
- }
-
- @Override
- public void onApplicationEvent(ApplicationEvent event) {
- this.listener.onApplicationEvent(event);
- }
-
- @Override
- public int getOrder() {
- return this.listener.getOrder();
- }
+ // Internal methods for UserRegistryMessageHandler to manage broadcasts
Object getLocalRegistryDto() {
- return new UserRegistryDto(this.id, this.localRegistry);
+ return new UserRegistrySnapshot(this.id, this.localRegistry);
}
void addRemoteRegistryDto(Message<?> message, MessageConverter converter, long expirationPeriod) {
- UserRegistryDto registryDto = (UserRegistryDto) converter.fromMessage(message, UserRegistryDto.class);
- if (registryDto != null && !registryDto.getId().equals(this.id)) {
- long expirationTime = System.currentTimeMillis() + expirationPeriod;
- registryDto.setExpirationTime(expirationTime);
- registryDto.restoreParentReferences();
- this.remoteRegistries.put(registryDto.getId(), registryDto);
+ UserRegistrySnapshot registry = (UserRegistrySnapshot) converter.fromMessage(message, UserRegistrySnapshot.class);
+ if (registry != null && !registry.getId().equals(this.id)) {
+ registry.init(expirationPeriod, this.sessionLookup);
+ this.remoteRegistries.put(registry.getId(), registry);
}
}
void purgeExpiredRegistries() {
long now = System.currentTimeMillis();
- Iterator<Map.Entry<String, UserRegistryDto>> iterator = this.remoteRegistries.entrySet().iterator();
+ Iterator<Map.Entry<String, UserRegistrySnapshot>> iterator = this.remoteRegistries.entrySet().iterator();
while (iterator.hasNext()) {
- Map.Entry<String, UserRegistryDto> entry = iterator.next();
- if (now > entry.getValue().getExpirationTime()) {
+ Map.Entry<String, UserRegistrySnapshot> entry = iterator.next();
+ if (entry.getValue().isExpired(now)) {
iterator.remove();
}
}
}
+
@Override
public String toString() {
return "local=[" + this.localRegistry + "], remote=" + this.remoteRegistries + "]";
}
+ /**
+ * Holds a copy of a SimpUserRegistry for the purpose of broadcasting to and
+ * receiving broadcasts from other application servers.
+ */
@SuppressWarnings("unused")
- private static class UserRegistryDto {
+ private static class UserRegistrySnapshot {
private String id;
- private Map<String, SimpUserDto> users;
+ private Map<String, TransferSimpUser> users;
private long expirationTime;
- public UserRegistryDto() {
+
+ /**
+ * Default constructor for JSON deserialization.
+ */
+ public UserRegistrySnapshot() {
}
- public UserRegistryDto(String id, SimpUserRegistry registry) {
+ /**
+ * Constructor to create DTO from a local user registry.
+ */
+ public UserRegistrySnapshot(String id, SimpUserRegistry registry) {
this.id = id;
Set<SimpUser> users = registry.getUsers();
- this.users = new HashMap<String, SimpUserDto>(users.size());
+ this.users = new HashMap<String, TransferSimpUser>(users.size());
for (SimpUser user : users) {
- this.users.put(user.getName(), new SimpUserDto(user));
+ this.users.put(user.getName(), new TransferSimpUser(user));
}
}
+
public void setId(String id) {
this.id = id;
}
@@ -193,18 +216,31 @@ public class MultiServerUserRegistry implements SimpUserRegistry, SmartApplicati
return this.id;
}
- public void setUsers(Map<String, SimpUserDto> users) {
+ public void setUserMap(Map<String, TransferSimpUser> users) {
this.users = users;
}
- public Map<String, SimpUserDto> getUsers() {
+ public Map<String, TransferSimpUser> getUserMap() {
return this.users;
}
+ public boolean isExpired(long now) {
+ return (now > this.expirationTime);
+ }
+
+
+ public void init(long expirationPeriod, SessionLookup sessionLookup) {
+ this.expirationTime = System.currentTimeMillis() + expirationPeriod;
+ for (TransferSimpUser user : this.users.values()) {
+ user.afterDeserialization(sessionLookup);
+ }
+ }
+
+
public Set<SimpSubscription> findSubscriptions(SimpSubscriptionMatcher matcher) {
Set<SimpSubscription> result = new HashSet<SimpSubscription>();
- for (SimpUserDto user : this.users.values()) {
- for (SimpSessionDto session : user.sessions) {
+ for (TransferSimpUser user : this.users.values()) {
+ for (TransferSimpSession session : user.sessions) {
for (SimpSubscription subscription : session.subscriptions) {
if (matcher.match(subscription)) {
result.add(subscription);
@@ -215,46 +251,50 @@ public class MultiServerUserRegistry implements SimpUserRegistry, SmartApplicati
return result;
}
- public void setExpirationTime(long expirationTime) {
- this.expirationTime = expirationTime;
- }
-
- public long getExpirationTime() {
- return this.expirationTime;
- }
- private void restoreParentReferences() {
- for (SimpUserDto user : this.users.values()) {
- user.restoreParentReferences();
- }
- }
@Override
public String toString() {
return "id=" + this.id + ", users=" + this.users;
}
+
}
+ /**
+ * SimpUser that can be (de)serialized and broadcast to other servers.
+ */
@SuppressWarnings("unused")
- private static class SimpUserDto implements SimpUser {
+ private static class TransferSimpUser implements SimpUser {
private String name;
- private Set<SimpSessionDto> sessions;
+ /* User sessions from "this" registry only (i.e. one server) */
+ private Set<TransferSimpSession> sessions;
- public SimpUserDto() {
- this.sessions = new HashSet<SimpSessionDto>(1);
+ /* Cross-server session lookup (e.g. user connected to multiple servers) */
+ private SessionLookup sessionLookup;
+
+
+ /**
+ * Default constructor for JSON deserialization.
+ */
+ public TransferSimpUser() {
+ this.sessions = new HashSet<TransferSimpSession>(1);
}
- public SimpUserDto(SimpUser user) {
+ /**
+ * Constructor to create user from a local user.
+ */
+ public TransferSimpUser(SimpUser user) {
this.name = user.getName();
Set<SimpSession> sessions = user.getSessions();
- this.sessions = new HashSet<SimpSessionDto>(sessions.size());
+ this.sessions = new HashSet<TransferSimpSession>(sessions.size());
for (SimpSession session : sessions) {
- this.sessions.add(new SimpSessionDto(session));
+ this.sessions.add(new TransferSimpSession(session));
}
}
+
public void setName(String name) {
this.name = name;
}
@@ -266,12 +306,18 @@ public class MultiServerUserRegistry implements SimpUserRegistry, SmartApplicati
@Override
public boolean hasSessions() {
+ if (this.sessionLookup != null) {
+ return !this.sessionLookup.findSessions(getName()).isEmpty();
+ }
return !this.sessions.isEmpty();
}
@Override
- public SimpSessionDto getSession(String sessionId) {
- for (SimpSessionDto session : this.sessions) {
+ public SimpSession getSession(String sessionId) {
+ if (this.sessionLookup != null) {
+ return this.sessionLookup.findSessions(getName()).get(sessionId);
+ }
+ for (TransferSimpSession session : this.sessions) {
if (session.getId().equals(sessionId)) {
return session;
}
@@ -279,22 +325,34 @@ public class MultiServerUserRegistry implements SimpUserRegistry, SmartApplicati
return null;
}
- public void setSessions(Set<SimpSessionDto> sessions) {
+ public void setSessions(Set<TransferSimpSession> sessions) {
this.sessions.addAll(sessions);
}
@Override
public Set<SimpSession> getSessions() {
+ if (this.sessionLookup != null) {
+ Map<String, SimpSession> sessions = this.sessionLookup.findSessions(getName());
+ return new HashSet<SimpSession>(sessions.values());
+ }
return new HashSet<SimpSession>(this.sessions);
}
- private void restoreParentReferences() {
- for (SimpSessionDto session : this.sessions) {
+ private void afterDeserialization(SessionLookup sessionLookup) {
+ this.sessionLookup = sessionLookup;
+ for (TransferSimpSession session : this.sessions) {
session.setUser(this);
- session.restoreParentReferences();
+ session.afterDeserialization();
}
}
+ private void addSessions(Map<String, SimpSession> map) {
+ for (SimpSession session : this.sessions) {
+ map.put(session.getId(), session);
+ }
+ }
+
+
@Override
public boolean equals(Object other) {
return (this == other || (other instanceof SimpUser && this.name.equals(((SimpUser) other).getName())));
@@ -311,26 +369,35 @@ public class MultiServerUserRegistry implements SimpUserRegistry, SmartApplicati
}
}
-
+ /**
+ * SimpSession that can be (de)serialized and broadcast to other servers.
+ */
@SuppressWarnings("unused")
- private static class SimpSessionDto implements SimpSession {
+ private static class TransferSimpSession implements SimpSession {
private String id;
- private SimpUserDto user;
+ private TransferSimpUser user;
+
+ private final Set<TransferSimpSubscription> subscriptions;
- private final Set<SimpSubscriptionDto> subscriptions;
- public SimpSessionDto() {
- this.subscriptions = new HashSet<SimpSubscriptionDto>(4);
+ /**
+ * Default constructor for JSON deserialization.
+ */
+ public TransferSimpSession() {
+ this.subscriptions = new HashSet<TransferSimpSubscription>(4);
}
- public SimpSessionDto(SimpSession session) {
+ /**
+ * Constructor to create DTO from the local user session.
+ */
+ public TransferSimpSession(SimpSession session) {
this.id = session.getId();
Set<SimpSubscription> subscriptions = session.getSubscriptions();
- this.subscriptions = new HashSet<SimpSubscriptionDto>(subscriptions.size());
+ this.subscriptions = new HashSet<TransferSimpSubscription>(subscriptions.size());
for (SimpSubscription subscription : subscriptions) {
- this.subscriptions.add(new SimpSubscriptionDto(subscription));
+ this.subscriptions.add(new TransferSimpSubscription(subscription));
}
}
@@ -343,16 +410,16 @@ public class MultiServerUserRegistry implements SimpUserRegistry, SmartApplicati
return this.id;
}
- public void setUser(SimpUserDto user) {
+ public void setUser(TransferSimpUser user) {
this.user = user;
}
@Override
- public SimpUserDto getUser() {
+ public TransferSimpUser getUser() {
return this.user;
}
- public void setSubscriptions(Set<SimpSubscriptionDto> subscriptions) {
+ public void setSubscriptions(Set<TransferSimpSubscription> subscriptions) {
this.subscriptions.addAll(subscriptions);
}
@@ -361,8 +428,8 @@ public class MultiServerUserRegistry implements SimpUserRegistry, SmartApplicati
return new HashSet<SimpSubscription>(this.subscriptions);
}
- private void restoreParentReferences() {
- for (SimpSubscriptionDto subscription : this.subscriptions) {
+ private void afterDeserialization() {
+ for (TransferSimpSubscription subscription : this.subscriptions) {
subscription.setSession(this);
}
}
@@ -383,24 +450,34 @@ public class MultiServerUserRegistry implements SimpUserRegistry, SmartApplicati
}
}
-
+ /**
+ * SimpSubscription that can be (de)serialized and broadcast to other servers.
+ */
@SuppressWarnings("unused")
- private static class SimpSubscriptionDto implements SimpSubscription {
+ private static class TransferSimpSubscription implements SimpSubscription {
private String id;
- private SimpSessionDto session;
+ private TransferSimpSession session;
private String destination;
- public SimpSubscriptionDto() {
+
+ /**
+ * Default constructor for JSON deserialization.
+ */
+ public TransferSimpSubscription() {
}
- public SimpSubscriptionDto(SimpSubscription subscription) {
+ /**
+ * Constructor to create DTO from a local user subscription.
+ */
+ public TransferSimpSubscription(SimpSubscription subscription) {
this.id = subscription.getId();
this.destination = subscription.getDestination();
}
+
public void setId(String id) {
this.id = id;
}
@@ -410,12 +487,12 @@ public class MultiServerUserRegistry implements SimpUserRegistry, SmartApplicati
return this.id;
}
- public void setSession(SimpSessionDto session) {
+ public void setSession(TransferSimpSession session) {
this.session = session;
}
@Override
- public SimpSessionDto getSession() {
+ public TransferSimpSession getSession() {
return this.session;
}
@@ -453,26 +530,28 @@ public class MultiServerUserRegistry implements SimpUserRegistry, SmartApplicati
}
- private static class NoOpSmartApplicationListener implements SmartApplicationListener {
-
- @Override
- public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
- return false;
- }
-
- @Override
- public boolean supportsSourceType(Class<?> sourceType) {
- return false;
- }
+ /**
+ * Helper class to find user sessions across all servers.
+ */
+ private class SessionLookup {
- @Override
- public void onApplicationEvent(ApplicationEvent event) {
+ public Map<String, SimpSession> findSessions(String userName) {
+ Map<String, SimpSession> map = new HashMap<String, SimpSession>(1);
+ SimpUser user = localRegistry.getUser(userName);
+ if (user != null) {
+ for (SimpSession session : user.getSessions()) {
+ map.put(session.getId(), session);
+ }
+ }
+ for (UserRegistrySnapshot registry : remoteRegistries.values()) {
+ TransferSimpUser transferUser = registry.getUserMap().get(userName);
+ if (transferUser != null) {
+ transferUser.addSessions(map);
+ }
+ }
+ return map;
}
- @Override
- public int getOrder() {
- return Ordered.LOWEST_PRECEDENCE;
- }
}
}
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/user/UserRegistryMessageHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/user/UserRegistryMessageHandler.java
index d5ac6e90..983127cd 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/simp/user/UserRegistryMessageHandler.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/user/UserRegistryMessageHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,9 +31,11 @@ import org.springframework.scheduling.TaskScheduler;
import org.springframework.util.Assert;
/**
- * A MessageHandler that is subscribed to listen to broadcasts of user registry
- * information from other application servers as well as to periodically
- * broadcast the content of the local user registry. The aggregated information
+ * {@code MessageHandler} that handles user registry broadcasts from other
+ * application servers and periodically broadcasts the content of the local
+ * user registry.
+ *
+ * The aggregated information
* is maintained in a {@link MultiServerUserRegistry}.
*
* @author Rossen Stoyanchev
@@ -56,16 +58,22 @@ public class UserRegistryMessageHandler implements MessageHandler, ApplicationLi
private long registryExpirationPeriod = 20 * 1000;
- public UserRegistryMessageHandler(SimpUserRegistry userRegistry, SimpMessagingTemplate brokerTemplate,
- String broadcastDestination, TaskScheduler scheduler) {
+ /**
+ * Constructor.
+ * @param userRegistry the registry with local and remote user registry information
+ * @param brokerTemplate template for broadcasting local registry information
+ * @param broadcastDestination the destination to broadcast to
+ * @param scheduler
+ */
+ public UserRegistryMessageHandler(MultiServerUserRegistry userRegistry,
+ SimpMessagingTemplate brokerTemplate, String broadcastDestination, TaskScheduler scheduler) {
Assert.notNull(userRegistry, "'userRegistry' is required");
- Assert.isInstanceOf(MultiServerUserRegistry.class, userRegistry);
Assert.notNull(brokerTemplate, "'brokerTemplate' is required");
Assert.hasText(broadcastDestination, "'broadcastDestination' is required");
Assert.notNull(scheduler, "'scheduler' is required");
- this.userRegistry = (MultiServerUserRegistry) userRegistry;
+ this.userRegistry = userRegistry;
this.brokerTemplate = brokerTemplate;
this.broadcastDestination = broadcastDestination;
this.scheduler = scheduler;
@@ -73,20 +81,21 @@ public class UserRegistryMessageHandler implements MessageHandler, ApplicationLi
/**
- * Return the destination for broadcasting user registry information to.
+ * Return the configured destination for broadcasting UserRegistry information.
*/
public String getBroadcastDestination() {
return this.broadcastDestination;
}
/**
- * Configure how long before a remote registry snapshot expires.
- * <p>By default this is set to 20000 (20 seconds).
- * @param expirationPeriod the expiration period in milliseconds
+ * Configure the amount of time (in milliseconds) before a remote user
+ * registry snapshot is considered expired.
+ * <p>By default this is set to 20 seconds (value of 20000).
+ * @param milliseconds the expiration period in milliseconds
*/
@SuppressWarnings("unused")
- public void setRegistryExpirationPeriod(long expirationPeriod) {
- this.registryExpirationPeriod = expirationPeriod;
+ public void setRegistryExpirationPeriod(long milliseconds) {
+ this.registryExpirationPeriod = milliseconds;
}
/**
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/user/UserSessionRegistryAdapter.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/user/UserSessionRegistryAdapter.java
index ffe4eb00..9be04989 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/simp/user/UserSessionRegistryAdapter.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/user/UserSessionRegistryAdapter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,7 +25,14 @@ import java.util.Set;
import org.springframework.util.CollectionUtils;
/**
- * A temporary adapter to allow use of deprecated {@link UserSessionRegistry}.
+ * An adapter that allows a {@code UserSessionRegistry}, which is deprecated in
+ * favor of {@code SimpUserRegistry}, to be used as a {@code SimpUserRegistry}.
+ * Due to the more limited information available, methods such as
+ * {@link #getUsers()} and {@link #findSubscriptions} are not supported.
+ *
+ * <p>As of 4.2, this adapter is used only in applications that explicitly
+ * register a custom {@code UserSessionRegistry} bean by overriding
+ * {@link org.springframework.messaging.simp.config.AbstractMessageBrokerConfiguration#userSessionRegistry()}.
*
* @author Rossen Stoyanchev
* @since 4.2
@@ -33,18 +40,18 @@ import org.springframework.util.CollectionUtils;
@SuppressWarnings("deprecation")
public class UserSessionRegistryAdapter implements SimpUserRegistry {
- private final UserSessionRegistry delegate;
+ private final UserSessionRegistry userSessionRegistry;
- public UserSessionRegistryAdapter(UserSessionRegistry delegate) {
- this.delegate = delegate;
+ public UserSessionRegistryAdapter(UserSessionRegistry registry) {
+ this.userSessionRegistry = registry;
}
@Override
public SimpUser getUser(String userName) {
- Set<String> sessionIds = this.delegate.getSessionIds(userName);
- return (!CollectionUtils.isEmpty(sessionIds) ? new SimpleSimpUser(userName, sessionIds) : null);
+ Set<String> sessionIds = this.userSessionRegistry.getSessionIds(userName);
+ return (!CollectionUtils.isEmpty(sessionIds) ? new SimpUserAdapter(userName, sessionIds) : null);
}
@Override
@@ -58,17 +65,21 @@ public class UserSessionRegistryAdapter implements SimpUserRegistry {
}
- private static class SimpleSimpUser implements SimpUser {
+ /**
+ * Expose the only information available from a UserSessionRegistry
+ * (name and session id's) as a {@code SimpUser}.
+ */
+ private static class SimpUserAdapter implements SimpUser {
private final String name;
private final Map<String, SimpSession> sessions;
- public SimpleSimpUser(String name, Set<String> sessionIds) {
+ public SimpUserAdapter(String name, Set<String> sessionIds) {
this.name = name;
this.sessions = new HashMap<String, SimpSession>(sessionIds.size());
for (String sessionId : sessionIds) {
- this.sessions.put(sessionId, new SimpleSimpSession(sessionId));
+ this.sessions.put(sessionId, new SimpSessionAdapter(sessionId));
}
}
@@ -94,11 +105,15 @@ public class UserSessionRegistryAdapter implements SimpUserRegistry {
}
- private static class SimpleSimpSession implements SimpSession {
+ /**
+ * Expose the only information available from a UserSessionRegistry
+ * (session ids but no subscriptions) as a {@code SimpSession}.
+ */
+ private static class SimpSessionAdapter implements SimpSession {
private final String id;
- public SimpleSimpSession(String id) {
+ public SimpSessionAdapter(String id) {
this.id = id;
}
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/support/MessageHeaderAccessor.java b/spring-messaging/src/main/java/org/springframework/messaging/support/MessageHeaderAccessor.java
index 8a84f6f8..cbd11bb4 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/support/MessageHeaderAccessor.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/support/MessageHeaderAccessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -107,7 +107,7 @@ import org.springframework.util.StringUtils;
* </pre>
*
* <p>Note that the above examples aim to demonstrate the general idea of using
- * header accessors. The most likely usage however is through sub-classes.
+ * header accessors. The most likely usage however is through subclasses.
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
@@ -306,12 +306,17 @@ public class MessageHeaderAccessor {
throw new IllegalArgumentException("'" + name + "' header is read-only");
}
verifyType(name, value);
- if (!ObjectUtils.nullSafeEquals(value, getHeader(name))) {
- this.modified = true;
- if (value != null) {
+ if (value != null) {
+ // Modify header if necessary
+ if (!ObjectUtils.nullSafeEquals(value, getHeader(name))) {
+ this.modified = true;
this.headers.getRawHeaders().put(name, value);
}
- else {
+ }
+ else {
+ // Remove header if available
+ if (this.headers.containsKey(name)) {
+ this.modified = true;
this.headers.getRawHeaders().remove(name);
}
}
@@ -501,7 +506,7 @@ public class MessageHeaderAccessor {
else if (payload instanceof byte[]) {
byte[] bytes = (byte[]) payload;
if (isReadableContentType()) {
- Charset charset = getContentType().getCharSet();
+ Charset charset = getContentType().getCharset();
charset = (charset != null ? charset : DEFAULT_CHARSET);
return (bytes.length < 80) ?
" payload=" + new String(bytes, charset) :
@@ -526,7 +531,7 @@ public class MessageHeaderAccessor {
else if (payload instanceof byte[]) {
byte[] bytes = (byte[]) payload;
if (isReadableContentType()) {
- Charset charset = getContentType().getCharSet();
+ Charset charset = getContentType().getCharset();
charset = (charset != null ? charset : DEFAULT_CHARSET);
return " payload=" + new String(bytes, charset);
}
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/tcp/TcpConnection.java b/spring-messaging/src/main/java/org/springframework/messaging/tcp/TcpConnection.java
index e7f59252..a4062e43 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/tcp/TcpConnection.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/tcp/TcpConnection.java
@@ -39,14 +39,14 @@ public interface TcpConnection<P> extends Closeable {
ListenableFuture<Void> send(Message<P> message);
/**
- * Register a task to invoke after a period of of read inactivity.
+ * Register a task to invoke after a period of read inactivity.
* @param runnable the task to invoke
* @param duration the amount of inactive time in milliseconds
*/
void onReadInactivity(Runnable runnable, long duration);
/**
- * Register a task to invoke after a period of of write inactivity.
+ * Register a task to invoke after a period of write inactivity.
* @param runnable the task to invoke
* @param duration the amount of inactive time in milliseconds
*/
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/tcp/reactor/AbstractPromiseToListenableFutureAdapter.java b/spring-messaging/src/main/java/org/springframework/messaging/tcp/reactor/AbstractPromiseToListenableFutureAdapter.java
index d2d6bc4e..633bd801 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/tcp/reactor/AbstractPromiseToListenableFutureAdapter.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/tcp/reactor/AbstractPromiseToListenableFutureAdapter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,12 +53,15 @@ abstract class AbstractPromiseToListenableFutureAdapter<S, T> implements Listena
this.promise.onSuccess(new Consumer<S>() {
@Override
public void accept(S result) {
+ T adapted;
try {
- registry.success(adapt(result));
+ adapted = adapt(result);
}
catch (Throwable ex) {
registry.failure(ex);
+ return;
}
+ registry.success(adapted);
}
});
diff --git a/spring-messaging/src/test/java/org/springframework/messaging/converter/AbstractMessageConverterTests.java b/spring-messaging/src/test/java/org/springframework/messaging/converter/AbstractMessageConverterTests.java
deleted file mode 100644
index 3f2524e8..00000000
--- a/spring-messaging/src/test/java/org/springframework/messaging/converter/AbstractMessageConverterTests.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright 2002-2013 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.messaging.converter;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import org.springframework.messaging.Message;
-import org.springframework.messaging.MessageHeaders;
-import org.springframework.messaging.support.MessageBuilder;
-import org.springframework.util.MimeType;
-import org.springframework.util.MimeTypeUtils;
-
-import static org.junit.Assert.*;
-
-/**
- * Test fixture for {@link org.springframework.messaging.converter.AbstractMessageConverter}.
- *
- * @author Rossen Stoyanchev
- */
-public class AbstractMessageConverterTests {
-
- private TestMessageConverter converter;
-
-
- @Before
- public void setup() {
- this.converter = new TestMessageConverter();
- this.converter.setContentTypeResolver(new DefaultContentTypeResolver());
- }
-
- @Test
- public void supportsTargetClass() {
- Message<String> message = MessageBuilder.withPayload("ABC").build();
-
- assertEquals("success-from", this.converter.fromMessage(message, String.class));
- assertNull(this.converter.fromMessage(message, Integer.class));
- }
-
- @Test
- public void supportsMimeType() {
- Message<String> message = MessageBuilder.withPayload(
- "ABC").setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.TEXT_PLAIN).build();
-
- assertEquals("success-from", this.converter.fromMessage(message, String.class));
- }
-
- @Test
- public void supportsMimeTypeNotSupported() {
- Message<String> message = MessageBuilder.withPayload(
- "ABC").setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON).build();
-
- assertNull(this.converter.fromMessage(message, String.class));
- }
-
- @Test
- public void supportsMimeTypeNotSpecified() {
- Message<String> message = MessageBuilder.withPayload("ABC").build();
- assertEquals("success-from", this.converter.fromMessage(message, String.class));
- }
-
- @Test
- public void supportsMimeTypeNoneConfigured() {
-
- Message<String> message = MessageBuilder.withPayload(
- "ABC").setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON).build();
-
- this.converter = new TestMessageConverter(Collections.<MimeType>emptyList());
- this.converter.setContentTypeResolver(new DefaultContentTypeResolver());
-
- assertEquals("success-from", this.converter.fromMessage(message, String.class));
- }
-
- @Test
- public void toMessageHeadersCopied() {
- Map<String, Object> map = new HashMap<String, Object>();
- map.put("foo", "bar");
- MessageHeaders headers = new MessageHeaders(map );
- Message<?> message = this.converter.toMessage("ABC", headers);
-
- assertEquals("bar", message.getHeaders().get("foo"));
- }
-
- @Test
- public void toMessageContentTypeHeader() {
- Message<?> message = this.converter.toMessage("ABC", null);
- assertEquals(MimeTypeUtils.TEXT_PLAIN, message.getHeaders().get(MessageHeaders.CONTENT_TYPE));
- }
-
-
- private static class TestMessageConverter extends AbstractMessageConverter {
-
- public TestMessageConverter() {
- super(MimeTypeUtils.TEXT_PLAIN);
- }
-
- public TestMessageConverter(Collection<MimeType> supportedMimeTypes) {
- super(supportedMimeTypes);
- }
-
- @Override
- protected boolean supports(Class<?> clazz) {
- return String.class.equals(clazz);
- }
-
- @Override
- public Object convertFromInternal(Message<?> message, Class<?> targetClass) {
- return "success-from";
- }
-
- @Override
- public Object convertToInternal(Object payload, MessageHeaders headers) {
- return "success-to";
- }
- }
-
-}
diff --git a/spring-messaging/src/test/java/org/springframework/messaging/converter/MessageConverterTests.java b/spring-messaging/src/test/java/org/springframework/messaging/converter/MessageConverterTests.java
index f10f2a17..be6d9cf6 100644
--- a/spring-messaging/src/test/java/org/springframework/messaging/converter/MessageConverterTests.java
+++ b/spring-messaging/src/test/java/org/springframework/messaging/converter/MessageConverterTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +44,7 @@ public class MessageConverterTests {
private TestMessageConverter converter = new TestMessageConverter();
+
@Test
public void supportsTargetClass() {
Message<String> message = MessageBuilder.withPayload("ABC").build();
@@ -105,7 +106,7 @@ public class MessageConverterTests {
@Test
public void toMessageWithHeaders() {
- Map<String, Object> map = new HashMap<String, Object>();
+ Map<String, Object> map = new HashMap<>();
map.put("foo", "bar");
MessageHeaders headers = new MessageHeaders(map);
Message<?> message = this.converter.toMessage("ABC", headers);
diff --git a/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/AnnotationExceptionHandlerMethodResolverTests.java b/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/AnnotationExceptionHandlerMethodResolverTests.java
index e63673e0..2966a290 100644
--- a/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/AnnotationExceptionHandlerMethodResolverTests.java
+++ b/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/AnnotationExceptionHandlerMethodResolverTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +33,7 @@ import static org.junit.Assert.*;
* Test fixture for {@link AnnotationExceptionHandlerMethodResolver} tests.
*
* @author Rossen Stoyanchev
+ * @author Juergen Hoeller
*/
public class AnnotationExceptionHandlerMethodResolverTests {
@@ -51,6 +52,13 @@ public class AnnotationExceptionHandlerMethodResolverTests {
}
@Test
+ public void resolveMethodFromArgumentWithErrorType() {
+ AnnotationExceptionHandlerMethodResolver resolver = new AnnotationExceptionHandlerMethodResolver(ExceptionController.class);
+ AssertionError exception = new AssertionError();
+ assertEquals("handleAssertionError", resolver.resolveMethod(new IllegalStateException(exception)).getName());
+ }
+
+ @Test
public void resolveMethodExceptionSubType() {
AnnotationExceptionHandlerMethodResolver resolver = new AnnotationExceptionHandlerMethodResolver(ExceptionController.class);
IOException ioException = new FileNotFoundException();
@@ -91,6 +99,7 @@ public class AnnotationExceptionHandlerMethodResolverTests {
new AnnotationExceptionHandlerMethodResolver(NoExceptionController.class);
}
+
@Controller
static class ExceptionController {
@@ -107,8 +116,13 @@ public class AnnotationExceptionHandlerMethodResolverTests {
@MessageExceptionHandler
public void handleIllegalArgumentException(IllegalArgumentException exception) {
}
+
+ @MessageExceptionHandler
+ public void handleAssertionError(AssertionError exception) {
+ }
}
+
@Controller
static class InheritedController extends ExceptionController {
@@ -117,6 +131,7 @@ public class AnnotationExceptionHandlerMethodResolverTests {
}
}
+
@Controller
static class AmbiguousController {
@@ -133,6 +148,7 @@ public class AnnotationExceptionHandlerMethodResolverTests {
}
}
+
@Controller
static class NoExceptionController {
diff --git a/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/HeaderMethodArgumentResolverTests.java b/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/HeaderMethodArgumentResolverTests.java
index 429b5ce8..52656e44 100644
--- a/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/HeaderMethodArgumentResolverTests.java
+++ b/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/HeaderMethodArgumentResolverTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +34,7 @@ import org.springframework.messaging.MessageHandlingException;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.messaging.support.NativeMessageHeaderAccessor;
+import org.springframework.util.ReflectionUtils;
import static org.junit.Assert.*;
@@ -49,7 +50,8 @@ public class HeaderMethodArgumentResolverTests {
private MethodParameter paramRequired;
private MethodParameter paramNamedDefaultValueStringHeader;
- private MethodParameter paramSystemProperty;
+ private MethodParameter paramSystemPropertyDefaultValue;
+ private MethodParameter paramSystemPropertyName;
private MethodParameter paramNotAnnotated;
private MethodParameter paramNativeHeader;
@@ -61,13 +63,13 @@ public class HeaderMethodArgumentResolverTests {
cxt.refresh();
this.resolver = new HeaderMethodArgumentResolver(new DefaultConversionService(), cxt.getBeanFactory());
- Method method = getClass().getDeclaredMethod("handleMessage",
- String.class, String.class, String.class, String.class, String.class);
+ Method method = ReflectionUtils.findMethod(getClass(), "handleMessage", (Class<?>[]) null);
this.paramRequired = new SynthesizingMethodParameter(method, 0);
this.paramNamedDefaultValueStringHeader = new SynthesizingMethodParameter(method, 1);
- this.paramSystemProperty = new SynthesizingMethodParameter(method, 2);
- this.paramNotAnnotated = new SynthesizingMethodParameter(method, 3);
- this.paramNativeHeader = new SynthesizingMethodParameter(method, 4);
+ this.paramSystemPropertyDefaultValue = new SynthesizingMethodParameter(method, 2);
+ this.paramSystemPropertyName = new SynthesizingMethodParameter(method, 3);
+ this.paramNotAnnotated = new SynthesizingMethodParameter(method, 4);
+ this.paramNativeHeader = new SynthesizingMethodParameter(method, 5);
this.paramRequired.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
GenericTypeResolver.resolveParameterType(this.paramRequired, HeaderMethodArgumentResolver.class);
@@ -84,18 +86,14 @@ public class HeaderMethodArgumentResolverTests {
public void resolveArgument() throws Exception {
Message<byte[]> message = MessageBuilder.withPayload(new byte[0]).setHeader("param1", "foo").build();
Object result = this.resolver.resolveArgument(this.paramRequired, message);
-
assertEquals("foo", result);
}
- // SPR-11326
-
- @Test
+ @Test // SPR-11326
public void resolveArgumentNativeHeader() throws Exception {
TestMessageHeaderAccessor headers = new TestMessageHeaderAccessor();
headers.setNativeHeader("param1", "foo");
Message<byte[]> message = MessageBuilder.withPayload(new byte[0]).setHeaders(headers).build();
-
assertEquals("foo", this.resolver.resolveArgument(this.paramRequired, message));
}
@@ -120,7 +118,6 @@ public class HeaderMethodArgumentResolverTests {
public void resolveArgumentDefaultValue() throws Exception {
Message<byte[]> message = MessageBuilder.withPayload(new byte[0]).build();
Object result = this.resolver.resolveArgument(this.paramNamedDefaultValueStringHeader, message);
-
assertEquals("bar", result);
}
@@ -129,7 +126,7 @@ public class HeaderMethodArgumentResolverTests {
System.setProperty("systemProperty", "sysbar");
try {
Message<byte[]> message = MessageBuilder.withPayload(new byte[0]).build();
- Object result = resolver.resolveArgument(paramSystemProperty, message);
+ Object result = resolver.resolveArgument(paramSystemPropertyDefaultValue, message);
assertEquals("sysbar", result);
}
finally {
@@ -137,13 +134,26 @@ public class HeaderMethodArgumentResolverTests {
}
}
+ @Test
+ public void resolveNameFromSystemProperty() throws Exception {
+ System.setProperty("systemProperty", "sysbar");
+ try {
+ Message<byte[]> message = MessageBuilder.withPayload(new byte[0]).setHeader("sysbar", "foo").build();
+ Object result = resolver.resolveArgument(paramSystemPropertyName, message);
+ assertEquals("foo", result);
+ }
+ finally {
+ System.clearProperty("systemProperty");
+ }
+ }
+
- @SuppressWarnings("unused")
- private void handleMessage(
+ public void handleMessage(
@Header String param1,
@Header(name = "name", defaultValue = "bar") String param2,
@Header(name = "name", defaultValue = "#{systemProperties.systemProperty}") String param3,
- String param4,
+ @Header(name = "#{systemProperties.systemProperty}") String param4,
+ String param5,
@Header("nativeHeaders.param1") String nativeHeaderParam1) {
}
diff --git a/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/MessageMethodArgumentResolverTests.java b/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/MessageMethodArgumentResolverTests.java
index f4b7e2ba..159207c1 100644
--- a/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/MessageMethodArgumentResolverTests.java
+++ b/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/MessageMethodArgumentResolverTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,11 +26,15 @@ import org.junit.rules.ExpectedException;
import org.springframework.core.MethodParameter;
import org.springframework.messaging.Message;
+import org.springframework.messaging.converter.MessageConversionException;
+import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.support.ErrorMessage;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.messaging.support.MessageBuilder;
import static org.junit.Assert.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
/**
* Unit tests for
@@ -43,102 +47,138 @@ public class MessageMethodArgumentResolverTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
- private final MessageMethodArgumentResolver resolver = new MessageMethodArgumentResolver();
+ private MessageConverter converter;
+
+ private MessageMethodArgumentResolver resolver;
private Method method;
@Before
public void setup() throws Exception {
- this.method = MessageMethodArgumentResolverTests.class.getDeclaredMethod("handleMessage",
- Message.class, Message.class, Message.class, Message.class, ErrorMessage.class);
+
+ this.method = MessageMethodArgumentResolverTests.class.getDeclaredMethod("handle",
+ Message.class, Message.class, Message.class, Message.class,
+ ErrorMessage.class);
+
+ this.converter = mock(MessageConverter.class);
+ this.resolver = new MessageMethodArgumentResolver(this.converter);
}
@Test
- public void resolveAnyPayloadType() throws Exception {
+ public void resolveWithPayloadTypeAsWildcard() throws Exception {
Message<String> message = MessageBuilder.withPayload("test").build();
MethodParameter parameter = new MethodParameter(this.method, 0);
- assertTrue("Parameter '" + parameter + "' should be supported", this.resolver.supportsParameter(parameter));
+ assertTrue(this.resolver.supportsParameter(parameter));
assertSame(message, this.resolver.resolveArgument(parameter, message));
}
@Test
- public void resolvePayloadTypeExactType() throws Exception {
+ public void resolveWithMatchingPayloadType() throws Exception {
Message<Integer> message = MessageBuilder.withPayload(123).build();
MethodParameter parameter = new MethodParameter(this.method, 1);
- assertTrue("Parameter '" + parameter + "' should be supported", this.resolver.supportsParameter(parameter));
+ assertTrue(this.resolver.supportsParameter(parameter));
assertSame(message, this.resolver.resolveArgument(parameter, message));
}
@Test
- public void resolvePayloadTypeSubClass() throws Exception {
+ public void resolveWithPayloadTypeSubClass() throws Exception {
Message<Integer> message = MessageBuilder.withPayload(123).build();
MethodParameter parameter = new MethodParameter(this.method, 2);
- assertTrue("Parameter '" + parameter + "' should be supported", this.resolver.supportsParameter(parameter));
+ assertTrue(this.resolver.supportsParameter(parameter));
assertSame(message, this.resolver.resolveArgument(parameter, message));
}
@Test
- public void resolveInvalidPayloadType() throws Exception {
+ public void resolveWithConversion() throws Exception {
Message<String> message = MessageBuilder.withPayload("test").build();
MethodParameter parameter = new MethodParameter(this.method, 1);
- assertTrue("Parameter '" + parameter + "' should be supported", this.resolver.supportsParameter(parameter));
- thrown.expect(MethodArgumentTypeMismatchException.class);
+ when(this.converter.fromMessage(message, Integer.class)).thenReturn(4);
+
+ @SuppressWarnings("unchecked")
+ Message<Integer> actual = (Message<Integer>) this.resolver.resolveArgument(parameter, message);
+
+ assertNotNull(actual);
+ assertSame(message.getHeaders(), actual.getHeaders());
+ assertEquals(new Integer(4), actual.getPayload());
+ }
+
+ @Test
+ public void resolveWithConversionNoMatchingConverter() throws Exception {
+ Message<String> message = MessageBuilder.withPayload("test").build();
+ MethodParameter parameter = new MethodParameter(this.method, 1);
+
+ assertTrue(this.resolver.supportsParameter(parameter));
+ thrown.expect(MessageConversionException.class);
+ thrown.expectMessage(Integer.class.getName());
+ thrown.expectMessage(String.class.getName());
+ this.resolver.resolveArgument(parameter, message);
+ }
+
+ @Test
+ public void resolveWithConversionEmptyPayload() throws Exception {
+ Message<String> message = MessageBuilder.withPayload("").build();
+ MethodParameter parameter = new MethodParameter(this.method, 1);
+
+ assertTrue(this.resolver.supportsParameter(parameter));
+ thrown.expect(MessageConversionException.class);
+ thrown.expectMessage("the payload is empty");
thrown.expectMessage(Integer.class.getName());
thrown.expectMessage(String.class.getName());
this.resolver.resolveArgument(parameter, message);
}
@Test
- public void resolveUpperBoundPayloadType() throws Exception {
+ public void resolveWithPayloadTypeUpperBound() throws Exception {
Message<Integer> message = MessageBuilder.withPayload(123).build();
MethodParameter parameter = new MethodParameter(this.method, 3);
- assertTrue("Parameter '" + parameter + "' should be supported", this.resolver.supportsParameter(parameter));
+ assertTrue(this.resolver.supportsParameter(parameter));
assertSame(message, this.resolver.resolveArgument(parameter, message));
}
@Test
- public void resolveOutOfBoundPayloadType() throws Exception {
+ public void resolveWithPayloadTypeOutOfBound() throws Exception {
Message<Locale> message = MessageBuilder.withPayload(Locale.getDefault()).build();
MethodParameter parameter = new MethodParameter(this.method, 3);
- assertTrue("Parameter '" + parameter + "' should be supported", this.resolver.supportsParameter(parameter));
- thrown.expect(MethodArgumentTypeMismatchException.class);
+ assertTrue(this.resolver.supportsParameter(parameter));
+ thrown.expect(MessageConversionException.class);
thrown.expectMessage(Number.class.getName());
thrown.expectMessage(Locale.class.getName());
this.resolver.resolveArgument(parameter, message);
}
@Test
- public void resolveMessageSubTypeExactMatch() throws Exception {
+ public void resolveMessageSubClassMatch() throws Exception {
ErrorMessage message = new ErrorMessage(new UnsupportedOperationException());
MethodParameter parameter = new MethodParameter(this.method, 4);
- assertTrue("Parameter '" + parameter + "' should be supported", this.resolver.supportsParameter(parameter));
+ assertTrue(this.resolver.supportsParameter(parameter));
assertSame(message, this.resolver.resolveArgument(parameter, message));
}
@Test
- public void resolveMessageSubTypeSubClass() throws Exception {
+ public void resolveWithMessageSubClassAndPayloadWildcard() throws Exception {
ErrorMessage message = new ErrorMessage(new UnsupportedOperationException());
MethodParameter parameter = new MethodParameter(this.method, 0);
- assertTrue("Parameter '" + parameter + "' should be supported", this.resolver.supportsParameter(parameter));
+ assertTrue(this.resolver.supportsParameter(parameter));
assertSame(message, this.resolver.resolveArgument(parameter, message));
}
@Test
- public void resolveWrongMessageType() throws Exception {
- Message<? extends Throwable> message = new GenericMessage<Throwable>(new UnsupportedOperationException());
+ public void resolveWithWrongMessageType() throws Exception {
+ UnsupportedOperationException ex = new UnsupportedOperationException();
+ Message<? extends Throwable> message = new GenericMessage<Throwable>(ex);
MethodParameter parameter = new MethodParameter(this.method, 4);
- assertTrue("Parameter '" + parameter + "' should be supported", this.resolver.supportsParameter(parameter));
+ assertTrue(this.resolver.supportsParameter(parameter));
thrown.expect(MethodArgumentTypeMismatchException.class);
thrown.expectMessage(ErrorMessage.class.getName());
thrown.expectMessage(GenericMessage.class.getName());
@@ -147,7 +187,7 @@ public class MessageMethodArgumentResolverTests {
@SuppressWarnings("unused")
- private void handleMessage(
+ private void handle(
Message<?> wildcardPayload,
Message<Integer> integerPayload,
Message<Number> numberPayload,
diff --git a/spring-messaging/src/test/java/org/springframework/messaging/handler/invocation/MethodMessageHandlerTests.java b/spring-messaging/src/test/java/org/springframework/messaging/handler/invocation/MethodMessageHandlerTests.java
index 7d70c7d0..be385a7c 100644
--- a/spring-messaging/src/test/java/org/springframework/messaging/handler/invocation/MethodMessageHandlerTests.java
+++ b/spring-messaging/src/test/java/org/springframework/messaging/handler/invocation/MethodMessageHandlerTests.java
@@ -34,6 +34,7 @@ import org.junit.Test;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.core.MethodIntrospector;
import org.springframework.messaging.Message;
+import org.springframework.messaging.converter.SimpleMessageConverter;
import org.springframework.messaging.handler.DestinationPatternsMessageCondition;
import org.springframework.messaging.handler.HandlerMethod;
import org.springframework.messaging.handler.annotation.support.MessageMethodArgumentResolver;
@@ -196,7 +197,7 @@ public class MethodMessageHandlerTests {
@Override
protected List<? extends HandlerMethodArgumentResolver> initArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
- resolvers.add(new MessageMethodArgumentResolver());
+ resolvers.add(new MessageMethodArgumentResolver(new SimpleMessageConverter()));
resolvers.addAll(getCustomArgumentResolvers());
return resolvers;
}
@@ -274,7 +275,7 @@ public class MethodMessageHandlerTests {
private static Map<Class<? extends Throwable>, Method> initExceptionMappings(Class<?> handlerType) {
Map<Class<? extends Throwable>, Method> result = new HashMap<Class<? extends Throwable>, Method>();
for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHOD_FILTER)) {
- for(Class<? extends Throwable> exception : getExceptionsFromMethodSignature(method)) {
+ for (Class<? extends Throwable> exception : getExceptionsFromMethodSignature(method)) {
result.put(exception, method);
}
}
diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SendToMethodReturnValueHandlerTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SendToMethodReturnValueHandlerTests.java
index 277f7701..5d831dca 100644
--- a/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SendToMethodReturnValueHandlerTests.java
+++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SendToMethodReturnValueHandlerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
package org.springframework.messaging.simp.annotation.support;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
@@ -34,13 +36,13 @@ import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.springframework.core.MethodParameter;
+import org.springframework.core.annotation.AliasFor;
import org.springframework.core.annotation.SynthesizingMethodParameter;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.messaging.converter.StringMessageConverter;
-import org.springframework.messaging.handler.DestinationPatternsMessageCondition;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessageSendingOperations;
@@ -53,6 +55,7 @@ import org.springframework.util.MimeType;
import static org.junit.Assert.*;
import static org.mockito.BDDMockito.*;
+import static org.springframework.messaging.handler.DestinationPatternsMessageCondition.*;
import static org.springframework.messaging.handler.annotation.support.DestinationVariableMethodArgumentResolver.*;
import static org.springframework.messaging.support.MessageHeaderAccessor.*;
@@ -61,6 +64,7 @@ import static org.springframework.messaging.support.MessageHeaderAccessor.*;
*
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
+ * @author Stephane Nicoll
*/
public class SendToMethodReturnValueHandlerTests {
@@ -88,6 +92,12 @@ public class SendToMethodReturnValueHandlerTests {
private MethodParameter sendToUserDefaultDestReturnType;
private MethodParameter sendToUserSingleSessionDefaultDestReturnType;
private MethodParameter jsonViewReturnType;
+ private MethodParameter defaultNoAnnotation;
+ private MethodParameter defaultEmptyAnnotation;
+ private MethodParameter defaultOverrideAnnotation;
+ private MethodParameter userDefaultNoAnnotation;
+ private MethodParameter userDefaultEmptyAnnotation;
+ private MethodParameter userDefaultOverrideAnnotation;
@Before
@@ -103,32 +113,50 @@ public class SendToMethodReturnValueHandlerTests {
jsonMessagingTemplate.setMessageConverter(new MappingJackson2MessageConverter());
this.jsonHandler = new SendToMethodReturnValueHandler(jsonMessagingTemplate, true);
- Method method = this.getClass().getDeclaredMethod("handleNoAnnotations");
+ Method method = getClass().getDeclaredMethod("handleNoAnnotations");
this.noAnnotationsReturnType = new SynthesizingMethodParameter(method, -1);
- method = this.getClass().getDeclaredMethod("handleAndSendToDefaultDestination");
+ method = getClass().getDeclaredMethod("handleAndSendToDefaultDestination");
this.sendToDefaultDestReturnType = new SynthesizingMethodParameter(method, -1);
- method = this.getClass().getDeclaredMethod("handleAndSendTo");
+ method = getClass().getDeclaredMethod("handleAndSendTo");
this.sendToReturnType = new SynthesizingMethodParameter(method, -1);
- method = this.getClass().getDeclaredMethod("handleAndSendToWithPlaceholders");
+ method = getClass().getDeclaredMethod("handleAndSendToWithPlaceholders");
this.sendToWithPlaceholdersReturnType = new SynthesizingMethodParameter(method, -1);
- method = this.getClass().getDeclaredMethod("handleAndSendToUser");
+ method = getClass().getDeclaredMethod("handleAndSendToUser");
this.sendToUserReturnType = new SynthesizingMethodParameter(method, -1);
- method = this.getClass().getDeclaredMethod("handleAndSendToUserSingleSession");
+ method = getClass().getDeclaredMethod("handleAndSendToUserSingleSession");
this.sendToUserSingleSessionReturnType = new SynthesizingMethodParameter(method, -1);
- method = this.getClass().getDeclaredMethod("handleAndSendToUserDefaultDestination");
+ method = getClass().getDeclaredMethod("handleAndSendToUserDefaultDestination");
this.sendToUserDefaultDestReturnType = new SynthesizingMethodParameter(method, -1);
- method = this.getClass().getDeclaredMethod("handleAndSendToUserDefaultDestinationSingleSession");
+ method = getClass().getDeclaredMethod("handleAndSendToUserDefaultDestinationSingleSession");
this.sendToUserSingleSessionDefaultDestReturnType = new SynthesizingMethodParameter(method, -1);
- method = this.getClass().getDeclaredMethod("handleAndSendToJsonView");
+ method = getClass().getDeclaredMethod("handleAndSendToJsonView");
this.jsonViewReturnType = new SynthesizingMethodParameter(method, -1);
+
+ method = SendToTestBean.class.getDeclaredMethod("handleNoAnnotation");
+ this.defaultNoAnnotation = new SynthesizingMethodParameter(method, -1);
+
+ method = SendToTestBean.class.getDeclaredMethod("handleAndSendToDefaultDestination");
+ this.defaultEmptyAnnotation = new SynthesizingMethodParameter(method, -1);
+
+ method = SendToTestBean.class.getDeclaredMethod("handleAndSendToOverride");
+ this.defaultOverrideAnnotation = new SynthesizingMethodParameter(method, -1);
+
+ method = SendToUserTestBean.class.getDeclaredMethod("handleNoAnnotation");
+ this.userDefaultNoAnnotation = new SynthesizingMethodParameter(method, -1);
+
+ method = SendToUserTestBean.class.getDeclaredMethod("handleAndSendToDefaultDestination");
+ this.userDefaultEmptyAnnotation = new SynthesizingMethodParameter(method, -1);
+
+ method = SendToUserTestBean.class.getDeclaredMethod("handleAndSendToOverride");
+ this.userDefaultOverrideAnnotation = new SynthesizingMethodParameter(method, -1);
}
@@ -138,23 +166,26 @@ public class SendToMethodReturnValueHandlerTests {
assertTrue(this.handler.supportsReturnType(this.sendToUserReturnType));
assertFalse(this.handler.supportsReturnType(this.noAnnotationsReturnType));
assertTrue(this.handlerAnnotationNotRequired.supportsReturnType(this.noAnnotationsReturnType));
+
+ assertTrue(this.handler.supportsReturnType(this.defaultNoAnnotation));
+ assertTrue(this.handler.supportsReturnType(this.defaultEmptyAnnotation));
+ assertTrue(this.handler.supportsReturnType(this.defaultOverrideAnnotation));
+
+ assertTrue(this.handler.supportsReturnType(this.userDefaultNoAnnotation));
+ assertTrue(this.handler.supportsReturnType(this.userDefaultEmptyAnnotation));
+ assertTrue(this.handler.supportsReturnType(this.userDefaultOverrideAnnotation));
}
@Test
public void sendToNoAnnotations() throws Exception {
given(this.messageChannel.send(any(Message.class))).willReturn(true);
- Message<?> inputMessage = createInputMessage("sess1", "sub1", "/app", "/dest", null);
+ String sessionId = "sess1";
+ Message<?> inputMessage = createMessage(sessionId, "sub1", "/app", "/dest", null);
this.handler.handleReturnValue(PAYLOAD, this.noAnnotationsReturnType, inputMessage);
verify(this.messageChannel, times(1)).send(this.messageCaptor.capture());
-
- SimpMessageHeaderAccessor accessor = getCapturedAccessor(0);
- assertEquals("sess1", accessor.getSessionId());
- assertEquals("/topic/dest", accessor.getDestination());
- assertEquals(MIME_TYPE, accessor.getContentType());
- assertNull("Subscription id should not be copied", accessor.getSubscriptionId());
- assertEquals(this.noAnnotationsReturnType, accessor.getHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER));
+ assertResponse(this.noAnnotationsReturnType, sessionId, 0, "/topic/dest");
}
@Test
@@ -162,24 +193,12 @@ public class SendToMethodReturnValueHandlerTests {
given(this.messageChannel.send(any(Message.class))).willReturn(true);
String sessionId = "sess1";
- Message<?> inputMessage = createInputMessage(sessionId, "sub1", null, null, null);
+ Message<?> inputMessage = createMessage(sessionId, "sub1", null, null, null);
this.handler.handleReturnValue(PAYLOAD, this.sendToReturnType, inputMessage);
verify(this.messageChannel, times(2)).send(this.messageCaptor.capture());
-
- SimpMessageHeaderAccessor accessor = getCapturedAccessor(0);
- assertEquals(sessionId, accessor.getSessionId());
- assertEquals("/dest1", accessor.getDestination());
- assertEquals(MIME_TYPE, accessor.getContentType());
- assertNull("Subscription id should not be copied", accessor.getSubscriptionId());
- assertEquals(this.sendToReturnType, accessor.getHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER));
-
- accessor = getCapturedAccessor(1);
- assertEquals(sessionId, accessor.getSessionId());
- assertEquals("/dest2", accessor.getDestination());
- assertEquals(MIME_TYPE, accessor.getContentType());
- assertNull("Subscription id should not be copied", accessor.getSubscriptionId());
- assertEquals(this.sendToReturnType, accessor.getHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER));
+ assertResponse(this.sendToReturnType, sessionId, 0, "/dest1");
+ assertResponse(this.sendToReturnType, sessionId, 1, "/dest2");
}
@Test
@@ -187,24 +206,137 @@ public class SendToMethodReturnValueHandlerTests {
given(this.messageChannel.send(any(Message.class))).willReturn(true);
String sessionId = "sess1";
- Message<?> inputMessage = createInputMessage(sessionId, "sub1", "/app", "/dest", null);
+ Message<?> inputMessage = createMessage(sessionId, "sub1", "/app", "/dest", null);
this.handler.handleReturnValue(PAYLOAD, this.sendToDefaultDestReturnType, inputMessage);
verify(this.messageChannel, times(1)).send(this.messageCaptor.capture());
+ assertResponse(this.sendToDefaultDestReturnType, sessionId, 0, "/topic/dest");
+ }
- SimpMessageHeaderAccessor accessor = getCapturedAccessor(0);
+ @Test
+ public void sendToClassDefaultNoAnnotation() throws Exception {
+ given(this.messageChannel.send(any(Message.class))).willReturn(true);
+
+ String sessionId = "sess1";
+ Message<?> inputMessage = createMessage(sessionId, "sub1", null, null, null);
+ this.handler.handleReturnValue(PAYLOAD, this.defaultNoAnnotation, inputMessage);
+
+ verify(this.messageChannel, times(1)).send(this.messageCaptor.capture());
+ assertResponse(this.defaultNoAnnotation, sessionId, 0, "/dest-default");
+ }
+
+ @Test
+ public void sendToClassDefaultEmptyAnnotation() throws Exception {
+ given(this.messageChannel.send(any(Message.class))).willReturn(true);
+
+ String sessionId = "sess1";
+ Message<?> inputMessage = createMessage(sessionId, "sub1", null, null, null);
+ this.handler.handleReturnValue(PAYLOAD, this.defaultEmptyAnnotation, inputMessage);
+
+ verify(this.messageChannel, times(1)).send(this.messageCaptor.capture());
+ assertResponse(this.defaultEmptyAnnotation, sessionId, 0, "/dest-default");
+ }
+
+ @Test
+ public void sendToClassDefaultOverride() throws Exception {
+ given(this.messageChannel.send(any(Message.class))).willReturn(true);
+
+ String sessionId = "sess1";
+ Message<?> inputMessage = createMessage(sessionId, "sub1", null, null, null);
+ this.handler.handleReturnValue(PAYLOAD, this.defaultOverrideAnnotation, inputMessage);
+
+ verify(this.messageChannel, times(2)).send(this.messageCaptor.capture());
+ assertResponse(this.defaultOverrideAnnotation, sessionId, 0, "/dest3");
+ assertResponse(this.defaultOverrideAnnotation, sessionId, 1, "/dest4");
+ }
+
+ @Test
+ public void sendToUserClassDefaultNoAnnotation() throws Exception {
+ given(this.messageChannel.send(any(Message.class))).willReturn(true);
+
+ String sessionId = "sess1";
+ Message<?> inputMessage = createMessage(sessionId, "sub1", null, null, null);
+ this.handler.handleReturnValue(PAYLOAD, this.userDefaultNoAnnotation, inputMessage);
+
+ verify(this.messageChannel, times(1)).send(this.messageCaptor.capture());
+ assertResponse(this.userDefaultNoAnnotation, sessionId, 0, "/user/sess1/dest-default");
+ }
+
+ @Test
+ public void sendToUserClassDefaultEmptyAnnotation() throws Exception {
+ given(this.messageChannel.send(any(Message.class))).willReturn(true);
+
+ String sessionId = "sess1";
+ Message<?> inputMessage = createMessage(sessionId, "sub1", null, null, null);
+ this.handler.handleReturnValue(PAYLOAD, this.userDefaultEmptyAnnotation, inputMessage);
+
+ verify(this.messageChannel, times(1)).send(this.messageCaptor.capture());
+ assertResponse(this.userDefaultEmptyAnnotation, sessionId, 0, "/user/sess1/dest-default");
+ }
+
+ @Test
+ public void sendToUserClassDefaultOverride() throws Exception {
+ given(this.messageChannel.send(any(Message.class))).willReturn(true);
+
+ String sessionId = "sess1";
+ Message<?> inputMessage = createMessage(sessionId, "sub1", null, null, null);
+ this.handler.handleReturnValue(PAYLOAD, this.userDefaultOverrideAnnotation, inputMessage);
+
+ verify(this.messageChannel, times(2)).send(this.messageCaptor.capture());
+ assertResponse(this.userDefaultOverrideAnnotation, sessionId, 0, "/user/sess1/dest3");
+ assertResponse(this.userDefaultOverrideAnnotation, sessionId, 1, "/user/sess1/dest4");
+ }
+
+ @Test // SPR-14238
+ public void sendToUserWithSendToDefaultOverride() throws Exception {
+ given(this.messageChannel.send(any(Message.class))).willReturn(true);
+
+ Class<?> clazz = SendToUserWithSendToOverrideTestBean.class;
+ Method method = clazz.getDeclaredMethod("handleAndSendToDefaultDestination");
+ MethodParameter parameter = new SynthesizingMethodParameter(method, -1);
+
+ String sessionId = "sess1";
+ Message<?> inputMessage = createMessage(sessionId, "sub1", null, null, null);
+ this.handler.handleReturnValue(PAYLOAD, parameter, inputMessage);
+
+ verify(this.messageChannel, times(1)).send(this.messageCaptor.capture());
+ assertResponse(parameter, sessionId, 0, "/user/sess1/dest-default");
+ }
+
+ @Test // SPR-14238
+ public void sendToUserWithSendToOverride() throws Exception {
+ given(this.messageChannel.send(any(Message.class))).willReturn(true);
+
+ Class<?> clazz = SendToUserWithSendToOverrideTestBean.class;
+ Method method = clazz.getDeclaredMethod("handleAndSendToOverride");
+ MethodParameter parameter = new SynthesizingMethodParameter(method, -1);
+
+ String sessionId = "sess1";
+ Message<?> inputMessage = createMessage(sessionId, "sub1", null, null, null);
+ this.handler.handleReturnValue(PAYLOAD, parameter, inputMessage);
+
+ verify(this.messageChannel, times(2)).send(this.messageCaptor.capture());
+ assertResponse(parameter, sessionId, 0, "/dest3");
+ assertResponse(parameter, sessionId, 1, "/dest4");
+ }
+
+
+ private void assertResponse(MethodParameter methodParameter, String sessionId,
+ int index, String destination) {
+
+ SimpMessageHeaderAccessor accessor = getCapturedAccessor(index);
assertEquals(sessionId, accessor.getSessionId());
- assertEquals("/topic/dest", accessor.getDestination());
+ assertEquals(destination, accessor.getDestination());
assertEquals(MIME_TYPE, accessor.getContentType());
assertNull("Subscription id should not be copied", accessor.getSubscriptionId());
- assertEquals(this.sendToDefaultDestReturnType, accessor.getHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER));
+ assertEquals(methodParameter, accessor.getHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER));
}
@Test
public void sendToDefaultDestinationWhenUsingDotPathSeparator() throws Exception {
given(this.messageChannel.send(any(Message.class))).willReturn(true);
- Message<?> inputMessage = createInputMessage("sess1", "sub1", "/app/", "dest.foo.bar", null);
+ Message<?> inputMessage = createMessage("sess1", "sub1", "/app/", "dest.foo.bar", null);
this.handler.handleReturnValue(PAYLOAD, this.sendToDefaultDestReturnType, inputMessage);
verify(this.messageChannel, times(1)).send(this.messageCaptor.capture());
@@ -215,23 +347,24 @@ public class SendToMethodReturnValueHandlerTests {
@Test
public void testHeadersToSend() throws Exception {
- Message<?> inputMessage = createInputMessage("sess1", "sub1", "/app", "/dest", null);
+ Message<?> message = createMessage("sess1", "sub1", "/app", "/dest", null);
SimpMessageSendingOperations messagingTemplate = Mockito.mock(SimpMessageSendingOperations.class);
SendToMethodReturnValueHandler handler = new SendToMethodReturnValueHandler(messagingTemplate, false);
- handler.handleReturnValue(PAYLOAD, this.noAnnotationsReturnType, inputMessage);
+ handler.handleReturnValue(PAYLOAD, this.noAnnotationsReturnType, message);
ArgumentCaptor<MessageHeaders> captor = ArgumentCaptor.forClass(MessageHeaders.class);
verify(messagingTemplate).convertAndSend(eq("/topic/dest"), eq(PAYLOAD), captor.capture());
- MessageHeaders messageHeaders = captor.getValue();
- SimpMessageHeaderAccessor accessor = getAccessor(messageHeaders, SimpMessageHeaderAccessor.class);
+ MessageHeaders headers = captor.getValue();
+ SimpMessageHeaderAccessor accessor = getAccessor(headers, SimpMessageHeaderAccessor.class);
assertNotNull(accessor);
assertTrue(accessor.isMutable());
assertEquals("sess1", accessor.getSessionId());
assertNull("Subscription id should not be copied", accessor.getSubscriptionId());
- assertEquals(this.noAnnotationsReturnType, accessor.getHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER));
+ assertEquals(this.noAnnotationsReturnType,
+ accessor.getHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER));
}
@Test
@@ -240,7 +373,7 @@ public class SendToMethodReturnValueHandlerTests {
String sessionId = "sess1";
TestUser user = new TestUser();
- Message<?> inputMessage = createInputMessage(sessionId, "sub1", null, null, user);
+ Message<?> inputMessage = createMessage(sessionId, "sub1", null, null, user);
this.handler.handleReturnValue(PAYLOAD, this.sendToUserReturnType, inputMessage);
verify(this.messageChannel, times(2)).send(this.messageCaptor.capture());
@@ -284,7 +417,7 @@ public class SendToMethodReturnValueHandlerTests {
String sessionId = "sess1";
TestUser user = new TestUser();
- Message<?> inputMessage = createInputMessage(sessionId, "sub1", null, null, user);
+ Message<?> inputMessage = createMessage(sessionId, "sub1", null, null, user);
this.handler.handleReturnValue(PAYLOAD, this.sendToUserSingleSessionReturnType, inputMessage);
verify(this.messageChannel, times(2)).send(this.messageCaptor.capture());
@@ -294,14 +427,16 @@ public class SendToMethodReturnValueHandlerTests {
assertEquals(MIME_TYPE, accessor.getContentType());
assertEquals("/user/" + user.getName() + "/dest1", accessor.getDestination());
assertNull("Subscription id should not be copied", accessor.getSubscriptionId());
- assertEquals(this.sendToUserSingleSessionReturnType, accessor.getHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER));
+ assertEquals(this.sendToUserSingleSessionReturnType,
+ accessor.getHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER));
accessor = getCapturedAccessor(1);
assertEquals(sessionId, accessor.getSessionId());
assertEquals("/user/" + user.getName() + "/dest2", accessor.getDestination());
assertEquals(MIME_TYPE, accessor.getContentType());
assertNull("Subscription id should not be copied", accessor.getSubscriptionId());
- assertEquals(this.sendToUserSingleSessionReturnType, accessor.getHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER));
+ assertEquals(this.sendToUserSingleSessionReturnType,
+ accessor.getHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER));
}
@Test
@@ -310,7 +445,7 @@ public class SendToMethodReturnValueHandlerTests {
String sessionId = "sess1";
TestUser user = new UniqueUser();
- Message<?> inputMessage = createInputMessage(sessionId, "sub1", null, null, user);
+ Message<?> inputMessage = createMessage(sessionId, "sub1", null, null, user);
this.handler.handleReturnValue(PAYLOAD, this.sendToUserReturnType, inputMessage);
verify(this.messageChannel, times(2)).send(this.messageCaptor.capture());
@@ -328,7 +463,7 @@ public class SendToMethodReturnValueHandlerTests {
String sessionId = "sess1";
TestUser user = new TestUser();
- Message<?> inputMessage = createInputMessage(sessionId, "sub1", "/app", "/dest", user);
+ Message<?> inputMessage = createMessage(sessionId, "sub1", "/app", "/dest", user);
this.handler.handleReturnValue(PAYLOAD, this.sendToUserDefaultDestReturnType, inputMessage);
verify(this.messageChannel, times(1)).send(this.messageCaptor.capture());
@@ -344,7 +479,7 @@ public class SendToMethodReturnValueHandlerTests {
given(this.messageChannel.send(any(Message.class))).willReturn(true);
TestUser user = new TestUser();
- Message<?> inputMessage = createInputMessage("sess1", "sub1", "/app/", "dest.foo.bar", user);
+ Message<?> inputMessage = createMessage("sess1", "sub1", "/app/", "dest.foo.bar", user);
this.handler.handleReturnValue(PAYLOAD, this.sendToUserDefaultDestReturnType, inputMessage);
verify(this.messageChannel, times(1)).send(this.messageCaptor.capture());
@@ -359,8 +494,8 @@ public class SendToMethodReturnValueHandlerTests {
String sessionId = "sess1";
TestUser user = new TestUser();
- Message<?> inputMessage = createInputMessage(sessionId, "sub1", "/app", "/dest", user);
- this.handler.handleReturnValue(PAYLOAD, this.sendToUserSingleSessionDefaultDestReturnType, inputMessage);
+ Message<?> message = createMessage(sessionId, "sub1", "/app", "/dest", user);
+ this.handler.handleReturnValue(PAYLOAD, this.sendToUserSingleSessionDefaultDestReturnType, message);
verify(this.messageChannel, times(1)).send(this.messageCaptor.capture());
@@ -369,7 +504,8 @@ public class SendToMethodReturnValueHandlerTests {
assertEquals("/user/" + user.getName() + "/queue/dest", accessor.getDestination());
assertEquals(MIME_TYPE, accessor.getContentType());
assertNull("Subscription id should not be copied", accessor.getSubscriptionId());
- assertEquals(this.sendToUserSingleSessionDefaultDestReturnType, accessor.getHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER));
+ assertEquals(this.sendToUserSingleSessionDefaultDestReturnType,
+ accessor.getHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER));
}
@Test
@@ -377,7 +513,7 @@ public class SendToMethodReturnValueHandlerTests {
given(this.messageChannel.send(any(Message.class))).willReturn(true);
String sessionId = "sess1";
- Message<?> inputMessage = createInputMessage(sessionId, "sub1", null, null, null);
+ Message<?> inputMessage = createMessage(sessionId, "sub1", null, null, null);
this.handler.handleReturnValue(PAYLOAD, this.sendToUserReturnType, inputMessage);
verify(this.messageChannel, times(2)).send(this.messageCaptor.capture());
@@ -396,29 +532,28 @@ public class SendToMethodReturnValueHandlerTests {
given(this.messageChannel.send(any(Message.class))).willReturn(true);
String sessionId = "sess1";
- Message<?> inputMessage = createInputMessage(sessionId, "sub1", "/app", "/dest", null);
+ Message<?> inputMessage = createMessage(sessionId, "sub1", "/app", "/dest", null);
this.jsonHandler.handleReturnValue(handleAndSendToJsonView(), this.jsonViewReturnType, inputMessage);
verify(this.messageChannel).send(this.messageCaptor.capture());
Message<?> message = this.messageCaptor.getValue();
assertNotNull(message);
- assertEquals("{\"withView1\":\"with\"}", new String((byte[]) message.getPayload(), StandardCharsets.UTF_8));
+ String bytes = new String((byte[]) message.getPayload(), StandardCharsets.UTF_8);
+ assertEquals("{\"withView1\":\"with\"}", bytes);
}
- private Message<?> createInputMessage(String sessId, String subsId, String destinationPrefix,
- String destination, Principal principal) {
-
+ private Message<?> createMessage(String sessId, String subsId, String destPrefix, String dest, Principal user) {
SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create();
headerAccessor.setSessionId(sessId);
headerAccessor.setSubscriptionId(subsId);
- if (destination != null && destinationPrefix != null) {
- headerAccessor.setDestination(destinationPrefix + destination);
- headerAccessor.setHeader(DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER, destination);
+ if (dest != null && destPrefix != null) {
+ headerAccessor.setDestination(destPrefix + dest);
+ headerAccessor.setHeader(LOOKUP_DESTINATION_HEADER, dest);
}
- if (principal != null) {
- headerAccessor.setUser(principal);
+ if (user != null) {
+ headerAccessor.setUser(user);
}
return MessageBuilder.createMessage(new byte[0], headerAccessor.getMessageHeaders());
}
@@ -448,48 +583,65 @@ public class SendToMethodReturnValueHandlerTests {
}
}
- public String handleNoAnnotations() {
+ @SendTo
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface MySendTo {
+
+ @AliasFor(annotation = SendTo.class, attribute = "value")
+ String[] dest();
+ }
+
+ @SendToUser
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface MySendToUser {
+
+ @AliasFor(annotation = SendToUser.class, attribute = "destinations")
+ String[] dest();
+ }
+
+
+ @SuppressWarnings("unused")
+ String handleNoAnnotations() {
return PAYLOAD;
}
- @SendTo
- public String handleAndSendToDefaultDestination() {
+ @SendTo @SuppressWarnings("unused")
+ String handleAndSendToDefaultDestination() {
return PAYLOAD;
}
- @SendTo({"/dest1", "/dest2"})
- public String handleAndSendTo() {
+ @SendTo({"/dest1", "/dest2"}) @SuppressWarnings("unused")
+ String handleAndSendTo() {
return PAYLOAD;
}
- @SendTo("/topic/chat.message.filtered.{roomName}")
- public String handleAndSendToWithPlaceholders() {
+ @SendTo("/topic/chat.message.filtered.{roomName}") @SuppressWarnings("unused")
+ String handleAndSendToWithPlaceholders() {
return PAYLOAD;
}
- @SendToUser
- public String handleAndSendToUserDefaultDestination() {
+ @SendToUser @SuppressWarnings("unused")
+ String handleAndSendToUserDefaultDestination() {
return PAYLOAD;
}
- @SendToUser(broadcast = false)
- public String handleAndSendToUserDefaultDestinationSingleSession() {
+ @SendToUser(broadcast = false) @SuppressWarnings("unused")
+ String handleAndSendToUserDefaultDestinationSingleSession() {
return PAYLOAD;
}
- @SendToUser({"/dest1", "/dest2"})
- public String handleAndSendToUser() {
+ @SendToUser({"/dest1", "/dest2"}) @SuppressWarnings("unused")
+ String handleAndSendToUser() {
return PAYLOAD;
}
- @SendToUser(destinations = { "/dest1", "/dest2" }, broadcast = false)
- public String handleAndSendToUserSingleSession() {
+ @SendToUser(destinations = { "/dest1", "/dest2" }, broadcast = false) @SuppressWarnings("unused")
+ String handleAndSendToUserSingleSession() {
return PAYLOAD;
}
- @SendTo("/dest")
- @JsonView(MyJacksonView1.class)
- public JacksonViewBean handleAndSendToJsonView() {
+ @JsonView(MyJacksonView1.class) @SuppressWarnings("unused")
+ JacksonViewBean handleAndSendToJsonView() {
JacksonViewBean payload = new JacksonViewBean();
payload.setWithView1("with");
payload.setWithView2("with");
@@ -498,6 +650,57 @@ public class SendToMethodReturnValueHandlerTests {
}
+ @MySendTo(dest = "/dest-default") @SuppressWarnings("unused")
+ private static class SendToTestBean {
+
+ String handleNoAnnotation() {
+ return PAYLOAD;
+ }
+
+ @SendTo
+ String handleAndSendToDefaultDestination() {
+ return PAYLOAD;
+ }
+
+ @MySendTo(dest = {"/dest3", "/dest4"})
+ String handleAndSendToOverride() {
+ return PAYLOAD;
+ }
+ }
+
+ @MySendToUser(dest = "/dest-default") @SuppressWarnings("unused")
+ private static class SendToUserTestBean {
+
+ String handleNoAnnotation() {
+ return PAYLOAD;
+ }
+
+ @SendToUser
+ String handleAndSendToDefaultDestination() {
+ return PAYLOAD;
+ }
+
+ @MySendToUser(dest = {"/dest3", "/dest4"})
+ String handleAndSendToOverride() {
+ return PAYLOAD;
+ }
+ }
+
+ @MySendToUser(dest = "/dest-default") @SuppressWarnings("unused")
+ private static class SendToUserWithSendToOverrideTestBean {
+
+ @SendTo
+ String handleAndSendToDefaultDestination() {
+ return PAYLOAD;
+ }
+
+ @MySendTo(dest = {"/dest3", "/dest4"})
+ String handleAndSendToOverride() {
+ return PAYLOAD;
+ }
+ }
+
+
private interface MyJacksonView1 {}
private interface MyJacksonView2 {}
@@ -516,23 +719,23 @@ public class SendToMethodReturnValueHandlerTests {
return withView1;
}
- public void setWithView1(String withView1) {
+ void setWithView1(String withView1) {
this.withView1 = withView1;
}
- public String getWithView2() {
+ String getWithView2() {
return withView2;
}
- public void setWithView2(String withView2) {
+ void setWithView2(String withView2) {
this.withView2 = withView2;
}
- public String getWithoutView() {
+ String getWithoutView() {
return withoutView;
}
- public void setWithoutView(String withoutView) {
+ void setWithoutView(String withoutView) {
this.withoutView = withoutView;
}
}
diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/config/MessageBrokerConfigurationTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/config/MessageBrokerConfigurationTests.java
index e72a77ce..5a868a75 100644
--- a/spring-messaging/src/test/java/org/springframework/messaging/simp/config/MessageBrokerConfigurationTests.java
+++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/config/MessageBrokerConfigurationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,9 +16,6 @@
package org.springframework.messaging.simp.config;
-import static org.junit.Assert.*;
-import static org.mockito.Mockito.*;
-
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@@ -28,6 +25,7 @@ import java.util.concurrent.ConcurrentHashMap;
import org.hamcrest.Matchers;
import org.junit.Test;
+import org.springframework.beans.DirectFieldAccessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
@@ -54,6 +52,7 @@ import org.springframework.messaging.simp.broker.SimpleBrokerMessageHandler;
import org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
+import org.springframework.messaging.simp.user.DefaultUserDestinationResolver;
import org.springframework.messaging.simp.user.MultiServerUserRegistry;
import org.springframework.messaging.simp.user.SimpUserRegistry;
import org.springframework.messaging.simp.user.UserDestinationMessageHandler;
@@ -71,6 +70,9 @@ import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
/**
* Test fixture for {@link AbstractMessageBrokerConfiguration}.
*
@@ -384,6 +386,17 @@ public class MessageBrokerConfigurationTests {
SimpAnnotationMethodMessageHandler handler = this.customContext.getBean(SimpAnnotationMethodMessageHandler.class);
assertEquals("a.a", handler.getPathMatcher().combine("a", "a"));
+
+ DefaultUserDestinationResolver resolver = this.customContext.getBean(DefaultUserDestinationResolver.class);
+ assertNotNull(resolver);
+ assertEquals(false, new DirectFieldAccessor(resolver).getPropertyValue("keepLeadingSlash"));
+ }
+
+ @Test
+ public void customCacheLimit() {
+ SimpleBrokerMessageHandler broker = this.customContext.getBean(SimpleBrokerMessageHandler.class);
+ DefaultSubscriptionRegistry registry = (DefaultSubscriptionRegistry) broker.getSubscriptionRegistry();
+ assertEquals(8192, registry.getCacheLimit());
}
@Test
@@ -435,6 +448,7 @@ public class MessageBrokerConfigurationTests {
}
}
+
static class BaseTestMessageBrokerConfig extends AbstractMessageBrokerConfiguration {
@Override
@@ -443,6 +457,7 @@ public class MessageBrokerConfigurationTests {
}
}
+
@SuppressWarnings("unused")
@Configuration
static class SimpleBrokerConfig extends BaseTestMessageBrokerConfig {
@@ -471,6 +486,7 @@ public class MessageBrokerConfigurationTests {
}
}
+
@Configuration
static class BrokerRelayConfig extends SimpleBrokerConfig {
@@ -482,10 +498,12 @@ public class MessageBrokerConfigurationTests {
}
}
+
@Configuration
static class DefaultConfig extends BaseTestMessageBrokerConfig {
}
+
@Configuration
static class CustomConfig extends BaseTestMessageBrokerConfig {
@@ -519,6 +537,7 @@ public class MessageBrokerConfigurationTests {
registry.configureBrokerChannel().setInterceptors(this.interceptor, this.interceptor, this.interceptor);
registry.configureBrokerChannel().taskExecutor().corePoolSize(31).maxPoolSize(32).keepAliveSeconds(33).queueCapacity(34);
registry.setPathMatcher(new AntPathMatcher(".")).enableSimpleBroker("/topic", "/queue");
+ registry.setCacheLimit(8192);
}
}
@@ -534,6 +553,7 @@ public class MessageBrokerConfigurationTests {
}
}
+
private static class TestValidator implements Validator {
@Override
@@ -546,6 +566,7 @@ public class MessageBrokerConfigurationTests {
}
}
+
@SuppressWarnings("serial")
private static class CustomThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {
}
diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/DefaultStompSessionTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/DefaultStompSessionTests.java
index ae7fd9d1..eb838dcf 100644
--- a/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/DefaultStompSessionTests.java
+++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/DefaultStompSessionTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,11 +16,6 @@
package org.springframework.messaging.simp.stomp;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.*;
-
import java.nio.charset.Charset;
import java.util.Date;
import java.util.Map;
@@ -51,6 +46,14 @@ import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
import org.springframework.util.concurrent.SettableListenableFuture;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.notNull;
+import static org.mockito.Mockito.same;
+
/**
* Unit tests for {@link DefaultStompSession}.
*
@@ -80,7 +83,6 @@ public class DefaultStompSessionTests {
@Before
public void setUp() throws Exception {
-
MockitoAnnotations.initMocks(this);
this.sessionHandler = mock(StompSessionHandler.class);
@@ -96,7 +98,6 @@ public class DefaultStompSessionTests {
@Test
public void afterConnected() throws Exception {
-
assertFalse(this.session.isConnected());
this.connectHeaders.setHost("my-host");
this.connectHeaders.setHeartbeat(new long[] {11, 12});
@@ -122,7 +123,6 @@ public class DefaultStompSessionTests {
@Test
public void handleConnectedFrame() throws Exception {
-
this.session.afterConnected(this.connection);
assertTrue(this.session.isConnected());
@@ -141,7 +141,6 @@ public class DefaultStompSessionTests {
@Test
public void heartbeatValues() throws Exception {
-
this.session.afterConnected(this.connection);
assertTrue(this.session.isConnected());
@@ -164,7 +163,6 @@ public class DefaultStompSessionTests {
@Test
public void heartbeatNotSupportedByServer() throws Exception {
-
this.session.afterConnected(this.connection);
verify(this.connection).send(any());
@@ -181,7 +179,6 @@ public class DefaultStompSessionTests {
@Test
public void heartbeatTasks() throws Exception {
-
this.session.afterConnected(this.connection);
verify(this.connection).send(any());
@@ -217,7 +214,6 @@ public class DefaultStompSessionTests {
@Test
public void handleErrorFrame() throws Exception {
-
StompHeaderAccessor accessor = StompHeaderAccessor.create(StompCommand.ERROR);
accessor.setContentType(new MimeType("text", "plain", UTF_8));
accessor.addNativeHeader("foo", "bar");
@@ -236,7 +232,6 @@ public class DefaultStompSessionTests {
@Test
public void handleErrorFrameWithEmptyPayload() throws Exception {
-
StompHeaderAccessor accessor = StompHeaderAccessor.create(StompCommand.ERROR);
accessor.addNativeHeader("foo", "bar");
accessor.setLeaveMutable(true);
@@ -249,7 +244,6 @@ public class DefaultStompSessionTests {
@Test
public void handleErrorFrameWithConversionException() throws Exception {
-
StompHeaderAccessor accessor = StompHeaderAccessor.create(StompCommand.ERROR);
accessor.setContentType(MimeTypeUtils.APPLICATION_JSON);
accessor.addNativeHeader("foo", "bar");
@@ -269,7 +263,6 @@ public class DefaultStompSessionTests {
@Test
public void handleMessageFrame() throws Exception {
-
this.session.afterConnected(this.connection);
StompFrameHandler frameHandler = mock(StompFrameHandler.class);
@@ -296,7 +289,6 @@ public class DefaultStompSessionTests {
@Test
public void handleMessageFrameWithConversionException() throws Exception {
-
this.session.afterConnected(this.connection);
assertTrue(this.session.isConnected());
@@ -344,7 +336,6 @@ public class DefaultStompSessionTests {
@Test
public void send() throws Exception {
-
this.session.afterConnected(this.connection);
assertTrue(this.session.isConnected());
@@ -361,13 +352,12 @@ public class DefaultStompSessionTests {
assertEquals(destination, stompHeaders.getDestination());
assertEquals(new MimeType("text", "plain", UTF_8), stompHeaders.getContentType());
- assertEquals(-1, stompHeaders.getContentLength()); // StompEncoder isn't involved
+ assertEquals(-1, stompHeaders.getContentLength()); // StompEncoder isn't involved
assertEquals(payload, new String(message.getPayload(), UTF_8));
}
@Test
public void sendWithReceipt() throws Exception {
-
this.session.afterConnected(this.connection);
assertTrue(this.session.isConnected());
@@ -391,7 +381,6 @@ public class DefaultStompSessionTests {
@Test
public void sendWithConversionException() throws Exception {
-
this.session.afterConnected(this.connection);
assertTrue(this.session.isConnected());
@@ -407,7 +396,6 @@ public class DefaultStompSessionTests {
@Test
public void sendWithExecutionException() throws Exception {
-
this.session.afterConnected(this.connection);
assertTrue(this.session.isConnected());
@@ -426,7 +414,6 @@ public class DefaultStompSessionTests {
@Test
public void subscribe() throws Exception {
-
this.session.afterConnected(this.connection);
assertTrue(this.session.isConnected());
@@ -446,7 +433,6 @@ public class DefaultStompSessionTests {
@Test
public void subscribeWithHeaders() throws Exception {
-
this.session.afterConnected(this.connection);
assertTrue(this.session.isConnected());
@@ -473,7 +459,6 @@ public class DefaultStompSessionTests {
@Test
public void unsubscribe() throws Exception {
-
this.session.afterConnected(this.connection);
assertTrue(this.session.isConnected());
@@ -492,8 +477,41 @@ public class DefaultStompSessionTests {
}
@Test
- public void receiptReceived() throws Exception {
+ public void ack() throws Exception {
+ this.session.afterConnected(this.connection);
+ assertTrue(this.session.isConnected());
+
+ String messageId = "123";
+ this.session.acknowledge(messageId, true);
+
+ Message<byte[]> message = this.messageCaptor.getValue();
+ StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
+ assertEquals(StompCommand.ACK, accessor.getCommand());
+
+ StompHeaders stompHeaders = StompHeaders.readOnlyStompHeaders(accessor.getNativeHeaders());
+ assertEquals(stompHeaders.toString(), 1, stompHeaders.size());
+ assertEquals(messageId, stompHeaders.getId());
+ }
+
+ @Test
+ public void nack() throws Exception {
+ this.session.afterConnected(this.connection);
+ assertTrue(this.session.isConnected());
+
+ String messageId = "123";
+ this.session.acknowledge(messageId, false);
+
+ Message<byte[]> message = this.messageCaptor.getValue();
+ StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
+ assertEquals(StompCommand.NACK, accessor.getCommand());
+
+ StompHeaders stompHeaders = StompHeaders.readOnlyStompHeaders(accessor.getNativeHeaders());
+ assertEquals(stompHeaders.toString(), 1, stompHeaders.size());
+ assertEquals(messageId, stompHeaders.getId());
+ }
+ @Test
+ public void receiptReceived() throws Exception {
this.session.afterConnected(this.connection);
this.session.setTaskScheduler(mock(TaskScheduler.class));
@@ -518,7 +536,6 @@ public class DefaultStompSessionTests {
@Test
public void receiptReceivedBeforeTaskAdded() throws Exception {
-
this.session.afterConnected(this.connection);
this.session.setTaskScheduler(mock(TaskScheduler.class));
@@ -543,7 +560,6 @@ public class DefaultStompSessionTests {
@Test
@SuppressWarnings({ "unchecked", "rawtypes" })
public void receiptNotReceived() throws Exception {
-
TaskScheduler taskScheduler = mock(TaskScheduler.class);
this.session.afterConnected(this.connection);
@@ -575,7 +591,6 @@ public class DefaultStompSessionTests {
@Test
public void disconnect() throws Exception {
-
this.session.afterConnected(this.connection);
assertTrue(this.session.isConnected());
diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/Reactor2TcpStompClientTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/Reactor2TcpStompClientTests.java
index 9d8a82e7..94bfce4b 100644
--- a/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/Reactor2TcpStompClientTests.java
+++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/Reactor2TcpStompClientTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,10 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.messaging.simp.stomp;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
+package org.springframework.messaging.simp.stomp;
import java.lang.reflect.Type;
import java.util.ArrayList;
@@ -41,6 +39,9 @@ import org.springframework.util.Assert;
import org.springframework.util.SocketUtils;
import org.springframework.util.concurrent.ListenableFuture;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
/**
* Integration tests for {@link Reactor2TcpStompClient}.
*
@@ -86,7 +87,8 @@ public class Reactor2TcpStompClientTests {
public void tearDown() throws Exception {
try {
this.client.shutdown();
- } catch (Throwable ex) {
+ }
+ catch (Throwable ex) {
logger.error("Failed to shut client", ex);
}
final CountDownLatch latch = new CountDownLatch(1);
@@ -100,7 +102,6 @@ public class Reactor2TcpStompClientTests {
@Test
public void publishSubscribe() throws Exception {
-
String destination = "/topic/foo";
ConsumingHandler consumingHandler1 = new ConsumingHandler(destination);
ListenableFuture<StompSession> consumerFuture1 = this.client.connect(consumingHandler1);
@@ -146,9 +147,9 @@ public class Reactor2TcpStompClientTests {
public void handleTransportError(StompSession session, Throwable exception) {
logger.error(exception);
}
-
}
+
private static class ConsumingHandler extends LoggingSessionHandler {
private final List<String> topics;
@@ -157,14 +158,12 @@ public class Reactor2TcpStompClientTests {
private final List<String> received = new ArrayList<>();
-
public ConsumingHandler(String... topics) {
Assert.notEmpty(topics);
this.topics = Arrays.asList(topics);
this.subscriptionLatch = new CountDownLatch(this.topics.size());
}
-
public List<String> getReceived() {
return this.received;
}
@@ -208,16 +207,15 @@ public class Reactor2TcpStompClientTests {
}
return true;
}
-
}
+
private static class ProducingHandler extends LoggingSessionHandler {
private final List<String> topics = new ArrayList<>();
private final List<Object> payloads = new ArrayList<>();
-
public ProducingHandler addToSend(String topic, Object payload) {
this.topics.add(topic);
this.payloads.add(payload);
@@ -226,7 +224,7 @@ public class Reactor2TcpStompClientTests {
@Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
- for (int i=0; i < this.topics.size(); i++) {
+ for (int i = 0; i < this.topics.size(); i++) {
session.send(this.topics.get(i), this.payloads.get(i));
}
}
diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandlerIntegrationTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandlerIntegrationTests.java
index c84dc71d..62e0407c 100644
--- a/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandlerIntegrationTests.java
+++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandlerIntegrationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +33,7 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
+
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.messaging.Message;
@@ -48,9 +49,7 @@ import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.Assert;
import org.springframework.util.SocketUtils;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
/**
* Integration tests for {@link StompBrokerRelayMessageHandler} running against ActiveMQ.
@@ -59,13 +58,14 @@ import static org.junit.Assert.assertTrue;
*/
public class StompBrokerRelayMessageHandlerIntegrationTests {
+ private static final Charset UTF_8 = Charset.forName("UTF-8");
+
+
@Rule
public final TestName testName = new TestName();
private static final Log logger = LogFactory.getLog(StompBrokerRelayMessageHandlerIntegrationTests.class);
- private static final Charset UTF_8 = Charset.forName("UTF-8");
-
private StompBrokerRelayMessageHandler relay;
private BrokerService activeMQBroker;
@@ -142,9 +142,9 @@ public class StompBrokerRelayMessageHandlerIntegrationTests {
logger.debug("Broker stopped");
}
+
@Test
public void publishSubscribe() throws Exception {
-
logger.debug("Starting test publishSubscribe()");
String sess1 = "sess1";
@@ -167,7 +167,7 @@ public class StompBrokerRelayMessageHandlerIntegrationTests {
this.responseHandler.expectMessages(send);
}
- @Test(expected=MessageDeliveryException.class)
+ @Test(expected = MessageDeliveryException.class)
public void messageDeliveryExceptionIfSystemSessionForwardFails() throws Exception {
logger.debug("Starting test messageDeliveryExceptionIfSystemSessionForwardFails()");
@@ -181,7 +181,6 @@ public class StompBrokerRelayMessageHandlerIntegrationTests {
@Test
public void brokerBecomingUnvailableTriggersErrorFrame() throws Exception {
-
logger.debug("Starting test brokerBecomingUnvailableTriggersErrorFrame()");
String sess1 = "sess1";
@@ -197,7 +196,6 @@ public class StompBrokerRelayMessageHandlerIntegrationTests {
@Test
public void brokerAvailabilityEventWhenStopped() throws Exception {
-
logger.debug("Starting test brokerAvailabilityEventWhenStopped()");
stopActiveMqBrokerAndAwait();
@@ -206,7 +204,6 @@ public class StompBrokerRelayMessageHandlerIntegrationTests {
@Test
public void relayReconnectsIfBrokerComesBackUp() throws Exception {
-
logger.debug("Starting test relayReconnectsIfBrokerComesBackUp()");
String sess1 = "sess1";
@@ -232,7 +229,6 @@ public class StompBrokerRelayMessageHandlerIntegrationTests {
@Test
public void disconnectWithReceipt() throws Exception {
-
logger.debug("Starting test disconnectWithReceipt()");
MessageExchange connect = MessageExchangeBuilder.connect("sess1").build();
@@ -270,6 +266,7 @@ public class StompBrokerRelayMessageHandlerIntegrationTests {
}
}
+
private static class TestMessageHandler implements MessageHandler {
private final BlockingQueue<Message<?>> queue = new LinkedBlockingQueue<>();
@@ -283,17 +280,13 @@ public class StompBrokerRelayMessageHandlerIntegrationTests {
}
public void expectMessages(MessageExchange... messageExchanges) throws InterruptedException {
-
List<MessageExchange> expectedMessages =
new ArrayList<MessageExchange>(Arrays.<MessageExchange>asList(messageExchanges));
-
while (expectedMessages.size() > 0) {
Message<?> message = this.queue.poll(10000, TimeUnit.MILLISECONDS);
assertNotNull("Timed out waiting for messages, expected [" + expectedMessages + "]", message);
-
MessageExchange match = findMatch(expectedMessages, message);
assertNotNull("Unexpected message=" + message + ", expected [" + expectedMessages + "]", match);
-
expectedMessages.remove(match);
}
}
@@ -308,6 +301,7 @@ public class StompBrokerRelayMessageHandlerIntegrationTests {
}
}
+
/**
* Holds a message as well as expected and actual messages matched against expectations.
*/
@@ -326,7 +320,7 @@ public class StompBrokerRelayMessageHandlerIntegrationTests {
}
public boolean matchMessage(Message<?> message) {
- for (int i=0 ; i < this.expected.length; i++) {
+ for (int i = 0 ; i < this.expected.length; i++) {
if (this.expected[i].match(message)) {
this.actual[i] = message;
return true;
@@ -343,6 +337,7 @@ public class StompBrokerRelayMessageHandlerIntegrationTests {
}
}
+
private static class MessageExchangeBuilder {
private final Message<?> message;
@@ -351,8 +346,7 @@ public class StompBrokerRelayMessageHandlerIntegrationTests {
private final List<MessageMatcher> expected = new ArrayList<>();
-
- private MessageExchangeBuilder(Message<?> message) {
+ public MessageExchangeBuilder(Message<?> message) {
this.message = message;
this.headers = StompHeaderAccessor.wrap(message);
}
@@ -442,25 +436,24 @@ public class StompBrokerRelayMessageHandlerIntegrationTests {
}
}
- private static interface MessageMatcher {
- boolean match(Message<?> message);
+ private interface MessageMatcher {
+ boolean match(Message<?> message);
}
+
private static class StompFrameMessageMatcher implements MessageMatcher {
private final StompCommand command;
private final String sessionId;
-
public StompFrameMessageMatcher(StompCommand command, String sessionId) {
this.command = command;
this.sessionId = sessionId;
}
-
@Override
public final boolean match(Message<?> message) {
StompHeaderAccessor headers = StompHeaderAccessor.wrap(message);
@@ -480,6 +473,7 @@ public class StompBrokerRelayMessageHandlerIntegrationTests {
}
}
+
private static class StompReceiptFrameMessageMatcher extends StompFrameMessageMatcher {
private final String receiptId;
@@ -500,6 +494,7 @@ public class StompBrokerRelayMessageHandlerIntegrationTests {
}
}
+
private static class StompMessageFrameMessageMatcher extends StompFrameMessageMatcher {
private final String subscriptionId;
@@ -508,7 +503,6 @@ public class StompBrokerRelayMessageHandlerIntegrationTests {
private final Object payload;
-
public StompMessageFrameMessageMatcher(String sessionId, String subscriptionId, String destination, Object payload) {
super(StompCommand.MESSAGE, sessionId);
this.subscriptionId = subscriptionId;
@@ -536,18 +530,17 @@ public class StompBrokerRelayMessageHandlerIntegrationTests {
}
protected String getPayloadAsText() {
- return (this.payload instanceof byte[])
- ? new String((byte[]) this.payload, UTF_8) : payload.toString();
+ return (this.payload instanceof byte[]) ?
+ new String((byte[]) this.payload, UTF_8) : this.payload.toString();
}
}
- private static class StompConnectedFrameMessageMatcher extends StompFrameMessageMatcher {
+ private static class StompConnectedFrameMessageMatcher extends StompFrameMessageMatcher {
public StompConnectedFrameMessageMatcher(String sessionId) {
super(StompCommand.CONNECTED, sessionId);
}
-
}
}
diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompCodecTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompCodecTests.java
index 3da0df98..b6afa9a7 100644
--- a/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompCodecTests.java
+++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompCodecTests.java
@@ -325,7 +325,8 @@ public class StompCodecTests {
this.decoder.apply(buffer);
if (consumer.arguments.isEmpty()) {
return null;
- } else {
+ }
+ else {
return consumer.arguments.get(0);
}
}
diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/user/DefaultUserDestinationResolverTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/user/DefaultUserDestinationResolverTests.java
index fc12a5ba..c872bc55 100644
--- a/spring-messaging/src/test/java/org/springframework/messaging/simp/user/DefaultUserDestinationResolverTests.java
+++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/user/DefaultUserDestinationResolverTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,6 +29,7 @@ import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessageType;
import org.springframework.messaging.simp.TestPrincipal;
import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
/**
@@ -71,9 +72,23 @@ public class DefaultUserDestinationResolverTests {
assertEquals(user.getName(), actual.getUser());
}
- // SPR-11325
+ @Test // SPR-14044
+ public void handleSubscribeForDestinationWithoutLeadingSlash() {
+ AntPathMatcher pathMatcher = new AntPathMatcher();
+ pathMatcher.setPathSeparator(".");
+ this.resolver.setPathMatcher(pathMatcher);
- @Test
+ TestPrincipal user = new TestPrincipal("joe");
+ String destination = "/user/jms.queue.call";
+ Message<?> message = createMessage(SimpMessageType.SUBSCRIBE, user, "123", destination);
+ UserDestinationResult actual = this.resolver.resolveDestination(message);
+
+ assertEquals(1, actual.getTargetDestinations().size());
+ assertEquals("jms.queue.call-user123", actual.getTargetDestinations().iterator().next());
+ assertEquals(destination, actual.getSubscribeDestination());
+ }
+
+ @Test // SPR-11325
public void handleSubscribeOneUserMultipleSessions() {
TestSimpUser simpUser = new TestSimpUser("joe");
@@ -125,9 +140,23 @@ public class DefaultUserDestinationResolverTests {
assertEquals(user.getName(), actual.getUser());
}
- // SPR-12444
+ @Test // SPR-14044
+ public void handleMessageForDestinationWithDotSeparator() {
+ AntPathMatcher pathMatcher = new AntPathMatcher();
+ pathMatcher.setPathSeparator(".");
+ this.resolver.setPathMatcher(pathMatcher);
- @Test
+ TestPrincipal user = new TestPrincipal("joe");
+ String destination = "/user/joe/jms.queue.call";
+ Message<?> message = createMessage(SimpMessageType.MESSAGE, user, "123", destination);
+ UserDestinationResult actual = this.resolver.resolveDestination(message);
+
+ assertEquals(1, actual.getTargetDestinations().size());
+ assertEquals("jms.queue.call-user123", actual.getTargetDestinations().iterator().next());
+ assertEquals("/user/jms.queue.call", actual.getSubscribeDestination());
+ }
+
+ @Test // SPR-12444
public void handleMessageToOtherUser() {
TestSimpUser otherSimpUser = new TestSimpUser("anna");
diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/user/MultiServerUserRegistryTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/user/MultiServerUserRegistryTests.java
index f4910601..5ce5dd70 100644
--- a/spring-messaging/src/test/java/org/springframework/messaging/simp/user/MultiServerUserRegistryTests.java
+++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/user/MultiServerUserRegistryTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
package org.springframework.messaging.simp.user;
+import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
@@ -42,7 +43,7 @@ public class MultiServerUserRegistryTests {
private SimpUserRegistry localRegistry;
- private MultiServerUserRegistry multiServerRegistry;
+ private MultiServerUserRegistry registry;
private MessageConverter converter;
@@ -50,48 +51,49 @@ public class MultiServerUserRegistryTests {
@Before
public void setUp() throws Exception {
this.localRegistry = Mockito.mock(SimpUserRegistry.class);
- this.multiServerRegistry = new MultiServerUserRegistry(this.localRegistry);
+ this.registry = new MultiServerUserRegistry(this.localRegistry);
this.converter = new MappingJackson2MessageConverter();
}
+
@Test
public void getUserFromLocalRegistry() throws Exception {
-
SimpUser user = Mockito.mock(SimpUser.class);
Set<SimpUser> users = Collections.singleton(user);
when(this.localRegistry.getUsers()).thenReturn(users);
when(this.localRegistry.getUser("joe")).thenReturn(user);
- assertEquals(1, this.multiServerRegistry.getUsers().size());
- assertSame(user, this.multiServerRegistry.getUser("joe"));
+ assertEquals(1, this.registry.getUsers().size());
+ assertSame(user, this.registry.getUser("joe"));
}
@Test
public void getUserFromRemoteRegistry() throws Exception {
- TestSimpSession remoteSession = new TestSimpSession("remote-sess");
- remoteSession.addSubscriptions(new TestSimpSubscription("remote-sub", "/remote-dest"));
- TestSimpUser remoteUser = new TestSimpUser("joe");
- remoteUser.addSessions(remoteSession);
- SimpUserRegistry remoteUserRegistry = mock(SimpUserRegistry.class);
- when(remoteUserRegistry.getUsers()).thenReturn(Collections.singleton(remoteUser));
+ // Prepare broadcast message from remote server
+ TestSimpUser testUser = new TestSimpUser("joe");
+ TestSimpSession testSession = new TestSimpSession("remote-sess");
+ testSession.addSubscriptions(new TestSimpSubscription("remote-sub", "/remote-dest"));
+ testUser.addSessions(testSession);
+ SimpUserRegistry testRegistry = mock(SimpUserRegistry.class);
+ when(testRegistry.getUsers()).thenReturn(Collections.singleton(testUser));
+ Object registryDto = new MultiServerUserRegistry(testRegistry).getLocalRegistryDto();
+ Message<?> message = this.converter.toMessage(registryDto, null);
- MultiServerUserRegistry remoteRegistry = new MultiServerUserRegistry(remoteUserRegistry);
- Message<?> message = this.converter.toMessage(remoteRegistry.getLocalRegistryDto(), null);
+ // Add remote registry
+ this.registry.addRemoteRegistryDto(message, this.converter, 20000);
- this.multiServerRegistry.addRemoteRegistryDto(message, this.converter, 20000);
- assertEquals(1, this.multiServerRegistry.getUsers().size());
- SimpUser user = this.multiServerRegistry.getUser("joe");
+ assertEquals(1, this.registry.getUsers().size());
+ SimpUser user = this.registry.getUser("joe");
assertNotNull(user);
+ assertTrue(user.hasSessions());
assertEquals(1, user.getSessions().size());
-
SimpSession session = user.getSession("remote-sess");
assertNotNull(session);
assertEquals("remote-sess", session.getId());
assertSame(user, session.getUser());
assertEquals(1, session.getSubscriptions().size());
-
SimpSubscription subscription = session.getSubscriptions().iterator().next();
assertEquals("remote-sub", subscription.getId());
assertSame(session, subscription.getSession());
@@ -99,44 +101,33 @@ public class MultiServerUserRegistryTests {
}
@Test
- public void findUserFromRemoteRegistry() throws Exception {
+ public void findSubscriptionsFromRemoteRegistry() throws Exception {
- TestSimpSubscription subscription1 = new TestSimpSubscription("sub1", "/match");
- TestSimpSession session1 = new TestSimpSession("sess1");
- session1.addSubscriptions(subscription1);
+ // Prepare broadcast message from remote server
TestSimpUser user1 = new TestSimpUser("joe");
- user1.addSessions(session1);
-
- TestSimpSubscription subscription2 = new TestSimpSubscription("sub1", "/match");
- TestSimpSession session2 = new TestSimpSession("sess2");
- session2.addSubscriptions(subscription2);
TestSimpUser user2 = new TestSimpUser("jane");
- user2.addSessions(session2);
-
- TestSimpSubscription subscription3 = new TestSimpSubscription("sub1", "/not-a-match");
- TestSimpSession session3 = new TestSimpSession("sess3");
- session3.addSubscriptions(subscription3);
TestSimpUser user3 = new TestSimpUser("jack");
+ TestSimpSession session1 = new TestSimpSession("sess1");
+ TestSimpSession session2 = new TestSimpSession("sess2");
+ TestSimpSession session3 = new TestSimpSession("sess3");
+ session1.addSubscriptions(new TestSimpSubscription("sub1", "/match"));
+ session2.addSubscriptions(new TestSimpSubscription("sub1", "/match"));
+ session3.addSubscriptions(new TestSimpSubscription("sub1", "/not-a-match"));
+ user1.addSessions(session1);
+ user2.addSessions(session2);
user3.addSessions(session3);
+ SimpUserRegistry userRegistry = mock(SimpUserRegistry.class);
+ when(userRegistry.getUsers()).thenReturn(new HashSet<>(Arrays.asList(user1, user2, user3)));
+ Object registryDto = new MultiServerUserRegistry(userRegistry).getLocalRegistryDto();
+ Message<?> message = this.converter.toMessage(registryDto, null);
- SimpUserRegistry remoteUserRegistry = mock(SimpUserRegistry.class);
- when(remoteUserRegistry.getUsers()).thenReturn(new HashSet<SimpUser>(Arrays.asList(user1, user2, user3)));
+ // Add remote registry
+ this.registry.addRemoteRegistryDto(message, this.converter, 20000);
- MultiServerUserRegistry remoteRegistry = new MultiServerUserRegistry(remoteUserRegistry);
- Message<?> message = this.converter.toMessage(remoteRegistry.getLocalRegistryDto(), null);
-
- this.multiServerRegistry.addRemoteRegistryDto(message, this.converter, 20000);
- assertEquals(3, this.multiServerRegistry.getUsers().size());
-
- Set<SimpSubscription> matches = this.multiServerRegistry.findSubscriptions(new SimpSubscriptionMatcher() {
- @Override
- public boolean match(SimpSubscription subscription) {
- return subscription.getDestination().equals("/match");
- }
- });
+ assertEquals(3, this.registry.getUsers().size());
+ Set<SimpSubscription> matches = this.registry.findSubscriptions(s -> s.getDestination().equals("/match"));
assertEquals(2, matches.size());
-
Iterator<SimpSubscription> iterator = matches.iterator();
Set<String> sessionIds = new HashSet<>(2);
sessionIds.add(iterator.next().getSession().getId());
@@ -144,23 +135,61 @@ public class MultiServerUserRegistryTests {
assertEquals(new HashSet<>(Arrays.asList("sess1", "sess2")), sessionIds);
}
+ @Test // SPR-13800
+ public void getSessionsWhenUserIsConnectedToMultipleServers() throws Exception {
+
+ // Add user to local registry
+ TestSimpUser localUser = new TestSimpUser("joe");
+ TestSimpSession localSession = new TestSimpSession("sess123");
+ localUser.addSessions(localSession);
+ when(this.localRegistry.getUser("joe")).thenReturn(localUser);
+
+ // Prepare broadcast message from remote server
+ TestSimpUser remoteUser = new TestSimpUser("joe");
+ TestSimpSession remoteSession = new TestSimpSession("sess456");
+ remoteUser.addSessions(remoteSession);
+ SimpUserRegistry remoteRegistry = mock(SimpUserRegistry.class);
+ when(remoteRegistry.getUsers()).thenReturn(Collections.singleton(remoteUser));
+ Object remoteRegistryDto = new MultiServerUserRegistry(remoteRegistry).getLocalRegistryDto();
+ Message<?> message = this.converter.toMessage(remoteRegistryDto, null);
+
+ // Add remote registry
+ this.registry.addRemoteRegistryDto(message, this.converter, 20000);
+
+
+ assertEquals(1, this.registry.getUsers().size());
+ SimpUser user = this.registry.getUsers().iterator().next();
+ assertTrue(user.hasSessions());
+ assertEquals(2, user.getSessions().size());
+ assertThat(user.getSessions(), containsInAnyOrder(localSession, remoteSession));
+ assertSame(localSession, user.getSession("sess123"));
+ assertEquals(remoteSession, user.getSession("sess456"));
+
+ user = this.registry.getUser("joe");
+ assertEquals(2, user.getSessions().size());
+ assertThat(user.getSessions(), containsInAnyOrder(localSession, remoteSession));
+ assertSame(localSession, user.getSession("sess123"));
+ assertEquals(remoteSession, user.getSession("sess456"));
+ }
+
@Test
public void purgeExpiredRegistries() throws Exception {
- TestSimpUser remoteUser = new TestSimpUser("joe");
- remoteUser.addSessions(new TestSimpSession("remote-sub"));
- SimpUserRegistry remoteUserRegistry = mock(SimpUserRegistry.class);
- when(remoteUserRegistry.getUsers()).thenReturn(Collections.singleton(remoteUser));
+ // Prepare broadcast message from remote server
+ TestSimpUser testUser = new TestSimpUser("joe");
+ testUser.addSessions(new TestSimpSession("remote-sub"));
+ SimpUserRegistry testRegistry = mock(SimpUserRegistry.class);
+ when(testRegistry.getUsers()).thenReturn(Collections.singleton(testUser));
+ Object registryDto = new MultiServerUserRegistry(testRegistry).getLocalRegistryDto();
+ Message<?> message = this.converter.toMessage(registryDto, null);
- MultiServerUserRegistry remoteRegistry = new MultiServerUserRegistry(remoteUserRegistry);
- Message<?> message = this.converter.toMessage(remoteRegistry.getLocalRegistryDto(), null);
+ // Add remote registry
+ this.registry.addRemoteRegistryDto(message, this.converter, -1);
- long expirationPeriod = -1;
- this.multiServerRegistry.addRemoteRegistryDto(message, this.converter, expirationPeriod);
- assertEquals(1, this.multiServerRegistry.getUsers().size());
- this.multiServerRegistry.purgeExpiredRegistries();
- assertEquals(0, this.multiServerRegistry.getUsers().size());
+ assertEquals(1, this.registry.getUsers().size());
+ this.registry.purgeExpiredRegistries();
+ assertEquals(0, this.registry.getUsers().size());
}
}
diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/user/TestSimpSession.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/user/TestSimpSession.java
index f608e030..4ab378ad 100644
--- a/spring-messaging/src/test/java/org/springframework/messaging/simp/user/TestSimpSession.java
+++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/user/TestSimpSession.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,4 +59,19 @@ public class TestSimpSession implements SimpSession {
}
}
+ @Override
+ public boolean equals(Object other) {
+ return (this == other || (other instanceof SimpSession && this.id.equals(((SimpSession) other).getId())));
+ }
+
+ @Override
+ public int hashCode() {
+ return this.id.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "id=" + this.id + ", subscriptions=" + this.subscriptions;
+ }
+
}
diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/user/TestSimpSubscription.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/user/TestSimpSubscription.java
index 28f92823..5a13c234 100644
--- a/spring-messaging/src/test/java/org/springframework/messaging/simp/user/TestSimpSubscription.java
+++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/user/TestSimpSubscription.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
package org.springframework.messaging.simp.user;
+import org.springframework.util.ObjectUtils;
+
public class TestSimpSubscription implements SimpSubscription {
private String id;
@@ -49,4 +51,27 @@ public class TestSimpSubscription implements SimpSubscription {
return destination;
}
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof SimpSubscription)) {
+ return false;
+ }
+ SimpSubscription otherSubscription = (SimpSubscription) other;
+ return (ObjectUtils.nullSafeEquals(getSession(), otherSubscription.getSession()) &&
+ this.id.equals(otherSubscription.getId()));
+ }
+
+ @Override
+ public int hashCode() {
+ return this.id.hashCode() * 31 + ObjectUtils.nullSafeHashCode(getSession());
+ }
+
+ @Override
+ public String toString() {
+ return "destination=" + this.destination;
+ }
+
}
diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/user/TestSimpUser.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/user/TestSimpUser.java
index 19d2099b..1607c201 100644
--- a/spring-messaging/src/test/java/org/springframework/messaging/simp/user/TestSimpUser.java
+++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/user/TestSimpUser.java
@@ -59,4 +59,19 @@ public class TestSimpUser implements SimpUser {
}
}
+ @Override
+ public boolean equals(Object other) {
+ return (this == other || (other instanceof SimpUser && this.name.equals(((SimpUser) other).getName())));
+ }
+
+ @Override
+ public int hashCode() {
+ return this.name.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "name=" + this.name + ", sessions=" + this.sessions;
+ }
+
}
diff --git a/spring-messaging/src/test/java/org/springframework/messaging/support/MessageHeaderAccessorTests.java b/spring-messaging/src/test/java/org/springframework/messaging/support/MessageHeaderAccessorTests.java
index a09429fb..7d350e8e 100644
--- a/spring-messaging/src/test/java/org/springframework/messaging/support/MessageHeaderAccessorTests.java
+++ b/spring-messaging/src/test/java/org/springframework/messaging/support/MessageHeaderAccessorTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.messaging.support;
import java.nio.charset.Charset;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@@ -40,6 +41,7 @@ import static org.junit.Assert.*;
*
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
+ * @author Juergen Hoeller
*/
public class MessageHeaderAccessorTests {
@@ -90,6 +92,24 @@ public class MessageHeaderAccessorTests {
}
@Test
+ public void testRemoveHeader() {
+ Message<?> message = new GenericMessage<>("payload", Collections.singletonMap("foo", "bar"));
+ MessageHeaderAccessor accessor = new MessageHeaderAccessor(message);
+ accessor.removeHeader("foo");
+ Map<String, Object> headers = accessor.toMap();
+ assertFalse(headers.containsKey("foo"));
+ }
+
+ @Test
+ public void testRemoveHeaderEvenIfNull() {
+ Message<?> message = new GenericMessage<>("payload", Collections.singletonMap("foo", null));
+ MessageHeaderAccessor accessor = new MessageHeaderAccessor(message);
+ accessor.removeHeader("foo");
+ Map<String, Object> headers = accessor.toMap();
+ assertFalse(headers.containsKey("foo"));
+ }
+
+ @Test
public void removeHeaders() {
Map<String, Object> map = new HashMap<>();
map.put("foo", "bar");
@@ -153,7 +173,6 @@ public class MessageHeaderAccessorTests {
@Test
public void toMap() {
-
MessageHeaderAccessor accessor = new MessageHeaderAccessor();
accessor.setHeader("foo", "bar1");
@@ -380,7 +399,6 @@ public class MessageHeaderAccessorTests {
}
-
public static class TestMessageHeaderAccessor extends MessageHeaderAccessor {
private TestMessageHeaderAccessor() {
diff --git a/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/HibernateExceptionTranslator.java b/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/HibernateExceptionTranslator.java
index 5e4886f2..38070d62 100644
--- a/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/HibernateExceptionTranslator.java
+++ b/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/HibernateExceptionTranslator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,14 +16,18 @@
package org.springframework.orm.hibernate5;
+import javax.persistence.PersistenceException;
+
import org.hibernate.HibernateException;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
+import org.springframework.orm.jpa.EntityManagerFactoryUtils;
/**
* {@link PersistenceExceptionTranslator} capable of translating {@link HibernateException}
- * instances to Spring's {@link DataAccessException} hierarchy.
+ * instances to Spring's {@link DataAccessException} hierarchy. As of Spring 4.3.2 and
+ * Hibernate 5.2, it also converts standard JPA {@link PersistenceException} instances.
*
* <p>Extended by {@link LocalSessionFactoryBean}, so there is no need to declare this
* translator in addition to a {@code LocalSessionFactoryBean}.
@@ -35,6 +39,7 @@ import org.springframework.dao.support.PersistenceExceptionTranslator;
* @since 4.2
* @see org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor
* @see SessionFactoryUtils#convertHibernateAccessException(HibernateException)
+ * @see EntityManagerFactoryUtils#convertJpaAccessExceptionIfPossible(RuntimeException)
*/
public class HibernateExceptionTranslator implements PersistenceExceptionTranslator {
@@ -43,13 +48,16 @@ public class HibernateExceptionTranslator implements PersistenceExceptionTransla
if (ex instanceof HibernateException) {
return convertHibernateAccessException((HibernateException) ex);
}
- return null;
+ if (ex instanceof PersistenceException && ex.getCause() instanceof HibernateException) {
+ return convertHibernateAccessException((HibernateException) ex.getCause());
+ }
+ return EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(ex);
}
/**
* Convert the given HibernateException to an appropriate exception from the
* {@code org.springframework.dao} hierarchy.
- * @param ex HibernateException that occured
+ * @param ex HibernateException that occurred
* @return a corresponding DataAccessException
* @see SessionFactoryUtils#convertHibernateAccessException
*/
diff --git a/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/HibernateTemplate.java b/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/HibernateTemplate.java
index 878357d4..83b43d4a 100644
--- a/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/HibernateTemplate.java
+++ b/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/HibernateTemplate.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,10 +24,10 @@ import java.lang.reflect.Proxy;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
+import javax.persistence.PersistenceException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-
import org.hibernate.Criteria;
import org.hibernate.Filter;
import org.hibernate.FlushMode;
@@ -35,7 +35,6 @@ import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
-import org.hibernate.Query;
import org.hibernate.ReplicationMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
@@ -47,6 +46,7 @@ import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
+import org.springframework.util.ReflectionUtils;
/**
* Helper class that simplifies Hibernate data access code. Automatically
@@ -85,6 +85,20 @@ import org.springframework.util.Assert;
*/
public class HibernateTemplate implements HibernateOperations, InitializingBean {
+ private static final Method createQueryMethod;
+
+ static {
+ // Hibernate 5.2's createQuery method declares a new subtype as return type,
+ // so we need to use reflection for binary compatibility with 5.0/5.1 here.
+ try {
+ createQueryMethod = Session.class.getMethod("createQuery", String.class);
+ }
+ catch (NoSuchMethodException ex) {
+ throw new IllegalStateException("Incompatible Hibernate Session API", ex);
+ }
+ }
+
+
protected final Log logger = LogFactory.getLog(getClass());
private SessionFactory sessionFactory;
@@ -211,7 +225,7 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
* <p>To specify the query region to be used for queries cached
* by this template, set the "queryCacheRegion" property.
* @see #setQueryCacheRegion
- * @see Query#setCacheable
+ * @see org.hibernate.Query#setCacheable
* @see Criteria#setCacheable
*/
public void setCacheQueries(boolean cacheQueries) {
@@ -232,7 +246,7 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
* <p>The cache region will not take effect unless queries created by this
* template are configured to be cached via the "cacheQueries" property.
* @see #setCacheQueries
- * @see Query#setCacheRegion
+ * @see org.hibernate.Query#setCacheRegion
* @see Criteria#setCacheRegion
*/
public void setQueryCacheRegion(String queryCacheRegion) {
@@ -317,6 +331,7 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
* @return a result object returned by the action, or {@code null}
* @throws DataAccessException in case of Hibernate errors
*/
+ @SuppressWarnings("deprecation")
protected <T> T doExecute(HibernateCallback<T> action, boolean enforceNativeSession) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
@@ -343,6 +358,12 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
catch (HibernateException ex) {
throw SessionFactoryUtils.convertHibernateAccessException(ex);
}
+ catch (PersistenceException ex) {
+ if (ex.getCause() instanceof HibernateException) {
+ throw SessionFactoryUtils.convertHibernateAccessException((HibernateException) ex.getCause());
+ }
+ throw ex;
+ }
catch (RuntimeException ex) {
// Callback code threw application exception...
throw ex;
@@ -499,7 +520,7 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
public <T> List<T> loadAll(final Class<T> entityClass) throws DataAccessException {
return executeWithNativeSession(new HibernateCallback<List<T>>() {
@Override
- @SuppressWarnings("unchecked")
+ @SuppressWarnings({"unchecked", "deprecation"})
public List<T> doInHibernate(Session session) throws HibernateException {
Criteria criteria = session.createCriteria(entityClass);
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
@@ -862,8 +883,10 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
public List<?> find(final String queryString, final Object... values) throws DataAccessException {
return executeWithNativeSession(new HibernateCallback<List<?>>() {
@Override
+ @SuppressWarnings({"rawtypes", "deprecation"})
public List<?> doInHibernate(Session session) throws HibernateException {
- Query queryObject = session.createQuery(queryString);
+ org.hibernate.Query queryObject = (org.hibernate.Query)
+ ReflectionUtils.invokeMethod(createQueryMethod, session, queryString);
prepareQuery(queryObject);
if (values != null) {
for (int i = 0; i < values.length; i++) {
@@ -891,13 +914,13 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
}
return executeWithNativeSession(new HibernateCallback<List<?>>() {
@Override
+ @SuppressWarnings({"rawtypes", "deprecation"})
public List<?> doInHibernate(Session session) throws HibernateException {
- Query queryObject = session.createQuery(queryString);
+ org.hibernate.Query queryObject = (org.hibernate.Query)
+ ReflectionUtils.invokeMethod(createQueryMethod, session, queryString);
prepareQuery(queryObject);
- if (values != null) {
- for (int i = 0; i < values.length; i++) {
- applyNamedParameterToQuery(queryObject, paramNames[i], values[i]);
- }
+ for (int i = 0; i < values.length; i++) {
+ applyNamedParameterToQuery(queryObject, paramNames[i], values[i]);
}
return queryObject.list();
}
@@ -910,8 +933,10 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
return executeWithNativeSession(new HibernateCallback<List<?>>() {
@Override
+ @SuppressWarnings({"rawtypes", "deprecation"})
public List<?> doInHibernate(Session session) throws HibernateException {
- Query queryObject = session.createQuery(queryString);
+ org.hibernate.Query queryObject = (org.hibernate.Query)
+ ReflectionUtils.invokeMethod(createQueryMethod, session, queryString);
prepareQuery(queryObject);
queryObject.setProperties(valueBean);
return queryObject.list();
@@ -928,8 +953,9 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
public List<?> findByNamedQuery(final String queryName, final Object... values) throws DataAccessException {
return executeWithNativeSession(new HibernateCallback<List<?>>() {
@Override
+ @SuppressWarnings({"rawtypes", "deprecation"})
public List<?> doInHibernate(Session session) throws HibernateException {
- Query queryObject = session.getNamedQuery(queryName);
+ org.hibernate.Query queryObject = session.getNamedQuery(queryName);
prepareQuery(queryObject);
if (values != null) {
for (int i = 0; i < values.length; i++) {
@@ -958,8 +984,9 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
}
return executeWithNativeSession(new HibernateCallback<List<?>>() {
@Override
+ @SuppressWarnings({"rawtypes", "deprecation"})
public List<?> doInHibernate(Session session) throws HibernateException {
- Query queryObject = session.getNamedQuery(queryName);
+ org.hibernate.Query queryObject = session.getNamedQuery(queryName);
prepareQuery(queryObject);
if (values != null) {
for (int i = 0; i < values.length; i++) {
@@ -977,8 +1004,9 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
return executeWithNativeSession(new HibernateCallback<List<?>>() {
@Override
+ @SuppressWarnings({"rawtypes", "deprecation"})
public List<?> doInHibernate(Session session) throws HibernateException {
- Query queryObject = session.getNamedQuery(queryName);
+ org.hibernate.Query queryObject = session.getNamedQuery(queryName);
prepareQuery(queryObject);
queryObject.setProperties(valueBean);
return queryObject.list();
@@ -1033,6 +1061,7 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
}
@Override
+ @SuppressWarnings("deprecation")
public <T> List<T> findByExample(
final String entityName, final T exampleEntity, final int firstResult, final int maxResults)
throws DataAccessException {
@@ -1066,8 +1095,10 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
public Iterator<?> iterate(final String queryString, final Object... values) throws DataAccessException {
return executeWithNativeSession(new HibernateCallback<Iterator<?>>() {
@Override
+ @SuppressWarnings({"rawtypes", "deprecation"})
public Iterator<?> doInHibernate(Session session) throws HibernateException {
- Query queryObject = session.createQuery(queryString);
+ org.hibernate.Query queryObject = (org.hibernate.Query)
+ ReflectionUtils.invokeMethod(createQueryMethod, session, queryString);
prepareQuery(queryObject);
if (values != null) {
for (int i = 0; i < values.length; i++) {
@@ -1093,8 +1124,10 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
public int bulkUpdate(final String queryString, final Object... values) throws DataAccessException {
return executeWithNativeSession(new HibernateCallback<Integer>() {
@Override
+ @SuppressWarnings({"rawtypes", "deprecation"})
public Integer doInHibernate(Session session) throws HibernateException {
- Query queryObject = session.createQuery(queryString);
+ org.hibernate.Query queryObject = (org.hibernate.Query)
+ ReflectionUtils.invokeMethod(createQueryMethod, session, queryString);
prepareQuery(queryObject);
if (values != null) {
for (int i = 0; i < values.length; i++) {
@@ -1122,7 +1155,7 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
* @see FlushMode#MANUAL
*/
protected void checkWriteOperationAllowed(Session session) throws InvalidDataAccessApiUsageException {
- if (isCheckWriteOperations() && session.getFlushMode().lessThan(FlushMode.COMMIT)) {
+ if (isCheckWriteOperations() && SessionFactoryUtils.getFlushMode(session).lessThan(FlushMode.COMMIT)) {
throw new InvalidDataAccessApiUsageException(
"Write operations are not allowed in read-only mode (FlushMode.MANUAL): "+
"Turn your Session into FlushMode.COMMIT/AUTO or remove 'readOnly' marker from transaction definition.");
@@ -1136,7 +1169,8 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
* @see #setCacheQueries
* @see #setQueryCacheRegion
*/
- protected void prepareQuery(Query queryObject) {
+ @SuppressWarnings({"rawtypes", "deprecation"})
+ protected void prepareQuery(org.hibernate.Query queryObject) {
if (isCacheQueries()) {
queryObject.setCacheable(true);
if (getQueryCacheRegion() != null) {
@@ -1192,7 +1226,8 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
* @param value the value of the parameter
* @throws HibernateException if thrown by the Query object
*/
- protected void applyNamedParameterToQuery(Query queryObject, String paramName, Object value)
+ @SuppressWarnings({"rawtypes", "deprecation"})
+ protected void applyNamedParameterToQuery(org.hibernate.Query queryObject, String paramName, Object value)
throws HibernateException {
if (value instanceof Collection) {
@@ -1221,6 +1256,7 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
}
@Override
+ @SuppressWarnings("deprecation")
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Invocation on Session interface coming in...
@@ -1243,8 +1279,8 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
// If return value is a Query or Criteria, apply transaction timeout.
// Applies to createQuery, getNamedQuery, createCriteria.
- if (retVal instanceof Query) {
- prepareQuery(((Query) retVal));
+ if (retVal instanceof org.hibernate.Query) {
+ prepareQuery(((org.hibernate.Query) retVal));
}
if (retVal instanceof Criteria) {
prepareCriteria(((Criteria) retVal));
diff --git a/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/HibernateTransactionManager.java b/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/HibernateTransactionManager.java
index 53f6b01a..746d70c2 100644
--- a/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/HibernateTransactionManager.java
+++ b/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/HibernateTransactionManager.java
@@ -18,6 +18,7 @@ package org.springframework.orm.hibernate5;
import java.sql.Connection;
import java.sql.ResultSet;
+import javax.persistence.PersistenceException;
import javax.sql.DataSource;
import org.hibernate.ConnectionReleaseMode;
@@ -411,6 +412,7 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
}
@Override
+ @SuppressWarnings("deprecation")
protected void doBegin(Object transaction, TransactionDefinition definition) {
HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
@@ -475,7 +477,7 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
if (!definition.isReadOnly() && !txObject.isNewSession()) {
// We need AUTO or COMMIT for a non-read-only transaction.
- FlushMode flushMode = session.getFlushMode();
+ FlushMode flushMode = SessionFactoryUtils.getFlushMode(session);
if (FlushMode.MANUAL.equals(flushMode)) {
session.setFlushMode(FlushMode.AUTO);
txObject.getSessionHolder().setPreviousFlushMode(flushMode);
@@ -587,6 +589,12 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
// assumably failed to flush changes to database
throw convertHibernateAccessException(ex);
}
+ catch (PersistenceException ex) {
+ if (ex.getCause() instanceof HibernateException) {
+ throw convertHibernateAccessException((HibernateException) ex.getCause());
+ }
+ throw ex;
+ }
}
@Override
@@ -606,6 +614,12 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
// Shouldn't really happen, as a rollback doesn't cause a flush.
throw convertHibernateAccessException(ex);
}
+ catch (PersistenceException ex) {
+ if (ex.getCause() instanceof HibernateException) {
+ throw convertHibernateAccessException((HibernateException) ex.getCause());
+ }
+ throw ex;
+ }
finally {
if (!txObject.isNewSession() && !this.hibernateManagedSession) {
// Clear all pending inserts/updates/deletes in the Session.
@@ -626,6 +640,7 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
}
@Override
+ @SuppressWarnings("deprecation")
protected void doCleanupAfterCompletion(Object transaction) {
HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
@@ -701,6 +716,7 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
* @param session the Hibernate Session to check
* @see ConnectionReleaseMode#ON_CLOSE
*/
+ @SuppressWarnings("deprecation")
protected boolean isSameConnectionForEntireSession(Session session) {
if (!(session instanceof SessionImplementor)) {
// The best we can do is to assume we're safe.
@@ -822,6 +838,12 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
catch (HibernateException ex) {
throw convertHibernateAccessException(ex);
}
+ catch (PersistenceException ex) {
+ if (ex.getCause() instanceof HibernateException) {
+ throw convertHibernateAccessException((HibernateException) ex.getCause());
+ }
+ throw ex;
+ }
}
}
diff --git a/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBean.java b/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBean.java
index bf613b02..c923a7d6 100644
--- a/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBean.java
+++ b/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,10 +23,13 @@ import javax.sql.DataSource;
import org.hibernate.Interceptor;
import org.hibernate.SessionFactory;
+import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.model.naming.ImplicitNamingStrategy;
import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
+import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
+import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
@@ -38,7 +41,9 @@ import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
+import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.core.type.filter.TypeFilter;
+import org.springframework.util.Assert;
/**
* {@link FactoryBean} that creates a Hibernate
@@ -46,9 +51,7 @@ import org.springframework.core.type.filter.TypeFilter;
* Hibernate SessionFactory in a Spring application context; the SessionFactory can
* then be passed to Hibernate-based data access objects via dependency injection.
*
- * <p>This class is similar in role to the same-named class in the {@code orm.hibernate3}
- * package. However, in practice, it is closer to {@code AnnotationSessionFactoryBean}
- * since its core purpose is to bootstrap a {@code SessionFactory} from package scanning.
+ * <p>Compatible with Hibernate 5.0/5.1 as well as 5.2, as of Spring 4.3.
*
* @author Juergen Hoeller
* @since 4.2
@@ -81,6 +84,8 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
private Object jtaTransactionManager;
+ private MultiTenantConnectionProvider multiTenantConnectionProvider;
+
private CurrentTenantIdentifierResolver currentTenantIdentifierResolver;
private TypeFilter[] entityTypeFilters;
@@ -93,7 +98,11 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
private String[] packagesToScan;
- private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
+ private AsyncTaskExecutor bootstrapExecutor;
+
+ private MetadataSources metadataSources;
+
+ private ResourcePatternResolver resourcePatternResolver;
private Configuration configuration;
@@ -231,6 +240,15 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
}
/**
+ * Set a {@link MultiTenantConnectionProvider} to be passed on to the SessionFactory.
+ * @since 4.3
+ * @see LocalSessionFactoryBuilder#setMultiTenantConnectionProvider
+ */
+ public void setMultiTenantConnectionProvider(MultiTenantConnectionProvider multiTenantConnectionProvider) {
+ this.multiTenantConnectionProvider = multiTenantConnectionProvider;
+ }
+
+ /**
* Set a {@link CurrentTenantIdentifierResolver} to be passed on to the SessionFactory.
* @see LocalSessionFactoryBuilder#setCurrentTenantIdentifierResolver
*/
@@ -243,7 +261,6 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
* <p>Default is to search all specified packages for classes annotated with
* {@code @javax.persistence.Entity}, {@code @javax.persistence.Embeddable}
* or {@code @javax.persistence.MappedSuperclass}.
- * @since 4.2
* @see #setPackagesToScan
*/
public void setEntityTypeFilters(TypeFilter... entityTypeFilters) {
@@ -298,15 +315,79 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
this.packagesToScan = packagesToScan;
}
+ /**
+ * Specify an asynchronous executor for background bootstrapping,
+ * e.g. a {@link org.springframework.core.task.SimpleAsyncTaskExecutor}.
+ * <p>{@code SessionFactory} initialization will then switch into background
+ * bootstrap mode, with a {@code SessionFactory} proxy immediately returned for
+ * injection purposes instead of waiting for Hibernate's bootstrapping to complete.
+ * However, note that the first actual call to a {@code SessionFactory} method will
+ * then block until Hibernate's bootstrapping completed, if not ready by then.
+ * For maximum benefit, make sure to avoid early {@code SessionFactory} calls
+ * in init methods of related beans, even for metadata introspection purposes.
+ * @see LocalSessionFactoryBuilder#buildSessionFactory(AsyncTaskExecutor)
+ * @since 4.3
+ */
+ public void setBootstrapExecutor(AsyncTaskExecutor bootstrapExecutor) {
+ this.bootstrapExecutor = bootstrapExecutor;
+ }
+
+ /**
+ * Specify a Hibernate {@link MetadataSources} service to use (e.g. reusing an
+ * existing one), potentially populated with a custom Hibernate bootstrap
+ * {@link org.hibernate.service.ServiceRegistry} as well.
+ * @since 4.3
+ */
+ public void setMetadataSources(MetadataSources metadataSources) {
+ Assert.notNull(metadataSources, "MetadataSources must not be null");
+ this.metadataSources = metadataSources;
+ }
+
+ /**
+ * Determine the Hibernate {@link MetadataSources} to use.
+ * <p>Can also be externally called to initialize and pre-populate a {@link MetadataSources}
+ * instance which is then going to be used for {@link SessionFactory} building.
+ * @return the MetadataSources to use (never {@code null})
+ * @since 4.3
+ * @see LocalSessionFactoryBuilder#LocalSessionFactoryBuilder(DataSource, ResourceLoader, MetadataSources)
+ */
+ public MetadataSources getMetadataSources() {
+ if (this.metadataSources == null) {
+ BootstrapServiceRegistryBuilder builder = new BootstrapServiceRegistryBuilder();
+ if (this.resourcePatternResolver != null) {
+ builder = builder.applyClassLoader(this.resourcePatternResolver.getClassLoader());
+ }
+ this.metadataSources = new MetadataSources(builder.build());
+ }
+ return this.metadataSources;
+ }
+
+ /**
+ * Specify a Spring {@link ResourceLoader} to use for Hibernate metadata.
+ * @param resourceLoader the ResourceLoader to use (never {@code null})
+ */
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
}
+ /**
+ * Determine the Spring {@link ResourceLoader} to use for Hibernate metadata.
+ * @return the ResourceLoader to use (never {@code null})
+ * @since 4.3
+ */
+ public ResourceLoader getResourceLoader() {
+ if (this.resourcePatternResolver == null) {
+ this.resourcePatternResolver = new PathMatchingResourcePatternResolver();
+ }
+ return this.resourcePatternResolver;
+ }
+
@Override
public void afterPropertiesSet() throws IOException {
- LocalSessionFactoryBuilder sfb = new LocalSessionFactoryBuilder(this.dataSource, this.resourcePatternResolver);
+ LocalSessionFactoryBuilder sfb = new LocalSessionFactoryBuilder(
+ this.dataSource, getResourceLoader(), getMetadataSources());
if (this.configLocations != null) {
for (Resource resource : this.configLocations) {
@@ -372,6 +453,10 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
sfb.setJtaTransactionManager(this.jtaTransactionManager);
}
+ if (this.multiTenantConnectionProvider != null) {
+ sfb.setMultiTenantConnectionProvider(this.multiTenantConnectionProvider);
+ }
+
if (this.currentTenantIdentifierResolver != null) {
sfb.setCurrentTenantIdentifierResolver(this.currentTenantIdentifierResolver);
}
@@ -413,7 +498,8 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
* @see LocalSessionFactoryBuilder#buildSessionFactory
*/
protected SessionFactory buildSessionFactory(LocalSessionFactoryBuilder sfb) {
- return sfb.buildSessionFactory();
+ return (this.bootstrapExecutor != null ? sfb.buildSessionFactory(this.bootstrapExecutor) :
+ sfb.buildSessionFactory());
}
/**
diff --git a/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBuilder.java b/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBuilder.java
index 73894090..f74fb8ad 100644
--- a/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBuilder.java
+++ b/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBuilder.java
@@ -17,9 +17,16 @@
package org.springframework.orm.hibernate5;
import java.io.IOException;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import javax.persistence.Embeddable;
@@ -30,16 +37,23 @@ import javax.transaction.TransactionManager;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
+import org.hibernate.SessionFactory;
+import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
+import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
+import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
+import org.hibernate.engine.spi.SessionFactoryImplementor;
+import org.springframework.core.InfrastructureProxy;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
+import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
@@ -57,6 +71,8 @@ import org.springframework.util.ClassUtils;
* <p>This is designed for programmatic use, e.g. in {@code @Bean} factory methods.
* Consider using {@link LocalSessionFactoryBean} for XML bean definition files.
*
+ * <p>Compatible with Hibernate 5.0/5.1 as well as 5.2, as of Spring 4.3.
+ *
* @author Juergen Hoeller
* @since 4.2
* @see LocalSessionFactoryBean
@@ -107,12 +123,29 @@ public class LocalSessionFactoryBuilder extends Configuration {
* @param resourceLoader the ResourceLoader to load application classes from
*/
public LocalSessionFactoryBuilder(DataSource dataSource, ResourceLoader resourceLoader) {
- super(new BootstrapServiceRegistryBuilder().applyClassLoader(resourceLoader.getClassLoader()).build());
+ this(dataSource, resourceLoader, new MetadataSources(
+ new BootstrapServiceRegistryBuilder().applyClassLoader(resourceLoader.getClassLoader()).build()));
+ }
+
+ /**
+ * Create a new LocalSessionFactoryBuilder for the given DataSource.
+ * @param dataSource the JDBC DataSource that the resulting Hibernate SessionFactory should be using
+ * (may be {@code null})
+ * @param resourceLoader the ResourceLoader to load application classes from
+ * @param metadataSources the Hibernate MetadataSources service to use (e.g. reusing an existing one)
+ * @since 4.3
+ */
+ public LocalSessionFactoryBuilder(DataSource dataSource, ResourceLoader resourceLoader, MetadataSources metadataSources) {
+ super(metadataSources);
getProperties().put(Environment.CURRENT_SESSION_CONTEXT_CLASS, SpringSessionContext.class.getName());
if (dataSource != null) {
getProperties().put(Environment.DATASOURCE, dataSource);
}
+
+ // Hibernate 5.2: manually enforce connection release mode ON_CLOSE (the former default)
+ getProperties().put("hibernate.connection.handling_mode", "DELAYED_ACQUISITION_AND_HOLD");
+
getProperties().put(AvailableSettings.CLASSLOADERS, Collections.singleton(resourceLoader.getClassLoader()));
this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
}
@@ -131,6 +164,7 @@ public class LocalSessionFactoryBuilder extends Configuration {
*/
public LocalSessionFactoryBuilder setJtaTransactionManager(Object jtaTransactionManager) {
Assert.notNull(jtaTransactionManager, "Transaction manager reference must not be null");
+
if (jtaTransactionManager instanceof JtaTransactionManager) {
boolean webspherePresent = ClassUtils.isPresent("com.ibm.wsspi.uow.UOWManager", getClass().getClassLoader());
if (webspherePresent) {
@@ -156,15 +190,39 @@ public class LocalSessionFactoryBuilder extends Configuration {
throw new IllegalArgumentException(
"Unknown transaction manager type: " + jtaTransactionManager.getClass().getName());
}
+
+ // Hibernate 5.2: manually enforce connection release mode AFTER_STATEMENT (the JTA default)
+ getProperties().put("hibernate.connection.handling_mode", "DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT");
+
+ return this;
+ }
+
+ /**
+ * Set a {@link MultiTenantConnectionProvider} to be passed on to the SessionFactory.
+ * @since 4.3
+ * @see AvailableSettings#MULTI_TENANT_CONNECTION_PROVIDER
+ */
+ public LocalSessionFactoryBuilder setMultiTenantConnectionProvider(MultiTenantConnectionProvider multiTenantConnectionProvider) {
+ getProperties().put(AvailableSettings.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProvider);
return this;
}
/**
+ * Overridden to reliably pass a {@link CurrentTenantIdentifierResolver} to the SessionFactory.
+ * @since 4.3.2
+ * @see AvailableSettings#MULTI_TENANT_IDENTIFIER_RESOLVER
+ */
+ @Override
+ public void setCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver currentTenantIdentifierResolver) {
+ getProperties().put(AvailableSettings.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolver);
+ super.setCurrentTenantIdentifierResolver(currentTenantIdentifierResolver);
+ }
+
+ /**
* Specify custom type filters for Spring-based scanning for entity classes.
* <p>Default is to search all specified packages for classes annotated with
* {@code @javax.persistence.Entity}, {@code @javax.persistence.Embeddable}
* or {@code @javax.persistence.MappedSuperclass}.
- * @since 4.2
* @see #scanPackages
*/
public LocalSessionFactoryBuilder setEntityTypeFilters(TypeFilter... entityTypeFilters) {
@@ -266,4 +324,86 @@ public class LocalSessionFactoryBuilder extends Configuration {
return false;
}
+ /**
+ * Build the Hibernate {@code SessionFactory} through background bootstrapping,
+ * using the given executor for a parallel initialization phase
+ * (e.g. a {@link org.springframework.core.task.SimpleAsyncTaskExecutor}).
+ * <p>{@code SessionFactory} initialization will then switch into background
+ * bootstrap mode, with a {@code SessionFactory} proxy immediately returned for
+ * injection purposes instead of waiting for Hibernate's bootstrapping to complete.
+ * However, note that the first actual call to a {@code SessionFactory} method will
+ * then block until Hibernate's bootstrapping completed, if not ready by then.
+ * For maximum benefit, make sure to avoid early {@code SessionFactory} calls
+ * in init methods of related beans, even for metadata introspection purposes.
+ * @since 4.3
+ * @see #buildSessionFactory()
+ */
+ public SessionFactory buildSessionFactory(AsyncTaskExecutor bootstrapExecutor) {
+ Assert.notNull(bootstrapExecutor, "AsyncTaskExecutor must not be null");
+ return (SessionFactory) Proxy.newProxyInstance(this.resourcePatternResolver.getClassLoader(),
+ new Class<?>[] {SessionFactoryImplementor.class, InfrastructureProxy.class},
+ new BootstrapSessionFactoryInvocationHandler(bootstrapExecutor));
+ }
+
+
+ /**
+ * Proxy invocation handler for background bootstrapping, only enforcing
+ * a fully initialized target {@code SessionFactory} when actually needed.
+ * @since 4.3
+ */
+ private class BootstrapSessionFactoryInvocationHandler implements InvocationHandler {
+
+ private final Future<SessionFactory> sessionFactoryFuture;
+
+ public BootstrapSessionFactoryInvocationHandler(AsyncTaskExecutor bootstrapExecutor) {
+ this.sessionFactoryFuture = bootstrapExecutor.submit(new Callable<SessionFactory>() {
+ @Override
+ public SessionFactory call() throws Exception {
+ return buildSessionFactory();
+ }
+ });
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ try {
+ if (method.getName().equals("equals")) {
+ // Only consider equal when proxies are identical.
+ return (proxy == args[0]);
+ }
+ else if (method.getName().equals("hashCode")) {
+ // Use hashCode of EntityManagerFactory proxy.
+ return System.identityHashCode(proxy);
+ }
+ else if (method.getName().equals("getProperties")) {
+ return getProperties();
+ }
+ else if (method.getName().equals("getWrappedObject")) {
+ // Call coming in through InfrastructureProxy interface...
+ return getSessionFactory();
+ }
+ // Regular delegation to the target SessionFactory,
+ // enforcing its full initialization...
+ return method.invoke(getSessionFactory(), args);
+ }
+ catch (InvocationTargetException ex) {
+ throw ex.getTargetException();
+ }
+ }
+
+ private SessionFactory getSessionFactory() {
+ try {
+ return this.sessionFactoryFuture.get();
+ }
+ catch (InterruptedException ex) {
+ throw new IllegalStateException("Interrupted during initialization of Hibernate SessionFactory: " +
+ ex.getMessage());
+ }
+ catch (ExecutionException ex) {
+ throw new IllegalStateException("Failed to asynchronously initialize Hibernate SessionFactory: " +
+ ex.getMessage(), ex.getCause());
+ }
+ }
+ }
+
}
diff --git a/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/SessionFactoryUtils.java b/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/SessionFactoryUtils.java
index 175dd05b..7f3d2e28 100644
--- a/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/SessionFactoryUtils.java
+++ b/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/SessionFactoryUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,11 +16,14 @@
package org.springframework.orm.hibernate5;
+import java.lang.reflect.Method;
+import java.util.Map;
+import javax.persistence.PersistenceException;
import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-
+import org.hibernate.FlushMode;
import org.hibernate.HibernateException;
import org.hibernate.JDBCException;
import org.hibernate.NonUniqueObjectException;
@@ -38,6 +41,7 @@ import org.hibernate.StaleStateException;
import org.hibernate.TransientObjectException;
import org.hibernate.UnresolvableObjectException;
import org.hibernate.WrongClassException;
+import org.hibernate.cfg.Environment;
import org.hibernate.dialect.lock.OptimisticEntityLockException;
import org.hibernate.dialect.lock.PessimisticEntityLockException;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
@@ -59,6 +63,9 @@ import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.dao.PessimisticLockingFailureException;
import org.springframework.jdbc.datasource.DataSourceUtils;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ReflectionUtils;
/**
* Helper class featuring methods for Hibernate Session handling.
@@ -86,28 +93,66 @@ public abstract class SessionFactoryUtils {
static final Log logger = LogFactory.getLog(SessionFactoryUtils.class);
- /**
- * Determine the DataSource of the given SessionFactory.
- * @param sessionFactory the SessionFactory to check
- * @return the DataSource, or {@code null} if none found
- * @see ConnectionProvider
- */
- public static DataSource getDataSource(SessionFactory sessionFactory) {
- if (sessionFactory instanceof SessionFactoryImplementor) {
+ private static Method getFlushMode;
+
+ static {
+ try {
+ // Hibernate 5.2+ getHibernateFlushMode()
+ getFlushMode = Session.class.getMethod("getHibernateFlushMode");
+ }
+ catch (NoSuchMethodException ex) {
try {
- ConnectionProvider cp = ((SessionFactoryImplementor) sessionFactory).getServiceRegistry().getService(
- ConnectionProvider.class);
- if (cp != null) {
- return cp.unwrap(DataSource.class);
- }
+ // Hibernate 5.0/5.1 getFlushMode() with FlushMode return type
+ getFlushMode = Session.class.getMethod("getFlushMode");
}
- catch (UnknownServiceException ex) {
- if (logger.isDebugEnabled()) {
- logger.debug("No ConnectionProvider found - cannot determine DataSource for SessionFactory: " + ex);
- }
+ catch (NoSuchMethodException ex2) {
+ throw new IllegalStateException("No compatible Hibernate getFlushMode signature found", ex2);
}
}
- return null;
+ // Check that it is the Hibernate FlushMode type, not JPA's...
+ Assert.state(FlushMode.class == getFlushMode.getReturnType());
+ }
+
+
+ /**
+ * Get the native Hibernate FlushMode, adapting between Hibernate 5.0/5.1 and 5.2+.
+ * @param session the Hibernate Session to get the flush mode from
+ * @return the FlushMode (never {@code null})
+ * @since 4.3
+ */
+ static FlushMode getFlushMode(Session session) {
+ return (FlushMode) ReflectionUtils.invokeMethod(getFlushMode, session);
+ }
+
+ /**
+ * Trigger a flush on the given Hibernate Session, converting regular
+ * {@link HibernateException} instances as well as Hibernate 5.2's
+ * {@link PersistenceException} wrappers accordingly.
+ * @param session the Hibernate Session to flush
+ * @param synch whether this flush is triggered by transaction synchronization
+ * @throws DataAccessException
+ * @since 4.3.2
+ */
+ static void flush(Session session, boolean synch) throws DataAccessException {
+ if (synch) {
+ logger.debug("Flushing Hibernate Session on transaction synchronization");
+ }
+ else {
+ logger.debug("Flushing Hibernate Session on explicit request");
+ }
+ try {
+ session.flush();
+ }
+ catch (HibernateException ex) {
+ throw convertHibernateAccessException(ex);
+ }
+ catch (PersistenceException ex) {
+ if (ex.getCause() instanceof HibernateException) {
+ throw convertHibernateAccessException((HibernateException) ex.getCause());
+ }
+ throw ex;
+ }
+
}
/**
@@ -131,6 +176,38 @@ public abstract class SessionFactoryUtils {
}
/**
+ * Determine the DataSource of the given SessionFactory.
+ * @param sessionFactory the SessionFactory to check
+ * @return the DataSource, or {@code null} if none found
+ * @see ConnectionProvider
+ */
+ public static DataSource getDataSource(SessionFactory sessionFactory) {
+ Method getProperties = ClassUtils.getMethodIfAvailable(sessionFactory.getClass(), "getProperties");
+ if (getProperties != null) {
+ Map<?, ?> props = (Map<?, ?>) ReflectionUtils.invokeMethod(getProperties, sessionFactory);
+ Object dataSourceValue = props.get(Environment.DATASOURCE);
+ if (dataSourceValue instanceof DataSource) {
+ return (DataSource) dataSourceValue;
+ }
+ }
+ if (sessionFactory instanceof SessionFactoryImplementor) {
+ SessionFactoryImplementor sfi = (SessionFactoryImplementor) sessionFactory;
+ try {
+ ConnectionProvider cp = sfi.getServiceRegistry().getService(ConnectionProvider.class);
+ if (cp != null) {
+ return cp.unwrap(DataSource.class);
+ }
+ }
+ catch (UnknownServiceException ex) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("No ConnectionProvider found - cannot determine DataSource for SessionFactory: " + ex);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
* Convert the given HibernateException to an appropriate exception
* from the {@code org.springframework.dao} hierarchy.
* @param ex HibernateException that occurred
diff --git a/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/SpringFlushSynchronization.java b/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/SpringFlushSynchronization.java
index b421a4f3..1c370a00 100644
--- a/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/SpringFlushSynchronization.java
+++ b/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/SpringFlushSynchronization.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@
package org.springframework.orm.hibernate5;
-import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
@@ -40,13 +39,7 @@ public class SpringFlushSynchronization extends TransactionSynchronizationAdapte
@Override
public void flush() {
- try {
- SessionFactoryUtils.logger.debug("Flushing Hibernate Session on explicit request");
- this.session.flush();
- }
- catch (HibernateException ex) {
- throw SessionFactoryUtils.convertHibernateAccessException(ex);
- }
+ SessionFactoryUtils.flush(this.session, false);
}
diff --git a/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/SpringJtaSessionContext.java b/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/SpringJtaSessionContext.java
index a4eb34ed..eed37f19 100644
--- a/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/SpringJtaSessionContext.java
+++ b/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/SpringJtaSessionContext.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -38,6 +38,7 @@ public class SpringJtaSessionContext extends JTASessionContext {
}
@Override
+ @SuppressWarnings("deprecation")
protected Session buildOrObtainSession() {
Session session = super.buildOrObtainSession();
if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
diff --git a/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/SpringSessionContext.java b/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/SpringSessionContext.java
index 2ad96a79..250fd609 100644
--- a/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/SpringSessionContext.java
+++ b/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/SpringSessionContext.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -76,6 +76,7 @@ public class SpringSessionContext implements CurrentSessionContext {
* Retrieve the Spring-managed Session for the current thread, if any.
*/
@Override
+ @SuppressWarnings("deprecation")
public Session currentSession() throws HibernateException {
Object value = TransactionSynchronizationManager.getResource(this.sessionFactory);
if (value instanceof Session) {
@@ -91,7 +92,7 @@ public class SpringSessionContext implements CurrentSessionContext {
sessionHolder.setSynchronizedWithTransaction(true);
// Switch to FlushMode.AUTO, as we have to assume a thread-bound Session
// with FlushMode.MANUAL, which needs to allow flushing within the transaction.
- FlushMode flushMode = session.getFlushMode();
+ FlushMode flushMode = SessionFactoryUtils.getFlushMode(session);
if (flushMode.equals(FlushMode.MANUAL) &&
!TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
session.setFlushMode(FlushMode.AUTO);
diff --git a/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/SpringSessionSynchronization.java b/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/SpringSessionSynchronization.java
index ca56b71a..b8066720 100644
--- a/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/SpringSessionSynchronization.java
+++ b/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/SpringSessionSynchronization.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@
package org.springframework.orm.hibernate5;
import org.hibernate.FlushMode;
-import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
@@ -83,13 +82,7 @@ public class SpringSessionSynchronization implements TransactionSynchronization,
@Override
public void flush() {
- try {
- SessionFactoryUtils.logger.debug("Flushing Hibernate Session on explicit request");
- getCurrentSession().flush();
- }
- catch (HibernateException ex) {
- throw SessionFactoryUtils.convertHibernateAccessException(ex);
- }
+ SessionFactoryUtils.flush(getCurrentSession(), false);
}
@Override
@@ -98,19 +91,14 @@ public class SpringSessionSynchronization implements TransactionSynchronization,
Session session = getCurrentSession();
// Read-write transaction -> flush the Hibernate Session.
// Further check: only flush when not FlushMode.MANUAL.
- if (!session.getFlushMode().equals(FlushMode.MANUAL)) {
- try {
- SessionFactoryUtils.logger.debug("Flushing Hibernate Session on transaction synchronization");
- session.flush();
- }
- catch (HibernateException ex) {
- throw SessionFactoryUtils.convertHibernateAccessException(ex);
- }
+ if (!FlushMode.MANUAL.equals(SessionFactoryUtils.getFlushMode(session))) {
+ SessionFactoryUtils.flush(getCurrentSession(), true);
}
}
}
@Override
+ @SuppressWarnings("deprecation")
public void beforeCompletion() {
try {
Session session = this.sessionHolder.getSession();
diff --git a/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/support/OpenSessionInViewFilter.java b/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/support/OpenSessionInViewFilter.java
index 9324ea4a..6f067d9c 100644
--- a/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/support/OpenSessionInViewFilter.java
+++ b/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/support/OpenSessionInViewFilter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -199,6 +199,7 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
* @throws DataAccessResourceFailureException if the Session could not be created
* @see FlushMode#MANUAL
*/
+ @SuppressWarnings("deprecation")
protected Session openSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException {
try {
Session session = sessionFactory.openSession();
diff --git a/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/support/OpenSessionInViewInterceptor.java b/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/support/OpenSessionInViewInterceptor.java
index 40b93f9c..5b2ff0bf 100644
--- a/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/support/OpenSessionInViewInterceptor.java
+++ b/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/support/OpenSessionInViewInterceptor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -179,6 +179,7 @@ public class OpenSessionInViewInterceptor implements AsyncWebRequestInterceptor
* @throws DataAccessResourceFailureException if the Session could not be created
* @see FlushMode#MANUAL
*/
+ @SuppressWarnings("deprecation")
protected Session openSession() throws DataAccessResourceFailureException {
try {
Session session = getSessionFactory().openSession();
diff --git a/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/support/OpenSessionInterceptor.java b/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/support/OpenSessionInterceptor.java
index dd1c7c16..1fbc3969 100644
--- a/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/support/OpenSessionInterceptor.java
+++ b/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/support/OpenSessionInterceptor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -102,6 +102,7 @@ public class OpenSessionInterceptor implements MethodInterceptor, InitializingBe
* @throws DataAccessResourceFailureException if the Session could not be created
* @see FlushMode#MANUAL
*/
+ @SuppressWarnings("deprecation")
protected Session openSession() throws DataAccessResourceFailureException {
try {
Session session = getSessionFactory().openSession();
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/AbstractSessionFactoryBean.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/AbstractSessionFactoryBean.java
index 2a53432f..61f73106 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/AbstractSessionFactoryBean.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/AbstractSessionFactoryBean.java
@@ -49,7 +49,9 @@ import org.springframework.beans.factory.InitializingBean;
* @see #setExposeTransactionAwareSessionFactory
* @see org.hibernate.SessionFactory#getCurrentSession()
* @see org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
public abstract class AbstractSessionFactoryBean extends HibernateExceptionTranslator
implements FactoryBean<SessionFactory>, InitializingBean, DisposableBean {
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/FilterDefinitionFactoryBean.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/FilterDefinitionFactoryBean.java
index 41b1f28a..24f5c33a 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/FilterDefinitionFactoryBean.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/FilterDefinitionFactoryBean.java
@@ -61,7 +61,9 @@ import org.springframework.beans.factory.InitializingBean;
* @since 1.2
* @see org.hibernate.engine.FilterDefinition
* @see LocalSessionFactoryBean#setFilterDefinitions
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
public class FilterDefinitionFactoryBean implements FactoryBean<FilterDefinition>, BeanNameAware, InitializingBean {
private final TypeResolver typeResolver = new TypeResolver();
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateAccessor.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateAccessor.java
index a5174439..8b5dc664 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateAccessor.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateAccessor.java
@@ -48,7 +48,9 @@ import org.springframework.jdbc.support.SQLExceptionTranslator;
* @see HibernateTemplate
* @see HibernateInterceptor
* @see #setFlushMode
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
public abstract class HibernateAccessor implements InitializingBean, BeanFactoryAware {
/**
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateCallback.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateCallback.java
index b0545f15..db081638 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateCallback.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateCallback.java
@@ -40,7 +40,9 @@ import org.hibernate.Session;
* @since 1.2
* @see HibernateTemplate
* @see HibernateTransactionManager
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
public interface HibernateCallback<T> {
/**
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateExceptionTranslator.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateExceptionTranslator.java
index ded5f704..db740174 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateExceptionTranslator.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateExceptionTranslator.java
@@ -39,7 +39,9 @@ import org.springframework.jdbc.support.SQLExceptionTranslator;
* @see org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor
* @see SessionFactoryUtils#convertHibernateAccessException(HibernateException)
* @see SQLExceptionTranslator
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
public class HibernateExceptionTranslator implements PersistenceExceptionTranslator {
private SQLExceptionTranslator jdbcExceptionTranslator;
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateJdbcException.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateJdbcException.java
index e43e55f4..24af6970 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateJdbcException.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateJdbcException.java
@@ -29,7 +29,9 @@ import org.springframework.dao.UncategorizedDataAccessException;
* @author Juergen Hoeller
* @since 1.2
* @see SessionFactoryUtils#convertHibernateAccessException
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
@SuppressWarnings("serial")
public class HibernateJdbcException extends UncategorizedDataAccessException {
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateObjectRetrievalFailureException.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateObjectRetrievalFailureException.java
index 37d5fde4..9a4cdf9a 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateObjectRetrievalFailureException.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateObjectRetrievalFailureException.java
@@ -28,7 +28,9 @@ import org.springframework.orm.ObjectRetrievalFailureException;
* @author Juergen Hoeller
* @since 1.2
* @see SessionFactoryUtils#convertHibernateAccessException
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
@SuppressWarnings("serial")
public class HibernateObjectRetrievalFailureException extends ObjectRetrievalFailureException {
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateOperations.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateOperations.java
index 449e5ad4..6facdbec 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateOperations.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateOperations.java
@@ -62,7 +62,9 @@ import org.springframework.dao.DataAccessException;
* @see org.springframework.transaction.jta.JtaTransactionManager
* @see org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
* @see org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
public interface HibernateOperations {
/**
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateOptimisticLockingFailureException.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateOptimisticLockingFailureException.java
index 43c73be2..47ff23a2 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateOptimisticLockingFailureException.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateOptimisticLockingFailureException.java
@@ -30,7 +30,9 @@ import org.springframework.orm.ObjectOptimisticLockingFailureException;
* @author Juergen Hoeller
* @since 1.2
* @see SessionFactoryUtils#convertHibernateAccessException
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
@SuppressWarnings("serial")
public class HibernateOptimisticLockingFailureException extends ObjectOptimisticLockingFailureException {
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateQueryException.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateQueryException.java
index 0dc209bf..81b82fff 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateQueryException.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateQueryException.java
@@ -27,7 +27,9 @@ import org.springframework.dao.InvalidDataAccessResourceUsageException;
* @author Juergen Hoeller
* @since 1.2
* @see SessionFactoryUtils#convertHibernateAccessException
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
@SuppressWarnings("serial")
public class HibernateQueryException extends InvalidDataAccessResourceUsageException {
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateSystemException.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateSystemException.java
index 040eaee1..ddc5a2e1 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateSystemException.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateSystemException.java
@@ -28,7 +28,9 @@ import org.springframework.dao.UncategorizedDataAccessException;
* @author Juergen Hoeller
* @since 1.2
* @see SessionFactoryUtils#convertHibernateAccessException
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
@SuppressWarnings("serial")
public class HibernateSystemException extends UncategorizedDataAccessException {
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateTemplate.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateTemplate.java
index ab8eaae3..1c1d0ad5 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateTemplate.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateTemplate.java
@@ -104,8 +104,9 @@ import org.springframework.util.Assert;
* @see org.springframework.transaction.jta.JtaTransactionManager
* @see org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
* @see org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
-@SuppressWarnings("deprecation")
+@Deprecated
public class HibernateTemplate extends HibernateAccessor implements HibernateOperations {
private boolean allowCreate = true;
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateTransactionManager.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateTransactionManager.java
index 229e1bfb..c6f802e6 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateTransactionManager.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateTransactionManager.java
@@ -128,7 +128,9 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
* @see org.springframework.jdbc.core.JdbcTemplate
* @see org.springframework.jdbc.datasource.DataSourceTransactionManager
* @see org.springframework.transaction.jta.JtaTransactionManager
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
@SuppressWarnings("serial")
public class HibernateTransactionManager extends AbstractPlatformTransactionManager
implements ResourceTransactionManager, BeanFactoryAware, InitializingBean {
@@ -475,7 +477,6 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
}
@Override
- @SuppressWarnings("deprecation")
protected void doBegin(Object transaction, TransactionDefinition definition) {
HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
@@ -708,7 +709,6 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
}
@Override
- @SuppressWarnings("deprecation")
protected void doCleanupAfterCompletion(Object transaction) {
HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/LocalDataSourceConnectionProvider.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/LocalDataSourceConnectionProvider.java
index 17c2312b..411d3857 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/LocalDataSourceConnectionProvider.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/LocalDataSourceConnectionProvider.java
@@ -36,7 +36,9 @@ import org.springframework.jdbc.datasource.DataSourceUtils;
* @author Juergen Hoeller
* @since 1.2
* @see LocalSessionFactoryBean#setDataSource
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
public class LocalDataSourceConnectionProvider implements ConnectionProvider {
private DataSource dataSource;
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/LocalJtaDataSourceConnectionProvider.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/LocalJtaDataSourceConnectionProvider.java
index 17764a5f..817be9d7 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/LocalJtaDataSourceConnectionProvider.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/LocalJtaDataSourceConnectionProvider.java
@@ -23,7 +23,9 @@ package org.springframework.orm.hibernate3;
*
* @author Juergen Hoeller
* @since 2.5.1
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
public class LocalJtaDataSourceConnectionProvider extends LocalDataSourceConnectionProvider {
/**
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/LocalRegionFactoryProxy.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/LocalRegionFactoryProxy.java
index 8b68dcb5..73e0b5bf 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/LocalRegionFactoryProxy.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/LocalRegionFactoryProxy.java
@@ -36,7 +36,9 @@ import org.hibernate.cfg.Settings;
* @author Juergen Hoeller
* @since 3.0
* @see LocalSessionFactoryBean#setCacheRegionFactory
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
public class LocalRegionFactoryProxy implements RegionFactory {
private final RegionFactory regionFactory;
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/LocalSessionFactoryBean.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/LocalSessionFactoryBean.java
index 7f9f72e2..81b3b246 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/LocalSessionFactoryBean.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/LocalSessionFactoryBean.java
@@ -102,7 +102,9 @@ import org.springframework.util.StringUtils;
* @see #setJtaTransactionManager
* @see org.hibernate.SessionFactory#getCurrentSession()
* @see HibernateTransactionManager
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
public class LocalSessionFactoryBean extends AbstractSessionFactoryBean implements BeanClassLoaderAware {
private static final ThreadLocal<DataSource> configTimeDataSourceHolder =
@@ -519,7 +521,7 @@ public class LocalSessionFactoryBean extends AbstractSessionFactoryBean implemen
}
if (this.lobHandler != null) {
// Make given LobHandler available for SessionFactory configuration.
- // Do early because because mapping resource might refer to custom types.
+ // Do early because mapping resource might refer to custom types.
configTimeLobHandlerHolder.set(this.lobHandler);
}
@@ -752,7 +754,7 @@ public class LocalSessionFactoryBean extends AbstractSessionFactoryBean implemen
}
/**
- * To be implemented by subclasses that want to to register further mappings
+ * To be implemented by subclasses that want to register further mappings
* on the Configuration object after this FactoryBean registered its specified
* mappings.
* <p>Invoked <i>before</i> the {@code Configuration.buildMappings()} call,
@@ -765,7 +767,7 @@ public class LocalSessionFactoryBean extends AbstractSessionFactoryBean implemen
}
/**
- * To be implemented by subclasses that want to to perform custom
+ * To be implemented by subclasses that want to perform custom
* post-processing of the Configuration object after this FactoryBean
* performed its default initialization.
* <p>Invoked <i>after</i> the {@code Configuration.buildMappings()} call,
@@ -871,7 +873,6 @@ public class LocalSessionFactoryBean extends AbstractSessionFactoryBean implemen
new HibernateCallback<Object>() {
@Override
public Object doInHibernate(Session session) throws HibernateException, SQLException {
- @SuppressWarnings("deprecation")
Connection con = session.connection();
DatabaseMetadata metadata = new DatabaseMetadata(con, dialect);
String[] sql = getConfiguration().generateSchemaUpdateScript(dialect, metadata);
@@ -917,7 +918,6 @@ public class LocalSessionFactoryBean extends AbstractSessionFactoryBean implemen
new HibernateCallback<Object>() {
@Override
public Object doInHibernate(Session session) throws HibernateException, SQLException {
- @SuppressWarnings("deprecation")
Connection con = session.connection();
DatabaseMetadata metadata = new DatabaseMetadata(con, dialect, false);
getConfiguration().validateSchema(dialect, metadata);
@@ -955,7 +955,6 @@ public class LocalSessionFactoryBean extends AbstractSessionFactoryBean implemen
new HibernateCallback<Object>() {
@Override
public Object doInHibernate(Session session) throws HibernateException, SQLException {
- @SuppressWarnings("deprecation")
Connection con = session.connection();
String[] sql = getConfiguration().generateDropSchemaScript(dialect);
executeSchemaScript(con, sql);
@@ -993,7 +992,6 @@ public class LocalSessionFactoryBean extends AbstractSessionFactoryBean implemen
new HibernateCallback<Object>() {
@Override
public Object doInHibernate(Session session) throws HibernateException, SQLException {
- @SuppressWarnings("deprecation")
Connection con = session.connection();
String[] sql = getConfiguration().generateSchemaCreationScript(dialect);
executeSchemaScript(con, sql);
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/LocalTransactionManagerLookup.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/LocalTransactionManagerLookup.java
index 9314a90b..fbfcf3c5 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/LocalTransactionManagerLookup.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/LocalTransactionManagerLookup.java
@@ -41,7 +41,9 @@ import org.hibernate.transaction.TransactionManagerLookup;
* @since 1.2
* @see LocalSessionFactoryBean#setJtaTransactionManager
* @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
public class LocalTransactionManagerLookup implements TransactionManagerLookup {
private final TransactionManager transactionManager;
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/SessionFactoryUtils.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/SessionFactoryUtils.java
index be43c7bc..2286a904 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/SessionFactoryUtils.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/SessionFactoryUtils.java
@@ -100,7 +100,9 @@ import org.springframework.util.Assert;
* @see HibernateTransactionManager
* @see org.springframework.transaction.jta.JtaTransactionManager
* @see org.springframework.transaction.support.TransactionSynchronizationManager
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
public abstract class SessionFactoryUtils {
/**
@@ -511,7 +513,6 @@ public abstract class SessionFactoryUtils {
* @param entityInterceptor Hibernate entity interceptor, or {@code null} if none
* @return the new Session
*/
- @SuppressWarnings("deprecation")
public static Session getNewSession(SessionFactory sessionFactory, Interceptor entityInterceptor) {
Assert.notNull(sessionFactory, "No SessionFactory specified");
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/SessionHolder.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/SessionHolder.java
index a3622f03..abfb36c5 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/SessionHolder.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/SessionHolder.java
@@ -37,7 +37,9 @@ import org.springframework.util.Assert;
* @since 1.2
* @see HibernateTransactionManager
* @see SessionFactoryUtils
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
public class SessionHolder extends ResourceHolderSupport {
private static final Object DEFAULT_KEY = new Object();
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/SpringSessionContext.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/SpringSessionContext.java
index 0e33b324..f0c1199e 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/SpringSessionContext.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/SpringSessionContext.java
@@ -37,7 +37,9 @@ import org.hibernate.engine.SessionFactoryImplementor;
* @since 2.0
* @see SessionFactoryUtils#doGetSession
* @see LocalSessionFactoryBean#setExposeTransactionAwareSessionFactory
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
@SuppressWarnings("serial")
public class SpringSessionContext implements CurrentSessionContext {
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/SpringSessionSynchronization.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/SpringSessionSynchronization.java
index 28acc3b6..70f8beff 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/SpringSessionSynchronization.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/SpringSessionSynchronization.java
@@ -42,7 +42,9 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
* @since 1.2
* @see SessionFactoryUtils
* @see org.springframework.transaction.jta.JtaTransactionManager
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
class SpringSessionSynchronization implements TransactionSynchronization, Ordered {
private final SessionHolder sessionHolder;
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/SpringTransactionFactory.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/SpringTransactionFactory.java
index c0258c7a..a381d6de 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/SpringTransactionFactory.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/SpringTransactionFactory.java
@@ -36,7 +36,9 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
* @since 2.5.4
* @see org.springframework.transaction.support.TransactionSynchronizationManager
* @see org.hibernate.transaction.JDBCTransactionFactory
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
public class SpringTransactionFactory implements TransactionFactory {
/**
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/TransactionAwareDataSourceConnectionProvider.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/TransactionAwareDataSourceConnectionProvider.java
index 368dcf54..c91a03ea 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/TransactionAwareDataSourceConnectionProvider.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/TransactionAwareDataSourceConnectionProvider.java
@@ -28,7 +28,9 @@ import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
* @author Juergen Hoeller
* @since 1.2
* @see LocalSessionFactoryBean#setUseTransactionAwareDataSource
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
public class TransactionAwareDataSourceConnectionProvider extends LocalDataSourceConnectionProvider {
/**
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/TypeDefinitionBean.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/TypeDefinitionBean.java
index 9b448402..ceae3a5d 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/TypeDefinitionBean.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/TypeDefinitionBean.java
@@ -48,7 +48,9 @@ import org.springframework.beans.factory.InitializingBean;
* @author Juergen Hoeller
* @since 1.2
* @see LocalSessionFactoryBean#setTypeDefinitions(TypeDefinitionBean[])
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
public class TypeDefinitionBean implements BeanNameAware, InitializingBean {
private String typeName;
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/annotation/AnnotationSessionFactoryBean.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/annotation/AnnotationSessionFactoryBean.java
index 8e90fdf1..bc6f0440 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/annotation/AnnotationSessionFactoryBean.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/annotation/AnnotationSessionFactoryBean.java
@@ -38,7 +38,6 @@ import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
-import org.springframework.orm.hibernate3.LocalSessionFactoryBean;
import org.springframework.util.ClassUtils;
/**
@@ -74,8 +73,11 @@ import org.springframework.util.ClassUtils;
* @see #setHibernateProperties
* @see #setAnnotatedClasses
* @see #setAnnotatedPackages
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
-public class AnnotationSessionFactoryBean extends LocalSessionFactoryBean implements ResourceLoaderAware {
+@Deprecated
+public class AnnotationSessionFactoryBean extends org.springframework.orm.hibernate3.LocalSessionFactoryBean
+ implements ResourceLoaderAware {
private static final String RESOURCE_PATTERN = "/**/*.class";
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/AbstractLobType.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/AbstractLobType.java
index 39d68e1b..96cb0170 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/AbstractLobType.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/AbstractLobType.java
@@ -32,7 +32,6 @@ import org.hibernate.util.EqualsHelper;
import org.springframework.jdbc.support.lob.LobCreator;
import org.springframework.jdbc.support.lob.LobCreatorUtils;
import org.springframework.jdbc.support.lob.LobHandler;
-import org.springframework.orm.hibernate3.LocalSessionFactoryBean;
/**
* Abstract base class for Hibernate UserType implementations that map to LOBs.
@@ -52,7 +51,9 @@ import org.springframework.orm.hibernate3.LocalSessionFactoryBean;
* @see org.springframework.jdbc.support.lob.LobCreator
* @see org.springframework.orm.hibernate3.LocalSessionFactoryBean#setLobHandler
* @see org.springframework.orm.hibernate3.LocalSessionFactoryBean#setJtaTransactionManager
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
public abstract class AbstractLobType implements UserType {
protected final Log logger = LogFactory.getLog(getClass());
@@ -69,8 +70,8 @@ public abstract class AbstractLobType implements UserType {
* @see org.springframework.orm.hibernate3.LocalSessionFactoryBean#getConfigTimeTransactionManager
*/
protected AbstractLobType() {
- this(LocalSessionFactoryBean.getConfigTimeLobHandler(),
- LocalSessionFactoryBean.getConfigTimeTransactionManager());
+ this(org.springframework.orm.hibernate3.LocalSessionFactoryBean.getConfigTimeLobHandler(),
+ org.springframework.orm.hibernate3.LocalSessionFactoryBean.getConfigTimeTransactionManager());
}
/**
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/AsyncRequestInterceptor.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/AsyncRequestInterceptor.java
index c58f0e01..844e2f3f 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/AsyncRequestInterceptor.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/AsyncRequestInterceptor.java
@@ -22,8 +22,6 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.SessionFactory;
-import org.springframework.orm.hibernate3.SessionFactoryUtils;
-import org.springframework.orm.hibernate3.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter;
@@ -40,19 +38,21 @@ import org.springframework.web.context.request.async.DeferredResultProcessingInt
*
* @author Rossen Stoyanchev
* @since 3.2.5
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
class AsyncRequestInterceptor extends CallableProcessingInterceptorAdapter implements DeferredResultProcessingInterceptor {
private static final Log logger = LogFactory.getLog(AsyncRequestInterceptor.class);
private final SessionFactory sessionFactory;
- private final SessionHolder sessionHolder;
+ private final org.springframework.orm.hibernate3.SessionHolder sessionHolder;
private volatile boolean timeoutInProgress;
- public AsyncRequestInterceptor(SessionFactory sessionFactory, SessionHolder sessionHolder) {
+ public AsyncRequestInterceptor(SessionFactory sessionFactory, org.springframework.orm.hibernate3.SessionHolder sessionHolder) {
this.sessionFactory = sessionFactory;
this.sessionHolder = sessionHolder;
}
@@ -87,7 +87,7 @@ class AsyncRequestInterceptor extends CallableProcessingInterceptorAdapter imple
private void closeAfterTimeout() {
if (this.timeoutInProgress) {
logger.debug("Closing Hibernate Session after async request timeout");
- SessionFactoryUtils.closeSession(this.sessionHolder.getSession());
+ org.springframework.orm.hibernate3.SessionFactoryUtils.closeSession(this.sessionHolder.getSession());
}
}
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/BlobByteArrayType.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/BlobByteArrayType.java
index 4ab0264b..f1dd0882 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/BlobByteArrayType.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/BlobByteArrayType.java
@@ -38,7 +38,9 @@ import org.springframework.jdbc.support.lob.LobHandler;
* @author Juergen Hoeller
* @since 1.2
* @see org.springframework.orm.hibernate3.LocalSessionFactoryBean#setLobHandler
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
public class BlobByteArrayType extends AbstractLobType {
/**
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/BlobSerializableType.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/BlobSerializableType.java
index 1ba48853..42dec75d 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/BlobSerializableType.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/BlobSerializableType.java
@@ -46,7 +46,9 @@ import org.springframework.jdbc.support.lob.LobHandler;
* @author Juergen Hoeller
* @since 1.2
* @see org.springframework.orm.hibernate3.LocalSessionFactoryBean#setLobHandler
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
public class BlobSerializableType extends AbstractLobType {
/**
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/BlobStringType.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/BlobStringType.java
index 40fd0cd4..0d6e399c 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/BlobStringType.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/BlobStringType.java
@@ -44,7 +44,9 @@ import org.springframework.jdbc.support.lob.LobHandler;
* @since 1.2.7
* @see #getCharacterEncoding()
* @see org.springframework.orm.hibernate3.LocalSessionFactoryBean#setLobHandler
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
public class BlobStringType extends AbstractLobType {
/**
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/ClobStringType.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/ClobStringType.java
index 0e293ef1..6fe9786c 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/ClobStringType.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/ClobStringType.java
@@ -40,7 +40,9 @@ import org.springframework.jdbc.support.lob.LobHandler;
* @author Juergen Hoeller
* @since 1.2
* @see org.springframework.orm.hibernate3.LocalSessionFactoryBean#setLobHandler
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
public class ClobStringType extends AbstractLobType {
/**
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/HibernateDaoSupport.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/HibernateDaoSupport.java
index 3b7fc146..32aa867f 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/HibernateDaoSupport.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/HibernateDaoSupport.java
@@ -23,8 +23,6 @@ import org.hibernate.SessionFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.dao.support.DaoSupport;
-import org.springframework.orm.hibernate3.HibernateTemplate;
-import org.springframework.orm.hibernate3.SessionFactoryUtils;
/**
* Convenient super class for Hibernate-based data access objects.
@@ -56,10 +54,12 @@ import org.springframework.orm.hibernate3.SessionFactoryUtils;
* @see #setSessionFactory
* @see #getHibernateTemplate
* @see org.springframework.orm.hibernate3.HibernateTemplate
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
public abstract class HibernateDaoSupport extends DaoSupport {
- private HibernateTemplate hibernateTemplate;
+ private org.springframework.orm.hibernate3.HibernateTemplate hibernateTemplate;
/**
@@ -83,8 +83,8 @@ public abstract class HibernateDaoSupport extends DaoSupport {
* @return the new HibernateTemplate instance
* @see #setSessionFactory
*/
- protected HibernateTemplate createHibernateTemplate(SessionFactory sessionFactory) {
- return new HibernateTemplate(sessionFactory);
+ protected org.springframework.orm.hibernate3.HibernateTemplate createHibernateTemplate(SessionFactory sessionFactory) {
+ return new org.springframework.orm.hibernate3.HibernateTemplate(sessionFactory);
}
/**
@@ -99,7 +99,7 @@ public abstract class HibernateDaoSupport extends DaoSupport {
* as an alternative to specifying a SessionFactory.
* @see #setSessionFactory
*/
- public final void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
+ public final void setHibernateTemplate(org.springframework.orm.hibernate3.HibernateTemplate hibernateTemplate) {
this.hibernateTemplate = hibernateTemplate;
}
@@ -113,7 +113,7 @@ public abstract class HibernateDaoSupport extends DaoSupport {
* {@code new HibernateTemplate(getSessionFactory())}, in which case
* you're allowed to customize the settings on the resulting instance.
*/
- public final HibernateTemplate getHibernateTemplate() {
+ public final org.springframework.orm.hibernate3.HibernateTemplate getHibernateTemplate() {
return this.hibernateTemplate;
}
@@ -141,7 +141,7 @@ public abstract class HibernateDaoSupport extends DaoSupport {
* @throws DataAccessResourceFailureException if the Session couldn't be created
* @throws IllegalStateException if no thread-bound Session found and allowCreate=false
* @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession(SessionFactory, boolean)
- * @deprecated as of Spring 3.2.7, in favor of {@link HibernateTemplate} usage
+ * @deprecated as of Spring 3.2.7, in favor of {@link org.springframework.orm.hibernate3.HibernateTemplate} usage
*/
@Deprecated
protected final Session getSession() throws DataAccessResourceFailureException, IllegalStateException {
@@ -166,15 +166,15 @@ public abstract class HibernateDaoSupport extends DaoSupport {
* @throws DataAccessResourceFailureException if the Session couldn't be created
* @throws IllegalStateException if no thread-bound Session found and allowCreate=false
* @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession(SessionFactory, boolean)
- * @deprecated as of Spring 3.2.7, in favor of {@link HibernateTemplate} usage
+ * @deprecated as of Spring 3.2.7, in favor of {@link org.springframework.orm.hibernate3.HibernateTemplate} usage
*/
@Deprecated
protected final Session getSession(boolean allowCreate)
throws DataAccessResourceFailureException, IllegalStateException {
return (!allowCreate ?
- SessionFactoryUtils.getSession(getSessionFactory(), false) :
- SessionFactoryUtils.getSession(
+ org.springframework.orm.hibernate3.SessionFactoryUtils.getSession(getSessionFactory(), false) :
+ org.springframework.orm.hibernate3.SessionFactoryUtils.getSession(
getSessionFactory(),
this.hibernateTemplate.getEntityInterceptor(),
this.hibernateTemplate.getJdbcExceptionTranslator()));
@@ -192,7 +192,7 @@ public abstract class HibernateDaoSupport extends DaoSupport {
* @param ex HibernateException that occurred
* @return the corresponding DataAccessException instance
* @see org.springframework.orm.hibernate3.SessionFactoryUtils#convertHibernateAccessException
- * @deprecated as of Spring 3.2.7, in favor of {@link HibernateTemplate} usage
+ * @deprecated as of Spring 3.2.7, in favor of {@link org.springframework.orm.hibernate3.HibernateTemplate} usage
*/
@Deprecated
protected final DataAccessException convertHibernateAccessException(HibernateException ex) {
@@ -206,11 +206,11 @@ public abstract class HibernateDaoSupport extends DaoSupport {
* {@link #getSession} and {@link #convertHibernateAccessException}.
* @param session the Session to close
* @see org.springframework.orm.hibernate3.SessionFactoryUtils#releaseSession
- * @deprecated as of Spring 3.2.7, in favor of {@link HibernateTemplate} usage
+ * @deprecated as of Spring 3.2.7, in favor of {@link org.springframework.orm.hibernate3.HibernateTemplate} usage
*/
@Deprecated
protected final void releaseSession(Session session) {
- SessionFactoryUtils.releaseSession(session, getSessionFactory());
+ org.springframework.orm.hibernate3.SessionFactoryUtils.releaseSession(session, getSessionFactory());
}
}
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/IdTransferringMergeEventListener.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/IdTransferringMergeEventListener.java
index c87e46c5..646c973a 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/IdTransferringMergeEventListener.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/IdTransferringMergeEventListener.java
@@ -48,8 +48,10 @@ import org.hibernate.persister.entity.EntityPersister;
* @author Juergen Hoeller
* @since 1.2
* @see org.springframework.orm.hibernate3.LocalSessionFactoryBean#setEventListeners(java.util.Map)
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
-@SuppressWarnings({"serial", "rawtypes", "deprecation"})
+@Deprecated
+@SuppressWarnings({"serial", "rawtypes"})
public class IdTransferringMergeEventListener extends DefaultMergeEventListener {
/**
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewFilter.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewFilter.java
index 72e15aaa..1ece0146 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewFilter.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewFilter.java
@@ -27,8 +27,6 @@ import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.dao.DataAccessResourceFailureException;
-import org.springframework.orm.hibernate3.SessionFactoryUtils;
-import org.springframework.orm.hibernate3.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
import org.springframework.web.context.WebApplicationContext;
@@ -91,7 +89,9 @@ import org.springframework.web.filter.OncePerRequestFilter;
* @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession
* @see org.springframework.transaction.support.TransactionSynchronizationManager
* @see org.hibernate.SessionFactory#getCurrentSession()
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
public class OpenSessionInViewFilter extends OncePerRequestFilter {
public static final String DEFAULT_SESSION_FACTORY_BEAN_NAME = "sessionFactory";
@@ -127,8 +127,8 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
* its own session (like without Open Session in View). Each of those
* sessions will be registered for deferred close, though, actually
* processed at request completion.
- * @see SessionFactoryUtils#initDeferredClose
- * @see SessionFactoryUtils#processDeferredClose
+ * @see org.springframework.orm.hibernate3.SessionFactoryUtils#initDeferredClose
+ * @see org.springframework.orm.hibernate3.SessionFactoryUtils#processDeferredClose
*/
public void setSingleSession(boolean singleSession) {
this.singleSession = singleSession;
@@ -206,7 +206,7 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
if (isFirstRequest || !applySessionBindingInterceptor(asyncManager, key)) {
logger.debug("Opening single Hibernate Session in OpenSessionInViewFilter");
Session session = getSession(sessionFactory);
- SessionHolder sessionHolder = new SessionHolder(session);
+ org.springframework.orm.hibernate3.SessionHolder sessionHolder = new org.springframework.orm.hibernate3.SessionHolder(session);
TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
AsyncRequestInterceptor interceptor = new AsyncRequestInterceptor(sessionFactory, sessionHolder);
@@ -218,12 +218,12 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
else {
// deferred close mode
Assert.state(!isAsyncStarted(request), "Deferred close mode is not supported on async dispatches");
- if (SessionFactoryUtils.isDeferredCloseActive(sessionFactory)) {
+ if (org.springframework.orm.hibernate3.SessionFactoryUtils.isDeferredCloseActive(sessionFactory)) {
// Do not modify deferred close: just set the participate flag.
participate = true;
}
else {
- SessionFactoryUtils.initDeferredClose(sessionFactory);
+ org.springframework.orm.hibernate3.SessionFactoryUtils.initDeferredClose(sessionFactory);
}
}
@@ -234,8 +234,8 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
if (!participate) {
if (isSingleSession()) {
// single session mode
- SessionHolder sessionHolder =
- (SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
+ org.springframework.orm.hibernate3.SessionHolder sessionHolder =
+ (org.springframework.orm.hibernate3.SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
if (!isAsyncStarted(request)) {
logger.debug("Closing single Hibernate Session in OpenSessionInViewFilter");
closeSession(sessionHolder.getSession(), sessionFactory);
@@ -243,7 +243,7 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
}
else {
// deferred close mode
- SessionFactoryUtils.processDeferredClose(sessionFactory);
+ org.springframework.orm.hibernate3.SessionFactoryUtils.processDeferredClose(sessionFactory);
}
}
}
@@ -291,7 +291,7 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
* @see org.hibernate.FlushMode#MANUAL
*/
protected Session getSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException {
- Session session = SessionFactoryUtils.getSession(sessionFactory, true);
+ Session session = org.springframework.orm.hibernate3.SessionFactoryUtils.getSession(sessionFactory, true);
FlushMode flushMode = getFlushMode();
if (flushMode != null) {
session.setFlushMode(flushMode);
@@ -310,7 +310,7 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
* @param sessionFactory the SessionFactory that this filter uses
*/
protected void closeSession(Session session, SessionFactory sessionFactory) {
- SessionFactoryUtils.closeSession(session);
+ org.springframework.orm.hibernate3.SessionFactoryUtils.closeSession(session);
}
private boolean applySessionBindingInterceptor(WebAsyncManager asyncManager, String key) {
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewInterceptor.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewInterceptor.java
index b868d797..10598cd8 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewInterceptor.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewInterceptor.java
@@ -20,9 +20,6 @@ import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.springframework.dao.DataAccessException;
-import org.springframework.orm.hibernate3.HibernateAccessor;
-import org.springframework.orm.hibernate3.SessionFactoryUtils;
-import org.springframework.orm.hibernate3.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.ui.ModelMap;
import org.springframework.web.context.request.AsyncWebRequestInterceptor;
@@ -91,8 +88,10 @@ import org.springframework.web.context.request.async.WebAsyncUtils;
* @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession
* @see org.springframework.transaction.support.TransactionSynchronizationManager
* @see org.hibernate.SessionFactory#getCurrentSession()
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
-public class OpenSessionInViewInterceptor extends HibernateAccessor implements AsyncWebRequestInterceptor {
+@Deprecated
+public class OpenSessionInViewInterceptor extends org.springframework.orm.hibernate3.HibernateAccessor implements AsyncWebRequestInterceptor {
/**
* Suffix that gets appended to the {@code SessionFactory}
@@ -121,8 +120,8 @@ public class OpenSessionInViewInterceptor extends HibernateAccessor implements A
* its own session (like without Open Session in View). Each of those
* sessions will be registered for deferred close, though, actually
* processed at request completion.
- * @see SessionFactoryUtils#initDeferredClose
- * @see SessionFactoryUtils#processDeferredClose
+ * @see org.springframework.orm.hibernate3.SessionFactoryUtils#initDeferredClose
+ * @see org.springframework.orm.hibernate3.SessionFactoryUtils#processDeferredClose
*/
public void setSingleSession(boolean singleSession) {
this.singleSession = singleSession;
@@ -154,7 +153,7 @@ public class OpenSessionInViewInterceptor extends HibernateAccessor implements A
}
if ((isSingleSession() && TransactionSynchronizationManager.hasResource(getSessionFactory())) ||
- SessionFactoryUtils.isDeferredCloseActive(getSessionFactory())) {
+ org.springframework.orm.hibernate3.SessionFactoryUtils.isDeferredCloseActive(getSessionFactory())) {
// Do not modify the Session: just mark the request accordingly.
Integer count = (Integer) request.getAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST);
int newCount = (count != null ? count + 1 : 1);
@@ -164,10 +163,10 @@ public class OpenSessionInViewInterceptor extends HibernateAccessor implements A
if (isSingleSession()) {
// single session mode
logger.debug("Opening single Hibernate Session in OpenSessionInViewInterceptor");
- Session session = SessionFactoryUtils.getSession(
+ Session session = org.springframework.orm.hibernate3.SessionFactoryUtils.getSession(
getSessionFactory(), getEntityInterceptor(), getJdbcExceptionTranslator());
applyFlushMode(session, false);
- SessionHolder sessionHolder = new SessionHolder(session);
+ org.springframework.orm.hibernate3.SessionHolder sessionHolder = new org.springframework.orm.hibernate3.SessionHolder(session);
TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder);
AsyncRequestInterceptor asyncRequestInterceptor =
@@ -177,7 +176,7 @@ public class OpenSessionInViewInterceptor extends HibernateAccessor implements A
}
else {
// deferred close mode
- SessionFactoryUtils.initDeferredClose(getSessionFactory());
+ org.springframework.orm.hibernate3.SessionFactoryUtils.initDeferredClose(getSessionFactory());
}
}
}
@@ -193,8 +192,8 @@ public class OpenSessionInViewInterceptor extends HibernateAccessor implements A
public void postHandle(WebRequest request, ModelMap model) throws DataAccessException {
if (isSingleSession()) {
// Only potentially flush in single session mode.
- SessionHolder sessionHolder =
- (SessionHolder) TransactionSynchronizationManager.getResource(getSessionFactory());
+ org.springframework.orm.hibernate3.SessionHolder sessionHolder =
+ (org.springframework.orm.hibernate3.SessionHolder) TransactionSynchronizationManager.getResource(getSessionFactory());
logger.debug("Flushing single Hibernate Session in OpenSessionInViewInterceptor");
try {
flushIfNecessary(sessionHolder.getSession(), false);
@@ -216,14 +215,14 @@ public class OpenSessionInViewInterceptor extends HibernateAccessor implements A
if (!decrementParticipateCount(request)) {
if (isSingleSession()) {
// single session mode
- SessionHolder sessionHolder =
- (SessionHolder) TransactionSynchronizationManager.unbindResource(getSessionFactory());
+ org.springframework.orm.hibernate3.SessionHolder sessionHolder =
+ (org.springframework.orm.hibernate3.SessionHolder) TransactionSynchronizationManager.unbindResource(getSessionFactory());
logger.debug("Closing single Hibernate Session in OpenSessionInViewInterceptor");
- SessionFactoryUtils.closeSession(sessionHolder.getSession());
+ org.springframework.orm.hibernate3.SessionFactoryUtils.closeSession(sessionHolder.getSession());
}
else {
// deferred close mode
- SessionFactoryUtils.processDeferredClose(getSessionFactory());
+ org.springframework.orm.hibernate3.SessionFactoryUtils.processDeferredClose(getSessionFactory());
}
}
}
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInterceptor.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInterceptor.java
index 3429d71a..1a61ffd5 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInterceptor.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInterceptor.java
@@ -25,8 +25,6 @@ import org.hibernate.SessionFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.DataAccessResourceFailureException;
-import org.springframework.orm.hibernate3.SessionFactoryUtils;
-import org.springframework.orm.hibernate3.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;
/**
@@ -49,7 +47,9 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
* @see org.springframework.orm.hibernate3.HibernateTransactionManager
* @see org.springframework.transaction.support.TransactionSynchronizationManager
* @see org.hibernate.SessionFactory#getCurrentSession()
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
public class OpenSessionInterceptor implements MethodInterceptor, InitializingBean {
private SessionFactory sessionFactory;
@@ -84,11 +84,11 @@ public class OpenSessionInterceptor implements MethodInterceptor, InitializingBe
// New Session to be bound for the current method's scope...
Session session = openSession();
try {
- TransactionSynchronizationManager.bindResource(sf, new SessionHolder(session));
+ TransactionSynchronizationManager.bindResource(sf, new org.springframework.orm.hibernate3.SessionHolder(session));
return invocation.proceed();
}
finally {
- SessionFactoryUtils.closeSession(session);
+ org.springframework.orm.hibernate3.SessionFactoryUtils.closeSession(session);
TransactionSynchronizationManager.unbindResource(sf);
}
}
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/ScopedBeanInterceptor.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/ScopedBeanInterceptor.java
index a189463f..6dec1f93 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/ScopedBeanInterceptor.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/ScopedBeanInterceptor.java
@@ -41,7 +41,9 @@ import org.springframework.aop.support.AopUtils;
* @author Costin Leau
* @author Juergen Hoeller
* @since 2.0
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
@SuppressWarnings("serial")
public class ScopedBeanInterceptor extends EmptyInterceptor {
diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java b/spring-orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java
index 4043bef0..f4401ae2 100644
--- a/spring-orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java
+++ b/spring-orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java
@@ -30,6 +30,9 @@ import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceException;
@@ -49,6 +52,7 @@ import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
+import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.util.Assert;
@@ -103,6 +107,8 @@ public abstract class AbstractEntityManagerFactoryBean implements
private JpaVendorAdapter jpaVendorAdapter;
+ private AsyncTaskExecutor bootstrapExecutor;
+
private ClassLoader beanClassLoader = getClass().getClassLoader();
private BeanFactory beanFactory;
@@ -110,8 +116,12 @@ public abstract class AbstractEntityManagerFactoryBean implements
private String beanName;
/** Raw EntityManagerFactory as returned by the PersistenceProvider */
- public EntityManagerFactory nativeEntityManagerFactory;
+ private EntityManagerFactory nativeEntityManagerFactory;
+
+ /** Future for lazily initializing raw target EntityManagerFactory */
+ private Future<EntityManagerFactory> nativeEntityManagerFactoryFuture;
+ /** Exposed client-level EntityManagerFactory proxy */
private EntityManagerFactory entityManagerFactory;
@@ -264,6 +274,30 @@ public abstract class AbstractEntityManagerFactoryBean implements
return this.jpaVendorAdapter;
}
+ /**
+ * Specify an asynchronous executor for background bootstrapping,
+ * e.g. a {@link org.springframework.core.task.SimpleAsyncTaskExecutor}.
+ * <p>{@code EntityManagerFactory} initialization will then switch into background
+ * bootstrap mode, with a {@code EntityManagerFactory} proxy immediately returned for
+ * injection purposes instead of waiting for the JPA provider's bootstrapping to complete.
+ * However, note that the first actual call to a {@code EntityManagerFactory} method will
+ * then block until the JPA provider's bootstrapping completed, if not ready by then.
+ * For maximum benefit, make sure to avoid early {@code EntityManagerFactory} calls
+ * in init methods of related beans, even for metadata introspection purposes.
+ * @since 4.3
+ */
+ public void setBootstrapExecutor(AsyncTaskExecutor bootstrapExecutor) {
+ this.bootstrapExecutor = bootstrapExecutor;
+ }
+
+ /**
+ * Return the asynchronous executor for background bootstrapping, if any.
+ * @since 4.3
+ */
+ public AsyncTaskExecutor getBootstrapExecutor() {
+ return this.bootstrapExecutor;
+ }
+
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
@@ -316,13 +350,16 @@ public abstract class AbstractEntityManagerFactoryBean implements
}
}
- this.nativeEntityManagerFactory = createNativeEntityManagerFactory();
- if (this.nativeEntityManagerFactory == null) {
- throw new IllegalStateException(
- "JPA PersistenceProvider returned null EntityManagerFactory - check your JPA provider setup!");
+ if (this.bootstrapExecutor != null) {
+ this.nativeEntityManagerFactoryFuture = this.bootstrapExecutor.submit(new Callable<EntityManagerFactory>() {
+ @Override
+ public EntityManagerFactory call() {
+ return buildNativeEntityManagerFactory();
+ }
+ });
}
- if (this.jpaVendorAdapter != null) {
- this.jpaVendorAdapter.postProcessEntityManagerFactory(this.nativeEntityManagerFactory);
+ else {
+ this.nativeEntityManagerFactory = buildNativeEntityManagerFactory();
}
// Wrap the EntityManagerFactory in a factory implementing all its interfaces.
@@ -332,6 +369,21 @@ public abstract class AbstractEntityManagerFactoryBean implements
this.entityManagerFactory = createEntityManagerFactoryProxy(this.nativeEntityManagerFactory);
}
+ private EntityManagerFactory buildNativeEntityManagerFactory() {
+ EntityManagerFactory emf = createNativeEntityManagerFactory();
+ if (emf == null) {
+ throw new IllegalStateException(
+ "JPA PersistenceProvider returned null EntityManagerFactory - check your JPA provider setup!");
+ }
+ if (this.jpaVendorAdapter != null) {
+ this.jpaVendorAdapter.postProcessEntityManagerFactory(emf);
+ }
+ if (logger.isInfoEnabled()) {
+ logger.info("Initialized JPA EntityManagerFactory for persistence unit '" + getPersistenceUnitName() + "'");
+ }
+ return emf;
+ }
+
/**
* Create a proxy of the given EntityManagerFactory. We do this to be able
* to return transaction-aware proxies for application-managed
@@ -344,9 +396,12 @@ public abstract class AbstractEntityManagerFactoryBean implements
if (this.entityManagerFactoryInterface != null) {
ifcs.add(this.entityManagerFactoryInterface);
}
- else {
+ else if (emf != null) {
ifcs.addAll(ClassUtils.getAllInterfacesForClassAsSet(emf.getClass(), this.beanClassLoader));
}
+ else {
+ ifcs.add(EntityManagerFactory.class);
+ }
ifcs.add(EntityManagerFactoryInfo.class);
try {
return (EntityManagerFactory) Proxy.newProxyInstance(
@@ -380,8 +435,8 @@ public abstract class AbstractEntityManagerFactoryBean implements
// JPA 2.1's createEntityManager(SynchronizationType, Map)
// Redirect to plain createEntityManager and add synchronization semantics through Spring proxy
EntityManager rawEntityManager = (args.length > 1 ?
- this.nativeEntityManagerFactory.createEntityManager((Map<?, ?>) args[1]) :
- this.nativeEntityManagerFactory.createEntityManager());
+ getNativeEntityManagerFactory().createEntityManager((Map<?, ?>) args[1]) :
+ getNativeEntityManagerFactory().createEntityManager());
return ExtendedEntityManagerCreator.createApplicationManagedEntityManager(rawEntityManager, this, true);
}
@@ -404,7 +459,7 @@ public abstract class AbstractEntityManagerFactoryBean implements
}
// Standard delegation to the native factory, just post-processing EntityManager return values
- Object retVal = method.invoke(this.nativeEntityManagerFactory, args);
+ Object retVal = method.invoke(getNativeEntityManagerFactory(), args);
if (retVal instanceof EntityManager) {
// Any other createEntityManager variant - expecting non-synchronized semantics
EntityManager rawEntityManager = (EntityManager) retVal;
@@ -439,7 +494,22 @@ public abstract class AbstractEntityManagerFactoryBean implements
@Override
public EntityManagerFactory getNativeEntityManagerFactory() {
- return this.nativeEntityManagerFactory;
+ if (this.nativeEntityManagerFactory != null) {
+ return this.nativeEntityManagerFactory;
+ }
+ else {
+ try {
+ return this.nativeEntityManagerFactoryFuture.get();
+ }
+ catch (InterruptedException ex) {
+ throw new IllegalStateException("Interrupted during initialization of native EntityManagerFactory: " +
+ ex.getMessage());
+ }
+ catch (ExecutionException ex) {
+ throw new IllegalStateException("Failed to asynchronously initialize native EntityManagerFactory: " +
+ ex.getMessage(), ex.getCause());
+ }
+ }
}
@Override
@@ -553,7 +623,10 @@ public abstract class AbstractEntityManagerFactoryBean implements
else if (method.getName().equals("unwrap")) {
// Handle JPA 2.1 unwrap method - could be a proxy match.
Class<?> targetClass = (Class<?>) args[0];
- if (targetClass == null || targetClass.isInstance(proxy)) {
+ if (targetClass == null) {
+ return this.entityManagerFactoryBean.getNativeEntityManagerFactory();
+ }
+ else if (targetClass.isInstance(proxy)) {
return proxy;
}
}
diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java b/spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java
index c2f3a588..219beb3c 100644
--- a/spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java
+++ b/spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -415,7 +415,7 @@ public abstract class EntityManagerFactoryUtils {
// If we have another kind of PersistenceException, throw it.
if (ex instanceof PersistenceException) {
- return new JpaSystemException((PersistenceException) ex);
+ return new JpaSystemException(ex);
}
// If we get here, we have an exception that resulted from user code,
diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/ExtendedEntityManagerCreator.java b/spring-orm/src/main/java/org/springframework/orm/jpa/ExtendedEntityManagerCreator.java
index 1faa7235..1575a7d2 100644
--- a/spring-orm/src/main/java/org/springframework/orm/jpa/ExtendedEntityManagerCreator.java
+++ b/spring-orm/src/main/java/org/springframework/orm/jpa/ExtendedEntityManagerCreator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -296,7 +296,10 @@ public abstract class ExtendedEntityManagerCreator {
else if (method.getName().equals("unwrap")) {
// Handle JPA 2.0 unwrap method - could be a proxy match.
Class<?> targetClass = (Class<?>) args[0];
- if (targetClass == null || targetClass.isInstance(proxy)) {
+ if (targetClass == null) {
+ return this.target;
+ }
+ else if (targetClass.isInstance(proxy)) {
return proxy;
}
}
diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/JpaSystemException.java b/spring-orm/src/main/java/org/springframework/orm/jpa/JpaSystemException.java
index 0b879063..9b16af72 100644
--- a/spring-orm/src/main/java/org/springframework/orm/jpa/JpaSystemException.java
+++ b/spring-orm/src/main/java/org/springframework/orm/jpa/JpaSystemException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,6 +32,7 @@ import org.springframework.dao.UncategorizedDataAccessException;
@SuppressWarnings("serial")
public class JpaSystemException extends UncategorizedDataAccessException {
+ @Deprecated
public JpaSystemException(PersistenceException ex) {
super(ex.getMessage(), ex);
}
diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/JpaVendorAdapter.java b/spring-orm/src/main/java/org/springframework/orm/jpa/JpaVendorAdapter.java
index bf585ada..84923cf2 100644
--- a/spring-orm/src/main/java/org/springframework/orm/jpa/JpaVendorAdapter.java
+++ b/spring-orm/src/main/java/org/springframework/orm/jpa/JpaVendorAdapter.java
@@ -52,7 +52,7 @@ public interface JpaVendorAdapter {
* <p>Note that there might be further JPA properties defined on
* the EntityManagerFactory bean, which might potentially override
* individual JPA property values specified here.
- * @return a Map of JPA properties, as as accepted by the standard
+ * @return a Map of JPA properties, as accepted by the standard
* JPA bootstrap facilities, or {@code null} or an empty Map
* if there are no such properties to expose
* @see javax.persistence.Persistence#createEntityManagerFactory(String, java.util.Map)
diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.java b/spring-orm/src/main/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.java
index e9fbb6ce..8cfd8e99 100644
--- a/spring-orm/src/main/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.java
+++ b/spring-orm/src/main/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2015 the original author 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,8 @@ import org.springframework.util.ClassUtils;
* up a shared JPA EntityManagerFactory in a Spring application context;
* the EntityManagerFactory can then be passed to JPA-based DAOs via
* dependency injection. Note that switching to a JNDI lookup or to a
- * {@link LocalEntityManagerFactoryBean}
- * definition is just a matter of configuration!
+ * {@link LocalEntityManagerFactoryBean} definition is just a matter of
+ * configuration!
*
* <p>As with {@link LocalEntityManagerFactoryBean}, configuration settings
* are usually read in from a {@code META-INF/persistence.xml} config file,
@@ -329,21 +329,16 @@ public class LocalContainerEntityManagerFactoryBean extends AbstractEntityManage
Class<?> providerClass = ClassUtils.resolveClassName(providerClassName, getBeanClassLoader());
provider = (PersistenceProvider) BeanUtils.instantiateClass(providerClass);
}
- if (provider == null) {
- throw new IllegalStateException("Unable to determine persistence provider. " +
- "Please check configuration of " + getClass().getName() + "; " +
- "ideally specify the appropriate JpaVendorAdapter class for this provider.");
- }
if (logger.isInfoEnabled()) {
logger.info("Building JPA container EntityManagerFactory for persistence unit '" +
this.persistenceUnitInfo.getPersistenceUnitName() + "'");
}
- this.nativeEntityManagerFactory =
+ EntityManagerFactory emf =
provider.createContainerEntityManagerFactory(this.persistenceUnitInfo, getJpaPropertyMap());
- postProcessEntityManagerFactory(this.nativeEntityManagerFactory, this.persistenceUnitInfo);
+ postProcessEntityManagerFactory(emf, this.persistenceUnitInfo);
- return this.nativeEntityManagerFactory;
+ return emf;
}
diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/SharedEntityManagerCreator.java b/spring-orm/src/main/java/org/springframework/orm/jpa/SharedEntityManagerCreator.java
index cbc5e6be..46f6ae68 100644
--- a/spring-orm/src/main/java/org/springframework/orm/jpa/SharedEntityManagerCreator.java
+++ b/spring-orm/src/main/java/org/springframework/orm/jpa/SharedEntityManagerCreator.java
@@ -115,11 +115,11 @@ public abstract class SharedEntityManagerCreator {
*/
public static EntityManager createSharedEntityManager(
EntityManagerFactory emf, Map<?, ?> properties, boolean synchronizedWithTransaction) {
- Class<?> entityManagerInterface = (emf instanceof EntityManagerFactoryInfo ?
+
+ Class<?> emIfc = (emf instanceof EntityManagerFactoryInfo ?
((EntityManagerFactoryInfo) emf).getEntityManagerInterface() : EntityManager.class);
return createSharedEntityManager(emf, properties, synchronizedWithTransaction,
- (entityManagerInterface == null ? NO_ENTITY_MANAGER_INTERFACES :
- new Class<?>[] { entityManagerInterface }));
+ (emIfc == null ? NO_ENTITY_MANAGER_INTERFACES : new Class<?>[] {emIfc}));
}
/**
@@ -232,7 +232,7 @@ public abstract class SharedEntityManagerCreator {
else if (method.getName().equals("unwrap")) {
// JPA 2.0: handle unwrap method - could be a proxy match.
Class<?> targetClass = (Class<?>) args[0];
- if (targetClass == null || targetClass.isInstance(proxy)) {
+ if (targetClass != null && targetClass.isInstance(proxy)) {
return proxy;
}
}
@@ -263,6 +263,10 @@ public abstract class SharedEntityManagerCreator {
return target;
}
else if (method.getName().equals("unwrap")) {
+ Class<?> targetClass = (Class<?>) args[0];
+ if (targetClass == null) {
+ return (target != null ? target : proxy);
+ }
// We need a transactional target now.
if (target == null) {
throw new IllegalStateException("No transactional EntityManager available");
diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/ClassFileTransformerAdapter.java b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/ClassFileTransformerAdapter.java
index c7c150e7..9db7454c 100644
--- a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/ClassFileTransformerAdapter.java
+++ b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/ClassFileTransformerAdapter.java
@@ -39,8 +39,11 @@ class ClassFileTransformerAdapter implements ClassFileTransformer {
private static final Log logger = LogFactory.getLog(ClassFileTransformerAdapter.class);
+
private final ClassTransformer classTransformer;
+ private boolean currentlyTransforming = false;
+
public ClassFileTransformerAdapter(ClassTransformer classTransformer) {
Assert.notNull(classTransformer, "ClassTransformer must not be null");
@@ -53,30 +56,42 @@ class ClassFileTransformerAdapter implements ClassFileTransformer {
ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) {
- try {
- byte[] transformed = this.classTransformer.transform(
- loader, className, classBeingRedefined, protectionDomain, classfileBuffer);
- if (transformed != null && logger.isDebugEnabled()) {
- logger.debug("Transformer of class [" + this.classTransformer.getClass().getName() +
- "] transformed class [" + className + "]; bytes in=" +
- classfileBuffer.length + "; bytes out=" + transformed.length);
+ synchronized (this) {
+ if (this.currentlyTransforming) {
+ // Defensively back out when called from within the transform delegate below:
+ // in particular, for the over-eager transformer implementation in Hibernate 5.
+ return null;
}
- return transformed;
- }
- catch (ClassCircularityError ex) {
- if (logger.isErrorEnabled()) {
- logger.error("Circularity error while weaving class [" + className + "] with " +
- "transformer of class [" + this.classTransformer.getClass().getName() + "]", ex);
+
+ this.currentlyTransforming = true;
+ try {
+ byte[] transformed = this.classTransformer.transform(
+ loader, className, classBeingRedefined, protectionDomain, classfileBuffer);
+ if (transformed != null && logger.isDebugEnabled()) {
+ logger.debug("Transformer of class [" + this.classTransformer.getClass().getName() +
+ "] transformed class [" + className + "]; bytes in=" +
+ classfileBuffer.length + "; bytes out=" + transformed.length);
+ }
+ return transformed;
}
- throw new IllegalStateException("Failed to weave class [" + className + "]", ex);
- }
- catch (Throwable ex) {
- if (logger.isWarnEnabled()) {
- logger.warn("Error weaving class [" + className + "] with transformer of class [" +
- this.classTransformer.getClass().getName() + "]", ex);
+ catch (ClassCircularityError ex) {
+ if (logger.isErrorEnabled()) {
+ logger.error("Circularity error while weaving class [" + className + "] with " +
+ "transformer of class [" + this.classTransformer.getClass().getName() + "]", ex);
+ }
+ throw new IllegalStateException("Failed to weave class [" + className + "]", ex);
+ }
+ catch (Throwable ex) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("Error weaving class [" + className + "] with transformer of class [" +
+ this.classTransformer.getClass().getName() + "]", ex);
+ }
+ // The exception will be ignored by the class loader, anyway...
+ throw new IllegalStateException("Could not weave class [" + className + "]", ex);
+ }
+ finally {
+ this.currentlyTransforming = false;
}
- // The exception will be ignored by the class loader, anyway...
- throw new IllegalStateException("Could not weave class [" + className + "]", ex);
}
}
diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/DefaultPersistenceUnitManager.java b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/DefaultPersistenceUnitManager.java
index 34ac4be0..bae326c4 100644
--- a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/DefaultPersistenceUnitManager.java
+++ b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/DefaultPersistenceUnitManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -553,8 +553,20 @@ public class DefaultPersistenceUnitManager
scannedUnit.addMappingFileName(mappingFileName);
}
}
- else if (useOrmXmlForDefaultPersistenceUnit()) {
- scannedUnit.addMappingFileName(DEFAULT_ORM_XML_RESOURCE);
+ else {
+ Resource ormXml = getOrmXmlForDefaultPersistenceUnit();
+ if (ormXml != null) {
+ scannedUnit.addMappingFileName(DEFAULT_ORM_XML_RESOURCE);
+ if (scannedUnit.getPersistenceUnitRootUrl() == null) {
+ try {
+ scannedUnit.setPersistenceUnitRootUrl(
+ PersistenceUnitReader.determinePersistenceUnitRootUrl(ormXml));
+ }
+ catch (IOException ex) {
+ logger.debug("Failed to determine persistence unit root URL from orm.xml location", ex);
+ }
+ }
+ }
}
return scannedUnit;
@@ -593,27 +605,27 @@ public class DefaultPersistenceUnitManager
}
/**
- * Determine whether to register JPA's default "META-INF/orm.xml" with
- * Spring's default persistence unit, if any.
- * <p>Checks whether a "META-INF/orm.xml" file exists in the classpath and
- * uses it if it is not co-located with a "META-INF/persistence.xml" file.
+ * Determine JPA's default "META-INF/orm.xml" resource for use with Spring's default
+ * persistence unit, if any.
+ * <p>Checks whether a "META-INF/orm.xml" file exists in the classpath and uses it
+ * if it is not co-located with a "META-INF/persistence.xml" file.
*/
- private boolean useOrmXmlForDefaultPersistenceUnit() {
+ private Resource getOrmXmlForDefaultPersistenceUnit() {
Resource ormXml = this.resourcePatternResolver.getResource(
this.defaultPersistenceUnitRootLocation + DEFAULT_ORM_XML_RESOURCE);
if (ormXml.exists()) {
try {
Resource persistenceXml = ormXml.createRelative(PERSISTENCE_XML_FILENAME);
if (!persistenceXml.exists()) {
- return true;
+ return ormXml;
}
}
catch (IOException ex) {
// Cannot resolve relative persistence.xml file - let's assume it's not there.
- return true;
+ return ormXml;
}
}
- return false;
+ return null;
}
diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitReader.java b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitReader.java
index aafa06ff..bb4c44f0 100644
--- a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitReader.java
+++ b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitReader.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -51,7 +51,7 @@ import org.springframework.util.xml.SimpleSaxErrorHandler;
* @author Juergen Hoeller
* @since 2.0
*/
-class PersistenceUnitReader {
+final class PersistenceUnitReader {
private static final String PERSISTENCE_VERSION = "version";
@@ -84,7 +84,7 @@ class PersistenceUnitReader {
private static final String META_INF = "META-INF";
- private final Log logger = LogFactory.getLog(getClass());
+ private static final Log logger = LogFactory.getLog(PersistenceUnitReader.class);
private final ResourcePatternResolver resourcePatternResolver;
@@ -186,47 +186,6 @@ class PersistenceUnitReader {
}
/**
- * Determine the persistence unit root URL based on the given resource
- * (which points to the {@code persistence.xml} file we're reading).
- * @param resource the resource to check
- * @return the corresponding persistence unit root URL
- * @throws IOException if the checking failed
- */
- protected URL determinePersistenceUnitRootUrl(Resource resource) throws IOException {
- URL originalURL = resource.getURL();
-
- // If we get an archive, simply return the jar URL (section 6.2 from the JPA spec)
- if (ResourceUtils.isJarURL(originalURL)) {
- return ResourceUtils.extractJarFileURL(originalURL);
- }
-
- // check META-INF folder
- String urlToString = originalURL.toExternalForm();
- if (!urlToString.contains(META_INF)) {
- if (logger.isInfoEnabled()) {
- logger.info(resource.getFilename() +
- " should be located inside META-INF directory; cannot determine persistence unit root URL for " +
- resource);
- }
- return null;
- }
- if (urlToString.lastIndexOf(META_INF) == urlToString.lastIndexOf('/') - (1 + META_INF.length())) {
- if (logger.isInfoEnabled()) {
- logger.info(resource.getFilename() +
- " is not located in the root of META-INF directory; cannot determine persistence unit root URL for " +
- resource);
- }
- return null;
- }
-
- String persistenceUnitRoot = urlToString.substring(0, urlToString.lastIndexOf(META_INF));
- if (persistenceUnitRoot.endsWith("/")) {
- persistenceUnitRoot = persistenceUnitRoot.substring(0, persistenceUnitRoot.length() - 1);
- }
- return new URL(persistenceUnitRoot);
- }
-
- /**
* Parse the unit info DOM element.
*/
protected SpringPersistenceUnitInfo parsePersistenceUnitInfo(Element persistenceUnit, String version, URL rootUrl)
@@ -365,4 +324,46 @@ class PersistenceUnitReader {
}
}
+
+ /**
+ * Determine the persistence unit root URL based on the given resource
+ * (which points to the {@code persistence.xml} file we're reading).
+ * @param resource the resource to check
+ * @return the corresponding persistence unit root URL
+ * @throws IOException if the checking failed
+ */
+ static URL determinePersistenceUnitRootUrl(Resource resource) throws IOException {
+ URL originalURL = resource.getURL();
+
+ // If we get an archive, simply return the jar URL (section 6.2 from the JPA spec)
+ if (ResourceUtils.isJarURL(originalURL)) {
+ return ResourceUtils.extractJarFileURL(originalURL);
+ }
+
+ // Check META-INF folder
+ String urlToString = originalURL.toExternalForm();
+ if (!urlToString.contains(META_INF)) {
+ if (logger.isInfoEnabled()) {
+ logger.info(resource.getFilename() +
+ " should be located inside META-INF directory; cannot determine persistence unit root URL for " +
+ resource);
+ }
+ return null;
+ }
+ if (urlToString.lastIndexOf(META_INF) == urlToString.lastIndexOf('/') - (1 + META_INF.length())) {
+ if (logger.isInfoEnabled()) {
+ logger.info(resource.getFilename() +
+ " is not located in the root of META-INF directory; cannot determine persistence unit root URL for " +
+ resource);
+ }
+ return null;
+ }
+
+ String persistenceUnitRoot = urlToString.substring(0, urlToString.lastIndexOf(META_INF));
+ if (persistenceUnitRoot.endsWith("/")) {
+ persistenceUnitRoot = persistenceUnitRoot.substring(0, persistenceUnitRoot.length() - 1);
+ }
+ return new URL(persistenceUnitRoot);
+ }
+
}
diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java b/spring-orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java
index 97297ae4..e65d70f0 100644
--- a/spring-orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java
+++ b/spring-orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java
@@ -375,6 +375,11 @@ public class PersistenceAnnotationBeanPostProcessor
EntityManagerFactoryUtils.closeEntityManager(emToClose);
}
+ @Override
+ public boolean requiresDestruction(Object bean) {
+ return this.extendedEntityManagersToClose.containsKey(bean);
+ }
+
private InjectionMetadata findPersistenceMetadata(String beanName, final Class<?> clazz, PropertyValues pvs) {
// Fall back to class name as cache key, for backwards compatibility with custom callers.
diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaDialect.java b/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaDialect.java
index 6ff396ea..c089b425 100644
--- a/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaDialect.java
+++ b/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaDialect.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -66,13 +66,14 @@ import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.transaction.InvalidIsolationLevelException;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
+import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
/**
* {@link org.springframework.orm.jpa.JpaDialect} implementation for
* Hibernate EntityManager. Developed and tested against Hibernate 3.6,
- * 4.2/4.3 as well as 5.0.
+ * 4.2/4.3 as well as 5.0/5.1/5.2.
*
* @author Juergen Hoeller
* @author Costin Leau
@@ -88,6 +89,8 @@ public class HibernateJpaDialect extends DefaultJpaDialect {
private static Class<?> pessimisticLockExceptionClass;
+ private static Method getFlushMode;
+
static {
// Checking for Hibernate 4.x's Optimistic/PessimisticEntityLockException
ClassLoader cl = HibernateJpaDialect.class.getClassLoader();
@@ -104,10 +107,26 @@ public class HibernateJpaDialect extends DefaultJpaDialect {
catch (ClassNotFoundException ex) {
pessimisticLockExceptionClass = null;
}
+
+ try {
+ // Hibernate 5.2+ getHibernateFlushMode()
+ getFlushMode = Session.class.getMethod("getHibernateFlushMode");
+ }
+ catch (NoSuchMethodException ex) {
+ try {
+ // Classic Hibernate getFlushMode() with FlushMode return type
+ getFlushMode = Session.class.getMethod("getFlushMode");
+ }
+ catch (NoSuchMethodException ex2) {
+ throw new IllegalStateException("No compatible Hibernate getFlushMode signature found", ex2);
+ }
+ }
+ // Check that it is the Hibernate FlushMode type, not JPA's...
+ Assert.state(FlushMode.class == getFlushMode.getReturnType());
}
- private boolean prepareConnection = (HibernateConnectionHandle.sessionConnectionMethod == null);
+ boolean prepareConnection = (HibernateConnectionHandle.sessionConnectionMethod == null);
/**
@@ -184,7 +203,7 @@ public class HibernateJpaDialect extends DefaultJpaDialect {
}
protected FlushMode prepareFlushMode(Session session, boolean readOnly) throws PersistenceException {
- FlushMode flushMode = session.getFlushMode();
+ FlushMode flushMode = (FlushMode) ReflectionUtils.invokeMethod(getFlushMode, session);
if (readOnly) {
// We should suppress flushing for a read-only transaction.
if (!flushMode.equals(FlushMode.MANUAL)) {
diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaVendorAdapter.java b/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaVendorAdapter.java
index ccb1510f..4451a8bf 100644
--- a/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaVendorAdapter.java
+++ b/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaVendorAdapter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,7 +35,7 @@ import org.hibernate.dialect.SQLServerDialect;
/**
* {@link org.springframework.orm.jpa.JpaVendorAdapter} implementation for Hibernate
- * EntityManager. Developed and tested against Hibernate 3.6, 4.2/4.3 as well as 5.0.
+ * EntityManager. Developed and tested against Hibernate 3.6, 4.2/4.3 as well as 5.x.
* <b>Hibernate 4.2+ is strongly recommended for use with Spring 4.0+.</b>
*
* <p>Exposes Hibernate's persistence provider and EntityManager extension interface,
@@ -101,6 +101,27 @@ public class HibernateJpaVendorAdapter extends AbstractJpaVendorAdapter {
}
+ /**
+ * Set whether to prepare the underlying JDBC Connection of a transactional
+ * Hibernate Session, that is, whether to apply a transaction-specific
+ * isolation level and/or the transaction's read-only flag to the underlying
+ * JDBC Connection.
+ * <p>See {@link HibernateJpaDialect#setPrepareConnection(boolean)} for details.
+ * This is just a convenience flag passed through to {@code HibernateJpaDialect}.
+ * <p>On Hibernate 5.2, this flag remains {@code true} by default like against
+ * previous Hibernate versions. The vendor adapter manually enforces Hibernate's
+ * new connection handling mode {@code DELAYED_ACQUISITION_AND_HOLD} in that case
+ * unless a user-specified connection handling mode property indicates otherwise;
+ * switch this flag to {@code false} to avoid that interference.
+ * @since 4.3.1
+ * @see #getJpaPropertyMap()
+ * @see HibernateJpaDialect#beginTransaction
+ */
+ public void setPrepareConnection(boolean prepareConnection) {
+ this.jpaDialect.setPrepareConnection(prepareConnection);
+ }
+
+
@Override
public PersistenceProvider getPersistenceProvider() {
return this.persistenceProvider;
@@ -132,6 +153,11 @@ public class HibernateJpaVendorAdapter extends AbstractJpaVendorAdapter {
jpaProperties.put(Environment.SHOW_SQL, "true");
}
+ if (this.jpaDialect.prepareConnection) {
+ // Hibernate 5.2: manually enforce connection release mode ON_CLOSE (the former default)
+ jpaProperties.put("hibernate.connection.handling_mode", "DELAYED_ACQUISITION_AND_HOLD");
+ }
+
return jpaProperties;
}
diff --git a/spring-orm/src/main/java/overview.html b/spring-orm/src/main/java/overview.html
index 37f532b3..67b448a9 100644
--- a/spring-orm/src/main/java/overview.html
+++ b/spring-orm/src/main/java/overview.html
@@ -1,7 +1,7 @@
<html>
<body>
<p>
-Spring's O/R Mapping package: supporting Hibernate, JPA, JDO, and iBATIS SQL Maps.
+Spring's O/R Mapping package: supporting Hibernate, JPA, and JDO.
</p>
</body>
</html> \ No newline at end of file
diff --git a/spring-orm/src/test/java/org/springframework/orm/hibernate3/HibernateInterceptorTests.java b/spring-orm/src/test/java/org/springframework/orm/hibernate3/HibernateInterceptorTests.java
index 0ab51e08..dd94db21 100644
--- a/spring-orm/src/test/java/org/springframework/orm/hibernate3/HibernateInterceptorTests.java
+++ b/spring-orm/src/test/java/org/springframework/orm/hibernate3/HibernateInterceptorTests.java
@@ -42,7 +42,9 @@ import static org.mockito.BDDMockito.*;
* @author Juergen Hoeller
* @author Phillip Webb
* @since 05.03.2005
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
public class HibernateInterceptorTests {
private SessionFactory sessionFactory;
diff --git a/spring-orm/src/test/java/org/springframework/orm/hibernate3/HibernateJtaTransactionTests.java b/spring-orm/src/test/java/org/springframework/orm/hibernate3/HibernateJtaTransactionTests.java
index d64e193e..9c1988ef 100644
--- a/spring-orm/src/test/java/org/springframework/orm/hibernate3/HibernateJtaTransactionTests.java
+++ b/spring-orm/src/test/java/org/springframework/orm/hibernate3/HibernateJtaTransactionTests.java
@@ -55,7 +55,9 @@ import static org.mockito.BDDMockito.*;
* @author Juergen Hoeller
* @author Phillip Webb
* @since 05.03.2005
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
public class HibernateJtaTransactionTests {
@After
@@ -164,7 +166,8 @@ public class HibernateJtaTransactionTests {
if (readOnly) {
verify(session).setFlushMode(FlushMode.MANUAL);
- } else {
+ }
+ else {
verify(session).flush();
}
verify(session).close();
@@ -186,7 +189,8 @@ public class HibernateJtaTransactionTests {
UserTransaction ut = mock(UserTransaction.class);
if (status == Status.STATUS_NO_TRANSACTION) {
given(ut.getStatus()).willReturn(status, status, Status.STATUS_ACTIVE);
- } else {
+ }
+ else {
given(ut.getStatus()).willReturn(status);
}
@@ -518,13 +522,13 @@ public class HibernateJtaTransactionTests {
verify(ut).commit();
if (flushNever) {
- if(!readOnly) {
+ if (!readOnly) {
InOrder ordered = inOrder(session);
ordered.verify(session).setFlushMode(FlushMode.AUTO);
ordered.verify(session).setFlushMode(FlushMode.MANUAL);
}
}
- if(!flushNever && !readOnly) {
+ if (!flushNever && !readOnly) {
verify(session).flush();
}
verify(session).afterTransactionCompletion(true, null);
@@ -714,7 +718,7 @@ public class HibernateJtaTransactionTests {
}
verify(session1).disconnect();
verify(session1).close();
- if(!rollback) {
+ if (!rollback) {
verify(session1).flush();
verify(session2, atLeastOnce()).flush();
}
@@ -790,7 +794,7 @@ public class HibernateJtaTransactionTests {
}
verify(ut, atLeastOnce()).begin();
- if(!suspendException) {
+ if (!suspendException) {
verify(tm).resume(tx);
}
verify(ut).rollback();
@@ -1446,10 +1450,11 @@ public class HibernateJtaTransactionTests {
assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
InOrder ordered = inOrder(session);
- if(flushNever) {
+ if (flushNever) {
ordered.verify(session).setFlushMode(FlushMode.AUTO);
ordered.verify(session).setFlushMode(FlushMode.MANUAL);
- } else {
+ }
+ else {
ordered.verify(session).flush();
}
ordered.verify(session).disconnect();
diff --git a/spring-orm/src/test/java/org/springframework/orm/hibernate3/HibernateTemplateTests.java b/spring-orm/src/test/java/org/springframework/orm/hibernate3/HibernateTemplateTests.java
index e1f690aa..25f3de52 100644
--- a/spring-orm/src/test/java/org/springframework/orm/hibernate3/HibernateTemplateTests.java
+++ b/spring-orm/src/test/java/org/springframework/orm/hibernate3/HibernateTemplateTests.java
@@ -73,8 +73,10 @@ import static org.mockito.BDDMockito.*;
* @author Juergen Hoeller
* @author Phillip Webb
* @since 05.03.2005
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
-@SuppressWarnings({ "rawtypes", "unchecked" })
+@Deprecated
+@SuppressWarnings({"rawtypes", "unchecked"})
public class HibernateTemplateTests {
private SessionFactory sessionFactory;
diff --git a/spring-orm/src/test/java/org/springframework/orm/hibernate3/HibernateTransactionManagerTests.java b/spring-orm/src/test/java/org/springframework/orm/hibernate3/HibernateTransactionManagerTests.java
index 99cdbd1b..22dbecc4 100644
--- a/spring-orm/src/test/java/org/springframework/orm/hibernate3/HibernateTransactionManagerTests.java
+++ b/spring-orm/src/test/java/org/springframework/orm/hibernate3/HibernateTransactionManagerTests.java
@@ -65,8 +65,10 @@ import static org.mockito.BDDMockito.*;
* @author Juergen Hoeller
* @author Phillip Webb
* @since 05.03.2005
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
-@SuppressWarnings({"rawtypes", "unchecked", "deprecation"})
+@Deprecated
+@SuppressWarnings({"rawtypes", "unchecked"})
public class HibernateTransactionManagerTests {
@After
diff --git a/spring-orm/src/test/java/org/springframework/orm/hibernate3/LocalSessionFactoryBeanTests.java b/spring-orm/src/test/java/org/springframework/orm/hibernate3/LocalSessionFactoryBeanTests.java
index a7fae646..0f51dbbf 100644
--- a/spring-orm/src/test/java/org/springframework/orm/hibernate3/LocalSessionFactoryBeanTests.java
+++ b/spring-orm/src/test/java/org/springframework/orm/hibernate3/LocalSessionFactoryBeanTests.java
@@ -61,7 +61,9 @@ import static org.mockito.BDDMockito.*;
* @author Juergen Hoeller
* @author Phillip Webb
* @since 05.03.2005
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
public class LocalSessionFactoryBeanTests {
@Test
diff --git a/spring-orm/src/test/java/org/springframework/orm/hibernate3/support/HibernateDaoSupportTests.java b/spring-orm/src/test/java/org/springframework/orm/hibernate3/support/HibernateDaoSupportTests.java
index e05d9c8f..8ffe0754 100644
--- a/spring-orm/src/test/java/org/springframework/orm/hibernate3/support/HibernateDaoSupportTests.java
+++ b/spring-orm/src/test/java/org/springframework/orm/hibernate3/support/HibernateDaoSupportTests.java
@@ -31,7 +31,9 @@ import static org.mockito.BDDMockito.*;
* @author Juergen Hoeller
* @author Phillip Webb
* @since 05.03.2005
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
public class HibernateDaoSupportTests {
@Test
diff --git a/spring-orm/src/test/java/org/springframework/orm/hibernate3/support/LobTypeTests.java b/spring-orm/src/test/java/org/springframework/orm/hibernate3/support/LobTypeTests.java
index 9f4bc637..48a590a6 100644
--- a/spring-orm/src/test/java/org/springframework/orm/hibernate3/support/LobTypeTests.java
+++ b/spring-orm/src/test/java/org/springframework/orm/hibernate3/support/LobTypeTests.java
@@ -50,7 +50,9 @@ import static org.mockito.BDDMockito.*;
* @author Juergen Hoeller
* @author Phillip Webb
* @since 05.03.2005
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
public class LobTypeTests {
private ResultSet rs = mock(ResultSet.class);
diff --git a/spring-orm/src/test/java/org/springframework/orm/hibernate3/support/OpenSessionInViewTests.java b/spring-orm/src/test/java/org/springframework/orm/hibernate3/support/OpenSessionInViewTests.java
index 7d874831..e2c379e5 100644
--- a/spring-orm/src/test/java/org/springframework/orm/hibernate3/support/OpenSessionInViewTests.java
+++ b/spring-orm/src/test/java/org/springframework/orm/hibernate3/support/OpenSessionInViewTests.java
@@ -68,7 +68,9 @@ import static org.mockito.BDDMockito.*;
* @author Rossen Stoyanchev
* @author Phillip Webb
* @since 05.03.2005
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
public class OpenSessionInViewTests {
private MockServletContext sc;
diff --git a/spring-orm/src/test/java/org/springframework/orm/hibernate3/support/ScopedBeanInterceptorTests.java b/spring-orm/src/test/java/org/springframework/orm/hibernate3/support/ScopedBeanInterceptorTests.java
index 8e901ad9..762c0444 100644
--- a/spring-orm/src/test/java/org/springframework/orm/hibernate3/support/ScopedBeanInterceptorTests.java
+++ b/spring-orm/src/test/java/org/springframework/orm/hibernate3/support/ScopedBeanInterceptorTests.java
@@ -27,7 +27,9 @@ import static org.junit.Assert.*;
* Unit tests for {@link ScopedBeanInterceptor}.
*
* @author Costin Leau
+ * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
*/
+@Deprecated
public class ScopedBeanInterceptorTests {
private final ScopedBeanInterceptor interceptor = new ScopedBeanInterceptor();
diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/persistenceunit/PersistenceXmlParsingTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/persistenceunit/PersistenceXmlParsingTests.java
index 36a170ae..12b6ddd9 100644
--- a/spring-orm/src/test/java/org/springframework/orm/jpa/persistenceunit/PersistenceXmlParsingTests.java
+++ b/spring-orm/src/test/java/org/springframework/orm/jpa/persistenceunit/PersistenceXmlParsingTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -282,28 +282,21 @@ public class PersistenceXmlParsingTests {
@Test
public void testPersistenceUnitRootUrl() throws Exception {
- PersistenceUnitReader reader = new PersistenceUnitReader(
- new PathMatchingResourcePatternResolver(), new JndiDataSourceLookup());
-
- URL url = reader.determinePersistenceUnitRootUrl(new ClassPathResource(
- "/org/springframework/orm/jpa/persistence-no-schema.xml"));
+ URL url = PersistenceUnitReader.determinePersistenceUnitRootUrl(new ClassPathResource("/org/springframework/orm/jpa/persistence-no-schema.xml"));
assertNull(url);
- url = reader.determinePersistenceUnitRootUrl(new ClassPathResource("/org/springframework/orm/jpa/META-INF/persistence.xml"));
+ url = PersistenceUnitReader.determinePersistenceUnitRootUrl(new ClassPathResource("/org/springframework/orm/jpa/META-INF/persistence.xml"));
assertTrue("the containing folder should have been returned", url.toString().endsWith("/org/springframework/orm/jpa"));
}
@Test
public void testPersistenceUnitRootUrlWithJar() throws Exception {
- PersistenceUnitReader reader = new PersistenceUnitReader(
- new PathMatchingResourcePatternResolver(), new JndiDataSourceLookup());
-
ClassPathResource archive = new ClassPathResource("/org/springframework/orm/jpa/jpa-archive.jar");
String newRoot = "jar:" + archive.getURL().toExternalForm() + "!/META-INF/persist.xml";
Resource insideArchive = new UrlResource(newRoot);
// make sure the location actually exists
assertTrue(insideArchive.exists());
- URL url = reader.determinePersistenceUnitRootUrl(insideArchive);
+ URL url = PersistenceUnitReader.determinePersistenceUnitRootUrl(insideArchive);
assertTrue("the archive location should have been returned", archive.getURL().sameFile(url));
}
diff --git a/spring-orm/src/test/resources/org/springframework/orm/jpa/hibernate/hibernate-manager.xml b/spring-orm/src/test/resources/org/springframework/orm/jpa/hibernate/hibernate-manager.xml
index 23501907..ea7ce16c 100644
--- a/spring-orm/src/test/resources/org/springframework/orm/jpa/hibernate/hibernate-manager.xml
+++ b/spring-orm/src/test/resources/org/springframework/orm/jpa/hibernate/hibernate-manager.xml
@@ -10,13 +10,13 @@
<context:annotation-config />
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
- <property name="persistenceXmlLocation" value="org/springframework/orm/jpa/domain/persistence-context.xml" />
- <property name="dataSource" ref="dataSource" />
+ <property name="persistenceXmlLocation" value="org/springframework/orm/jpa/domain/persistence-context.xml"/>
+ <property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
- <property name="database" value="HSQL" />
- <property name="showSql" value="true" />
- <property name="generateDdl" value="true" />
+ <property name="database" value="HSQL"/>
+ <property name="showSql" value="true"/>
+ <property name="generateDdl" value="true"/>
</bean>
</property>
<property name="jpaPropertyMap">
@@ -25,6 +25,9 @@
<!-- <prop key="hibernate.ejb.use_class_enhancer">true</prop> -->
</props>
</property>
+ <property name="bootstrapExecutor">
+ <bean class="org.springframework.core.task.SimpleAsyncTaskExecutor"/>
+ </property>
</bean>
</beans>
diff --git a/spring-oxm/oxm.gradle b/spring-oxm/oxm.gradle
index 140b3513..f232e897 100644
--- a/spring-oxm/oxm.gradle
+++ b/spring-oxm/oxm.gradle
@@ -6,11 +6,10 @@ configurations {
}
dependencies {
castor "org.codehaus.castor:castor-anttasks:1.4.1"
- castor "org.apache.velocity:velocity:1.7"
+ jibx "org.jibx:jibx-bind:1.2.6"
+ jibx "org.apache.bcel:bcel:6.0"
xjc "com.sun.xml.bind:jaxb-xjc:2.1.17"
xmlbeans "org.apache.xmlbeans:xmlbeans:2.6.0"
- jibx "org.jibx:jibx-bind:1.2.6"
- jibx "bcel:bcel:5.1"
}
ext.genSourcesDir = "${buildDir}/generated-sources"
@@ -110,23 +109,18 @@ task genXmlbeans {
}
}
-// add jibx binding to the normal test compilation process
-// INCOMPATIBLE WITH OPENJDK 8 b89+
-def jibxEnabled = project.properties.get("testGroups")?.toLowerCase()?.split(",")?.contains("custom_compilation")
-if (jibxEnabled) {
- compileTestJava {
- def bindingXml = "${projectDir}/src/test/resources/org/springframework/oxm/jibx/binding.xml"
-
- doLast() {
- project.ant {
- taskdef(name: "jibx",
- classname: "org.jibx.binding.ant.CompileTask",
- classpath: configurations.jibx.asPath)
-
- jibx(verbose: true, load: true, binding: bindingXml) {
- classpathset(dir: sourceSets.test.output.classesDir) {
- include(name: "**/jibx/**/*")
- }
+compileTestJava {
+ def bindingXml = "${projectDir}/src/test/resources/org/springframework/oxm/jibx/binding.xml"
+
+ doLast() {
+ project.ant {
+ taskdef(name: "jibx",
+ classname: "org.jibx.binding.ant.CompileTask",
+ classpath: configurations.jibx.asPath)
+
+ jibx(verbose: true, load: true, binding: bindingXml) {
+ classpathset(dir: sourceSets.test.output.classesDir) {
+ include(name: "**/jibx/**/*")
}
}
}
diff --git a/spring-oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java b/spring-oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java
index 981db621..e198d10b 100644
--- a/spring-oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java
+++ b/spring-oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -897,7 +897,7 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi
/**
* Convert the given {@code JAXBException} to an appropriate exception from the
* {@code org.springframework.oxm} hierarchy.
- * @param ex {@code JAXBException} that occured
+ * @param ex {@code JAXBException} that occurred
* @return the corresponding {@code XmlMappingException}
*/
protected XmlMappingException convertJaxbException(JAXBException ex) {
diff --git a/spring-oxm/src/main/java/org/springframework/oxm/jibx/JibxMarshaller.java b/spring-oxm/src/main/java/org/springframework/oxm/jibx/JibxMarshaller.java
index 4815b8c3..c481fd1b 100644
--- a/spring-oxm/src/main/java/org/springframework/oxm/jibx/JibxMarshaller.java
+++ b/spring-oxm/src/main/java/org/springframework/oxm/jibx/JibxMarshaller.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -461,7 +461,7 @@ public class JibxMarshaller extends AbstractMarshaller implements InitializingBe
* {@code org.springframework.oxm} hierarchy.
* <p>A boolean flag is used to indicate whether this exception occurs during marshalling or
* unmarshalling, since JiBX itself does not make this distinction in its exception hierarchy.
- * @param ex {@code JiBXException} that occured
+ * @param ex {@code JiBXException} that occurred
* @param marshalling indicates whether the exception occurs during marshalling ({@code true}),
* or unmarshalling ({@code false})
* @return the corresponding {@code XmlMappingException}
diff --git a/spring-oxm/src/main/java/org/springframework/oxm/xstream/XStreamMarshaller.java b/spring-oxm/src/main/java/org/springframework/oxm/xstream/XStreamMarshaller.java
index 504f6c58..7ce47f58 100644
--- a/spring-oxm/src/main/java/org/springframework/oxm/xstream/XStreamMarshaller.java
+++ b/spring-oxm/src/main/java/org/springframework/oxm/xstream/XStreamMarshaller.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2015 the original author 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,6 +44,7 @@ import com.thoughtworks.xstream.converters.ConverterRegistry;
import com.thoughtworks.xstream.converters.DataHolder;
import com.thoughtworks.xstream.converters.SingleValueConverter;
import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
+import com.thoughtworks.xstream.core.ClassLoaderReference;
import com.thoughtworks.xstream.core.DefaultConverterLookup;
import com.thoughtworks.xstream.core.util.CompositeClassLoader;
import com.thoughtworks.xstream.io.HierarchicalStreamDriver;
@@ -104,7 +105,7 @@ import org.springframework.util.xml.StaxUtils;
* Therefore, it has limited namespace support. As such, it is rather unsuitable for
* usage within Web Services.
*
- * <p>This marshaller requires XStream 1.4 or higher, as of Spring 4.0.
+ * <p>This marshaller requires XStream 1.4.5 or higher, as of Spring 4.3.
* Note that {@link XStream} construction has been reworked in 4.0, with the
* stream driver and the class loader getting passed into XStream itself now.
*
@@ -405,12 +406,9 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin
* standard constructors or creating a custom subclass.
* @return the {@code XStream} instance
*/
- @SuppressWarnings("deprecation")
protected XStream constructXStream() {
- // The referenced XStream constructor has been deprecated as of 1.4.5.
- // We're preserving this call for broader XStream 1.4.x compatibility.
- return new XStream(this.reflectionProvider, getDefaultDriver(),
- this.beanClassLoader, this.mapper, this.converterLookup, this.converterRegistry) {
+ return new XStream(this.reflectionProvider, getDefaultDriver(), new ClassLoaderReference(this.beanClassLoader),
+ this.mapper, this.converterLookup, this.converterRegistry) {
@Override
protected MapperWrapper wrapMapper(MapperWrapper next) {
MapperWrapper mapperToWrap = next;
diff --git a/spring-oxm/src/main/resources/META-INF/spring.schemas b/spring-oxm/src/main/resources/META-INF/spring.schemas
index c009b020..48ab1cfb 100644
--- a/spring-oxm/src/main/resources/META-INF/spring.schemas
+++ b/spring-oxm/src/main/resources/META-INF/spring.schemas
@@ -4,4 +4,5 @@ http\://www.springframework.org/schema/oxm/spring-oxm-3.2.xsd=org/springframewor
http\://www.springframework.org/schema/oxm/spring-oxm-4.0.xsd=org/springframework/oxm/config/spring-oxm-4.0.xsd
http\://www.springframework.org/schema/oxm/spring-oxm-4.1.xsd=org/springframework/oxm/config/spring-oxm-4.1.xsd
http\://www.springframework.org/schema/oxm/spring-oxm-4.2.xsd=org/springframework/oxm/config/spring-oxm-4.2.xsd
-http\://www.springframework.org/schema/oxm/spring-oxm.xsd=org/springframework/oxm/config/spring-oxm-4.2.xsd
+http\://www.springframework.org/schema/oxm/spring-oxm-4.3.xsd=org/springframework/oxm/config/spring-oxm-4.3.xsd
+http\://www.springframework.org/schema/oxm/spring-oxm.xsd=org/springframework/oxm/config/spring-oxm-4.3.xsd
diff --git a/spring-oxm/src/main/resources/org/springframework/oxm/config/spring-oxm-4.3.xsd b/spring-oxm/src/main/resources/org/springframework/oxm/config/spring-oxm-4.3.xsd
new file mode 100644
index 00000000..db17b8ff
--- /dev/null
+++ b/spring-oxm/src/main/resources/org/springframework/oxm/config/spring-oxm-4.3.xsd
@@ -0,0 +1,136 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<xsd:schema xmlns="http://www.springframework.org/schema/oxm" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xmlns:tool="http://www.springframework.org/schema/tool"
+ targetNamespace="http://www.springframework.org/schema/oxm"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified">
+
+ <xsd:import namespace="http://www.springframework.org/schema/beans" schemaLocation="http://www.springframework.org/schema/beans/spring-beans-4.3.xsd"/>
+ <xsd:import namespace="http://www.springframework.org/schema/tool" schemaLocation="http://www.springframework.org/schema/tool/spring-tool-4.3.xsd"/>
+
+ <xsd:annotation>
+ <xsd:documentation>
+ Defines the elements used in Spring's Object/XML Mapping integration.
+ </xsd:documentation>
+ </xsd:annotation>
+
+ <xsd:element name="jaxb2-marshaller">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.oxm.jaxb.Jaxb2Marshaller">
+ Defines a JAXB2 Marshaller.
+ </xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation>
+ <tool:exports type="org.springframework.oxm.jaxb.Jaxb2Marshaller"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:complexContent>
+ <xsd:extension base="beans:identifiedType">
+ <xsd:sequence>
+ <xsd:element name="class-to-be-bound" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:attribute name="name" type="classType" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="context-path" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation>The JAXB context path.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="jibx-marshaller">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.oxm.jibx.JibxMarshaller">
+ Defines a JiBX Marshaller.
+ </xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation>
+ <tool:exports type="org.springframework.oxm.jibx.JibxMarshaller"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:complexContent>
+ <xsd:extension base="beans:identifiedType">
+ <xsd:attribute name="target-class" type="classType">
+ <xsd:annotation>
+ <xsd:documentation>The target class to be bound with JiBX.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="target-package" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation>The target package for the JiBX binding.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="binding-name" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation>The binding name used by this marshaller.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="castor-marshaller">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation
+ source="java:org.springframework.oxm.castor.CastorMarshaller">
+ Defines a Castor Marshaller.
+ </xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation>
+ <tool:exports type="org.springframework.oxm.castor.CastorMarshaller" />
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:complexContent>
+ <xsd:extension base="beans:identifiedType">
+ <xsd:attribute name="encoding" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation>The encoding to use for stream reading.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="target-class" type="classType">
+ <xsd:annotation>
+ <xsd:documentation>The target class to be bound with the Castor marshaller.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="target-package" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation>The target package that contains Castor descriptor classes.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="mapping-location" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation>The path to the Castor mapping file.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:simpleType name="classType">
+ <xsd:annotation>
+ <xsd:documentation source="java:java.lang.Class">A class supported by a marshaller.</xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="direct">
+ <tool:expected-type type="java.lang.Class"/>
+ <tool:assignable-to restriction="class-only"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:union memberTypes="xsd:string"/>
+ </xsd:simpleType>
+
+</xsd:schema>
diff --git a/spring-oxm/src/test/java/org/springframework/oxm/config/OxmNamespaceHandlerTests.java b/spring-oxm/src/test/java/org/springframework/oxm/config/OxmNamespaceHandlerTests.java
index 9595d808..4a6a46ba 100644
--- a/spring-oxm/src/test/java/org/springframework/oxm/config/OxmNamespaceHandlerTests.java
+++ b/spring-oxm/src/test/java/org/springframework/oxm/config/OxmNamespaceHandlerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,8 +37,9 @@ import static org.junit.Assert.*;
@SuppressWarnings("deprecation")
public class OxmNamespaceHandlerTests {
- private final ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
- "oxmNamespaceHandlerTest.xml", getClass());
+ private final ApplicationContext applicationContext =
+ new ClassPathXmlApplicationContext("oxmNamespaceHandlerTest.xml", getClass());
+
@Test
public void xmlBeansMarshaller() throws Exception {
@@ -85,4 +86,5 @@ public class OxmNamespaceHandlerTests {
CastorMarshaller castorMarshaller = applicationContext.getBean("castorMappingLocationMarshaller", CastorMarshaller.class);
assertNotNull(castorMarshaller);
}
-} \ No newline at end of file
+
+}
diff --git a/spring-oxm/src/test/java/org/springframework/oxm/jibx/Flights.java b/spring-oxm/src/test/java/org/springframework/oxm/jibx/Flights.java
index f44e14c8..c12ca200 100644
--- a/spring-oxm/src/test/java/org/springframework/oxm/jibx/Flights.java
+++ b/spring-oxm/src/test/java/org/springframework/oxm/jibx/Flights.java
@@ -17,11 +17,10 @@
package org.springframework.oxm.jibx;
import java.util.ArrayList;
-import java.util.List;
public class Flights {
- protected List<FlightType> flightList = new ArrayList<FlightType>();
+ protected ArrayList<FlightType> flightList = new ArrayList<FlightType>();
public void addFlight(FlightType flight) {
flightList.add(flight);
diff --git a/spring-oxm/src/test/java/org/springframework/oxm/jibx/JibxMarshallerTests.java b/spring-oxm/src/test/java/org/springframework/oxm/jibx/JibxMarshallerTests.java
index 3102f11f..e9a26ecc 100644
--- a/spring-oxm/src/test/java/org/springframework/oxm/jibx/JibxMarshallerTests.java
+++ b/spring-oxm/src/test/java/org/springframework/oxm/jibx/JibxMarshallerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,13 +20,9 @@ import java.io.StringWriter;
import javax.xml.transform.stream.StreamResult;
import org.custommonkey.xmlunit.XMLUnit;
-
-import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.oxm.AbstractMarshallerTests;
-import org.springframework.tests.Assume;
-import org.springframework.tests.TestGroup;
import static org.custommonkey.xmlunit.XMLAssert.*;
import static org.junit.Assert.assertFalse;
@@ -41,11 +37,6 @@ import static org.junit.Assert.assertTrue;
*/
public class JibxMarshallerTests extends AbstractMarshallerTests<JibxMarshaller> {
- @BeforeClass
- public static void compilerAssumptions() {
- Assume.group(TestGroup.CUSTOM_COMPILATION);
- }
-
@Override
protected JibxMarshaller createMarshaller() throws Exception {
JibxMarshaller marshaller = new JibxMarshaller();
@@ -63,6 +54,7 @@ public class JibxMarshallerTests extends AbstractMarshallerTests<JibxMarshaller>
return flights;
}
+
@Test(expected = IllegalArgumentException.class)
public void afterPropertiesSetNoContextPath() throws Exception {
JibxMarshaller marshaller = new JibxMarshaller();
diff --git a/spring-oxm/src/test/java/org/springframework/oxm/jibx/JibxUnmarshallerTests.java b/spring-oxm/src/test/java/org/springframework/oxm/jibx/JibxUnmarshallerTests.java
index c6e8c553..1363f809 100644
--- a/spring-oxm/src/test/java/org/springframework/oxm/jibx/JibxUnmarshallerTests.java
+++ b/spring-oxm/src/test/java/org/springframework/oxm/jibx/JibxUnmarshallerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,12 +19,11 @@ package org.springframework.oxm.jibx;
import java.io.ByteArrayInputStream;
import javax.xml.transform.stream.StreamSource;
+import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.oxm.AbstractUnmarshallerTests;
-import org.springframework.tests.Assume;
-import org.springframework.tests.TestGroup;
import static org.junit.Assert.*;
@@ -41,11 +40,6 @@ public class JibxUnmarshallerTests extends AbstractUnmarshallerTests<JibxMarshal
"<tns:flights xmlns:tns=\"http://samples.springframework.org/flight\">" +
"<tns:flight><tns:airline>Air Libert\u00e9</tns:airline><tns:number>42</tns:number></tns:flight></tns:flights>";
- @BeforeClass
- public static void compilerAssumptions() {
- Assume.group(TestGroup.CUSTOM_COMPILATION);
- }
-
@Override
protected JibxMarshaller createUnmarshaller() throws Exception {
@@ -70,6 +64,7 @@ public class JibxUnmarshallerTests extends AbstractUnmarshallerTests<JibxMarshal
assertEquals("Number is invalid", 42L, flight.getNumber());
}
+
@Test
@Override
public void unmarshalPartialStaxSourceXmlStreamReader() throws Exception {
diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletRequest.java b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletRequest.java
index 01840d66..d1a8c37c 100644
--- a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletRequest.java
+++ b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletRequest.java
@@ -390,8 +390,8 @@ public class MockHttpServletRequest implements HttpServletRequest {
if (contentType != null) {
try {
MediaType mediaType = MediaType.parseMediaType(contentType);
- if (mediaType.getCharSet() != null) {
- this.characterEncoding = mediaType.getCharSet().name();
+ if (mediaType.getCharset() != null) {
+ this.characterEncoding = mediaType.getCharset().name();
}
}
catch (Exception ex) {
diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java
index 5fc724b8..1d5b838d 100644
--- a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java
+++ b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java
@@ -236,8 +236,8 @@ public class MockHttpServletResponse implements HttpServletResponse {
if (contentType != null) {
try {
MediaType mediaType = MediaType.parseMediaType(contentType);
- if (mediaType.getCharSet() != null) {
- this.characterEncoding = mediaType.getCharSet().name();
+ if (mediaType.getCharset() != null) {
+ this.characterEncoding = mediaType.getCharset().name();
this.charset = true;
}
}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockServletContext.java b/spring-test/src/main/java/org/springframework/mock/web/MockServletContext.java
index 8be8a9a1..7bca348a 100644
--- a/spring-test/src/main/java/org/springframework/mock/web/MockServletContext.java
+++ b/spring-test/src/main/java/org/springframework/mock/web/MockServletContext.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,7 +25,6 @@ import java.util.Collections;
import java.util.Enumeration;
import java.util.EventListener;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
@@ -144,7 +143,7 @@ public class MockServletContext implements ServletContext {
private String servletContextName = "MockServletContext";
- private final Set<String> declaredRoles = new HashSet<String>();
+ private final Set<String> declaredRoles = new LinkedHashSet<String>();
private Set<SessionTrackingMode> sessionTrackingModes;
@@ -370,7 +369,6 @@ public class MockServletContext implements ServletContext {
/**
* Register a {@link RequestDispatcher} (typically a {@link MockRequestDispatcher})
* that acts as a wrapper for the named Servlet.
- *
* @param name the name of the wrapped Servlet
* @param requestDispatcher the dispatcher that wraps the named Servlet
* @see #getNamedDispatcher
@@ -384,7 +382,6 @@ public class MockServletContext implements ServletContext {
/**
* Unregister the {@link RequestDispatcher} with the given name.
- *
* @param name the name of the dispatcher to unregister
* @see #getNamedDispatcher
* @see #registerNamedDispatcher
@@ -429,13 +426,13 @@ public class MockServletContext implements ServletContext {
@Override
@Deprecated
public Enumeration<Servlet> getServlets() {
- return Collections.enumeration(new HashSet<Servlet>());
+ return Collections.enumeration(Collections.<Servlet>emptySet());
}
@Override
@Deprecated
public Enumeration<String> getServletNames() {
- return Collections.enumeration(new HashSet<String>());
+ return Collections.enumeration(Collections.<String>emptySet());
}
@Override
diff --git a/spring-test/src/main/java/org/springframework/mock/web/portlet/ServletWrappingPortletContext.java b/spring-test/src/main/java/org/springframework/mock/web/portlet/ServletWrappingPortletContext.java
index 66a663e0..716f71b0 100644
--- a/spring-test/src/main/java/org/springframework/mock/web/portlet/ServletWrappingPortletContext.java
+++ b/spring-test/src/main/java/org/springframework/mock/web/portlet/ServletWrappingPortletContext.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,6 @@ import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.Enumeration;
-import java.util.HashSet;
import java.util.Set;
import javax.portlet.PortletContext;
import javax.portlet.PortletRequestDispatcher;
@@ -156,7 +155,7 @@ public class ServletWrappingPortletContext implements PortletContext {
@Override
public Enumeration<String> getContainerRuntimeOptions() {
- return Collections.enumeration(new HashSet<String>());
+ return Collections.enumeration(Collections.<String>emptySet());
}
}
diff --git a/spring-test/src/main/java/org/springframework/test/annotation/Commit.java b/spring-test/src/main/java/org/springframework/test/annotation/Commit.java
index e23c254f..98b3d96b 100644
--- a/spring-test/src/main/java/org/springframework/test/annotation/Commit.java
+++ b/spring-test/src/main/java/org/springframework/test/annotation/Commit.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.test.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@@ -51,6 +52,7 @@ import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
+@Inherited
@Rollback(false)
public @interface Commit {
}
diff --git a/spring-test/src/main/java/org/springframework/test/annotation/ProfileValueSourceConfiguration.java b/spring-test/src/main/java/org/springframework/test/annotation/ProfileValueSourceConfiguration.java
index cd285587..e5f61e45 100644
--- a/spring-test/src/main/java/org/springframework/test/annotation/ProfileValueSourceConfiguration.java
+++ b/spring-test/src/main/java/org/springframework/test/annotation/ProfileValueSourceConfiguration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,7 +24,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
- * <p>{@code ProfileValueSourceConfiguration} is a class-level annotation which
+ * {@code ProfileValueSourceConfiguration} is a class-level annotation which
* is used to specify what type of {@link ProfileValueSource} to use when
* retrieving <em>profile values</em> configured via the {@link IfProfileValue
* &#064;IfProfileValue} annotation.
@@ -38,17 +38,15 @@ import java.lang.annotation.Target;
* @see IfProfileValue
* @see ProfileValueUtils
*/
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.TYPE)
public @interface ProfileValueSourceConfiguration {
/**
- * <p>
* The type of {@link ProfileValueSource} to use when retrieving
* <em>profile values</em>.
- * </p>
*
* @see SystemProfileValueSource
*/
diff --git a/spring-test/src/main/java/org/springframework/test/annotation/ProfileValueUtils.java b/spring-test/src/main/java/org/springframework/test/annotation/ProfileValueUtils.java
index 27ce7f64..17c60cad 100644
--- a/spring-test/src/main/java/org/springframework/test/annotation/ProfileValueUtils.java
+++ b/spring-test/src/main/java/org/springframework/test/annotation/ProfileValueUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,13 +21,12 @@ import java.lang.reflect.Method;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
-import static org.springframework.core.annotation.AnnotationUtils.*;
-
/**
* General utility methods for working with <em>profile values</em>.
*
@@ -49,12 +48,10 @@ public abstract class ProfileValueUtils {
* {@link ProfileValueSourceConfiguration
* &#064;ProfileValueSourceConfiguration} annotation and instantiates a new
* instance of that type.
- * <p>
- * If {@link ProfileValueSourceConfiguration
+ * <p>If {@link ProfileValueSourceConfiguration
* &#064;ProfileValueSourceConfiguration} is not present on the specified
* class or if a custom {@link ProfileValueSource} is not declared, the
* default {@link SystemProfileValueSource} will be returned instead.
- *
* @param testClass The test class for which the ProfileValueSource should
* be retrieved
* @return the configured (or default) ProfileValueSource for the specified
@@ -66,10 +63,10 @@ public abstract class ProfileValueUtils {
Assert.notNull(testClass, "testClass must not be null");
Class<ProfileValueSourceConfiguration> annotationType = ProfileValueSourceConfiguration.class;
- ProfileValueSourceConfiguration config = findAnnotation(testClass, annotationType);
+ ProfileValueSourceConfiguration config = AnnotatedElementUtils.findMergedAnnotation(testClass, annotationType);
if (logger.isDebugEnabled()) {
- logger.debug("Retrieved @ProfileValueSourceConfiguration [" + config + "] for test class ["
- + testClass.getName() + "]");
+ logger.debug("Retrieved @ProfileValueSourceConfiguration [" + config + "] for test class [" +
+ testClass.getName() + "]");
}
Class<? extends ProfileValueSource> profileValueSourceType;
@@ -80,8 +77,8 @@ public abstract class ProfileValueUtils {
profileValueSourceType = (Class<? extends ProfileValueSource>) AnnotationUtils.getDefaultValue(annotationType);
}
if (logger.isDebugEnabled()) {
- logger.debug("Retrieved ProfileValueSource type [" + profileValueSourceType + "] for class ["
- + testClass.getName() + "]");
+ logger.debug("Retrieved ProfileValueSource type [" + profileValueSourceType + "] for class [" +
+ testClass.getName() + "]");
}
ProfileValueSource profileValueSource;
@@ -92,10 +89,10 @@ public abstract class ProfileValueUtils {
try {
profileValueSource = profileValueSourceType.newInstance();
}
- catch (Exception e) {
+ catch (Exception ex) {
if (logger.isWarnEnabled()) {
- logger.warn("Could not instantiate a ProfileValueSource of type [" + profileValueSourceType
- + "] for class [" + testClass.getName() + "]: using default.", e);
+ logger.warn("Could not instantiate a ProfileValueSource of type [" + profileValueSourceType +
+ "] for class [" + testClass.getName() + "]: using default.", ex);
}
profileValueSource = SystemProfileValueSource.getInstance();
}
@@ -108,16 +105,14 @@ public abstract class ProfileValueUtils {
* Determine if the supplied {@code testClass} is <em>enabled</em> in
* the current environment, as specified by the {@link IfProfileValue
* &#064;IfProfileValue} annotation at the class level.
- * <p>
- * Defaults to {@code true} if no {@link IfProfileValue
+ * <p>Defaults to {@code true} if no {@link IfProfileValue
* &#064;IfProfileValue} annotation is declared.
- *
* @param testClass the test class
* @return {@code true} if the test is <em>enabled</em> in the current
* environment
*/
public static boolean isTestEnabledInThisEnvironment(Class<?> testClass) {
- IfProfileValue ifProfileValue = findAnnotation(testClass, IfProfileValue.class);
+ IfProfileValue ifProfileValue = AnnotatedElementUtils.findMergedAnnotation(testClass, IfProfileValue.class);
return isTestEnabledInThisEnvironment(retrieveProfileValueSource(testClass), ifProfileValue);
}
@@ -127,10 +122,8 @@ public abstract class ProfileValueUtils {
* &#064;IfProfileValue} annotation, which may be declared on the test
* method itself or at the class level. Class-level usage overrides
* method-level usage.
- * <p>
- * Defaults to {@code true} if no {@link IfProfileValue
+ * <p>Defaults to {@code true} if no {@link IfProfileValue
* &#064;IfProfileValue} annotation is declared.
- *
* @param testMethod the test method
* @param testClass the test class
* @return {@code true} if the test is <em>enabled</em> in the current
@@ -146,10 +139,8 @@ public abstract class ProfileValueUtils {
* &#064;IfProfileValue} annotation, which may be declared on the test
* method itself or at the class level. Class-level usage overrides
* method-level usage.
- * <p>
- * Defaults to {@code true} if no {@link IfProfileValue
+ * <p>Defaults to {@code true} if no {@link IfProfileValue
* &#064;IfProfileValue} annotation is declared.
- *
* @param profileValueSource the ProfileValueSource to use to determine if
* the test is enabled
* @param testMethod the test method
@@ -160,11 +151,11 @@ public abstract class ProfileValueUtils {
public static boolean isTestEnabledInThisEnvironment(ProfileValueSource profileValueSource, Method testMethod,
Class<?> testClass) {
- IfProfileValue ifProfileValue = findAnnotation(testClass, IfProfileValue.class);
+ IfProfileValue ifProfileValue = AnnotatedElementUtils.findMergedAnnotation(testClass, IfProfileValue.class);
boolean classLevelEnabled = isTestEnabledInThisEnvironment(profileValueSource, ifProfileValue);
if (classLevelEnabled) {
- ifProfileValue = findAnnotation(testMethod, IfProfileValue.class);
+ ifProfileValue = AnnotatedElementUtils.findMergedAnnotation(testMethod, IfProfileValue.class);
return isTestEnabledInThisEnvironment(profileValueSource, ifProfileValue);
}
@@ -175,7 +166,6 @@ public abstract class ProfileValueUtils {
* Determine if the {@code value} (or one of the {@code values})
* in the supplied {@link IfProfileValue &#064;IfProfileValue} annotation is
* <em>enabled</em> in the current environment.
- *
* @param profileValueSource the ProfileValueSource to use to determine if
* the test is enabled
* @param ifProfileValue the annotation to introspect; may be
@@ -195,8 +185,8 @@ public abstract class ProfileValueUtils {
String[] annotatedValues = ifProfileValue.values();
if (StringUtils.hasLength(ifProfileValue.value())) {
if (annotatedValues.length > 0) {
- throw new IllegalArgumentException("Setting both the 'value' and 'values' attributes "
- + "of @IfProfileValue is not allowed: choose one or the other.");
+ throw new IllegalArgumentException("Setting both the 'value' and 'values' attributes " +
+ "of @IfProfileValue is not allowed: choose one or the other.");
}
annotatedValues = new String[] { ifProfileValue.value() };
}
diff --git a/spring-test/src/main/java/org/springframework/test/annotation/Rollback.java b/spring-test/src/main/java/org/springframework/test/annotation/Rollback.java
index 37bf2102..c1f06e64 100644
--- a/spring-test/src/main/java/org/springframework/test/annotation/Rollback.java
+++ b/spring-test/src/main/java/org/springframework/test/annotation/Rollback.java
@@ -18,6 +18,7 @@ package org.springframework.test.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@@ -56,6 +57,7 @@ import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
+@Inherited
public @interface Rollback {
/**
diff --git a/spring-test/src/main/java/org/springframework/test/annotation/TestAnnotationUtils.java b/spring-test/src/main/java/org/springframework/test/annotation/TestAnnotationUtils.java
index 5f4eb1c2..a2b33faa 100644
--- a/spring-test/src/main/java/org/springframework/test/annotation/TestAnnotationUtils.java
+++ b/spring-test/src/main/java/org/springframework/test/annotation/TestAnnotationUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,7 +19,6 @@ package org.springframework.test.annotation;
import java.lang.reflect.Method;
import org.springframework.core.annotation.AnnotatedElementUtils;
-import org.springframework.core.annotation.AnnotationUtils;
/**
* Collection of utility methods for working with Spring's core testing annotations.
@@ -52,7 +51,7 @@ public class TestAnnotationUtils {
* not annotated with {@code @Repeat}
*/
public static int getRepeatCount(Method method) {
- Repeat repeat = AnnotationUtils.findAnnotation(method, Repeat.class);
+ Repeat repeat = AnnotatedElementUtils.findMergedAnnotation(method, Repeat.class);
if (repeat == null) {
return 1;
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/ActiveProfiles.java b/spring-test/src/main/java/org/springframework/test/context/ActiveProfiles.java
index fc259102..54b08892 100644
--- a/spring-test/src/main/java/org/springframework/test/context/ActiveProfiles.java
+++ b/spring-test/src/main/java/org/springframework/test/context/ActiveProfiles.java
@@ -43,10 +43,10 @@ import org.springframework.core.annotation.AliasFor;
* @see org.springframework.context.ApplicationContext
* @see org.springframework.context.annotation.Profile
*/
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.TYPE)
public @interface ActiveProfiles {
/**
diff --git a/spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java b/spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java
index c16d7def..22358071 100644
--- a/spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java
+++ b/spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java
@@ -17,22 +17,22 @@
package org.springframework.test.context;
import java.lang.reflect.Constructor;
-import java.util.List;
+import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.core.annotation.AnnotatedElementUtils;
-import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.util.ClassUtils;
-import org.springframework.util.MultiValueMap;
/**
* {@code BootstrapUtils} is a collection of utility methods to assist with
* bootstrapping the <em>Spring TestContext Framework</em>.
*
* @author Sam Brannen
+ * @author Phillip Webb
* @since 4.1
* @see BootstrapWith
* @see BootstrapContext
@@ -49,6 +49,12 @@ abstract class BootstrapUtils {
private static final String DEFAULT_TEST_CONTEXT_BOOTSTRAPPER_CLASS_NAME =
"org.springframework.test.context.support.DefaultTestContextBootstrapper";
+ private static final String DEFAULT_WEB_TEST_CONTEXT_BOOTSTRAPPER_CLASS_NAME =
+ "org.springframework.test.context.web.WebTestContextBootstrapper";
+
+ private static final String WEB_APP_CONFIGURATION_ANNOTATION_CLASS_NAME =
+ "org.springframework.test.context.web.WebAppConfiguration";
+
private static final Log logger = LogFactory.getLog(BootstrapUtils.class);
@@ -103,51 +109,64 @@ abstract class BootstrapUtils {
* <p>If the {@link BootstrapWith @BootstrapWith} annotation is present on
* the test class, either directly or as a meta-annotation, then its
* {@link BootstrapWith#value value} will be used as the bootstrapper type.
- * Otherwise, the {@link org.springframework.test.context.support.DefaultTestContextBootstrapper
- * DefaultTestContextBootstrapper} will be used.
+ * Otherwise, either the
+ * {@link org.springframework.test.context.support.DefaultTestContextBootstrapper
+ * DefaultTestContextBootstrapper} or the
+ * {@link org.springframework.test.context.web.WebTestContextBootstrapper
+ * WebTestContextBootstrapper} will be used, depending on the presence of
+ * {@link org.springframework.test.context.web.WebAppConfiguration @WebAppConfiguration}.
* @param bootstrapContext the bootstrap context to use
* @return a fully configured {@code TestContextBootstrapper}
*/
- @SuppressWarnings("unchecked")
static TestContextBootstrapper resolveTestContextBootstrapper(BootstrapContext bootstrapContext) {
Class<?> testClass = bootstrapContext.getTestClass();
- Class<? extends TestContextBootstrapper> clazz = null;
+ Class<?> clazz = null;
try {
- MultiValueMap<String, Object> attributesMultiMap = AnnotatedElementUtils.getAllAnnotationAttributes(
- testClass, BootstrapWith.class.getName());
- List<Object> values = (attributesMultiMap == null ? null : attributesMultiMap.get(AnnotationUtils.VALUE));
-
- if (values != null) {
- if (values.size() != 1) {
- throw new IllegalStateException(String.format("Configuration error: found multiple declarations of " +
- "@BootstrapWith on test class [%s] with values %s", testClass.getName(), values));
- }
- clazz = (Class<? extends TestContextBootstrapper>) values.get(0);
- }
- else {
- clazz = (Class<? extends TestContextBootstrapper>) ClassUtils.forName(
- DEFAULT_TEST_CONTEXT_BOOTSTRAPPER_CLASS_NAME, BootstrapUtils.class.getClassLoader());
+ clazz = resolveExplicitTestContextBootstrapper(testClass);
+ if (clazz == null) {
+ clazz = resolveDefaultTestContextBootstrapper(testClass);
}
-
if (logger.isDebugEnabled()) {
logger.debug(String.format("Instantiating TestContextBootstrapper for test class [%s] from class [%s]",
testClass.getName(), clazz.getName()));
}
-
TestContextBootstrapper testContextBootstrapper =
BeanUtils.instantiateClass(clazz, TestContextBootstrapper.class);
testContextBootstrapper.setBootstrapContext(bootstrapContext);
return testContextBootstrapper;
}
+ catch (IllegalStateException ex) {
+ throw ex;
+ }
catch (Throwable ex) {
- if (ex instanceof IllegalStateException) {
- throw (IllegalStateException) ex;
- }
throw new IllegalStateException("Could not load TestContextBootstrapper [" + clazz +
"]. Specify @BootstrapWith's 'value' attribute or make the default bootstrapper class available.",
ex);
}
}
+ private static Class<?> resolveExplicitTestContextBootstrapper(Class<?> testClass) {
+ Set<BootstrapWith> annotations = AnnotatedElementUtils.findAllMergedAnnotations(testClass, BootstrapWith.class);
+ if (annotations.size() < 1) {
+ return null;
+ }
+ if (annotations.size() > 1) {
+ throw new IllegalStateException(String.format(
+ "Configuration error: found multiple declarations of @BootstrapWith for test class [%s]: %s",
+ testClass.getName(), annotations));
+ }
+ return annotations.iterator().next().value();
+ }
+
+ private static Class<?> resolveDefaultTestContextBootstrapper(Class<?> testClass) throws Exception {
+ ClassLoader classLoader = BootstrapUtils.class.getClassLoader();
+ AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(testClass,
+ WEB_APP_CONFIGURATION_ANNOTATION_CLASS_NAME, false, false);
+ if (attributes != null) {
+ return ClassUtils.forName(DEFAULT_WEB_TEST_CONTEXT_BOOTSTRAPPER_CLASS_NAME, classLoader);
+ }
+ return ClassUtils.forName(DEFAULT_TEST_CONTEXT_BOOTSTRAPPER_CLASS_NAME, classLoader);
+ }
+
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/BootstrapWith.java b/spring-test/src/main/java/org/springframework/test/context/BootstrapWith.java
index 302ce42e..7cf742f6 100644
--- a/spring-test/src/main/java/org/springframework/test/context/BootstrapWith.java
+++ b/spring-test/src/main/java/org/springframework/test/context/BootstrapWith.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,10 +35,10 @@ import java.lang.annotation.Target;
* @see BootstrapContext
* @see TestContextBootstrapper
*/
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.TYPE)
public @interface BootstrapWith {
/**
diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java
index 9087bdbe..669adbd6 100644
--- a/spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java
+++ b/spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -83,10 +83,10 @@ import org.springframework.core.annotation.AliasFor;
* @see MergedContextConfiguration
* @see org.springframework.context.ApplicationContext
*/
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.TYPE)
public @interface ContextConfiguration {
/**
diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextConfigurationAttributes.java b/spring-test/src/main/java/org/springframework/test/context/ContextConfigurationAttributes.java
index 0e0feb47..f32731df 100644
--- a/spring-test/src/main/java/org/springframework/test/context/ContextConfigurationAttributes.java
+++ b/spring-test/src/main/java/org/springframework/test/context/ContextConfigurationAttributes.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +34,7 @@ import org.springframework.util.StringUtils;
* attributes declared via {@link ContextConfiguration @ContextConfiguration}.
*
* @author Sam Brannen
+ * @author Phillip Webb
* @since 3.1
* @see ContextConfiguration
* @see SmartContextLoader#processContextConfiguration(ContextConfigurationAttributes)
@@ -41,6 +42,11 @@ import org.springframework.util.StringUtils;
*/
public class ContextConfigurationAttributes {
+ private static final String[] EMPTY_LOCATIONS = new String[0];
+
+ private static final Class<?>[] EMPTY_CLASSES = new Class<?>[0];
+
+
private static final Log logger = LogFactory.getLog(ContextConfigurationAttributes.class);
private final Class<?> declaringClass;
@@ -61,6 +67,17 @@ public class ContextConfigurationAttributes {
/**
+ * Construct a new {@link ContextConfigurationAttributes} instance with default values.
+ * @param declaringClass the test class that declared {@code @ContextConfiguration},
+ * either explicitly or implicitly
+ * @since 4.3
+ */
+ @SuppressWarnings("unchecked")
+ public ContextConfigurationAttributes(Class<?> declaringClass) {
+ this(declaringClass, EMPTY_LOCATIONS, EMPTY_CLASSES, false, (Class[]) EMPTY_CLASSES, true, ContextLoader.class);
+ }
+
+ /**
* Construct a new {@link ContextConfigurationAttributes} instance for the
* supplied {@link ContextConfiguration @ContextConfiguration} annotation and
* the {@linkplain Class test class} that declared it.
@@ -84,8 +101,8 @@ public class ContextConfigurationAttributes {
@SuppressWarnings("unchecked")
public ContextConfigurationAttributes(Class<?> declaringClass, AnnotationAttributes annAttrs) {
this(declaringClass, annAttrs.getStringArray("locations"), annAttrs.getClassArray("classes"), annAttrs.getBoolean("inheritLocations"),
- (Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>[]) annAttrs.getClassArray("initializers"),
- annAttrs.getBoolean("inheritInitializers"), annAttrs.getString("name"), (Class<? extends ContextLoader>) annAttrs.getClass("loader"));
+ (Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>[]) annAttrs.getClassArray("initializers"),
+ annAttrs.getBoolean("inheritInitializers"), annAttrs.getString("name"), (Class<? extends ContextLoader>) annAttrs.getClass("loader"));
}
/**
@@ -139,8 +156,8 @@ public class ContextConfigurationAttributes {
if (!ObjectUtils.isEmpty(locations) && !ObjectUtils.isEmpty(classes) && logger.isDebugEnabled()) {
logger.debug(String.format(
"Test class [%s] has been configured with @ContextConfiguration's 'locations' (or 'value') %s " +
- "and 'classes' %s attributes. Most SmartContextLoader implementations support " +
- "only one declaration of resources per @ContextConfiguration annotation.",
+ "and 'classes' %s attributes. Most SmartContextLoader implementations support " +
+ "only one declaration of resources per @ContextConfiguration annotation.",
declaringClass.getName(), ObjectUtils.nullSafeToString(locations),
ObjectUtils.nullSafeToString(classes)));
}
@@ -158,7 +175,8 @@ public class ContextConfigurationAttributes {
/**
* Get the {@linkplain Class class} that declared the
- * {@link ContextConfiguration @ContextConfiguration} annotation.
+ * {@link ContextConfiguration @ContextConfiguration} annotation, either explicitly
+ * or implicitly.
* @return the declaring class (never {@code null})
*/
public Class<?> getDeclaringClass() {
diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextCustomizer.java b/spring-test/src/main/java/org/springframework/test/context/ContextCustomizer.java
new file mode 100644
index 00000000..d9462faa
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/ContextCustomizer.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context;
+
+import org.springframework.context.ConfigurableApplicationContext;
+
+/**
+ * Strategy interface for customizing {@link ConfigurableApplicationContext
+ * application contexts} that are created and managed by the <em>Spring
+ * TestContext Framework</em>.
+ *
+ * <p>Customizers are created by {@link ContextCustomizerFactory} implementations.
+ *
+ * <p>Implementations must implement correct {@code equals} and {@code hashCode}
+ * methods since customizers form part of the {@link MergedContextConfiguration}
+ * which is used as a cache key.
+ *
+ * @author Phillip Webb
+ * @author Sam Brannen
+ * @since 4.3
+ * @see ContextCustomizerFactory
+ * @see org.springframework.test.context.support.AbstractContextLoader#customizeContext
+ */
+public interface ContextCustomizer {
+
+ /**
+ * Customize the supplied {@code ConfigurableApplicationContext} <em>after</em>
+ * bean definitions have been loaded into the context but <em>before</em> the
+ * context has been refreshed.
+ * @param context the context to customize
+ * @param mergedConfig the merged context configuration
+ */
+ void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig);
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextCustomizerFactory.java b/spring-test/src/main/java/org/springframework/test/context/ContextCustomizerFactory.java
new file mode 100644
index 00000000..9f07700a
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/ContextCustomizerFactory.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context;
+
+import java.util.List;
+
+/**
+ * Factory for creating {@link ContextCustomizer ContextCustomizers}.
+ *
+ * <p>Factories are invoked after {@link ContextLoader ContextLoaders} have
+ * processed context configuration attributes but before the
+ * {@link MergedContextConfiguration} is created.
+ *
+ * <p>By default, the Spring TestContext Framework will use the
+ * {@link org.springframework.core.io.support.SpringFactoriesLoader SpringFactoriesLoader}
+ * mechanism for loading factories configured in all {@code META-INF/spring.factories}
+ * files on the classpath.
+ *
+ * @author Phillip Webb
+ * @author Sam Brannen
+ * @since 4.3
+ */
+public interface ContextCustomizerFactory {
+
+ /**
+ * Create a {@link ContextCustomizer} that should be used to customize a
+ * {@link org.springframework.context.ConfigurableApplicationContext ConfigurableApplicationContext}
+ * before it is refreshed.
+ * @param testClass the test class
+ * @param configAttributes the list of context configuration attributes for
+ * the test class, ordered <em>bottom-up</em> (i.e., as if we were traversing
+ * up the class hierarchy); never {@code null} or empty
+ * @return a {@link ContextCustomizer} or {@code null} if no customizer should
+ * be used
+ */
+ ContextCustomizer createContextCustomizer(Class<?> testClass, List<ContextConfigurationAttributes> configAttributes);
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextHierarchy.java b/spring-test/src/main/java/org/springframework/test/context/ContextHierarchy.java
index 61b2fa8c..3a3d2859 100644
--- a/spring-test/src/main/java/org/springframework/test/context/ContextHierarchy.java
+++ b/spring-test/src/main/java/org/springframework/test/context/ContextHierarchy.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -139,10 +139,10 @@ import java.lang.annotation.Target;
* @see ContextConfiguration
* @see org.springframework.context.ApplicationContext
*/
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.TYPE)
public @interface ContextHierarchy {
/**
diff --git a/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java
index 5090d7c5..45f31a18 100644
--- a/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java
+++ b/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java
@@ -55,6 +55,7 @@ import org.springframework.util.StringUtils;
* that was loaded using properties of this {@code MergedContextConfiguration}.
*
* @author Sam Brannen
+ * @author Phillip Webb
* @since 3.1
* @see ContextConfiguration
* @see ContextHierarchy
@@ -74,6 +75,8 @@ public class MergedContextConfiguration implements Serializable {
private static final Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> EMPTY_INITIALIZER_CLASSES =
Collections.<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> emptySet();
+ private static final Set<ContextCustomizer> EMPTY_CONTEXT_CUSTOMIZERS = Collections.<ContextCustomizer> emptySet();
+
private final Class<?> testClass;
@@ -89,6 +92,8 @@ public class MergedContextConfiguration implements Serializable {
private final String[] propertySourceProperties;
+ private final Set<ContextCustomizer> contextCustomizers;
+
private final ContextLoader contextLoader;
private final CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate;
@@ -111,6 +116,11 @@ public class MergedContextConfiguration implements Serializable {
Collections.unmodifiableSet(contextInitializerClasses) : EMPTY_INITIALIZER_CLASSES);
}
+ private static Set<ContextCustomizer> processContextCustomizers(Set<ContextCustomizer> contextCustomizers) {
+ return (contextCustomizers != null ?
+ Collections.unmodifiableSet(contextCustomizers) : EMPTY_CONTEXT_CUSTOMIZERS);
+ }
+
private static String[] processActiveProfiles(String[] activeProfiles) {
if (activeProfiles == null) {
return EMPTY_STRING_ARRAY;
@@ -201,8 +211,8 @@ public class MergedContextConfiguration implements Serializable {
public MergedContextConfiguration(MergedContextConfiguration mergedConfig) {
this(mergedConfig.testClass, mergedConfig.locations, mergedConfig.classes,
mergedConfig.contextInitializerClasses, mergedConfig.activeProfiles, mergedConfig.propertySourceLocations,
- mergedConfig.propertySourceProperties, mergedConfig.contextLoader,
- mergedConfig.cacheAwareContextLoaderDelegate, mergedConfig.parent);
+ mergedConfig.propertySourceProperties, mergedConfig.contextCustomizers,
+ mergedConfig.contextLoader, mergedConfig.cacheAwareContextLoaderDelegate, mergedConfig.parent);
}
/**
@@ -233,6 +243,41 @@ public class MergedContextConfiguration implements Serializable {
String[] activeProfiles, String[] propertySourceLocations, String[] propertySourceProperties,
ContextLoader contextLoader, CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate,
MergedContextConfiguration parent) {
+ this(testClass, locations, classes, contextInitializerClasses, activeProfiles,
+ propertySourceLocations, propertySourceProperties,
+ EMPTY_CONTEXT_CUSTOMIZERS, contextLoader,
+ cacheAwareContextLoaderDelegate, parent);
+ }
+
+ /**
+ * Create a new {@code MergedContextConfiguration} instance for the
+ * supplied parameters.
+ * <p>If a {@code null} value is supplied for {@code locations},
+ * {@code classes}, {@code activeProfiles}, {@code propertySourceLocations},
+ * or {@code propertySourceProperties} an empty array will be stored instead.
+ * If a {@code null} value is supplied for {@code contextInitializerClasses}
+ * or {@code contextCustomizers}, an empty set will be stored instead.
+ * Furthermore, active profiles will be sorted, and duplicate profiles
+ * will be removed.
+ * @param testClass the test class for which the configuration was merged
+ * @param locations the merged context resource locations
+ * @param classes the merged annotated classes
+ * @param contextInitializerClasses the merged context initializer classes
+ * @param activeProfiles the merged active bean definition profiles
+ * @param propertySourceLocations the merged {@code PropertySource} locations
+ * @param propertySourceProperties the merged {@code PropertySource} properties
+ * @param contextCustomizers the context customizers
+ * @param contextLoader the resolved {@code ContextLoader}
+ * @param cacheAwareContextLoaderDelegate a cache-aware context loader
+ * delegate with which to retrieve the parent context
+ * @param parent the parent configuration or {@code null} if there is no parent
+ * @since 4.3
+ */
+ public MergedContextConfiguration(Class<?> testClass, String[] locations, Class<?>[] classes,
+ Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses,
+ String[] activeProfiles, String[] propertySourceLocations, String[] propertySourceProperties,
+ Set<ContextCustomizer> contextCustomizers, ContextLoader contextLoader,
+ CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parent) {
this.testClass = testClass;
this.locations = processStrings(locations);
@@ -241,6 +286,7 @@ public class MergedContextConfiguration implements Serializable {
this.activeProfiles = processActiveProfiles(activeProfiles);
this.propertySourceLocations = processStrings(propertySourceLocations);
this.propertySourceProperties = processStrings(propertySourceProperties);
+ this.contextCustomizers = processContextCustomizers(contextCustomizers);
this.contextLoader = contextLoader;
this.cacheAwareContextLoaderDelegate = cacheAwareContextLoaderDelegate;
this.parent = parent;
@@ -349,6 +395,14 @@ public class MergedContextConfiguration implements Serializable {
}
/**
+ * Get the merged {@link ContextCustomizer ContextCustomizers} that will be applied
+ * when the application context is loaded.
+ */
+ public Set<ContextCustomizer> getContextCustomizers() {
+ return this.contextCustomizers;
+ }
+
+ /**
* Get the resolved {@link ContextLoader} for the {@linkplain #getTestClass() test class}.
*/
public ContextLoader getContextLoader() {
@@ -424,6 +478,9 @@ public class MergedContextConfiguration implements Serializable {
if (!Arrays.equals(this.propertySourceProperties, otherConfig.propertySourceProperties)) {
return false;
}
+ if (!this.contextCustomizers.equals(otherConfig.contextCustomizers)) {
+ return false;
+ }
if (this.parent == null) {
if (otherConfig.parent != null) {
@@ -454,6 +511,7 @@ public class MergedContextConfiguration implements Serializable {
result = 31 * result + Arrays.hashCode(this.activeProfiles);
result = 31 * result + Arrays.hashCode(this.propertySourceLocations);
result = 31 * result + Arrays.hashCode(this.propertySourceProperties);
+ result = 31 * result + this.contextCustomizers.hashCode();
result = 31 * result + (this.parent != null ? this.parent.hashCode() : 0);
result = 31 * result + nullSafeToString(this.contextLoader).hashCode();
return result;
@@ -466,6 +524,7 @@ public class MergedContextConfiguration implements Serializable {
* {@linkplain #getActiveProfiles() active profiles},
* {@linkplain #getPropertySourceLocations() property source locations},
* {@linkplain #getPropertySourceProperties() property source properties},
+ * {@linkplain #getContextCustomizers() context customizers},
* the name of the {@link #getContextLoader() ContextLoader}, and the
* {@linkplain #getParent() parent configuration}.
*/
@@ -479,6 +538,7 @@ public class MergedContextConfiguration implements Serializable {
.append("activeProfiles", ObjectUtils.nullSafeToString(this.activeProfiles))
.append("propertySourceLocations", ObjectUtils.nullSafeToString(this.propertySourceLocations))
.append("propertySourceProperties", ObjectUtils.nullSafeToString(this.propertySourceProperties))
+ .append("contextCustomizers", this.contextCustomizers)
.append("contextLoader", nullSafeToString(this.contextLoader))
.append("parent", this.parent)
.toString();
diff --git a/spring-test/src/main/java/org/springframework/test/context/TestContextBootstrapper.java b/spring-test/src/main/java/org/springframework/test/context/TestContextBootstrapper.java
index 2ec2b7d4..905ed70b 100644
--- a/spring-test/src/main/java/org/springframework/test/context/TestContextBootstrapper.java
+++ b/spring-test/src/main/java/org/springframework/test/context/TestContextBootstrapper.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,13 +31,14 @@ import java.util.List;
*
* <p>A custom bootstrapping strategy can be configured for a test class (or
* test class hierarchy) via {@link BootstrapWith @BootstrapWith}, either
- * directly or as a meta-annotation. See
- * {@link org.springframework.test.context.web.WebAppConfiguration @WebAppConfiguration}
- * for an example.
+ * directly or as a meta-annotation.
*
- * <p>If a bootstrapper is not explicitly configured via {@code @BootstrapWith}, the
- * {@link org.springframework.test.context.support.DefaultTestContextBootstrapper DefaultTestContextBootstrapper}
- * will be used.
+ * <p>If a bootstrapper is not explicitly configured via {@code @BootstrapWith},
+ * either the {@link org.springframework.test.context.support.DefaultTestContextBootstrapper
+ * DefaultTestContextBootstrapper} or the
+ * {@link org.springframework.test.context.web.WebTestContextBootstrapper
+ * WebTestContextBootstrapper} will be used, depending on the presence of
+ * {@link org.springframework.test.context.web.WebAppConfiguration @WebAppConfiguration}.
*
* <h3>Implementation Notes</h3>
*
diff --git a/spring-test/src/main/java/org/springframework/test/context/TestContextManager.java b/spring-test/src/main/java/org/springframework/test/context/TestContextManager.java
index 3fd77678..28979647 100644
--- a/spring-test/src/main/java/org/springframework/test/context/TestContextManager.java
+++ b/spring-test/src/main/java/org/springframework/test/context/TestContextManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +25,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
+import org.springframework.util.ReflectionUtils;
/**
* {@code TestContextManager} is the main entry point into the <em>Spring
@@ -37,18 +38,18 @@ import org.springframework.util.Assert;
*
* <ul>
* <li>{@link #beforeTestClass() before test class execution}: prior to any
- * <em>before class methods</em> of a particular testing framework (e.g., JUnit
- * 4's {@link org.junit.BeforeClass @BeforeClass})</li>
+ * <em>before class callbacks</em> of a particular testing framework (e.g.,
+ * JUnit 4's {@link org.junit.BeforeClass @BeforeClass})</li>
* <li>{@link #prepareTestInstance(Object) test instance preparation}:
* immediately following instantiation of the test instance</li>
* <li>{@link #beforeTestMethod(Object, Method) before test method execution}:
- * prior to any <em>before methods</em> of a particular testing framework (e.g.,
- * JUnit 4's {@link org.junit.Before @Before})</li>
+ * prior to any <em>before method callbacks</em> of a particular testing framework
+ * (e.g., JUnit 4's {@link org.junit.Before @Before})</li>
* <li>{@link #afterTestMethod(Object, Method, Throwable) after test method
- * execution}: after any <em>after methods</em> of a particular testing
+ * execution}: after any <em>after method callbacks</em> of a particular testing
* framework (e.g., JUnit 4's {@link org.junit.After @After})</li>
* <li>{@link #afterTestClass() after test class execution}: after any
- * <em>after class methods</em> of a particular testing framework (e.g., JUnit
+ * <em>after class callbacks</em> of a particular testing framework (e.g., JUnit
* 4's {@link org.junit.AfterClass @AfterClass})</li>
* </ul>
*
@@ -78,7 +79,6 @@ import org.springframework.util.Assert;
* @see TestExecutionListeners
* @see ContextConfiguration
* @see ContextHierarchy
- * @see org.springframework.test.context.transaction.TransactionConfiguration
*/
public class TestContextManager {
@@ -124,7 +124,7 @@ public class TestContextManager {
/**
* Get the {@link TestContext} managed by this {@code TestContextManager}.
*/
- protected final TestContext getTestContext() {
+ public final TestContext getTestContext() {
return this.testContext;
}
@@ -194,10 +194,12 @@ public class TestContextManager {
try {
testExecutionListener.beforeTestClass(getTestContext());
}
- catch (Exception ex) {
- logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener +
- "] to process 'before class' callback for test class [" + testClass + "]", ex);
- throw ex;
+ catch (Throwable ex) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener +
+ "] to process 'before class' callback for test class [" + testClass + "]", ex);
+ }
+ ReflectionUtils.rethrowException(ex);
}
}
}
@@ -227,10 +229,12 @@ public class TestContextManager {
try {
testExecutionListener.prepareTestInstance(getTestContext());
}
- catch (Exception ex) {
- logger.error("Caught exception while allowing TestExecutionListener [" + testExecutionListener +
- "] to prepare test instance [" + testInstance + "]", ex);
- throw ex;
+ catch (Throwable ex) {
+ if (logger.isErrorEnabled()) {
+ logger.error("Caught exception while allowing TestExecutionListener [" + testExecutionListener +
+ "] to prepare test instance [" + testInstance + "]", ex);
+ }
+ ReflectionUtils.rethrowException(ex);
}
}
}
@@ -264,11 +268,13 @@ public class TestContextManager {
try {
testExecutionListener.beforeTestMethod(getTestContext());
}
- catch (Exception ex) {
- logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener +
- "] to process 'before' execution of test method [" + testMethod + "] for test instance [" +
- testInstance + "]", ex);
- throw ex;
+ catch (Throwable ex) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener +
+ "] to process 'before' execution of test method [" + testMethod + "] for test instance [" +
+ testInstance + "]", ex);
+ }
+ ReflectionUtils.rethrowException(ex);
}
}
}
@@ -305,24 +311,26 @@ public class TestContextManager {
}
getTestContext().updateState(testInstance, testMethod, exception);
- Exception afterTestMethodException = null;
+ Throwable afterTestMethodException = null;
// Traverse the TestExecutionListeners in reverse order to ensure proper
// "wrapper"-style execution of listeners.
for (TestExecutionListener testExecutionListener : getReversedTestExecutionListeners()) {
try {
testExecutionListener.afterTestMethod(getTestContext());
}
- catch (Exception ex) {
- logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener +
- "] to process 'after' execution for test: method [" + testMethod + "], instance [" +
- testInstance + "], exception [" + exception + "]", ex);
+ catch (Throwable ex) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener +
+ "] to process 'after' execution for test: method [" + testMethod + "], instance [" +
+ testInstance + "], exception [" + exception + "]", ex);
+ }
if (afterTestMethodException == null) {
afterTestMethodException = ex;
}
}
}
if (afterTestMethodException != null) {
- throw afterTestMethodException;
+ ReflectionUtils.rethrowException(afterTestMethodException);
}
}
@@ -347,23 +355,25 @@ public class TestContextManager {
}
getTestContext().updateState(null, null, null);
- Exception afterTestClassException = null;
+ Throwable afterTestClassException = null;
// Traverse the TestExecutionListeners in reverse order to ensure proper
// "wrapper"-style execution of listeners.
for (TestExecutionListener testExecutionListener : getReversedTestExecutionListeners()) {
try {
testExecutionListener.afterTestClass(getTestContext());
}
- catch (Exception ex) {
- logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener +
- "] to process 'after class' callback for test class [" + testClass + "]", ex);
+ catch (Throwable ex) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener +
+ "] to process 'after class' callback for test class [" + testClass + "]", ex);
+ }
if (afterTestClassException == null) {
afterTestClassException = ex;
}
}
}
if (afterTestClassException != null) {
- throw afterTestClassException;
+ ReflectionUtils.rethrowException(afterTestClassException);
}
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/TestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/TestExecutionListener.java
index 81d61cb2..fbca021a 100644
--- a/spring-test/src/main/java/org/springframework/test/context/TestExecutionListener.java
+++ b/spring-test/src/main/java/org/springframework/test/context/TestExecutionListener.java
@@ -36,6 +36,8 @@ package org.springframework.test.context;
* <ul>
* <li>{@link org.springframework.test.context.web.ServletTestExecutionListener
* ServletTestExecutionListener}</li>
+ * <li>{@link org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener
+ * DirtiesContextBeforeModesTestExecutionListener}</li>
* <li>{@link org.springframework.test.context.support.DependencyInjectionTestExecutionListener
* DependencyInjectionTestExecutionListener}</li>
* <li>{@link org.springframework.test.context.support.DirtiesContextTestExecutionListener
diff --git a/spring-test/src/main/java/org/springframework/test/context/TestExecutionListeners.java b/spring-test/src/main/java/org/springframework/test/context/TestExecutionListeners.java
index d99c48d7..d1db5c75 100644
--- a/spring-test/src/main/java/org/springframework/test/context/TestExecutionListeners.java
+++ b/spring-test/src/main/java/org/springframework/test/context/TestExecutionListeners.java
@@ -42,10 +42,10 @@ import org.springframework.core.annotation.AliasFor;
* @see TestContextManager
* @see ContextConfiguration
*/
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.TYPE)
public @interface TestExecutionListeners {
/**
diff --git a/spring-test/src/main/java/org/springframework/test/context/TestPropertySource.java b/spring-test/src/main/java/org/springframework/test/context/TestPropertySource.java
index ec2528af..6937870b 100644
--- a/spring-test/src/main/java/org/springframework/test/context/TestPropertySource.java
+++ b/spring-test/src/main/java/org/springframework/test/context/TestPropertySource.java
@@ -82,10 +82,10 @@ import org.springframework.core.annotation.AliasFor;
* @see org.springframework.core.env.PropertySource
* @see org.springframework.context.annotation.PropertySource
*/
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.TYPE)
public @interface TestPropertySource {
/**
diff --git a/spring-test/src/main/java/org/springframework/test/context/cache/ContextCache.java b/spring-test/src/main/java/org/springframework/test/context/cache/ContextCache.java
index 27902a6d..1f15c763 100644
--- a/spring-test/src/main/java/org/springframework/test/context/cache/ContextCache.java
+++ b/spring-test/src/main/java/org/springframework/test/context/cache/ContextCache.java
@@ -26,7 +26,9 @@ import org.springframework.test.context.MergedContextConfiguration;
* <em>Spring TestContext Framework</em>.
*
* <p>A {@code ContextCache} maintains a cache of {@code ApplicationContexts}
- * keyed by {@link MergedContextConfiguration} instances.
+ * keyed by {@link MergedContextConfiguration} instances, potentially configured
+ * with a {@linkplain ContextCacheUtils#retrieveMaxCacheSize maximum size} and
+ * a custom eviction policy.
*
* <h3>Rationale</h3>
* <p>Context caching can have significant performance benefits if context
@@ -40,6 +42,7 @@ import org.springframework.test.context.MergedContextConfiguration;
* @author Sam Brannen
* @author Juergen Hoeller
* @since 4.2
+ * @see ContextCacheUtils#retrieveMaxCacheSize()
*/
public interface ContextCache {
@@ -49,6 +52,25 @@ public interface ContextCache {
*/
String CONTEXT_CACHE_LOGGING_CATEGORY = "org.springframework.test.context.cache";
+ /**
+ * The default maximum size of the context cache: {@value #DEFAULT_MAX_CONTEXT_CACHE_SIZE}.
+ * @since 4.3
+ * @see #MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME
+ */
+ int DEFAULT_MAX_CONTEXT_CACHE_SIZE = 32;
+
+ /**
+ * System property used to configure the maximum size of the {@link ContextCache}
+ * as a positive integer. May alternatively be configured via the
+ * {@link org.springframework.core.SpringProperties} mechanism.
+ * <p>Note that implementations of {@code ContextCache} are not required to
+ * actually support a maximum cache size. Consult the documentation of the
+ * corresponding implementation for details.
+ * @since 4.3
+ * @see #DEFAULT_MAX_CONTEXT_CACHE_SIZE
+ */
+ String MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME = "spring.test.context.cache.maxSize";
+
/**
* Determine whether there is a cached context for the given key.
diff --git a/spring-test/src/main/java/org/springframework/test/context/cache/ContextCacheUtils.java b/spring-test/src/main/java/org/springframework/test/context/cache/ContextCacheUtils.java
new file mode 100644
index 00000000..66427cee
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/cache/ContextCacheUtils.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.cache;
+
+import org.springframework.core.SpringProperties;
+import org.springframework.util.StringUtils;
+
+/**
+ * Collection of utilities for working with {@link ContextCache ContextCaches}.
+ *
+ * @author Sam Brannen
+ * @since 4.3
+ */
+public abstract class ContextCacheUtils {
+
+ /**
+ * Retrieve the maximum size of the {@link ContextCache}.
+ * <p>Uses {@link SpringProperties} to retrieve a system property or Spring
+ * property named {@code spring.test.context.cache.maxSize}.
+ * <p>Falls back to the value of the {@link ContextCache#DEFAULT_MAX_CONTEXT_CACHE_SIZE}
+ * if no such property has been set or if the property is not an integer.
+ * @return the maximum size of the context cache
+ * @see ContextCache#MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME
+ */
+ public static int retrieveMaxCacheSize() {
+ try {
+ String maxSize = SpringProperties.getProperty(ContextCache.MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME);
+ if (StringUtils.hasText(maxSize)) {
+ return Integer.parseInt(maxSize.trim());
+ }
+ }
+ catch (Exception ex) {
+ // ignore
+ }
+
+ // Fallback
+ return ContextCache.DEFAULT_MAX_CONTEXT_CACHE_SIZE;
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/cache/DefaultContextCache.java b/spring-test/src/main/java/org/springframework/test/context/cache/DefaultContextCache.java
index 09678135..d4aaa441 100644
--- a/spring-test/src/main/java/org/springframework/test/context/cache/DefaultContextCache.java
+++ b/spring-test/src/main/java/org/springframework/test/context/cache/DefaultContextCache.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,9 @@
package org.springframework.test.context.cache;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -37,12 +39,18 @@ import org.springframework.util.Assert;
/**
* Default implementation of the {@link ContextCache} API.
*
- * <p>Uses {@link ConcurrentHashMap ConcurrentHashMaps} to cache
- * {@link ApplicationContext} and {@link MergedContextConfiguration} instances.
+ * <p>Uses a synchronized {@link Map} configured with a maximum size
+ * and a <em>least recently used</em> (LRU) eviction policy to cache
+ * {@link ApplicationContext} instances.
+ *
+ * <p>The maximum size may be supplied as a {@linkplain #DefaultContextCache(int)
+ * constructor argument} or set via a system property or Spring property named
+ * {@code spring.test.context.cache.maxSize}.
*
* @author Sam Brannen
* @author Juergen Hoeller
* @since 2.5
+ * @see ContextCacheUtils#retrieveMaxCacheSize()
*/
public class DefaultContextCache implements ContextCache {
@@ -52,7 +60,7 @@ public class DefaultContextCache implements ContextCache {
* Map of context keys to Spring {@code ApplicationContext} instances.
*/
private final Map<MergedContextConfiguration, ApplicationContext> contextMap =
- new ConcurrentHashMap<MergedContextConfiguration, ApplicationContext>(64);
+ Collections.synchronizedMap(new LruCache(32, 0.75f));
/**
* Map of parent keys to sets of children keys, representing a top-down <em>tree</em>
@@ -61,7 +69,9 @@ public class DefaultContextCache implements ContextCache {
* of other contexts.
*/
private final Map<MergedContextConfiguration, Set<MergedContextConfiguration>> hierarchyMap =
- new ConcurrentHashMap<MergedContextConfiguration, Set<MergedContextConfiguration>>(64);
+ new ConcurrentHashMap<MergedContextConfiguration, Set<MergedContextConfiguration>>(32);
+
+ private final int maxSize;
private final AtomicInteger hitCount = new AtomicInteger();
@@ -69,6 +79,32 @@ public class DefaultContextCache implements ContextCache {
/**
+ * Create a new {@code DefaultContextCache} using the maximum cache size
+ * obtained via {@link ContextCacheUtils#retrieveMaxCacheSize()}.
+ * @since 4.3
+ * @see #DefaultContextCache(int)
+ * @see ContextCacheUtils#retrieveMaxCacheSize()
+ */
+ public DefaultContextCache() {
+ this(ContextCacheUtils.retrieveMaxCacheSize());
+ }
+
+ /**
+ * Create a new {@code DefaultContextCache} using the supplied maximum
+ * cache size.
+ * @param maxSize the maximum cache size
+ * @throws IllegalArgumentException if the supplied {@code maxSize} value
+ * is not positive
+ * @since 4.3
+ * @see #DefaultContextCache()
+ */
+ public DefaultContextCache(int maxSize) {
+ Assert.isTrue(maxSize > 0, "'maxSize' must be positive");
+ this.maxSize = maxSize;
+ }
+
+
+ /**
* {@inheritDoc}
*/
@Override
@@ -182,6 +218,13 @@ public class DefaultContextCache implements ContextCache {
}
/**
+ * Get the maximum size of this cache.
+ */
+ public int getMaxSize() {
+ return this.maxSize;
+ }
+
+ /**
* {@inheritDoc}
*/
@Override
@@ -210,7 +253,7 @@ public class DefaultContextCache implements ContextCache {
*/
@Override
public void reset() {
- synchronized (contextMap) {
+ synchronized (this.contextMap) {
clear();
clearStatistics();
}
@@ -221,7 +264,7 @@ public class DefaultContextCache implements ContextCache {
*/
@Override
public void clear() {
- synchronized (contextMap) {
+ synchronized (this.contextMap) {
this.contextMap.clear();
this.hierarchyMap.clear();
}
@@ -232,7 +275,7 @@ public class DefaultContextCache implements ContextCache {
*/
@Override
public void clearStatistics() {
- synchronized (contextMap) {
+ synchronized (this.contextMap) {
this.hitCount.set(0);
this.missCount.set(0);
}
@@ -259,10 +302,44 @@ public class DefaultContextCache implements ContextCache {
public String toString() {
return new ToStringCreator(this)
.append("size", size())
+ .append("maxSize", getMaxSize())
.append("parentContextCount", getParentContextCount())
.append("hitCount", getHitCount())
.append("missCount", getMissCount())
.toString();
}
+
+ /**
+ * Simple cache implementation based on {@link LinkedHashMap} with a maximum
+ * size and a <em>least recently used</em> (LRU) eviction policy that
+ * properly closes application contexts.
+ * @since 4.3
+ */
+ @SuppressWarnings("serial")
+ private class LruCache extends LinkedHashMap<MergedContextConfiguration, ApplicationContext> {
+
+ /**
+ * Create a new {@code LruCache} with the supplied initial capacity
+ * and load factor.
+ * @param initialCapacity the initial capacity
+ * @param loadFactor the load factor
+ */
+ LruCache(int initialCapacity, float loadFactor) {
+ super(initialCapacity, loadFactor, true);
+ }
+
+ @Override
+ protected boolean removeEldestEntry(Map.Entry<MergedContextConfiguration, ApplicationContext> eldest) {
+ if (this.size() > DefaultContextCache.this.getMaxSize()) {
+ // Do NOT delete "DefaultContextCache.this."; otherwise, we accidentally
+ // invoke java.util.Map.remove(Object, Object).
+ DefaultContextCache.this.remove(eldest.getKey(), HierarchyMode.CURRENT_LEVEL);
+ }
+
+ // Return false since we invoke a custom eviction algorithm.
+ return false;
+ }
+ }
+
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java b/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java
index 65fe0a83..5794dde3 100644
--- a/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java
+++ b/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java
@@ -50,9 +50,7 @@ import org.springframework.core.annotation.AliasFor;
* multiple instances of {@code @Sql}.
*
* <p>This annotation may be used as a <em>meta-annotation</em> to create custom
- * <em>composed annotations</em>; however, attribute overrides are not currently
- * supported for {@linkplain Repeatable repeatable} annotations that are used as
- * meta-annotations.
+ * <em>composed annotations</em> with attribute overrides.
*
* @author Sam Brannen
* @since 4.1
diff --git a/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java
index 71e0e1f6..c58867eb 100644
--- a/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java
+++ b/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,7 +26,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
-import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
@@ -129,10 +129,10 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen
private void executeSqlScripts(TestContext testContext, ExecutionPhase executionPhase) throws Exception {
boolean classLevel = false;
- Set<Sql> sqlAnnotations = AnnotationUtils.getRepeatableAnnotations(testContext.getTestMethod(), Sql.class,
+ Set<Sql> sqlAnnotations = AnnotatedElementUtils.getMergedRepeatableAnnotations(testContext.getTestMethod(), Sql.class,
SqlGroup.class);
if (sqlAnnotations.isEmpty()) {
- sqlAnnotations = AnnotationUtils.getRepeatableAnnotations(testContext.getTestClass(), Sql.class,
+ sqlAnnotations = AnnotatedElementUtils.getMergedRepeatableAnnotations(testContext.getTestClass(), Sql.class,
SqlGroup.class);
if (!sqlAnnotations.isEmpty()) {
classLevel = true;
@@ -157,7 +157,6 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen
* @param classLevel {@code true} if {@link Sql @Sql} was declared at the
* class level
*/
- @SuppressWarnings("serial")
private void executeSqlScripts(Sql sql, ExecutionPhase executionPhase, TestContext testContext, boolean classLevel)
throws Exception {
if (executionPhase != sql.executionPhase()) {
diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/AbstractJUnit4SpringContextTests.java b/spring-test/src/main/java/org/springframework/test/context/junit4/AbstractJUnit4SpringContextTests.java
index d16b05f8..474aef2c 100644
--- a/spring-test/src/main/java/org/springframework/test/context/junit4/AbstractJUnit4SpringContextTests.java
+++ b/spring-test/src/main/java/org/springframework/test/context/junit4/AbstractJUnit4SpringContextTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -61,16 +61,16 @@ import org.springframework.test.context.web.ServletTestExecutionListener;
* <ul>
* <li>If you do not wish for your test classes to be tied to a Spring-specific
* class hierarchy, you may configure your own custom test classes by using
- * {@link SpringJUnit4ClassRunner}, {@link ContextConfiguration @ContextConfiguration},
+ * {@link SpringRunner}, {@link ContextConfiguration @ContextConfiguration},
* {@link TestExecutionListeners @TestExecutionListeners}, etc.</li>
* <li>If you wish to extend this class and use a runner other than the
- * {@link SpringJUnit4ClassRunner}, as of Spring Framework 4.2 you can use
+ * {@link SpringRunner}, as of Spring Framework 4.2 you can use
* {@link org.springframework.test.context.junit4.rules.SpringClassRule SpringClassRule} and
* {@link org.springframework.test.context.junit4.rules.SpringMethodRule SpringMethodRule}
* and specify your runner of choice via {@link RunWith @RunWith(...)}.</li>
* </ul>
*
- * <p><strong>NOTE:</strong> As of Spring Framework 4.1, this class requires JUnit 4.9 or higher.
+ * <p><strong>NOTE:</strong> As of Spring Framework 4.3, this class requires JUnit 4.12 or higher.
*
* @author Sam Brannen
* @since 2.5
@@ -85,7 +85,7 @@ import org.springframework.test.context.web.ServletTestExecutionListener;
* @see AbstractTransactionalJUnit4SpringContextTests
* @see org.springframework.test.context.testng.AbstractTestNGSpringContextTests
*/
-@RunWith(SpringJUnit4ClassRunner.class)
+@RunWith(SpringRunner.class)
@TestExecutionListeners({ ServletTestExecutionListener.class, DirtiesContextBeforeModesTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class })
public abstract class AbstractJUnit4SpringContextTests implements ApplicationContextAware {
diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/AbstractTransactionalJUnit4SpringContextTests.java b/spring-test/src/main/java/org/springframework/test/context/junit4/AbstractTransactionalJUnit4SpringContextTests.java
index 9745e597..b6c533de 100644
--- a/spring-test/src/main/java/org/springframework/test/context/junit4/AbstractTransactionalJUnit4SpringContextTests.java
+++ b/spring-test/src/main/java/org/springframework/test/context/junit4/AbstractTransactionalJUnit4SpringContextTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -64,16 +64,16 @@ import org.springframework.transaction.annotation.Transactional;
* <ul>
* <li>If you do not wish for your test classes to be tied to a Spring-specific
* class hierarchy, you may configure your own custom test classes by using
- * {@link SpringJUnit4ClassRunner}, {@link ContextConfiguration @ContextConfiguration},
+ * {@link SpringRunner}, {@link ContextConfiguration @ContextConfiguration},
* {@link TestExecutionListeners @TestExecutionListeners}, etc.</li>
* <li>If you wish to extend this class and use a runner other than the
- * {@link SpringJUnit4ClassRunner}, as of Spring Framework 4.2 you can use
+ * {@link SpringRunner}, as of Spring Framework 4.2 you can use
* {@link org.springframework.test.context.junit4.rules.SpringClassRule SpringClassRule} and
* {@link org.springframework.test.context.junit4.rules.SpringMethodRule SpringMethodRule}
* and specify your runner of choice via {@link org.junit.runner.RunWith @RunWith(...)}.</li>
* </ul>
*
- * <p><strong>NOTE:</strong> As of Spring Framework 4.1, this class requires JUnit 4.9 or higher.
+ * <p><strong>NOTE:</strong> As of Spring Framework 4.3, this class requires JUnit 4.12 or higher.
*
* @author Sam Brannen
* @author Juergen Hoeller
@@ -83,7 +83,6 @@ import org.springframework.transaction.annotation.Transactional;
* @see org.springframework.test.context.TestExecutionListeners
* @see org.springframework.test.context.transaction.TransactionalTestExecutionListener
* @see org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener
- * @see org.springframework.test.context.transaction.TransactionConfiguration
* @see org.springframework.transaction.annotation.Transactional
* @see org.springframework.test.annotation.Commit
* @see org.springframework.test.annotation.Rollback
diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.java b/spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.java
index 1d6a11f9..a0ed3db9 100644
--- a/spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.java
+++ b/spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.test.context.junit4;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
+import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -55,6 +56,9 @@ import org.springframework.util.ReflectionUtils;
* <em>Spring TestContext Framework</em> to standard JUnit tests by means of the
* {@link TestContextManager} and associated support classes and annotations.
*
+ * <p>To use this class, simply annotate a JUnit 4 based test class with
+ * {@code @RunWith(SpringJUnit4ClassRunner.class)} or {@code @RunWith(SpringRunner.class)}.
+ *
* <p>The following list constitutes all annotations currently supported directly
* or indirectly by {@code SpringJUnit4ClassRunner}. <em>(Note that additional
* annotations may be supported by various
@@ -75,11 +79,12 @@ import org.springframework.util.ReflectionUtils;
* <p>If you would like to use the Spring TestContext Framework with a runner
* other than this one, use {@link SpringClassRule} and {@link SpringMethodRule}.
*
- * <p><strong>NOTE:</strong> As of Spring Framework 4.1, this class requires JUnit 4.9 or higher.
+ * <p><strong>NOTE:</strong> As of Spring Framework 4.3, this class requires JUnit 4.12 or higher.
*
* @author Sam Brannen
* @author Juergen Hoeller
* @since 2.5
+ * @see SpringRunner
* @see TestContextManager
* @see AbstractJUnit4SpringContextTests
* @see AbstractTransactionalJUnit4SpringContextTests
@@ -92,27 +97,20 @@ public class SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner {
private static final Method withRulesMethod;
- // Used by RunAfterTestClassCallbacks and RunAfterTestMethodCallbacks
- private static final String MULTIPLE_FAILURE_EXCEPTION_CLASS_NAME = "org.junit.runners.model.MultipleFailureException";
-
static {
- boolean junit4dot9Present = ClassUtils.isPresent(MULTIPLE_FAILURE_EXCEPTION_CLASS_NAME,
- SpringJUnit4ClassRunner.class.getClassLoader());
- if (!junit4dot9Present) {
- throw new IllegalStateException(String.format(
- "Failed to find class [%s]: SpringJUnit4ClassRunner requires JUnit 4.9 or higher.",
- MULTIPLE_FAILURE_EXCEPTION_CLASS_NAME));
+ if (!ClassUtils.isPresent("org.junit.internal.Throwables", SpringJUnit4ClassRunner.class.getClassLoader())) {
+ throw new IllegalStateException("SpringJUnit4ClassRunner requires JUnit 4.12 or higher.");
}
withRulesMethod = ReflectionUtils.findMethod(SpringJUnit4ClassRunner.class, "withRules",
FrameworkMethod.class, Object.class, Statement.class);
if (withRulesMethod == null) {
- throw new IllegalStateException(
- "Failed to find withRules() method: SpringJUnit4ClassRunner requires JUnit 4.9 or higher.");
+ throw new IllegalStateException("SpringJUnit4ClassRunner requires JUnit 4.12 or higher.");
}
ReflectionUtils.makeAccessible(withRulesMethod);
}
+
private final TestContextManager testContextManager;
@@ -376,8 +374,7 @@ public class SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner {
statement = new SpringFailOnTimeout(next, springTimeout);
}
else if (junitTimeout > 0) {
- // TODO Use FailOnTimeout.builder() once JUnit 4.12 is the minimum supported version.
- statement = new FailOnTimeout(next, junitTimeout);
+ statement = FailOnTimeout.builder().withTimeout(junitTimeout, TimeUnit.MILLISECONDS).build(next);
}
else {
statement = next;
diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/SpringRunner.java b/spring-test/src/main/java/org/springframework/test/context/junit4/SpringRunner.java
new file mode 100644
index 00000000..37dad335
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/junit4/SpringRunner.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4;
+
+import org.junit.runners.model.InitializationError;
+
+/**
+ * {@code SpringRunner} is an <em>alias</em> for the {@link SpringJUnit4ClassRunner}.
+ *
+ * <p>To use this class, simply annotate a JUnit 4 based test class with
+ * {@code @RunWith(SpringRunner.class)}.
+ *
+ * <p>If you would like to use the Spring TestContext Framework with a runner other than
+ * this one, use {@link org.springframework.test.context.junit4.rules.SpringClassRule}
+ * and {@link org.springframework.test.context.junit4.rules.SpringMethodRule}.
+ *
+ * <p><strong>NOTE:</strong> This class requires JUnit 4.12 or higher.
+ *
+ * @author Sam Brannen
+ * @since 4.3
+ * @see SpringJUnit4ClassRunner
+ * @see org.springframework.test.context.junit4.rules.SpringClassRule
+ * @see org.springframework.test.context.junit4.rules.SpringMethodRule
+ */
+public final class SpringRunner extends SpringJUnit4ClassRunner {
+
+ /**
+ * Construct a new {@code SpringRunner} and initialize a
+ * {@link org.springframework.test.context.TestContextManager TestContextManager}
+ * to provide Spring testing functionality to standard JUnit 4 tests.
+ * @param clazz the test class to be run
+ * @see #createTestContextManager(Class)
+ */
+ public SpringRunner(Class<?> clazz) throws InitializationError {
+ super(clazz);
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/package-info.java b/spring-test/src/main/java/org/springframework/test/context/junit4/package-info.java
index d322cc36..0a93b268 100644
--- a/spring-test/src/main/java/org/springframework/test/context/junit4/package-info.java
+++ b/spring-test/src/main/java/org/springframework/test/context/junit4/package-info.java
@@ -1,4 +1,5 @@
/**
- * Support classes for integrating the <em>Spring TestContext Framework</em> with JUnit.
+ * Support classes for integrating the <em>Spring TestContext Framework</em>
+ * with JUnit 4.12 or higher.
*/
package org.springframework.test.context.junit4;
diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/rules/SpringClassRule.java b/spring-test/src/main/java/org/springframework/test/context/junit4/rules/SpringClassRule.java
index 904a3643..cbbe7437 100644
--- a/spring-test/src/main/java/org/springframework/test/context/junit4/rules/SpringClassRule.java
+++ b/spring-test/src/main/java/org/springframework/test/context/junit4/rules/SpringClassRule.java
@@ -23,6 +23,7 @@ import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+
import org.junit.Rule;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
@@ -76,7 +77,7 @@ import org.springframework.util.ClassUtils;
* <li>{@link org.springframework.test.annotation.IfProfileValue @IfProfileValue}</li>
* </ul>
*
- * <p><strong>NOTE:</strong> This class requires JUnit 4.9 or higher.
+ * <p><strong>NOTE:</strong> As of Spring Framework 4.3, this class requires JUnit 4.12 or higher.
*
* @author Sam Brannen
* @author Philippe Marschall
@@ -96,16 +97,9 @@ public class SpringClassRule implements TestRule {
private static final Map<Class<?>, TestContextManager> testContextManagerCache =
new ConcurrentHashMap<Class<?>, TestContextManager>(64);
- // Used by RunAfterTestClassCallbacks
- private static final String MULTIPLE_FAILURE_EXCEPTION_CLASS_NAME = "org.junit.runners.model.MultipleFailureException";
-
static {
- boolean junit4dot9Present = ClassUtils.isPresent(MULTIPLE_FAILURE_EXCEPTION_CLASS_NAME,
- SpringClassRule.class.getClassLoader());
- if (!junit4dot9Present) {
- throw new IllegalStateException(String.format(
- "Failed to find class [%s]: SpringClassRule requires JUnit 4.9 or higher.",
- MULTIPLE_FAILURE_EXCEPTION_CLASS_NAME));
+ if (!ClassUtils.isPresent("org.junit.internal.Throwables", SpringClassRule.class.getClassLoader())) {
+ throw new IllegalStateException("SpringClassRule requires JUnit 4.12 or higher.");
}
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/rules/SpringMethodRule.java b/spring-test/src/main/java/org/springframework/test/context/junit4/rules/SpringMethodRule.java
index 7bc200a0..89a97418 100644
--- a/spring-test/src/main/java/org/springframework/test/context/junit4/rules/SpringMethodRule.java
+++ b/spring-test/src/main/java/org/springframework/test/context/junit4/rules/SpringMethodRule.java
@@ -20,6 +20,7 @@ import java.lang.reflect.Field;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+
import org.junit.ClassRule;
import org.junit.rules.MethodRule;
import org.junit.runners.model.FrameworkMethod;
@@ -79,7 +80,7 @@ import org.springframework.util.ReflectionUtils;
* <li>{@link org.springframework.test.annotation.IfProfileValue @IfProfileValue}</li>
* </ul>
*
- * <p><strong>NOTE:</strong> This class requires JUnit 4.9 or higher.
+ * <p><strong>NOTE:</strong> As of Spring Framework 4.3, this class requires JUnit 4.12 or higher.
*
* @author Sam Brannen
* @author Philippe Marschall
@@ -93,16 +94,9 @@ public class SpringMethodRule implements MethodRule {
private static final Log logger = LogFactory.getLog(SpringMethodRule.class);
- // Used by RunAfterTestMethodCallbacks
- private static final String MULTIPLE_FAILURE_EXCEPTION_CLASS_NAME = "org.junit.runners.model.MultipleFailureException";
-
static {
- boolean junit4dot9Present = ClassUtils.isPresent(MULTIPLE_FAILURE_EXCEPTION_CLASS_NAME,
- SpringMethodRule.class.getClassLoader());
- if (!junit4dot9Present) {
- throw new IllegalStateException(String.format(
- "Failed to find class [%s]: SpringMethodRule requires JUnit 4.9 or higher.",
- MULTIPLE_FAILURE_EXCEPTION_CLASS_NAME));
+ if (!ClassUtils.isPresent("org.junit.internal.Throwables", SpringMethodRule.class.getClassLoader())) {
+ throw new IllegalStateException("SpringMethodRule requires JUnit 4.12 or higher.");
}
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/statements/ProfileValueChecker.java b/spring-test/src/main/java/org/springframework/test/context/junit4/statements/ProfileValueChecker.java
index 144c4154..5d53bbff 100644
--- a/spring-test/src/main/java/org/springframework/test/context/junit4/statements/ProfileValueChecker.java
+++ b/spring-test/src/main/java/org/springframework/test/context/junit4/statements/ProfileValueChecker.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,10 +19,10 @@ package org.springframework.test.context.junit4.statements;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
-import org.junit.Assume;
+import org.junit.AssumptionViolatedException;
import org.junit.runners.model.Statement;
-import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.test.annotation.IfProfileValue;
import org.springframework.test.annotation.ProfileValueUtils;
import org.springframework.util.Assert;
@@ -64,6 +64,7 @@ public class ProfileValueChecker extends Statement {
this.testMethod = testMethod;
}
+
/**
* Determine if the test specified by arguments to the
* {@linkplain #ProfileValueChecker constructor} is <em>enabled</em> in
@@ -76,27 +77,24 @@ public class ProfileValueChecker extends Statement {
* will simply evaluate the next {@link Statement} in the execution chain.
* @see ProfileValueUtils#isTestEnabledInThisEnvironment(Class)
* @see ProfileValueUtils#isTestEnabledInThisEnvironment(Method, Class)
- * @see org.junit.Assume
+ * @throws AssumptionViolatedException if the test is disabled
+ * @throws Throwable if evaluation of the next statement fails
*/
@Override
public void evaluate() throws Throwable {
if (this.testMethod == null) {
if (!ProfileValueUtils.isTestEnabledInThisEnvironment(this.testClass)) {
- // Invoke assumeTrue() with false to avoid direct reference to JUnit's
- // AssumptionViolatedException which exists in two packages as of JUnit 4.12.
- Annotation ann = AnnotationUtils.findAnnotation(this.testClass, IfProfileValue.class);
- Assume.assumeTrue(String.format(
+ Annotation ann = AnnotatedElementUtils.findMergedAnnotation(this.testClass, IfProfileValue.class);
+ throw new AssumptionViolatedException(String.format(
"Profile configured via [%s] is not enabled in this environment for test class [%s].",
- ann, this.testClass.getName()), false);
+ ann, this.testClass.getName()));
}
}
else {
if (!ProfileValueUtils.isTestEnabledInThisEnvironment(this.testMethod, this.testClass)) {
- // Invoke assumeTrue() with false to avoid direct reference to JUnit's
- // AssumptionViolatedException which exists in two packages as of JUnit 4.12.
- Assume.assumeTrue(String.format(
+ throw new AssumptionViolatedException(String.format(
"Profile configured via @IfProfileValue is not enabled in this environment for test method [%s].",
- this.testMethod), false);
+ this.testMethod));
}
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunAfterTestClassCallbacks.java b/spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunAfterTestClassCallbacks.java
index e7946dd6..bf05536a 100644
--- a/spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunAfterTestClassCallbacks.java
+++ b/spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunAfterTestClassCallbacks.java
@@ -80,13 +80,7 @@ public class RunAfterTestClassCallbacks extends Statement {
errors.add(ex);
}
- if (errors.isEmpty()) {
- return;
- }
- if (errors.size() == 1) {
- throw errors.get(0);
- }
- throw new MultipleFailureException(errors);
+ MultipleFailureException.assertEmpty(errors);
}
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunAfterTestMethodCallbacks.java b/spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunAfterTestMethodCallbacks.java
index 1a1aa877..e0771264 100644
--- a/spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunAfterTestMethodCallbacks.java
+++ b/spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunAfterTestMethodCallbacks.java
@@ -97,13 +97,7 @@ public class RunAfterTestMethodCallbacks extends Statement {
errors.add(ex);
}
- if (errors.isEmpty()) {
- return;
- }
- if (errors.size() == 1) {
- throw errors.get(0);
- }
- throw new MultipleFailureException(errors);
+ MultipleFailureException.assertEmpty(errors);
}
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java
index 6994c798..f17782f8 100644
--- a/spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java
+++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java
@@ -33,6 +33,7 @@ import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.test.context.ContextConfigurationAttributes;
+import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextLoader;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.SmartContextLoader;
@@ -56,10 +57,13 @@ import org.springframework.util.ResourceUtils;
*
* @author Sam Brannen
* @author Juergen Hoeller
+ * @author Phillip Webb
* @since 2.5
* @see #generateDefaultLocations
* @see #getResourceSuffixes
* @see #modifyLocations
+ * @see #prepareContext
+ * @see #customizeContext
*/
public abstract class AbstractContextLoader implements SmartContextLoader {
@@ -110,12 +114,13 @@ public abstract class AbstractContextLoader implements SmartContextLoader {
* <li>Determines what (if any) context initializer classes have been supplied
* via the {@code MergedContextConfiguration} and instantiates and
* {@linkplain ApplicationContextInitializer#initialize invokes} each with the
- * given application context.</li>
+ * given application context.
* <ul>
* <li>Any {@code ApplicationContextInitializers} implementing
* {@link org.springframework.core.Ordered Ordered} or annotated with {@link
* org.springframework.core.annotation.Order @Order} will be sorted appropriately.</li>
* </ul>
+ * </li>
* </ul>
* @param context the newly created application context
* @param mergedConfig the merged context configuration
@@ -166,6 +171,23 @@ public abstract class AbstractContextLoader implements SmartContextLoader {
}
}
+ /**
+ * Customize the {@link ConfigurableApplicationContext} created by this
+ * {@code ContextLoader} <em>after</em> bean definitions have been loaded
+ * into the context but <em>before</em> the context has been refreshed.
+ * <p>The default implementation delegates to all
+ * {@link MergedContextConfiguration#getContextCustomizers context customizers}
+ * that have been registered with the supplied {@code mergedConfig}.
+ * @param context the newly created application context
+ * @param mergedConfig the merged context configuration
+ * @since 4.3
+ */
+ protected void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
+ for (ContextCustomizer contextCustomizer : mergedConfig.getContextCustomizers()) {
+ contextCustomizer.customizeContext(context, mergedConfig);
+ }
+ }
+
// --- ContextLoader -------------------------------------------------------
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractDelegatingSmartContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractDelegatingSmartContextLoader.java
index ca60c8dd..c0ae608e 100644
--- a/spring-test/src/main/java/org/springframework/test/context/support/AbstractDelegatingSmartContextLoader.java
+++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractDelegatingSmartContextLoader.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,7 +29,6 @@ import org.springframework.test.context.ContextLoader;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.SmartContextLoader;
import org.springframework.util.Assert;
-import org.springframework.util.ObjectUtils;
/**
* {@code AbstractDelegatingSmartContextLoader} serves as an abstract base class
@@ -202,15 +201,6 @@ public abstract class AbstractDelegatingSmartContextLoader implements SmartConte
name(getAnnotationConfigLoader()), configAttributes));
}
- // If neither loader detected defaults and no initializers were declared,
- // throw an exception.
- if (!configAttributes.hasResources() && ObjectUtils.isEmpty(configAttributes.getInitializers())) {
- throw new IllegalStateException(String.format(
- "Neither %s nor %s was able to detect defaults, and no ApplicationContextInitializers "
- + "were declared for context configuration %s", name(getXmlLoader()),
- name(getAnnotationConfigLoader()), configAttributes));
- }
-
if (configAttributes.hasLocations() && configAttributes.hasClasses()) {
String message = String.format(
"Configuration error: both default locations AND default configuration classes "
@@ -263,8 +253,9 @@ public abstract class AbstractDelegatingSmartContextLoader implements SmartConte
}
// If neither of the candidates supports the mergedConfig based on resources but
- // ACIs were declared, then delegate to the annotation config loader.
- if (!mergedConfig.getContextInitializerClasses().isEmpty()) {
+ // ACIs or customizers were declared, then delegate to the annotation config
+ // loader.
+ if (!mergedConfig.getContextInitializerClasses().isEmpty() || !mergedConfig.getContextCustomizers().isEmpty()) {
return delegateLoading(getAnnotationConfigLoader(), mergedConfig);
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java
index 41e42fcf..4bc65d29 100644
--- a/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java
+++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java
@@ -16,7 +16,6 @@
package org.springframework.test.context.support;
-
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -53,6 +52,7 @@ import org.springframework.util.StringUtils;
*
* @author Sam Brannen
* @author Juergen Hoeller
+ * @author Phillip Webb
* @since 2.5
* @see #loadContext(MergedContextConfiguration)
* @see #loadContext(String...)
@@ -92,6 +92,8 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
* annotation configuration processors.</li>
* <li>Calls {@link #customizeContext(GenericApplicationContext)} to allow for customizing the context
* before it is refreshed.</li>
+ * <li>Calls {@link #customizeContext(ConfigurableApplicationContext, MergedContextConfiguration)} to
+ * allow for customizing the context before it is refreshed.</li>
* <li>{@link ConfigurableApplicationContext#refresh Refreshes} the
* context and registers a JVM shutdown hook for it.</li>
* </ul>
@@ -122,6 +124,7 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
loadBeanDefinitions(context, mergedConfig);
AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
customizeContext(context);
+ customizeContext(context, mergedConfig);
context.refresh();
context.registerShutdownHook();
return context;
@@ -205,6 +208,7 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
* @see GenericApplicationContext#setAllowBeanDefinitionOverriding
* @see GenericApplicationContext#setResourceLoader
* @see GenericApplicationContext#setId
+ * @see #prepareContext(ConfigurableApplicationContext, MergedContextConfiguration)
* @since 2.5
*/
protected void prepareContext(GenericApplicationContext context) {
@@ -278,6 +282,7 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
* @param context the newly created application context
* @see #loadContext(MergedContextConfiguration)
* @see #loadContext(String...)
+ * @see #customizeContext(ConfigurableApplicationContext, MergedContextConfiguration)
* @since 2.5
*/
protected void customizeContext(GenericApplicationContext context) {
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java
index 7049115c..fb9e28c0 100644
--- a/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java
+++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java
@@ -18,6 +18,7 @@ package org.springframework.test.context.support;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
@@ -30,8 +31,6 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanInstantiationException;
import org.springframework.beans.BeanUtils;
-import org.springframework.context.ApplicationContextInitializer;
-import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.io.support.SpringFactoriesLoader;
@@ -39,6 +38,8 @@ import org.springframework.test.context.BootstrapContext;
import org.springframework.test.context.CacheAwareContextLoaderDelegate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.ContextConfigurationAttributes;
+import org.springframework.test.context.ContextCustomizer;
+import org.springframework.test.context.ContextCustomizerFactory;
import org.springframework.test.context.ContextHierarchy;
import org.springframework.test.context.ContextLoader;
import org.springframework.test.context.MergedContextConfiguration;
@@ -71,6 +72,7 @@ import org.springframework.util.StringUtils;
*
* @author Sam Brannen
* @author Juergen Hoeller
+ * @author Phillip Webb
* @since 4.1
*/
public abstract class AbstractTestContextBootstrapper implements TestContextBootstrapper {
@@ -272,11 +274,7 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
if (MetaAnnotationUtils.findAnnotationDescriptorForTypes(
testClass, ContextConfiguration.class, ContextHierarchy.class) == null) {
- if (logger.isInfoEnabled()) {
- logger.info(String.format("Neither @ContextConfiguration nor @ContextHierarchy found for test class [%s]",
- testClass.getName()));
- }
- return new MergedContextConfiguration(testClass, null, null, null, null);
+ return buildDefaultMergedContextConfiguration(testClass, cacheAwareContextLoaderDelegate);
}
if (AnnotationUtils.findAnnotation(testClass, ContextHierarchy.class) != null) {
@@ -296,7 +294,7 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
Class<?> declaringClass = reversedList.get(0).getDeclaringClass();
mergedConfig = buildMergedContextConfiguration(
- declaringClass, reversedList, parentConfig, cacheAwareContextLoaderDelegate);
+ declaringClass, reversedList, parentConfig, cacheAwareContextLoaderDelegate, true);
parentConfig = mergedConfig;
}
@@ -306,8 +304,24 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
else {
return buildMergedContextConfiguration(testClass,
ContextLoaderUtils.resolveContextConfigurationAttributes(testClass),
- null, cacheAwareContextLoaderDelegate);
+ null, cacheAwareContextLoaderDelegate, true);
+ }
+ }
+
+ private MergedContextConfiguration buildDefaultMergedContextConfiguration(Class<?> testClass,
+ CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) {
+
+ List<ContextConfigurationAttributes> defaultConfigAttributesList =
+ Collections.singletonList(new ContextConfigurationAttributes(testClass));
+
+ ContextLoader contextLoader = resolveContextLoader(testClass, defaultConfigAttributesList);
+ if (logger.isInfoEnabled()) {
+ logger.info(String.format(
+ "Neither @ContextConfiguration nor @ContextHierarchy found for test class [%s], using %s",
+ testClass.getName(), contextLoader.getClass().getSimpleName()));
}
+ return buildMergedContextConfiguration(testClass, defaultConfigAttributesList, null,
+ cacheAwareContextLoaderDelegate, false);
}
/**
@@ -323,6 +337,9 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
* context in a context hierarchy, or {@code null} if there is no parent
* @param cacheAwareContextLoaderDelegate the cache-aware context loader delegate to
* be passed to the {@code MergedContextConfiguration} constructor
+ * @param requireLocationsClassesOrInitializers whether locations, classes, or
+ * initializers are required; typically {@code true} but may be set to {@code false}
+ * if the configured loader supports empty configuration
* @return the merged context configuration
* @see #resolveContextLoader
* @see ContextLoaderUtils#resolveContextConfigurationAttributes
@@ -334,48 +351,90 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
*/
private MergedContextConfiguration buildMergedContextConfiguration(Class<?> testClass,
List<ContextConfigurationAttributes> configAttributesList, MergedContextConfiguration parentConfig,
- CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) {
+ CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate,
+ boolean requireLocationsClassesOrInitializers) {
+
+ Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be null or empty");
ContextLoader contextLoader = resolveContextLoader(testClass, configAttributesList);
- List<String> locationsList = new ArrayList<String>();
- List<Class<?>> classesList = new ArrayList<Class<?>>();
+ List<String> locations = new ArrayList<String>();
+ List<Class<?>> classes = new ArrayList<Class<?>>();
+ List<Class<?>> initializers = new ArrayList<Class<?>>();
for (ContextConfigurationAttributes configAttributes : configAttributesList) {
if (logger.isTraceEnabled()) {
logger.trace(String.format("Processing locations and classes for context configuration attributes %s",
- configAttributes));
+ configAttributes));
}
if (contextLoader instanceof SmartContextLoader) {
SmartContextLoader smartContextLoader = (SmartContextLoader) contextLoader;
smartContextLoader.processContextConfiguration(configAttributes);
- locationsList.addAll(0, Arrays.asList(configAttributes.getLocations()));
- classesList.addAll(0, Arrays.asList(configAttributes.getClasses()));
+ locations.addAll(0, Arrays.asList(configAttributes.getLocations()));
+ classes.addAll(0, Arrays.asList(configAttributes.getClasses()));
}
else {
- String[] processedLocations = contextLoader.processLocations(configAttributes.getDeclaringClass(),
- configAttributes.getLocations());
- locationsList.addAll(0, Arrays.asList(processedLocations));
+ String[] processedLocations = contextLoader.processLocations(
+ configAttributes.getDeclaringClass(), configAttributes.getLocations());
+ locations.addAll(0, Arrays.asList(processedLocations));
// Legacy ContextLoaders don't know how to process classes
}
+ initializers.addAll(0, Arrays.asList(configAttributes.getInitializers()));
if (!configAttributes.isInheritLocations()) {
break;
}
}
- String[] locations = StringUtils.toStringArray(locationsList);
- Class<?>[] classes = ClassUtils.toClassArray(classesList);
- Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses = //
- ApplicationContextInitializerUtils.resolveInitializerClasses(configAttributesList);
- String[] activeProfiles = ActiveProfilesUtils.resolveActiveProfiles(testClass);
- MergedTestPropertySources mergedTestPropertySources = TestPropertySourceUtils.buildMergedTestPropertySources(testClass);
+ Set<ContextCustomizer> contextCustomizers = getContextCustomizers(testClass,
+ Collections.unmodifiableList(configAttributesList));
- MergedContextConfiguration mergedConfig = new MergedContextConfiguration(testClass, locations, classes,
- initializerClasses, activeProfiles, mergedTestPropertySources.getLocations(),
- mergedTestPropertySources.getProperties(), contextLoader, cacheAwareContextLoaderDelegate, parentConfig);
+ if (requireLocationsClassesOrInitializers &&
+ areAllEmpty(locations, classes, initializers, contextCustomizers)) {
+ throw new IllegalStateException(String.format(
+ "%s was unable to detect defaults, and no ApplicationContextInitializers " +
+ "or ContextCustomizers were declared for context configuration attributes %s",
+ contextLoader.getClass().getSimpleName(), configAttributesList));
+ }
+
+ MergedTestPropertySources mergedTestPropertySources =
+ TestPropertySourceUtils.buildMergedTestPropertySources(testClass);
+ MergedContextConfiguration mergedConfig = new MergedContextConfiguration(testClass,
+ StringUtils.toStringArray(locations),
+ ClassUtils.toClassArray(classes),
+ ApplicationContextInitializerUtils.resolveInitializerClasses(configAttributesList),
+ ActiveProfilesUtils.resolveActiveProfiles(testClass),
+ mergedTestPropertySources.getLocations(),
+ mergedTestPropertySources.getProperties(),
+ contextCustomizers, contextLoader, cacheAwareContextLoaderDelegate, parentConfig);
return processMergedContextConfiguration(mergedConfig);
}
+ private Set<ContextCustomizer> getContextCustomizers(Class<?> testClass,
+ List<ContextConfigurationAttributes> configAttributes) {
+
+ List<ContextCustomizerFactory> factories = getContextCustomizerFactories();
+ Set<ContextCustomizer> customizers = new LinkedHashSet<ContextCustomizer>(factories.size());
+ for (ContextCustomizerFactory factory : factories) {
+ ContextCustomizer customizer = factory.createContextCustomizer(testClass, configAttributes);
+ if (customizer != null) {
+ customizers.add(customizer);
+ }
+ }
+ return customizers;
+ }
+
+ /**
+ * Get the {@link ContextCustomizerFactory} instances for this bootstrapper.
+ * <p>The default implementation uses the {@link SpringFactoriesLoader} mechanism
+ * for loading factories configured in all {@code META-INF/spring.factories}
+ * files on the classpath.
+ * @since 4.3
+ * @see SpringFactoriesLoader#loadFactories
+ */
+ protected List<ContextCustomizerFactory> getContextCustomizerFactories() {
+ return SpringFactoriesLoader.loadFactories(ContextCustomizerFactory.class, getClass().getClassLoader());
+ }
+
/**
* Resolve the {@link ContextLoader} {@linkplain Class class} to use for the
* supplied list of {@link ContextConfigurationAttributes} and then instantiate
@@ -388,7 +447,7 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
* @param testClass the test class for which the {@code ContextLoader} should be
* resolved; must not be {@code null}
* @param configAttributesList the list of configuration attributes to process; must
- * not be {@code null} or <em>empty</em>; must be ordered <em>bottom-up</em>
+ * not be {@code null}; must be ordered <em>bottom-up</em>
* (i.e., as if we were traversing up the class hierarchy)
* @return the resolved {@code ContextLoader} for the supplied {@code testClass}
* (never {@code null})
@@ -399,7 +458,7 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
List<ContextConfigurationAttributes> configAttributesList) {
Assert.notNull(testClass, "Class must not be null");
- Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty");
+ Assert.notNull(configAttributesList, "ContextConfigurationAttributes list must not be null");
Class<? extends ContextLoader> contextLoaderClass = resolveExplicitContextLoaderClass(configAttributesList);
if (contextLoaderClass == null) {
@@ -428,7 +487,7 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
* step #1.</li>
* </ol>
* @param configAttributesList the list of configuration attributes to process;
- * must not be {@code null} or <em>empty</em>; must be ordered <em>bottom-up</em>
+ * must not be {@code null}; must be ordered <em>bottom-up</em>
* (i.e., as if we were traversing up the class hierarchy)
* @return the {@code ContextLoader} class to use for the supplied configuration
* attributes, or {@code null} if no explicit loader is found
@@ -438,7 +497,8 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
protected Class<? extends ContextLoader> resolveExplicitContextLoaderClass(
List<ContextConfigurationAttributes> configAttributesList) {
- Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty");
+ Assert.notNull(configAttributesList, "ContextConfigurationAttributes list must not be null");
+
for (ContextConfigurationAttributes configAttributes : configAttributesList) {
if (logger.isTraceEnabled()) {
logger.trace(String.format("Resolving ContextLoader for context configuration attributes %s",
@@ -497,4 +557,14 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
return mergedConfig;
}
+
+ private static boolean areAllEmpty(Collection<?>... collections) {
+ for (Collection<?> collection : collections) {
+ if (!collection.isEmpty()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AnnotationConfigContextLoaderUtils.java b/spring-test/src/main/java/org/springframework/test/context/support/AnnotationConfigContextLoaderUtils.java
index baa15001..968d2300 100644
--- a/spring-test/src/main/java/org/springframework/test/context/support/AnnotationConfigContextLoaderUtils.java
+++ b/spring-test/src/main/java/org/springframework/test/context/support/AnnotationConfigContextLoaderUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,7 +24,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.annotation.Configuration;
-import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.test.context.SmartContextLoader;
import org.springframework.util.Assert;
@@ -103,7 +103,7 @@ public abstract class AnnotationConfigContextLoaderUtils {
*/
private static boolean isDefaultConfigurationClassCandidate(Class<?> clazz) {
return (clazz != null && isStaticNonPrivateAndNonFinal(clazz) &&
- (AnnotationUtils.findAnnotation(clazz, Configuration.class) != null));
+ AnnotatedElementUtils.hasAnnotation(clazz, Configuration.class));
}
private static boolean isStaticNonPrivateAndNonFinal(Class<?> clazz) {
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/ContextLoaderUtils.java b/spring-test/src/main/java/org/springframework/test/context/support/ContextLoaderUtils.java
index da3383cb..49819fd5 100644
--- a/spring-test/src/main/java/org/springframework/test/context/support/ContextLoaderUtils.java
+++ b/spring-test/src/main/java/org/springframework/test/context/support/ContextLoaderUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -81,13 +81,12 @@ abstract class ContextLoaderUtils {
* (must not be {@code null})
* @return the list of lists of configuration attributes for the specified class;
* never {@code null}
- * @throws IllegalArgumentException if the supplied class is {@code null}; if
+ * @throws IllegalArgumentException if the supplied class is {@code null}; or if
* neither {@code @ContextConfiguration} nor {@code @ContextHierarchy} is
- * <em>present</em> on the supplied class; or if a test class or composed annotation
+ * <em>present</em> on the supplied class
+ * @throws IllegalStateException if a test class or composed annotation
* in the class hierarchy declares both {@code @ContextConfiguration} and
* {@code @ContextHierarchy} as top-level annotations.
- * @throws IllegalStateException if no class in the class hierarchy declares
- * {@code @ContextHierarchy}.
* @since 3.2.2
* @see #buildContextHierarchyMap(Class)
* @see #resolveContextConfigurationAttributes(Class)
@@ -95,11 +94,10 @@ abstract class ContextLoaderUtils {
@SuppressWarnings("unchecked")
static List<List<ContextConfigurationAttributes>> resolveContextHierarchyAttributes(Class<?> testClass) {
Assert.notNull(testClass, "Class must not be null");
- Assert.state(findAnnotation(testClass, ContextHierarchy.class) != null, "@ContextHierarchy must be present");
- final Class<ContextConfiguration> contextConfigType = ContextConfiguration.class;
- final Class<ContextHierarchy> contextHierarchyType = ContextHierarchy.class;
- final List<List<ContextConfigurationAttributes>> hierarchyAttributes = new ArrayList<List<ContextConfigurationAttributes>>();
+ Class<ContextConfiguration> contextConfigType = ContextConfiguration.class;
+ Class<ContextHierarchy> contextHierarchyType = ContextHierarchy.class;
+ List<List<ContextConfigurationAttributes>> hierarchyAttributes = new ArrayList<List<ContextConfigurationAttributes>>();
UntypedAnnotationDescriptor desc =
findAnnotationDescriptorForTypes(testClass, contextConfigType, contextHierarchyType);
@@ -124,7 +122,7 @@ abstract class ContextLoaderUtils {
throw new IllegalStateException(msg);
}
- final List<ContextConfigurationAttributes> configAttributesList = new ArrayList<ContextConfigurationAttributes>();
+ List<ContextConfigurationAttributes> configAttributesList = new ArrayList<ContextConfigurationAttributes>();
if (contextConfigDeclaredLocally) {
ContextConfiguration contextConfiguration = AnnotationUtils.synthesizeAnnotation(
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceUtils.java b/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceUtils.java
index 1619ea84..98e9c936 100644
--- a/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceUtils.java
+++ b/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,10 +35,11 @@ import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySources;
import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePropertySource;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.util.TestContextResourceUtils;
-import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor;
+import org.springframework.test.util.MetaAnnotationUtils.*;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
@@ -57,19 +58,15 @@ import static org.springframework.test.util.MetaAnnotationUtils.*;
*/
public abstract class TestPropertySourceUtils {
- private static final Log logger = LogFactory.getLog(TestPropertySourceUtils.class);
-
/**
* The name of the {@link MapPropertySource} created from <em>inlined properties</em>.
* @since 4.1.5
- * @see {@link #addInlinedPropertiesToEnvironment(ConfigurableEnvironment, String[])}
+ * @see #addInlinedPropertiesToEnvironment
*/
public static final String INLINED_PROPERTIES_PROPERTY_SOURCE_NAME = "Inlined Test Properties";
+ private static final Log logger = LogFactory.getLog(TestPropertySourceUtils.class);
- private TestPropertySourceUtils() {
- /* no-op */
- }
static MergedTestPropertySources buildMergedTestPropertySources(Class<?> testClass) {
Class<TestPropertySource> annotationType = TestPropertySource.class;
@@ -78,7 +75,6 @@ public abstract class TestPropertySourceUtils {
return new MergedTestPropertySources();
}
- // else...
List<TestPropertySourceAttributes> attributesList = resolveTestPropertySourceAttributes(testClass);
String[] locations = mergeLocations(attributesList);
String[] properties = mergeProperties(attributesList);
@@ -87,30 +83,27 @@ public abstract class TestPropertySourceUtils {
private static List<TestPropertySourceAttributes> resolveTestPropertySourceAttributes(Class<?> testClass) {
Assert.notNull(testClass, "Class must not be null");
+ List<TestPropertySourceAttributes> attributesList = new ArrayList<TestPropertySourceAttributes>();
+ Class<TestPropertySource> annotationType = TestPropertySource.class;
- final List<TestPropertySourceAttributes> attributesList = new ArrayList<TestPropertySourceAttributes>();
- final Class<TestPropertySource> annotationType = TestPropertySource.class;
AnnotationDescriptor<TestPropertySource> descriptor = findAnnotationDescriptor(testClass, annotationType);
Assert.notNull(descriptor, String.format(
- "Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]",
- annotationType.getName(), testClass.getName()));
+ "Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]",
+ annotationType.getName(), testClass.getName()));
while (descriptor != null) {
TestPropertySource testPropertySource = descriptor.synthesizeAnnotation();
Class<?> rootDeclaringClass = descriptor.getRootDeclaringClass();
-
if (logger.isTraceEnabled()) {
logger.trace(String.format("Retrieved @TestPropertySource [%s] for declaring class [%s].",
testPropertySource, rootDeclaringClass.getName()));
}
-
- TestPropertySourceAttributes attributes = new TestPropertySourceAttributes(rootDeclaringClass,
- testPropertySource);
+ TestPropertySourceAttributes attributes =
+ new TestPropertySourceAttributes(rootDeclaringClass, testPropertySource);
if (logger.isTraceEnabled()) {
logger.trace("Resolved TestPropertySource attributes: " + attributes);
}
attributesList.add(attributes);
-
descriptor = findAnnotationDescriptor(rootDeclaringClass.getSuperclass(), annotationType);
}
@@ -119,74 +112,90 @@ public abstract class TestPropertySourceUtils {
private static String[] mergeLocations(List<TestPropertySourceAttributes> attributesList) {
final List<String> locations = new ArrayList<String>();
-
for (TestPropertySourceAttributes attrs : attributesList) {
if (logger.isTraceEnabled()) {
logger.trace(String.format("Processing locations for TestPropertySource attributes %s", attrs));
}
-
String[] locationsArray = TestContextResourceUtils.convertToClasspathResourcePaths(
- attrs.getDeclaringClass(), attrs.getLocations());
+ attrs.getDeclaringClass(), attrs.getLocations());
locations.addAll(0, Arrays.<String> asList(locationsArray));
-
if (!attrs.isInheritLocations()) {
break;
}
}
-
return StringUtils.toStringArray(locations);
}
private static String[] mergeProperties(List<TestPropertySourceAttributes> attributesList) {
final List<String> properties = new ArrayList<String>();
-
for (TestPropertySourceAttributes attrs : attributesList) {
if (logger.isTraceEnabled()) {
logger.trace(String.format("Processing inlined properties for TestPropertySource attributes %s", attrs));
}
-
- properties.addAll(0, Arrays.<String> asList(attrs.getProperties()));
-
+ properties.addAll(0, Arrays.<String>asList(attrs.getProperties()));
if (!attrs.isInheritProperties()) {
break;
}
}
-
return StringUtils.toStringArray(properties);
}
/**
* Add the {@link Properties} files from the given resource {@code locations}
* to the {@link Environment} of the supplied {@code context}.
+ * <p>This method simply delegates to
+ * {@link #addPropertiesFilesToEnvironment(ConfigurableEnvironment, ResourceLoader, String...)}.
+ * @param context the application context whose environment should be updated;
+ * never {@code null}
+ * @param locations the resource locations of {@code Properties} files to add
+ * to the environment; potentially empty but never {@code null}
+ * @since 4.1.5
+ * @see ResourcePropertySource
+ * @see TestPropertySource#locations
+ * @see #addPropertiesFilesToEnvironment(ConfigurableEnvironment, ResourceLoader, String...)
+ * @throws IllegalStateException if an error occurs while processing a properties file
+ */
+ public static void addPropertiesFilesToEnvironment(ConfigurableApplicationContext context, String... locations) {
+ Assert.notNull(context, "'context' must not be null");
+ Assert.notNull(locations, "'locations' must not be null");
+ addPropertiesFilesToEnvironment(context.getEnvironment(), context, locations);
+ }
+
+ /**
+ * Add the {@link Properties} files from the given resource {@code locations}
+ * to the supplied {@link ConfigurableEnvironment environment}.
* <p>Property placeholders in resource locations (i.e., <code>${...}</code>)
* will be {@linkplain Environment#resolveRequiredPlaceholders(String) resolved}
* against the {@code Environment}.
* <p>Each properties file will be converted to a {@link ResourcePropertySource}
* that will be added to the {@link PropertySources} of the environment with
* highest precedence.
- * @param context the application context whose environment should be updated;
+ * @param environment the environment to update; never {@code null}
+ * @param resourceLoader the {@code ResourceLoader} to use to load each resource;
* never {@code null}
* @param locations the resource locations of {@code Properties} files to add
* to the environment; potentially empty but never {@code null}
- * @since 4.1.5
+ * @since 4.3
* @see ResourcePropertySource
* @see TestPropertySource#locations
+ * @see #addPropertiesFilesToEnvironment(ConfigurableApplicationContext, String...)
* @throws IllegalStateException if an error occurs while processing a properties file
*/
- public static void addPropertiesFilesToEnvironment(ConfigurableApplicationContext context,
- String[] locations) {
- Assert.notNull(context, "context must not be null");
- Assert.notNull(locations, "locations must not be null");
+ public static void addPropertiesFilesToEnvironment(ConfigurableEnvironment environment,
+ ResourceLoader resourceLoader, String... locations) {
+
+ Assert.notNull(environment, "'environment' must not be null");
+ Assert.notNull(resourceLoader, "'resourceLoader' must not be null");
+ Assert.notNull(locations, "'locations' must not be null");
try {
- ConfigurableEnvironment environment = context.getEnvironment();
for (String location : locations) {
String resolvedLocation = environment.resolveRequiredPlaceholders(location);
- Resource resource = context.getResource(resolvedLocation);
+ Resource resource = resourceLoader.getResource(resolvedLocation);
environment.getPropertySources().addFirst(new ResourcePropertySource(resource));
}
}
- catch (IOException e) {
- throw new IllegalStateException("Failed to add PropertySource to Environment", e);
+ catch (IOException ex) {
+ throw new IllegalStateException("Failed to add PropertySource to Environment", ex);
}
}
@@ -203,10 +212,9 @@ public abstract class TestPropertySourceUtils {
* @see TestPropertySource#properties
* @see #addInlinedPropertiesToEnvironment(ConfigurableEnvironment, String[])
*/
- public static void addInlinedPropertiesToEnvironment(ConfigurableApplicationContext context,
- String[] inlinedProperties) {
- Assert.notNull(context, "context must not be null");
- Assert.notNull(inlinedProperties, "inlinedProperties must not be null");
+ public static void addInlinedPropertiesToEnvironment(ConfigurableApplicationContext context, String... inlinedProperties) {
+ Assert.notNull(context, "'context' must not be null");
+ Assert.notNull(inlinedProperties, "'inlinedProperties' must not be null");
addInlinedPropertiesToEnvironment(context.getEnvironment(), inlinedProperties);
}
@@ -226,17 +234,22 @@ public abstract class TestPropertySourceUtils {
* @see TestPropertySource#properties
* @see #convertInlinedPropertiesToMap
*/
- public static void addInlinedPropertiesToEnvironment(ConfigurableEnvironment environment, String[] inlinedProperties) {
- Assert.notNull(environment, "environment must not be null");
- Assert.notNull(inlinedProperties, "inlinedProperties must not be null");
+ public static void addInlinedPropertiesToEnvironment(ConfigurableEnvironment environment, String... inlinedProperties) {
+ Assert.notNull(environment, "'environment' must not be null");
+ Assert.notNull(inlinedProperties, "'inlinedProperties' must not be null");
if (!ObjectUtils.isEmpty(inlinedProperties)) {
if (logger.isDebugEnabled()) {
- logger.debug("Adding inlined properties to environment: "
- + ObjectUtils.nullSafeToString(inlinedProperties));
+ logger.debug("Adding inlined properties to environment: " +
+ ObjectUtils.nullSafeToString(inlinedProperties));
+ }
+ MapPropertySource ps = (MapPropertySource)
+ environment.getPropertySources().get(INLINED_PROPERTIES_PROPERTY_SOURCE_NAME);
+ if (ps == null) {
+ ps = new MapPropertySource(INLINED_PROPERTIES_PROPERTY_SOURCE_NAME,
+ new LinkedHashMap<String, Object>());
+ environment.getPropertySources().addFirst(ps);
}
- MapPropertySource ps = new MapPropertySource(INLINED_PROPERTIES_PROPERTY_SOURCE_NAME,
- convertInlinedPropertiesToMap(inlinedProperties));
- environment.getPropertySources().addFirst(ps);
+ ps.getSource().putAll(convertInlinedPropertiesToMap(inlinedProperties));
}
}
@@ -257,24 +270,22 @@ public abstract class TestPropertySourceUtils {
* a given inlined property contains multiple key-value pairs
* @see #addInlinedPropertiesToEnvironment(ConfigurableEnvironment, String[])
*/
- public static Map<String, Object> convertInlinedPropertiesToMap(String[] inlinedProperties) {
- Assert.notNull(inlinedProperties, "inlinedProperties must not be null");
+ public static Map<String, Object> convertInlinedPropertiesToMap(String... inlinedProperties) {
+ Assert.notNull(inlinedProperties, "'inlinedProperties' must not be null");
Map<String, Object> map = new LinkedHashMap<String, Object>();
-
Properties props = new Properties();
+
for (String pair : inlinedProperties) {
if (!StringUtils.hasText(pair)) {
continue;
}
-
try {
props.load(new StringReader(pair));
}
- catch (Exception e) {
- throw new IllegalStateException("Failed to load test environment property from [" + pair + "].", e);
+ catch (Exception ex) {
+ throw new IllegalStateException("Failed to load test environment property from [" + pair + "]", ex);
}
- Assert.state(props.size() == 1, "Failed to load exactly one test environment property from [" + pair + "].");
-
+ Assert.state(props.size() == 1, "Failed to load exactly one test environment property from [" + pair + "]");
for (String name : props.stringPropertyNames()) {
map.put(name, props.getProperty(name));
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/testng/AbstractTransactionalTestNGSpringContextTests.java b/spring-test/src/main/java/org/springframework/test/context/testng/AbstractTransactionalTestNGSpringContextTests.java
index 1fd08e32..0899e13b 100644
--- a/spring-test/src/main/java/org/springframework/test/context/testng/AbstractTransactionalTestNGSpringContextTests.java
+++ b/spring-test/src/main/java/org/springframework/test/context/testng/AbstractTransactionalTestNGSpringContextTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -67,7 +67,6 @@ import org.springframework.transaction.annotation.Transactional;
* @see org.springframework.test.context.TestExecutionListeners
* @see org.springframework.test.context.transaction.TransactionalTestExecutionListener
* @see org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener
- * @see org.springframework.test.context.transaction.TransactionConfiguration
* @see org.springframework.transaction.annotation.Transactional
* @see org.springframework.test.annotation.Commit
* @see org.springframework.test.annotation.Rollback
diff --git a/spring-test/src/main/java/org/springframework/test/context/transaction/AfterTransaction.java b/spring-test/src/main/java/org/springframework/test/context/transaction/AfterTransaction.java
index 04ebe481..a7f6653e 100644
--- a/spring-test/src/main/java/org/springframework/test/context/transaction/AfterTransaction.java
+++ b/spring-test/src/main/java/org/springframework/test/context/transaction/AfterTransaction.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author 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,12 +23,16 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
- * <p>Test annotation to indicate that the annotated {@code public void} method
+ * <p>Test annotation which indicates that the annotated {@code void} method
* should be executed <em>after</em> a transaction is ended for a test method
- * configured to run within a transaction via the {@code @Transactional} annotation.
+ * configured to run within a transaction via Spring's {@code @Transactional}
+ * annotation.
*
- * <p>The {@code @AfterTransaction} methods of superclasses will be executed
- * after those of the current class.
+ * <p>As of Spring Framework 4.3, {@code @AfterTransaction} may be declared on
+ * Java 8 based interface default methods.
+ *
+ * <p>{@code @AfterTransaction} methods declared in superclasses or as interface
+ * default methods will be executed after those of the current test class.
*
* <p>As of Spring Framework 4.0, this annotation may be used as a
* <em>meta-annotation</em> to create custom <em>composed annotations</em>.
diff --git a/spring-test/src/main/java/org/springframework/test/context/transaction/BeforeTransaction.java b/spring-test/src/main/java/org/springframework/test/context/transaction/BeforeTransaction.java
index b7110015..217c7d17 100644
--- a/spring-test/src/main/java/org/springframework/test/context/transaction/BeforeTransaction.java
+++ b/spring-test/src/main/java/org/springframework/test/context/transaction/BeforeTransaction.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author 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,12 +23,16 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
- * <p>Test annotation to indicate that the annotated {@code public void} method
+ * <p>Test annotation which indicates that the annotated {@code void} method
* should be executed <em>before</em> a transaction is started for a test method
- * configured to run within a transaction via the {@code @Transactional} annotation.
+ * configured to run within a transaction via Spring's {@code @Transactional}
+ * annotation.
*
- * <p>The {@code @BeforeTransaction} methods of superclasses will be executed
- * before those of the current class.
+ * <p>As of Spring Framework 4.3, {@code @BeforeTransaction} may be declared on
+ * Java 8 based interface default methods.
+ *
+ * <p>{@code @BeforeTransaction} methods declared in superclasses or as interface
+ * default methods will be executed before those of the current test class.
*
* <p>As of Spring Framework 4.0, this annotation may be used as a
* <em>meta-annotation</em> to create custom <em>composed annotations</em>.
diff --git a/spring-test/src/main/java/org/springframework/test/context/transaction/TestContextTransactionUtils.java b/spring-test/src/main/java/org/springframework/test/context/transaction/TestContextTransactionUtils.java
index 3b65d690..b00a9955 100644
--- a/spring-test/src/main/java/org/springframework/test/context/transaction/TestContextTransactionUtils.java
+++ b/spring-test/src/main/java/org/springframework/test/context/transaction/TestContextTransactionUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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.test.context.transaction;
import java.util.Map;
+
import javax.sql.DataSource;
import org.apache.commons.logging.Log;
@@ -73,7 +74,8 @@ public abstract class TestContextTransactionUtils {
* <li>Look up the {@code DataSource} by type and name, if the supplied
* {@code name} is non-empty, throwing a {@link BeansException} if the named
* {@code DataSource} does not exist.
- * <li>Attempt to look up a single {@code DataSource} by type.
+ * <li>Attempt to look up the single {@code DataSource} by type.
+ * <li>Attempt to look up the <em>primary</em> {@code DataSource} by type.
* <li>Attempt to look up the {@code DataSource} by type and the
* {@linkplain #DEFAULT_DATA_SOURCE_NAME default data source name}.
* @param testContext the test context for which the {@code DataSource}
@@ -110,15 +112,21 @@ public abstract class TestContextTransactionUtils {
if (dataSources.size() == 1) {
return dataSources.values().iterator().next();
}
+
+ try {
+ // look up single bean by type, with support for 'primary' beans
+ return bf.getBean(DataSource.class);
+ }
+ catch (BeansException ex) {
+ logBeansException(testContext, ex, PlatformTransactionManager.class);
+ }
}
// look up by type and default name
return bf.getBean(DEFAULT_DATA_SOURCE_NAME, DataSource.class);
}
catch (BeansException ex) {
- if (logger.isDebugEnabled()) {
- logger.debug("Caught exception while retrieving DataSource for test context " + testContext, ex);
- }
+ logBeansException(testContext, ex, DataSource.class);
return null;
}
}
@@ -133,7 +141,8 @@ public abstract class TestContextTransactionUtils {
* <li>Look up the transaction manager by type and explicit name, if the supplied
* {@code name} is non-empty, throwing a {@link BeansException} if the named
* transaction manager does not exist.
- * <li>Attempt to look up the transaction manager by type.
+ * <li>Attempt to look up the single transaction manager by type.
+ * <li>Attempt to look up the <em>primary</em> transaction manager by type.
* <li>Attempt to look up the transaction manager via a
* {@link TransactionManagementConfigurer}, if present.
* <li>Attempt to look up the transaction manager by type and the
@@ -176,6 +185,14 @@ public abstract class TestContextTransactionUtils {
return txMgrs.values().iterator().next();
}
+ try {
+ // look up single bean by type, with support for 'primary' beans
+ return bf.getBean(PlatformTransactionManager.class);
+ }
+ catch (BeansException ex) {
+ logBeansException(testContext, ex, PlatformTransactionManager.class);
+ }
+
// look up single TransactionManagementConfigurer
Map<String, TransactionManagementConfigurer> configurers = BeanFactoryUtils.beansOfTypeIncludingAncestors(
lbf, TransactionManagementConfigurer.class);
@@ -192,14 +209,18 @@ public abstract class TestContextTransactionUtils {
return bf.getBean(DEFAULT_TRANSACTION_MANAGER_NAME, PlatformTransactionManager.class);
}
catch (BeansException ex) {
- if (logger.isDebugEnabled()) {
- logger.debug("Caught exception while retrieving transaction manager for test context " + testContext,
- ex);
- }
+ logBeansException(testContext, ex, PlatformTransactionManager.class);
return null;
}
}
+ private static void logBeansException(TestContext testContext, BeansException ex, Class<?> beanType) {
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Caught exception while retrieving %s for test context %s",
+ beanType.getSimpleName(), testContext), ex);
+ }
+ }
+
/**
* Create a delegating {@link TransactionAttribute} for the supplied target
* {@link TransactionAttribute} and {@link TestContext}, using the names of
diff --git a/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionConfiguration.java
index 98857e5a..e35e862e 100644
--- a/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionConfiguration.java
+++ b/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionConfiguration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -40,8 +40,9 @@ import java.lang.annotation.Target;
* @see org.springframework.test.context.jdbc.SqlConfig
* @see org.springframework.test.context.jdbc.SqlConfig#transactionManager
* @see org.springframework.test.context.ContextConfiguration
- * @deprecated As of Spring Framework 4.2, use {@code @Rollback} at the class
- * level and the {@code transactionManager} qualifier in {@code @Transactional}.
+ * @deprecated As of Spring Framework 4.2, use {@code @Rollback} or
+ * {@code @Commit} at the class level and the {@code transactionManager}
+ * qualifier in {@code @Transactional}.
*/
@Deprecated
@Documented
diff --git a/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java
index 6ae8d4bd..356eb3d0 100644
--- a/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java
+++ b/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +30,8 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.test.annotation.Commit;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener;
@@ -43,8 +45,6 @@ import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
-import static org.springframework.core.annotation.AnnotationUtils.*;
-
/**
* {@code TestExecutionListener} that provides support for executing tests
* within <em>test-managed transactions</em> by honoring Spring's
@@ -83,8 +83,9 @@ import static org.springframework.core.annotation.AnnotationUtils.*;
* <h3>Declarative Rollback and Commit Behavior</h3>
* <p>By default, test transactions will be automatically <em>rolled back</em>
* after completion of the test; however, transactional commit and rollback
- * behavior can be configured declaratively via the {@link Rollback @Rollback}
- * annotation at the class level and at the method level.
+ * behavior can be configured declaratively via the {@link Commit @Commit}
+ * and {@link Rollback @Rollback} annotations at the class level and at the
+ * method level.
*
* <h3>Programmatic Transaction Management</h3>
* <p>As of Spring Framework 4.1, it is possible to interact with test-managed
@@ -96,9 +97,10 @@ import static org.springframework.core.annotation.AnnotationUtils.*;
* <p>When executing transactional tests, it is sometimes useful to be able to
* execute certain <em>set up</em> or <em>tear down</em> code outside of a
* transaction. {@code TransactionalTestExecutionListener} provides such
- * support for methods annotated with
- * {@link BeforeTransaction @BeforeTransaction} or
- * {@link AfterTransaction @AfterTransaction}.
+ * support for methods annotated with {@link BeforeTransaction @BeforeTransaction}
+ * or {@link AfterTransaction @AfterTransaction}. As of Spring Framework 4.3,
+ * {@code @BeforeTransaction} and {@code @AfterTransaction} may also be declared
+ * on Java 8 based interface default methods.
*
* <h3>Configuring a Transaction Manager</h3>
* <p>{@code TransactionalTestExecutionListener} expects a
@@ -133,7 +135,8 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
@SuppressWarnings("deprecation")
private static final TransactionConfigurationAttributes defaultTxConfigAttributes = new TransactionConfigurationAttributes();
- protected final TransactionAttributeSource attributeSource = new AnnotationTransactionAttributeSource();
+ // Do not require @Transactional test methods to be public.
+ protected final TransactionAttributeSource attributeSource = new AnnotationTransactionAttributeSource(false);
@SuppressWarnings("deprecation")
private TransactionConfigurationAttributes configurationAttributes;
@@ -177,8 +180,8 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
transactionAttribute);
if (logger.isDebugEnabled()) {
- logger.debug("Explicit transaction definition [" + transactionAttribute + "] found for test context "
- + testContext);
+ logger.debug("Explicit transaction definition [" + transactionAttribute + "] found for test context " +
+ testContext);
}
if (transactionAttribute.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
@@ -189,8 +192,8 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
if (tm == null) {
throw new IllegalStateException(String.format(
- "Failed to retrieve PlatformTransactionManager for @Transactional test for test context %s.",
- testContext));
+ "Failed to retrieve PlatformTransactionManager for @Transactional test for test context %s.",
+ testContext));
}
}
@@ -246,12 +249,15 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
if (logger.isDebugEnabled()) {
logger.debug("Executing @BeforeTransaction method [" + method + "] for test context " + testContext);
}
+ ReflectionUtils.makeAccessible(method);
method.invoke(testContext.getTestInstance());
}
}
catch (InvocationTargetException ex) {
- logger.error("Exception encountered while executing @BeforeTransaction methods for test context "
- + testContext + ".", ex.getTargetException());
+ if (logger.isErrorEnabled()) {
+ logger.error("Exception encountered while executing @BeforeTransaction methods for test context " +
+ testContext + ".", ex.getTargetException());
+ }
ReflectionUtils.rethrowException(ex.getTargetException());
}
}
@@ -273,6 +279,7 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
if (logger.isDebugEnabled()) {
logger.debug("Executing @AfterTransaction method [" + method + "] for test context " + testContext);
}
+ ReflectionUtils.makeAccessible(method);
method.invoke(testContext.getTestInstance());
}
catch (InvocationTargetException ex) {
@@ -280,15 +287,15 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
if (afterTransactionException == null) {
afterTransactionException = targetException;
}
- logger.error("Exception encountered while executing @AfterTransaction method [" + method
- + "] for test context " + testContext, targetException);
+ logger.error("Exception encountered while executing @AfterTransaction method [" + method +
+ "] for test context " + testContext, targetException);
}
catch (Exception ex) {
if (afterTransactionException == null) {
afterTransactionException = ex;
}
- logger.error("Exception encountered while executing @AfterTransaction method [" + method
- + "] for test context " + testContext, ex);
+ logger.error("Exception encountered while executing @AfterTransaction method [" + method +
+ "] for test context " + testContext, ex);
}
}
@@ -311,20 +318,18 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
* @see #getTransactionManager(TestContext)
*/
protected PlatformTransactionManager getTransactionManager(TestContext testContext, String qualifier) {
- // look up by type and qualifier from @Transactional
+ // Look up by type and qualifier from @Transactional
if (StringUtils.hasText(qualifier)) {
try {
- // Use autowire-capable factory in order to support extended qualifier
- // matching (only exposed on the internal BeanFactory, not on the
- // ApplicationContext).
+ // Use autowire-capable factory in order to support extended qualifier matching
+ // (only exposed on the internal BeanFactory, not on the ApplicationContext).
BeanFactory bf = testContext.getApplicationContext().getAutowireCapableBeanFactory();
return BeanFactoryAnnotationUtils.qualifiedBeanOfType(bf, PlatformTransactionManager.class, qualifier);
}
catch (RuntimeException ex) {
if (logger.isWarnEnabled()) {
- logger.warn(
- String.format(
+ logger.warn(String.format(
"Caught exception while retrieving transaction manager with qualifier '%s' for test context %s",
qualifier, testContext), ex);
}
@@ -359,7 +364,7 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
/**
* Determine whether or not to rollback transactions by default for the
* supplied {@linkplain TestContext test context}.
- * <p>Supports {@link Rollback @Rollback} or
+ * <p>Supports {@link Rollback @Rollback}, {@link Commit @Commit}, or
* {@link TransactionConfiguration @TransactionConfiguration} at the
* class-level.
* @param testContext the test context for which the default rollback flag
@@ -370,7 +375,7 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
@SuppressWarnings("deprecation")
protected final boolean isDefaultRollback(TestContext testContext) throws Exception {
Class<?> testClass = testContext.getTestClass();
- Rollback rollback = findAnnotation(testClass, Rollback.class);
+ Rollback rollback = AnnotatedElementUtils.findMergedAnnotation(testClass, Rollback.class);
boolean rollbackPresent = (rollback != null);
TransactionConfigurationAttributes txConfigAttributes = retrieveConfigurationAttributes(testContext);
@@ -405,111 +410,45 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
*/
protected final boolean isRollback(TestContext testContext) throws Exception {
boolean rollback = isDefaultRollback(testContext);
- Rollback rollbackAnnotation = findAnnotation(testContext.getTestMethod(), Rollback.class);
+ Rollback rollbackAnnotation =
+ AnnotatedElementUtils.findMergedAnnotation(testContext.getTestMethod(), Rollback.class);
if (rollbackAnnotation != null) {
boolean rollbackOverride = rollbackAnnotation.value();
if (logger.isDebugEnabled()) {
logger.debug(String.format(
- "Method-level @Rollback(%s) overrides default rollback [%s] for test context %s.",
- rollbackOverride, rollback, testContext));
+ "Method-level @Rollback(%s) overrides default rollback [%s] for test context %s.",
+ rollbackOverride, rollback, testContext));
}
rollback = rollbackOverride;
}
else {
if (logger.isDebugEnabled()) {
logger.debug(String.format(
- "No method-level @Rollback override: using default rollback [%s] for test context %s.", rollback,
- testContext));
+ "No method-level @Rollback override: using default rollback [%s] for test context %s.",
+ rollback, testContext));
}
}
return rollback;
}
/**
- * Gets all superclasses of the supplied {@link Class class}, including the
- * class itself. The ordering of the returned list will begin with the
- * supplied class and continue up the class hierarchy, excluding {@link Object}.
- * <p>Note: This code has been borrowed from
- * {@link org.junit.internal.runners.TestClass#getSuperClasses(Class)} and
- * adapted.
- * @param clazz the class for which to retrieve the superclasses
- * @return all superclasses of the supplied class, excluding {@code Object}
- */
- private List<Class<?>> getSuperClasses(Class<?> clazz) {
- List<Class<?>> results = new ArrayList<Class<?>>();
- Class<?> current = clazz;
- while (current != null && Object.class != current) {
- results.add(current);
- current = current.getSuperclass();
- }
- return results;
- }
-
- /**
- * Gets all methods in the supplied {@link Class class} and its superclasses
+ * Get all methods in the supplied {@link Class class} and its superclasses
* which are annotated with the supplied {@code annotationType} but
* which are not <em>shadowed</em> by methods overridden in subclasses.
- * <p>Note: This code has been borrowed from
- * {@link org.junit.internal.runners.TestClass#getAnnotatedMethods(Class)}
- * and adapted.
+ * <p>Default methods on interfaces are also detected.
* @param clazz the class for which to retrieve the annotated methods
* @param annotationType the annotation type for which to search
* @return all annotated methods in the supplied class and its superclasses
+ * as well as annotated interface default methods
*/
private List<Method> getAnnotatedMethods(Class<?> clazz, Class<? extends Annotation> annotationType) {
- List<Method> results = new ArrayList<Method>();
- for (Class<?> current : getSuperClasses(clazz)) {
- for (Method method : current.getDeclaredMethods()) {
- Annotation annotation = getAnnotation(method, annotationType);
- if (annotation != null && !isShadowed(method, results)) {
- results.add(method);
- }
+ List<Method> methods = new ArrayList<Method>(4);
+ for (Method method : ReflectionUtils.getUniqueDeclaredMethods(clazz)) {
+ if (AnnotationUtils.getAnnotation(method, annotationType) != null) {
+ methods.add(method);
}
}
- return results;
- }
-
- /**
- * Determine if the supplied {@link Method method} is <em>shadowed</em> by
- * a method in the supplied {@link List list} of previous methods.
- * <p>Note: This code has been borrowed from
- * {@link org.junit.internal.runners.TestClass#isShadowed(Method, List)}.
- * @param method the method to check for shadowing
- * @param previousMethods the list of methods which have previously been processed
- * @return {@code true} if the supplied method is shadowed by a
- * method in the {@code previousMethods} list
- */
- private boolean isShadowed(Method method, List<Method> previousMethods) {
- for (Method each : previousMethods) {
- if (isShadowed(method, each)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Determine if the supplied {@linkplain Method current method} is
- * <em>shadowed</em> by a {@linkplain Method previous method}.
- * <p>Note: This code has been borrowed from
- * {@link org.junit.internal.runners.TestClass#isShadowed(Method, Method)}.
- * @param current the current method
- * @param previous the previous method
- * @return {@code true} if the previous method shadows the current one
- */
- private boolean isShadowed(Method current, Method previous) {
- if (!previous.getName().equals(current.getName())) {
- return false;
- }
- if (previous.getParameterTypes().length != current.getParameterTypes().length) {
- return false;
- }
- for (int i = 0; i < previous.getParameterTypes().length; i++) {
- if (!previous.getParameterTypes()[i].equals(current.getParameterTypes()[i])) {
- return false;
- }
- }
- return true;
+ return methods;
}
/**
@@ -531,19 +470,18 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
if (this.configurationAttributes == null) {
Class<?> clazz = testContext.getTestClass();
- TransactionConfiguration txConfig = AnnotatedElementUtils.findMergedAnnotation(clazz,
- TransactionConfiguration.class);
+ TransactionConfiguration txConfig =
+ AnnotatedElementUtils.findMergedAnnotation(clazz, TransactionConfiguration.class);
if (logger.isDebugEnabled()) {
logger.debug(String.format("Retrieved @TransactionConfiguration [%s] for test class [%s].",
- txConfig, clazz.getName()));
+ txConfig, clazz.getName()));
}
- TransactionConfigurationAttributes configAttributes = (txConfig == null ? defaultTxConfigAttributes
- : new TransactionConfigurationAttributes(txConfig.transactionManager(), txConfig.defaultRollback()));
-
+ TransactionConfigurationAttributes configAttributes = (txConfig == null ? defaultTxConfigAttributes :
+ new TransactionConfigurationAttributes(txConfig.transactionManager(), txConfig.defaultRollback()));
if (logger.isDebugEnabled()) {
logger.debug(String.format("Using TransactionConfigurationAttributes %s for test class [%s].",
- configAttributes, clazz.getName()));
+ configAttributes, clazz.getName()));
}
this.configurationAttributes = configAttributes;
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/web/AbstractGenericWebContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/web/AbstractGenericWebContextLoader.java
index f6e1ac9e..a74ca6a2 100644
--- a/spring-test/src/main/java/org/springframework/test/context/web/AbstractGenericWebContextLoader.java
+++ b/spring-test/src/main/java/org/springframework/test/context/web/AbstractGenericWebContextLoader.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author 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 @@ import org.springframework.web.context.support.GenericWebApplicationContext;
* {@link #loadBeanDefinitions}.
*
* @author Sam Brannen
+ * @author Phillip Webb
* @since 3.2
* @see #loadContext(MergedContextConfiguration)
* @see #loadContext(String...)
@@ -256,15 +257,17 @@ public abstract class AbstractGenericWebContextLoader extends AbstractContextLoa
* loader <i>after</i> bean definitions have been loaded into the context but
* <i>before</i> the context is refreshed.
*
- * <p>The default implementation is empty but can be overridden in subclasses
- * to customize the web application context.
+ * <p>The default implementation simply delegates to
+ * {@link AbstractContextLoader#customizeContext(ConfigurableApplicationContext, MergedContextConfiguration)}.
*
* @param context the newly created web application context
* @param webMergedConfig the merged context configuration to use to load the
* web application context
* @see #loadContext(MergedContextConfiguration)
+ * @see #customizeContext(ConfigurableApplicationContext, MergedContextConfiguration)
*/
protected void customizeContext(GenericWebApplicationContext context, WebMergedContextConfiguration webMergedConfig) {
+ super.customizeContext(context, webMergedConfig);
}
// --- ContextLoader -------------------------------------------------------
diff --git a/spring-test/src/main/java/org/springframework/test/context/web/ServletTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/web/ServletTestExecutionListener.java
index a1bdb5a2..32e79eb5 100644
--- a/spring-test/src/main/java/org/springframework/test/context/web/ServletTestExecutionListener.java
+++ b/spring-test/src/main/java/org/springframework/test/context/web/ServletTestExecutionListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,7 +25,7 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.Conventions;
-import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletContext;
@@ -33,7 +33,6 @@ import org.springframework.test.context.TestContext;
import org.springframework.test.context.TestExecutionListener;
import org.springframework.test.context.support.AbstractTestExecutionListener;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
-import org.springframework.util.Assert;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
@@ -58,9 +57,10 @@ import org.springframework.web.context.request.ServletWebRequest;
* <p>Note that {@code ServletTestExecutionListener} is enabled by default but
* generally takes no action if the {@linkplain TestContext#getTestClass() test
* class} is not annotated with {@link WebAppConfiguration @WebAppConfiguration}.
- * See the Javadoc for individual methods in this class for details.
+ * See the javadocs for individual methods in this class for details.
*
* @author Sam Brannen
+ * @author Phillip Webb
* @since 3.2
*/
public class ServletTestExecutionListener extends AbstractTestExecutionListener {
@@ -70,33 +70,42 @@ public class ServletTestExecutionListener extends AbstractTestExecutionListener
* whether or not the {@code ServletTestExecutionListener} should {@linkplain
* RequestContextHolder#resetRequestAttributes() reset} Spring Web's
* {@code RequestContextHolder} in {@link #afterTestMethod(TestContext)}.
- *
* <p>Permissible values include {@link Boolean#TRUE} and {@link Boolean#FALSE}.
*/
public static final String RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE = Conventions.getQualifiedAttributeName(
- ServletTestExecutionListener.class, "resetRequestContextHolder");
+ ServletTestExecutionListener.class, "resetRequestContextHolder");
/**
* Attribute name for a {@link TestContext} attribute which indicates that
* {@code ServletTestExecutionListener} has already populated Spring Web's
* {@code RequestContextHolder}.
- *
* <p>Permissible values include {@link Boolean#TRUE} and {@link Boolean#FALSE}.
*/
public static final String POPULATED_REQUEST_CONTEXT_HOLDER_ATTRIBUTE = Conventions.getQualifiedAttributeName(
- ServletTestExecutionListener.class, "populatedRequestContextHolder");
+ ServletTestExecutionListener.class, "populatedRequestContextHolder");
/**
* Attribute name for a request attribute which indicates that the
* {@link MockHttpServletRequest} stored in the {@link RequestAttributes}
* in Spring Web's {@link RequestContextHolder} was created by the TestContext
* framework.
- *
* <p>Permissible values include {@link Boolean#TRUE} and {@link Boolean#FALSE}.
* @since 4.2
*/
public static final String CREATED_BY_THE_TESTCONTEXT_FRAMEWORK = Conventions.getQualifiedAttributeName(
- ServletTestExecutionListener.class, "createdByTheTestContextFramework");
+ ServletTestExecutionListener.class, "createdByTheTestContextFramework");
+
+ /**
+ * Attribute name for a {@link TestContext} attribute which indicates that that
+ * the {@code ServletTestExecutionListener} should be activated. When not set to
+ * {@code true}, activation occurs when the {@linkplain TestContext#getTestClass()
+ * test class} is annotated with {@link WebAppConfiguration @WebAppConfiguration}.
+ * <p>Permissible values include {@link Boolean#TRUE} and {@link Boolean#FALSE}.
+ * @since 4.3
+ */
+ public static final String ACTIVATE_LISTENER = Conventions.getQualifiedAttributeName(
+ ServletTestExecutionListener.class, "activateListener");
+
private static final Log logger = LogFactory.getLog(ServletTestExecutionListener.class);
@@ -114,7 +123,6 @@ public class ServletTestExecutionListener extends AbstractTestExecutionListener
* callback phase via Spring Web's {@link RequestContextHolder}, but only if
* the {@linkplain TestContext#getTestClass() test class} is annotated with
* {@link WebAppConfiguration @WebAppConfiguration}.
- *
* @see TestExecutionListener#prepareTestInstance(TestContext)
* @see #setUpRequestContextIfNecessary(TestContext)
*/
@@ -128,7 +136,6 @@ public class ServletTestExecutionListener extends AbstractTestExecutionListener
* {@link RequestContextHolder}, but only if the
* {@linkplain TestContext#getTestClass() test class} is annotated with
* {@link WebAppConfiguration @WebAppConfiguration}.
- *
* @see TestExecutionListener#beforeTestMethod(TestContext)
* @see #setUpRequestContextIfNecessary(TestContext)
*/
@@ -146,11 +153,9 @@ public class ServletTestExecutionListener extends AbstractTestExecutionListener
* into the test instance for subsequent tests by setting the
* {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE}
* in the test context to {@code true}.
- *
* <p>The {@link #RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE} and
* {@link #POPULATED_REQUEST_CONTEXT_HOLDER_ATTRIBUTE} will be subsequently
* removed from the test context, regardless of their values.
- *
* @see TestExecutionListener#afterTestMethod(TestContext)
*/
@Override
@@ -167,8 +172,9 @@ public class ServletTestExecutionListener extends AbstractTestExecutionListener
testContext.removeAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE);
}
- private boolean notAnnotatedWithWebAppConfiguration(TestContext testContext) {
- return AnnotationUtils.findAnnotation(testContext.getTestClass(), WebAppConfiguration.class) == null;
+ private boolean isActivated(TestContext testContext) {
+ return (Boolean.TRUE.equals(testContext.getAttribute(ACTIVATE_LISTENER)) ||
+ AnnotatedElementUtils.hasAnnotation(testContext.getTestClass(), WebAppConfiguration.class));
}
private boolean alreadyPopulatedRequestContextHolder(TestContext testContext) {
@@ -176,7 +182,7 @@ public class ServletTestExecutionListener extends AbstractTestExecutionListener
}
private void setUpRequestContextIfNecessary(TestContext testContext) {
- if (notAnnotatedWithWebAppConfiguration(testContext) || alreadyPopulatedRequestContextHolder(testContext)) {
+ if (!isActivated(testContext) || alreadyPopulatedRequestContextHolder(testContext)) {
return;
}
@@ -185,14 +191,16 @@ public class ServletTestExecutionListener extends AbstractTestExecutionListener
if (context instanceof WebApplicationContext) {
WebApplicationContext wac = (WebApplicationContext) context;
ServletContext servletContext = wac.getServletContext();
- Assert.state(servletContext instanceof MockServletContext, String.format(
- "The WebApplicationContext for test context %s must be configured with a MockServletContext.",
- testContext));
+ if (!(servletContext instanceof MockServletContext)) {
+ throw new IllegalStateException(String.format(
+ "The WebApplicationContext for test context %s must be configured with a MockServletContext.",
+ testContext));
+ }
if (logger.isDebugEnabled()) {
logger.debug(String.format(
- "Setting up MockHttpServletRequest, MockHttpServletResponse, ServletWebRequest, and RequestContextHolder for test context %s.",
- testContext));
+ "Setting up MockHttpServletRequest, MockHttpServletResponse, ServletWebRequest, and RequestContextHolder for test context %s.",
+ testContext));
}
MockServletContext mockServletContext = (MockServletContext) servletContext;
diff --git a/spring-test/src/main/java/org/springframework/test/context/web/WebAppConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/web/WebAppConfiguration.java
index e9d17610..695d5f5a 100644
--- a/spring-test/src/main/java/org/springframework/test/context/web/WebAppConfiguration.java
+++ b/spring-test/src/main/java/org/springframework/test/context/web/WebAppConfiguration.java
@@ -23,8 +23,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-import org.springframework.test.context.BootstrapWith;
-
/**
* {@code @WebAppConfiguration} is a class-level annotation that is used to
* declare that the {@code ApplicationContext} loaded for an integration test
@@ -53,7 +51,6 @@ import org.springframework.test.context.BootstrapWith;
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
-@BootstrapWith(WebTestContextBootstrapper.class)
public @interface WebAppConfiguration {
/**
diff --git a/spring-test/src/main/java/org/springframework/test/context/web/WebMergedContextConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/web/WebMergedContextConfiguration.java
index cd1187b5..98bbdad2 100644
--- a/spring-test/src/main/java/org/springframework/test/context/web/WebMergedContextConfiguration.java
+++ b/spring-test/src/main/java/org/springframework/test/context/web/WebMergedContextConfiguration.java
@@ -22,6 +22,7 @@ import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.style.ToStringCreator;
import org.springframework.test.context.CacheAwareContextLoaderDelegate;
+import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextLoader;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.util.ObjectUtils;
@@ -132,13 +133,48 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
String resourceBasePath, ContextLoader contextLoader,
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parent) {
+ this(testClass, locations, classes, contextInitializerClasses, activeProfiles, propertySourceLocations,
+ propertySourceProperties, null, resourceBasePath, contextLoader, cacheAwareContextLoaderDelegate, parent);
+ }
+
+ /**
+ * Create a new {@code WebMergedContextConfiguration} instance for the
+ * supplied parameters.
+ * <p>If a {@code null} value is supplied for {@code locations},
+ * {@code classes}, {@code activeProfiles}, {@code propertySourceLocations},
+ * or {@code propertySourceProperties} an empty array will be stored instead.
+ * If a {@code null} value is supplied for {@code contextInitializerClasses}
+ * or {@code contextCustomizers}, an empty set will be stored instead.
+ * If an <em>empty</em> value is supplied for the {@code resourceBasePath}
+ * an empty string will be used. Furthermore, active profiles will be sorted,
+ * and duplicate profiles will be removed.
+ * @param testClass the test class for which the configuration was merged
+ * @param locations the merged context resource locations
+ * @param classes the merged annotated classes
+ * @param contextInitializerClasses the merged context initializer classes
+ * @param activeProfiles the merged active bean definition profiles
+ * @param propertySourceLocations the merged {@code PropertySource} locations
+ * @param propertySourceProperties the merged {@code PropertySource} properties
+ * @param contextCustomizers the context customizers
+ * @param resourceBasePath the resource path to the root directory of the web application
+ * @param contextLoader the resolved {@code ContextLoader}
+ * @param cacheAwareContextLoaderDelegate a cache-aware context loader
+ * delegate with which to retrieve the parent context
+ * @param parent the parent configuration or {@code null} if there is no parent
+ * @since 4.3
+ */
+ public WebMergedContextConfiguration(Class<?> testClass, String[] locations, Class<?>[] classes,
+ Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses,
+ String[] activeProfiles, String[] propertySourceLocations, String[] propertySourceProperties,
+ Set<ContextCustomizer> contextCustomizers, String resourceBasePath, ContextLoader contextLoader,
+ CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parent) {
+
super(testClass, locations, classes, contextInitializerClasses, activeProfiles, propertySourceLocations,
- propertySourceProperties, contextLoader, cacheAwareContextLoaderDelegate, parent);
+ propertySourceProperties, contextCustomizers, contextLoader, cacheAwareContextLoaderDelegate, parent);
- this.resourceBasePath = !StringUtils.hasText(resourceBasePath) ? "" : resourceBasePath;
+ this.resourceBasePath = (StringUtils.hasText(resourceBasePath) ? resourceBasePath : "");
}
-
/**
* Get the resource path to the root directory of the web application for the
* {@linkplain #getTestClass() test class}, configured via {@code @WebAppConfiguration}.
@@ -182,6 +218,7 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
* {@linkplain #getActiveProfiles() active profiles},
* {@linkplain #getPropertySourceLocations() property source locations},
* {@linkplain #getPropertySourceProperties() property source properties},
+ * {@linkplain #getContextCustomizers() context customizers},
* {@linkplain #getResourceBasePath() resource base path}, the name of the
* {@link #getContextLoader() ContextLoader}, and the
* {@linkplain #getParent() parent configuration}.
@@ -196,6 +233,7 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
.append("activeProfiles", ObjectUtils.nullSafeToString(getActiveProfiles()))
.append("propertySourceLocations", ObjectUtils.nullSafeToString(getPropertySourceLocations()))
.append("propertySourceProperties", ObjectUtils.nullSafeToString(getPropertySourceProperties()))
+ .append("contextCustomizers", getContextCustomizers())
.append("resourceBasePath", getResourceBasePath())
.append("contextLoader", nullSafeToString(getContextLoader()))
.append("parent", getParent())
diff --git a/spring-test/src/main/java/org/springframework/test/context/web/WebTestContextBootstrapper.java b/spring-test/src/main/java/org/springframework/test/context/web/WebTestContextBootstrapper.java
index 94741f5a..f779450c 100644
--- a/spring-test/src/main/java/org/springframework/test/context/web/WebTestContextBootstrapper.java
+++ b/spring-test/src/main/java/org/springframework/test/context/web/WebTestContextBootstrapper.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
package org.springframework.test.context.web;
-import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.test.context.ContextLoader;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.TestContextBootstrapper;
@@ -45,12 +45,12 @@ public class WebTestContextBootstrapper extends DefaultTestContextBootstrapper {
*/
@Override
protected Class<? extends ContextLoader> getDefaultContextLoaderClass(Class<?> testClass) {
- if (AnnotationUtils.findAnnotation(testClass, WebAppConfiguration.class) != null) {
+ if (AnnotatedElementUtils.findMergedAnnotation(testClass, WebAppConfiguration.class) != null) {
return WebDelegatingSmartContextLoader.class;
}
-
- // else...
- return super.getDefaultContextLoaderClass(testClass);
+ else {
+ return super.getDefaultContextLoaderClass(testClass);
+ }
}
/**
@@ -61,14 +61,14 @@ public class WebTestContextBootstrapper extends DefaultTestContextBootstrapper {
*/
@Override
protected MergedContextConfiguration processMergedContextConfiguration(MergedContextConfiguration mergedConfig) {
- WebAppConfiguration webAppConfiguration = AnnotationUtils.findAnnotation(mergedConfig.getTestClass(),
- WebAppConfiguration.class);
+ WebAppConfiguration webAppConfiguration =
+ AnnotatedElementUtils.findMergedAnnotation(mergedConfig.getTestClass(), WebAppConfiguration.class);
if (webAppConfiguration != null) {
return new WebMergedContextConfiguration(mergedConfig, webAppConfiguration.value());
}
-
- // else...
- return mergedConfig;
+ else {
+ return mergedConfig;
+ }
}
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/web/socket/MockServerContainer.java b/spring-test/src/main/java/org/springframework/test/context/web/socket/MockServerContainer.java
new file mode 100644
index 00000000..bd7c7257
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/web/socket/MockServerContainer.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.web.socket;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Collections;
+import java.util.Set;
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.DeploymentException;
+import javax.websocket.Endpoint;
+import javax.websocket.Extension;
+import javax.websocket.Session;
+import javax.websocket.server.ServerContainer;
+import javax.websocket.server.ServerEndpointConfig;
+
+/**
+ * Mock implementation of the {@link javax.websocket.server.ServerContainer} interface.
+ *
+ * @author Sam Brannen
+ * @since 4.3.1
+ */
+class MockServerContainer implements ServerContainer {
+
+ private long defaultAsyncSendTimeout;
+
+ private long defaultMaxSessionIdleTimeout;
+
+ private int defaultMaxBinaryMessageBufferSize;
+
+ private int defaultMaxTextMessageBufferSize;
+
+
+ // --- WebSocketContainer --------------------------------------------------
+
+ @Override
+ public long getDefaultAsyncSendTimeout() {
+ return this.defaultAsyncSendTimeout;
+ }
+
+ @Override
+ public void setAsyncSendTimeout(long timeout) {
+ this.defaultAsyncSendTimeout = timeout;
+ }
+
+ @Override
+ public long getDefaultMaxSessionIdleTimeout() {
+ return this.defaultMaxSessionIdleTimeout;
+ }
+
+ @Override
+ public void setDefaultMaxSessionIdleTimeout(long timeout) {
+ this.defaultMaxSessionIdleTimeout = timeout;
+ }
+
+ @Override
+ public int getDefaultMaxBinaryMessageBufferSize() {
+ return this.defaultMaxBinaryMessageBufferSize;
+ }
+
+ @Override
+ public void setDefaultMaxBinaryMessageBufferSize(int max) {
+ this.defaultMaxBinaryMessageBufferSize = max;
+ }
+
+ @Override
+ public int getDefaultMaxTextMessageBufferSize() {
+ return this.defaultMaxTextMessageBufferSize;
+ }
+
+ @Override
+ public void setDefaultMaxTextMessageBufferSize(int max) {
+ this.defaultMaxTextMessageBufferSize = max;
+ }
+
+ @Override
+ public Set<Extension> getInstalledExtensions() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Session connectToServer(Object annotatedEndpointInstance, URI path) throws DeploymentException, IOException {
+ throw new UnsupportedOperationException("MockServerContainer does not support connectToServer(Object, URI)");
+ }
+
+ @Override
+ public Session connectToServer(Class<?> annotatedEndpointClass, URI path) throws DeploymentException, IOException {
+ throw new UnsupportedOperationException("MockServerContainer does not support connectToServer(Class, URI)");
+ }
+
+ @Override
+ public Session connectToServer(Endpoint endpointInstance, ClientEndpointConfig cec, URI path)
+ throws DeploymentException, IOException {
+
+ throw new UnsupportedOperationException(
+ "MockServerContainer does not support connectToServer(Endpoint, ClientEndpointConfig, URI)");
+ }
+
+ @Override
+ public Session connectToServer(Class<? extends Endpoint> endpointClass, ClientEndpointConfig cec, URI path)
+ throws DeploymentException, IOException {
+
+ throw new UnsupportedOperationException(
+ "MockServerContainer does not support connectToServer(Class, ClientEndpointConfig, URI)");
+ }
+
+
+ // --- ServerContainer -----------------------------------------------------
+
+ @Override
+ public void addEndpoint(Class<?> endpointClass) throws DeploymentException {
+ throw new UnsupportedOperationException("MockServerContainer does not support addEndpoint(Class)");
+ }
+
+ @Override
+ public void addEndpoint(ServerEndpointConfig serverConfig) throws DeploymentException {
+ throw new UnsupportedOperationException(
+ "MockServerContainer does not support addEndpoint(ServerEndpointConfig)");
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/web/socket/MockServerContainerContextCustomizer.java b/spring-test/src/main/java/org/springframework/test/context/web/socket/MockServerContainerContextCustomizer.java
new file mode 100644
index 00000000..e68914ee
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/web/socket/MockServerContainerContextCustomizer.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.web.socket;
+
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.test.context.ContextCustomizer;
+import org.springframework.test.context.MergedContextConfiguration;
+import org.springframework.web.context.WebApplicationContext;
+
+/**
+ * {@link ContextCustomizer} that instantiates a new {@link MockServerContainer}
+ * and stores it in the {@code ServletContext} under the attribute named
+ * {@code "javax.websocket.server.ServerContainer"}.
+ *
+ * @author Sam Brannen
+ * @since 4.3.1
+ */
+class MockServerContainerContextCustomizer implements ContextCustomizer {
+
+ @Override
+ public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
+ if (context instanceof WebApplicationContext) {
+ WebApplicationContext wac = (WebApplicationContext) context;
+ wac.getServletContext().setAttribute("javax.websocket.server.ServerContainer", new MockServerContainer());
+ }
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return (this == other || (other != null && getClass() == other.getClass()));
+ }
+
+ @Override
+ public int hashCode() {
+ return getClass().hashCode();
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/web/socket/MockServerContainerContextCustomizerFactory.java b/spring-test/src/main/java/org/springframework/test/context/web/socket/MockServerContainerContextCustomizerFactory.java
new file mode 100644
index 00000000..c76959c3
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/web/socket/MockServerContainerContextCustomizerFactory.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.web.socket;
+
+import java.util.List;
+
+import org.springframework.beans.BeanUtils;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.test.context.ContextConfigurationAttributes;
+import org.springframework.test.context.ContextCustomizer;
+import org.springframework.test.context.ContextCustomizerFactory;
+import org.springframework.util.ClassUtils;
+
+/**
+ * {@link ContextCustomizerFactory} which creates a {@link MockServerContainerContextCustomizer}
+ * if WebSocket support is present in the classpath and the test class is annotated
+ * with {@code @WebAppConfiguration}.
+ *
+ * @author Sam Brannen
+ * @since 4.3.1
+ */
+class MockServerContainerContextCustomizerFactory implements ContextCustomizerFactory {
+
+ private static final String WEB_APP_CONFIGURATION_ANNOTATION_CLASS_NAME =
+ "org.springframework.test.context.web.WebAppConfiguration";
+
+ private static final String MOCK_SERVER_CONTAINER_CONTEXT_CUSTOMIZER_CLASS_NAME =
+ "org.springframework.test.context.web.socket.MockServerContainerContextCustomizer";
+
+ private static final boolean webSocketPresent = ClassUtils.isPresent("javax.websocket.server.ServerContainer",
+ MockServerContainerContextCustomizerFactory.class.getClassLoader());
+
+
+ @Override
+ public ContextCustomizer createContextCustomizer(Class<?> testClass,
+ List<ContextConfigurationAttributes> configAttributes) {
+
+ if (webSocketPresent && isAnnotatedWithWebAppConfiguration(testClass)) {
+ try {
+ Class<?> clazz = ClassUtils.forName(MOCK_SERVER_CONTAINER_CONTEXT_CUSTOMIZER_CLASS_NAME,
+ getClass().getClassLoader());
+ return (ContextCustomizer) BeanUtils.instantiateClass(clazz);
+ }
+ catch (Throwable ex) {
+ throw new IllegalStateException("Failed to enable WebSocket test support; could not load class: " +
+ MOCK_SERVER_CONTAINER_CONTEXT_CUSTOMIZER_CLASS_NAME, ex);
+ }
+ }
+
+ // Else, nothing to customize
+ return null;
+ }
+
+ private static boolean isAnnotatedWithWebAppConfiguration(Class<?> testClass) {
+ return (AnnotatedElementUtils.findMergedAnnotationAttributes(testClass,
+ WEB_APP_CONFIGURATION_ANNOTATION_CLASS_NAME, false, false) != null);
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/web/socket/package-info.java b/spring-test/src/main/java/org/springframework/test/context/web/socket/package-info.java
new file mode 100644
index 00000000..7b97278f
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/web/socket/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * WebSocket support classes for the <em>Spring TestContext Framework</em>.
+ */
+package org.springframework.test.context.web.socket;
diff --git a/spring-test/src/main/java/org/springframework/test/jdbc/JdbcTestUtils.java b/spring-test/src/main/java/org/springframework/test/jdbc/JdbcTestUtils.java
index 8e3e9a05..e0e1bc0e 100644
--- a/spring-test/src/main/java/org/springframework/test/jdbc/JdbcTestUtils.java
+++ b/spring-test/src/main/java/org/springframework/test/jdbc/JdbcTestUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -124,6 +124,7 @@ public class JdbcTestUtils {
*/
public static int deleteFromTableWhere(JdbcTemplate jdbcTemplate, String tableName, String whereClause,
Object... args) {
+
String sql = "DELETE FROM " + tableName;
if (StringUtils.hasText(whereClause)) {
sql += " WHERE " + whereClause;
@@ -170,6 +171,7 @@ public class JdbcTestUtils {
@Deprecated
public static void executeSqlScript(JdbcTemplate jdbcTemplate, ResourceLoader resourceLoader,
String sqlResourcePath, boolean continueOnError) throws DataAccessException {
+
Resource resource = resourceLoader.getResource(sqlResourcePath);
executeSqlScript(jdbcTemplate, resource, continueOnError);
}
@@ -197,6 +199,7 @@ public class JdbcTestUtils {
@Deprecated
public static void executeSqlScript(JdbcTemplate jdbcTemplate, Resource resource, boolean continueOnError)
throws DataAccessException {
+
executeSqlScript(jdbcTemplate, new EncodedResource(resource), continueOnError);
}
@@ -220,6 +223,7 @@ public class JdbcTestUtils {
@Deprecated
public static void executeSqlScript(JdbcTemplate jdbcTemplate, EncodedResource resource, boolean continueOnError)
throws DataAccessException {
+
new ResourceDatabasePopulator(continueOnError, false, resource.getEncoding(), resource.getResource()).execute(jdbcTemplate.getDataSource());
}
@@ -288,4 +292,5 @@ public class JdbcTestUtils {
public static void splitSqlScript(String script, char delim, List<String> statements) {
ScriptUtils.splitSqlScript(script, delim, statements);
}
+
}
diff --git a/spring-test/src/main/java/org/springframework/test/util/AopTestUtils.java b/spring-test/src/main/java/org/springframework/test/util/AopTestUtils.java
index 4eda8a45..7660ee2c 100644
--- a/spring-test/src/main/java/org/springframework/test/util/AopTestUtils.java
+++ b/spring-test/src/main/java/org/springframework/test/util/AopTestUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,6 +32,7 @@ import org.springframework.util.Assert;
* @since 4.2
* @see org.springframework.aop.support.AopUtils
* @see org.springframework.aop.framework.AopProxyUtils
+ * @see ReflectionTestUtils
*/
public class AopTestUtils {
diff --git a/spring-test/src/main/java/org/springframework/test/util/AssertionErrors.java b/spring-test/src/main/java/org/springframework/test/util/AssertionErrors.java
index 0302686b..f07574e8 100644
--- a/spring-test/src/main/java/org/springframework/test/util/AssertionErrors.java
+++ b/spring-test/src/main/java/org/springframework/test/util/AssertionErrors.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package org.springframework.test.util;
import org.springframework.util.ObjectUtils;
@@ -26,13 +27,8 @@ import org.springframework.util.ObjectUtils;
*/
public abstract class AssertionErrors {
-
- private AssertionErrors() {
- }
-
/**
* Fails a test with the given message.
- *
* @param message describes the reason for the failure
*/
public static void fail(String message) {
@@ -42,7 +38,6 @@ public abstract class AssertionErrors {
/**
* Fails a test with the given message passing along expected and actual
* values to be added to the message.
- *
* <p>For example given:
* <pre class="code">
* assertEquals("Response header [" + name + "]", actual, expected);
@@ -51,7 +46,6 @@ public abstract class AssertionErrors {
* <pre class="code">
* Response header [Accept] expected:&lt;application/json&gt; but was:&lt;text/plain&gt;
* </pre>
- *
* @param message describes the value that failed the match
* @param expected expected value
* @param actual actual value
@@ -63,7 +57,6 @@ public abstract class AssertionErrors {
/**
* Assert the given condition is {@code true} and raise an
* {@link AssertionError} if it is not.
- *
* @param message the message
* @param condition the condition to test for
*/
@@ -79,14 +72,13 @@ public abstract class AssertionErrors {
* <pre class="code">
* assertEquals("Response header [" + name + "]", actual, expected);
* </pre>
- *
* @param message describes the value being checked
* @param expected the expected value
* @param actual the actual value
*/
public static void assertEquals(String message, Object expected, Object actual) {
if (!ObjectUtils.nullSafeEquals(expected, actual)) {
- fail(message, expected, actual);
+ fail(message, ObjectUtils.nullSafeToString(expected), ObjectUtils.nullSafeToString(actual));
}
}
diff --git a/spring-test/src/main/java/org/springframework/test/util/JsonPathExpectationsHelper.java b/spring-test/src/main/java/org/springframework/test/util/JsonPathExpectationsHelper.java
index a82b8a40..cbb74dc6 100644
--- a/spring-test/src/main/java/org/springframework/test/util/JsonPathExpectationsHelper.java
+++ b/spring-test/src/main/java/org/springframework/test/util/JsonPathExpectationsHelper.java
@@ -16,27 +16,21 @@
package org.springframework.test.util;
-import java.lang.reflect.Array;
-import java.lang.reflect.Method;
import java.text.ParseException;
import java.util.List;
import java.util.Map;
+import com.jayway.jsonpath.InvalidPathException;
+import com.jayway.jsonpath.JsonPath;
import org.hamcrest.Matcher;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
-import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
-import com.jayway.jsonpath.InvalidPathException;
-import com.jayway.jsonpath.JsonPath;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.core.IsInstanceOf.instanceOf;
-import static org.springframework.test.util.AssertionErrors.assertEquals;
-import static org.springframework.test.util.AssertionErrors.assertTrue;
-import static org.springframework.test.util.AssertionErrors.fail;
+import static org.hamcrest.MatcherAssert.*;
+import static org.hamcrest.core.IsInstanceOf.*;
+import static org.springframework.test.util.AssertionErrors.*;
/**
* A helper class for applying assertions via JSON path expressions.
@@ -52,26 +46,6 @@ import static org.springframework.test.util.AssertionErrors.fail;
*/
public class JsonPathExpectationsHelper {
- private static Method compileMethod;
-
- private static Object emptyFilters;
-
- static {
- // Reflective bridging between JsonPath 0.9.x and 1.x
- for (Method candidate : JsonPath.class.getMethods()) {
- if (candidate.getName().equals("compile")) {
- Class<?>[] paramTypes = candidate.getParameterTypes();
- if (paramTypes.length == 2 && String.class == paramTypes[0] && paramTypes[1].isArray()) {
- compileMethod = candidate;
- emptyFilters = Array.newInstance(paramTypes[1].getComponentType(), 0);
- break;
- }
- }
- }
- Assert.state(compileMethod != null, "Unexpected JsonPath API - no compile(String, ...) method found");
- }
-
-
private final String expression;
private final JsonPath jsonPath;
@@ -86,8 +60,7 @@ public class JsonPathExpectationsHelper {
public JsonPathExpectationsHelper(String expression, Object... args) {
Assert.hasText(expression, "expression must not be null or empty");
this.expression = String.format(expression, args);
- this.jsonPath = (JsonPath) ReflectionUtils.invokeMethod(
- compileMethod, null, this.expression, emptyFilters);
+ this.jsonPath = JsonPath.compile(this.expression);
}
diff --git a/spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java b/spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java
index 913986a4..9c203d74 100644
--- a/spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java
+++ b/spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java
@@ -57,8 +57,8 @@ public abstract class MetaAnnotationUtils {
/**
* Find the {@link AnnotationDescriptor} for the supplied {@code annotationType}
- * on the supplied {@link Class}, traversing its annotations and superclasses
- * if no annotation can be found on the given class itself.
+ * on the supplied {@link Class}, traversing its annotations, interfaces, and
+ * superclasses if no annotation can be found on the given class itself.
* <p>This method explicitly handles class-level annotations which are not
* declared as {@linkplain java.lang.annotation.Inherited inherited} <em>as
* well as meta-annotations</em>.
@@ -67,14 +67,12 @@ public abstract class MetaAnnotationUtils {
* <li>Search for the annotation on the given class and return a corresponding
* {@code AnnotationDescriptor} if found.
* <li>Recursively search through all annotations that the given class declares.
+ * <li>Recursively search through all interfaces implemented by the given class.
* <li>Recursively search through the superclass hierarchy of the given class.
* </ol>
* <p>In this context, the term <em>recursively</em> means that the search
- * process continues by returning to step #1 with the current annotation or
- * superclass as the class to look for annotations on.
- * <p>If the supplied {@code clazz} is an interface, only the interface
- * itself will be checked; the inheritance hierarchy for interfaces will not
- * be traversed.
+ * process continues by returning to step #1 with the current annotation,
+ * interface, or superclass as the class to look for annotations on.
* @param clazz the class to look for annotations on
* @param annotationType the type of annotation to look for
* @return the corresponding annotation descriptor if the annotation was found;
@@ -123,6 +121,15 @@ public abstract class MetaAnnotationUtils {
}
}
+ // Declared on interface?
+ for (Class<?> ifc : clazz.getInterfaces()) {
+ AnnotationDescriptor<T> descriptor = findAnnotationDescriptor(ifc, visited, annotationType);
+ if (descriptor != null) {
+ return new AnnotationDescriptor<T>(clazz, descriptor.getDeclaringClass(),
+ descriptor.getComposedAnnotation(), descriptor.getAnnotation());
+ }
+ }
+
// Declared on a superclass?
return findAnnotationDescriptor(clazz.getSuperclass(), visited, annotationType);
}
@@ -132,8 +139,9 @@ public abstract class MetaAnnotationUtils {
* in the inheritance hierarchy of the specified {@code clazz} (including
* the specified {@code clazz} itself) which declares at least one of the
* specified {@code annotationTypes}.
- * <p>This method traverses the annotations and superclasses of the specified
- * {@code clazz} if no annotation can be found on the given class itself.
+ * <p>This method traverses the annotations, interfaces, and superclasses
+ * of the specified {@code clazz} if no annotation can be found on the given
+ * class itself.
* <p>This method explicitly handles class-level annotations which are not
* declared as {@linkplain java.lang.annotation.Inherited inherited} <em>as
* well as meta-annotations</em>.
@@ -143,14 +151,12 @@ public abstract class MetaAnnotationUtils {
* the given class and return a corresponding {@code UntypedAnnotationDescriptor}
* if found.
* <li>Recursively search through all annotations that the given class declares.
+ * <li>Recursively search through all interfaces implemented by the given class.
* <li>Recursively search through the superclass hierarchy of the given class.
* </ol>
* <p>In this context, the term <em>recursively</em> means that the search
- * process continues by returning to step #1 with the current annotation or
- * superclass as the class to look for annotations on.
- * <p>If the supplied {@code clazz} is an interface, only the interface
- * itself will be checked; the inheritance hierarchy for interfaces will not
- * be traversed.
+ * process continues by returning to step #1 with the current annotation,
+ * interface, or superclass as the class to look for annotations on.
* @param clazz the class to look for annotations on
* @param annotationTypes the types of annotations to look for
* @return the corresponding annotation descriptor if one of the annotations
@@ -203,6 +209,15 @@ public abstract class MetaAnnotationUtils {
}
}
+ // Declared on interface?
+ for (Class<?> ifc : clazz.getInterfaces()) {
+ UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes(ifc, visited, annotationTypes);
+ if (descriptor != null) {
+ return new UntypedAnnotationDescriptor(clazz, descriptor.getDeclaringClass(),
+ descriptor.getComposedAnnotation(), descriptor.getAnnotation());
+ }
+ }
+
// Declared on a superclass?
return findAnnotationDescriptorForTypes(clazz.getSuperclass(), visited, annotationTypes);
}
diff --git a/spring-test/src/main/java/org/springframework/test/util/ReflectionTestUtils.java b/spring-test/src/main/java/org/springframework/test/util/ReflectionTestUtils.java
index f40227fb..4f508511 100644
--- a/spring-test/src/main/java/org/springframework/test/util/ReflectionTestUtils.java
+++ b/spring-test/src/main/java/org/springframework/test/util/ReflectionTestUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -60,6 +60,7 @@ import org.springframework.util.StringUtils;
* @author Juergen Hoeller
* @since 2.5
* @see ReflectionUtils
+ * @see AopTestUtils
*/
public class ReflectionTestUtils {
@@ -137,6 +138,9 @@ public class ReflectionTestUtils {
* Set the {@linkplain Field field} with the given {@code name}/{@code type}
* on the provided {@code targetObject}/{@code targetClass} to the supplied
* {@code value}.
+ * <p>If the supplied {@code targetObject} is a <em>proxy</em>, it will
+ * be {@linkplain AopTestUtils#getUltimateTargetObject unwrapped} allowing
+ * the field to be set on the ultimate target of the proxy.
* <p>This method traverses the class hierarchy in search of the desired
* field. In addition, an attempt will be made to make non-{@code public}
* fields <em>accessible</em>, thus allowing one to set {@code protected},
@@ -150,33 +154,36 @@ public class ReflectionTestUtils {
* @param value the value to set
* @param type the type of the field to set; may be {@code null} if
* {@code name} is specified
+ * @since 4.2
* @see ReflectionUtils#findField(Class, String, Class)
* @see ReflectionUtils#makeAccessible(Field)
* @see ReflectionUtils#setField(Field, Object, Object)
- * @since 4.2
+ * @see AopTestUtils#getUltimateTargetObject(Object)
*/
public static void setField(Object targetObject, Class<?> targetClass, String name, Object value, Class<?> type) {
Assert.isTrue(targetObject != null || targetClass != null,
"Either targetObject or targetClass for the field must be specified");
+ Object ultimateTarget = (targetObject != null ? AopTestUtils.getUltimateTargetObject(targetObject) : null);
+
if (targetClass == null) {
- targetClass = targetObject.getClass();
+ targetClass = ultimateTarget.getClass();
}
Field field = ReflectionUtils.findField(targetClass, name, type);
if (field == null) {
throw new IllegalArgumentException(String.format(
- "Could not find field '%s' of type [%s] on target object [%s] or target class [%s]", name, type,
- targetObject, targetClass));
+ "Could not find field '%s' of type [%s] on %s or target class [%s]", name, type,
+ safeToString(ultimateTarget), targetClass));
}
if (logger.isDebugEnabled()) {
logger.debug(String.format(
- "Setting field '%s' of type [%s] on target object [%s] or target class [%s] to value [%s]", name, type,
- targetObject, targetClass, value));
+ "Setting field '%s' of type [%s] on %s or target class [%s] to value [%s]", name, type,
+ safeToString(ultimateTarget), targetClass, value));
}
ReflectionUtils.makeAccessible(field);
- ReflectionUtils.setField(field, targetObject, value);
+ ReflectionUtils.setField(field, ultimateTarget, value);
}
/**
@@ -213,6 +220,9 @@ public class ReflectionTestUtils {
/**
* Get the value of the {@linkplain Field field} with the given {@code name}
* from the provided {@code targetObject}/{@code targetClass}.
+ * <p>If the supplied {@code targetObject} is a <em>proxy</em>, it will
+ * be {@linkplain AopTestUtils#getUltimateTargetObject unwrapped} allowing
+ * the field to be retrieved from the ultimate target of the proxy.
* <p>This method traverses the class hierarchy in search of the desired
* field. In addition, an attempt will be made to make non-{@code public}
* fields <em>accessible</em>, thus allowing one to get {@code protected},
@@ -229,28 +239,30 @@ public class ReflectionTestUtils {
* @see ReflectionUtils#findField(Class, String, Class)
* @see ReflectionUtils#makeAccessible(Field)
* @see ReflectionUtils#getField(Field, Object)
+ * @see AopTestUtils#getUltimateTargetObject(Object)
*/
public static Object getField(Object targetObject, Class<?> targetClass, String name) {
Assert.isTrue(targetObject != null || targetClass != null,
"Either targetObject or targetClass for the field must be specified");
+ Object ultimateTarget = (targetObject != null ? AopTestUtils.getUltimateTargetObject(targetObject) : null);
+
if (targetClass == null) {
- targetClass = targetObject.getClass();
+ targetClass = ultimateTarget.getClass();
}
Field field = ReflectionUtils.findField(targetClass, name);
if (field == null) {
- throw new IllegalArgumentException(
- String.format("Could not find field '%s' on target object [%s] or target class [%s]", name,
- targetObject, targetClass));
+ throw new IllegalArgumentException(String.format("Could not find field '%s' on %s or target class [%s]",
+ name, safeToString(ultimateTarget), targetClass));
}
if (logger.isDebugEnabled()) {
- logger.debug(String.format("Getting field '%s' from target object [%s] or target class [%s]", name,
- targetObject, targetClass));
+ logger.debug(String.format("Getting field '%s' from %s or target class [%s]", name,
+ safeToString(ultimateTarget), targetClass));
}
ReflectionUtils.makeAccessible(field);
- return ReflectionUtils.getField(field, targetObject);
+ return ReflectionUtils.getField(field, ultimateTarget);
}
/**
@@ -314,13 +326,16 @@ public class ReflectionTestUtils {
method = ReflectionUtils.findMethod(target.getClass(), setterMethodName, paramTypes);
}
if (method == null) {
- throw new IllegalArgumentException("Could not find setter method '" + setterMethodName +
- "' on target [" + target + "] with parameter type [" + type + "]");
+ throw new IllegalArgumentException(String.format(
+ "Could not find setter method '%s' on %s with parameter type [%s]", setterMethodName,
+ safeToString(target), type));
}
if (logger.isDebugEnabled()) {
- logger.debug("Invoking setter method '" + setterMethodName + "' on target [" + target + "]");
+ logger.debug(String.format("Invoking setter method '%s' on %s with value [%s]", setterMethodName,
+ safeToString(target), value));
}
+
ReflectionUtils.makeAccessible(method);
ReflectionUtils.invokeMethod(method, target, value);
}
@@ -359,12 +374,12 @@ public class ReflectionTestUtils {
method = ReflectionUtils.findMethod(target.getClass(), getterMethodName);
}
if (method == null) {
- throw new IllegalArgumentException("Could not find getter method '" + getterMethodName +
- "' on target [" + target + "]");
+ throw new IllegalArgumentException(String.format(
+ "Could not find getter method '%s' on %s", getterMethodName, safeToString(target)));
}
if (logger.isDebugEnabled()) {
- logger.debug("Invoking getter method '" + getterMethodName + "' on target [" + target + "]");
+ logger.debug(String.format("Invoking getter method '%s' on %s", getterMethodName, safeToString(target)));
}
ReflectionUtils.makeAccessible(method);
return ReflectionUtils.invokeMethod(method, target);
@@ -399,8 +414,8 @@ public class ReflectionTestUtils {
methodInvoker.prepare();
if (logger.isDebugEnabled()) {
- logger.debug("Invoking method '" + name + "' on target [" + target + "] with arguments [" +
- ObjectUtils.nullSafeToString(args) + "]");
+ logger.debug(String.format("Invoking method '%s' on %s with arguments %s", name, safeToString(target),
+ ObjectUtils.nullSafeToString(args)));
}
return (T) methodInvoker.invoke();
@@ -411,4 +426,14 @@ public class ReflectionTestUtils {
}
}
+ private static String safeToString(Object target) {
+ try {
+ return String.format("target object [%s]", target);
+ }
+ catch (Exception ex) {
+ return String.format("target of type [%s] whose toString() method threw [%s]",
+ (target != null ? target.getClass().getName() : "unknown"), ex);
+ }
+ }
+
}
diff --git a/spring-test/src/main/java/org/springframework/test/web/client/AbstractRequestExpectationManager.java b/spring-test/src/main/java/org/springframework/test/web/client/AbstractRequestExpectationManager.java
new file mode 100644
index 00000000..37139cb7
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/web/client/AbstractRequestExpectationManager.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.web.client;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import org.springframework.http.HttpMethod;
+import org.springframework.http.client.ClientHttpRequest;
+import org.springframework.http.client.ClientHttpResponse;
+import org.springframework.util.Assert;
+
+/**
+ * Base class for {@code RequestExpectationManager} implementations responsible
+ * for storing expectations and actual requests, and checking for unsatisfied
+ * expectations at the end.
+ *
+ * <p>Subclasses are responsible for validating each request by matching it to
+ * to expectations following the order of declaration or not.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3
+ */
+public abstract class AbstractRequestExpectationManager implements RequestExpectationManager {
+
+ private final List<RequestExpectation> expectations = new LinkedList<RequestExpectation>();
+
+ private final List<ClientHttpRequest> requests = new LinkedList<ClientHttpRequest>();
+
+
+ protected List<RequestExpectation> getExpectations() {
+ return this.expectations;
+ }
+
+ protected List<ClientHttpRequest> getRequests() {
+ return this.requests;
+ }
+
+
+ @Override
+ public ResponseActions expectRequest(ExpectedCount count, RequestMatcher matcher) {
+ Assert.state(getRequests().isEmpty(), "Cannot add more expectations after actual requests are made");
+ RequestExpectation expectation = new DefaultRequestExpectation(count, matcher);
+ getExpectations().add(expectation);
+ return expectation;
+ }
+
+ @Override
+ public ClientHttpResponse validateRequest(ClientHttpRequest request) throws IOException {
+ if (getRequests().isEmpty()) {
+ afterExpectationsDeclared();
+ }
+ ClientHttpResponse response = validateRequestInternal(request);
+ getRequests().add(request);
+ return response;
+ }
+
+ /**
+ * Invoked after the phase of declaring expected requests is over. This is
+ * detected from {@link #validateRequest} on the first actual request.
+ */
+ protected void afterExpectationsDeclared() {
+ }
+
+ /**
+ * Subclasses must implement the actual validation of the request
+ * matching it to a declared expectation.
+ */
+ protected abstract ClientHttpResponse validateRequestInternal(ClientHttpRequest request) throws IOException;
+
+ @Override
+ public void verify() {
+ if (getExpectations().isEmpty()) {
+ return;
+ }
+ int count = 0;
+ for (RequestExpectation expectation : getExpectations()) {
+ if (!expectation.isSatisfied()) {
+ count++;
+ }
+ }
+ if (count > 0) {
+ String message = "Further request(s) expected leaving " + count + " unsatisfied expectation(s).\n";
+ throw new AssertionError(message + getRequestDetails());
+ }
+ }
+
+ /**
+ * Return details of executed requests.
+ */
+ protected String getRequestDetails() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(getRequests().size()).append(" request(s) executed");
+ if (!getRequests().isEmpty()) {
+ sb.append(":\n");
+ for (ClientHttpRequest request : getRequests()) {
+ sb.append(request.toString()).append("\n");
+ }
+ }
+ else {
+ sb.append(".\n");
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Return an {@code AssertionError} that a sub-class can raise for an
+ * unexpected request.
+ */
+ protected AssertionError createUnexpectedRequestError(ClientHttpRequest request) {
+ HttpMethod method = request.getMethod();
+ URI uri = request.getURI();
+ String message = "No further requests expected: HTTP " + method + " " + uri + "\n";
+ return new AssertionError(message + getRequestDetails());
+ }
+
+ @Override
+ public void reset() {
+ this.expectations.clear();
+ this.requests.clear();
+ }
+
+
+ /**
+ * Helper class to manage a group of request expectations. It helps with
+ * operations against the entire group such as finding a match and updating
+ * (add or remove) based on expected request count.
+ */
+ protected static class RequestExpectationGroup {
+
+ private final Set<RequestExpectation> expectations = new LinkedHashSet<RequestExpectation>();
+
+ public Set<RequestExpectation> getExpectations() {
+ return this.expectations;
+ }
+
+ public void update(RequestExpectation expectation) {
+ if (expectation.hasRemainingCount()) {
+ getExpectations().add(expectation);
+ }
+ else {
+ getExpectations().remove(expectation);
+ }
+ }
+
+ public void updateAll(Collection<RequestExpectation> expectations) {
+ for (RequestExpectation expectation : expectations) {
+ update(expectation);
+ }
+ }
+
+ public RequestExpectation findExpectation(ClientHttpRequest request) throws IOException {
+ for (RequestExpectation expectation : getExpectations()) {
+ try {
+ expectation.match(request);
+ return expectation;
+ }
+ catch (AssertionError error) {
+ // Ignore
+ }
+ }
+ return null;
+ }
+
+ public void reset() {
+ this.expectations.clear();
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/web/client/DefaultRequestExpectation.java b/spring-test/src/main/java/org/springframework/test/web/client/DefaultRequestExpectation.java
new file mode 100644
index 00000000..93a4752a
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/web/client/DefaultRequestExpectation.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.web.client;
+
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.springframework.http.client.ClientHttpRequest;
+import org.springframework.http.client.ClientHttpResponse;
+import org.springframework.util.Assert;
+
+/**
+ * Default implementation of {@code RequestExpectation} that simply delegates
+ * to the request matchers and the response creator it contains.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3
+ */
+public class DefaultRequestExpectation implements RequestExpectation {
+
+ private final RequestCount requestCount;
+
+ private final List<RequestMatcher> requestMatchers = new LinkedList<RequestMatcher>();
+
+ private ResponseCreator responseCreator;
+
+
+ /**
+ * Create a new request expectation that should be called a number of times
+ * as indicated by {@code RequestCount}.
+ * @param expectedCount the expected request expectedCount
+ */
+ public DefaultRequestExpectation(ExpectedCount expectedCount, RequestMatcher requestMatcher) {
+ Assert.notNull(expectedCount, "'expectedCount' is required");
+ Assert.notNull(requestMatcher, "'requestMatcher' is required");
+ this.requestCount = new RequestCount(expectedCount);
+ this.requestMatchers.add(requestMatcher);
+ }
+
+
+ protected RequestCount getRequestCount() {
+ return this.requestCount;
+ }
+
+ protected List<RequestMatcher> getRequestMatchers() {
+ return this.requestMatchers;
+ }
+
+ protected ResponseCreator getResponseCreator() {
+ return this.responseCreator;
+ }
+
+ @Override
+ public ResponseActions andExpect(RequestMatcher requestMatcher) {
+ Assert.notNull(requestMatcher, "RequestMatcher is required");
+ this.requestMatchers.add(requestMatcher);
+ return this;
+ }
+
+ @Override
+ public void andRespond(ResponseCreator responseCreator) {
+ Assert.notNull(responseCreator, "ResponseCreator is required");
+ this.responseCreator = responseCreator;
+ }
+
+ @Override
+ public void match(ClientHttpRequest request) throws IOException {
+ for (RequestMatcher matcher : getRequestMatchers()) {
+ matcher.match(request);
+ }
+ }
+
+ @Override
+ public ClientHttpResponse createResponse(ClientHttpRequest request) throws IOException {
+ ResponseCreator responseCreator = getResponseCreator();
+ if (responseCreator == null) {
+ throw new IllegalStateException("createResponse called before ResponseCreator was set");
+ }
+ getRequestCount().incrementAndValidate();
+ return responseCreator.createResponse(request);
+ }
+
+ @Override
+ public boolean hasRemainingCount() {
+ return getRequestCount().hasRemainingCount();
+ }
+
+ @Override
+ public boolean isSatisfied() {
+ return getRequestCount().isSatisfied();
+ }
+
+
+ /**
+ * Helper class that keeps track of actual vs expected request count.
+ */
+ protected static class RequestCount {
+
+ private final ExpectedCount expectedCount;
+
+ private int matchedRequestCount;
+
+ public RequestCount(ExpectedCount expectedCount) {
+ this.expectedCount = expectedCount;
+ }
+
+ public ExpectedCount getExpectedCount() {
+ return this.expectedCount;
+ }
+
+ public int getMatchedRequestCount() {
+ return this.matchedRequestCount;
+ }
+
+ public void incrementAndValidate() {
+ this.matchedRequestCount++;
+ if (getMatchedRequestCount() > getExpectedCount().getMaxCount()) {
+ throw new AssertionError("No more calls expected.");
+ }
+ }
+
+ public boolean hasRemainingCount() {
+ return (getMatchedRequestCount() < getExpectedCount().getMaxCount());
+ }
+
+ public boolean isSatisfied() {
+ return (getMatchedRequestCount() >= getExpectedCount().getMinCount());
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/web/client/ExpectedCount.java b/spring-test/src/main/java/org/springframework/test/web/client/ExpectedCount.java
new file mode 100644
index 00000000..09f3077c
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/web/client/ExpectedCount.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.web.client;
+
+import org.springframework.util.Assert;
+
+/**
+ * A simple type representing a range for an expected count.
+ *
+ * <p>Examples:
+ * <pre>
+ * import static org.springframework.test.web.client.ExpectedCount.*
+ *
+ * once()
+ * manyTimes()
+ * times(5)
+ * min(2)
+ * max(4)
+ * between(2, 4)
+ * </pre>
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3
+ */
+public class ExpectedCount {
+
+ private final int minCount;
+
+ private final int maxCount;
+
+
+ /**
+ * Private constructor.
+ * See static factory methods in this class.
+ */
+ private ExpectedCount(int minCount, int maxCount) {
+ Assert.isTrue(minCount >= 1, "minCount >= 0 is required");
+ Assert.isTrue(maxCount >= minCount, "maxCount >= minCount is required");
+ this.minCount = minCount;
+ this.maxCount = maxCount;
+ }
+
+
+ /**
+ * Return the {@code min} boundary of the expected count range.
+ */
+ public int getMinCount() {
+ return this.minCount;
+ }
+
+ /**
+ * Return the {@code max} boundary of the expected count range.
+ */
+ public int getMaxCount() {
+ return this.maxCount;
+ }
+
+
+ /**
+ * Exactly once.
+ */
+ public static ExpectedCount once() {
+ return new ExpectedCount(1, 1);
+ }
+
+ /**
+ * Many times (range of 1..Integer.MAX_VALUE).
+ */
+ public static ExpectedCount manyTimes() {
+ return new ExpectedCount(1, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Exactly N times.
+ */
+ public static ExpectedCount times(int count) {
+ Assert.isTrue(count >= 1, "'count' must be >= 1");
+ return new ExpectedCount(count, count);
+ }
+
+ /**
+ * At least {@code min} number of times.
+ */
+ public static ExpectedCount min(int min) {
+ Assert.isTrue(min >= 1, "'min' must be >= 1");
+ return new ExpectedCount(min, Integer.MAX_VALUE);
+ }
+
+ /**
+ * At most {@code max} number of times.
+ */
+ public static ExpectedCount max(int max) {
+ Assert.isTrue(max >= 1, "'max' must be >= 1");
+ return new ExpectedCount(1, max);
+ }
+
+ /**
+ * Between {@code min} and {@code max} number of times.
+ */
+ public static ExpectedCount between(int min, int max) {
+ return new ExpectedCount(min, max);
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/web/client/MockMvcClientHttpRequestFactory.java b/spring-test/src/main/java/org/springframework/test/web/client/MockMvcClientHttpRequestFactory.java
index 7c7b6115..d1086a53 100644
--- a/spring-test/src/main/java/org/springframework/test/web/client/MockMvcClientHttpRequestFactory.java
+++ b/spring-test/src/main/java/org/springframework/test/web/client/MockMvcClientHttpRequestFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.test.web.client;
import java.io.IOException;
import java.net.URI;
+import java.nio.charset.Charset;
import java.util.List;
import org.springframework.http.HttpHeaders;
@@ -43,6 +44,8 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
*/
public class MockMvcClientHttpRequestFactory implements ClientHttpRequestFactory {
+ private static final Charset UTF8_CHARSET = Charset.forName("UTF-8");
+
private final MockMvc mockMvc;
@@ -50,6 +53,7 @@ public class MockMvcClientHttpRequestFactory implements ClientHttpRequestFactory
this.mockMvc = mockMvc;
}
+
@Override
public ClientHttpRequest createRequest(final URI uri, final HttpMethod httpMethod) throws IOException {
return new MockClientHttpRequest(httpMethod, uri) {
@@ -73,7 +77,7 @@ public class MockMvcClientHttpRequestFactory implements ClientHttpRequestFactory
return clientResponse;
}
catch (Exception ex) {
- byte[] body = ex.toString().getBytes("UTF-8");
+ byte[] body = ex.toString().getBytes(UTF8_CHARSET);
return new MockClientHttpResponse(body, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
diff --git a/spring-test/src/main/java/org/springframework/test/web/client/MockRestServiceServer.java b/spring-test/src/main/java/org/springframework/test/web/client/MockRestServiceServer.java
index e1376041..09207f8c 100644
--- a/spring-test/src/main/java/org/springframework/test/web/client/MockRestServiceServer.java
+++ b/spring-test/src/main/java/org/springframework/test/web/client/MockRestServiceServer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,17 +18,14 @@ package org.springframework.test.web.client;
import java.io.IOException;
import java.net.URI;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.AsyncClientHttpRequest;
import org.springframework.http.client.AsyncClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory;
-import org.springframework.test.web.client.match.MockRestRequestMatchers;
-import org.springframework.test.web.client.response.MockRestResponseCreators;
+import org.springframework.http.client.ClientHttpResponse;
+import org.springframework.mock.http.client.MockAsyncClientHttpRequest;
import org.springframework.util.Assert;
import org.springframework.web.client.AsyncRestTemplate;
import org.springframework.web.client.RestTemplate;
@@ -36,53 +33,33 @@ import org.springframework.web.client.support.RestGatewaySupport;
/**
* <strong>Main entry point for client-side REST testing</strong>. Used for tests
- * that involve direct or indirect (through client code) use of the
- * {@link RestTemplate}. Provides a way to set up fine-grained expectations
- * on the requests that will be performed through the {@code RestTemplate} and
- * a way to define the responses to send back removing the need for an
- * actual running server.
+ * that involve direct or indirect use of the {@link RestTemplate}. Provides a
+ * way to set up expected requests that will be performed through the
+ * {@code RestTemplate} as well as mock responses to send back thus removing the
+ * need for an actual server.
*
- * <p>Below is an example:
- * <pre class="code">
- * import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
- * import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
- * import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
- *
- * ...
+ * <p>Below is an example that assumes static imports from
+ * {@code MockRestRequestMatchers}, {@code MockRestResponseCreators},
+ * and {@code ExpectedCount}:
*
+ * <pre class="code">
* RestTemplate restTemplate = new RestTemplate()
- * MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate);
+ * MockRestServiceServer server = MockRestServiceServer.bindTo(restTemplate).build();
*
- * mockServer.expect(requestTo("/hotels/42")).andExpect(method(HttpMethod.GET))
+ * server.expect(manyTimes(), requestTo("/hotels/42")).andExpect(method(HttpMethod.GET))
* .andRespond(withSuccess("{ \"id\" : \"42\", \"name\" : \"Holiday Inn\"}", MediaType.APPLICATION_JSON));
*
* Hotel hotel = restTemplate.getForObject("/hotels/{id}", Hotel.class, 42);
* &#47;&#47; Use the hotel instance...
*
- * mockServer.verify();
+ * // Verify all expectations met
+ * server.verify();
* </pre>
*
- * <p>To create an instance of this class, use {@link #createServer(RestTemplate)}
- * and provide the {@code RestTemplate} to set up for the mock testing.
- *
- * <p>After that use {@link #expect(RequestMatcher)} and fluent API methods
- * {@link ResponseActions#andExpect(RequestMatcher) andExpect(RequestMatcher)} and
- * {@link ResponseActions#andRespond(ResponseCreator) andRespond(ResponseCreator)}
- * to set up request expectations and responses, most likely relying on the default
- * {@code RequestMatcher} implementations provided in {@link MockRestRequestMatchers}
- * and the {@code ResponseCreator} implementations provided in
- * {@link MockRestResponseCreators} both of which can be statically imported.
- *
- * <p>At the end of the test use {@link #verify()} to ensure all expected
- * requests were actually performed.
- *
- * <p>Note that because of the fluent API offered by this class (and related
- * classes), you can typically use the Code Completion features (i.e.
- * ctrl-space) in your IDE to set up the mocks.
- *
- * <p><strong>Credits:</strong> The client-side REST testing support was
- * inspired by and initially based on similar code in the Spring WS project for
- * client-side tests involving the {@code WebServiceTemplate}.
+ * <p>Note that as an alternative to the above you can also set the
+ * {@link MockMvcClientHttpRequestFactory} on a {@code RestTemplate} which
+ * allows executing requests against an instance of
+ * {@link org.springframework.test.web.servlet.MockMvc MockMvc}.
*
* @author Craig Walls
* @author Rossen Stoyanchev
@@ -90,143 +67,230 @@ import org.springframework.web.client.support.RestGatewaySupport;
*/
public class MockRestServiceServer {
- private final List<RequestMatcherClientHttpRequest> expectedRequests =
- new LinkedList<RequestMatcherClientHttpRequest>();
+ private final RequestExpectationManager expectationManager;
+
+
+ /**
+ * Private constructor with {@code RequestExpectationManager}.
+ * See static builder methods and {@code createServer} shortcut methods.
+ */
+ private MockRestServiceServer(RequestExpectationManager expectationManager) {
+ this.expectationManager = expectationManager;
+ }
+
+
+ /**
+ * Set up an expectation for a single HTTP request. The returned
+ * {@link ResponseActions} can be used to set up further expectations as
+ * well as to define the response.
+ * <p>This method may be invoked any number times before starting to make
+ * request through the underlying {@code RestTemplate} in order to set up
+ * all expected requests.
+ * @param matcher request matcher
+ * @return a representation of the expectation
+ */
+ public ResponseActions expect(RequestMatcher matcher) {
+ return expect(ExpectedCount.once(), matcher);
+ }
+
+ /**
+ * An alternative to {@link #expect(RequestMatcher)} with an indication how
+ * many times the request is expected to be executed.
+ * <p>When request expectations have an expected count greater than one, only
+ * the first execution is expected to match the order of declaration. Subsequent
+ * request executions may be inserted anywhere thereafter.
+ * @param count the expected count
+ * @param matcher request matcher
+ * @return a representation of the expectation
+ * @since 4.3
+ */
+ public ResponseActions expect(ExpectedCount count, RequestMatcher matcher) {
+ return this.expectationManager.expectRequest(count, matcher);
+ }
+
+ /**
+ * Verify that all expected requests set up via
+ * {@link #expect(RequestMatcher)} were indeed performed.
+ * @throws AssertionError when some expectations were not met
+ */
+ public void verify() {
+ this.expectationManager.verify();
+ }
- private final List<RequestMatcherClientHttpRequest> actualRequests =
- new LinkedList<RequestMatcherClientHttpRequest>();
+ /**
+ * Reset the internal state removing all expectations and recorded requests.
+ */
+ public void reset() {
+ this.expectationManager.reset();
+ }
+
+
+ /**
+ * Return a builder for a {@code MockRestServiceServer} that should be used
+ * to reply to the given {@code RestTemplate}.
+ * @since 4.3
+ */
+ public static MockRestServiceServerBuilder bindTo(RestTemplate restTemplate) {
+ return new DefaultBuilder(restTemplate);
+ }
+ /**
+ * Return a builder for a {@code MockRestServiceServer} that should be used
+ * to reply to the given {@code AsyncRestTemplate}.
+ * @since 4.3
+ */
+ public static MockRestServiceServerBuilder bindTo(AsyncRestTemplate asyncRestTemplate) {
+ return new DefaultBuilder(asyncRestTemplate);
+ }
/**
- * Private constructor.
- * @see #createServer(RestTemplate)
- * @see #createServer(RestGatewaySupport)
+ * Return a builder for a {@code MockRestServiceServer} that should be used
+ * to reply to the given {@code RestGatewaySupport}.
+ * @since 4.3
*/
- private MockRestServiceServer() {
+ public static MockRestServiceServerBuilder bindTo(RestGatewaySupport restGateway) {
+ Assert.notNull(restGateway, "'gatewaySupport' must not be null");
+ return new DefaultBuilder(restGateway.getRestTemplate());
}
/**
- * Create a {@code MockRestServiceServer} and set up the given
- * {@code RestTemplate} with a mock {@link ClientHttpRequestFactory}.
+ * A shortcut for {@code bindTo(restTemplate).build()}.
* @param restTemplate the RestTemplate to set up for mock testing
- * @return the created mock server
+ * @return the mock server
*/
public static MockRestServiceServer createServer(RestTemplate restTemplate) {
- Assert.notNull(restTemplate, "'restTemplate' must not be null");
- MockRestServiceServer mockServer = new MockRestServiceServer();
- RequestMatcherClientHttpRequestFactory factory = mockServer.new RequestMatcherClientHttpRequestFactory();
- restTemplate.setRequestFactory(factory);
- return mockServer;
+ return bindTo(restTemplate).build();
}
/**
- * Create a {@code MockRestServiceServer} and set up the given
- * {@code AsyRestTemplate} with a mock {@link AsyncClientHttpRequestFactory}.
+ * A shortcut for {@code bindTo(asyncRestTemplate).build()}.
* @param asyncRestTemplate the AsyncRestTemplate to set up for mock testing
* @return the created mock server
*/
public static MockRestServiceServer createServer(AsyncRestTemplate asyncRestTemplate) {
- Assert.notNull(asyncRestTemplate, "'asyncRestTemplate' must not be null");
- MockRestServiceServer mockServer = new MockRestServiceServer();
- RequestMatcherClientHttpRequestFactory factory = mockServer.new RequestMatcherClientHttpRequestFactory();
- asyncRestTemplate.setAsyncRequestFactory(factory);
- return mockServer;
+ return bindTo(asyncRestTemplate).build();
}
/**
- * Create a {@code MockRestServiceServer} and set up the given
- * {@code RestGatewaySupport} with a mock {@link ClientHttpRequestFactory}.
+ * A shortcut for {@code bindTo(restGateway).build()}.
* @param restGateway the REST gateway to set up for mock testing
* @return the created mock server
*/
public static MockRestServiceServer createServer(RestGatewaySupport restGateway) {
- Assert.notNull(restGateway, "'gatewaySupport' must not be null");
- return createServer(restGateway.getRestTemplate());
+ return bindTo(restGateway).build();
}
/**
- * Set up a new HTTP request expectation. The returned {@link ResponseActions}
- * is used to set up further expectations and to define the response.
- * <p>This method may be invoked multiple times before starting the test, i.e. before
- * using the {@code RestTemplate}, to set up expectations for multiple requests.
- * @param requestMatcher a request expectation, see {@link MockRestRequestMatchers}
- * @return used to set up further expectations or to define a response
+ * Builder to create a {@code MockRestServiceServer}.
*/
- public ResponseActions expect(RequestMatcher requestMatcher) {
- Assert.state(this.actualRequests.isEmpty(), "Can't add more expected requests with test already underway");
- RequestMatcherClientHttpRequest request = new RequestMatcherClientHttpRequest(requestMatcher);
- this.expectedRequests.add(request);
- return request;
+ public interface MockRestServiceServerBuilder {
+
+ /**
+ * Whether to allow expected requests to be executed in any order not
+ * necessarily matching the order of declaration.
+ * <p>When set to "true" this is effectively a shortcut for:<br>
+ * {@code builder.build(new UnorderedRequestExpectationManager)}.
+ * @param ignoreExpectOrder whether to ignore the order of expectations
+ */
+ MockRestServiceServerBuilder ignoreExpectOrder(boolean ignoreExpectOrder);
+
+ /**
+ * Build the {@code MockRestServiceServer} and set up the underlying
+ * {@code RestTemplate} or {@code AsyncRestTemplate} with a
+ * {@link ClientHttpRequestFactory} that creates mock requests.
+ */
+ MockRestServiceServer build();
+
+ /**
+ * An overloaded build alternative that accepts a custom
+ * {@link RequestExpectationManager}.
+ */
+ MockRestServiceServer build(RequestExpectationManager manager);
}
- /**
- * Verify that all expected requests set up via
- * {@link #expect(RequestMatcher)} were indeed performed.
- * @throws AssertionError when some expectations were not met
- */
- public void verify() {
- if (this.expectedRequests.isEmpty() || this.expectedRequests.equals(this.actualRequests)) {
- return;
+
+ private static class DefaultBuilder implements MockRestServiceServerBuilder {
+
+ private final RestTemplate restTemplate;
+
+ private final AsyncRestTemplate asyncRestTemplate;
+
+ private boolean ignoreExpectOrder;
+
+ public DefaultBuilder(RestTemplate restTemplate) {
+ Assert.notNull(restTemplate, "RestTemplate must not be null");
+ this.restTemplate = restTemplate;
+ this.asyncRestTemplate = null;
+ }
+
+ public DefaultBuilder(AsyncRestTemplate asyncRestTemplate) {
+ Assert.notNull(asyncRestTemplate, "AsyncRestTemplate must not be null");
+ this.restTemplate = null;
+ this.asyncRestTemplate = asyncRestTemplate;
+ }
+
+ @Override
+ public MockRestServiceServerBuilder ignoreExpectOrder(boolean ignoreExpectOrder) {
+ this.ignoreExpectOrder = ignoreExpectOrder;
+ return this;
}
- throw new AssertionError(getVerifyMessage());
- }
- private String getVerifyMessage() {
- StringBuilder sb = new StringBuilder("Further request(s) expected\n");
- if (this.actualRequests.size() > 0) {
- sb.append("The following ");
+ @Override
+ public MockRestServiceServer build() {
+ if (this.ignoreExpectOrder) {
+ return build(new UnorderedRequestExpectationManager());
+ }
+ else {
+ return build(new SimpleRequestExpectationManager());
+ }
}
- sb.append(this.actualRequests.size()).append(" out of ");
- sb.append(this.expectedRequests.size()).append(" were executed");
- if (this.actualRequests.size() > 0) {
- sb.append(":\n");
- for (RequestMatcherClientHttpRequest request : this.actualRequests) {
- sb.append(request.toString()).append("\n");
+ @Override
+ public MockRestServiceServer build(RequestExpectationManager manager) {
+ MockRestServiceServer server = new MockRestServiceServer(manager);
+ MockClientHttpRequestFactory factory = server.new MockClientHttpRequestFactory();
+ if (this.restTemplate != null) {
+ this.restTemplate.setRequestFactory(factory);
+ }
+ if (this.asyncRestTemplate != null) {
+ this.asyncRestTemplate.setAsyncRequestFactory(factory);
}
+ return server;
}
- return sb.toString();
}
/**
* Mock ClientHttpRequestFactory that creates requests by iterating
- * over the list of expected {@link RequestMatcherClientHttpRequest}'s.
+ * over the list of expected {@link DefaultRequestExpectation}'s.
*/
- private class RequestMatcherClientHttpRequestFactory
- implements ClientHttpRequestFactory, AsyncClientHttpRequestFactory {
-
- private Iterator<RequestMatcherClientHttpRequest> requestIterator;
+ private class MockClientHttpRequestFactory implements ClientHttpRequestFactory, AsyncClientHttpRequestFactory {
@Override
- public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
+ public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) {
return createRequestInternal(uri, httpMethod);
}
@Override
- public AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod) throws IOException {
+ public AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod) {
return createRequestInternal(uri, httpMethod);
}
- private RequestMatcherClientHttpRequest createRequestInternal(URI uri, HttpMethod httpMethod) {
+ private MockAsyncClientHttpRequest createRequestInternal(URI uri, HttpMethod method) {
Assert.notNull(uri, "'uri' must not be null");
- Assert.notNull(httpMethod, "'httpMethod' must not be null");
-
- if (this.requestIterator == null) {
- this.requestIterator = MockRestServiceServer.this.expectedRequests.iterator();
- }
- if (!this.requestIterator.hasNext()) {
- throw new AssertionError("No further requests expected: HTTP " + httpMethod + " " + uri);
- }
+ Assert.notNull(method, "'httpMethod' must not be null");
- RequestMatcherClientHttpRequest request = this.requestIterator.next();
- request.setURI(uri);
- request.setMethod(httpMethod);
+ return new MockAsyncClientHttpRequest(method, uri) {
- MockRestServiceServer.this.actualRequests.add(request);
- return request;
+ @Override
+ protected ClientHttpResponse executeInternal() throws IOException {
+ ClientHttpResponse response = expectationManager.validateRequest(this);
+ setResponse(response);
+ return response;
+ }
+ };
}
}
diff --git a/spring-test/src/main/java/org/springframework/test/web/client/RequestExpectation.java b/spring-test/src/main/java/org/springframework/test/web/client/RequestExpectation.java
new file mode 100644
index 00000000..b2d8f144
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/web/client/RequestExpectation.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.web.client;
+
+/**
+ * An extension of {@code ResponseActions} that also implements
+ * {@code RequestMatcher} and {@code ResponseCreator}
+ *
+ * <p>While {@code ResponseActions} is the API for defining expectations this
+ * sub-interface is the internal SPI for matching these expectations to actual
+ * requests and for creating responses.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3
+ */
+public interface RequestExpectation extends ResponseActions, RequestMatcher, ResponseCreator {
+
+ /**
+ * Whether there is a remaining count of invocations for this expectation.
+ */
+ boolean hasRemainingCount();
+
+ /**
+ * Whether the requirements for this request expectation have been met.
+ */
+ boolean isSatisfied();
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/web/client/RequestExpectationManager.java b/spring-test/src/main/java/org/springframework/test/web/client/RequestExpectationManager.java
new file mode 100644
index 00000000..a79f9210
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/web/client/RequestExpectationManager.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.web.client;
+
+import java.io.IOException;
+
+import org.springframework.http.client.ClientHttpRequest;
+import org.springframework.http.client.ClientHttpResponse;
+
+/**
+ * Abstraction for creating HTTP request expectations, applying them to actual
+ * requests (in strict or random order), and verifying whether expectations
+ * have been met.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3
+ */
+public interface RequestExpectationManager {
+
+ /**
+ * Set up a new request expectation. The returned {@link ResponseActions} is
+ * used to add more expectations and define a response.
+ * @param requestMatcher a request expectation
+ * @return for setting up further expectations and define a response
+ */
+ ResponseActions expectRequest(ExpectedCount count, RequestMatcher requestMatcher);
+
+ /**
+ * Validate the given actual request against the declared expectations.
+ * Is successful return the mock response to use or raise an error.
+ * @param request the request
+ * @return the response to return if the request was validated.
+ * @throws AssertionError when some expectations were not met
+ * @throws IOException
+ */
+ ClientHttpResponse validateRequest(ClientHttpRequest request) throws IOException;
+
+ /**
+ * Verify that all expectations have been met.
+ * @throws AssertionError when some expectations were not met
+ */
+ void verify();
+
+ /**
+ * Reset the internal state removing all expectations and recorded requests.
+ */
+ void reset();
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/web/client/RequestMatcher.java b/spring-test/src/main/java/org/springframework/test/web/client/RequestMatcher.java
index bc169f48..14237729 100644
--- a/spring-test/src/main/java/org/springframework/test/web/client/RequestMatcher.java
+++ b/spring-test/src/main/java/org/springframework/test/web/client/RequestMatcher.java
@@ -23,6 +23,9 @@ import org.springframework.http.client.ClientHttpRequest;
/**
* A contract for matching requests to expectations.
*
+ * <p>See {@link org.springframework.test.web.client.match.MockRestRequestMatchers
+ * MockRestRequestMatchers} for static factory methods.
+ *
* @author Craig Walls
* @since 3.2
*/
diff --git a/spring-test/src/main/java/org/springframework/test/web/client/RequestMatcherClientHttpRequest.java b/spring-test/src/main/java/org/springframework/test/web/client/RequestMatcherClientHttpRequest.java
deleted file mode 100644
index 1a01328b..00000000
--- a/spring-test/src/main/java/org/springframework/test/web/client/RequestMatcherClientHttpRequest.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright 2002-2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.test.web.client;
-
-import java.io.IOException;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.springframework.http.client.ClientHttpResponse;
-import org.springframework.mock.http.client.MockAsyncClientHttpRequest;
-import org.springframework.util.Assert;
-
-/**
- * A specialization of {@code MockClientHttpRequest} that matches the request
- * against a set of expectations, via {@link RequestMatcher} instances. The
- * expectations are checked when the request is executed. This class also uses a
- * {@link ResponseCreator} to create the response.
- *
- * @author Craig Walls
- * @author Rossen Stoyanchev
- * @since 3.2
- */
-class RequestMatcherClientHttpRequest extends MockAsyncClientHttpRequest implements ResponseActions {
-
- private final List<RequestMatcher> requestMatchers = new LinkedList<RequestMatcher>();
-
- private ResponseCreator responseCreator;
-
-
- public RequestMatcherClientHttpRequest(RequestMatcher requestMatcher) {
- Assert.notNull(requestMatcher, "RequestMatcher is required");
- this.requestMatchers.add(requestMatcher);
- }
-
-
- @Override
- public ResponseActions andExpect(RequestMatcher requestMatcher) {
- Assert.notNull(requestMatcher, "RequestMatcher is required");
- this.requestMatchers.add(requestMatcher);
- return this;
- }
-
- @Override
- public void andRespond(ResponseCreator responseCreator) {
- Assert.notNull(responseCreator, "ResponseCreator is required");
- this.responseCreator = responseCreator;
- }
-
- @Override
- public ClientHttpResponse executeInternal() throws IOException {
- if (this.requestMatchers.isEmpty()) {
- throw new AssertionError("No request expectations to execute");
- }
-
- if (this.responseCreator == null) {
- throw new AssertionError("No ResponseCreator was set up. Add it after request expectations, " +
- "e.g. MockRestServiceServer.expect(requestTo(\"/foo\")).andRespond(withSuccess())");
- }
-
- for (RequestMatcher requestMatcher : this.requestMatchers) {
- requestMatcher.match(this);
- }
- setResponse(this.responseCreator.createResponse(this));
- return super.executeInternal();
- }
-
-}
diff --git a/spring-test/src/main/java/org/springframework/test/web/client/SimpleRequestExpectationManager.java b/spring-test/src/main/java/org/springframework/test/web/client/SimpleRequestExpectationManager.java
new file mode 100644
index 00000000..dbcf3852
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/web/client/SimpleRequestExpectationManager.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.web.client;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+import org.springframework.http.client.ClientHttpRequest;
+import org.springframework.http.client.ClientHttpResponse;
+import org.springframework.util.Assert;
+
+/**
+ * Simple {@code RequestExpectationManager} that matches requests to expectations
+ * sequentially, i.e. in the order of declaration of expectations.
+ *
+ * <p>When request expectations have an expected count greater than one,
+ * only the first execution is expected to match the order of declaration.
+ * Subsequent request executions may be inserted anywhere thereafter.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3
+ */
+public class SimpleRequestExpectationManager extends AbstractRequestExpectationManager {
+
+ private Iterator<RequestExpectation> expectationIterator;
+
+ private final RequestExpectationGroup repeatExpectations = new RequestExpectationGroup();
+
+
+ @Override
+ protected void afterExpectationsDeclared() {
+ Assert.state(this.expectationIterator == null);
+ this.expectationIterator = getExpectations().iterator();
+ }
+
+ @Override
+ public ClientHttpResponse validateRequestInternal(ClientHttpRequest request) throws IOException {
+ RequestExpectation expectation;
+ try {
+ expectation = next(request);
+ expectation.match(request);
+ }
+ catch (AssertionError error) {
+ expectation = this.repeatExpectations.findExpectation(request);
+ if (expectation == null) {
+ throw error;
+ }
+ }
+ ClientHttpResponse response = expectation.createResponse(request);
+ this.repeatExpectations.update(expectation);
+ return response;
+ }
+
+ private RequestExpectation next(ClientHttpRequest request) {
+ if (this.expectationIterator.hasNext()) {
+ return this.expectationIterator.next();
+ }
+ throw createUnexpectedRequestError(request);
+ }
+
+ @Override
+ public void reset() {
+ super.reset();
+ this.expectationIterator = null;
+ this.repeatExpectations.reset();
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/web/client/UnorderedRequestExpectationManager.java b/spring-test/src/main/java/org/springframework/test/web/client/UnorderedRequestExpectationManager.java
new file mode 100644
index 00000000..42c1a6aa
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/web/client/UnorderedRequestExpectationManager.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.web.client;
+
+import java.io.IOException;
+
+import org.springframework.http.client.ClientHttpRequest;
+import org.springframework.http.client.ClientHttpResponse;
+
+/**
+ * {@code RequestExpectationManager} that matches requests to expectations
+ * regardless of the order of declaration of expected requests.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3
+ */
+public class UnorderedRequestExpectationManager extends AbstractRequestExpectationManager {
+
+ private final RequestExpectationGroup remainingExpectations = new RequestExpectationGroup();
+
+
+ @Override
+ protected void afterExpectationsDeclared() {
+ this.remainingExpectations.updateAll(getExpectations());
+ }
+
+ @Override
+ public ClientHttpResponse validateRequestInternal(ClientHttpRequest request) throws IOException {
+ RequestExpectation expectation = this.remainingExpectations.findExpectation(request);
+ if (expectation != null) {
+ ClientHttpResponse response = expectation.createResponse(request);
+ this.remainingExpectations.update(expectation);
+ return response;
+ }
+ throw createUnexpectedRequestError(request);
+ }
+
+ @Override
+ public void reset() {
+ super.reset();
+ this.remainingExpectations.reset();
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/web/client/match/ContentRequestMatchers.java b/spring-test/src/main/java/org/springframework/test/web/client/match/ContentRequestMatchers.java
index 4acbf82e..e32a68aa 100644
--- a/spring-test/src/main/java/org/springframework/test/web/client/match/ContentRequestMatchers.java
+++ b/spring-test/src/main/java/org/springframework/test/web/client/match/ContentRequestMatchers.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,18 +16,24 @@
package org.springframework.test.web.client.match;
+import java.io.ByteArrayInputStream;
import java.io.IOException;
+import java.io.InputStream;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMSource;
import org.hamcrest.Matcher;
import org.w3c.dom.Node;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpInputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpRequest;
+import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.mock.http.client.MockClientHttpRequest;
import org.springframework.test.util.XmlExpectationsHelper;
import org.springframework.test.web.client.RequestMatcher;
+import org.springframework.util.MultiValueMap;
import static org.hamcrest.MatcherAssert.*;
import static org.springframework.test.util.AssertionErrors.*;
@@ -137,13 +143,36 @@ public class ContentRequestMatchers {
}
/**
+ * Parse the body as form data and compare to the given {@code MultiValueMap}.
+ * @since 4.3
+ */
+ public RequestMatcher formData(final MultiValueMap<String, String> expectedContent) {
+ return new RequestMatcher() {
+ @Override
+ public void match(final ClientHttpRequest request) throws IOException, AssertionError {
+ HttpInputMessage inputMessage = new HttpInputMessage() {
+ @Override
+ public InputStream getBody() throws IOException {
+ MockClientHttpRequest mockRequest = (MockClientHttpRequest) request;
+ return new ByteArrayInputStream(mockRequest.getBodyAsBytes());
+ }
+ @Override
+ public HttpHeaders getHeaders() {
+ return request.getHeaders();
+ }
+ };
+ FormHttpMessageConverter converter = new FormHttpMessageConverter();
+ assertEquals("Request content", expectedContent, converter.read(null, inputMessage));
+ }
+ };
+ }
+
+ /**
* Parse the request body and the given String as XML and assert that the
* two are "similar" - i.e. they contain the same elements and attributes
* regardless of order.
- *
* <p>Use of this matcher assumes the
* <a href="http://xmlunit.sourceforge.net/">XMLUnit<a/> library is available.
- *
* @param expectedXmlContent the expected XML content
*/
public RequestMatcher xml(final String expectedXmlContent) {
@@ -180,6 +209,7 @@ public class ContentRequestMatchers {
};
}
+
/**
* Abstract base class for XML {@link RequestMatcher}'s.
*/
@@ -191,12 +221,13 @@ public class ContentRequestMatchers {
MockClientHttpRequest mockRequest = (MockClientHttpRequest) request;
matchInternal(mockRequest);
}
- catch (Exception e) {
- throw new AssertionError("Failed to parse expected or actual XML request content: " + e.getMessage());
+ catch (Exception ex) {
+ throw new AssertionError("Failed to parse expected or actual XML request content: " + ex.getMessage());
}
}
protected abstract void matchInternal(MockClientHttpRequest request) throws Exception;
-
}
+
}
+
diff --git a/spring-test/src/main/java/org/springframework/test/web/client/match/MockRestRequestMatchers.java b/spring-test/src/main/java/org/springframework/test/web/client/match/MockRestRequestMatchers.java
index 318e9d88..d9d78bb9 100644
--- a/spring-test/src/main/java/org/springframework/test/web/client/match/MockRestRequestMatchers.java
+++ b/spring-test/src/main/java/org/springframework/test/web/client/match/MockRestRequestMatchers.java
@@ -123,7 +123,7 @@ public abstract class MockRestRequestMatchers {
/**
* Assert request header values with the given Hamcrest matcher.
*/
- @SuppressWarnings("unchecked")
+ @SafeVarargs
public static RequestMatcher header(final String name, final Matcher<? super String>... matchers) {
return new RequestMatcher() {
@Override
diff --git a/spring-test/src/main/java/org/springframework/test/web/client/response/DefaultResponseCreator.java b/spring-test/src/main/java/org/springframework/test/web/client/response/DefaultResponseCreator.java
index 3f2be1ae..213bc5d7 100644
--- a/spring-test/src/main/java/org/springframework/test/web/client/response/DefaultResponseCreator.java
+++ b/spring-test/src/main/java/org/springframework/test/web/client/response/DefaultResponseCreator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,12 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package org.springframework.test.web.client.response;
import java.io.IOException;
import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
import java.net.URI;
+import java.nio.charset.Charset;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
@@ -38,6 +39,9 @@ import org.springframework.util.Assert;
*/
public class DefaultResponseCreator implements ResponseCreator {
+ private static final Charset UTF8_CHARSET = Charset.forName("UTF-8");
+
+
private byte[] content;
private Resource contentResource;
@@ -56,6 +60,7 @@ public class DefaultResponseCreator implements ResponseCreator {
this.statusCode = statusCode;
}
+
@Override
public ClientHttpResponse createResponse(ClientHttpRequest request) throws IOException {
MockClientHttpResponse response;
@@ -74,13 +79,7 @@ public class DefaultResponseCreator implements ResponseCreator {
* Set the body as a UTF-8 String.
*/
public DefaultResponseCreator body(String content) {
- try {
- this.content = content.getBytes("UTF-8");
- }
- catch (UnsupportedEncodingException e) {
- // should not happen, UTF-8 is always supported
- throw new IllegalStateException(e);
- }
+ this.content = content.getBytes(UTF8_CHARSET);
return this;
}
diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnection.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnection.java
index 3db5711d..1f64c74e 100644
--- a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnection.java
+++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnection.java
@@ -37,8 +37,7 @@ import org.springframework.util.Assert;
* WebClient webClient = new WebClient();
*
* MockMvc mockMvc = ...
- * MockMvcWebConnection mockConnection = new MockMvcWebConnection(mockMvc);
- * mockConnection.setWebClient(webClient);
+ * MockMvcWebConnection mockConnection = new MockMvcWebConnection(mockMvc, webClient);
*
* WebRequestMatcher cdnMatcher = new UrlRegexRequestMatcher(".*?//code.jquery.com/.*");
* WebConnection httpConnection = new HttpWebConnection(webClient);
diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilder.java
index 497bf4ab..597ac8f4 100644
--- a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilder.java
+++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilder.java
@@ -106,7 +106,7 @@ public class MockMvcWebClientBuilder extends MockMvcWebConnectionBuilderSupport<
*/
public MockMvcWebClientBuilder withDelegate(WebClient webClient) {
Assert.notNull(webClient, "WebClient must not be null");
- webClient.setWebConnection(createConnection(webClient.getWebConnection()));
+ webClient.setWebConnection(createConnection(webClient));
this.webClient = webClient;
return this;
}
diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnection.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnection.java
index 2f19cea7..fd4ef702 100644
--- a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnection.java
+++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnection.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,13 +17,16 @@
package org.springframework.test.web.servlet.htmlunit;
import java.io.IOException;
+import java.util.Date;
import java.util.HashMap;
import java.util.Map;
+import com.gargoylesoftware.htmlunit.CookieManager;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebConnection;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.WebResponse;
+import com.gargoylesoftware.htmlunit.util.Cookie;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockHttpSession;
@@ -41,8 +44,7 @@ import org.springframework.util.Assert;
* <pre class="code">
* WebClient webClient = new WebClient();
* MockMvc mockMvc = ...
- * MockMvcWebConnection webConnection = new MockMvcWebConnection(mockMvc);
- * mockConnection.setWebClient(webClient);
+ * MockMvcWebConnection webConnection = new MockMvcWebConnection(mockMvc, webClient);
* webClient.setWebConnection(webConnection);
*
* // Use webClient as normal ...
@@ -70,9 +72,10 @@ public final class MockMvcWebConnection implements WebConnection {
* <p>For example, the URL {@code http://localhost/test/this} would use
* {@code ""} as the context path.
* @param mockMvc the {@code MockMvc} instance to use; never {@code null}
+ * @param webClient the {@link WebClient} to use. never {@code null}
*/
- public MockMvcWebConnection(MockMvc mockMvc) {
- this(mockMvc, "");
+ public MockMvcWebConnection(MockMvc mockMvc, WebClient webClient) {
+ this(mockMvc, webClient, "");
}
/**
@@ -83,17 +86,68 @@ public final class MockMvcWebConnection implements WebConnection {
* which states that it can be an empty string and otherwise must start
* with a "/" character and not end with a "/" character.
* @param mockMvc the {@code MockMvc} instance to use; never {@code null}
+ * @param webClient the {@link WebClient} to use. never {@code null}
* @param contextPath the contextPath to use
*/
- public MockMvcWebConnection(MockMvc mockMvc, String contextPath) {
+ public MockMvcWebConnection(MockMvc mockMvc, WebClient webClient, String contextPath) {
Assert.notNull(mockMvc, "MockMvc must not be null");
+ Assert.notNull(webClient, "WebClient must not be null");
validateContextPath(contextPath);
- this.webClient = new WebClient();
+ this.webClient = webClient;
this.mockMvc = mockMvc;
this.contextPath = contextPath;
}
+ /**
+ * Create a new instance that assumes the context path of the application
+ * is {@code ""} (i.e., the root context).
+ * <p>For example, the URL {@code http://localhost/test/this} would use
+ * {@code ""} as the context path.
+ * @param mockMvc the {@code MockMvc} instance to use; never {@code null}
+ * @deprecated Use {@link #MockMvcWebConnection(MockMvc, WebClient)}
+ */
+ @Deprecated
+ public MockMvcWebConnection(MockMvc mockMvc) {
+ this(mockMvc, "");
+ }
+
+ /**
+ * Create a new instance with the specified context path.
+ * <p>The path may be {@code null} in which case the first path segment
+ * of the URL is turned into the contextPath. Otherwise it must conform
+ * to {@link javax.servlet.http.HttpServletRequest#getContextPath()}
+ * which states that it can be an empty string and otherwise must start
+ * with a "/" character and not end with a "/" character.
+ * @param mockMvc the {@code MockMvc} instance to use; never {@code null}
+ * @param contextPath the contextPath to use
+ * @deprecated use {@link #MockMvcWebConnection(MockMvc, WebClient, String)}
+ */
+ @Deprecated
+ public MockMvcWebConnection(MockMvc mockMvc, String contextPath) {
+ this(mockMvc, new WebClient(), contextPath);
+ }
+
+ /**
+ * Validate the supplied {@code contextPath}.
+ * <p>If the value is not {@code null}, it must conform to
+ * {@link javax.servlet.http.HttpServletRequest#getContextPath()} which
+ * states that it can be an empty string and otherwise must start with
+ * a "/" character and not end with a "/" character.
+ * @param contextPath the path to validate
+ */
+ static void validateContextPath(String contextPath) {
+ if (contextPath == null || "".equals(contextPath)) {
+ return;
+ }
+ if (!contextPath.startsWith("/")) {
+ throw new IllegalArgumentException("contextPath '" + contextPath + "' must start with '/'.");
+ }
+ if (contextPath.endsWith("/")) {
+ throw new IllegalArgumentException("contextPath '" + contextPath + "' must not end with '/'.");
+ }
+ }
+
public void setWebClient(WebClient webClient) {
Assert.notNull(webClient, "WebClient must not be null");
@@ -113,6 +167,7 @@ public final class MockMvcWebConnection implements WebConnection {
httpServletResponse = getResponse(requestBuilder);
forwardedUrl = httpServletResponse.getForwardedUrl();
}
+ storeCookies(webRequest, httpServletResponse.getCookies());
return new MockWebResponseBuilder(startTime, webRequest, httpServletResponse).build();
}
@@ -129,29 +184,29 @@ public final class MockMvcWebConnection implements WebConnection {
return resultActions.andReturn().getResponse();
}
- @Override
- public void close() {
- }
-
-
- /**
- * Validate the supplied {@code contextPath}.
- * <p>If the value is not {@code null}, it must conform to
- * {@link javax.servlet.http.HttpServletRequest#getContextPath()} which
- * states that it can be an empty string and otherwise must start with
- * a "/" character and not end with a "/" character.
- * @param contextPath the path to validate
- */
- static void validateContextPath(String contextPath) {
- if (contextPath == null || "".equals(contextPath)) {
+ private void storeCookies(WebRequest webRequest, javax.servlet.http.Cookie[] cookies) {
+ if (cookies == null) {
return;
}
- if (!contextPath.startsWith("/")) {
- throw new IllegalArgumentException("contextPath '" + contextPath + "' must start with '/'.");
- }
- if (contextPath.endsWith("/")) {
- throw new IllegalArgumentException("contextPath '" + contextPath + "' must not end with '/'.");
+ Date now = new Date();
+ CookieManager cookieManager = this.webClient.getCookieManager();
+ for (javax.servlet.http.Cookie cookie : cookies) {
+ if (cookie.getDomain() == null) {
+ cookie.setDomain(webRequest.getUrl().getHost());
+ }
+ Cookie toManage = MockWebResponseBuilder.createCookie(cookie);
+ Date expires = toManage.getExpires();
+ if (expires == null || expires.after(now)) {
+ cookieManager.addCookie(toManage);
+ }
+ else {
+ cookieManager.removeCookie(toManage);
+ }
}
}
+ @Override
+ public void close() {
+ }
+
}
diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionBuilderSupport.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionBuilderSupport.java
index cc957dca..c173fdfb 100644
--- a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionBuilderSupport.java
+++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionBuilderSupport.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,9 +19,11 @@ package org.springframework.test.web.servlet.htmlunit;
import java.util.ArrayList;
import java.util.List;
+import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebConnection;
import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.htmlunit.DelegatingWebConnection.DelegateWebConnection;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.test.web.servlet.setup.MockMvcConfigurer;
import org.springframework.util.Assert;
@@ -43,7 +45,7 @@ public abstract class MockMvcWebConnectionBuilderSupport<T extends MockMvcWebCon
private final MockMvc mockMvc;
- private final List<WebRequestMatcher> mockMvcRequestMatchers = new ArrayList<WebRequestMatcher>();
+ private final List<WebRequestMatcher> requestMatchers = new ArrayList<WebRequestMatcher>();
private String contextPath = "";
@@ -57,7 +59,7 @@ public abstract class MockMvcWebConnectionBuilderSupport<T extends MockMvcWebCon
protected MockMvcWebConnectionBuilderSupport(MockMvc mockMvc) {
Assert.notNull(mockMvc, "MockMvc must not be null");
this.mockMvc = mockMvc;
- this.mockMvcRequestMatchers.add(new HostRequestMatcher("localhost"));
+ this.requestMatchers.add(new HostRequestMatcher("localhost"));
}
/**
@@ -116,7 +118,7 @@ public abstract class MockMvcWebConnectionBuilderSupport<T extends MockMvcWebCon
@SuppressWarnings("unchecked")
public T useMockMvc(WebRequestMatcher... matchers) {
for (WebRequestMatcher matcher : matchers) {
- this.mockMvcRequestMatchers.add(matcher);
+ this.requestMatchers.add(matcher);
}
return (T) this;
}
@@ -130,7 +132,7 @@ public abstract class MockMvcWebConnectionBuilderSupport<T extends MockMvcWebCon
*/
@SuppressWarnings("unchecked")
public T useMockMvcForHosts(String... hosts) {
- this.mockMvcRequestMatchers.add(new HostRequestMatcher(hosts));
+ this.requestMatchers.add(new HostRequestMatcher(hosts));
return (T) this;
}
@@ -145,21 +147,41 @@ public abstract class MockMvcWebConnectionBuilderSupport<T extends MockMvcWebCon
* @see #alwaysUseMockMvc()
* @see #useMockMvc(WebRequestMatcher...)
* @see #useMockMvcForHosts(String...)
+ * @deprecated Use {@link #createConnection(WebClient)} instead
*/
+ @Deprecated
protected final WebConnection createConnection(WebConnection defaultConnection) {
Assert.notNull(defaultConnection, "Default WebConnection must not be null");
- MockMvcWebConnection mockMvcWebConnection = new MockMvcWebConnection(this.mockMvc, this.contextPath);
+ return createConnection(new WebClient(), defaultConnection);
+ }
+ /**
+ * Create a new {@link WebConnection} that will use a {@link MockMvc}
+ * instance if one of the specified {@link WebRequestMatcher} instances
+ * matches.
+ * @param webClient the WebClient to use if none of the specified
+ * {@code WebRequestMatcher} instances matches (never {@code null})
+ * @return a new {@code WebConnection} that will use a {@code MockMvc}
+ * instance if one of the specified {@code WebRequestMatcher} matches
+ * @see #alwaysUseMockMvc()
+ * @see #useMockMvc(WebRequestMatcher...)
+ * @see #useMockMvcForHosts(String...)
+ * @since 4.3
+ */
+ protected final WebConnection createConnection(WebClient webClient) {
+ Assert.notNull(webClient, "WebClient must not be null");
+ return createConnection(webClient, webClient.getWebConnection());
+ }
+
+ private WebConnection createConnection(WebClient webClient, WebConnection defaultConnection) {
+ WebConnection connection = new MockMvcWebConnection(this.mockMvc, webClient, this.contextPath);
if (this.alwaysUseMockMvc) {
- return mockMvcWebConnection;
+ return connection;
}
-
- List<DelegatingWebConnection.DelegateWebConnection> delegates = new ArrayList<DelegatingWebConnection.DelegateWebConnection>(
- this.mockMvcRequestMatchers.size());
- for (WebRequestMatcher matcher : this.mockMvcRequestMatchers) {
- delegates.add(new DelegatingWebConnection.DelegateWebConnection(matcher, mockMvcWebConnection));
+ List<DelegateWebConnection> delegates = new ArrayList<DelegateWebConnection>(this.requestMatchers.size());
+ for (WebRequestMatcher matcher : this.requestMatchers) {
+ delegates.add(new DelegateWebConnection(matcher, connection));
}
-
return new DelegatingWebConnection(defaultConnection, delegates);
}
diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilder.java
index 7ead8e13..fd716f47 100644
--- a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilder.java
+++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,13 +19,17 @@ package org.springframework.test.web.servlet.htmlunit;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Date;
import java.util.List;
+import javax.servlet.http.Cookie;
+
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.WebResponse;
import com.gargoylesoftware.htmlunit.WebResponseData;
import com.gargoylesoftware.htmlunit.util.NameValuePair;
+import org.apache.http.impl.cookie.BasicClientCookie;
import org.springframework.http.HttpStatus;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.util.Assert;
@@ -34,6 +38,7 @@ import org.springframework.util.StringUtils;
/**
* @author Rob Winch
* @author Sam Brannen
+ * @author Rossen Stoyanchev
* @since 4.2
*/
final class MockWebResponseBuilder {
@@ -100,7 +105,30 @@ final class MockWebResponseBuilder {
if (location != null) {
responseHeaders.add(new NameValuePair("Location", location));
}
+ for (Cookie cookie : this.response.getCookies()) {
+ responseHeaders.add(new NameValuePair("Set-Cookie", valueOfCookie(cookie)));
+ }
return responseHeaders;
}
+ private String valueOfCookie(Cookie cookie) {
+ return createCookie(cookie).toString();
+ }
+
+ static com.gargoylesoftware.htmlunit.util.Cookie createCookie(Cookie cookie) {
+ Date expires = null;
+ if (cookie.getMaxAge() > -1) {
+ expires = new Date(System.currentTimeMillis() + cookie.getMaxAge() * 1000);
+ }
+ BasicClientCookie result = new BasicClientCookie(cookie.getName(), cookie.getValue());
+ result.setDomain(cookie.getDomain());
+ result.setComment(cookie.getComment());
+ result.setExpiryDate(expires);
+ result.setPath(cookie.getPath());
+ result.setSecure(cookie.getSecure());
+ if(cookie.isHttpOnly()) {
+ result.setAttribute("httponly", "true");
+ }
+ return new com.gargoylesoftware.htmlunit.util.Cookie(result);
+ }
}
diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilder.java
index 9454c0a3..9a5ab8ac 100644
--- a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilder.java
+++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilder.java
@@ -129,7 +129,7 @@ public class MockMvcHtmlUnitDriverBuilder extends MockMvcWebConnectionBuilderSup
public MockMvcHtmlUnitDriverBuilder withDelegate(WebConnectionHtmlUnitDriver driver) {
Assert.notNull(driver, "HtmlUnitDriver must not be null");
driver.setJavascriptEnabled(this.javascriptEnabled);
- driver.setWebConnection(createConnection(driver.getWebConnection()));
+ driver.setWebConnection(createConnection(driver.getWebClient()));
this.driver = driver;
return this;
}
diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/WebConnectionHtmlUnitDriver.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/WebConnectionHtmlUnitDriver.java
index bdb67b87..e8fc5f18 100644
--- a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/WebConnectionHtmlUnitDriver.java
+++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/WebConnectionHtmlUnitDriver.java
@@ -88,6 +88,14 @@ public class WebConnectionHtmlUnitDriver extends HtmlUnitDriver {
}
/**
+ * Return the current {@link WebClient}.
+ * @since 4.3
+ */
+ public WebClient getWebClient() {
+ return this.webClient;
+ }
+
+ /**
* Set the {@link WebConnection} to be used with the {@link WebClient}.
* @param webConnection the {@code WebConnection} to use (never {@code null})
*/
diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java
index 1663bd2f..d181a46f 100644
--- a/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java
+++ b/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,8 +16,12 @@
package org.springframework.test.web.servlet.request;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
+import java.nio.charset.Charset;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Arrays;
@@ -33,8 +37,10 @@ import javax.servlet.http.Cookie;
import org.springframework.beans.Mergeable;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
+import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockHttpSession;
@@ -60,16 +66,23 @@ import org.springframework.web.util.UriUtils;
*
* <p>Application tests will typically access this builder through the static factory
* methods in {@link MockMvcRequestBuilders}.
+ * <p>Although this class cannot be extended, additional ways to initialize
+ * the {@code MockHttpServletRequest} can be plugged in via
+ * {@link #with(RequestPostProcessor)}.
*
* @author Rossen Stoyanchev
* @author Arjen Poutsma
* @author Sam Brannen
+ * @author Kamill Sokol
* @since 3.2
*/
public class MockHttpServletRequestBuilder
implements ConfigurableSmartRequestBuilder<MockHttpServletRequestBuilder>, Mergeable {
- private final HttpMethod method;
+ private static final Charset UTF8_CHARSET = Charset.forName("UTF-8");
+
+
+ private final String method;
private final URI url;
@@ -119,27 +132,33 @@ public class MockHttpServletRequestBuilder
* @param vars zero or more URL variables
*/
MockHttpServletRequestBuilder(HttpMethod httpMethod, String url, Object... vars) {
- this(httpMethod, UriComponentsBuilder.fromUriString(url).buildAndExpand(vars).encode().toUri());
+ this(httpMethod.name(), UriComponentsBuilder.fromUriString(url).buildAndExpand(vars).encode().toUri());
}
/**
- * Package private constructor. To get an instance, use static factory
- * methods in {@link MockMvcRequestBuilders}.
- * <p>Although this class cannot be extended, additional ways to initialize
- * the {@code MockHttpServletRequest} can be plugged in via
- * {@link #with(RequestPostProcessor)}.
+ * Alternative to {@link #MockHttpServletRequestBuilder(HttpMethod, String, Object...)}
+ * with a pre-built URI.
* @param httpMethod the HTTP method (GET, POST, etc)
* @param url the URL
* @since 4.0.3
*/
MockHttpServletRequestBuilder(HttpMethod httpMethod, URI url) {
- Assert.notNull(httpMethod, "httpMethod is required");
- Assert.notNull(url, "url is required");
+ this(httpMethod.name(), url);
+ }
+
+ /**
+ * Alternative constructor for custom HTTP methods.
+ * @param httpMethod the HTTP method (GET, POST, etc)
+ * @param url the URL
+ * @since 4.3
+ */
+ MockHttpServletRequestBuilder(String httpMethod, URI url) {
+ Assert.notNull(httpMethod, "'httpMethod' is required");
+ Assert.notNull(url, "'url' is required");
this.method = httpMethod;
this.url = url;
}
-
/**
* Add a request parameter to the {@link MockHttpServletRequest}.
* <p>If called more than once, new values get added to existing ones.
@@ -257,12 +276,7 @@ public class MockHttpServletRequestBuilder
* @param content the body content
*/
public MockHttpServletRequestBuilder content(String content) {
- try {
- this.content = content.getBytes("UTF-8");
- }
- catch (UnsupportedEncodingException e) {
- // should never happen
- }
+ this.content = content.getBytes(UTF8_CHARSET);
return this;
}
@@ -320,7 +334,7 @@ public class MockHttpServletRequestBuilder
* @param sessionAttributes the session attributes
*/
public MockHttpServletRequestBuilder sessionAttrs(Map<String, Object> sessionAttributes) {
- Assert.notEmpty(sessionAttributes, "'sessionAttrs' must not be empty");
+ Assert.notEmpty(sessionAttributes, "'sessionAttributes' must not be empty");
for (String name : sessionAttributes.keySet()) {
sessionAttr(name, sessionAttributes.get(name));
}
@@ -342,7 +356,7 @@ public class MockHttpServletRequestBuilder
* @param flashAttributes the flash attributes
*/
public MockHttpServletRequestBuilder flashAttrs(Map<String, Object> flashAttributes) {
- Assert.notEmpty(flashAttributes, "'flashAttrs' must not be empty");
+ Assert.notEmpty(flashAttributes, "'flashAttributes' must not be empty");
for (String name : flashAttributes.keySet()) {
flashAttr(name, flashAttributes.get(name));
}
@@ -585,7 +599,7 @@ public class MockHttpServletRequestBuilder
request.setServerPort(this.url.getPort());
}
- request.setMethod(this.method.name());
+ request.setMethod(this.method);
for (String name : this.headers.keySet()) {
for (Object value : this.headers.get(name)) {
@@ -593,24 +607,10 @@ public class MockHttpServletRequestBuilder
}
}
- try {
- if (this.url.getRawQuery() != null) {
- request.setQueryString(this.url.getRawQuery());
- }
-
- MultiValueMap<String, String> queryParams =
- UriComponentsBuilder.fromUri(this.url).build().getQueryParams();
-
- for (Entry<String, List<String>> entry : queryParams.entrySet()) {
- for (String value : entry.getValue()) {
- value = (value != null) ? UriUtils.decode(value, "UTF-8") : null;
- request.addParameter(UriUtils.decode(entry.getKey(), "UTF-8"), value);
- }
- }
- }
- catch (UnsupportedEncodingException ex) {
- // shouldn't happen
+ if (this.url.getRawQuery() != null) {
+ request.setQueryString(this.url.getRawQuery());
}
+ addRequestParams(request, UriComponentsBuilder.fromUri(this.url).build().getQueryParams());
for (String name : this.parameters.keySet()) {
for (String value : this.parameters.get(name)) {
@@ -622,6 +622,13 @@ public class MockHttpServletRequestBuilder
request.setContent(this.content);
request.setCharacterEncoding(this.characterEncoding);
+ if (this.content != null && this.contentType != null) {
+ MediaType mediaType = MediaType.parseMediaType(this.contentType);
+ if (MediaType.APPLICATION_FORM_URLENCODED.includes(mediaType)) {
+ addRequestParams(request, parseFormData(mediaType));
+ }
+ }
+
if (!ObjectUtils.isEmpty(this.cookies)) {
request.setCookies(this.cookies.toArray(new Cookie[this.cookies.size()]));
}
@@ -685,6 +692,44 @@ public class MockHttpServletRequestBuilder
request.setPathInfo(this.pathInfo);
}
+ private void addRequestParams(MockHttpServletRequest request, MultiValueMap<String, String> map) {
+ try {
+ for (Entry<String, List<String>> entry : map.entrySet()) {
+ for (String value : entry.getValue()) {
+ value = (value != null) ? UriUtils.decode(value, "UTF-8") : null;
+ request.addParameter(UriUtils.decode(entry.getKey(), "UTF-8"), value);
+ }
+ }
+ }
+ catch (UnsupportedEncodingException ex) {
+ // shouldn't happen
+ }
+ }
+
+ private MultiValueMap<String, String> parseFormData(final MediaType mediaType) {
+ MultiValueMap<String, String> map;
+ HttpInputMessage message = new HttpInputMessage() {
+ @Override
+ public InputStream getBody() throws IOException {
+ return new ByteArrayInputStream(content);
+ }
+
+ @Override
+ public HttpHeaders getHeaders() {
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(mediaType);
+ return headers;
+ }
+ };
+ try {
+ map = new FormHttpMessageConverter().read(null, message);
+ }
+ catch (IOException ex) {
+ throw new IllegalStateException("Failed to parse form data in request body", ex);
+ }
+ return map;
+ }
+
private FlashMapManager getFlashMapManager(MockHttpServletRequest request) {
FlashMapManager flashMapManager = null;
try {
diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMvcRequestBuilders.java b/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMvcRequestBuilders.java
index 571699e6..f0e21adb 100644
--- a/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMvcRequestBuilders.java
+++ b/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMvcRequestBuilders.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -42,6 +42,7 @@ import org.springframework.test.web.servlet.RequestBuilder;
* @author Greg Turnquist
* @author Sebastien Deleuze
* @author Sam Brannen
+ * @author Kamill Sokol
* @since 3.2
*/
public abstract class MockMvcRequestBuilders {
@@ -49,10 +50,10 @@ public abstract class MockMvcRequestBuilders {
/**
* Create a {@link MockHttpServletRequestBuilder} for a GET request.
* @param urlTemplate a URL template; the resulting URL will be encoded
- * @param urlVariables zero or more URL variables
+ * @param urlVars zero or more URL variables
*/
- public static MockHttpServletRequestBuilder get(String urlTemplate, Object... urlVariables) {
- return new MockHttpServletRequestBuilder(HttpMethod.GET, urlTemplate, urlVariables);
+ public static MockHttpServletRequestBuilder get(String urlTemplate, Object... urlVars) {
+ return new MockHttpServletRequestBuilder(HttpMethod.GET, urlTemplate, urlVars);
}
/**
@@ -67,10 +68,10 @@ public abstract class MockMvcRequestBuilders {
/**
* Create a {@link MockHttpServletRequestBuilder} for a POST request.
* @param urlTemplate a URL template; the resulting URL will be encoded
- * @param urlVariables zero or more URL variables
+ * @param urlVars zero or more URL variables
*/
- public static MockHttpServletRequestBuilder post(String urlTemplate, Object... urlVariables) {
- return new MockHttpServletRequestBuilder(HttpMethod.POST, urlTemplate, urlVariables);
+ public static MockHttpServletRequestBuilder post(String urlTemplate, Object... urlVars) {
+ return new MockHttpServletRequestBuilder(HttpMethod.POST, urlTemplate, urlVars);
}
/**
@@ -85,10 +86,10 @@ public abstract class MockMvcRequestBuilders {
/**
* Create a {@link MockHttpServletRequestBuilder} for a PUT request.
* @param urlTemplate a URL template; the resulting URL will be encoded
- * @param urlVariables zero or more URL variables
+ * @param urlVars zero or more URL variables
*/
- public static MockHttpServletRequestBuilder put(String urlTemplate, Object... urlVariables) {
- return new MockHttpServletRequestBuilder(HttpMethod.PUT, urlTemplate, urlVariables);
+ public static MockHttpServletRequestBuilder put(String urlTemplate, Object... urlVars) {
+ return new MockHttpServletRequestBuilder(HttpMethod.PUT, urlTemplate, urlVars);
}
/**
@@ -103,10 +104,10 @@ public abstract class MockMvcRequestBuilders {
/**
* Create a {@link MockHttpServletRequestBuilder} for a PATCH request.
* @param urlTemplate a URL template; the resulting URL will be encoded
- * @param urlVariables zero or more URL variables
+ * @param urlVars zero or more URL variables
*/
- public static MockHttpServletRequestBuilder patch(String urlTemplate, Object... urlVariables) {
- return new MockHttpServletRequestBuilder(HttpMethod.PATCH, urlTemplate, urlVariables);
+ public static MockHttpServletRequestBuilder patch(String urlTemplate, Object... urlVars) {
+ return new MockHttpServletRequestBuilder(HttpMethod.PATCH, urlTemplate, urlVars);
}
/**
@@ -121,10 +122,10 @@ public abstract class MockMvcRequestBuilders {
/**
* Create a {@link MockHttpServletRequestBuilder} for a DELETE request.
* @param urlTemplate a URL template; the resulting URL will be encoded
- * @param urlVariables zero or more URL variables
+ * @param urlVars zero or more URL variables
*/
- public static MockHttpServletRequestBuilder delete(String urlTemplate, Object... urlVariables) {
- return new MockHttpServletRequestBuilder(HttpMethod.DELETE, urlTemplate, urlVariables);
+ public static MockHttpServletRequestBuilder delete(String urlTemplate, Object... urlVars) {
+ return new MockHttpServletRequestBuilder(HttpMethod.DELETE, urlTemplate, urlVars);
}
/**
@@ -139,10 +140,10 @@ public abstract class MockMvcRequestBuilders {
/**
* Create a {@link MockHttpServletRequestBuilder} for an OPTIONS request.
* @param urlTemplate a URL template; the resulting URL will be encoded
- * @param urlVariables zero or more URL variables
+ * @param urlVars zero or more URL variables
*/
- public static MockHttpServletRequestBuilder options(String urlTemplate, Object... urlVariables) {
- return new MockHttpServletRequestBuilder(HttpMethod.OPTIONS, urlTemplate, urlVariables);
+ public static MockHttpServletRequestBuilder options(String urlTemplate, Object... urlVars) {
+ return new MockHttpServletRequestBuilder(HttpMethod.OPTIONS, urlTemplate, urlVars);
}
/**
@@ -157,11 +158,11 @@ public abstract class MockMvcRequestBuilders {
/**
* Create a {@link MockHttpServletRequestBuilder} for a HEAD request.
* @param urlTemplate a URL template; the resulting URL will be encoded
- * @param urlVariables zero or more URL variables
+ * @param urlVars zero or more URL variables
* @since 4.1
*/
- public static MockHttpServletRequestBuilder head(String urlTemplate, Object... urlVariables) {
- return new MockHttpServletRequestBuilder(HttpMethod.HEAD, urlTemplate, urlVariables);
+ public static MockHttpServletRequestBuilder head(String urlTemplate, Object... urlVars) {
+ return new MockHttpServletRequestBuilder(HttpMethod.HEAD, urlTemplate, urlVars);
}
/**
@@ -175,12 +176,12 @@ public abstract class MockMvcRequestBuilders {
/**
* Create a {@link MockHttpServletRequestBuilder} for a request with the given HTTP method.
- * @param httpMethod the HTTP method
+ * @param method the HTTP method (GET, POST, etc)
* @param urlTemplate a URL template; the resulting URL will be encoded
- * @param urlVariables zero or more URL variables
+ * @param urlVars zero or more URL variables
*/
- public static MockHttpServletRequestBuilder request(HttpMethod httpMethod, String urlTemplate, Object... urlVariables) {
- return new MockHttpServletRequestBuilder(httpMethod, urlTemplate, urlVariables);
+ public static MockHttpServletRequestBuilder request(HttpMethod method, String urlTemplate, Object... urlVars) {
+ return new MockHttpServletRequestBuilder(method, urlTemplate, urlVars);
}
/**
@@ -194,12 +195,22 @@ public abstract class MockMvcRequestBuilders {
}
/**
+ * Alternative factory method that allows for custom HTTP verbs (e.g. WebDAV).
+ * @param httpMethod the HTTP method
+ * @param uri the URL
+ * @since 4.3
+ */
+ public static MockHttpServletRequestBuilder request(String httpMethod, URI uri) {
+ return new MockHttpServletRequestBuilder(httpMethod, uri);
+ }
+
+ /**
* Create a {@link MockMultipartHttpServletRequestBuilder} for a multipart request.
* @param urlTemplate a URL template; the resulting URL will be encoded
- * @param urlVariables zero or more URL variables
+ * @param urlVars zero or more URL variables
*/
- public static MockMultipartHttpServletRequestBuilder fileUpload(String urlTemplate, Object... urlVariables) {
- return new MockMultipartHttpServletRequestBuilder(urlTemplate, urlVariables);
+ public static MockMultipartHttpServletRequestBuilder fileUpload(String urlTemplate, Object... urlVars) {
+ return new MockMultipartHttpServletRequestBuilder(urlTemplate, urlVars);
}
/**
diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/result/HandlerResultMatchers.java b/spring-test/src/main/java/org/springframework/test/web/servlet/result/HandlerResultMatchers.java
index 982eacdf..211ca9a7 100644
--- a/spring-test/src/main/java/org/springframework/test/web/servlet/result/HandlerResultMatchers.java
+++ b/spring-test/src/main/java/org/springframework/test/web/servlet/result/HandlerResultMatchers.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,22 +24,33 @@ import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.util.ClassUtils;
import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
+import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.MethodInvocationInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
-import static org.hamcrest.MatcherAssert.*;
-import static org.springframework.test.util.AssertionErrors.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.springframework.test.util.AssertionErrors.assertEquals;
+import static org.springframework.test.util.AssertionErrors.assertTrue;
+import static org.springframework.test.util.AssertionErrors.fail;
/**
- * Factory for assertions on the selected handler.
+ * Factory for assertions on the selected handler or handler method.
* <p>An instance of this class is typically accessed via
* {@link MockMvcResultMatchers#handler}.
*
+ * <p><strong>Note:</strong> Expectations that assert the controller method
+ * used to process the request work only for requests processed with
+ * {@link RequestMappingHandlerMapping} and {@link RequestMappingHandlerAdapter}
+ * which is used by default with the Spring MVC Java config and XML namespace.
+ *
* @author Rossen Stoyanchev
+ * @author Sam Brannen
* @since 3.2
*/
public class HandlerResultMatchers {
+
/**
* Protected constructor.
* Use {@link MockMvcResultMatchers#handler()}.
@@ -67,56 +78,92 @@ public class HandlerResultMatchers {
}
/**
- * Assert the name of the controller method that processed the request with
- * the given Hamcrest {@link Matcher}.
- * <p>Use of this method implies annotated controllers are processed with
- * {@link RequestMappingHandlerMapping} and {@link RequestMappingHandlerAdapter}.
+ * Assert the controller method used to process the request.
+ * <p>The expected method is specified through a "mock" controller method
+ * invocation similar to {@link MvcUriComponentsBuilder#fromMethodCall(Object)}.
+ * <p>For example, given this controller:
+ * <pre class="code">
+ * &#064;RestController
+ * public class SimpleController {
+ *
+ * &#064;RequestMapping("/")
+ * public ResponseEntity<Void> handle() {
+ * return ResponseEntity.ok().build();
+ * }
+ * }
+ * </pre>
+ * <p>A test that has statically imported {@link MvcUriComponentsBuilder#on}
+ * can be performed as follows:
+ * <pre class="code">
+ * mockMvc.perform(get("/"))
+ * .andExpect(handler().methodCall(on(SimpleController.class).handle()));
+ * </pre>
+ *
+ * @param obj either the value returned from a "mock" controller invocation
+ * or the "mock" controller itself after an invocation
+ */
+ public ResultMatcher methodCall(final Object obj) {
+ return new ResultMatcher() {
+ @Override
+ public void match(MvcResult result) throws Exception {
+ if (!MethodInvocationInfo.class.isInstance(obj)) {
+ fail(String.format("The supplied object [%s] is not an instance of %s. "
+ + "Ensure that you invoke the handler method via MvcUriComponentsBuilder.on().",
+ obj, MethodInvocationInfo.class.getName()));
+ }
+ MethodInvocationInfo invocationInfo = (MethodInvocationInfo) obj;
+ Method expected = invocationInfo.getControllerMethod();
+ Method actual = getHandlerMethod(result).getMethod();
+ assertEquals("Handler method", expected, actual);
+ }
+ };
+ }
+
+ /**
+ * Assert the name of the controller method used to process the request
+ * using the given Hamcrest {@link Matcher}.
*/
public ResultMatcher methodName(final Matcher<? super String> matcher) {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
- Object handler = assertHandlerMethod(result);
- assertThat("HandlerMethod", ((HandlerMethod) handler).getMethod().getName(), matcher);
+ HandlerMethod handlerMethod = getHandlerMethod(result);
+ assertThat("Handler method", handlerMethod.getMethod().getName(), matcher);
}
};
}
/**
- * Assert the name of the controller method that processed the request.
- * <p>Use of this method implies annotated controllers are processed with
- * {@link RequestMappingHandlerMapping} and {@link RequestMappingHandlerAdapter}.
+ * Assert the name of the controller method used to process the request.
*/
public ResultMatcher methodName(final String name) {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
- Object handler = assertHandlerMethod(result);
- assertEquals("HandlerMethod", name, ((HandlerMethod) handler).getMethod().getName());
+ HandlerMethod handlerMethod = getHandlerMethod(result);
+ assertEquals("Handler method", name, handlerMethod.getMethod().getName());
}
};
}
/**
- * Assert the controller method that processed the request.
- * <p>Use of this method implies annotated controllers are processed with
- * {@link RequestMappingHandlerMapping} and {@link RequestMappingHandlerAdapter}.
+ * Assert the controller method used to process the request.
*/
public ResultMatcher method(final Method method) {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
- Object handler = assertHandlerMethod(result);
- assertEquals("HandlerMethod", method, ((HandlerMethod) handler).getMethod());
+ HandlerMethod handlerMethod = getHandlerMethod(result);
+ assertEquals("Handler method", method, handlerMethod.getMethod());
}
};
}
- private static Object assertHandlerMethod(MvcResult result) {
+ private static HandlerMethod getHandlerMethod(MvcResult result) {
Object handler = result.getHandler();
assertTrue("No handler: ", handler != null);
assertTrue("Not a HandlerMethod: " + handler, HandlerMethod.class.isInstance(handler));
- return handler;
+ return (HandlerMethod) handler;
}
}
diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/result/HeaderResultMatchers.java b/spring-test/src/main/java/org/springframework/test/web/servlet/result/HeaderResultMatchers.java
index c57387ee..aac0bab9 100644
--- a/spring-test/src/main/java/org/springframework/test/web/servlet/result/HeaderResultMatchers.java
+++ b/spring-test/src/main/java/org/springframework/test/web/servlet/result/HeaderResultMatchers.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,22 +16,26 @@
package org.springframework.test.web.servlet.result;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
+
import org.hamcrest.Matcher;
+import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultMatcher;
-import static org.hamcrest.MatcherAssert.*;
-import static org.springframework.test.util.AssertionErrors.*;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-import java.util.TimeZone;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.springframework.test.util.AssertionErrors.assertEquals;
+import static org.springframework.test.util.AssertionErrors.assertTrue;
/**
* Factory for response header assertions.
- * <p>An instance of this class is usually accessed via
+ * <p>An instance of this class is available via
* {@link MockMvcResultMatchers#header}.
*
* @author Rossen Stoyanchev
@@ -41,16 +45,18 @@ import java.util.TimeZone;
*/
public class HeaderResultMatchers {
+
/**
* Protected constructor.
- * Use {@link MockMvcResultMatchers#header()}.
+ * See {@link MockMvcResultMatchers#header()}.
*/
protected HeaderResultMatchers() {
}
+
/**
- * Assert the primary value of the named response header with the given
- * Hamcrest {@link Matcher}.
+ * Assert the primary value of the response header with the given Hamcrest
+ * String {@code Matcher}.
*/
public ResultMatcher string(final String name, final Matcher<? super String> matcher) {
return new ResultMatcher() {
@@ -62,7 +68,22 @@ public class HeaderResultMatchers {
}
/**
- * Assert the primary value of the named response header as a {@link String}.
+ * Assert the values of the response header with the given Hamcrest
+ * Iterable {@link Matcher}.
+ * @since 4.3
+ */
+ public <T> ResultMatcher stringValues(final String name, final Matcher<Iterable<String>> matcher) {
+ return new ResultMatcher() {
+ @Override
+ public void match(MvcResult result) {
+ List<String> values = result.getResponse().getHeaders(name);
+ assertThat("Response header " + name, values, matcher);
+ }
+ };
+ }
+
+ /**
+ * Assert the primary value of the response header as a String value.
*/
public ResultMatcher string(final String name, final String value) {
return new ResultMatcher() {
@@ -74,6 +95,20 @@ public class HeaderResultMatchers {
}
/**
+ * Assert the values of the response header as String values.
+ * @since 4.3
+ */
+ public ResultMatcher stringValues(final String name, final String... values) {
+ return new ResultMatcher() {
+ @Override
+ public void match(MvcResult result) {
+ List<Object> actual = result.getResponse().getHeaderValues(name);
+ assertEquals("Response header " + name, Arrays.asList(values), actual);
+ }
+ };
+ }
+
+ /**
* Assert that the named response header does not exist.
* @since 4.0
*/
@@ -81,23 +116,25 @@ public class HeaderResultMatchers {
return new ResultMatcher() {
@Override
public void match(MvcResult result) {
- assertTrue("Response should not contain header " + name, !result.getResponse().containsHeader(name));
+ assertTrue("Response should not contain header " + name,
+ !result.getResponse().containsHeader(name));
}
};
}
/**
* Assert the primary value of the named response header as a {@code long}.
- * <p>The {@link ResultMatcher} returned by this method throws an {@link AssertionError}
- * if the response does not contain the specified header, or if the supplied
- * {@code value} does not match the primary value.
+ * <p>The {@link ResultMatcher} returned by this method throws an
+ * {@link AssertionError} if the response does not contain the specified
+ * header, or if the supplied {@code value} does not match the primary value.
*/
public ResultMatcher longValue(final String name, final long value) {
return new ResultMatcher() {
@Override
public void match(MvcResult result) {
- assertTrue("Response does not contain header " + name, result.getResponse().containsHeader(name));
- assertEquals("Response header " + name, value, Long.parseLong(result.getResponse().getHeader(name)));
+ MockHttpServletResponse response = result.getResponse();
+ assertTrue("Response does not contain header " + name, response.containsHeader(name));
+ assertEquals("Response header " + name, value, Long.parseLong(response.getHeader(name)));
}
};
}
@@ -105,10 +142,9 @@ public class HeaderResultMatchers {
/**
* Assert the primary value of the named response header as a date String,
* using the preferred date format described in RFC 7231.
- * <p>The {@link ResultMatcher} returned by this method throws an {@link AssertionError}
- * if the response does not contain the specified header, or if the supplied
- * {@code value} does not match the primary value.
- *
+ * <p>The {@link ResultMatcher} returned by this method throws an
+ * {@link AssertionError} if the response does not contain the specified
+ * header, or if the supplied {@code value} does not match the primary value.
* @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.1.1">Section 7.1.1.1 of RFC 7231</a>
* @since 4.2
*/
@@ -118,8 +154,10 @@ public class HeaderResultMatchers {
public void match(MvcResult result) {
SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
format.setTimeZone(TimeZone.getTimeZone("GMT"));
- assertTrue("Response does not contain header " + name, result.getResponse().containsHeader(name));
- assertEquals("Response header " + name, format.format(new Date(value)), result.getResponse().getHeader(name));
+ String formatted = format.format(new Date(value));
+ MockHttpServletResponse response = result.getResponse();
+ assertTrue("Response does not contain header " + name, response.containsHeader(name));
+ assertEquals("Response header " + name, formatted, response.getHeader(name));
}
};
}
diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/result/JsonPathResultMatchers.java b/spring-test/src/main/java/org/springframework/test/web/servlet/result/JsonPathResultMatchers.java
index 39377075..f572a372 100644
--- a/spring-test/src/main/java/org/springframework/test/web/servlet/result/JsonPathResultMatchers.java
+++ b/spring-test/src/main/java/org/springframework/test/web/servlet/result/JsonPathResultMatchers.java
@@ -16,12 +16,17 @@
package org.springframework.test.web.servlet.result;
+import java.io.UnsupportedEncodingException;
+
import com.jayway.jsonpath.JsonPath;
import org.hamcrest.Matcher;
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.core.StringStartsWith;
import org.springframework.test.util.JsonPathExpectationsHelper;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultMatcher;
+import org.springframework.util.StringUtils;
/**
* Factory for assertions on the response content using
@@ -33,12 +38,15 @@ import org.springframework.test.web.servlet.ResultMatcher;
* @author Rossen Stoyanchev
* @author Craig Andrews
* @author Sam Brannen
+ * @author Brian Clozel
* @since 3.2
*/
public class JsonPathResultMatchers {
private final JsonPathExpectationsHelper jsonPathHelper;
+ private String prefix;
+
/**
* Protected constructor.
@@ -52,6 +60,19 @@ public class JsonPathResultMatchers {
this.jsonPathHelper = new JsonPathExpectationsHelper(expression, args);
}
+ /**
+ * Configures the current {@code JsonPathResultMatchers} instance
+ * to verify that the JSON payload is prepended with the given prefix.
+ * <p>Use this method if the JSON payloads are prefixed to avoid
+ * Cross Site Script Inclusion (XSSI) attacks.
+ * @param prefix the string prefix prepended to the actual JSON payload
+ * @since 4.3
+ */
+ public JsonPathResultMatchers prefix(String prefix) {
+ this.prefix = prefix;
+ return this;
+ }
+
/**
* Evaluate the JSON path expression against the response content and
@@ -61,7 +82,7 @@ public class JsonPathResultMatchers {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
- String content = result.getResponse().getContentAsString();
+ String content = getContent(result);
jsonPathHelper.assertValue(content, matcher);
}
};
@@ -75,7 +96,7 @@ public class JsonPathResultMatchers {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
- jsonPathHelper.assertValue(result.getResponse().getContentAsString(), expectedValue);
+ jsonPathHelper.assertValue(getContent(result), expectedValue);
}
};
}
@@ -91,7 +112,7 @@ public class JsonPathResultMatchers {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
- String content = result.getResponse().getContentAsString();
+ String content = getContent(result);
jsonPathHelper.exists(content);
}
};
@@ -108,7 +129,7 @@ public class JsonPathResultMatchers {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
- String content = result.getResponse().getContentAsString();
+ String content = getContent(result);
jsonPathHelper.doesNotExist(content);
}
};
@@ -128,7 +149,7 @@ public class JsonPathResultMatchers {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
- String content = result.getResponse().getContentAsString();
+ String content = getContent(result);
jsonPathHelper.assertValueIsEmpty(content);
}
};
@@ -148,7 +169,7 @@ public class JsonPathResultMatchers {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
- String content = result.getResponse().getContentAsString();
+ String content = getContent(result);
jsonPathHelper.assertValueIsNotEmpty(content);
}
};
@@ -163,7 +184,7 @@ public class JsonPathResultMatchers {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
- String content = result.getResponse().getContentAsString();
+ String content = getContent(result);
jsonPathHelper.assertValueIsString(content);
}
};
@@ -178,7 +199,7 @@ public class JsonPathResultMatchers {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
- String content = result.getResponse().getContentAsString();
+ String content = getContent(result);
jsonPathHelper.assertValueIsBoolean(content);
}
};
@@ -193,7 +214,7 @@ public class JsonPathResultMatchers {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
- String content = result.getResponse().getContentAsString();
+ String content = getContent(result);
jsonPathHelper.assertValueIsNumber(content);
}
};
@@ -207,7 +228,7 @@ public class JsonPathResultMatchers {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
- String content = result.getResponse().getContentAsString();
+ String content = getContent(result);
jsonPathHelper.assertValueIsArray(content);
}
};
@@ -222,10 +243,29 @@ public class JsonPathResultMatchers {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
- String content = result.getResponse().getContentAsString();
+ String content = getContent(result);
jsonPathHelper.assertValueIsMap(content);
}
};
}
+ private String getContent(MvcResult result) throws UnsupportedEncodingException {
+ String content = result.getResponse().getContentAsString();
+ if (StringUtils.hasLength(this.prefix)) {
+ try {
+ String reason = String.format("Expected a JSON payload prefixed with \"%s\" but found: %s",
+ this.prefix, StringUtils.quote(content.substring(0, this.prefix.length())));
+ MatcherAssert.assertThat(reason, content, StringStartsWith.startsWith(this.prefix));
+ return content.substring(this.prefix.length());
+ }
+ catch (StringIndexOutOfBoundsException oobe) {
+ throw new AssertionError(
+ "JSON prefix \"" + this.prefix + "\" not found, exception: " + oobe.getMessage());
+ }
+ }
+ else {
+ return content;
+ }
+ }
+
}
diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/result/MockMvcResultMatchers.java b/spring-test/src/main/java/org/springframework/test/web/servlet/result/MockMvcResultMatchers.java
index 32ad49fd..a12baf94 100644
--- a/spring-test/src/main/java/org/springframework/test/web/servlet/result/MockMvcResultMatchers.java
+++ b/spring-test/src/main/java/org/springframework/test/web/servlet/result/MockMvcResultMatchers.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/result/StatusResultMatchers.java b/spring-test/src/main/java/org/springframework/test/web/servlet/result/StatusResultMatchers.java
index 5efa9100..f2eba236 100644
--- a/spring-test/src/main/java/org/springframework/test/web/servlet/result/StatusResultMatchers.java
+++ b/spring-test/src/main/java/org/springframework/test/web/servlet/result/StatusResultMatchers.java
@@ -33,6 +33,7 @@ import static org.springframework.test.util.AssertionErrors.*;
* @author Keesun Baik
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
+ * @author Brian Clozel
* @since 3.2
*/
public class StatusResultMatchers {
@@ -562,6 +563,14 @@ public class StatusResultMatchers {
}
/**
+ * Assert the response status code is {@code HttpStatus.UNAVAILABLE_FOR_LEGAL_REASONS} (451).
+ * @since 4.3
+ */
+ public ResultMatcher isUnavailableForLegalReasons() {
+ return matcher(HttpStatus.valueOf(451));
+ }
+
+ /**
* Assert the response status code is {@code HttpStatus.INTERNAL_SERVER_ERROR} (500).
*/
public ResultMatcher isInternalServerError() {
diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/AbstractMockMvcBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/AbstractMockMvcBuilder.java
index 21dd9ebb..50c4db84 100644
--- a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/AbstractMockMvcBuilder.java
+++ b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/AbstractMockMvcBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -55,7 +55,7 @@ public abstract class AbstractMockMvcBuilder<B extends AbstractMockMvcBuilder<B>
private final List<ResultHandler> globalResultHandlers = new ArrayList<ResultHandler>();
- private Boolean dispatchOptions = Boolean.FALSE;
+ private Boolean dispatchOptions = Boolean.TRUE;
private final List<MockMvcConfigurer> configurers = new ArrayList<MockMvcConfigurer>(4);
diff --git a/spring-test/src/main/resources/META-INF/spring.factories b/spring-test/src/main/resources/META-INF/spring.factories
index 012b38df..30cd85a1 100644
--- a/spring-test/src/main/resources/META-INF/spring.factories
+++ b/spring-test/src/main/resources/META-INF/spring.factories
@@ -7,3 +7,8 @@ org.springframework.test.context.TestExecutionListener = \
org.springframework.test.context.support.DirtiesContextTestExecutionListener,\
org.springframework.test.context.transaction.TransactionalTestExecutionListener,\
org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener
+
+# Default ContextCustomizerFactory implementations for the Spring TestContext Framework
+#
+org.springframework.test.context.ContextCustomizerFactory = \
+ org.springframework.test.context.web.socket.MockServerContainerContextCustomizerFactory
diff --git a/spring-test/src/test/java/org/springframework/mock/web/MockFilterChainTests.java b/spring-test/src/test/java/org/springframework/mock/web/MockFilterChainTests.java
index bbafd498..d008751f 100644
--- a/spring-test/src/test/java/org/springframework/mock/web/MockFilterChainTests.java
+++ b/spring-test/src/test/java/org/springframework/mock/web/MockFilterChainTests.java
@@ -83,7 +83,7 @@ public class MockFilterChainTests {
chain.doFilter(this.request, this.response);
fail("Expected Exception");
}
- catch(IllegalStateException ex) {
+ catch (IllegalStateException ex) {
assertEquals("This FilterChain has already been called!", ex.getMessage());
}
}
@@ -98,7 +98,7 @@ public class MockFilterChainTests {
chain.doFilter(this.request, this.response);
fail("Expected Exception");
}
- catch(IllegalStateException ex) {
+ catch (IllegalStateException ex) {
assertEquals("This FilterChain has already been called!", ex.getMessage());
}
}
@@ -122,7 +122,7 @@ public class MockFilterChainTests {
chain.doFilter(this.request, this.response);
fail("Expected Exception");
}
- catch(IllegalStateException ex) {
+ catch (IllegalStateException ex) {
assertEquals("This FilterChain has already been called!", ex.getMessage());
}
}
diff --git a/spring-test/src/test/java/org/springframework/test/annotation/ProfileValueUtilsTests.java b/spring-test/src/test/java/org/springframework/test/annotation/ProfileValueUtilsTests.java
index 4296fd07..da461bee 100644
--- a/spring-test/src/test/java/org/springframework/test/annotation/ProfileValueUtilsTests.java
+++ b/spring-test/src/test/java/org/springframework/test/annotation/ProfileValueUtilsTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -91,7 +91,10 @@ public class ProfileValueUtilsTests {
assertClassIsEnabled(EnabledAnnotatedMultiValue.class);
assertClassIsEnabled(MetaEnabledClass.class);
assertClassIsEnabled(MetaEnabledWithCustomProfileValueSourceClass.class);
+ assertClassIsEnabled(EnabledWithCustomProfileValueSourceOnTestInterface.class);
+
assertClassIsDisabled(DisabledAnnotatedSingleValue.class);
+ assertClassIsDisabled(DisabledAnnotatedSingleValueOnTestInterface.class);
assertClassIsDisabled(DisabledAnnotatedMultiValue.class);
assertClassIsDisabled(MetaDisabledClass.class);
assertClassIsDisabled(MetaDisabledWithCustomProfileValueSourceClass.class);
@@ -105,6 +108,7 @@ public class ProfileValueUtilsTests {
assertMethodIsEnabled(ENABLED_ANNOTATED_METHOD, EnabledAnnotatedSingleValue.class);
assertMethodIsDisabled(DISABLED_ANNOTATED_METHOD, EnabledAnnotatedSingleValue.class);
+
assertMethodIsEnabled(NON_ANNOTATED_METHOD, MetaEnabledAnnotatedSingleValue.class);
assertMethodIsEnabled(ENABLED_ANNOTATED_METHOD, MetaEnabledAnnotatedSingleValue.class);
assertMethodIsDisabled(DISABLED_ANNOTATED_METHOD, MetaEnabledAnnotatedSingleValue.class);
@@ -117,6 +121,8 @@ public class ProfileValueUtilsTests {
assertMethodIsDisabled(ENABLED_ANNOTATED_METHOD, DisabledAnnotatedSingleValue.class);
assertMethodIsDisabled(DISABLED_ANNOTATED_METHOD, DisabledAnnotatedSingleValue.class);
+ assertMethodIsDisabled(NON_ANNOTATED_METHOD, DisabledAnnotatedSingleValueOnTestInterface.class);
+
assertMethodIsDisabled(NON_ANNOTATED_METHOD, MetaDisabledAnnotatedSingleValue.class);
assertMethodIsDisabled(ENABLED_ANNOTATED_METHOD, MetaDisabledAnnotatedSingleValue.class);
assertMethodIsDisabled(DISABLED_ANNOTATED_METHOD, MetaDisabledAnnotatedSingleValue.class);
@@ -176,6 +182,17 @@ public class ProfileValueUtilsTests {
}
}
+ @IfProfileValue(name = NAME, value = VALUE + "X")
+ private interface IfProfileValueTestInterface {
+ }
+
+ @SuppressWarnings("unused")
+ private static class DisabledAnnotatedSingleValueOnTestInterface implements IfProfileValueTestInterface {
+
+ public void nonAnnotatedMethod() {
+ }
+ }
+
@SuppressWarnings("unused")
@IfProfileValue(name = NAME, values = { "foo", VALUE, "bar" })
private static class EnabledAnnotatedMultiValue {
@@ -302,4 +319,13 @@ public class ProfileValueUtilsTests {
private static class MetaDisabledWithCustomProfileValueSourceClass {
}
+ @ProfileValueSourceConfiguration(HardCodedProfileValueSource.class)
+ private interface CustomProfileValueSourceTestInterface {
+ }
+
+ @IfProfileValue(name = NAME, value = "42")
+ private static class EnabledWithCustomProfileValueSourceOnTestInterface
+ implements CustomProfileValueSourceTestInterface {
+ }
+
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/BootstrapUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/BootstrapUtilsTests.java
index 8efb56d3..70d80ee3 100644
--- a/spring-test/src/test/java/org/springframework/test/context/BootstrapUtilsTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/BootstrapUtilsTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,16 +24,20 @@ import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.test.context.support.DefaultTestContextBootstrapper;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.test.context.web.WebTestContextBootstrapper;
-import static org.hamcrest.CoreMatchers.*;
-import static org.junit.Assert.*;
-import static org.mockito.Mockito.*;
-import static org.springframework.test.context.BootstrapUtils.*;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.mock;
+import static org.springframework.test.context.BootstrapUtils.resolveTestContextBootstrapper;
/**
* Unit tests for {@link BootstrapUtils}.
*
* @author Sam Brannen
+ * @author Phillip Webb
* @since 4.2
*/
public class BootstrapUtilsTests {
@@ -41,7 +45,7 @@ public class BootstrapUtilsTests {
private final CacheAwareContextLoaderDelegate delegate = mock(CacheAwareContextLoaderDelegate.class);
@Rule
- public ExpectedException exception = ExpectedException.none();
+ public final ExpectedException exception = ExpectedException.none();
@Test
public void resolveTestContextBootstrapperForNonAnnotatedClass() {
@@ -49,6 +53,11 @@ public class BootstrapUtilsTests {
}
@Test
+ public void resolveTestContextBootstrapperForWebAppConfigurationAnnotatedClass() {
+ assertBootstrapper(WebAppConfigurationAnnotatedClass.class, WebTestContextBootstrapper.class);
+ }
+
+ @Test
public void resolveTestContextBootstrapperWithEmptyBootstrapWithAnnotation() {
BootstrapContext bootstrapContext = BootstrapTestUtils.buildBootstrapContext(EmptyBootstrapWithAnnotationClass.class, delegate);
@@ -124,4 +133,7 @@ public class BootstrapUtilsTests {
@BootWithFoo
static class DoubleMetaAnnotatedBootstrapWithAnnotationClass {}
+ @WebAppConfiguration
+ static class WebAppConfigurationAnnotatedClass {}
+
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/MergedContextConfigurationTests.java b/spring-test/src/test/java/org/springframework/test/context/MergedContextConfigurationTests.java
index bf8eba04..0409e11f 100644
--- a/spring-test/src/test/java/org/springframework/test/context/MergedContextConfigurationTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/MergedContextConfigurationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
package org.springframework.test.context;
+import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@@ -28,6 +29,7 @@ import org.springframework.test.context.support.AnnotationConfigContextLoader;
import org.springframework.test.context.support.GenericXmlContextLoader;
import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
/**
* Unit tests for {@link MergedContextConfiguration}.
@@ -37,6 +39,7 @@ import static org.junit.Assert.*;
* {@link org.springframework.test.context.cache.ContextCache ContextCache}.
*
* @author Sam Brannen
+ * @author Phillip Webb
* @since 3.1
*/
public class MergedContextConfigurationTests {
@@ -401,6 +404,35 @@ public class MergedContextConfigurationTests {
}
/**
+ * @since 4.3
+ */
+ @Test
+ public void equalsWithSameContextCustomizers() {
+ Set<ContextCustomizer> customizers = Collections.singleton(mock(ContextCustomizer.class));
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, null, null, customizers, loader, null, null);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, null, null, customizers, loader, null, null);
+ assertEquals(mergedConfig1, mergedConfig2);
+ }
+
+ /**
+ * @since 4.3
+ */
+ @Test
+ public void equalsWithDifferentContextCustomizers() {
+ Set<ContextCustomizer> customizers1 = Collections.singleton(mock(ContextCustomizer.class));
+ Set<ContextCustomizer> customizers2 = Collections.singleton(mock(ContextCustomizer.class));
+
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, null, null, customizers1, loader, null, null);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, null, null, customizers2, loader, null, null);
+ assertNotEquals(mergedConfig1, mergedConfig2);
+ assertNotEquals(mergedConfig2, mergedConfig1);
+ }
+
+ /**
* @since 3.2.2
*/
@Test
diff --git a/spring-test/src/test/java/org/springframework/test/context/TestExecutionListenersTests.java b/spring-test/src/test/java/org/springframework/test/context/TestExecutionListenersTests.java
index 648655fb..4df53d82 100644
--- a/spring-test/src/test/java/org/springframework/test/context/TestExecutionListenersTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/TestExecutionListenersTests.java
@@ -116,6 +116,12 @@ public class TestExecutionListenersTests {
}
@Test
+ public void customListenersDeclaredOnInterface() {
+ assertRegisteredListeners(ExplicitListenersOnTestInterfaceTestCase.class,
+ asList(FooTestExecutionListener.class, BarTestExecutionListener.class));
+ }
+
+ @Test
public void nonInheritedListeners() {
assertNumRegisteredListeners(NonInheritedListenersTestCase.class, 1);
}
@@ -229,6 +235,13 @@ public class TestExecutionListenersTests {
static class NonInheritedListenersTestCase extends InheritedListenersTestCase {
}
+ @TestExecutionListeners({ FooTestExecutionListener.class, BarTestExecutionListener.class })
+ interface ExplicitListenersTestInterface {
+ }
+
+ static class ExplicitListenersOnTestInterfaceTestCase implements ExplicitListenersTestInterface {
+ }
+
@TestExecutionListeners(listeners = FooTestExecutionListener.class, value = BarTestExecutionListener.class)
static class DuplicateListenersConfigTestCase {
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/cache/ClassLevelDirtiesContextTestNGTests.java b/spring-test/src/test/java/org/springframework/test/context/cache/ClassLevelDirtiesContextTestNGTests.java
index 2fcbdb46..1c603e33 100644
--- a/spring-test/src/test/java/org/springframework/test/context/cache/ClassLevelDirtiesContextTestNGTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/cache/ClassLevelDirtiesContextTestNGTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -171,7 +171,7 @@ public class ClassLevelDirtiesContextTestNGTests {
@TestExecutionListeners(listeners = { DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class }, inheritListeners = false)
@ContextConfiguration
- public static abstract class BaseTestCase extends AbstractTestNGSpringContextTests {
+ static abstract class BaseTestCase extends AbstractTestNGSpringContextTests {
@Configuration
static class Config {
@@ -189,75 +189,75 @@ public class ClassLevelDirtiesContextTestNGTests {
}
}
- public static final class CleanTestCase extends BaseTestCase {
+ static final class CleanTestCase extends BaseTestCase {
@org.testng.annotations.Test
- public void verifyContextWasAutowired() {
+ void verifyContextWasAutowired() {
assertApplicationContextWasAutowired();
}
}
@DirtiesContext
- public static class ClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase extends BaseTestCase {
+ static class ClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase extends BaseTestCase {
@org.testng.annotations.Test
- public void verifyContextWasAutowired() {
+ void verifyContextWasAutowired() {
assertApplicationContextWasAutowired();
}
}
- public static class InheritedClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase extends
+ static class InheritedClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase extends
ClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase {
}
@DirtiesContext(classMode = ClassMode.AFTER_CLASS)
- public static class ClassLevelDirtiesContextWithCleanMethodsAndAfterClassModeTestCase extends BaseTestCase {
+ static class ClassLevelDirtiesContextWithCleanMethodsAndAfterClassModeTestCase extends BaseTestCase {
@org.testng.annotations.Test
- public void verifyContextWasAutowired() {
+ void verifyContextWasAutowired() {
assertApplicationContextWasAutowired();
}
}
- public static class InheritedClassLevelDirtiesContextWithCleanMethodsAndAfterClassModeTestCase extends
+ static class InheritedClassLevelDirtiesContextWithCleanMethodsAndAfterClassModeTestCase extends
ClassLevelDirtiesContextWithCleanMethodsAndAfterClassModeTestCase {
}
@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
- public static class ClassLevelDirtiesContextWithAfterEachTestMethodModeTestCase extends BaseTestCase {
+ static class ClassLevelDirtiesContextWithAfterEachTestMethodModeTestCase extends BaseTestCase {
@org.testng.annotations.Test
- public void verifyContextWasAutowired1() {
+ void verifyContextWasAutowired1() {
assertApplicationContextWasAutowired();
}
@org.testng.annotations.Test
- public void verifyContextWasAutowired2() {
+ void verifyContextWasAutowired2() {
assertApplicationContextWasAutowired();
}
@org.testng.annotations.Test
- public void verifyContextWasAutowired3() {
+ void verifyContextWasAutowired3() {
assertApplicationContextWasAutowired();
}
}
- public static class InheritedClassLevelDirtiesContextWithAfterEachTestMethodModeTestCase extends
+ static class InheritedClassLevelDirtiesContextWithAfterEachTestMethodModeTestCase extends
ClassLevelDirtiesContextWithAfterEachTestMethodModeTestCase {
}
@DirtiesContext
- public static class ClassLevelDirtiesContextWithDirtyMethodsTestCase extends BaseTestCase {
+ static class ClassLevelDirtiesContextWithDirtyMethodsTestCase extends BaseTestCase {
@org.testng.annotations.Test
@DirtiesContext
- public void dirtyContext() {
+ void dirtyContext() {
assertApplicationContextWasAutowired();
}
}
- public static class InheritedClassLevelDirtiesContextWithDirtyMethodsTestCase extends
+ static class InheritedClassLevelDirtiesContextWithDirtyMethodsTestCase extends
ClassLevelDirtiesContextWithDirtyMethodsTestCase {
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/cache/ClassLevelDirtiesContextTests.java b/spring-test/src/test/java/org/springframework/test/context/cache/ClassLevelDirtiesContextTests.java
index 896c4654..9da13e7b 100644
--- a/spring-test/src/test/java/org/springframework/test/context/cache/ClassLevelDirtiesContextTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/cache/ClassLevelDirtiesContextTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,10 +30,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode;
import org.springframework.test.context.ContextConfiguration;
-import org.springframework.test.context.TestExecutionListeners;
-import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
-import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
-import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
+import org.springframework.test.context.junit4.SpringRunner;
import static org.junit.Assert.*;
import static org.springframework.test.context.cache.ContextCacheTestUtils.*;
@@ -41,9 +38,8 @@ import static org.springframework.test.context.junit4.JUnitTestingUtils.*;
/**
* JUnit 4 based integration test which verifies correct {@linkplain ContextCache
- * application context caching} in conjunction with the
- * {@link SpringJUnit4ClassRunner} and {@link DirtiesContext @DirtiesContext}
- * at the class level.
+ * application context caching} in conjunction with the {@link SpringRunner} and
+ * {@link DirtiesContext @DirtiesContext} at the class level.
*
* @author Sam Brannen
* @since 3.0
@@ -148,10 +144,9 @@ public class ClassLevelDirtiesContextTests {
// -------------------------------------------------------------------
- @RunWith(SpringJUnit4ClassRunner.class)
- @TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class })
+ @RunWith(SpringRunner.class)
@ContextConfiguration
- public static abstract class BaseTestCase {
+ static abstract class BaseTestCase {
@Configuration
static class Config {
diff --git a/spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheTests.java b/spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheTests.java
index 5d518d3c..4624fcb7 100644
--- a/spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -42,6 +42,7 @@ import static org.springframework.test.context.cache.ContextCacheTestUtils.*;
* @author Sam Brannen
* @author Michail Nikolaev
* @since 3.1
+ * @see LruContextCacheTests
* @see SpringRunnerContextCacheTests
*/
public class ContextCacheTests {
diff --git a/spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheUtilsTests.java
new file mode 100644
index 00000000..ac6cc723
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheUtilsTests.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.cache;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.springframework.core.SpringProperties;
+
+import static org.junit.Assert.*;
+import static org.springframework.test.context.cache.ContextCacheUtils.*;
+import static org.springframework.test.context.cache.ContextCache.*;
+
+/**
+ * Unit tests for {@link ContextCacheUtils}.
+ *
+ * @author Sam Brannen
+ * @since 4.3
+ */
+public class ContextCacheUtilsTests {
+
+ @Before
+ @After
+ public void clearProperties() {
+ System.clearProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME);
+ SpringProperties.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, null);
+ }
+
+ @Test
+ public void retrieveMaxCacheSizeFromDefault() {
+ assertDefaultValue();
+ }
+
+ @Test
+ public void retrieveMaxCacheSizeFromBogusSystemProperty() {
+ System.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, "bogus");
+ assertDefaultValue();
+ }
+
+ @Test
+ public void retrieveMaxCacheSizeFromBogusSpringProperty() {
+ SpringProperties.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, "bogus");
+ assertDefaultValue();
+ }
+
+ @Test
+ public void retrieveMaxCacheSizeFromDecimalSpringProperty() {
+ SpringProperties.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, "3.14");
+ assertDefaultValue();
+ }
+
+ @Test
+ public void retrieveMaxCacheSizeFromSystemProperty() {
+ System.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, "42");
+ assertEquals(42, retrieveMaxCacheSize());
+ }
+
+ @Test
+ public void retrieveMaxCacheSizeFromSystemPropertyContainingWhitespace() {
+ System.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, "42\t");
+ assertEquals(42, retrieveMaxCacheSize());
+ }
+
+ @Test
+ public void retrieveMaxCacheSizeFromSpringProperty() {
+ SpringProperties.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, "99");
+ assertEquals(99, retrieveMaxCacheSize());
+ }
+
+ private static void assertDefaultValue() {
+ assertEquals(DEFAULT_MAX_CONTEXT_CACHE_SIZE, retrieveMaxCacheSize());
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/cache/LruContextCacheTests.java b/spring-test/src/test/java/org/springframework/test/context/cache/LruContextCacheTests.java
new file mode 100644
index 00000000..4ae97b61
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/cache/LruContextCacheTests.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.cache;
+
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Test;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.test.context.MergedContextConfiguration;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import static java.util.Arrays.*;
+import static java.util.stream.Collectors.*;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * Unit tests for the LRU eviction policy in {@link DefaultContextCache}.
+ *
+ * @author Sam Brannen
+ * @since 4.3
+ * @see ContextCacheTests
+ */
+public class LruContextCacheTests {
+
+ private static final MergedContextConfiguration abcConfig = config(Abc.class);
+ private static final MergedContextConfiguration fooConfig = config(Foo.class);
+ private static final MergedContextConfiguration barConfig = config(Bar.class);
+ private static final MergedContextConfiguration bazConfig = config(Baz.class);
+
+
+ private final ConfigurableApplicationContext abcContext = mock(ConfigurableApplicationContext.class);
+ private final ConfigurableApplicationContext fooContext = mock(ConfigurableApplicationContext.class);
+ private final ConfigurableApplicationContext barContext = mock(ConfigurableApplicationContext.class);
+ private final ConfigurableApplicationContext bazContext = mock(ConfigurableApplicationContext.class);
+
+
+ @Test(expected = IllegalArgumentException.class)
+ public void maxCacheSizeNegativeOne() {
+ new DefaultContextCache(-1);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void maxCacheSizeZero() {
+ new DefaultContextCache(0);
+ }
+
+ @Test
+ public void maxCacheSizeOne() {
+ DefaultContextCache cache = new DefaultContextCache(1);
+ assertEquals(0, cache.size());
+ assertEquals(1, cache.getMaxSize());
+
+ cache.put(fooConfig, fooContext);
+ assertCacheContents(cache, "Foo");
+
+ cache.put(fooConfig, fooContext);
+ assertCacheContents(cache, "Foo");
+
+ cache.put(barConfig, barContext);
+ assertCacheContents(cache, "Bar");
+
+ cache.put(fooConfig, fooContext);
+ assertCacheContents(cache, "Foo");
+ }
+
+ @Test
+ public void maxCacheSizeThree() {
+ DefaultContextCache cache = new DefaultContextCache(3);
+ assertEquals(0, cache.size());
+ assertEquals(3, cache.getMaxSize());
+
+ cache.put(fooConfig, fooContext);
+ assertCacheContents(cache, "Foo");
+
+ cache.put(fooConfig, fooContext);
+ assertCacheContents(cache, "Foo");
+
+ cache.put(barConfig, barContext);
+ assertCacheContents(cache, "Foo", "Bar");
+
+ cache.put(bazConfig, bazContext);
+ assertCacheContents(cache, "Foo", "Bar", "Baz");
+
+ cache.put(abcConfig, abcContext);
+ assertCacheContents(cache, "Bar", "Baz", "Abc");
+ }
+
+ @Test
+ public void ensureLruOrderingIsUpdated() {
+ DefaultContextCache cache = new DefaultContextCache(3);
+
+ // Note: when a new entry is added it is considered the MRU entry and inserted at the tail.
+ cache.put(fooConfig, fooContext);
+ cache.put(barConfig, barContext);
+ cache.put(bazConfig, bazContext);
+ assertCacheContents(cache, "Foo", "Bar", "Baz");
+
+ // Note: the MRU entry is moved to the tail when accessed.
+ cache.get(fooConfig);
+ assertCacheContents(cache, "Bar", "Baz", "Foo");
+
+ cache.get(barConfig);
+ assertCacheContents(cache, "Baz", "Foo", "Bar");
+
+ cache.get(bazConfig);
+ assertCacheContents(cache, "Foo", "Bar", "Baz");
+
+ cache.get(barConfig);
+ assertCacheContents(cache, "Foo", "Baz", "Bar");
+ }
+
+ @Test
+ public void ensureEvictedContextsAreClosed() {
+ DefaultContextCache cache = new DefaultContextCache(2);
+
+ cache.put(fooConfig, fooContext);
+ cache.put(barConfig, barContext);
+ assertCacheContents(cache, "Foo", "Bar");
+
+ cache.put(bazConfig, bazContext);
+ assertCacheContents(cache, "Bar", "Baz");
+ verify(fooContext, times(1)).close();
+
+ cache.put(abcConfig, abcContext);
+ assertCacheContents(cache, "Baz", "Abc");
+ verify(barContext, times(1)).close();
+
+ verify(abcContext, never()).close();
+ verify(bazContext, never()).close();
+ }
+
+
+ private static MergedContextConfiguration config(Class<?> clazz) {
+ return new MergedContextConfiguration(null, null, new Class<?>[] { clazz }, null, null);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static void assertCacheContents(DefaultContextCache cache, String... expectedNames) {
+
+ Map<MergedContextConfiguration, ApplicationContext> contextMap =
+ (Map<MergedContextConfiguration, ApplicationContext>) ReflectionTestUtils.getField(cache, "contextMap");
+
+ // @formatter:off
+ List<String> actualNames = contextMap.keySet().stream()
+ .map(cfg -> cfg.getClasses()[0])
+ .map(Class::getSimpleName)
+ .collect(toList());
+ // @formatter:on
+
+ assertEquals(asList(expectedNames), actualNames);
+ }
+
+
+ private static class Abc {}
+ private static class Foo {}
+ private static class Bar {}
+ private static class Baz {}
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/cache/SpringRunnerContextCacheTests.java b/spring-test/src/test/java/org/springframework/test/context/cache/SpringRunnerContextCacheTests.java
index 44962e76..bde994dc 100644
--- a/spring-test/src/test/java/org/springframework/test/context/cache/SpringRunnerContextCacheTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/cache/SpringRunnerContextCacheTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,6 +45,7 @@ import static org.springframework.test.context.cache.ContextCacheTestUtils.*;
* @author Juergen Hoeller
* @since 2.5
* @see ContextCacheTests
+ * @see LruContextCacheTests
*/
@RunWith(SpringJUnit4ClassRunner.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ActiveProfilesInterfaceTests.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ActiveProfilesInterfaceTests.java
new file mode 100644
index 00000000..80d33c8d
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ActiveProfilesInterfaceTests.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.configuration.interfaces;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.tests.sample.beans.Employee;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Sam Brannen
+ * @since 4.3
+ */
+@RunWith(SpringRunner.class)
+public class ActiveProfilesInterfaceTests implements ActiveProfilesTestInterface {
+
+ @Autowired
+ Employee employee;
+
+
+ @Test
+ public void profileFromTestInterface() {
+ assertNotNull(employee);
+ assertEquals("dev", employee.getName());
+ }
+
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ @Profile("dev")
+ Employee employee1() {
+ return new Employee("dev");
+ }
+
+ @Bean
+ @Profile("prod")
+ Employee employee2() {
+ return new Employee("prod");
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ActiveProfilesTestInterface.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ActiveProfilesTestInterface.java
new file mode 100644
index 00000000..63ef2004
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ActiveProfilesTestInterface.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.configuration.interfaces;
+
+import org.springframework.test.context.ActiveProfiles;
+
+/**
+ * @author Sam Brannen
+ * @since 4.3
+ */
+@ActiveProfiles("dev")
+interface ActiveProfilesTestInterface {
+}
diff --git a/spring-core/src/test/java/org/springframework/tests/BuildTests.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/BootstrapWithInterfaceTests.java
index ee704806..47e52b9f 100644
--- a/spring-core/src/test/java/org/springframework/tests/BuildTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/BootstrapWithInterfaceTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,23 +14,30 @@
* limitations under the License.
*/
-package org.springframework.tests;
+package org.springframework.test.context.configuration.interfaces;
import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.junit4.SpringRunner;
-import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
/**
- * General build related tests. Part of spring-core to ensure that they run early in the
- * build process.
+ * @author Sam Brannen
+ * @since 4.3
*/
-public class BuildTests {
+@RunWith(SpringRunner.class)
+public class BootstrapWithInterfaceTests implements BootstrapWithTestInterface {
+
+ @Autowired
+ String foo;
+
@Test
- public void javaVersion() throws Exception {
- Assume.group(TestGroup.CI);
- assertThat("Java Version", JavaVersion.runningVersion(), equalTo(JavaVersion.JAVA_18));
+ public void injectedBean() {
+ assertEquals("foo", foo);
}
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/BootstrapWithTestInterface.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/BootstrapWithTestInterface.java
new file mode 100644
index 00000000..993f3afa
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/BootstrapWithTestInterface.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.configuration.interfaces;
+
+import java.util.List;
+
+import org.springframework.test.context.BootstrapWith;
+import org.springframework.test.context.ContextCustomizer;
+import org.springframework.test.context.ContextCustomizerFactory;
+import org.springframework.test.context.configuration.interfaces.BootstrapWithTestInterface.CustomTestContextBootstrapper;
+import org.springframework.test.context.support.DefaultTestContextBootstrapper;
+
+import static java.util.Collections.*;
+
+/**
+ * @author Sam Brannen
+ * @author Phillip Webb
+ * @since 4.3
+ */
+@BootstrapWith(CustomTestContextBootstrapper.class)
+interface BootstrapWithTestInterface {
+
+ static class CustomTestContextBootstrapper extends DefaultTestContextBootstrapper {
+
+ @Override
+ protected List<ContextCustomizerFactory> getContextCustomizerFactories() {
+ return singletonList(
+ (ContextCustomizerFactory) (testClass, configAttributes) -> (ContextCustomizer) (context,
+ mergedConfig) -> context.getBeanFactory().registerSingleton("foo", "foo"));
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ContextConfigurationInterfaceTests.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ContextConfigurationInterfaceTests.java
new file mode 100644
index 00000000..4a035e77
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ContextConfigurationInterfaceTests.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.configuration.interfaces;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.tests.sample.beans.Employee;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Sam Brannen
+ * @since 4.3
+ */
+@RunWith(SpringRunner.class)
+public class ContextConfigurationInterfaceTests implements ContextConfigurationTestInterface {
+
+ @Autowired
+ Employee employee;
+
+
+ @Test
+ public void profileFromTestInterface() {
+ assertNotNull(employee);
+ assertEquals("Dilbert", employee.getName());
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ContextConfigurationTestInterface.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ContextConfigurationTestInterface.java
new file mode 100644
index 00000000..7eb452fc
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ContextConfigurationTestInterface.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.configuration.interfaces;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.configuration.interfaces.ContextConfigurationTestInterface.Config;
+import org.springframework.tests.sample.beans.Employee;
+
+/**
+ * @author Sam Brannen
+ * @since 4.3
+ */
+@ContextConfiguration(classes = Config.class)
+interface ContextConfigurationTestInterface {
+
+ static class Config {
+
+ @Bean
+ Employee employee() {
+ return new Employee("Dilbert");
+ }
+ }
+
+} \ No newline at end of file
diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ContextHierarchyInterfaceTests.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ContextHierarchyInterfaceTests.java
new file mode 100644
index 00000000..832c244b
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ContextHierarchyInterfaceTests.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.configuration.interfaces;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Sam Brannen
+ * @since 4.3
+ */
+@RunWith(SpringRunner.class)
+public class ContextHierarchyInterfaceTests implements ContextHierarchyTestInterface {
+
+ @Autowired
+ String foo;
+
+ @Autowired
+ String bar;
+
+ @Autowired
+ String baz;
+
+ @Autowired
+ ApplicationContext context;
+
+
+ @Test
+ public void loadContextHierarchy() {
+ assertNotNull("child ApplicationContext", context);
+ assertNotNull("parent ApplicationContext", context.getParent());
+ assertNull("grandparent ApplicationContext", context.getParent().getParent());
+ assertEquals("foo", foo);
+ assertEquals("bar", bar);
+ assertEquals("baz-child", baz);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ContextHierarchyTestInterface.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ContextHierarchyTestInterface.java
new file mode 100644
index 00000000..538e2a93
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ContextHierarchyTestInterface.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.configuration.interfaces;
+
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextHierarchy;
+import org.springframework.test.context.hierarchies.standard.SingleTestClassWithTwoLevelContextHierarchyTests;
+
+/**
+ * @author Sam Brannen
+ * @since 4.3
+ */
+@ContextHierarchy({
+ @ContextConfiguration(classes = SingleTestClassWithTwoLevelContextHierarchyTests.ParentConfig.class),
+ @ContextConfiguration(classes = SingleTestClassWithTwoLevelContextHierarchyTests.ChildConfig.class) })
+interface ContextHierarchyTestInterface {
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/DirtiesContextInterfaceTests.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/DirtiesContextInterfaceTests.java
new file mode 100644
index 00000000..6b0be0a1
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/DirtiesContextInterfaceTests.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.configuration.interfaces;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import static org.junit.Assert.*;
+import static org.springframework.test.context.cache.ContextCacheTestUtils.*;
+import static org.springframework.test.context.junit4.JUnitTestingUtils.*;
+
+/**
+ * @author Sam Brannen
+ * @since 4.3
+ */
+@RunWith(JUnit4.class)
+public class DirtiesContextInterfaceTests {
+
+ private static final AtomicInteger cacheHits = new AtomicInteger(0);
+ private static final AtomicInteger cacheMisses = new AtomicInteger(0);
+
+
+ @BeforeClass
+ public static void verifyInitialCacheState() {
+ resetContextCache();
+ // Reset static counters in case tests are run multiple times in a test suite --
+ // for example, via JUnit's @Suite.
+ cacheHits.set(0);
+ cacheMisses.set(0);
+ assertContextCacheStatistics("BeforeClass", 0, cacheHits.get(), cacheMisses.get());
+ }
+
+ @AfterClass
+ public static void verifyFinalCacheState() {
+ assertContextCacheStatistics("AfterClass", 0, cacheHits.get(), cacheMisses.get());
+ }
+
+ @Test
+ public void verifyDirtiesContextBehavior() throws Exception {
+ runTestClassAndAssertStats(ClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase.class, 1);
+ assertContextCacheStatistics("after class-level @DirtiesContext with clean test method and default class mode",
+ 0, cacheHits.get(), cacheMisses.incrementAndGet());
+ }
+
+ private void runTestClassAndAssertStats(Class<?> testClass, int expectedTestCount) throws Exception {
+ runTestsAndAssertCounters(testClass, expectedTestCount, 0, expectedTestCount, 0, 0);
+ }
+
+
+ @RunWith(SpringRunner.class)
+ public static class ClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase
+ implements DirtiesContextTestInterface {
+
+ @Autowired
+ ApplicationContext applicationContext;
+
+
+ @Test
+ public void verifyContextWasAutowired() {
+ assertNotNull("The application context should have been autowired.", this.applicationContext);
+ }
+
+
+ @Configuration
+ static class Config {
+ /* no beans */
+ }
+
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/DirtiesContextTestInterface.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/DirtiesContextTestInterface.java
new file mode 100644
index 00000000..cd486e14
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/DirtiesContextTestInterface.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.configuration.interfaces;
+
+import org.springframework.test.annotation.DirtiesContext;
+
+/**
+ * @author Sam Brannen
+ * @since 4.3
+ */
+@DirtiesContext
+interface DirtiesContextTestInterface {
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/SqlConfigInterfaceTests.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/SqlConfigInterfaceTests.java
new file mode 100644
index 00000000..26f044f8
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/SqlConfigInterfaceTests.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.configuration.interfaces;
+
+import org.junit.Test;
+
+import org.springframework.test.context.jdbc.Sql;
+import org.springframework.test.context.jdbc.SqlConfig;
+import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Sam Brannen
+ * @since 4.3
+ */
+public class SqlConfigInterfaceTests extends AbstractTransactionalJUnit4SpringContextTests
+ implements SqlConfigTestInterface {
+
+ @Test
+ @Sql(scripts = "/org/springframework/test/context/jdbc/schema.sql", //
+ config = @SqlConfig(separator = ";"))
+ @Sql("/org/springframework/test/context/jdbc/data-add-users-with-custom-script-syntax.sql")
+ public void methodLevelScripts() {
+ assertNumUsers(3);
+ }
+
+ protected void assertNumUsers(int expected) {
+ assertEquals("Number of rows in the 'user' table.", expected, countRowsInTable("user"));
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/SqlConfigTestInterface.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/SqlConfigTestInterface.java
new file mode 100644
index 00000000..a707a4c2
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/SqlConfigTestInterface.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.configuration.interfaces;
+
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.jdbc.EmptyDatabaseConfig;
+import org.springframework.test.context.jdbc.SqlConfig;
+
+/**
+ * @author Sam Brannen
+ * @since 4.3
+ */
+@ContextConfiguration(classes = EmptyDatabaseConfig.class)
+@DirtiesContext
+@SqlConfig(commentPrefix = "`", blockCommentStartDelimiter = "#$", blockCommentEndDelimiter = "$#", separator = "@@")
+interface SqlConfigTestInterface {
+} \ No newline at end of file
diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/TestPropertySourceInterfaceTests.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/TestPropertySourceInterfaceTests.java
new file mode 100644
index 00000000..66dd6057
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/TestPropertySourceInterfaceTests.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.configuration.interfaces;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.env.Environment;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * @author Sam Brannen
+ * @since 4.3
+ */
+@RunWith(SpringRunner.class)
+public class TestPropertySourceInterfaceTests implements TestPropertySourceTestInterface {
+
+ @Autowired
+ Environment env;
+
+
+ @Test
+ public void propertiesAreAvailableInEnvironment() {
+ assertThat(property("foo"), is("bar"));
+ assertThat(property("enigma"), is("42"));
+ }
+
+ private String property(String key) {
+ return env.getProperty(key);
+ }
+
+
+ @Configuration
+ static class Config {
+ /* no user beans required for these tests */
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/TestPropertySourceTestInterface.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/TestPropertySourceTestInterface.java
new file mode 100644
index 00000000..af27989e
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/TestPropertySourceTestInterface.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.configuration.interfaces;
+
+import org.springframework.test.context.TestPropertySource;
+
+/**
+ * @author Sam Brannen
+ * @since 4.3
+ */
+@TestPropertySource(properties = { "foo = bar", "enigma: 42" })
+interface TestPropertySourceTestInterface {
+}
diff --git a/spring-core/src/test/java/org/springframework/tests/JavaVersionTests.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/WebAppConfigurationInterfaceTests.java
index 05c341fb..fdb6706b 100644
--- a/spring-core/src/test/java/org/springframework/tests/JavaVersionTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/WebAppConfigurationInterfaceTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,30 +14,31 @@
* limitations under the License.
*/
-package org.springframework.tests;
+package org.springframework.test.context.configuration.interfaces;
import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.web.context.WebApplicationContext;
-import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
/**
- * Tests for {@link JavaVersion}.
- *
- * @author Phillip Webb
+ * @author Sam Brannen
+ * @since 4.3
*/
-public class JavaVersionTests {
+@RunWith(SpringRunner.class)
+public class WebAppConfigurationInterfaceTests implements WebAppConfigurationTestInterface {
+
+ @Autowired
+ WebApplicationContext wac;
- @Test
- public void runningVersion() {
- assertNotNull(JavaVersion.runningVersion());
- assertThat(System.getProperty("java.version"), startsWith(JavaVersion.runningVersion().toString()));
- }
@Test
- public void isAtLeast() throws Exception {
- assertTrue(JavaVersion.JAVA_16.isAtLeast(JavaVersion.JAVA_16));
- assertFalse(JavaVersion.JAVA_16.isAtLeast(JavaVersion.JAVA_17));
+ public void wacLoaded() {
+ assertNotNull(wac);
}
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/WebAppConfigurationTestInterface.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/WebAppConfigurationTestInterface.java
new file mode 100644
index 00000000..dcfabd14
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/WebAppConfigurationTestInterface.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.configuration.interfaces;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.configuration.interfaces.WebAppConfigurationTestInterface.Config;
+import org.springframework.test.context.web.WebAppConfiguration;
+
+/**
+ * @author Sam Brannen
+ * @since 4.3
+ */
+@WebAppConfiguration
+@ContextConfiguration(classes = Config.class)
+interface WebAppConfigurationTestInterface {
+
+ @Configuration
+ static class Config {
+ /* no user beans required for these tests */
+ }
+
+} \ No newline at end of file
diff --git a/spring-test/src/test/java/org/springframework/test/context/env/InlinedPropertiesOverridePropertiesFilesTestPropertySourceTests.java b/spring-test/src/test/java/org/springframework/test/context/env/InlinedPropertiesOverridePropertiesFilesTestPropertySourceTests.java
new file mode 100644
index 00000000..c50f7ba6
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/env/InlinedPropertiesOverridePropertiesFilesTestPropertySourceTests.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.env;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
+import org.springframework.core.env.Environment;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import static org.junit.Assert.*;
+
+/**
+ * Integration tests for {@link TestPropertySource @TestPropertySource} support with
+ * inlined properties that overrides properties files.
+ *
+ * @author Sam Brannen
+ * @since 4.3
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+@TestPropertySource(locations = "explicit.properties", properties = "explicit = inlined")
+public class InlinedPropertiesOverridePropertiesFilesTestPropertySourceTests {
+
+ @Autowired
+ Environment env;
+
+ @Value("${explicit}")
+ String explicit;
+
+
+ @Test
+ public void inlinedPropertyOverridesValueFromPropertiesFile() {
+ assertEquals("inlined", env.getProperty("explicit"));
+ assertEquals("inlined", this.explicit);
+ }
+
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
+ return new PropertySourcesPlaceholderConfigurer();
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/SingleTestClassWithTwoLevelContextHierarchyTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/SingleTestClassWithTwoLevelContextHierarchyTests.java
index 2868b694..f8ba83fd 100644
--- a/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/SingleTestClassWithTwoLevelContextHierarchyTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/SingleTestClassWithTwoLevelContextHierarchyTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -40,7 +40,7 @@ import static org.junit.Assert.*;
public class SingleTestClassWithTwoLevelContextHierarchyTests {
@Configuration
- static class ParentConfig {
+ public static class ParentConfig {
@Bean
public String foo() {
@@ -54,7 +54,7 @@ public class SingleTestClassWithTwoLevelContextHierarchyTests {
}
@Configuration
- static class ChildConfig {
+ public static class ChildConfig {
@Bean
public String bar() {
diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/ComposedAnnotationSqlScriptsTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/ComposedAnnotationSqlScriptsTests.java
new file mode 100644
index 00000000..0e709739
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/ComposedAnnotationSqlScriptsTests.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.jdbc;
+
+import java.lang.annotation.Retention;
+
+import org.junit.Test;
+
+import org.springframework.core.annotation.AliasFor;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.jdbc.Sql.ExecutionPhase;
+import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
+
+import static java.lang.annotation.RetentionPolicy.*;
+import static org.junit.Assert.*;
+import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.*;
+
+/**
+ * Integration tests that verify support for using {@link Sql @Sql} as a
+ * merged, composed annotation.
+ *
+ * @author Sam Brannen
+ * @since 4.3
+ */
+@ContextConfiguration(classes = EmptyDatabaseConfig.class)
+@DirtiesContext
+public class ComposedAnnotationSqlScriptsTests extends AbstractTransactionalJUnit4SpringContextTests {
+
+ @Test
+ @ComposedSql(
+ scripts = { "drop-schema.sql", "schema.sql" },
+ statements = "INSERT INTO user VALUES('Dilbert')",
+ executionPhase = BEFORE_TEST_METHOD
+ )
+ public void composedSqlAnnotation() {
+ assertEquals("Number of rows in the 'user' table.", 1, countRowsInTable("user"));
+ }
+
+
+ @Sql
+ @Retention(RUNTIME)
+ @interface ComposedSql {
+
+ @AliasFor(annotation = Sql.class)
+ String[] value() default {};
+
+ @AliasFor(annotation = Sql.class)
+ String[] scripts() default {};
+
+ @AliasFor(annotation = Sql.class)
+ String[] statements() default {};
+
+ @AliasFor(annotation = Sql.class)
+ ExecutionPhase executionPhase();
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/MetaAnnotationSqlScriptsTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/MetaAnnotationSqlScriptsTests.java
index 3417c17f..4fc2069e 100644
--- a/spring-test/src/test/java/org/springframework/test/context/jdbc/MetaAnnotationSqlScriptsTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/MetaAnnotationSqlScriptsTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,7 +31,7 @@ import static org.junit.Assert.*;
/**
* Integration tests that verify support for using {@link Sql @Sql} and
- * {@link SqlGroup @SqlGroup} as a meta-annotations.
+ * {@link SqlGroup @SqlGroup} as meta-annotations.
*
* @author Sam Brannen
* @since 4.1
diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/PrimaryDataSourceTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/PrimaryDataSourceTests.java
new file mode 100644
index 00000000..f0716780
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/PrimaryDataSourceTests.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.jdbc;
+
+import javax.sql.DataSource;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.jdbc.JdbcTestUtils;
+import org.springframework.test.transaction.TransactionTestUtils;
+
+import static org.junit.Assert.*;
+
+/**
+ * Integration tests that ensure that <em>primary</em> data sources are
+ * supported.
+ *
+ * @author Sam Brannen
+ * @since 4.3
+ * @see org.springframework.test.context.transaction.PrimaryTransactionManagerTests
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+@DirtiesContext
+public class PrimaryDataSourceTests {
+
+ @Configuration
+ static class Config {
+
+ @Primary
+ @Bean
+ public DataSource primaryDataSource() {
+ // @formatter:off
+ return new EmbeddedDatabaseBuilder()
+ .generateUniqueName(true)
+ .addScript("classpath:/org/springframework/test/context/jdbc/schema.sql")
+ .build();
+ // @formatter:on
+ }
+
+ @Bean
+ public DataSource additionalDataSource() {
+ return new EmbeddedDatabaseBuilder().generateUniqueName(true).build();
+ }
+
+ }
+
+
+ private JdbcTemplate jdbcTemplate;
+
+
+ @Autowired
+ public void setDataSource(DataSource dataSource) {
+ this.jdbcTemplate = new JdbcTemplate(dataSource);
+ }
+
+ @Test
+ @Sql("data.sql")
+ public void dataSourceTest() {
+ TransactionTestUtils.assertInTransaction(false);
+ assertEquals("Number of rows in the 'user' table.", 1,
+ JdbcTestUtils.countRowsInTable(this.jdbcTemplate, "user"));
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/AbstractTransactionalSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/AbstractTransactionalSpringRunnerTests.java
index 493be806..5760755f 100644
--- a/spring-test/src/test/java/org/springframework/test/context/junit4/AbstractTransactionalSpringRunnerTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/AbstractTransactionalSpringRunnerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,7 +32,7 @@ import org.springframework.transaction.annotation.Transactional;
* @see MethodLevelTransactionalSpringRunnerTests
* @see Transactional
*/
-@RunWith(SpringJUnit4ClassRunner.class)
+@RunWith(SpringRunner.class)
@ContextConfiguration("transactionalTests-context.xml")
public abstract class AbstractTransactionalSpringRunnerTests {
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/BeforeAndAfterTransactionAnnotationTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/BeforeAndAfterTransactionAnnotationTests.java
index 23440f7d..4596e6cf 100644
--- a/spring-test/src/test/java/org/springframework/test/context/junit4/BeforeAndAfterTransactionAnnotationTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/BeforeAndAfterTransactionAnnotationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +30,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.transaction.AfterTransaction;
import org.springframework.test.context.transaction.BeforeTransaction;
+import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import static org.junit.Assert.*;
@@ -43,6 +44,7 @@ import static org.springframework.test.transaction.TransactionTestUtils.*;
* @author Sam Brannen
* @since 2.5
*/
+@Transactional
public class BeforeAndAfterTransactionAnnotationTests extends AbstractTransactionalSpringRunnerTests {
protected static JdbcTemplate jdbcTemplate;
@@ -79,7 +81,7 @@ public class BeforeAndAfterTransactionAnnotationTests extends AbstractTransactio
}
@BeforeTransaction
- public void beforeTransaction() {
+ void beforeTransaction() {
assertInTransaction(false);
this.inTransaction = true;
BeforeAndAfterTransactionAnnotationTests.numBeforeTransactionCalls++;
@@ -88,7 +90,7 @@ public class BeforeAndAfterTransactionAnnotationTests extends AbstractTransactio
}
@AfterTransaction
- public void afterTransaction() {
+ void afterTransaction() {
assertInTransaction(false);
this.inTransaction = false;
BeforeAndAfterTransactionAnnotationTests.numAfterTransactionCalls++;
@@ -115,7 +117,6 @@ public class BeforeAndAfterTransactionAnnotationTests extends AbstractTransactio
}
@Test
- @Transactional
public void transactionalMethod1() {
assertInTransaction(true);
assertEquals("Adding jane", 1, addPerson(jdbcTemplate, JANE));
@@ -124,7 +125,6 @@ public class BeforeAndAfterTransactionAnnotationTests extends AbstractTransactio
}
@Test
- @Transactional
public void transactionalMethod2() {
assertInTransaction(true);
assertEquals("Adding jane", 1, addPerson(jdbcTemplate, JANE));
@@ -134,6 +134,7 @@ public class BeforeAndAfterTransactionAnnotationTests extends AbstractTransactio
}
@Test
+ @Transactional(propagation = Propagation.NOT_SUPPORTED)
public void nonTransactionalMethod() {
assertInTransaction(false);
assertEquals("Adding luke", 1, addPerson(jdbcTemplate, LUKE));
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/ClassLevelDisabledSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/ClassLevelDisabledSpringRunnerTests.java
index b55abdaa..2db442a9 100644
--- a/spring-test/src/test/java/org/springframework/test/context/junit4/ClassLevelDisabledSpringRunnerTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/ClassLevelDisabledSpringRunnerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author 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 @@ import static org.junit.Assert.*;
* @author Juergen Hoeller
* @author Sam Brannen
*/
-@RunWith(SpringJUnit4ClassRunner.class)
+@RunWith(SpringRunner.class)
@TestExecutionListeners(ClassLevelDisabledSpringRunnerTests.CustomTestExecutionListener.class)
@IfProfileValue(name = "ClassLevelDisabledSpringRunnerTests.profile_value.name", value = "enigmaX")
public class ClassLevelDisabledSpringRunnerTests {
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/ClassLevelTransactionalSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/ClassLevelTransactionalSpringRunnerTests.java
index 8f29a2d0..5eb8c8ed 100644
--- a/spring-test/src/test/java/org/springframework/test/context/junit4/ClassLevelTransactionalSpringRunnerTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/ClassLevelTransactionalSpringRunnerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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 static org.springframework.test.transaction.TransactionTestUtils.*;
* {@link Transactional &#64;Transactional}, {@link TestExecutionListeners
* &#64;TestExecutionListeners}, and {@link ContextConfiguration
* &#64;ContextConfiguration} annotations in conjunction with the
- * {@link SpringJUnit4ClassRunner} and the following
+ * {@link SpringRunner} and the following
* {@link TestExecutionListener TestExecutionListeners}:
*
* <ul>
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/ContextCustomizerSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/ContextCustomizerSpringRunnerTests.java
new file mode 100644
index 00000000..2edfb0d5
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/ContextCustomizerSpringRunnerTests.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4;
+
+import java.util.List;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.BootstrapWith;
+import org.springframework.test.context.ContextCustomizer;
+import org.springframework.test.context.ContextCustomizerFactory;
+import org.springframework.test.context.junit4.ContextCustomizerSpringRunnerTests.CustomTestContextBootstrapper;
+import org.springframework.test.context.support.DefaultTestContextBootstrapper;
+
+import static java.util.Collections.*;
+import static org.junit.Assert.*;
+
+/**
+ * JUnit 4 based integration test which verifies support of
+ * {@link ContextCustomizerFactory} and {@link ContextCustomizer}.
+ *
+ * @author Sam Brannen
+ * @author Phillip Webb
+ * @since 4.3
+ */
+@RunWith(SpringRunner.class)
+@BootstrapWith(CustomTestContextBootstrapper.class)
+public class ContextCustomizerSpringRunnerTests {
+
+ @Autowired String foo;
+
+
+ @Test
+ public void injectedBean() {
+ assertEquals("foo", foo);
+ }
+
+
+ static class CustomTestContextBootstrapper extends DefaultTestContextBootstrapper {
+
+ @Override
+ protected List<ContextCustomizerFactory> getContextCustomizerFactories() {
+ return singletonList(
+ (ContextCustomizerFactory) (testClass, configAttributes) ->
+ (ContextCustomizer) (context, mergedConfig) -> context.getBeanFactory().registerSingleton("foo", "foo")
+ );
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/CustomDefaultContextLoaderClassSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/CustomDefaultContextLoaderClassSpringRunnerTests.java
index aab80b71..be8a2ff9 100644
--- a/spring-test/src/test/java/org/springframework/test/context/junit4/CustomDefaultContextLoaderClassSpringRunnerTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/CustomDefaultContextLoaderClassSpringRunnerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author 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 static org.junit.Assert.*;
* @author Sam Brannen
* @since 3.0
*/
-@RunWith(SpringJUnit4ClassRunner.class)
+@RunWith(SpringRunner.class)
@BootstrapWith(CustomDefaultContextLoaderClassSpringRunnerTests.PropertiesBasedTestContextBootstrapper.class)
@ContextConfiguration("PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests-context.properties")
public class CustomDefaultContextLoaderClassSpringRunnerTests {
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackFalseRollbackAnnotationTransactionalTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackFalseRollbackAnnotationTransactionalTests.java
index 0c1f043a..1b027899 100644
--- a/spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackFalseRollbackAnnotationTransactionalTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackFalseRollbackAnnotationTransactionalTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,7 +45,7 @@ import static org.springframework.test.transaction.TransactionTestUtils.*;
* @see Transactional#transactionManager
* @see DefaultRollbackFalseTransactionalTests
*/
-@RunWith(SpringJUnit4ClassRunner.class)
+@RunWith(SpringRunner.class)
@ContextConfiguration(classes = EmbeddedPersonDatabaseTestsConfig.class, inheritLocations = false)
@Transactional("txMgr")
@Rollback(false)
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackTrueRollbackAnnotationTransactionalTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackTrueRollbackAnnotationTransactionalTests.java
index 984fc62f..49f70f6d 100644
--- a/spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackTrueRollbackAnnotationTransactionalTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackTrueRollbackAnnotationTransactionalTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,7 +45,7 @@ import static org.springframework.test.transaction.TransactionTestUtils.*;
* @see Transactional#transactionManager
* @see DefaultRollbackTrueTransactionalTests
*/
-@RunWith(SpringJUnit4ClassRunner.class)
+@RunWith(SpringRunner.class)
@ContextConfiguration(classes = EmbeddedPersonDatabaseTestsConfig.class, inheritLocations = false)
@Transactional("txMgr")
@Rollback(true)
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/EnabledAndIgnoredSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/EnabledAndIgnoredSpringRunnerTests.java
index a445fcaf..f4e84996 100644
--- a/spring-test/src/test/java/org/springframework/test/context/junit4/EnabledAndIgnoredSpringRunnerTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/EnabledAndIgnoredSpringRunnerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author 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 @@ import static org.junit.Assert.*;
* {@link IfProfileValue &#064;IfProfileValue} and
* {@link ProfileValueSourceConfiguration &#064;ProfileValueSourceConfiguration}
* (with the <em>implicit, default {@link ProfileValueSource}</em>) annotations in
- * conjunction with the {@link SpringJUnit4ClassRunner}.
+ * conjunction with the {@link SpringRunner}.
* <p>
* Note that {@link TestExecutionListeners &#064;TestExecutionListeners} is
* explicitly configured with an empty list, thus disabling all default
@@ -44,7 +44,7 @@ import static org.junit.Assert.*;
* @since 2.5
* @see HardCodedProfileValueSourceSpringRunnerTests
*/
-@RunWith(SpringJUnit4ClassRunner.class)
+@RunWith(SpringRunner.class)
@TestExecutionListeners( {})
public class EnabledAndIgnoredSpringRunnerTests {
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/ExpectedExceptionSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/ExpectedExceptionSpringRunnerTests.java
index 5fc3947d..4cfb4699 100644
--- a/spring-test/src/test/java/org/springframework/test/context/junit4/ExpectedExceptionSpringRunnerTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/ExpectedExceptionSpringRunnerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@ import static org.springframework.test.context.junit4.JUnitTestingUtils.*;
/**
* Verifies proper handling of JUnit's {@link Test#expected() &#064;Test(expected=...)}
- * support in conjunction with the {@link SpringJUnit4ClassRunner}.
+ * support in conjunction with the {@link SpringRunner}.
*
* @author Sam Brannen
* @since 3.0
@@ -39,7 +39,7 @@ public class ExpectedExceptionSpringRunnerTests {
@Test
public void expectedExceptions() throws Exception {
- runTestsAndAssertCounters(SpringJUnit4ClassRunner.class, ExpectedExceptionSpringRunnerTestCase.class, 1, 0, 1, 0, 0);
+ runTestsAndAssertCounters(SpringRunner.class, ExpectedExceptionSpringRunnerTestCase.class, 1, 0, 1, 0, 0);
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/FailingBeforeAndAfterMethodsJUnitTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/FailingBeforeAndAfterMethodsJUnitTests.java
index daebe2bc..4918606f 100644
--- a/spring-test/src/test/java/org/springframework/test/context/junit4/FailingBeforeAndAfterMethodsJUnitTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/FailingBeforeAndAfterMethodsJUnitTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -79,7 +79,7 @@ public class FailingBeforeAndAfterMethodsJUnitTests {
}
protected Class<? extends Runner> getRunnerClass() {
- return SpringJUnit4ClassRunner.class;
+ return SpringRunner.class;
}
@Test
@@ -133,7 +133,7 @@ public class FailingBeforeAndAfterMethodsJUnitTests {
}
}
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
@TestExecutionListeners({})
public static abstract class BaseTestCase {
@@ -168,7 +168,7 @@ public class FailingBeforeAndAfterMethodsJUnitTests {
}
@Ignore("TestCase classes are run manually by the enclosing test class")
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
@ContextConfiguration("FailingBeforeAndAfterMethodsTests-context.xml")
@Transactional
public static class FailingBeforeTransactionTestCase {
@@ -184,7 +184,7 @@ public class FailingBeforeAndAfterMethodsJUnitTests {
}
@Ignore("TestCase classes are run manually by the enclosing test class")
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
@ContextConfiguration("FailingBeforeAndAfterMethodsTests-context.xml")
@Transactional
public static class FailingAfterTransactionTestCase {
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/HardCodedProfileValueSourceSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/HardCodedProfileValueSourceSpringRunnerTests.java
index 9a26751f..b0e18843 100644
--- a/spring-test/src/test/java/org/springframework/test/context/junit4/HardCodedProfileValueSourceSpringRunnerTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/HardCodedProfileValueSourceSpringRunnerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@ import org.springframework.test.annotation.ProfileValueSourceConfiguration;
* &#064;IfProfileValue} and {@link ProfileValueSourceConfiguration
* &#064;ProfileValueSourceConfiguration} (with an
* <em>explicit, custom defined {@link ProfileValueSource}</em>) annotations in
- * conjunction with the {@link SpringJUnit4ClassRunner}.
+ * conjunction with the {@link SpringRunner}.
* </p>
*
* @author Sam Brannen
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/MethodLevelTransactionalSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/MethodLevelTransactionalSpringRunnerTests.java
index 42700595..f9b6b749 100644
--- a/spring-test/src/test/java/org/springframework/test/context/junit4/MethodLevelTransactionalSpringRunnerTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/MethodLevelTransactionalSpringRunnerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -40,7 +40,7 @@ import static org.springframework.test.transaction.TransactionTestUtils.*;
* {@link Transactional &#64;Transactional}, {@link TestExecutionListeners
* &#64;TestExecutionListeners}, and {@link ContextConfiguration
* &#64;ContextConfiguration} annotations in conjunction with the
- * {@link SpringJUnit4ClassRunner} and the following
+ * {@link SpringRunner} and the following
* {@link TestExecutionListener TestExecutionListeners}:
*
* <ul>
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/OptionalContextConfigurationSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/OptionalContextConfigurationSpringRunnerTests.java
new file mode 100644
index 00000000..5cb4ce83
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/OptionalContextConfigurationSpringRunnerTests.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * JUnit 4 based integration test which verifies that {@link @ContextConfiguration}
+ * is optional.
+ *
+ * @author Phillip Webb
+ * @author Sam Brannen
+ * @since 4.3
+ */
+@RunWith(SpringRunner.class)
+public class OptionalContextConfigurationSpringRunnerTests {
+
+ @Autowired
+ String foo;
+
+
+ @Test
+ public void contextConfigurationAnnotationIsOptional() {
+ assertEquals("foo", foo);
+ }
+
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ String foo() {
+ return "foo";
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests.java
index acbb6cc2..b52a9fa1 100644
--- a/spring-test/src/test/java/org/springframework/test/context/junit4/PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,7 +32,7 @@ import static org.junit.Assert.*;
/**
* <p>
* JUnit 4 based test class, which verifies the expected functionality of
- * {@link SpringJUnit4ClassRunner} in conjunction with support for application contexts
+ * {@link SpringRunner} in conjunction with support for application contexts
* loaded from Java {@link Properties} files. Specifically, the
* {@link ContextConfiguration#loader() loader} attribute of {@code ContextConfiguration}
* and the
@@ -54,7 +54,7 @@ import static org.junit.Assert.*;
* @see GenericPropertiesContextLoader
* @see SpringJUnit4ClassRunnerAppCtxTests
*/
-@RunWith(SpringJUnit4ClassRunner.class)
+@RunWith(SpringRunner.class)
@ContextConfiguration(loader = GenericPropertiesContextLoader.class)
public class PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests {
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/RepeatedSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/RepeatedSpringRunnerTests.java
index 94a884bf..d2bf46bf 100644
--- a/spring-test/src/test/java/org/springframework/test/context/junit4/RepeatedSpringRunnerTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/RepeatedSpringRunnerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -38,7 +38,7 @@ import static org.springframework.test.context.junit4.JUnitTestingUtils.*;
/**
* Verifies proper handling of the following in conjunction with the
- * {@link SpringJUnit4ClassRunner}:
+ * {@link SpringRunner}:
* <ul>
* <li>Spring's {@link Repeat @Repeat}</li>
* <li>Spring's {@link Timed @Timed}</li>
@@ -82,7 +82,7 @@ public class RepeatedSpringRunnerTests {
}
protected Class<? extends Runner> getRunnerClass() {
- return SpringJUnit4ClassRunner.class;
+ return SpringRunner.class;
}
@Test
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit47ClassRunnerRuleTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit47ClassRunnerRuleTests.java
index 6f0eddff..e59cdde9 100644
--- a/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit47ClassRunnerRuleTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit47ClassRunnerRuleTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,14 +27,14 @@ import static org.junit.Assert.*;
/**
* Verifies support for JUnit 4.7 {@link Rule Rules} in conjunction with the
- * {@link SpringJUnit4ClassRunner}. The body of this test class is taken from
- * the JUnit 4.7 release notes.
+ * {@link SpringRunner}. The body of this test class is taken from the
+ * JUnit 4.7 release notes.
*
* @author JUnit 4.7 Team
* @author Sam Brannen
* @since 3.0
*/
-@RunWith(SpringJUnit4ClassRunner.class)
+@RunWith(SpringRunner.class)
@TestExecutionListeners( {})
public class SpringJUnit47ClassRunnerRuleTests {
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerAppCtxTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerAppCtxTests.java
index 292aebfc..c60745d3 100644
--- a/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerAppCtxTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerAppCtxTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -43,7 +43,7 @@ import static org.junit.Assert.*;
/**
* SpringJUnit4ClassRunnerAppCtxTests serves as a <em>proof of concept</em>
* JUnit 4 based test class, which verifies the expected functionality of
- * {@link SpringJUnit4ClassRunner} in conjunction with the following:
+ * {@link SpringRunner} in conjunction with the following:
*
* <ul>
* <li>{@link ContextConfiguration @ContextConfiguration}</li>
@@ -73,7 +73,7 @@ import static org.junit.Assert.*;
* @see RelativePathSpringJUnit4ClassRunnerAppCtxTests
* @see InheritedConfigSpringJUnit4ClassRunnerAppCtxTests
*/
-@RunWith(SpringJUnit4ClassRunner.class)
+@RunWith(SpringRunner.class)
@ContextConfiguration
@TestExecutionListeners(DependencyInjectionTestExecutionListener.class)
public class SpringJUnit4ClassRunnerAppCtxTests implements ApplicationContextAware, BeanNameAware, InitializingBean {
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4TestSuite.java b/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4TestSuite.java
index 8f301117..020bda60 100644
--- a/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4TestSuite.java
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4TestSuite.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,12 +45,12 @@ import org.springframework.test.context.junit4.profile.xml.DevProfileXmlConfigTe
import org.springframework.test.context.transaction.programmatic.ProgrammaticTxMgmtTests;
/**
- * JUnit test suite for tests involving {@link SpringJUnit4ClassRunner} and the
+ * JUnit test suite for tests involving {@link SpringRunner} and the
* <em>Spring TestContext Framework</em>; only intended to be run manually as a
* convenience.
*
* <p>This test suite serves a dual purpose of verifying that tests run with
- * {@link SpringJUnit4ClassRunner} can be used in conjunction with JUnit's
+ * {@link SpringRunner} can be used in conjunction with JUnit's
* {@link Suite} runner.
*
* <p>Note that tests included in this suite will be executed at least twice if
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/StandardJUnit4FeaturesSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/StandardJUnit4FeaturesSpringRunnerTests.java
index 4dd270ca..f4cda4ea 100644
--- a/spring-test/src/test/java/org/springframework/test/context/junit4/StandardJUnit4FeaturesSpringRunnerTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/StandardJUnit4FeaturesSpringRunnerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2008 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,7 +22,7 @@ import org.springframework.test.context.TestExecutionListeners;
/**
* <p>
- * Simple unit test to verify that {@link SpringJUnit4ClassRunner} does not
+ * Simple unit test to verify that {@link SpringRunner} does not
* hinder correct functionality of standard JUnit 4.4+ testing features.
* </p>
* <p>
@@ -35,7 +35,7 @@ import org.springframework.test.context.TestExecutionListeners;
* @since 2.5
* @see StandardJUnit4FeaturesTests
*/
-@RunWith(SpringJUnit4ClassRunner.class)
+@RunWith(SpringRunner.class)
@TestExecutionListeners({})
public class StandardJUnit4FeaturesSpringRunnerTests extends StandardJUnit4FeaturesTests {
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/TimedSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/TimedSpringRunnerTests.java
index d59bc140..cede912c 100644
--- a/spring-test/src/test/java/org/springframework/test/context/junit4/TimedSpringRunnerTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/TimedSpringRunnerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,7 +32,7 @@ import static org.springframework.test.context.junit4.JUnitTestingUtils.*;
/**
* Verifies proper handling of the following in conjunction with the
- * {@link SpringJUnit4ClassRunner}:
+ * {@link SpringRunner}:
* <ul>
* <li>JUnit's {@link Test#timeout() @Test(timeout=...)}</li>
* <li>Spring's {@link Timed @Timed}</li>
@@ -49,7 +49,7 @@ public class TimedSpringRunnerTests {
}
protected Class<? extends Runner> getRunnerClass() {
- return SpringJUnit4ClassRunner.class;
+ return SpringRunner.class;
}
@Test
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/TimedTransactionalSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/TimedTransactionalSpringRunnerTests.java
index 3fcdb040..cffe292f 100644
--- a/spring-test/src/test/java/org/springframework/test/context/junit4/TimedTransactionalSpringRunnerTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/TimedTransactionalSpringRunnerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,7 +36,7 @@ import static org.springframework.test.transaction.TransactionTestUtils.*;
* @author Sam Brannen
* @since 2.5
*/
-@RunWith(SpringJUnit4ClassRunner.class)
+@RunWith(SpringRunner.class)
@ContextConfiguration("transactionalTests-context.xml")
@Transactional
public class TimedTransactionalSpringRunnerTests {
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/BarConfig.java b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/BarConfig.java
new file mode 100644
index 00000000..1ee6b6fd
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/BarConfig.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.aci.annotation;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author Sam Brannen
+ * @since 4.3
+ */
+@Configuration
+class BarConfig {
+
+ @Bean
+ String bar() {
+ return "bar";
+ }
+
+} \ No newline at end of file
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/FooConfig.java b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/FooConfig.java
new file mode 100644
index 00000000..a080fd84
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/FooConfig.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.aci.annotation;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author Sam Brannen
+ * @since 4.3
+ */
+@Configuration
+class FooConfig {
+
+ @Bean
+ String foo() {
+ return "foo";
+ }
+
+} \ No newline at end of file
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/InitializerConfiguredViaMetaAnnotationTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/InitializerConfiguredViaMetaAnnotationTests.java
new file mode 100644
index 00000000..971e3f4b
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/InitializerConfiguredViaMetaAnnotationTests.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.aci.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.List;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
+import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.core.annotation.AliasFor;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.context.junit4.aci.annotation.InitializerConfiguredViaMetaAnnotationTests.ComposedContextConfiguration;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Integration test that demonstrates how to register one or more {@code @Configuration}
+ * classes via an {@link ApplicationContextInitializer} in a composed annotation so
+ * that certain {@code @Configuration} classes are always registered whenever the composed
+ * annotation is used, even if the composed annotation is used to declare additional
+ * {@code @Configuration} classes.
+ *
+ * <p>This class has been implemented in response to the following Stack Overflow question:
+ * <a href="http://stackoverflow.com/questions/35733344/can-contextconfiguration-in-a-custom-annotation-be-merged">
+ * Can {@code @ContextConfiguration} in a custom annotation be merged?</a>
+ *
+ * @author Sam Brannen
+ * @since 4.3
+ */
+@RunWith(SpringRunner.class)
+@ComposedContextConfiguration(BarConfig.class)
+public class InitializerConfiguredViaMetaAnnotationTests {
+
+ @Autowired
+ String foo;
+
+ @Autowired
+ String bar;
+
+ @Autowired
+ List<String> strings;
+
+
+ @Test
+ public void beansFromInitializerAndComposedAnnotation() {
+ assertEquals(2, strings.size());
+ assertEquals("foo", foo);
+ assertEquals("bar", bar);
+ }
+
+
+ static class FooConfigInitializer implements ApplicationContextInitializer<GenericApplicationContext> {
+
+ @Override
+ public void initialize(GenericApplicationContext applicationContext) {
+ new AnnotatedBeanDefinitionReader(applicationContext).register(FooConfig.class);
+ }
+ }
+
+ @ContextConfiguration(loader = AnnotationConfigContextLoader.class, initializers = FooConfigInitializer.class)
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.TYPE)
+ @interface ComposedContextConfiguration {
+
+ @AliasFor(annotation = ContextConfiguration.class, attribute = "classes")
+ Class<?>[] value() default {};
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/support/BootstrapTestUtilsContextInitializerTests.java b/spring-test/src/test/java/org/springframework/test/context/support/BootstrapTestUtilsContextInitializerTests.java
index 4b70a8cb..f92c7418 100644
--- a/spring-test/src/test/java/org/springframework/test/context/support/BootstrapTestUtilsContextInitializerTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/support/BootstrapTestUtilsContextInitializerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
package org.springframework.test.context.support;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
@@ -35,63 +36,62 @@ import org.springframework.web.context.support.GenericWebApplicationContext;
* @author Sam Brannen
* @since 3.1
*/
+@SuppressWarnings("unchecked")
public class BootstrapTestUtilsContextInitializerTests extends AbstractContextConfigurationUtilsTests {
@Test
- public void buildMergedConfigWithLocalInitializer() {
- Class<?> testClass = InitializersFoo.class;
- Class<?>[] expectedClasses = new Class<?>[] { FooConfig.class };
- Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> expectedInitializerClasses//
- = new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
- expectedInitializerClasses.add(FooInitializer.class);
+ public void buildMergedConfigWithSingleLocalInitializer() {
+ Class<?> testClass = SingleInitializer.class;
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass);
+
+ assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY,
+ initializers(FooInitializer.class), DelegatingSmartContextLoader.class);
+ }
+ @Test
+ public void buildMergedConfigWithLocalInitializerAndConfigClass() {
+ Class<?> testClass = InitializersFoo.class;
MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass);
- assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, expectedInitializerClasses,
- DelegatingSmartContextLoader.class);
+ assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, classes(FooConfig.class),
+ initializers(FooInitializer.class), DelegatingSmartContextLoader.class);
}
@Test
public void buildMergedConfigWithLocalAndInheritedInitializer() {
Class<?> testClass = InitializersBar.class;
- Class<?>[] expectedClasses = new Class<?>[] { FooConfig.class, BarConfig.class };
- Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> expectedInitializerClasses//
- = new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
- expectedInitializerClasses.add(FooInitializer.class);
- expectedInitializerClasses.add(BarInitializer.class);
-
MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass);
- assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, expectedInitializerClasses,
- DelegatingSmartContextLoader.class);
+ assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, classes(FooConfig.class, BarConfig.class),
+ initializers(FooInitializer.class, BarInitializer.class), DelegatingSmartContextLoader.class);
}
@Test
public void buildMergedConfigWithOverriddenInitializers() {
Class<?> testClass = OverriddenInitializersBar.class;
- Class<?>[] expectedClasses = new Class<?>[] { FooConfig.class, BarConfig.class };
- Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> expectedInitializerClasses//
- = new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
- expectedInitializerClasses.add(BarInitializer.class);
-
MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass);
- assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, expectedInitializerClasses,
- DelegatingSmartContextLoader.class);
+ assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, classes(FooConfig.class, BarConfig.class),
+ initializers(BarInitializer.class), DelegatingSmartContextLoader.class);
}
@Test
public void buildMergedConfigWithOverriddenInitializersAndClasses() {
Class<?> testClass = OverriddenInitializersAndClassesBar.class;
- Class<?>[] expectedClasses = new Class<?>[] { BarConfig.class };
- Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> expectedInitializerClasses//
- = new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
- expectedInitializerClasses.add(BarInitializer.class);
-
MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass);
- assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, expectedInitializerClasses,
- DelegatingSmartContextLoader.class);
+ assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, classes(BarConfig.class),
+ initializers(BarInitializer.class), DelegatingSmartContextLoader.class);
+ }
+
+ private Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializers(
+ Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>... classes) {
+
+ return new HashSet<>(Arrays.asList(classes));
+ }
+
+ private Class<?>[] classes(Class<?>... classes) {
+ return classes;
}
@@ -109,6 +109,10 @@ public class BootstrapTestUtilsContextInitializerTests extends AbstractContextCo
}
}
+ @ContextConfiguration(initializers = FooInitializer.class)
+ private static class SingleInitializer {
+ }
+
@ContextConfiguration(classes = FooConfig.class, initializers = FooInitializer.class)
private static class InitializersFoo {
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/support/BootstrapTestUtilsMergedConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/support/BootstrapTestUtilsMergedConfigTests.java
index cef782e2..b499c257 100644
--- a/spring-test/src/test/java/org/springframework/test/context/support/BootstrapTestUtilsMergedConfigTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/support/BootstrapTestUtilsMergedConfigTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,15 +21,20 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.springframework.test.context.BootstrapTestUtils;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.ContextLoader;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.web.WebDelegatingSmartContextLoader;
import org.springframework.test.context.web.WebMergedContextConfiguration;
-import static org.junit.Assert.*;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
/**
* Unit tests for {@link BootstrapTestUtils} involving {@link MergedContextConfiguration}.
@@ -39,12 +44,28 @@ import static org.junit.Assert.*;
*/
public class BootstrapTestUtilsMergedConfigTests extends AbstractContextConfigurationUtilsTests {
+ @Rule
+ public final ExpectedException exception = ExpectedException.none();
+
+
@Test
- public void buildMergedConfigWithoutAnnotation() {
+ public void buildImplicitMergedConfigWithoutAnnotation() {
Class<?> testClass = Enigma.class;
MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass);
- assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, null);
+ assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, DelegatingSmartContextLoader.class);
+ }
+
+ /**
+ * @since 4.3
+ */
+ @Test
+ public void buildMergedConfigWithContextConfigurationWithoutLocationsClassesOrInitializers() {
+ exception.expect(IllegalStateException.class);
+ exception.expectMessage(startsWith("DelegatingSmartContextLoader was unable to detect defaults, "
+ + "and no ApplicationContextInitializers or ContextCustomizers were declared for context configuration attributes"));
+
+ buildMergedContextConfiguration(MissingContextAttributesTestCase.class);
}
@Test
@@ -200,4 +221,8 @@ public class BootstrapTestUtilsMergedConfigTests extends AbstractContextConfigur
public static class GermanShepherd extends WorkingDog {
}
+ @ContextConfiguration
+ static class MissingContextAttributesTestCase {
+ }
+
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsContextHierarchyTests.java b/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsContextHierarchyTests.java
index 3faab204..37025fdf 100644
--- a/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsContextHierarchyTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsContextHierarchyTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author 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,9 +59,13 @@ public class ContextLoaderUtilsContextHierarchyTests extends AbstractContextConf
resolveContextHierarchyAttributes(SingleTestClassWithContextConfigurationAndContextHierarchyOnSingleMetaAnnotation.class);
}
- @Test(expected = IllegalStateException.class)
+ @Test
public void resolveContextHierarchyAttributesForSingleTestClassWithImplicitSingleLevelContextHierarchy() {
- resolveContextHierarchyAttributes(BareAnnotations.class);
+ List<List<ContextConfigurationAttributes>> hierarchyAttributes = resolveContextHierarchyAttributes(BareAnnotations.class);
+ assertEquals(1, hierarchyAttributes.size());
+ List<ContextConfigurationAttributes> configAttributesList = hierarchyAttributes.get(0);
+ assertEquals(1, configAttributesList.size());
+ debugConfigAttributes(configAttributesList);
}
@Test
diff --git a/spring-test/src/test/java/org/springframework/test/context/support/DelegatingSmartContextLoaderTests.java b/spring-test/src/test/java/org/springframework/test/context/support/DelegatingSmartContextLoaderTests.java
index 0395ee36..8f416cfa 100644
--- a/spring-test/src/test/java/org/springframework/test/context/support/DelegatingSmartContextLoaderTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/support/DelegatingSmartContextLoaderTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author 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,17 +56,6 @@ public class DelegatingSmartContextLoaderTests {
// --- SmartContextLoader - processContextConfiguration() ------------------
@Test
- public void processContextConfigurationWithoutLocationsAndConfigurationClassesForBogusTestClass() {
- expectedException.expect(IllegalStateException.class);
- expectedException.expectMessage(startsWith("Neither"));
- expectedException.expectMessage(containsString("was able to detect defaults"));
-
- ContextConfigurationAttributes configAttributes = new ContextConfigurationAttributes(getClass(),
- EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, true, null, true, ContextLoader.class);
- loader.processContextConfiguration(configAttributes);
- }
-
- @Test
public void processContextConfigurationWithDefaultXmlConfigGeneration() {
ContextConfigurationAttributes configAttributes = new ContextConfigurationAttributes(XmlTestCase.class,
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, true, null, true, ContextLoader.class);
diff --git a/spring-test/src/test/java/org/springframework/test/context/support/TestPropertySourceUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/support/TestPropertySourceUtilsTests.java
index 8f6c8d7e..b1d7ddfd 100644
--- a/spring-test/src/test/java/org/springframework/test/context/support/TestPropertySourceUtilsTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/support/TestPropertySourceUtilsTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,13 +26,17 @@ import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.AnnotationConfigurationException;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
+import org.springframework.core.io.ByteArrayResource;
+import org.springframework.core.io.ResourceLoader;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.mock.env.MockPropertySource;
import org.springframework.test.context.TestPropertySource;
import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.CoreMatchers.startsWith;
import static org.junit.Assert.*;
-import static org.mockito.Mockito.mock;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.*;
import static org.springframework.test.context.support.TestPropertySourceUtils.*;
/**
@@ -44,20 +48,16 @@ import static org.springframework.test.context.support.TestPropertySourceUtils.*
public class TestPropertySourceUtilsTests {
private static final String[] EMPTY_STRING_ARRAY = new String[0];
- private static final String[] KEY_VALUE_PAIR = new String[] { "key = value" };
+
+ private static final String[] KEY_VALUE_PAIR = new String[] {"key = value"};
+
+ private static final String[] FOO_LOCATIONS = new String[] {"classpath:/foo.properties"};
+
@Rule
public ExpectedException expectedException = ExpectedException.none();
- private void assertMergedTestPropertySources(Class<?> testClass, String[] expectedLocations,
- String[] expectedProperties) {
- MergedTestPropertySources mergedPropertySources = buildMergedTestPropertySources(testClass);
- assertNotNull(mergedPropertySources);
- assertArrayEquals(expectedLocations, mergedPropertySources.getLocations());
- assertArrayEquals(expectedProperties, mergedPropertySources.getProperties());
- }
-
@Test
public void emptyAnnotation() {
expectedException.expect(IllegalStateException.class);
@@ -76,8 +76,8 @@ public class TestPropertySourceUtilsTests {
@Test
public void value() {
- assertMergedTestPropertySources(ValuePropertySources.class, new String[] { "classpath:/value.xml" },
- EMPTY_STRING_ARRAY);
+ assertMergedTestPropertySources(ValuePropertySources.class, asArray("classpath:/value.xml"),
+ EMPTY_STRING_ARRAY);
}
@Test
@@ -88,44 +88,88 @@ public class TestPropertySourceUtilsTests {
@Test
public void locationsAndProperties() {
- assertMergedTestPropertySources(LocationsAndPropertiesPropertySources.class, new String[] {
- "classpath:/foo1.xml", "classpath:/foo2.xml" }, new String[] { "k1a=v1a", "k1b: v1b" });
+ assertMergedTestPropertySources(LocationsAndPropertiesPropertySources.class,
+ asArray("classpath:/foo1.xml", "classpath:/foo2.xml"), asArray("k1a=v1a", "k1b: v1b"));
}
@Test
public void inheritedLocationsAndProperties() {
- assertMergedTestPropertySources(InheritedPropertySources.class, new String[] { "classpath:/foo1.xml",
- "classpath:/foo2.xml" }, new String[] { "k1a=v1a", "k1b: v1b" });
+ assertMergedTestPropertySources(InheritedPropertySources.class,
+ asArray("classpath:/foo1.xml", "classpath:/foo2.xml"), asArray("k1a=v1a", "k1b: v1b"));
}
@Test
public void extendedLocationsAndProperties() {
- assertMergedTestPropertySources(ExtendedPropertySources.class, new String[] { "classpath:/foo1.xml",
- "classpath:/foo2.xml", "classpath:/bar1.xml", "classpath:/bar2.xml" }, new String[] { "k1a=v1a",
- "k1b: v1b", "k2a v2a", "k2b: v2b" });
+ assertMergedTestPropertySources(ExtendedPropertySources.class,
+ asArray("classpath:/foo1.xml", "classpath:/foo2.xml", "classpath:/bar1.xml", "classpath:/bar2.xml"),
+ asArray("k1a=v1a", "k1b: v1b", "k2a v2a", "k2b: v2b"));
}
@Test
public void overriddenLocations() {
assertMergedTestPropertySources(OverriddenLocationsPropertySources.class,
- new String[] { "classpath:/baz.properties" }, new String[] { "k1a=v1a", "k1b: v1b", "key = value" });
+ asArray("classpath:/baz.properties"), asArray("k1a=v1a", "k1b: v1b", "key = value"));
}
@Test
public void overriddenProperties() {
- assertMergedTestPropertySources(OverriddenPropertiesPropertySources.class, new String[] {
- "classpath:/foo1.xml", "classpath:/foo2.xml", "classpath:/baz.properties" }, KEY_VALUE_PAIR);
+ assertMergedTestPropertySources(OverriddenPropertiesPropertySources.class,
+ asArray("classpath:/foo1.xml", "classpath:/foo2.xml", "classpath:/baz.properties"), KEY_VALUE_PAIR);
}
@Test
public void overriddenLocationsAndProperties() {
assertMergedTestPropertySources(OverriddenLocationsAndPropertiesPropertySources.class,
- new String[] { "classpath:/baz.properties" }, KEY_VALUE_PAIR);
+ asArray("classpath:/baz.properties"), KEY_VALUE_PAIR);
+ }
+
+
+ @Test
+ public void addPropertiesFilesToEnvironmentWithNullContext() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("must not be null");
+ addPropertiesFilesToEnvironment((ConfigurableApplicationContext) null, FOO_LOCATIONS);
+ }
+
+ @Test
+ public void addPropertiesFilesToEnvironmentWithContextAndNullLocations() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("must not be null");
+ addPropertiesFilesToEnvironment(mock(ConfigurableApplicationContext.class), (String[]) null);
+ }
+
+ @Test
+ public void addPropertiesFilesToEnvironmentWithNullEnvironment() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("must not be null");
+ addPropertiesFilesToEnvironment((ConfigurableEnvironment) null, mock(ResourceLoader.class), FOO_LOCATIONS);
+ }
+
+ @Test
+ public void addPropertiesFilesToEnvironmentWithEnvironmentAndNullLocations() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("must not be null");
+ addPropertiesFilesToEnvironment(new MockEnvironment(), mock(ResourceLoader.class), (String[]) null);
+ }
+
+ @Test
+ public void addPropertiesFilesToEnvironmentWithSinglePropertyFromVirtualFile() {
+ ConfigurableEnvironment environment = new MockEnvironment();
+
+ MutablePropertySources propertySources = environment.getPropertySources();
+ propertySources.remove(MockPropertySource.MOCK_PROPERTIES_PROPERTY_SOURCE_NAME);
+ assertEquals(0, propertySources.size());
+
+ String pair = "key = value";
+ ByteArrayResource resource = new ByteArrayResource(pair.getBytes(), "from inlined property: " + pair);
+ ResourceLoader resourceLoader = mock(ResourceLoader.class);
+ when(resourceLoader.getResource(anyString())).thenReturn(resource);
+
+ addPropertiesFilesToEnvironment(environment, resourceLoader, FOO_LOCATIONS);
+ assertEquals(1, propertySources.size());
+ assertEquals("value", environment.getProperty("key"));
}
- /**
- * @since 4.1.5
- */
@Test
public void addInlinedPropertiesToEnvironmentWithNullContext() {
expectedException.expect(IllegalArgumentException.class);
@@ -133,19 +177,13 @@ public class TestPropertySourceUtilsTests {
addInlinedPropertiesToEnvironment((ConfigurableApplicationContext) null, KEY_VALUE_PAIR);
}
- /**
- * @since 4.1.5
- */
@Test
public void addInlinedPropertiesToEnvironmentWithContextAndNullInlinedProperties() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("inlined");
- addInlinedPropertiesToEnvironment(mock(ConfigurableApplicationContext.class), null);
+ addInlinedPropertiesToEnvironment(mock(ConfigurableApplicationContext.class), (String[]) null);
}
- /**
- * @since 4.1.5
- */
@Test
public void addInlinedPropertiesToEnvironmentWithNullEnvironment() {
expectedException.expect(IllegalArgumentException.class);
@@ -153,39 +191,27 @@ public class TestPropertySourceUtilsTests {
addInlinedPropertiesToEnvironment((ConfigurableEnvironment) null, KEY_VALUE_PAIR);
}
- /**
- * @since 4.1.5
- */
@Test
public void addInlinedPropertiesToEnvironmentWithEnvironmentAndNullInlinedProperties() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("inlined");
- addInlinedPropertiesToEnvironment(new MockEnvironment(), null);
+ addInlinedPropertiesToEnvironment(new MockEnvironment(), (String[]) null);
}
- /**
- * @since 4.1.5
- */
@Test
public void addInlinedPropertiesToEnvironmentWithMalformedUnicodeInValue() {
expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("Failed to load test environment property");
- addInlinedPropertiesToEnvironment(new MockEnvironment(), new String[] { "key = \\uZZZZ" });
+ addInlinedPropertiesToEnvironment(new MockEnvironment(), asArray("key = \\uZZZZ"));
}
- /**
- * @since 4.1.5
- */
@Test
public void addInlinedPropertiesToEnvironmentWithMultipleKeyValuePairsInSingleInlinedProperty() {
expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("Failed to load exactly one test environment property");
- addInlinedPropertiesToEnvironment(new MockEnvironment(), new String[] { "a=b\nx=y" });
+ addInlinedPropertiesToEnvironment(new MockEnvironment(), asArray("a=b\nx=y"));
}
- /**
- * @since 4.1.5
- */
@Test
@SuppressWarnings("rawtypes")
public void addInlinedPropertiesToEnvironmentWithEmptyProperty() {
@@ -193,7 +219,7 @@ public class TestPropertySourceUtilsTests {
MutablePropertySources propertySources = environment.getPropertySources();
propertySources.remove(MockPropertySource.MOCK_PROPERTIES_PROPERTY_SOURCE_NAME);
assertEquals(0, propertySources.size());
- addInlinedPropertiesToEnvironment(environment, new String[] { " " });
+ addInlinedPropertiesToEnvironment(environment, asArray(" "));
assertEquals(1, propertySources.size());
assertEquals(0, ((Map) propertySources.iterator().next().getSource()).size());
}
@@ -202,10 +228,25 @@ public class TestPropertySourceUtilsTests {
public void convertInlinedPropertiesToMapWithNullInlinedProperties() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("inlined");
- convertInlinedPropertiesToMap(null);
+ convertInlinedPropertiesToMap((String[]) null);
+ }
+
+
+ private static void assertMergedTestPropertySources(Class<?> testClass, String[] expectedLocations,
+ String[] expectedProperties) {
+
+ MergedTestPropertySources mergedPropertySources = buildMergedTestPropertySources(testClass);
+ assertNotNull(mergedPropertySources);
+ assertArrayEquals(expectedLocations, mergedPropertySources.getLocations());
+ assertArrayEquals(expectedProperties, mergedPropertySources.getProperties());
+ }
+
+
+ @SafeVarargs
+ private static <T> T[] asArray(T... arr) {
+ return arr;
}
- // -------------------------------------------------------------------
@TestPropertySource
static class EmptyPropertySources {
diff --git a/spring-test/src/test/java/org/springframework/test/context/testng/AnnotationConfigTransactionalTestNGSpringContextTests.java b/spring-test/src/test/java/org/springframework/test/context/testng/AnnotationConfigTransactionalTestNGSpringContextTests.java
index a7c9f974..6c515027 100644
--- a/spring-test/src/test/java/org/springframework/test/context/testng/AnnotationConfigTransactionalTestNGSpringContextTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/testng/AnnotationConfigTransactionalTestNGSpringContextTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author 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,8 +54,8 @@ import static org.testng.Assert.*;
* @since 3.1
*/
@ContextConfiguration
-public class AnnotationConfigTransactionalTestNGSpringContextTests extends
- AbstractTransactionalTestNGSpringContextTests {
+public class AnnotationConfigTransactionalTestNGSpringContextTests
+ extends AbstractTransactionalTestNGSpringContextTests {
private static final String JANE = "jane";
private static final String SUE = "sue";
@@ -94,7 +94,7 @@ public class AnnotationConfigTransactionalTestNGSpringContextTests extends
}
@BeforeClass
- public void beforeClass() {
+ void beforeClass() {
numSetUpCalls = 0;
numSetUpCallsInTransaction = 0;
numTearDownCalls = 0;
@@ -102,7 +102,7 @@ public class AnnotationConfigTransactionalTestNGSpringContextTests extends
}
@AfterClass
- public void afterClass() {
+ void afterClass() {
assertEquals(numSetUpCalls, NUM_TESTS, "number of calls to setUp().");
assertEquals(numSetUpCallsInTransaction, NUM_TX_TESTS, "number of calls to setUp() within a transaction.");
assertEquals(numTearDownCalls, NUM_TESTS, "number of calls to tearDown().");
@@ -111,7 +111,7 @@ public class AnnotationConfigTransactionalTestNGSpringContextTests extends
@Test
@Transactional(propagation = Propagation.NOT_SUPPORTED)
- public void autowiringFromConfigClass() {
+ void autowiringFromConfigClass() {
assertNotNull(employee, "The employee should have been autowired.");
assertEquals(employee.getName(), "John Smith");
@@ -120,13 +120,13 @@ public class AnnotationConfigTransactionalTestNGSpringContextTests extends
}
@BeforeTransaction
- public void beforeTransaction() {
+ void beforeTransaction() {
assertNumRowsInPersonTable(1, "before a transactional test method");
assertAddPerson(YODA);
}
@BeforeMethod
- public void setUp() throws Exception {
+ void setUp() throws Exception {
numSetUpCalls++;
if (inTransaction()) {
numSetUpCallsInTransaction++;
@@ -135,7 +135,7 @@ public class AnnotationConfigTransactionalTestNGSpringContextTests extends
}
@Test
- public void modifyTestDataWithinTransaction() {
+ void modifyTestDataWithinTransaction() {
assertInTransaction(true);
assertAddPerson(JANE);
assertAddPerson(SUE);
@@ -143,7 +143,7 @@ public class AnnotationConfigTransactionalTestNGSpringContextTests extends
}
@AfterMethod
- public void tearDown() throws Exception {
+ void tearDown() throws Exception {
numTearDownCalls++;
if (inTransaction()) {
numTearDownCallsInTransaction++;
@@ -152,7 +152,7 @@ public class AnnotationConfigTransactionalTestNGSpringContextTests extends
}
@AfterTransaction
- public void afterTransaction() {
+ void afterTransaction() {
assertEquals(deletePerson(YODA), 1, "Deleting yoda");
assertNumRowsInPersonTable(1, "after a transactional test method");
}
@@ -162,7 +162,7 @@ public class AnnotationConfigTransactionalTestNGSpringContextTests extends
static class ContextConfiguration {
@Bean
- public Employee employee() {
+ Employee employee() {
Employee employee = new Employee();
employee.setName("John Smith");
employee.setAge(42);
@@ -171,17 +171,17 @@ public class AnnotationConfigTransactionalTestNGSpringContextTests extends
}
@Bean
- public Pet pet() {
+ Pet pet() {
return new Pet("Fido");
}
@Bean
- public PlatformTransactionManager transactionManager() {
+ PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
@Bean
- public DataSource dataSource() {
+ DataSource dataSource() {
return new EmbeddedDatabaseBuilder()//
.addScript("classpath:/org/springframework/test/jdbc/schema.sql")//
.addScript("classpath:/org/springframework/test/jdbc/data.sql")//
diff --git a/spring-test/src/test/java/org/springframework/test/context/testng/ConcreteTransactionalTestNGSpringContextTests.java b/spring-test/src/test/java/org/springframework/test/context/testng/ConcreteTransactionalTestNGSpringContextTests.java
index 2acc370e..d249fc38 100644
--- a/spring-test/src/test/java/org/springframework/test/context/testng/ConcreteTransactionalTestNGSpringContextTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/testng/ConcreteTransactionalTestNGSpringContextTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -117,7 +117,7 @@ public class ConcreteTransactionalTestNGSpringContextTests extends AbstractTrans
}
@BeforeClass
- public void beforeClass() {
+ void beforeClass() {
numSetUpCalls = 0;
numSetUpCallsInTransaction = 0;
numTearDownCalls = 0;
@@ -125,7 +125,7 @@ public class ConcreteTransactionalTestNGSpringContextTests extends AbstractTrans
}
@AfterClass
- public void afterClass() {
+ void afterClass() {
assertEquals(numSetUpCalls, NUM_TESTS, "number of calls to setUp().");
assertEquals(numSetUpCallsInTransaction, NUM_TX_TESTS, "number of calls to setUp() within a transaction.");
assertEquals(numTearDownCalls, NUM_TESTS, "number of calls to tearDown().");
@@ -134,7 +134,7 @@ public class ConcreteTransactionalTestNGSpringContextTests extends AbstractTrans
@Test
@Transactional(propagation = Propagation.NOT_SUPPORTED)
- public void verifyApplicationContextSet() {
+ void verifyApplicationContextSet() {
assertInTransaction(false);
assertNotNull(super.applicationContext,
"The application context should have been set due to ApplicationContextAware semantics.");
@@ -144,7 +144,7 @@ public class ConcreteTransactionalTestNGSpringContextTests extends AbstractTrans
@Test
@Transactional(propagation = Propagation.NOT_SUPPORTED)
- public void verifyBeanInitialized() {
+ void verifyBeanInitialized() {
assertInTransaction(false);
assertTrue(beanInitialized,
"This test instance should have been initialized due to InitializingBean semantics.");
@@ -152,7 +152,7 @@ public class ConcreteTransactionalTestNGSpringContextTests extends AbstractTrans
@Test
@Transactional(propagation = Propagation.NOT_SUPPORTED)
- public void verifyBeanNameSet() {
+ void verifyBeanNameSet() {
assertInTransaction(false);
assertEquals(beanName, getClass().getName(),
"The bean name of this test instance should have been set due to BeanNameAware semantics.");
@@ -160,7 +160,7 @@ public class ConcreteTransactionalTestNGSpringContextTests extends AbstractTrans
@Test
@Transactional(propagation = Propagation.NOT_SUPPORTED)
- public void verifyAnnotationAutowiredFields() {
+ void verifyAnnotationAutowiredFields() {
assertInTransaction(false);
assertNull(nonrequiredLong, "The nonrequiredLong field should NOT have been autowired.");
assertNotNull(pet, "The pet field should have been autowired.");
@@ -169,7 +169,7 @@ public class ConcreteTransactionalTestNGSpringContextTests extends AbstractTrans
@Test
@Transactional(propagation = Propagation.NOT_SUPPORTED)
- public void verifyAnnotationAutowiredMethods() {
+ void verifyAnnotationAutowiredMethods() {
assertInTransaction(false);
assertNotNull(employee, "The setEmployee() method should have been autowired.");
assertEquals(employee.getName(), "John Smith", "employee's name.");
@@ -177,26 +177,26 @@ public class ConcreteTransactionalTestNGSpringContextTests extends AbstractTrans
@Test
@Transactional(propagation = Propagation.NOT_SUPPORTED)
- public void verifyResourceAnnotationInjectedFields() {
+ void verifyResourceAnnotationInjectedFields() {
assertInTransaction(false);
assertEquals(foo, "Foo", "The foo field should have been injected via @Resource.");
}
@Test
@Transactional(propagation = Propagation.NOT_SUPPORTED)
- public void verifyResourceAnnotationInjectedMethods() {
+ void verifyResourceAnnotationInjectedMethods() {
assertInTransaction(false);
assertEquals(bar, "Bar", "The setBar() method should have been injected via @Resource.");
}
@BeforeTransaction
- public void beforeTransaction() {
+ void beforeTransaction() {
assertNumRowsInPersonTable(1, "before a transactional test method");
assertAddPerson(YODA);
}
@BeforeMethod
- public void setUp() throws Exception {
+ void setUp() throws Exception {
numSetUpCalls++;
if (inTransaction()) {
numSetUpCallsInTransaction++;
@@ -205,7 +205,7 @@ public class ConcreteTransactionalTestNGSpringContextTests extends AbstractTrans
}
@Test
- public void modifyTestDataWithinTransaction() {
+ void modifyTestDataWithinTransaction() {
assertInTransaction(true);
assertAddPerson(JANE);
assertAddPerson(SUE);
@@ -213,7 +213,7 @@ public class ConcreteTransactionalTestNGSpringContextTests extends AbstractTrans
}
@AfterMethod
- public void tearDown() throws Exception {
+ void tearDown() throws Exception {
numTearDownCalls++;
if (inTransaction()) {
numTearDownCallsInTransaction++;
@@ -222,7 +222,7 @@ public class ConcreteTransactionalTestNGSpringContextTests extends AbstractTrans
}
@AfterTransaction
- public void afterTransaction() {
+ void afterTransaction() {
assertEquals(deletePerson(YODA), 1, "Deleting yoda");
assertNumRowsInPersonTable(1, "after a transactional test method");
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/testng/web/ServletTestExecutionListenerTestNGIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/testng/web/ServletTestExecutionListenerTestNGIntegrationTests.java
index 25f33425..6de10205 100644
--- a/spring-test/src/test/java/org/springframework/test/context/testng/web/ServletTestExecutionListenerTestNGIntegrationTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/testng/web/ServletTestExecutionListenerTestNGIntegrationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -57,7 +57,7 @@ public class ServletTestExecutionListenerTestNGIntegrationTests extends Abstract
* @see #ensureMocksAreReinjectedBetweenTests_2
*/
@Test
- public void ensureMocksAreReinjectedBetweenTests_1() {
+ void ensureMocksAreReinjectedBetweenTests_1() {
assertInjectedServletRequestEqualsRequestInRequestContextHolder();
}
@@ -67,7 +67,7 @@ public class ServletTestExecutionListenerTestNGIntegrationTests extends Abstract
* @see #ensureMocksAreReinjectedBetweenTests_1
*/
@Test
- public void ensureMocksAreReinjectedBetweenTests_2() {
+ void ensureMocksAreReinjectedBetweenTests_2() {
assertInjectedServletRequestEqualsRequestInRequestContextHolder();
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/testng/web/TestNGSpringContextWebTests.java b/spring-test/src/test/java/org/springframework/test/context/testng/web/TestNGSpringContextWebTests.java
index 9092c3fe..c14c0f24 100644
--- a/spring-test/src/test/java/org/springframework/test/context/testng/web/TestNGSpringContextWebTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/testng/web/TestNGSpringContextWebTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -52,34 +52,34 @@ public class TestNGSpringContextWebTests extends AbstractTestNGSpringContextTest
static class Config {
@Bean
- public String foo() {
+ String foo() {
return "enigma";
}
}
- protected ServletContext servletContext;
+ ServletContext servletContext;
@Autowired
- protected WebApplicationContext wac;
+ WebApplicationContext wac;
@Autowired
- protected MockServletContext mockServletContext;
+ MockServletContext mockServletContext;
@Autowired
- protected MockHttpServletRequest request;
+ MockHttpServletRequest request;
@Autowired
- protected MockHttpServletResponse response;
+ MockHttpServletResponse response;
@Autowired
- protected MockHttpSession session;
+ MockHttpSession session;
@Autowired
- protected ServletWebRequest webRequest;
+ ServletWebRequest webRequest;
@Autowired
- protected String foo;
+ String foo;
@Override
@@ -88,7 +88,7 @@ public class TestNGSpringContextWebTests extends AbstractTestNGSpringContextTest
}
@Test
- public void basicWacFeatures() throws Exception {
+ void basicWacFeatures() throws Exception {
assertNotNull("ServletContext should be set in the WAC.", wac.getServletContext());
assertNotNull("ServletContext should have been set via ServletContextAware.", servletContext);
@@ -112,7 +112,7 @@ public class TestNGSpringContextWebTests extends AbstractTestNGSpringContextTest
}
@Test
- public void fooEnigmaAutowired() {
+ void fooEnigmaAutowired() {
assertEquals("enigma", foo);
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/transaction/PrimaryTransactionManagerTests.java b/spring-test/src/test/java/org/springframework/test/context/transaction/PrimaryTransactionManagerTests.java
new file mode 100644
index 00000000..d9cd1526
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/transaction/PrimaryTransactionManagerTests.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.transaction;
+
+import javax.sql.DataSource;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.DataSourceTransactionManager;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
+import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.jdbc.JdbcTestUtils;
+import org.springframework.test.transaction.TransactionTestUtils;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.Transactional;
+
+import static org.junit.Assert.*;
+
+/**
+ * Integration tests that ensure that <em>primary</em> transaction managers
+ * are supported.
+ *
+ * @author Sam Brannen
+ * @since 4.3
+ * @see org.springframework.test.context.jdbc.PrimaryDataSourceTests
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+@DirtiesContext
+public class PrimaryTransactionManagerTests {
+
+ @Configuration
+ static class Config {
+
+ @Primary
+ @Bean
+ public PlatformTransactionManager primaryTransactionManager() {
+ return new DataSourceTransactionManager(dataSource1());
+ }
+
+ @Bean
+ public PlatformTransactionManager additionalTransactionManager() {
+ return new DataSourceTransactionManager(dataSource2());
+ }
+
+ @Bean
+ public DataSource dataSource1() {
+ // @formatter:off
+ return new EmbeddedDatabaseBuilder()
+ .generateUniqueName(true)
+ .addScript("classpath:/org/springframework/test/context/jdbc/schema.sql")
+ .build();
+ // @formatter:on
+ }
+
+ @Bean
+ public DataSource dataSource2() {
+ return new EmbeddedDatabaseBuilder().generateUniqueName(true).build();
+ }
+
+ }
+
+
+ private JdbcTemplate jdbcTemplate;
+
+
+ @Autowired
+ public void setDataSource(DataSource dataSource1) {
+ this.jdbcTemplate = new JdbcTemplate(dataSource1);
+ }
+
+ @BeforeTransaction
+ public void beforeTransaction() {
+ assertNumUsers(0);
+ }
+
+ @Test
+ @Transactional
+ public void transactionalTest() {
+ TransactionTestUtils.assertInTransaction(true);
+
+ ClassPathResource resource = new ClassPathResource("/org/springframework/test/context/jdbc/data.sql");
+ new ResourceDatabasePopulator(resource).execute(jdbcTemplate.getDataSource());
+
+ assertNumUsers(1);
+ }
+
+ @AfterTransaction
+ public void afterTransaction() {
+ assertNumUsers(0);
+ }
+
+ private void assertNumUsers(int expected) {
+ assertEquals("Number of rows in the 'user' table.", expected,
+ JdbcTestUtils.countRowsInTable(this.jdbcTemplate, "user"));
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/transaction/TransactionalTestExecutionListenerTests.java b/spring-test/src/test/java/org/springframework/test/context/transaction/TransactionalTestExecutionListenerTests.java
index 8d4e015f..82cb0081 100644
--- a/spring-test/src/test/java/org/springframework/test/context/transaction/TransactionalTestExecutionListenerTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/transaction/TransactionalTestExecutionListenerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.SimpleTransactionStatus;
import static org.hamcrest.CoreMatchers.*;
-
import static org.junit.Assert.*;
import static org.mockito.BDDMockito.*;
import static org.springframework.transaction.annotation.Propagation.*;
@@ -55,6 +54,7 @@ public class TransactionalTestExecutionListenerTests {
private final TransactionalTestExecutionListener listener = new TransactionalTestExecutionListener() {
+ @Override
protected PlatformTransactionManager getTransactionManager(TestContext testContext, String qualifier) {
return tm;
}
@@ -82,10 +82,10 @@ public class TransactionalTestExecutionListenerTests {
given(testContext.getTestInstance()).willReturn(instance);
given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("transactionalTest"));
- assertFalse(instance.invoked);
+ assertFalse("callback should not have been invoked", instance.invoked());
TransactionContextHolder.removeCurrentTransactionContext();
listener.beforeTestMethod(testContext);
- assertEquals(invokedInTx, instance.invoked);
+ assertEquals(invokedInTx, instance.invoked());
}
private void assertBeforeTestMethodWithNonTransactionalTestMethod(Class<? extends Invocable> clazz)
@@ -95,10 +95,10 @@ public class TransactionalTestExecutionListenerTests {
given(testContext.getTestInstance()).willReturn(instance);
given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("nonTransactionalTest"));
- assertFalse(instance.invoked);
+ assertFalse("callback should not have been invoked", instance.invoked());
TransactionContextHolder.removeCurrentTransactionContext();
listener.beforeTestMethod(testContext);
- assertFalse(instance.invoked);
+ assertFalse("callback should not have been invoked", instance.invoked());
}
private void assertAfterTestMethod(Class<? extends Invocable> clazz) throws Exception {
@@ -114,11 +114,12 @@ public class TransactionalTestExecutionListenerTests {
given(tm.getTransaction(BDDMockito.any(TransactionDefinition.class))).willReturn(new SimpleTransactionStatus());
- assertFalse(instance.invoked);
+ assertFalse("callback should not have been invoked", instance.invoked());
TransactionContextHolder.removeCurrentTransactionContext();
listener.beforeTestMethod(testContext);
+ assertFalse("callback should not have been invoked", instance.invoked());
listener.afterTestMethod(testContext);
- assertTrue(instance.invoked);
+ assertTrue("callback should have been invoked", instance.invoked());
}
private void assertAfterTestMethodWithNonTransactionalTestMethod(Class<? extends Invocable> clazz) throws Exception {
@@ -127,11 +128,11 @@ public class TransactionalTestExecutionListenerTests {
given(testContext.getTestInstance()).willReturn(instance);
given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("nonTransactionalTest"));
- assertFalse(instance.invoked);
+ assertFalse("callback should not have been invoked", instance.invoked());
TransactionContextHolder.removeCurrentTransactionContext();
listener.beforeTestMethod(testContext);
listener.afterTestMethod(testContext);
- assertFalse(instance.invoked);
+ assertFalse("callback should not have been invoked", instance.invoked());
}
private void assertTransactionConfigurationAttributes(Class<?> clazz, String transactionManagerName,
@@ -174,7 +175,7 @@ public class TransactionalTestExecutionListenerTests {
given(testContext.getTestInstance()).willReturn(instance);
given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("transactionalTest"));
- assertFalse(instance.invoked);
+ assertFalse("callback should not have been invoked", instance.invoked());
TransactionContextHolder.removeCurrentTransactionContext();
try {
@@ -245,6 +246,16 @@ public class TransactionalTestExecutionListenerTests {
}
@Test
+ public void beforeTestMethodWithBeforeTransactionDeclaredAsInterfaceDefaultMethod() throws Exception {
+ assertBeforeTestMethod(BeforeTransactionDeclaredAsInterfaceDefaultMethodTestCase.class);
+ }
+
+ @Test
+ public void afterTestMethodWithAfterTransactionDeclaredAsInterfaceDefaultMethod() throws Exception {
+ assertAfterTestMethod(AfterTransactionDeclaredAsInterfaceDefaultMethodTestCase.class);
+ }
+
+ @Test
public void retrieveConfigurationAttributesWithMissingTransactionConfiguration() throws Exception {
assertTransactionConfigurationAttributes(MissingTransactionConfigurationTestCase.class, "", true);
}
@@ -344,6 +355,16 @@ public class TransactionalTestExecutionListenerTests {
}
@Test
+ public void isRollbackWithClassLevelRollbackWithExplicitValueOnTestInterface() throws Exception {
+ assertIsRollback(ClassLevelRollbackWithExplicitValueOnTestInterfaceTestCase.class, false);
+ }
+
+ @Test
+ public void isRollbackWithClassLevelRollbackViaMetaAnnotationOnTestInterface() throws Exception {
+ assertIsRollback(ClassLevelRollbackViaMetaAnnotationOnTestInterfaceTestCase.class, false);
+ }
+
+ @Test
public void isRollbackWithRollbackAndTransactionConfigurationDeclaredAtClassLevel() throws Exception {
Class<?> clazz = ClassLevelRollbackAndTransactionConfigurationTestCase.class;
BDDMockito.<Class<?>> given(testContext.getTestClass()).willReturn(clazz);
@@ -388,17 +409,35 @@ public class TransactionalTestExecutionListenerTests {
String transactionManager() default "metaTxMgr";
}
- private static abstract class Invocable {
+ private interface Invocable {
+
+ void invoked(boolean invoked);
+
+ boolean invoked();
+ }
+
+ private static class AbstractInvocable implements Invocable {
boolean invoked = false;
+
+
+ @Override
+ public void invoked(boolean invoked) {
+ this.invoked = invoked;
+ }
+
+ @Override
+ public boolean invoked() {
+ return this.invoked;
+ }
}
@Transactional
- static class TransactionalDeclaredOnClassLocallyTestCase extends Invocable {
+ static class TransactionalDeclaredOnClassLocallyTestCase extends AbstractInvocable {
@BeforeTransaction
public void beforeTransaction() {
- invoked = true;
+ invoked(true);
}
public void transactionalTest() {
@@ -406,11 +445,11 @@ public class TransactionalTestExecutionListenerTests {
}
}
- static class TransactionalDeclaredOnMethodLocallyTestCase extends Invocable {
+ static class TransactionalDeclaredOnMethodLocallyTestCase extends AbstractInvocable {
@BeforeTransaction
public void beforeTransaction() {
- invoked = true;
+ invoked(true);
}
@Transactional
@@ -424,11 +463,11 @@ public class TransactionalTestExecutionListenerTests {
}
@MetaTransactional
- static class TransactionalDeclaredOnClassViaMetaAnnotationTestCase extends Invocable {
+ static class TransactionalDeclaredOnClassViaMetaAnnotationTestCase extends AbstractInvocable {
@BeforeTransaction
public void beforeTransaction() {
- invoked = true;
+ invoked(true);
}
public void transactionalTest() {
@@ -436,11 +475,11 @@ public class TransactionalTestExecutionListenerTests {
}
}
- static class TransactionalDeclaredOnMethodViaMetaAnnotationTestCase extends Invocable {
+ static class TransactionalDeclaredOnMethodViaMetaAnnotationTestCase extends AbstractInvocable {
@BeforeTransaction
public void beforeTransaction() {
- invoked = true;
+ invoked(true);
}
@MetaTransactional
@@ -454,11 +493,11 @@ public class TransactionalTestExecutionListenerTests {
}
@MetaTxWithOverride(propagation = NOT_SUPPORTED)
- static class TransactionalDeclaredOnClassViaMetaAnnotationWithOverrideTestCase extends Invocable {
+ static class TransactionalDeclaredOnClassViaMetaAnnotationWithOverrideTestCase extends AbstractInvocable {
@BeforeTransaction
public void beforeTransaction() {
- invoked = true;
+ invoked(true);
}
public void transactionalTest() {
@@ -466,11 +505,11 @@ public class TransactionalTestExecutionListenerTests {
}
}
- static class TransactionalDeclaredOnMethodViaMetaAnnotationWithOverrideTestCase extends Invocable {
+ static class TransactionalDeclaredOnMethodViaMetaAnnotationWithOverrideTestCase extends AbstractInvocable {
@BeforeTransaction
public void beforeTransaction() {
- invoked = true;
+ invoked(true);
}
@MetaTxWithOverride(propagation = NOT_SUPPORTED)
@@ -483,11 +522,11 @@ public class TransactionalTestExecutionListenerTests {
}
}
- static class BeforeTransactionDeclaredLocallyTestCase extends Invocable {
+ static class BeforeTransactionDeclaredLocallyTestCase extends AbstractInvocable {
@BeforeTransaction
public void beforeTransaction() {
- invoked = true;
+ invoked(true);
}
@Transactional
@@ -500,11 +539,11 @@ public class TransactionalTestExecutionListenerTests {
}
}
- static class BeforeTransactionDeclaredViaMetaAnnotationTestCase extends Invocable {
+ static class BeforeTransactionDeclaredViaMetaAnnotationTestCase extends AbstractInvocable {
@MetaBeforeTransaction
public void beforeTransaction() {
- invoked = true;
+ invoked(true);
}
@Transactional
@@ -517,11 +556,11 @@ public class TransactionalTestExecutionListenerTests {
}
}
- static class AfterTransactionDeclaredLocallyTestCase extends Invocable {
+ static class AfterTransactionDeclaredLocallyTestCase extends AbstractInvocable {
@AfterTransaction
public void afterTransaction() {
- invoked = true;
+ invoked(true);
}
@Transactional
@@ -534,12 +573,54 @@ public class TransactionalTestExecutionListenerTests {
}
}
- static class AfterTransactionDeclaredViaMetaAnnotationTestCase extends Invocable {
+ static class AfterTransactionDeclaredViaMetaAnnotationTestCase extends AbstractInvocable {
@MetaAfterTransaction
public void afterTransaction() {
- invoked = true;
+ invoked(true);
+ }
+
+ @Transactional
+ public void transactionalTest() {
+ /* no-op */
+ }
+
+ public void nonTransactionalTest() {
+ /* no-op */
+ }
+ }
+
+ interface BeforeTransactionInterface extends Invocable {
+
+ @BeforeTransaction
+ default void beforeTransaction() {
+ invoked(true);
}
+ }
+
+ interface AfterTransactionInterface extends Invocable {
+
+ @AfterTransaction
+ default void afterTransaction() {
+ invoked(true);
+ }
+ }
+
+ static class BeforeTransactionDeclaredAsInterfaceDefaultMethodTestCase extends AbstractInvocable
+ implements BeforeTransactionInterface {
+
+ @Transactional
+ public void transactionalTest() {
+ /* no-op */
+ }
+
+ public void nonTransactionalTest() {
+ /* no-op */
+ }
+ }
+
+ static class AfterTransactionDeclaredAsInterfaceDefaultMethodTestCase extends AbstractInvocable
+ implements AfterTransactionInterface {
@Transactional
public void transactionalTest() {
@@ -642,4 +723,25 @@ public class TransactionalTestExecutionListenerTests {
}
}
+ @Rollback(false)
+ interface RollbackFalseTestInterface {
+ }
+
+ static class ClassLevelRollbackWithExplicitValueOnTestInterfaceTestCase implements RollbackFalseTestInterface {
+
+ public void test() {
+ }
+ }
+
+ @Commit
+ interface RollbackFalseViaMetaAnnotationTestInterface {
+ }
+
+ static class ClassLevelRollbackViaMetaAnnotationOnTestInterfaceTestCase
+ implements RollbackFalseViaMetaAnnotationTestInterface {
+
+ public void test() {
+ }
+ }
+
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/web/ServletTestExecutionListenerTests.java b/spring-test/src/test/java/org/springframework/test/context/web/ServletTestExecutionListenerTests.java
index 0353a870..55201245 100644
--- a/spring-test/src/test/java/org/springframework/test/context/web/ServletTestExecutionListenerTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/web/ServletTestExecutionListenerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -38,11 +38,12 @@ import static org.springframework.test.context.web.ServletTestExecutionListener.
* Unit tests for {@link ServletTestExecutionListener}.
*
* @author Sam Brannen
+ * @author Phillip Webb
* @since 3.2.6
*/
public class ServletTestExecutionListenerTests {
- private static final String SET_UP_OUTSIDE_OF_STEL = "SET_UP_OUTSIDE_OF_STEL";
+ private static final String SET_UP_OUTSIDE_OF_STEL = "setUpOutsideOfStel";
private final WebApplicationContext wac = mock(WebApplicationContext.class);
private final MockServletContext mockServletContext = new MockServletContext();
@@ -50,30 +51,6 @@ public class ServletTestExecutionListenerTests {
private final ServletTestExecutionListener listener = new ServletTestExecutionListener();
- private void assertAttributesAvailable() {
- assertNotNull("request attributes should be available", RequestContextHolder.getRequestAttributes());
- }
-
- private void assertAttributesNotAvailable() {
- assertNull("request attributes should not be available", RequestContextHolder.getRequestAttributes());
- }
-
- private void assertAttributeExists() {
- RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
- assertNotNull("request attributes should exist", requestAttributes);
- Object setUpOutsideOfStel = requestAttributes.getAttribute(SET_UP_OUTSIDE_OF_STEL,
- RequestAttributes.SCOPE_REQUEST);
- assertNotNull(SET_UP_OUTSIDE_OF_STEL + " should exist as a request attribute", setUpOutsideOfStel);
- }
-
- private void assertAttributeDoesNotExist() {
- RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
- assertNotNull("request attributes should exist", requestAttributes);
- Object setUpOutsideOfStel = requestAttributes.getAttribute(SET_UP_OUTSIDE_OF_STEL,
- RequestAttributes.SCOPE_REQUEST);
- assertNull(SET_UP_OUTSIDE_OF_STEL + " should NOT exist as a request attribute", setUpOutsideOfStel);
- }
-
@Before
public void setUp() {
given(wac.getServletContext()).willReturn(mockServletContext);
@@ -86,7 +63,7 @@ public class ServletTestExecutionListenerTests {
request.setAttribute(SET_UP_OUTSIDE_OF_STEL, "true");
RequestContextHolder.setRequestAttributes(servletWebRequest);
- assertAttributeExists();
+ assertSetUpOutsideOfStelAttributeExists();
}
@Test
@@ -95,16 +72,16 @@ public class ServletTestExecutionListenerTests {
given(testContext.getApplicationContext()).willReturn(mock(ApplicationContext.class));
listener.beforeTestClass(testContext);
- assertAttributeExists();
+ assertSetUpOutsideOfStelAttributeExists();
listener.prepareTestInstance(testContext);
- assertAttributeExists();
+ assertSetUpOutsideOfStelAttributeExists();
listener.beforeTestMethod(testContext);
- assertAttributeExists();
+ assertSetUpOutsideOfStelAttributeExists();
listener.afterTestMethod(testContext);
- assertAttributeExists();
+ assertSetUpOutsideOfStelAttributeExists();
}
@Test
@@ -112,22 +89,22 @@ public class ServletTestExecutionListenerTests {
BDDMockito.<Class<?>> given(testContext.getTestClass()).willReturn(LegacyWebTestCase.class);
RequestContextHolder.resetRequestAttributes();
- assertAttributesNotAvailable();
+ assertRequestAttributesDoNotExist();
listener.beforeTestClass(testContext);
listener.prepareTestInstance(testContext);
- assertAttributesNotAvailable();
+ assertRequestAttributesDoNotExist();
verify(testContext, times(0)).setAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE, Boolean.TRUE);
given(testContext.getAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE)).willReturn(null);
listener.beforeTestMethod(testContext);
- assertAttributesNotAvailable();
+ assertRequestAttributesDoNotExist();
verify(testContext, times(0)).setAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE, Boolean.TRUE);
listener.afterTestMethod(testContext);
verify(testContext, times(1)).removeAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE);
- assertAttributesNotAvailable();
+ assertRequestAttributesDoNotExist();
}
@Test
@@ -135,21 +112,21 @@ public class ServletTestExecutionListenerTests {
BDDMockito.<Class<?>> given(testContext.getTestClass()).willReturn(LegacyWebTestCase.class);
listener.beforeTestClass(testContext);
- assertAttributeExists();
+ assertSetUpOutsideOfStelAttributeExists();
listener.prepareTestInstance(testContext);
- assertAttributeExists();
+ assertSetUpOutsideOfStelAttributeExists();
verify(testContext, times(0)).setAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE, Boolean.TRUE);
given(testContext.getAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE)).willReturn(null);
listener.beforeTestMethod(testContext);
- assertAttributeExists();
+ assertSetUpOutsideOfStelAttributeExists();
verify(testContext, times(0)).setAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE, Boolean.TRUE);
given(testContext.getAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE)).willReturn(null);
listener.afterTestMethod(testContext);
verify(testContext, times(1)).removeAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE);
- assertAttributeExists();
+ assertSetUpOutsideOfStelAttributeExists();
}
@Test
@@ -158,7 +135,7 @@ public class ServletTestExecutionListenerTests {
RequestContextHolder.resetRequestAttributes();
listener.beforeTestClass(testContext);
- assertAttributesNotAvailable();
+ assertRequestAttributesDoNotExist();
assertWebAppConfigTestCase();
}
@@ -168,28 +145,70 @@ public class ServletTestExecutionListenerTests {
BDDMockito.<Class<?>> given(testContext.getTestClass()).willReturn(AtWebAppConfigWebTestCase.class);
listener.beforeTestClass(testContext);
- assertAttributesAvailable();
+ assertRequestAttributesExist();
assertWebAppConfigTestCase();
}
+ /**
+ * @since 4.3
+ */
+ @Test
+ public void activateListenerWithoutExistingRequestAttributes() throws Exception {
+ BDDMockito.<Class<?>> given(testContext.getTestClass()).willReturn(NoAtWebAppConfigWebTestCase.class);
+ given(testContext.getAttribute(ServletTestExecutionListener.ACTIVATE_LISTENER)).willReturn(true);
+
+ RequestContextHolder.resetRequestAttributes();
+ listener.beforeTestClass(testContext);
+ assertRequestAttributesDoNotExist();
+
+ assertWebAppConfigTestCase();
+ }
+
+
+ private RequestAttributes assertRequestAttributesExist() {
+ RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
+ assertNotNull("request attributes should exist", requestAttributes);
+ return requestAttributes;
+ }
+
+ private void assertRequestAttributesDoNotExist() {
+ assertNull("request attributes should not exist", RequestContextHolder.getRequestAttributes());
+ }
+
+ private void assertSetUpOutsideOfStelAttributeExists() {
+ RequestAttributes requestAttributes = assertRequestAttributesExist();
+ Object setUpOutsideOfStel = requestAttributes.getAttribute(SET_UP_OUTSIDE_OF_STEL,
+ RequestAttributes.SCOPE_REQUEST);
+ assertNotNull(SET_UP_OUTSIDE_OF_STEL + " should exist as a request attribute", setUpOutsideOfStel);
+ }
+
+ private void assertSetUpOutsideOfStelAttributeDoesNotExist() {
+ RequestAttributes requestAttributes = assertRequestAttributesExist();
+ Object setUpOutsideOfStel = requestAttributes.getAttribute(SET_UP_OUTSIDE_OF_STEL,
+ RequestAttributes.SCOPE_REQUEST);
+ assertNull(SET_UP_OUTSIDE_OF_STEL + " should NOT exist as a request attribute", setUpOutsideOfStel);
+ }
+
private void assertWebAppConfigTestCase() throws Exception {
listener.prepareTestInstance(testContext);
- assertAttributeDoesNotExist();
+ assertRequestAttributesExist();
+ assertSetUpOutsideOfStelAttributeDoesNotExist();
verify(testContext, times(1)).setAttribute(POPULATED_REQUEST_CONTEXT_HOLDER_ATTRIBUTE, Boolean.TRUE);
verify(testContext, times(1)).setAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE, Boolean.TRUE);
given(testContext.getAttribute(POPULATED_REQUEST_CONTEXT_HOLDER_ATTRIBUTE)).willReturn(Boolean.TRUE);
given(testContext.getAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE)).willReturn(Boolean.TRUE);
listener.beforeTestMethod(testContext);
- assertAttributeDoesNotExist();
+ assertRequestAttributesExist();
+ assertSetUpOutsideOfStelAttributeDoesNotExist();
verify(testContext, times(1)).setAttribute(POPULATED_REQUEST_CONTEXT_HOLDER_ATTRIBUTE, Boolean.TRUE);
verify(testContext, times(1)).setAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE, Boolean.TRUE);
listener.afterTestMethod(testContext);
verify(testContext).removeAttribute(POPULATED_REQUEST_CONTEXT_HOLDER_ATTRIBUTE);
verify(testContext).removeAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE);
- assertAttributesNotAvailable();
+ assertRequestAttributesDoNotExist();
}
@@ -200,4 +219,7 @@ public class ServletTestExecutionListenerTests {
static class AtWebAppConfigWebTestCase {
}
+ static class NoAtWebAppConfigWebTestCase {
+ }
+
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/web/WebAppConfigurationBootstrapWithTests.java b/spring-test/src/test/java/org/springframework/test/context/web/WebAppConfigurationBootstrapWithTests.java
new file mode 100644
index 00000000..ad03bb81
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/web/WebAppConfigurationBootstrapWithTests.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.web;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.Resource;
+import org.springframework.test.context.BootstrapWith;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.MergedContextConfiguration;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.context.web.WebAppConfigurationBootstrapWithTests.CustomWebTestContextBootstrapper;
+import org.springframework.web.context.WebApplicationContext;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * JUnit-based integration tests that verify support for loading a
+ * {@link WebApplicationContext} with a custom {@link WebTestContextBootstrapper}.
+ *
+ * @author Sam Brannen
+ * @author Phillip Webb
+ * @since 4.3
+ */
+@RunWith(SpringRunner.class)
+@ContextConfiguration
+@WebAppConfiguration
+@BootstrapWith(CustomWebTestContextBootstrapper.class)
+public class WebAppConfigurationBootstrapWithTests {
+
+ @Autowired
+ WebApplicationContext wac;
+
+
+ @Test
+ public void webApplicationContextIsLoaded() {
+ // from: src/test/webapp/resources/Spring.js
+ Resource resource = wac.getResource("/resources/Spring.js");
+ assertNotNull(resource);
+ assertTrue(resource.exists());
+ }
+
+
+ @Configuration
+ static class Config {
+ }
+
+ /**
+ * Custom {@link WebTestContextBootstrapper} that requires {@code @WebAppConfiguration}
+ * but hard codes the resource base path.
+ */
+ static class CustomWebTestContextBootstrapper extends WebTestContextBootstrapper {
+
+ @Override
+ protected MergedContextConfiguration processMergedContextConfiguration(MergedContextConfiguration mergedConfig) {
+ return new WebMergedContextConfiguration(mergedConfig, "src/test/webapp");
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/web/socket/WebSocketServletServerContainerFactoryBeanTests.java b/spring-test/src/test/java/org/springframework/test/context/web/socket/WebSocketServletServerContainerFactoryBeanTests.java
new file mode 100644
index 00000000..f35b87f9
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/web/socket/WebSocketServletServerContainerFactoryBeanTests.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.web.socket;
+
+import javax.websocket.server.ServerContainer;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.web.socket.config.annotation.EnableWebSocket;
+import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;
+
+import static org.junit.Assert.*;
+
+/**
+ * Integration tests that validate support for {@link ServletServerContainerFactoryBean}
+ * in conjunction with {@link WebAppConfiguration @WebAppConfiguration} and the
+ * Spring TestContext Framework.
+ *
+ * @author Sam Brannen
+ * @since 4.3.1
+ */
+@RunWith(SpringRunner.class)
+@WebAppConfiguration
+public class WebSocketServletServerContainerFactoryBeanTests {
+
+ @Autowired
+ ServerContainer serverContainer;
+
+
+ @Test
+ public void servletServerContainerFactoryBeanSupport() {
+ assertEquals(42, serverContainer.getDefaultMaxTextMessageBufferSize());
+ }
+
+
+ @Configuration
+ @EnableWebSocket
+ static class WebSocketConfig {
+
+ @Bean
+ ServletServerContainerFactoryBean createWebSocketContainer() {
+ ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
+ container.setMaxTextMessageBufferSize(42);
+ return container;
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/util/MetaAnnotationUtilsTests.java b/spring-test/src/test/java/org/springframework/test/util/MetaAnnotationUtilsTests.java
index 03db1d60..3416f215 100644
--- a/spring-test/src/test/java/org/springframework/test/util/MetaAnnotationUtilsTests.java
+++ b/spring-test/src/test/java/org/springframework/test/util/MetaAnnotationUtilsTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,10 +25,13 @@ import java.lang.annotation.Target;
import org.junit.Test;
+import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor;
+import org.springframework.test.util.MetaAnnotationUtils.UntypedAnnotationDescriptor;
import org.springframework.transaction.annotation.Transactional;
import static org.junit.Assert.*;
@@ -112,10 +115,27 @@ public class MetaAnnotationUtilsTests {
@Test
public void findAnnotationDescriptorWithInheritedAnnotationOnInterface() throws Exception {
// Note: @Transactional is inherited
- assertEquals(InheritedAnnotationInterface.class,
- findAnnotationDescriptor(InheritedAnnotationInterface.class, Transactional.class).getRootDeclaringClass());
- assertNull(findAnnotationDescriptor(SubInheritedAnnotationInterface.class, Transactional.class));
- assertNull(findAnnotationDescriptor(SubSubInheritedAnnotationInterface.class, Transactional.class));
+ Transactional rawAnnotation = InheritedAnnotationInterface.class.getAnnotation(Transactional.class);
+
+ AnnotationDescriptor<Transactional> descriptor;
+
+ descriptor = findAnnotationDescriptor(InheritedAnnotationInterface.class, Transactional.class);
+ assertNotNull(descriptor);
+ assertEquals(InheritedAnnotationInterface.class, descriptor.getRootDeclaringClass());
+ assertEquals(InheritedAnnotationInterface.class, descriptor.getDeclaringClass());
+ assertEquals(rawAnnotation, descriptor.getAnnotation());
+
+ descriptor = findAnnotationDescriptor(SubInheritedAnnotationInterface.class, Transactional.class);
+ assertNotNull(descriptor);
+ assertEquals(SubInheritedAnnotationInterface.class, descriptor.getRootDeclaringClass());
+ assertEquals(InheritedAnnotationInterface.class, descriptor.getDeclaringClass());
+ assertEquals(rawAnnotation, descriptor.getAnnotation());
+
+ descriptor = findAnnotationDescriptor(SubSubInheritedAnnotationInterface.class, Transactional.class);
+ assertNotNull(descriptor);
+ assertEquals(SubSubInheritedAnnotationInterface.class, descriptor.getRootDeclaringClass());
+ assertEquals(InheritedAnnotationInterface.class, descriptor.getDeclaringClass());
+ assertEquals(rawAnnotation, descriptor.getAnnotation());
}
@Test
@@ -130,9 +150,21 @@ public class MetaAnnotationUtilsTests {
@Test
public void findAnnotationDescriptorForNonInheritedAnnotationOnInterface() throws Exception {
// Note: @Order is not inherited.
- assertEquals(NonInheritedAnnotationInterface.class,
- findAnnotationDescriptor(NonInheritedAnnotationInterface.class, Order.class).getRootDeclaringClass());
- assertNull(findAnnotationDescriptor(SubNonInheritedAnnotationInterface.class, Order.class));
+ Order rawAnnotation = NonInheritedAnnotationInterface.class.getAnnotation(Order.class);
+
+ AnnotationDescriptor<Order> descriptor;
+
+ descriptor = findAnnotationDescriptor(NonInheritedAnnotationInterface.class, Order.class);
+ assertNotNull(descriptor);
+ assertEquals(NonInheritedAnnotationInterface.class, descriptor.getRootDeclaringClass());
+ assertEquals(NonInheritedAnnotationInterface.class, descriptor.getDeclaringClass());
+ assertEquals(rawAnnotation, descriptor.getAnnotation());
+
+ descriptor = findAnnotationDescriptor(SubNonInheritedAnnotationInterface.class, Order.class);
+ assertNotNull(descriptor);
+ assertEquals(SubNonInheritedAnnotationInterface.class, descriptor.getRootDeclaringClass());
+ assertEquals(NonInheritedAnnotationInterface.class, descriptor.getDeclaringClass());
+ assertEquals(rawAnnotation, descriptor.getAnnotation());
}
@Test
@@ -158,7 +190,17 @@ public class MetaAnnotationUtilsTests {
@Test
public void findAnnotationDescriptorForClassWithMetaAnnotatedInterface() {
- assertNull(findAnnotationDescriptor(ClassWithMetaAnnotatedInterface.class, Component.class));
+ Component rawAnnotation = AnnotationUtils.findAnnotation(ClassWithMetaAnnotatedInterface.class,
+ Component.class);
+
+ AnnotationDescriptor<Component> descriptor;
+
+ descriptor = findAnnotationDescriptor(ClassWithMetaAnnotatedInterface.class, Component.class);
+ assertNotNull(descriptor);
+ assertEquals(ClassWithMetaAnnotatedInterface.class, descriptor.getRootDeclaringClass());
+ assertEquals(Meta1.class, descriptor.getDeclaringClass());
+ assertEquals(rawAnnotation, descriptor.getAnnotation());
+ assertEquals(Meta1.class, descriptor.getComposedAnnotation().annotationType());
}
@Test
@@ -253,11 +295,27 @@ public class MetaAnnotationUtilsTests {
@SuppressWarnings("unchecked")
public void findAnnotationDescriptorForTypesWithInheritedAnnotationOnInterface() throws Exception {
// Note: @Transactional is inherited
- assertEquals(
- InheritedAnnotationInterface.class,
- findAnnotationDescriptorForTypes(InheritedAnnotationInterface.class, Transactional.class).getRootDeclaringClass());
- assertNull(findAnnotationDescriptorForTypes(SubInheritedAnnotationInterface.class, Transactional.class));
- assertNull(findAnnotationDescriptorForTypes(SubSubInheritedAnnotationInterface.class, Transactional.class));
+ Transactional rawAnnotation = InheritedAnnotationInterface.class.getAnnotation(Transactional.class);
+
+ UntypedAnnotationDescriptor descriptor;
+
+ descriptor = findAnnotationDescriptorForTypes(InheritedAnnotationInterface.class, Transactional.class);
+ assertNotNull(descriptor);
+ assertEquals(InheritedAnnotationInterface.class, descriptor.getRootDeclaringClass());
+ assertEquals(InheritedAnnotationInterface.class, descriptor.getDeclaringClass());
+ assertEquals(rawAnnotation, descriptor.getAnnotation());
+
+ descriptor = findAnnotationDescriptorForTypes(SubInheritedAnnotationInterface.class, Transactional.class);
+ assertNotNull(descriptor);
+ assertEquals(SubInheritedAnnotationInterface.class, descriptor.getRootDeclaringClass());
+ assertEquals(InheritedAnnotationInterface.class, descriptor.getDeclaringClass());
+ assertEquals(rawAnnotation, descriptor.getAnnotation());
+
+ descriptor = findAnnotationDescriptorForTypes(SubSubInheritedAnnotationInterface.class, Transactional.class);
+ assertNotNull(descriptor);
+ assertEquals(SubSubInheritedAnnotationInterface.class, descriptor.getRootDeclaringClass());
+ assertEquals(InheritedAnnotationInterface.class, descriptor.getDeclaringClass());
+ assertEquals(rawAnnotation, descriptor.getAnnotation());
}
@Test
@@ -274,10 +332,21 @@ public class MetaAnnotationUtilsTests {
@SuppressWarnings("unchecked")
public void findAnnotationDescriptorForTypesForNonInheritedAnnotationOnInterface() throws Exception {
// Note: @Order is not inherited.
- assertEquals(
- NonInheritedAnnotationInterface.class,
- findAnnotationDescriptorForTypes(NonInheritedAnnotationInterface.class, Order.class).getRootDeclaringClass());
- assertNull(findAnnotationDescriptorForTypes(SubNonInheritedAnnotationInterface.class, Order.class));
+ Order rawAnnotation = NonInheritedAnnotationInterface.class.getAnnotation(Order.class);
+
+ UntypedAnnotationDescriptor descriptor;
+
+ descriptor = findAnnotationDescriptorForTypes(NonInheritedAnnotationInterface.class, Order.class);
+ assertNotNull(descriptor);
+ assertEquals(NonInheritedAnnotationInterface.class, descriptor.getRootDeclaringClass());
+ assertEquals(NonInheritedAnnotationInterface.class, descriptor.getDeclaringClass());
+ assertEquals(rawAnnotation, descriptor.getAnnotation());
+
+ descriptor = findAnnotationDescriptorForTypes(SubNonInheritedAnnotationInterface.class, Order.class);
+ assertNotNull(descriptor);
+ assertEquals(SubNonInheritedAnnotationInterface.class, descriptor.getRootDeclaringClass());
+ assertEquals(NonInheritedAnnotationInterface.class, descriptor.getDeclaringClass());
+ assertEquals(rawAnnotation, descriptor.getAnnotation());
}
@Test
@@ -345,8 +414,18 @@ public class MetaAnnotationUtilsTests {
@Test
@SuppressWarnings("unchecked")
public void findAnnotationDescriptorForTypesForClassWithMetaAnnotatedInterface() {
- assertNull(findAnnotationDescriptorForTypes(ClassWithMetaAnnotatedInterface.class, Service.class,
- Component.class, Order.class, Transactional.class));
+ Component rawAnnotation = AnnotationUtils.findAnnotation(ClassWithMetaAnnotatedInterface.class,
+ Component.class);
+
+ UntypedAnnotationDescriptor descriptor;
+
+ descriptor = findAnnotationDescriptorForTypes(ClassWithMetaAnnotatedInterface.class, Service.class,
+ Component.class, Order.class, Transactional.class);
+ assertNotNull(descriptor);
+ assertEquals(ClassWithMetaAnnotatedInterface.class, descriptor.getRootDeclaringClass());
+ assertEquals(Meta1.class, descriptor.getDeclaringClass());
+ assertEquals(rawAnnotation, descriptor.getAnnotation());
+ assertEquals(Meta1.class, descriptor.getComposedAnnotation().annotationType());
}
@Test
diff --git a/spring-test/src/test/java/org/springframework/test/util/ReflectionTestUtilsTests.java b/spring-test/src/test/java/org/springframework/test/util/ReflectionTestUtilsTests.java
index fa692ed9..3ea39f9b 100644
--- a/spring-test/src/test/java/org/springframework/test/util/ReflectionTestUtilsTests.java
+++ b/spring-test/src/test/java/org/springframework/test/util/ReflectionTestUtilsTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,9 +22,12 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
+import org.springframework.aop.framework.ProxyFactory;
+import org.springframework.aop.support.AopUtils;
import org.springframework.test.util.subpackage.Component;
import org.springframework.test.util.subpackage.LegacyEntity;
import org.springframework.test.util.subpackage.Person;
+import org.springframework.test.util.subpackage.PersonEntity;
import org.springframework.test.util.subpackage.StaticFields;
import static org.hamcrest.CoreMatchers.*;
@@ -41,12 +44,14 @@ public class ReflectionTestUtilsTests {
private static final Float PI = new Float((float) 22 / 7);
- private final Person person = new Person();
+ private final Person person = new PersonEntity();
private final Component component = new Component();
+ private final LegacyEntity entity = new LegacyEntity();
+
@Rule
- public ExpectedException exception = ExpectedException.none();
+ public final ExpectedException exception = ExpectedException.none();
@Before
@@ -54,7 +59,6 @@ public class ReflectionTestUtilsTests {
StaticFields.reset();
}
-
@Test
public void setFieldWithNullTargetObject() throws Exception {
exception.expect(IllegalArgumentException.class);
@@ -106,6 +110,29 @@ public class ReflectionTestUtilsTests {
@Test
public void setFieldAndGetFieldForStandardUseCases() throws Exception {
+ assertSetFieldAndGetFieldBehavior(this.person);
+ }
+
+ @Test
+ public void setFieldAndGetFieldViaJdkDynamicProxy() throws Exception {
+ ProxyFactory pf = new ProxyFactory(this.person);
+ pf.addInterface(Person.class);
+ Person proxy = (Person) pf.getProxy();
+ assertTrue("Proxy is a JDK dynamic proxy", AopUtils.isJdkDynamicProxy(proxy));
+ assertSetFieldAndGetFieldBehaviorForProxy(proxy, this.person);
+ }
+
+ @Test
+ public void setFieldAndGetFieldViaCglibProxy() throws Exception {
+ ProxyFactory pf = new ProxyFactory(this.person);
+ pf.setProxyTargetClass(true);
+ Person proxy = (Person) pf.getProxy();
+ assertTrue("Proxy is a CGLIB proxy", AopUtils.isCglibProxy(proxy));
+ assertSetFieldAndGetFieldBehaviorForProxy(proxy, this.person);
+ }
+
+ private static void assertSetFieldAndGetFieldBehavior(Person person) {
+ // Set reflectively
setField(person, "id", new Long(99), long.class);
setField(person, "name", "Tom");
setField(person, "age", new Integer(42));
@@ -113,19 +140,33 @@ public class ReflectionTestUtilsTests {
setField(person, "likesPets", Boolean.TRUE);
setField(person, "favoriteNumber", PI, Number.class);
+ // Get reflectively
+ assertEquals(new Long(99), getField(person, "id"));
+ assertEquals("Tom", getField(person, "name"));
+ assertEquals(new Integer(42), getField(person, "age"));
+ assertEquals("blue", getField(person, "eyeColor"));
+ assertEquals(Boolean.TRUE, getField(person, "likesPets"));
+ assertEquals(PI, getField(person, "favoriteNumber"));
+
+ // Get directly
assertEquals("ID (private field in a superclass)", 99, person.getId());
assertEquals("name (protected field)", "Tom", person.getName());
assertEquals("age (private field)", 42, person.getAge());
assertEquals("eye color (package private field)", "blue", person.getEyeColor());
assertEquals("'likes pets' flag (package private boolean field)", true, person.likesPets());
assertEquals("'favorite number' (package field)", PI, person.getFavoriteNumber());
+ }
- assertEquals(new Long(99), getField(person, "id"));
- assertEquals("Tom", getField(person, "name"));
- assertEquals(new Integer(42), getField(person, "age"));
- assertEquals("blue", getField(person, "eyeColor"));
- assertEquals(Boolean.TRUE, getField(person, "likesPets"));
- assertEquals(PI, getField(person, "favoriteNumber"));
+ private static void assertSetFieldAndGetFieldBehaviorForProxy(Person proxy, Person target) {
+ assertSetFieldAndGetFieldBehavior(proxy);
+
+ // Get directly from Target
+ assertEquals("ID (private field in a superclass)", 99, target.getId());
+ assertEquals("name (protected field)", "Tom", target.getName());
+ assertEquals("age (private field)", 42, target.getAge());
+ assertEquals("eye color (package private field)", "blue", target.getEyeColor());
+ assertEquals("'likes pets' flag (package private boolean field)", true, target.likesPets());
+ assertEquals("'favorite number' (package field)", PI, target.getFavoriteNumber());
}
@Test
@@ -163,17 +204,6 @@ public class ReflectionTestUtilsTests {
setField(person, "likesPets", null, boolean.class);
}
- /**
- * Verifies behavior requested in <a href="https://jira.spring.io/browse/SPR-9571">SPR-9571</a>.
- */
- @Test
- public void setFieldOnLegacyEntityWithSideEffectsInToString() {
- String testCollaborator = "test collaborator";
- LegacyEntity entity = new LegacyEntity();
- setField(entity, "collaborator", testCollaborator, Object.class);
- assertTrue(entity.toString().contains(testCollaborator));
- }
-
@Test
public void setStaticFieldViaClass() throws Exception {
setField(StaticFields.class, "publicField", "xxx");
@@ -359,4 +389,37 @@ public class ReflectionTestUtilsTests {
invokeMethod(component, "configure", new Integer(42), "enigma", "baz", "quux");
}
+ @Test // SPR-14363
+ public void getFieldOnLegacyEntityWithSideEffectsInToString() {
+ Object collaborator = getField(entity, "collaborator");
+ assertNotNull(collaborator);
+ }
+
+ @Test // SPR-9571 and SPR-14363
+ public void setFieldOnLegacyEntityWithSideEffectsInToString() {
+ String testCollaborator = "test collaborator";
+ setField(entity, "collaborator", testCollaborator, Object.class);
+ assertTrue(entity.toString().contains(testCollaborator));
+ }
+
+ @Test // SPR-14363
+ public void invokeMethodOnLegacyEntityWithSideEffectsInToString() {
+ invokeMethod(entity, "configure", new Integer(42), "enigma");
+ assertEquals("number should have been configured", new Integer(42), entity.getNumber());
+ assertEquals("text should have been configured", "enigma", entity.getText());
+ }
+
+ @Test // SPR-14363
+ public void invokeGetterMethodOnLegacyEntityWithSideEffectsInToString() {
+ Object collaborator = invokeGetterMethod(entity, "collaborator");
+ assertNotNull(collaborator);
+ }
+
+ @Test // SPR-14363
+ public void invokeSetterMethodOnLegacyEntityWithSideEffectsInToString() {
+ String testCollaborator = "test collaborator";
+ invokeSetterMethod(entity, "collaborator", testCollaborator);
+ assertTrue(entity.toString().contains(testCollaborator));
+ }
+
}
diff --git a/spring-test/src/test/java/org/springframework/test/util/subpackage/LegacyEntity.java b/spring-test/src/test/java/org/springframework/test/util/subpackage/LegacyEntity.java
index 8c8ddfd4..72d4633f 100644
--- a/spring-test/src/test/java/org/springframework/test/util/subpackage/LegacyEntity.java
+++ b/spring-test/src/test/java/org/springframework/test/util/subpackage/LegacyEntity.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,17 +31,41 @@ public class LegacyEntity {
@Override
public String toString() {
- throw new RuntimeException(
+ throw new LegacyEntityException(
"Invoking toString() on the default collaborator causes an undesirable side effect");
- };
+ }
};
+ private Integer number;
+ private String text;
+
+
+ public void configure(Integer number, String text) {
+ this.number = number;
+ this.text = text;
+ }
+
+ public Integer getNumber() {
+ return this.number;
+ }
+
+ public String getText() {
+ return this.text;
+ }
+
+ public Object getCollaborator() {
+ return this.collaborator;
+ }
+
+ public void setCollaborator(Object collaborator) {
+ this.collaborator = collaborator;
+ }
@Override
public String toString() {
return new ToStringCreator(this)//
- .append("collaborator", this.collaborator)//
- .toString();
+ .append("collaborator", this.collaborator)//
+ .toString();
}
}
diff --git a/spring-test/src/test/java/org/springframework/test/util/subpackage/LegacyEntityException.java b/spring-test/src/test/java/org/springframework/test/util/subpackage/LegacyEntityException.java
new file mode 100644
index 00000000..2318e90a
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/util/subpackage/LegacyEntityException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.util.subpackage;
+
+/**
+ * Exception thrown by a {@link LegacyEntity}.
+ *
+ * @author Sam Brannen
+ * @since 4.3.1
+ */
+@SuppressWarnings("serial")
+public class LegacyEntityException extends RuntimeException {
+
+ public LegacyEntityException(String message) {
+ super(message);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/util/subpackage/PersistentEntity.java b/spring-test/src/test/java/org/springframework/test/util/subpackage/PersistentEntity.java
index 3ff3d91d..4f34e2e8 100644
--- a/spring-test/src/test/java/org/springframework/test/util/subpackage/PersistentEntity.java
+++ b/spring-test/src/test/java/org/springframework/test/util/subpackage/PersistentEntity.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2007-2011 the original author or authors.
+ * Copyright 2007-2016 the original author 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,11 +28,12 @@ public abstract class PersistentEntity {
private long id;
- public final long getId() {
+ public long getId() {
return this.id;
}
- protected final void setId(long id) {
+ protected void setId(long id) {
this.id = id;
}
+
}
diff --git a/spring-test/src/test/java/org/springframework/test/util/subpackage/Person.java b/spring-test/src/test/java/org/springframework/test/util/subpackage/Person.java
index b89f5616..20177ed0 100644
--- a/spring-test/src/test/java/org/springframework/test/util/subpackage/Person.java
+++ b/spring-test/src/test/java/org/springframework/test/util/subpackage/Person.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author 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,85 +16,27 @@
package org.springframework.test.util.subpackage;
-import org.springframework.core.style.ToStringCreator;
-
/**
- * Concrete subclass of {@link PersistentEntity} representing a <em>person</em>
- * entity; intended for use in unit tests.
+ * Interface representing a <em>person</em> entity; intended for use in unit tests.
+ *
+ * <p>The introduction of an interface is necessary in order to test support for
+ * JDK dynamic proxies.
*
* @author Sam Brannen
- * @since 2.5
+ * @since 4.3
*/
-public class Person extends PersistentEntity {
-
- protected String name;
-
- private int age;
-
- String eyeColor;
-
- boolean likesPets = false;
-
- private Number favoriteNumber;
-
-
- public final String getName() {
- return this.name;
- }
-
- @SuppressWarnings("unused")
- private final void setName(final String name) {
- this.name = name;
- }
-
- public final int getAge() {
- return this.age;
- }
-
- protected final void setAge(final int age) {
- this.age = age;
- }
-
- public final String getEyeColor() {
- return this.eyeColor;
- }
-
- final void setEyeColor(final String eyeColor) {
- this.eyeColor = eyeColor;
- }
-
- public final boolean likesPets() {
- return this.likesPets;
- }
-
- protected final void setLikesPets(final boolean likesPets) {
- this.likesPets = likesPets;
- }
-
- public final Number getFavoriteNumber() {
- return this.favoriteNumber;
- }
-
- protected final void setFavoriteNumber(Number favoriteNumber) {
- this.favoriteNumber = favoriteNumber;
- }
-
- @Override
- public String toString() {
- return new ToStringCreator(this)
+public interface Person {
- .append("id", this.getId())
+ long getId();
- .append("name", this.name)
+ String getName();
- .append("age", this.age)
+ int getAge();
- .append("eyeColor", this.eyeColor)
+ String getEyeColor();
- .append("likesPets", this.likesPets)
+ boolean likesPets();
- .append("favoriteNumber", this.favoriteNumber)
+ Number getFavoriteNumber();
- .toString();
- }
}
diff --git a/spring-test/src/test/java/org/springframework/test/util/subpackage/PersonEntity.java b/spring-test/src/test/java/org/springframework/test/util/subpackage/PersonEntity.java
new file mode 100644
index 00000000..1cdff47b
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/util/subpackage/PersonEntity.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.util.subpackage;
+
+import org.springframework.core.style.ToStringCreator;
+
+/**
+ * Concrete subclass of {@link PersistentEntity} representing a <em>person</em>
+ * entity; intended for use in unit tests.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+public class PersonEntity extends PersistentEntity implements Person {
+
+ protected String name;
+
+ private int age;
+
+ String eyeColor;
+
+ boolean likesPets = false;
+
+ private Number favoriteNumber;
+
+
+ public String getName() {
+ return this.name;
+ }
+
+ @SuppressWarnings("unused")
+ private void setName(final String name) {
+ this.name = name;
+ }
+
+ public int getAge() {
+ return this.age;
+ }
+
+ protected void setAge(final int age) {
+ this.age = age;
+ }
+
+ public String getEyeColor() {
+ return this.eyeColor;
+ }
+
+ void setEyeColor(final String eyeColor) {
+ this.eyeColor = eyeColor;
+ }
+
+ public boolean likesPets() {
+ return this.likesPets;
+ }
+
+ protected void setLikesPets(final boolean likesPets) {
+ this.likesPets = likesPets;
+ }
+
+ public Number getFavoriteNumber() {
+ return this.favoriteNumber;
+ }
+
+ protected void setFavoriteNumber(Number favoriteNumber) {
+ this.favoriteNumber = favoriteNumber;
+ }
+
+ @Override
+ public String toString() {
+ // @formatter:off
+ return new ToStringCreator(this)
+ .append("id", this.getId())
+ .append("name", this.name)
+ .append("age", this.age)
+ .append("eyeColor", this.eyeColor)
+ .append("likesPets", this.likesPets)
+ .append("favoriteNumber", this.favoriteNumber)
+ .toString();
+ // @formatter:on
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/web/client/DefaultRequestExpectationTests.java b/spring-test/src/test/java/org/springframework/test/web/client/DefaultRequestExpectationTests.java
new file mode 100644
index 00000000..aea4f2d8
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/web/client/DefaultRequestExpectationTests.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.test.web.client;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import org.springframework.http.HttpMethod;
+import org.springframework.http.client.ClientHttpRequest;
+import org.springframework.mock.http.client.MockAsyncClientHttpRequest;
+
+import static junit.framework.TestCase.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.springframework.http.HttpMethod.GET;
+import static org.springframework.http.HttpMethod.POST;
+import static org.springframework.test.web.client.ExpectedCount.once;
+import static org.springframework.test.web.client.ExpectedCount.times;
+import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
+import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
+import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
+
+/**
+ * Unit tests for {@link DefaultRequestExpectation}.
+ * @author Rossen Stoyanchev
+ */
+public class DefaultRequestExpectationTests {
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+
+ @Test
+ public void match() throws Exception {
+ RequestExpectation expectation = new DefaultRequestExpectation(once(), requestTo("/foo"));
+ expectation.match(createRequest(GET, "/foo"));
+ }
+
+ @Test
+ public void matchWithFailedExpection() throws Exception {
+ RequestExpectation expectation = new DefaultRequestExpectation(once(), requestTo("/foo"));
+ expectation.andExpect(method(POST));
+
+ this.thrown.expectMessage("Unexpected HttpMethod expected:<POST> but was:<GET>");
+ expectation.match(createRequest(GET, "/foo"));
+ }
+
+ @Test
+ public void hasRemainingCount() throws Exception {
+ RequestExpectation expectation = new DefaultRequestExpectation(times(2), requestTo("/foo"));
+ expectation.andRespond(withSuccess());
+
+ expectation.createResponse(createRequest(GET, "/foo"));
+ assertTrue(expectation.hasRemainingCount());
+
+ expectation.createResponse(createRequest(GET, "/foo"));
+ assertFalse(expectation.hasRemainingCount());
+ }
+
+ @Test
+ public void isSatisfied() throws Exception {
+ RequestExpectation expectation = new DefaultRequestExpectation(times(2), requestTo("/foo"));
+ expectation.andRespond(withSuccess());
+
+ expectation.createResponse(createRequest(GET, "/foo"));
+ assertFalse(expectation.isSatisfied());
+
+ expectation.createResponse(createRequest(GET, "/foo"));
+ assertTrue(expectation.isSatisfied());
+ }
+
+
+
+ private ClientHttpRequest createRequest(HttpMethod method, String url) {
+ try {
+ return new MockAsyncClientHttpRequest(method, new URI(url));
+ }
+ catch (URISyntaxException ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/web/client/MockClientHttpRequestFactoryTests.java b/spring-test/src/test/java/org/springframework/test/web/client/MockClientHttpRequestFactoryTests.java
deleted file mode 100644
index dfd3a714..00000000
--- a/spring-test/src/test/java/org/springframework/test/web/client/MockClientHttpRequestFactoryTests.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright 2002-2014 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.test.web.client;
-
-import java.net.URI;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import org.springframework.http.HttpMethod;
-import org.springframework.http.client.ClientHttpRequest;
-import org.springframework.http.client.ClientHttpRequestFactory;
-import org.springframework.web.client.RestTemplate;
-
-import static org.junit.Assert.*;
-import static org.springframework.test.web.client.match.MockRestRequestMatchers.*;
-
-/**
- * Tests for
- * {@link org.springframework.test.web.client.MockMvcClientHttpRequestFactory}.
- *
- * @author Rossen Stoyanchev
- */
-public class MockClientHttpRequestFactoryTests {
-
- private MockRestServiceServer server;
-
- private ClientHttpRequestFactory factory;
-
-
- @Before
- public void setup() {
- RestTemplate restTemplate = new RestTemplate();
- this.server = MockRestServiceServer.createServer(restTemplate);
- this.factory = restTemplate.getRequestFactory();
- }
-
- @Test
- public void createRequest() throws Exception {
- URI uri = new URI("/foo");
- ClientHttpRequest expected = (ClientHttpRequest) this.server.expect(anything());
- ClientHttpRequest actual = this.factory.createRequest(uri, HttpMethod.GET);
-
- assertSame(expected, actual);
- assertEquals(uri, actual.getURI());
- assertEquals(HttpMethod.GET, actual.getMethod());
- }
-
- @Test
- public void noFurtherRequestsExpected() throws Exception {
- try {
- this.factory.createRequest(new URI("/foo"), HttpMethod.GET);
- }
- catch (AssertionError error) {
- assertEquals("No further requests expected: HTTP GET /foo", error.getMessage());
- }
- }
-
- @Test
- public void verifyZeroExpected() throws Exception {
- this.server.verify();
- }
-
- @Test
- public void verifyExpectedEqualExecuted() throws Exception {
- this.server.expect(anything());
- this.server.expect(anything());
-
- this.factory.createRequest(new URI("/foo"), HttpMethod.GET);
- this.factory.createRequest(new URI("/bar"), HttpMethod.POST);
- }
-
- @Test
- public void verifyMoreExpected() throws Exception {
- this.server.expect(anything());
- this.server.expect(anything());
-
- this.factory.createRequest(new URI("/foo"), HttpMethod.GET);
-
- try {
- this.server.verify();
- }
- catch (AssertionError error) {
- assertTrue(error.getMessage(), error.getMessage().contains("1 out of 2 were executed"));
- }
- }
-
-}
diff --git a/spring-test/src/test/java/org/springframework/test/web/client/MockRestServiceServerTests.java b/spring-test/src/test/java/org/springframework/test/web/client/MockRestServiceServerTests.java
new file mode 100644
index 00000000..ddf8b26f
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/web/client/MockRestServiceServerTests.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.test.web.client;
+
+import org.junit.Test;
+
+import org.springframework.test.web.client.MockRestServiceServer.MockRestServiceServerBuilder;
+import org.springframework.web.client.RestTemplate;
+
+import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
+import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
+
+/**
+ * Unit tests for {@link MockRestServiceServer}.
+ * @author Rossen Stoyanchev
+ */
+public class MockRestServiceServerTests {
+
+ private RestTemplate restTemplate = new RestTemplate();
+
+
+ @Test
+ public void buildMultipleTimes() throws Exception {
+ MockRestServiceServerBuilder builder = MockRestServiceServer.bindTo(this.restTemplate);
+
+ MockRestServiceServer server = builder.build();
+ server.expect(requestTo("/foo")).andRespond(withSuccess());
+ this.restTemplate.getForObject("/foo", Void.class);
+ server.verify();
+
+ server = builder.ignoreExpectOrder(true).build();
+ server.expect(requestTo("/foo")).andRespond(withSuccess());
+ server.expect(requestTo("/bar")).andRespond(withSuccess());
+ this.restTemplate.getForObject("/bar", Void.class);
+ this.restTemplate.getForObject("/foo", Void.class);
+ server.verify();
+
+ server = builder.build();
+ server.expect(requestTo("/bar")).andRespond(withSuccess());
+ this.restTemplate.getForObject("/bar", Void.class);
+ server.verify();
+ }
+
+ @Test(expected = AssertionError.class)
+ public void exactExpectOrder() throws Exception {
+ MockRestServiceServer server = MockRestServiceServer.bindTo(this.restTemplate)
+ .ignoreExpectOrder(false).build();
+
+ server.expect(requestTo("/foo")).andRespond(withSuccess());
+ server.expect(requestTo("/bar")).andRespond(withSuccess());
+ this.restTemplate.getForObject("/bar", Void.class);
+ }
+
+ @Test
+ public void ignoreExpectOrder() throws Exception {
+ MockRestServiceServer server = MockRestServiceServer.bindTo(this.restTemplate)
+ .ignoreExpectOrder(true).build();
+
+ server.expect(requestTo("/foo")).andRespond(withSuccess());
+ server.expect(requestTo("/bar")).andRespond(withSuccess());
+ this.restTemplate.getForObject("/bar", Void.class);
+ this.restTemplate.getForObject("/foo", Void.class);
+ server.verify();
+ }
+
+ @Test
+ public void resetAndReuseServer() throws Exception {
+ MockRestServiceServer server = MockRestServiceServer.bindTo(this.restTemplate).build();
+
+ server.expect(requestTo("/foo")).andRespond(withSuccess());
+ this.restTemplate.getForObject("/foo", Void.class);
+ server.verify();
+ server.reset();
+
+ server.expect(requestTo("/bar")).andRespond(withSuccess());
+ this.restTemplate.getForObject("/bar", Void.class);
+ server.verify();
+ }
+
+ @Test
+ public void resetAndReuseServerWithUnorderedExpectationManager() throws Exception {
+ MockRestServiceServer server = MockRestServiceServer.bindTo(this.restTemplate)
+ .ignoreExpectOrder(true).build();
+
+ server.expect(requestTo("/foo")).andRespond(withSuccess());
+ this.restTemplate.getForObject("/foo", Void.class);
+ server.verify();
+ server.reset();
+
+ server.expect(requestTo("/foo")).andRespond(withSuccess());
+ server.expect(requestTo("/bar")).andRespond(withSuccess());
+ this.restTemplate.getForObject("/bar", Void.class);
+ this.restTemplate.getForObject("/foo", Void.class);
+ server.verify();
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/web/client/SimpleRequestExpectationManagerTests.java b/spring-test/src/test/java/org/springframework/test/web/client/SimpleRequestExpectationManagerTests.java
new file mode 100644
index 00000000..d6f2b9cb
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/web/client/SimpleRequestExpectationManagerTests.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.web.client;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import org.springframework.http.HttpMethod;
+import org.springframework.http.client.ClientHttpRequest;
+import org.springframework.mock.http.client.MockAsyncClientHttpRequest;
+
+import static org.junit.Assert.assertEquals;
+import static org.springframework.http.HttpMethod.GET;
+import static org.springframework.http.HttpMethod.POST;
+import static org.springframework.test.web.client.ExpectedCount.max;
+import static org.springframework.test.web.client.ExpectedCount.min;
+import static org.springframework.test.web.client.ExpectedCount.once;
+import static org.springframework.test.web.client.ExpectedCount.times;
+import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
+import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
+import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
+
+/**
+ * Unit tests for {@link SimpleRequestExpectationManager}.
+ * @author Rossen Stoyanchev
+ */
+public class SimpleRequestExpectationManagerTests {
+
+ private SimpleRequestExpectationManager manager = new SimpleRequestExpectationManager();
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+
+ @Test
+ public void unexpectedRequest() throws Exception {
+ try {
+ this.manager.validateRequest(createRequest(GET, "/foo"));
+ }
+ catch (AssertionError error) {
+ assertEquals("No further requests expected: HTTP GET /foo\n" +
+ "0 request(s) executed.\n", error.getMessage());
+ }
+ }
+
+ @Test
+ public void zeroExpectedRequests() throws Exception {
+ this.manager.verify();
+ }
+
+ @Test
+ public void sequentialRequests() throws Exception {
+ this.manager.expectRequest(once(), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess());
+ this.manager.expectRequest(once(), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess());
+
+ this.manager.validateRequest(createRequest(GET, "/foo"));
+ this.manager.validateRequest(createRequest(GET, "/bar"));
+ this.manager.verify();
+ }
+
+ @Test
+ public void sequentialRequestsTooMany() throws Exception {
+ this.manager.expectRequest(max(1), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess());
+ this.manager.expectRequest(max(1), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess());
+
+ this.thrown.expectMessage("No further requests expected: HTTP GET /baz\n" +
+ "2 request(s) executed:\n" +
+ "GET /foo\n" +
+ "GET /bar\n");
+
+ this.manager.validateRequest(createRequest(GET, "/foo"));
+ this.manager.validateRequest(createRequest(GET, "/bar"));
+ this.manager.validateRequest(createRequest(GET, "/baz"));
+ }
+
+ @Test
+ public void sequentialRequestsTooFew() throws Exception {
+ this.manager.expectRequest(min(1), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess());
+ this.manager.expectRequest(min(1), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess());
+
+ this.thrown.expectMessage("Further request(s) expected leaving 1 unsatisfied expectation(s).\n" +
+ "1 request(s) executed:\nGET /foo\n");
+
+ this.manager.validateRequest(createRequest(GET, "/foo"));
+ this.manager.verify();
+ }
+
+ @Test
+ public void repeatedRequests() throws Exception {
+ this.manager.expectRequest(times(2), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess());
+ this.manager.expectRequest(times(2), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess());
+
+ this.manager.validateRequest(createRequest(GET, "/foo"));
+ this.manager.validateRequest(createRequest(GET, "/bar"));
+ this.manager.validateRequest(createRequest(GET, "/foo"));
+ this.manager.validateRequest(createRequest(GET, "/bar"));
+ this.manager.verify();
+ }
+
+ @Test
+ public void repeatedRequestsTooMany() throws Exception {
+ this.manager.expectRequest(max(2), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess());
+ this.manager.expectRequest(max(2), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess());
+
+ this.thrown.expectMessage("No further requests expected: HTTP GET /foo\n" +
+ "4 request(s) executed:\n" +
+ "GET /foo\n" +
+ "GET /bar\n" +
+ "GET /foo\n" +
+ "GET /bar\n");
+
+ this.manager.validateRequest(createRequest(GET, "/foo"));
+ this.manager.validateRequest(createRequest(GET, "/bar"));
+ this.manager.validateRequest(createRequest(GET, "/foo"));
+ this.manager.validateRequest(createRequest(GET, "/bar"));
+ this.manager.validateRequest(createRequest(GET, "/foo"));
+ }
+
+ @Test
+ public void repeatedRequestsTooFew() throws Exception {
+ this.manager.expectRequest(min(2), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess());
+ this.manager.expectRequest(min(2), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess());
+
+ this.thrown.expectMessage("3 request(s) executed:\n" +
+ "GET /foo\n" +
+ "GET /bar\n" +
+ "GET /foo\n");
+
+ this.manager.validateRequest(createRequest(GET, "/foo"));
+ this.manager.validateRequest(createRequest(GET, "/bar"));
+ this.manager.validateRequest(createRequest(GET, "/foo"));
+ this.manager.verify();
+ }
+
+ @Test
+ public void repeatedRequestsNotInOrder() throws Exception {
+ this.manager.expectRequest(times(2), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess());
+ this.manager.expectRequest(times(2), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess());
+ this.manager.expectRequest(times(2), requestTo("/baz")).andExpect(method(GET)).andRespond(withSuccess());
+
+ this.thrown.expectMessage("Unexpected HttpMethod expected:<GET> but was:<POST>");
+ this.manager.validateRequest(createRequest(POST, "/foo"));
+ }
+
+
+ private ClientHttpRequest createRequest(HttpMethod method, String url) {
+ try {
+ return new MockAsyncClientHttpRequest(method, new URI(url));
+ }
+ catch (URISyntaxException ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/web/client/UnorderedRequestExpectationManagerTests.java b/spring-test/src/test/java/org/springframework/test/web/client/UnorderedRequestExpectationManagerTests.java
new file mode 100644
index 00000000..163f3865
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/web/client/UnorderedRequestExpectationManagerTests.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.test.web.client;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import org.springframework.http.HttpMethod;
+import org.springframework.http.client.ClientHttpRequest;
+import org.springframework.mock.http.client.MockAsyncClientHttpRequest;
+
+import static org.junit.Assert.assertEquals;
+import static org.springframework.http.HttpMethod.GET;
+import static org.springframework.test.web.client.ExpectedCount.max;
+import static org.springframework.test.web.client.ExpectedCount.min;
+import static org.springframework.test.web.client.ExpectedCount.once;
+import static org.springframework.test.web.client.ExpectedCount.times;
+import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
+import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
+import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
+
+/**
+ * Unit tests for {@link UnorderedRequestExpectationManager}.
+ * @author Rossen Stoyanchev
+ */
+public class UnorderedRequestExpectationManagerTests {
+
+ private UnorderedRequestExpectationManager manager = new UnorderedRequestExpectationManager();
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+
+ @Test
+ public void unexpectedRequest() throws Exception {
+ try {
+ this.manager.validateRequest(createRequest(GET, "/foo"));
+ }
+ catch (AssertionError error) {
+ assertEquals("No further requests expected: HTTP GET /foo\n" +
+ "0 request(s) executed.\n", error.getMessage());
+ }
+ }
+
+ @Test
+ public void zeroExpectedRequests() throws Exception {
+ this.manager.verify();
+ }
+
+ @Test
+ public void multipleRequests() throws Exception {
+ this.manager.expectRequest(once(), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess());
+ this.manager.expectRequest(once(), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess());
+
+ this.manager.validateRequest(createRequest(GET, "/bar"));
+ this.manager.validateRequest(createRequest(GET, "/foo"));
+ this.manager.verify();
+ }
+
+ @Test
+ public void repeatedRequests() throws Exception {
+ this.manager.expectRequest(times(2), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess());
+ this.manager.expectRequest(times(2), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess());
+
+ this.manager.validateRequest(createRequest(GET, "/bar"));
+ this.manager.validateRequest(createRequest(GET, "/foo"));
+ this.manager.validateRequest(createRequest(GET, "/foo"));
+ this.manager.validateRequest(createRequest(GET, "/bar"));
+ this.manager.verify();
+ }
+
+ @Test
+ public void repeatedRequestsTooMany() throws Exception {
+ this.manager.expectRequest(max(2), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess());
+ this.manager.expectRequest(max(2), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess());
+
+ this.thrown.expectMessage("No further requests expected: HTTP GET /foo\n" +
+ "4 request(s) executed:\n" +
+ "GET /bar\n" +
+ "GET /foo\n" +
+ "GET /bar\n" +
+ "GET /foo\n");
+
+ this.manager.validateRequest(createRequest(GET, "/bar"));
+ this.manager.validateRequest(createRequest(GET, "/foo"));
+ this.manager.validateRequest(createRequest(GET, "/bar"));
+ this.manager.validateRequest(createRequest(GET, "/foo"));
+ this.manager.validateRequest(createRequest(GET, "/foo"));
+ }
+
+ @Test
+ public void repeatedRequestsTooFew() throws Exception {
+ this.manager.expectRequest(min(2), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess());
+ this.manager.expectRequest(min(2), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess());
+
+ this.thrown.expectMessage("3 request(s) executed:\n" +
+ "GET /bar\n" +
+ "GET /foo\n" +
+ "GET /foo\n");
+
+ this.manager.validateRequest(createRequest(GET, "/bar"));
+ this.manager.validateRequest(createRequest(GET, "/foo"));
+ this.manager.validateRequest(createRequest(GET, "/foo"));
+ this.manager.verify();
+ }
+
+
+ private ClientHttpRequest createRequest(HttpMethod method, String url) {
+ try {
+ return new MockAsyncClientHttpRequest(method, new URI(url));
+ }
+ catch (URISyntaxException ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
+}
diff --git a/spring-test/src/test/java/org/springframework/test/web/client/match/ContentRequestMatchersTests.java b/spring-test/src/test/java/org/springframework/test/web/client/match/ContentRequestMatchersTests.java
index eada001d..de034c24 100644
--- a/spring-test/src/test/java/org/springframework/test/web/client/match/ContentRequestMatchersTests.java
+++ b/spring-test/src/test/java/org/springframework/test/web/client/match/ContentRequestMatchersTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,11 +15,15 @@
*/
package org.springframework.test.web.client.match;
+import java.nio.charset.Charset;
+
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.MediaType;
import org.springframework.mock.http.client.MockClientHttpRequest;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
import static org.hamcrest.Matchers.*;
@@ -32,11 +36,13 @@ public class ContentRequestMatchersTests {
private MockClientHttpRequest request;
+
@Before
public void setUp() {
this.request = new MockClientHttpRequest();
}
+
@Test
public void testContentType() throws Exception {
this.request.getHeaders().setContentType(MediaType.APPLICATION_JSON);
@@ -45,14 +51,14 @@ public class ContentRequestMatchersTests {
MockRestRequestMatchers.content().contentType(MediaType.APPLICATION_JSON).match(this.request);
}
- @Test(expected=AssertionError.class)
+ @Test(expected = AssertionError.class)
public void testContentTypeNoMatch1() throws Exception {
this.request.getHeaders().setContentType(MediaType.APPLICATION_JSON);
MockRestRequestMatchers.content().contentType("application/xml").match(this.request);
}
- @Test(expected=AssertionError.class)
+ @Test(expected = AssertionError.class)
public void testContentTypeNoMatch2() throws Exception {
this.request.getHeaders().setContentType(MediaType.APPLICATION_JSON);
@@ -66,7 +72,7 @@ public class ContentRequestMatchersTests {
MockRestRequestMatchers.content().string("test").match(this.request);
}
- @Test(expected=AssertionError.class)
+ @Test(expected = AssertionError.class)
public void testStringNoMatch() throws Exception {
this.request.getBody().write("test".getBytes());
@@ -81,7 +87,7 @@ public class ContentRequestMatchersTests {
MockRestRequestMatchers.content().bytes(content).match(this.request);
}
- @Test(expected=AssertionError.class)
+ @Test(expected = AssertionError.class)
public void testBytesNoMatch() throws Exception {
this.request.getBody().write("test".getBytes());
@@ -89,6 +95,22 @@ public class ContentRequestMatchersTests {
}
@Test
+ public void testFormData() throws Exception {
+ String contentType = "application/x-www-form-urlencoded;charset=UTF-8";
+ String body = "name+1=value+1&name+2=value+A&name+2=value+B&name+3";
+
+ this.request.getHeaders().setContentType(MediaType.parseMediaType(contentType));
+ this.request.getBody().write(body.getBytes(Charset.forName("UTF-8")));
+
+ MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
+ map.add("name 1", "value 1");
+ map.add("name 2", "value A");
+ map.add("name 2", "value B");
+ map.add("name 3", null);
+ MockRestRequestMatchers.content().formData(map).match(this.request);
+ }
+
+ @Test
public void testXml() throws Exception {
String content = "<foo><bar>baz</bar><bar>bazz</bar></foo>";
this.request.getBody().write(content.getBytes());
@@ -96,7 +118,7 @@ public class ContentRequestMatchersTests {
MockRestRequestMatchers.content().xml(content).match(this.request);
}
- @Test(expected=AssertionError.class)
+ @Test(expected = AssertionError.class)
public void testXmlNoMatch() throws Exception {
this.request.getBody().write("<foo>11</foo>".getBytes());
@@ -111,7 +133,7 @@ public class ContentRequestMatchersTests {
MockRestRequestMatchers.content().node(hasXPath("/foo/bar")).match(this.request);
}
- @Test(expected=AssertionError.class)
+ @Test(expected = AssertionError.class)
public void testNodeMatcherNoMatch() throws Exception {
String content = "<foo><bar>baz</bar></foo>";
this.request.getBody().write(content.getBytes());
diff --git a/spring-test/src/test/java/org/springframework/test/web/client/match/MockRestRequestMatchersTests.java b/spring-test/src/test/java/org/springframework/test/web/client/match/MockRestRequestMatchersTests.java
index 787020d6..58cad79c 100644
--- a/spring-test/src/test/java/org/springframework/test/web/client/match/MockRestRequestMatchersTests.java
+++ b/spring-test/src/test/java/org/springframework/test/web/client/match/MockRestRequestMatchersTests.java
@@ -91,7 +91,6 @@ public class MockRestRequestMatchersTests {
MockRestRequestMatchers.header("foo", "bad").match(this.request);
}
- @SuppressWarnings("unchecked")
@Test
public void headerContains() throws Exception {
this.request.getHeaders().put("foo", Arrays.asList("bar", "baz"));
@@ -99,13 +98,11 @@ public class MockRestRequestMatchersTests {
MockRestRequestMatchers.header("foo", containsString("ba")).match(this.request);
}
- @SuppressWarnings("unchecked")
@Test(expected = AssertionError.class)
public void headerContainsWithMissingHeader() throws Exception {
MockRestRequestMatchers.header("foo", containsString("baz")).match(this.request);
}
- @SuppressWarnings("unchecked")
@Test(expected = AssertionError.class)
public void headerContainsWithMissingValue() throws Exception {
this.request.getHeaders().put("foo", Arrays.asList("bar", "baz"));
diff --git a/spring-test/src/test/java/org/springframework/test/web/client/samples/SampleAsyncTests.java b/spring-test/src/test/java/org/springframework/test/web/client/samples/SampleAsyncTests.java
index 73b17cb5..091b64d5 100644
--- a/spring-test/src/test/java/org/springframework/test/web/client/samples/SampleAsyncTests.java
+++ b/spring-test/src/test/java/org/springframework/test/web/client/samples/SampleAsyncTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package org.springframework.test.web.client.samples;
-import org.junit.Before;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
@@ -29,6 +29,7 @@ import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.web.client.AsyncRestTemplate;
import static org.junit.Assert.*;
+import static org.springframework.test.web.client.ExpectedCount.manyTimes;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.*;
import static org.springframework.test.web.client.response.MockRestResponseCreators.*;
@@ -39,20 +40,14 @@ import static org.springframework.test.web.client.response.MockRestResponseCreat
* code.
*
* @author Rossen Stoyanchev
+ * @since 4.1
*/
public class SampleAsyncTests {
- private MockRestServiceServer mockServer;
-
- private AsyncRestTemplate restTemplate;
+ private final AsyncRestTemplate restTemplate = new AsyncRestTemplate();
+ private final MockRestServiceServer mockServer = MockRestServiceServer.createServer(this.restTemplate);
- @Before
- public void setup() {
- this.restTemplate = new AsyncRestTemplate();
- this.mockServer = MockRestServiceServer.createServer(this.restTemplate);
-
- }
@Test
public void performGet() throws Exception {
@@ -63,7 +58,8 @@ public class SampleAsyncTests {
.andRespond(withSuccess(responseBody, MediaType.APPLICATION_JSON));
@SuppressWarnings("unused")
- ListenableFuture<ResponseEntity<Person>> ludwig = restTemplate.getForEntity("/composers/{id}", Person.class, 42);
+ ListenableFuture<ResponseEntity<Person>> ludwig =
+ this.restTemplate.getForEntity("/composers/{id}", Person.class, 42);
// We are only validating the request. The response is mocked out.
// person.getName().equals("Ludwig van Beethoven")
@@ -73,19 +69,26 @@ public class SampleAsyncTests {
}
@Test
- public void performGetAsync() throws Exception {
+ public void performGetManyTimes() throws Exception {
String responseBody = "{\"name\" : \"Ludwig van Beethoven\", \"someDouble\" : \"1.6035\"}";
- this.mockServer.expect(requestTo("/composers/42")).andExpect(method(HttpMethod.GET))
+ this.mockServer.expect(manyTimes(), requestTo("/composers/42")).andExpect(method(HttpMethod.GET))
.andRespond(withSuccess(responseBody, MediaType.APPLICATION_JSON));
@SuppressWarnings("unused")
- ListenableFuture<ResponseEntity<Person>> ludwig = restTemplate.getForEntity("/composers/{id}", Person.class, 42);
+ ListenableFuture<ResponseEntity<Person>> ludwig =
+ this.restTemplate.getForEntity("/composers/{id}", Person.class, 42);
+ // We are only validating the request. The response is mocked out.
// person.getName().equals("Ludwig van Beethoven")
// person.getDouble().equals(1.6035)
+ this.restTemplate.getForEntity("/composers/{id}", Person.class, 42);
+ this.restTemplate.getForEntity("/composers/{id}", Person.class, 42);
+ this.restTemplate.getForEntity("/composers/{id}", Person.class, 42);
+ this.restTemplate.getForEntity("/composers/{id}", Person.class, 42);
+
this.mockServer.verify();
}
@@ -98,7 +101,8 @@ public class SampleAsyncTests {
.andRespond(withSuccess(responseBody, MediaType.APPLICATION_JSON));
@SuppressWarnings("unused")
- ListenableFuture<ResponseEntity<Person>> ludwig = restTemplate.getForEntity("/composers/{id}", Person.class, 42);
+ ListenableFuture<ResponseEntity<Person>> ludwig =
+ this.restTemplate.getForEntity("/composers/{id}", Person.class, 42);
// hotel.getId() == 42
// hotel.getName().equals("Holiday Inn")
@@ -132,7 +136,8 @@ public class SampleAsyncTests {
this.mockServer.verify();
}
catch (AssertionError error) {
- assertTrue(error.getMessage(), error.getMessage().contains("2 out of 4 were executed"));
+ assertTrue(error.getMessage(), error.getMessage().contains("2 unsatisfied expectation(s)"));
}
}
+
}
diff --git a/spring-test/src/test/java/org/springframework/test/web/client/samples/SampleTests.java b/spring-test/src/test/java/org/springframework/test/web/client/samples/SampleTests.java
index d3fc4823..6d5c7452 100644
--- a/spring-test/src/test/java/org/springframework/test/web/client/samples/SampleTests.java
+++ b/spring-test/src/test/java/org/springframework/test/web/client/samples/SampleTests.java
@@ -27,6 +27,7 @@ import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.RestTemplate;
import static org.junit.Assert.assertTrue;
+import static org.springframework.test.web.client.ExpectedCount.manyTimes;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
@@ -48,7 +49,7 @@ public class SampleTests {
@Before
public void setup() {
this.restTemplate = new RestTemplate();
- this.mockServer = MockRestServiceServer.createServer(this.restTemplate);
+ this.mockServer = MockRestServiceServer.bindTo(this.restTemplate).ignoreExpectOrder(true).build();
}
@Test
@@ -60,7 +61,7 @@ public class SampleTests {
.andRespond(withSuccess(responseBody, MediaType.APPLICATION_JSON));
@SuppressWarnings("unused")
- Person ludwig = restTemplate.getForObject("/composers/{id}", Person.class, 42);
+ Person ludwig = this.restTemplate.getForObject("/composers/{id}", Person.class, 42);
// We are only validating the request. The response is mocked out.
// hotel.getId() == 42
@@ -70,6 +71,28 @@ public class SampleTests {
}
@Test
+ public void performGetManyTimes() throws Exception {
+
+ String responseBody = "{\"name\" : \"Ludwig van Beethoven\", \"someDouble\" : \"1.6035\"}";
+
+ this.mockServer.expect(manyTimes(), requestTo("/composers/42")).andExpect(method(HttpMethod.GET))
+ .andRespond(withSuccess(responseBody, MediaType.APPLICATION_JSON));
+
+ @SuppressWarnings("unused")
+ Person ludwig = this.restTemplate.getForObject("/composers/{id}", Person.class, 42);
+
+ // We are only validating the request. The response is mocked out.
+ // hotel.getId() == 42
+ // hotel.getName().equals("Holiday Inn")
+
+ this.restTemplate.getForObject("/composers/{id}", Person.class, 42);
+ this.restTemplate.getForObject("/composers/{id}", Person.class, 42);
+ this.restTemplate.getForObject("/composers/{id}", Person.class, 42);
+
+ this.mockServer.verify();
+ }
+
+ @Test
public void performGetWithResponseBodyFromFile() throws Exception {
Resource responseBody = new ClassPathResource("ludwig.json", this.getClass());
@@ -78,7 +101,7 @@ public class SampleTests {
.andRespond(withSuccess(responseBody, MediaType.APPLICATION_JSON));
@SuppressWarnings("unused")
- Person ludwig = restTemplate.getForObject("/composers/{id}", Person.class, 42);
+ Person ludwig = this.restTemplate.getForObject("/composers/{id}", Person.class, 42);
// hotel.getId() == 42
// hotel.getName().equals("Holiday Inn")
@@ -102,17 +125,18 @@ public class SampleTests {
.andRespond(withSuccess("8", MediaType.TEXT_PLAIN));
@SuppressWarnings("unused")
- String result = this.restTemplate.getForObject("/number", String.class);
- // result == "1"
+ String result1 = this.restTemplate.getForObject("/number", String.class);
+ // result1 == "1"
- result = this.restTemplate.getForObject("/number", String.class);
+ @SuppressWarnings("unused")
+ String result2 = this.restTemplate.getForObject("/number", String.class);
// result == "2"
try {
this.mockServer.verify();
}
catch (AssertionError error) {
- assertTrue(error.getMessage(), error.getMessage().contains("2 out of 4 were executed"));
+ assertTrue(error.getMessage(), error.getMessage().contains("2 unsatisfied expectation(s)"));
}
}
}
diff --git a/spring-test/src/test/java/org/springframework/test/web/client/samples/matchers/HeaderRequestMatchersIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/web/client/samples/matchers/HeaderRequestMatchersIntegrationTests.java
index 7ffb5eb9..1b56ffa2 100644
--- a/spring-test/src/test/java/org/springframework/test/web/client/samples/matchers/HeaderRequestMatchersIntegrationTests.java
+++ b/spring-test/src/test/java/org/springframework/test/web/client/samples/matchers/HeaderRequestMatchersIntegrationTests.java
@@ -71,7 +71,6 @@ public class HeaderRequestMatchersIntegrationTests {
this.mockServer.verify();
}
- @SuppressWarnings("unchecked")
@Test
public void testStringContains() throws Exception {
diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnectionTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnectionTests.java
index beab7668..bf347eed 100644
--- a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnectionTests.java
+++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnectionTests.java
@@ -129,8 +129,7 @@ public class DelegatingWebConnectionTests {
WebClient webClient = new WebClient();
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(TestController.class).build();
- MockMvcWebConnection mockConnection = new MockMvcWebConnection(mockMvc);
- mockConnection.setWebClient(webClient);
+ MockMvcWebConnection mockConnection = new MockMvcWebConnection(mockMvc, webClient);
WebRequestMatcher cdnMatcher = new UrlRegexRequestMatcher(".*?//code.jquery.com/.*");
WebConnection httpConnection = new HttpWebConnection(webClient);
diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilderTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilderTests.java
index ddf68306..4c7f9cb5 100644
--- a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilderTests.java
+++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilderTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2016 the original author or authors.
+ * Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@
package org.springframework.test.web.servlet.htmlunit;
-import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
@@ -28,11 +27,9 @@ import javax.servlet.ServletContext;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpSession;
-import com.gargoylesoftware.htmlunit.HttpMethod;
-import com.gargoylesoftware.htmlunit.WebClient;
-import com.gargoylesoftware.htmlunit.WebRequest;
-import com.gargoylesoftware.htmlunit.util.NameValuePair;
+import org.apache.commons.io.IOUtils;
import org.apache.http.auth.UsernamePasswordCredentials;
+
import org.junit.Before;
import org.junit.Test;
@@ -42,12 +39,16 @@ import org.springframework.mock.web.MockServletContext;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
-import org.springframework.util.FileCopyUtils;
-import static java.util.Arrays.*;
+import com.gargoylesoftware.htmlunit.HttpMethod;
+import com.gargoylesoftware.htmlunit.WebClient;
+import com.gargoylesoftware.htmlunit.WebRequest;
+import com.gargoylesoftware.htmlunit.util.NameValuePair;
+
+import static java.util.Arrays.asList;
import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.junit.Assert.assertThat;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
/**
* Unit tests for {@link HtmlUnitRequestBuilder}.
@@ -76,6 +77,7 @@ public class HtmlUnitRequestBuilderTests {
requestBuilder = new HtmlUnitRequestBuilder(sessions, webClient, webRequest);
}
+ // --- constructor
@Test(expected = IllegalArgumentException.class)
public void constructorNullSessions() {
@@ -92,6 +94,8 @@ public class HtmlUnitRequestBuilderTests {
new HtmlUnitRequestBuilder(sessions, webClient, null);
}
+ // --- buildRequest
+
@Test
@SuppressWarnings("deprecation")
public void buildRequestBasicAuth() {
@@ -241,8 +245,7 @@ public class HtmlUnitRequestBuilderTests {
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
- assertThat(FileCopyUtils.copyToString(new InputStreamReader(actualRequest.getInputStream(), "ISO-8859-1")),
- equalTo(content));
+ assertThat(IOUtils.toString(actualRequest.getInputStream()), equalTo(content));
}
@Test
@@ -408,7 +411,8 @@ public class HtmlUnitRequestBuilderTests {
assertThat(actualRequest.getParameter("name"), equalTo("value"));
}
- @Test // SPR-14177
+ // SPR-14177
+ @Test
public void buildRequestParameterMapDecodesParameterName() throws Exception {
webRequest.setUrl(new URL("http://example.com/example/?row%5B0%5D=value"));
@@ -561,7 +565,7 @@ public class HtmlUnitRequestBuilderTests {
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
- assertThat(FileCopyUtils.copyToString(actualRequest.getReader()), equalTo(expectedBody));
+ assertThat(IOUtils.toString(actualRequest.getReader()), equalTo(expectedBody));
}
@Test
diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcConnectionBuilderSupportTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcConnectionBuilderSupportTests.java
index c89f4f40..303f5af9 100644
--- a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcConnectionBuilderSupportTests.java
+++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcConnectionBuilderSupportTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,11 @@ import java.io.IOException;
import java.net.URL;
import javax.servlet.http.HttpServletRequest;
+import com.gargoylesoftware.htmlunit.WebClient;
+import com.gargoylesoftware.htmlunit.WebConnection;
+import com.gargoylesoftware.htmlunit.WebRequest;
+import com.gargoylesoftware.htmlunit.WebResponse;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -36,20 +41,18 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
-import com.gargoylesoftware.htmlunit.WebConnection;
-import com.gargoylesoftware.htmlunit.WebRequest;
-import com.gargoylesoftware.htmlunit.WebResponse;
-
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
/**
* Integration tests for {@link MockMvcWebConnectionBuilderSupport}.
*
* @author Rob Winch
+ * @author Rossen Stoyanchev
* @since 4.2
*/
@RunWith(SpringJUnit4ClassRunner.class)
@@ -58,93 +61,85 @@ import static org.mockito.Mockito.mock;
@SuppressWarnings("rawtypes")
public class MockMvcConnectionBuilderSupportTests {
- private final WebConnection delegateConnection = mock(WebConnection.class);
+ private final WebClient client = mock(WebClient.class);
+
+ private MockMvcWebConnectionBuilderSupport builder;
@Autowired
private WebApplicationContext wac;
- private MockMvc mockMvc;
-
- private WebConnection connection;
@Before
public void setup() {
- mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
-
- connection = new MockMvcWebConnectionBuilderSupport(mockMvc){}
- .createConnection(delegateConnection);
+ when(this.client.getWebConnection()).thenReturn(mock(WebConnection.class));
+ this.builder = new MockMvcWebConnectionBuilderSupport(this.wac) {};
}
+
@Test(expected = IllegalArgumentException.class)
public void constructorMockMvcNull() {
- new MockMvcWebConnectionBuilderSupport((MockMvc)null){};
+ new MockMvcWebConnectionBuilderSupport((MockMvc) null){};
}
@Test(expected = IllegalArgumentException.class)
public void constructorContextNull() {
- new MockMvcWebConnectionBuilderSupport((WebApplicationContext)null){};
+ new MockMvcWebConnectionBuilderSupport((WebApplicationContext) null){};
}
@Test
public void context() throws Exception {
- connection = new MockMvcWebConnectionBuilderSupport(wac) {}
- .createConnection(delegateConnection);
+ WebConnection conn = this.builder.createConnection(this.client);
- assertMvcProcessed("http://localhost/");
- assertDelegateProcessed("http://example.com/");
+ assertMockMvcUsed(conn, "http://localhost/");
+ assertMockMvcNotUsed(conn, "http://example.com/");
}
@Test
public void mockMvc() throws Exception {
- assertMvcProcessed("http://localhost/");
- assertDelegateProcessed("http://example.com/");
+ MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
+ WebConnection conn = new MockMvcWebConnectionBuilderSupport(mockMvc) {}.createConnection(this.client);
+
+ assertMockMvcUsed(conn, "http://localhost/");
+ assertMockMvcNotUsed(conn, "http://example.com/");
}
@Test
public void mockMvcExampleDotCom() throws Exception {
- connection = new MockMvcWebConnectionBuilderSupport(wac) {}
- .useMockMvcForHosts("example.com")
- .createConnection(delegateConnection);
+ WebConnection conn = this.builder.useMockMvcForHosts("example.com").createConnection(this.client);
- assertMvcProcessed("http://localhost/");
- assertMvcProcessed("http://example.com/");
- assertDelegateProcessed("http://other.com/");
+ assertMockMvcUsed(conn, "http://localhost/");
+ assertMockMvcUsed(conn, "http://example.com/");
+ assertMockMvcNotUsed(conn, "http://other.com/");
}
@Test
public void mockMvcAlwaysUseMockMvc() throws Exception {
- connection = new MockMvcWebConnectionBuilderSupport(wac) {}
- .alwaysUseMockMvc()
- .createConnection(delegateConnection);
-
- assertMvcProcessed("http://other.com/");
+ WebConnection conn = this.builder.alwaysUseMockMvc().createConnection(this.client);
+ assertMockMvcUsed(conn, "http://other.com/");
}
@Test
public void defaultContextPathEmpty() throws Exception {
- connection = new MockMvcWebConnectionBuilderSupport(wac) {}
- .createConnection(delegateConnection);
-
- assertThat(getWebResponse("http://localhost/abc").getContentAsString(), equalTo(""));
+ WebConnection conn = this.builder.createConnection(this.client);
+ assertThat(getResponse(conn, "http://localhost/abc").getContentAsString(), equalTo(""));
}
@Test
public void defaultContextPathCustom() throws Exception {
- connection = new MockMvcWebConnectionBuilderSupport(wac) {}
- .contextPath("/abc").createConnection(delegateConnection);
-
- assertThat(getWebResponse("http://localhost/abc/def").getContentAsString(), equalTo("/abc"));
+ WebConnection conn = this.builder.contextPath("/abc").createConnection(this.client);
+ assertThat(getResponse(conn, "http://localhost/abc/def").getContentAsString(), equalTo("/abc"));
}
- private void assertMvcProcessed(String url) throws Exception {
- assertThat(getWebResponse(url), notNullValue());
+
+ private void assertMockMvcUsed(WebConnection connection, String url) throws Exception {
+ assertThat(getResponse(connection, url), notNullValue());
}
- private void assertDelegateProcessed(String url) throws Exception {
- assertThat(getWebResponse(url), nullValue());
+ private void assertMockMvcNotUsed(WebConnection connection, String url) throws Exception {
+ assertThat(getResponse(connection, url), nullValue());
}
- private WebResponse getWebResponse(String url) throws IOException {
+ private WebResponse getResponse(WebConnection connection, String url) throws IOException {
return connection.getResponse(new WebRequest(new URL(url)));
}
diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilderTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilderTests.java
index a7473c18..79a6af18 100644
--- a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilderTests.java
+++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilderTests.java
@@ -19,10 +19,13 @@ package org.springframework.test.web.servlet.htmlunit;
import java.io.IOException;
import java.net.URL;
import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import com.gargoylesoftware.htmlunit.HttpMethod;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.WebResponse;
+import com.gargoylesoftware.htmlunit.util.Cookie;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -30,13 +33,17 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
-import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.tests.Assume;
import org.springframework.tests.TestGroup;
+import org.springframework.web.bind.annotation.CookieValue;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@@ -49,9 +56,10 @@ import static org.junit.Assert.*;
*
* @author Rob Winch
* @author Sam Brannen
+ * @author Rossen Stoyanchev
* @since 4.2
*/
-@RunWith(SpringJUnit4ClassRunner.class)
+@RunWith(SpringRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class MockMvcWebClientBuilderTests {
@@ -61,8 +69,6 @@ public class MockMvcWebClientBuilderTests {
private MockMvc mockMvc;
- private WebClient webClient;
-
@Before
public void setup() {
@@ -82,31 +88,65 @@ public class MockMvcWebClientBuilderTests {
@Test
public void mockMvcSetupWithDefaultWebClientDelegate() throws Exception {
- this.webClient = MockMvcWebClientBuilder.mockMvcSetup(this.mockMvc).build();
+ WebClient client = MockMvcWebClientBuilder.mockMvcSetup(this.mockMvc).build();
- assertMvcProcessed("http://localhost/test");
- Assume.group(TestGroup.PERFORMANCE, () -> assertDelegateProcessed("http://example.com/"));
+ assertMockMvcUsed(client, "http://localhost/test");
+ Assume.group(TestGroup.PERFORMANCE, () -> assertMockMvcNotUsed(client, "http://example.com/"));
}
@Test
public void mockMvcSetupWithCustomWebClientDelegate() throws Exception {
WebClient otherClient = new WebClient();
- this.webClient = MockMvcWebClientBuilder.mockMvcSetup(this.mockMvc).withDelegate(otherClient).build();
+ WebClient client = MockMvcWebClientBuilder.mockMvcSetup(this.mockMvc).withDelegate(otherClient).build();
+
+ assertMockMvcUsed(client, "http://localhost/test");
+ Assume.group(TestGroup.PERFORMANCE, () -> assertMockMvcNotUsed(client, "http://example.com/"));
+ }
+
+ @Test // SPR-14066
+ public void cookieManagerShared() throws Exception {
+ this.mockMvc = MockMvcBuilders.standaloneSetup(new CookieController()).build();
+ WebClient client = MockMvcWebClientBuilder.mockMvcSetup(this.mockMvc).build();
+
+ assertThat(getResponse(client, "http://localhost/").getContentAsString(), equalTo("NA"));
+ client.getCookieManager().addCookie(new Cookie("localhost", "cookie", "cookieManagerShared"));
+ assertThat(getResponse(client, "http://localhost/").getContentAsString(), equalTo("cookieManagerShared"));
+ }
+
+ @Test // SPR-14265
+ public void cookiesAreManaged() throws Exception {
+ this.mockMvc = MockMvcBuilders.standaloneSetup(new CookieController()).build();
+ WebClient client = MockMvcWebClientBuilder.mockMvcSetup(this.mockMvc).build();
+
+ assertThat(getResponse(client, "http://localhost/").getContentAsString(), equalTo("NA"));
+ assertThat(postResponse(client, "http://localhost/?cookie=foo").getContentAsString(), equalTo("Set"));
+ assertThat(getResponse(client, "http://localhost/").getContentAsString(), equalTo("foo"));
+ assertThat(deleteResponse(client, "http://localhost/").getContentAsString(), equalTo("Delete"));
+ assertThat(getResponse(client, "http://localhost/").getContentAsString(), equalTo("NA"));
+ }
+
+ private void assertMockMvcUsed(WebClient client, String url) throws Exception {
+ assertThat(getResponse(client, url).getContentAsString(), equalTo("mvc"));
+ }
- assertMvcProcessed("http://localhost/test");
- Assume.group(TestGroup.PERFORMANCE, () -> assertDelegateProcessed("http://example.com/"));
+ private void assertMockMvcNotUsed(WebClient client, String url) throws Exception {
+ assertThat(getResponse(client, url).getContentAsString(), not(equalTo("mvc")));
}
- private void assertMvcProcessed(String url) throws Exception {
- assertThat(getWebResponse(url).getContentAsString(), equalTo("mvc"));
+ private WebResponse getResponse(WebClient client, String url) throws IOException {
+ return createResponse(client, new WebRequest(new URL(url)));
}
- private void assertDelegateProcessed(String url) throws Exception {
- assertThat(getWebResponse(url).getContentAsString(), not(equalTo("mvc")));
+ private WebResponse postResponse(WebClient client, String url) throws IOException {
+ return createResponse(client, new WebRequest(new URL(url), HttpMethod.POST));
}
- private WebResponse getWebResponse(String url) throws IOException {
- return this.webClient.getWebConnection().getResponse(new WebRequest(new URL(url)));
+ private WebResponse deleteResponse(WebClient client, String url) throws IOException {
+ return createResponse(client, new WebRequest(new URL(url), HttpMethod.DELETE));
+ }
+
+ private WebResponse createResponse(WebClient client, WebRequest request) throws IOException {
+ return client.getWebConnection().getResponse(request);
}
@@ -124,4 +164,29 @@ public class MockMvcWebClientBuilderTests {
}
}
+ @RestController
+ static class CookieController {
+
+ static final String COOKIE_NAME = "cookie";
+
+ @RequestMapping(path = "/", produces = "text/plain")
+ String cookie(@CookieValue(name = COOKIE_NAME, defaultValue = "NA") String cookie) {
+ return cookie;
+ }
+
+ @PostMapping(path = "/", produces = "text/plain")
+ String setCookie(@RequestParam String cookie, HttpServletResponse response) {
+ response.addCookie(new javax.servlet.http.Cookie(COOKIE_NAME, cookie));
+ return "Set";
+ }
+
+ @DeleteMapping(path = "/", produces = "text/plain")
+ String deleteCookie(HttpServletResponse response) {
+ javax.servlet.http.Cookie cookie = new javax.servlet.http.Cookie(COOKIE_NAME, "");
+ cookie.setMaxAge(0);
+ response.addCookie(cookie);
+ return "Delete";
+ }
+ }
+
}
diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionTests.java
index b74a22f2..1b3ba8ce 100644
--- a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionTests.java
+++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionTests.java
@@ -35,6 +35,7 @@ import static org.junit.Assert.*;
* @author Rob Winch
* @since 4.2
*/
+@SuppressWarnings("deprecation")
public class MockMvcWebConnectionTests {
private final WebClient webClient = new WebClient();
@@ -49,7 +50,7 @@ public class MockMvcWebConnectionTests {
@Test
public void contextPathNull() throws IOException {
- this.webClient.setWebConnection(new MockMvcWebConnection(this.mockMvc, null));
+ this.webClient.setWebConnection(new MockMvcWebConnection(this.mockMvc, (String) null));
Page page = this.webClient.getPage("http://localhost/context/a");
diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilderTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilderTests.java
index f367153c..29c83115 100644
--- a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilderTests.java
+++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilderTests.java
@@ -18,6 +18,7 @@ package org.springframework.test.web.servlet.htmlunit;
import java.net.URL;
import java.util.List;
+import javax.servlet.http.Cookie;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.WebResponse;
@@ -95,16 +96,41 @@ public class MockWebResponseBuilderTests {
public void buildResponseHeaders() throws Exception {
this.response.addHeader("Content-Type", "text/html");
this.response.addHeader("X-Test", "value");
+ Cookie cookie = new Cookie("cookieA", "valueA");
+ cookie.setDomain("domain");
+ cookie.setPath("/path");
+ cookie.setMaxAge(1800);
+ cookie.setSecure(true);
+ cookie.setHttpOnly(true);
+ this.response.addCookie(cookie);
WebResponse webResponse = this.responseBuilder.build();
List<NameValuePair> responseHeaders = webResponse.getResponseHeaders();
- assertThat(responseHeaders.size(), equalTo(2));
+ assertThat(responseHeaders.size(), equalTo(3));
NameValuePair header = responseHeaders.get(0);
assertThat(header.getName(), equalTo("Content-Type"));
assertThat(header.getValue(), equalTo("text/html"));
header = responseHeaders.get(1);
assertThat(header.getName(), equalTo("X-Test"));
assertThat(header.getValue(), equalTo("value"));
+ header = responseHeaders.get(2);
+ assertThat(header.getName(), equalTo("Set-Cookie"));
+ assertThat(header.getValue(), startsWith("cookieA=valueA;domain=domain;path=/path;expires="));
+ assertThat(header.getValue(), endsWith(";secure;httpOnly"));
+ }
+
+ // SPR-14169
+ @Test
+ public void buildResponseHeadersNullDomainDefaulted() throws Exception {
+ Cookie cookie = new Cookie("cookieA", "valueA");
+ this.response.addCookie(cookie);
+ WebResponse webResponse = this.responseBuilder.build();
+
+ List<NameValuePair> responseHeaders = webResponse.getResponseHeaders();
+ assertThat(responseHeaders.size(), equalTo(1));
+ NameValuePair header = responseHeaders.get(0);
+ assertThat(header.getName(), equalTo("Set-Cookie"));
+ assertThat(header.getValue(), equalTo("cookieA=valueA"));
}
@Test
diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilderTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilderTests.java
index 85e41b02..f60e1a76 100644
--- a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilderTests.java
+++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilderTests.java
@@ -19,6 +19,7 @@ package org.springframework.test.web.servlet.htmlunit.webdriver;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
+import com.gargoylesoftware.htmlunit.util.Cookie;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -27,12 +28,13 @@ import org.openqa.selenium.htmlunit.HtmlUnitDriver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
-import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.tests.Assume;
import org.springframework.tests.TestGroup;
+import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
@@ -48,7 +50,7 @@ import static org.junit.Assert.*;
* @author Sam Brannen
* @since 4.2
*/
-@RunWith(SpringJUnit4ClassRunner.class)
+@RunWith(SpringRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class MockMvcHtmlUnitDriverBuilderTests {
@@ -84,16 +86,16 @@ public class MockMvcHtmlUnitDriverBuilderTests {
WebConnectionHtmlUnitDriver otherDriver = new WebConnectionHtmlUnitDriver();
this.driver = MockMvcHtmlUnitDriverBuilder.mockMvcSetup(this.mockMvc).withDelegate(otherDriver).build();
- assertMvcProcessed("http://localhost/test");
- Assume.group(TestGroup.PERFORMANCE, () -> assertDelegateProcessed("http://example.com/"));
+ assertMockMvcUsed("http://localhost/test");
+ Assume.group(TestGroup.PERFORMANCE, () -> assertMockMvcNotUsed("http://example.com/"));
}
@Test
public void mockMvcSetupWithDefaultDriverDelegate() throws Exception {
this.driver = MockMvcHtmlUnitDriverBuilder.mockMvcSetup(this.mockMvc).build();
- assertMvcProcessed("http://localhost/test");
- Assume.group(TestGroup.PERFORMANCE, () -> assertDelegateProcessed("http://example.com/"));
+ assertMockMvcUsed("http://localhost/test");
+ Assume.group(TestGroup.PERFORMANCE, () -> assertMockMvcNotUsed("http://example.com/"));
}
@Test
@@ -108,11 +110,25 @@ public class MockMvcHtmlUnitDriverBuilderTests {
assertFalse(this.driver.isJavascriptEnabled());
}
- private void assertMvcProcessed(String url) throws Exception {
+ @Test // SPR-14066
+ public void cookieManagerShared() throws Exception {
+ WebConnectionHtmlUnitDriver otherDriver = new WebConnectionHtmlUnitDriver();
+ this.mockMvc = MockMvcBuilders.standaloneSetup(new CookieController()).build();
+ this.driver = MockMvcHtmlUnitDriverBuilder.mockMvcSetup(this.mockMvc)
+ .withDelegate(otherDriver).build();
+
+ assertThat(get("http://localhost/"), equalTo(""));
+ Cookie cookie = new Cookie("localhost", "cookie", "cookieManagerShared");
+ otherDriver.getWebClient().getCookieManager().addCookie(cookie);
+ assertThat(get("http://localhost/"), equalTo("cookieManagerShared"));
+ }
+
+
+ private void assertMockMvcUsed(String url) throws Exception {
assertThat(get(url), containsString(EXPECTED_BODY));
}
- private void assertDelegateProcessed(String url) throws Exception {
+ private void assertMockMvcNotUsed(String url) throws Exception {
assertThat(get(url), not(containsString(EXPECTED_BODY)));
}
@@ -136,4 +152,13 @@ public class MockMvcHtmlUnitDriverBuilderTests {
}
}
-} \ No newline at end of file
+ @RestController
+ static class CookieController {
+
+ @RequestMapping(path = "/", produces = "text/plain")
+ String cookie(@CookieValue("cookie") String cookie) {
+ return cookie;
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilderTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilderTests.java
index 90ab35ba..53c76989 100644
--- a/spring-test/src/test/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilderTests.java
+++ b/spring-test/src/test/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilderTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.test.web.servlet.request;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
+import java.nio.charset.Charset;
import java.security.Principal;
import java.util.Arrays;
import java.util.Collections;
@@ -25,7 +26,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
-
import javax.servlet.ServletContext;
import javax.servlet.http.Cookie;
@@ -43,8 +43,12 @@ import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.servlet.FlashMap;
import org.springframework.web.servlet.support.SessionFlashMapManager;
+import org.springframework.web.util.UriComponentsBuilder;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
/**
* Unit tests for building a {@link MockHttpServletRequest} with
@@ -270,6 +274,20 @@ public class MockHttpServletRequestBuilderTests {
}
@Test
+ public void requestParameterFromRequestBodyFormData() throws Exception {
+ String contentType = "application/x-www-form-urlencoded;charset=UTF-8";
+ String body = "name+1=value+1&name+2=value+A&name+2=value+B&name+3";
+
+ MockHttpServletRequest request = new MockHttpServletRequestBuilder(HttpMethod.POST, "/foo")
+ .contentType(contentType).content(body.getBytes(Charset.forName("UTF-8")))
+ .buildRequest(this.servletContext);
+
+ assertArrayEquals(new String[] {"value 1"}, request.getParameterMap().get("name 1"));
+ assertArrayEquals(new String[] {"value A", "value B"}, request.getParameterMap().get("name 2"));
+ assertArrayEquals(new String[] {null}, request.getParameterMap().get("name 3"));
+ }
+
+ @Test
public void acceptHeader() {
this.builder.accept(MediaType.TEXT_HTML, MediaType.APPLICATION_XML);
@@ -429,7 +447,7 @@ public class MockHttpServletRequestBuilderTests {
@Test
public void sessionAttributes() {
- Map<String, Object> map = new HashMap<String, Object>();
+ Map<String, Object> map = new HashMap<>();
map.put("foo", "bar");
this.builder.sessionAttrs(map);
@@ -472,6 +490,7 @@ public class MockHttpServletRequestBuilderTests {
}
// SPR-12945
+
@Test
public void mergeInvokesDefaultRequestPostProcessorFirst() {
final String ATTR = "ATTR";
@@ -492,6 +511,20 @@ public class MockHttpServletRequestBuilderTests {
assertEquals(EXEPCTED, request.getAttribute(ATTR));
}
+ // SPR-13719
+
+ @Test
+ public void arbitraryMethod() {
+ String httpMethod = "REPort";
+ URI url = UriComponentsBuilder.fromPath("/foo/{bar}").buildAndExpand(42).toUri();
+ this.builder = new MockHttpServletRequestBuilder(httpMethod, url);
+
+ MockHttpServletRequest request = this.builder.buildRequest(this.servletContext);
+
+ assertEquals(httpMethod, request.getMethod());
+ assertEquals("/foo/42", request.getPathInfo());
+ }
+
private final class User implements Principal {
diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/result/JsonPathResultMatchersTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/result/JsonPathResultMatchersTests.java
index bf0b5885..b587a34e 100644
--- a/spring-test/src/test/java/org/springframework/test/web/servlet/result/JsonPathResultMatchersTests.java
+++ b/spring-test/src/test/java/org/springframework/test/web/servlet/result/JsonPathResultMatchersTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,6 +29,7 @@ import org.springframework.test.web.servlet.StubMvcResult;
* @author Rossen Stoyanchev
* @author Craig Andrews
* @author Sam Brannen
+ * @author Brian Clozel
*/
public class JsonPathResultMatchersTests {
@@ -41,7 +42,7 @@ public class JsonPathResultMatchersTests {
"'emptyString': '', " + //
"'emptyArray': [], " + //
"'emptyMap': {} " + //
- "}";
+ "}";
private static final StubMvcResult stubMvcResult;
@@ -57,7 +58,6 @@ public class JsonPathResultMatchersTests {
}
}
-
@Test
public void value() throws Exception {
new JsonPathResultMatchers("$.str").value("foo").match(stubMvcResult);
@@ -233,4 +233,42 @@ public class JsonPathResultMatchersTests {
new JsonPathResultMatchers("$.arr").isString().match(stubMvcResult);
}
+ @Test(expected = AssertionError.class)
+ public void valueWithJsonPrefixNotConfigured() throws Exception {
+ String jsonPrefix = "prefix";
+ StubMvcResult result = createPrefixedStubMvcResult(jsonPrefix);
+ new JsonPathResultMatchers("$.str").value("foo").match(result);
+ }
+
+ @Test(expected = AssertionError.class)
+ public void valueWithJsonWrongPrefix() throws Exception {
+ String jsonPrefix = "prefix";
+ StubMvcResult result = createPrefixedStubMvcResult(jsonPrefix);
+ new JsonPathResultMatchers("$.str").prefix("wrong").value("foo").match(result);
+ }
+
+ @Test
+ public void valueWithJsonPrefix() throws Exception {
+ String jsonPrefix = "prefix";
+ StubMvcResult result = createPrefixedStubMvcResult(jsonPrefix);
+ new JsonPathResultMatchers("$.str").prefix(jsonPrefix).value("foo").match(result);
+ }
+
+ @Test(expected = AssertionError.class)
+ public void prefixWithPayloadNotLongEnough() throws Exception {
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ response.addHeader("Content-Type", "application/json");
+ response.getWriter().print(new String("test".getBytes("ISO-8859-1")));
+ StubMvcResult result = new StubMvcResult(null, null, null, null, null, null, response);
+
+ new JsonPathResultMatchers("$.str").prefix("prefix").value("foo").match(result);
+ }
+
+ private StubMvcResult createPrefixedStubMvcResult(String jsonPrefix) throws Exception {
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ response.addHeader("Content-Type", "application/json");
+ response.getWriter().print(jsonPrefix + new String(RESPONSE_CONTENT.getBytes("ISO-8859-1")));
+ return new StubMvcResult(null, null, null, null, null, null, response);
+ }
+
}
diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/result/StatusResultMatchersTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/result/StatusResultMatchersTests.java
index 7b1c04d4..ead30b9e 100644
--- a/spring-test/src/test/java/org/springframework/test/web/servlet/result/StatusResultMatchersTests.java
+++ b/spring-test/src/test/java/org/springframework/test/web/servlet/result/StatusResultMatchersTests.java
@@ -59,7 +59,7 @@ public class StatusResultMatchersTests {
List<AssertionError> failures = new ArrayList<AssertionError>();
- for(HttpStatus status : HttpStatus.values()) {
+ for (HttpStatus status : HttpStatus.values()) {
MockHttpServletResponse response = new MockHttpServletResponse();
response.setStatus(status.value());
MvcResult mvcResult = new StubMvcResult(request, null, null, null, null, null, response);
@@ -91,9 +91,7 @@ public class StatusResultMatchersTests {
@Test
public void statusRanges() throws Exception {
-
- for(HttpStatus status : HttpStatus.values()) {
-
+ for (HttpStatus status : HttpStatus.values()) {
MockHttpServletResponse response = new MockHttpServletResponse();
response.setStatus(status.value());
MvcResult mvcResult = new StubMvcResult(request, null, null, null, null, null, response);
diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/AsyncControllerJavaConfigTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/AsyncControllerJavaConfigTests.java
index 31943512..0613048d 100644
--- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/AsyncControllerJavaConfigTests.java
+++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/AsyncControllerJavaConfigTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@ import java.util.concurrent.Callable;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
@@ -36,7 +37,7 @@ import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
-import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.async.CallableProcessingInterceptor;
@@ -55,6 +56,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* Tests with Java configuration.
*
* @author Rossen Stoyanchev
+ * @author Sam Brannen
*/
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@@ -123,7 +125,7 @@ public class AsyncControllerJavaConfigTests {
@RestController
static class AsyncController {
- @RequestMapping(path = "/callable")
+ @GetMapping("/callable")
public Callable<Map<String, String>> getCallable() {
return () -> Collections.singletonMap("key", "value");
}
diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/PersonController.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/PersonController.java
index 5e115f66..ee365936 100644
--- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/PersonController.java
+++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/PersonController.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author 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,25 +16,24 @@
package org.springframework.test.web.servlet.samples.context;
-import org.springframework.stereotype.Controller;
import org.springframework.test.web.Person;
+import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
-@Controller
+@RestController
+@RequestMapping("/person")
public class PersonController {
private final PersonDao personDao;
- public PersonController(PersonDao personDao) {
+ PersonController(PersonDao personDao) {
this.personDao = personDao;
}
- @RequestMapping(value="/person/{id}", method=RequestMethod.GET)
- @ResponseBody
+ @GetMapping("/{id}")
public Person getPerson(@PathVariable long id) {
return this.personDao.getPerson(id);
}
diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/AsyncTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/AsyncTests.java
index 48038763..c922297f 100644
--- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/AsyncTests.java
+++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/AsyncTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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.test.web.servlet.samples.standalone;
import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
@@ -26,6 +27,7 @@ import org.junit.Test;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
import org.springframework.test.web.Person;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
@@ -36,6 +38,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
+import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import static org.junit.Assert.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
@@ -67,11 +70,39 @@ public class AsyncTests {
this.mockMvc.perform(asyncDispatch(mvcResult))
.andExpect(status().isOk())
- .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
.andExpect(content().string("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}"));
}
@Test
+ public void streaming() throws Exception {
+ this.mockMvc.perform(get("/1").param("streaming", "true"))
+ .andExpect(request().asyncStarted())
+ .andDo(r -> r.getAsyncResult()) // fetch async result similar to "asyncDispatch" builder
+ .andExpect(status().isOk())
+ .andExpect(content().string("name=Joe"));
+ }
+
+ @Test
+ public void streamingSlow() throws Exception {
+ this.mockMvc.perform(get("/1").param("streamingSlow", "true"))
+ .andExpect(request().asyncStarted())
+ .andDo(r -> r.getAsyncResult())
+ .andExpect(status().isOk())
+ .andExpect(content().string("name=Joe&someBoolean=true"));
+ }
+
+ @Test
+ public void streamingJson() throws Exception {
+ this.mockMvc.perform(get("/1").param("streamingJson", "true"))
+ .andExpect(request().asyncStarted())
+ .andDo(r -> r.getAsyncResult())
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
+ .andExpect(content().string("{\"name\":\"Joe\",\"someDouble\":0.5}"));
+ }
+
+ @Test
public void deferredResult() throws Exception {
MvcResult mvcResult = this.mockMvc.perform(get("/1").param("deferredResult", "true"))
.andExpect(request().asyncStarted())
@@ -81,7 +112,7 @@ public class AsyncTests {
this.mockMvc.perform(asyncDispatch(mvcResult))
.andExpect(status().isOk())
- .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
.andExpect(content().string("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}"));
}
@@ -94,7 +125,7 @@ public class AsyncTests {
this.mockMvc.perform(asyncDispatch(mvcResult))
.andExpect(status().isOk())
- .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
.andExpect(content().string("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}"));
}
@@ -122,7 +153,7 @@ public class AsyncTests {
this.mockMvc.perform(asyncDispatch(mvcResult))
.andExpect(status().isOk())
- .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
.andExpect(content().string("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}"));
}
@@ -137,7 +168,7 @@ public class AsyncTests {
this.mockMvc.perform(asyncDispatch(mvcResult))
.andExpect(status().isOk())
- .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
.andExpect(content().string("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}"));
}
@@ -161,7 +192,7 @@ public class AsyncTests {
this.mockMvc.perform(asyncDispatch(mvcResult))
.andDo(print(writer))
.andExpect(status().isOk())
- .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
.andExpect(content().string("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}"));
assertTrue(writer.toString().contains("Async started = false"));
@@ -184,6 +215,31 @@ public class AsyncTests {
return () -> new Person("Joe");
}
+ @RequestMapping(params = "streaming")
+ public StreamingResponseBody getStreaming() {
+ return os -> os.write("name=Joe".getBytes());
+ }
+
+ @RequestMapping(params = "streamingSlow")
+ public StreamingResponseBody getStreamingSlow() {
+ return os -> {
+ os.write("name=Joe".getBytes());
+ try {
+ Thread.sleep(200);
+ os.write("&someBoolean=true".getBytes());
+ }
+ catch (InterruptedException e) {
+ /* no-op */
+ }
+ };
+ }
+
+ @RequestMapping(params = "streamingJson")
+ public ResponseEntity<StreamingResponseBody> getStreamingJson() {
+ return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON_UTF8)
+ .body(os -> os.write("{\"name\":\"Joe\",\"someDouble\":0.5}".getBytes(StandardCharsets.UTF_8)));
+ }
+
@RequestMapping(params = "deferredResult")
public DeferredResult<Person> getDeferredResult() {
DeferredResult<Person> deferredResult = new DeferredResult<Person>();
diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/ViewResolutionTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/ViewResolutionTests.java
index 0ac2b07c..b286f626 100644
--- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/ViewResolutionTests.java
+++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/ViewResolutionTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2015 the original author 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,10 +53,7 @@ public class ViewResolutionTests {
@Test
public void testJspOnly() throws Exception {
-
- InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
- viewResolver.setPrefix("/WEB-INF/");
- viewResolver.setSuffix(".jsp");
+ InternalResourceViewResolver viewResolver = new InternalResourceViewResolver("/WEB-INF/", ".jsp");
standaloneSetup(new PersonController()).setViewResolvers(viewResolver).build()
.perform(get("/person/Corea"))
@@ -68,7 +65,6 @@ public class ViewResolutionTests {
@Test
public void testJsonOnly() throws Exception {
-
standaloneSetup(new PersonController()).setSingleView(new MappingJackson2JsonView()).build()
.perform(get("/person/Corea"))
.andExpect(status().isOk())
@@ -78,7 +74,6 @@ public class ViewResolutionTests {
@Test
public void testXmlOnly() throws Exception {
-
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setClassesToBeBound(Person.class);
@@ -91,7 +86,6 @@ public class ViewResolutionTests {
@Test
public void testContentNegotiation() throws Exception {
-
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setClassesToBeBound(Person.class);
@@ -131,7 +125,6 @@ public class ViewResolutionTests {
@Test
public void defaultViewResolver() throws Exception {
-
standaloneSetup(new PersonController()).build()
.perform(get("/person/Corea"))
.andExpect(model().attribute("person", hasProperty("name", equalTo("Corea"))))
diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/ContentAssertionTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/ContentAssertionTests.java
index 0c470ea5..d0d60138 100644
--- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/ContentAssertionTests.java
+++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/ContentAssertionTests.java
@@ -16,8 +16,6 @@
package org.springframework.test.web.servlet.samples.standalone.resultmatchers;
-import java.nio.charset.Charset;
-
import org.junit.Before;
import org.junit.Test;
@@ -44,8 +42,6 @@ import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;
*/
public class ContentAssertionTests {
- public static final MediaType TEXT_PLAIN_UTF8 = new MediaType("text", "plain", Charset.forName("UTF-8"));
-
private MockMvc mockMvc;
@Before
@@ -56,8 +52,10 @@ public class ContentAssertionTests {
@Test
public void testContentType() throws Exception {
this.mockMvc.perform(get("/handle").accept(MediaType.TEXT_PLAIN))
- .andExpect(content().contentType(MediaType.TEXT_PLAIN))
- .andExpect(content().contentType("text/plain"));
+ .andExpect(content().contentType(MediaType.valueOf("text/plain;charset=ISO-8859-1")))
+ .andExpect(content().contentType("text/plain;charset=ISO-8859-1"))
+ .andExpect(content().contentTypeCompatibleWith("text/plain"))
+ .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_PLAIN));
this.mockMvc.perform(get("/handleUtf8"))
.andExpect(content().contentType(MediaType.valueOf("text/plain;charset=UTF-8")))
diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/HandlerAssertionTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/HandlerAssertionTests.java
index 9d3b883d..2c671bbf 100644
--- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/HandlerAssertionTests.java
+++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/HandlerAssertionTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,66 +18,84 @@ package org.springframework.test.web.servlet.samples.standalone.resultmatchers;
import java.lang.reflect.Method;
-import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
-import org.springframework.stereotype.Controller;
+import org.springframework.http.ResponseEntity;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.bind.annotation.RequestMapping;
-
-import static org.hamcrest.Matchers.*;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
-import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.handler;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
+import static org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.on;
/**
- * Examples of expectations on the handler or handler method that executed the request.
- *
- * <p>Note that in most cases "handler" is synonymous with "controller".
- * For example an {@code @Controller} is a kind of handler.
+ * Examples of expectations on the controller type and controller method.
*
* @author Rossen Stoyanchev
+ * @author Sam Brannen
*/
public class HandlerAssertionTests {
- private MockMvc mockMvc;
+ private final MockMvc mockMvc = standaloneSetup(new SimpleController()).alwaysExpect(status().isOk()).build();
+
+ @Rule
+ public final ExpectedException exception = ExpectedException.none();
- @Before
- public void setup() {
- this.mockMvc = standaloneSetup(new SimpleController()).alwaysExpect(status().isOk()).build();
- }
@Test
- public void testHandlerType() throws Exception {
+ public void handlerType() throws Exception {
this.mockMvc.perform(get("/")).andExpect(handler().handlerType(SimpleController.class));
}
@Test
- public void testHandlerMethodNameEqualTo() throws Exception {
- this.mockMvc.perform(get("/")).andExpect(handler().methodName("handle"));
+ public void methodCallOnNonMock() throws Exception {
+ exception.expect(AssertionError.class);
+ exception.expectMessage("The supplied object [bogus] is not an instance of");
+ exception.expectMessage(MvcUriComponentsBuilder.MethodInvocationInfo.class.getName());
+ exception.expectMessage("Ensure that you invoke the handler method via MvcUriComponentsBuilder.on()");
- // Hamcrest matcher..
- this.mockMvc.perform(get("/")).andExpect(handler().methodName(equalTo("handle")));
+ this.mockMvc.perform(get("/")).andExpect(handler().methodCall("bogus"));
}
@Test
- public void testHandlerMethodNameMatcher() throws Exception {
+ public void methodCall() throws Exception {
+ this.mockMvc.perform(get("/")).andExpect(handler().methodCall(on(SimpleController.class).handle()));
+ }
+
+ @Test
+ public void methodName() throws Exception {
+ this.mockMvc.perform(get("/")).andExpect(handler().methodName("handle"));
+ }
+
+ @Test
+ public void methodNameMatchers() throws Exception {
+ this.mockMvc.perform(get("/")).andExpect(handler().methodName(equalTo("handle")));
this.mockMvc.perform(get("/")).andExpect(handler().methodName(is(not("save"))));
}
@Test
- public void testHandlerMethod() throws Exception {
+ public void method() throws Exception {
Method method = SimpleController.class.getMethod("handle");
this.mockMvc.perform(get("/")).andExpect(handler().method(method));
}
- @Controller
- private static class SimpleController {
+ @RestController
+ static class SimpleController {
@RequestMapping("/")
- public String handle() {
- return "view";
+ public ResponseEntity<Void> handle() {
+ return ResponseEntity.ok().build();
}
}
+
}
diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/HeaderAssertionTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/HeaderAssertionTests.java
index e81824c8..38fbba01 100644
--- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/HeaderAssertionTests.java
+++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/HeaderAssertionTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,11 @@
package org.springframework.test.web.servlet.samples.standalone.resultmatchers;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
import org.junit.Before;
import org.junit.Test;
@@ -28,16 +33,22 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.WebRequest;
-import static org.hamcrest.CoreMatchers.*;
-import static org.junit.Assert.*;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
-import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.hasItems;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.springframework.http.HttpHeaders.IF_MODIFIED_SINCE;
+import static org.springframework.http.HttpHeaders.LAST_MODIFIED;
+import static org.springframework.http.HttpHeaders.VARY;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-import java.util.TimeZone;
/**
* Examples of expectations on response header values.
@@ -48,86 +59,95 @@ import java.util.TimeZone;
*/
public class HeaderAssertionTests {
- private static final String EXPECTED_ASSERTION_ERROR_MSG = "Should have thrown an AssertionError";
-
- private static final String IF_MODIFIED_SINCE = "If-Modified-Since";
+ private static final String ERROR_MESSAGE = "Should have thrown an AssertionError";
- private static final String LAST_MODIFIED = "Last-Modified";
-
- private final long currentTime = System.currentTimeMillis();
private String now;
- private String oneMinuteAgo;
+ private String minuteAgo;
- private String oneSecondLater;
+ private String secondLater;
private MockMvc mockMvc;
- private PersonController personController;
+ private final long currentTime = System.currentTimeMillis();
@Before
public void setup() {
SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
- this.now = dateFormat.format(new Date(currentTime));
- this.oneMinuteAgo = dateFormat.format(new Date(currentTime - (1000 * 60)));
- this.oneSecondLater = dateFormat.format(new Date(currentTime + 1000));
- this.personController = new PersonController();
- this.personController.setStubTimestamp(currentTime);
- this.mockMvc = standaloneSetup(this.personController).build();
+ this.now = dateFormat.format(new Date(this.currentTime));
+ this.minuteAgo = dateFormat.format(new Date(this.currentTime - (1000 * 60)));
+ this.secondLater = dateFormat.format(new Date(this.currentTime + 1000));
+
+ PersonController controller = new PersonController();
+ controller.setStubTimestamp(this.currentTime);
+ this.mockMvc = standaloneSetup(controller).build();
}
+
@Test
public void stringWithCorrectResponseHeaderValue() throws Exception {
- this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, oneMinuteAgo))//
- .andExpect(header().string(LAST_MODIFIED, now));
+ this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, minuteAgo))
+ .andExpect(header().string(LAST_MODIFIED, now));
}
@Test
public void stringWithMatcherAndCorrectResponseHeaderValue() throws Exception {
- this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, oneMinuteAgo))//
- .andExpect(header().string(LAST_MODIFIED, equalTo(now)));
+ this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, minuteAgo))
+ .andExpect(header().string(LAST_MODIFIED, equalTo(now)));
+ }
+
+ @Test
+ public void multiStringHeaderValue() throws Exception {
+ this.mockMvc.perform(get("/persons/1")).andExpect(header().stringValues(VARY, "foo", "bar"));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void multiStringHeaderValueWithMatchers() throws Exception {
+ this.mockMvc.perform(get("/persons/1"))
+ .andExpect(header().stringValues(VARY, hasItems(containsString("foo"), startsWith("bar"))));
}
@Test
public void dateValueWithCorrectResponseHeaderValue() throws Exception {
- this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, oneMinuteAgo))//
- .andExpect(header().dateValue(LAST_MODIFIED, currentTime));
+ this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, minuteAgo))
+ .andExpect(header().dateValue(LAST_MODIFIED, this.currentTime));
}
@Test
public void longValueWithCorrectResponseHeaderValue() throws Exception {
- this.mockMvc.perform(get("/persons/1"))//
- .andExpect(header().longValue("X-Rate-Limiting", 42));
+ this.mockMvc.perform(get("/persons/1"))
+ .andExpect(header().longValue("X-Rate-Limiting", 42));
}
@Test
public void stringWithMissingResponseHeader() throws Exception {
- this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, now))//
- .andExpect(status().isNotModified())//
- .andExpect(header().string("X-Custom-Header", (String) null));
+ this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, now))
+ .andExpect(status().isNotModified())
+ .andExpect(header().stringValues("X-Custom-Header"));
}
@Test
public void stringWithMatcherAndMissingResponseHeader() throws Exception {
- this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, now))//
- .andExpect(status().isNotModified())//
- .andExpect(header().string("X-Custom-Header", nullValue()));
+ this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, now))
+ .andExpect(status().isNotModified())
+ .andExpect(header().string("X-Custom-Header", nullValue()));
}
@Test
public void longValueWithMissingResponseHeader() throws Exception {
try {
- this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, now))//
- .andExpect(status().isNotModified())//
- .andExpect(header().longValue("X-Custom-Header", 99L));
+ this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, now))
+ .andExpect(status().isNotModified())
+ .andExpect(header().longValue("X-Custom-Header", 99L));
- fail(EXPECTED_ASSERTION_ERROR_MSG);
+ fail(ERROR_MESSAGE);
}
catch (AssertionError e) {
- if (EXPECTED_ASSERTION_ERROR_MSG.equals(e.getMessage())) {
+ if (ERROR_MESSAGE.equals(e.getMessage())) {
throw e;
}
assertEquals("Response does not contain header " + "X-Custom-Header", e.getMessage());
@@ -138,32 +158,30 @@ public class HeaderAssertionTests {
@Test
public void doesNotExist() throws Exception {
- this.mockMvc.perform(get("/persons/1"))
- .andExpect(header().doesNotExist("X-Custom-Header"));
+ this.mockMvc.perform(get("/persons/1")).andExpect(header().doesNotExist("X-Custom-Header"));
}
// SPR-10771
@Test(expected = AssertionError.class)
public void doesNotExistFail() throws Exception {
- this.mockMvc.perform(get("/persons/1"))
- .andExpect(header().doesNotExist(LAST_MODIFIED));
+ this.mockMvc.perform(get("/persons/1")).andExpect(header().doesNotExist(LAST_MODIFIED));
}
@Test
public void stringWithIncorrectResponseHeaderValue() throws Exception {
- assertIncorrectResponseHeaderValue(header().string(LAST_MODIFIED, oneSecondLater), oneSecondLater);
+ assertIncorrectResponseHeader(header().string(LAST_MODIFIED, secondLater), secondLater);
}
@Test
public void stringWithMatcherAndIncorrectResponseHeaderValue() throws Exception {
- assertIncorrectResponseHeaderValue(header().string(LAST_MODIFIED, equalTo(oneSecondLater)), oneSecondLater);
+ assertIncorrectResponseHeader(header().string(LAST_MODIFIED, equalTo(secondLater)), secondLater);
}
@Test
public void dateValueWithIncorrectResponseHeaderValue() throws Exception {
- long unexpected = currentTime + 1000;
- assertIncorrectResponseHeaderValue(header().dateValue(LAST_MODIFIED, unexpected), oneSecondLater);
+ long unexpected = this.currentTime + 1000;
+ assertIncorrectResponseHeader(header().dateValue(LAST_MODIFIED, unexpected), secondLater);
}
@Test(expected = AssertionError.class)
@@ -171,21 +189,20 @@ public class HeaderAssertionTests {
this.mockMvc.perform(get("/persons/1")).andExpect(header().longValue("X-Rate-Limiting", 1));
}
- private void assertIncorrectResponseHeaderValue(ResultMatcher resultMatcher, String unexpected) throws Exception {
+ private void assertIncorrectResponseHeader(ResultMatcher matcher, String unexpected) throws Exception {
try {
- this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, oneMinuteAgo))//
- .andExpect(resultMatcher);
+ this.mockMvc.perform(get("/persons/1")
+ .header(IF_MODIFIED_SINCE, minuteAgo))
+ .andExpect(matcher);
- fail(EXPECTED_ASSERTION_ERROR_MSG);
+ fail(ERROR_MESSAGE);
}
catch (AssertionError e) {
- if (EXPECTED_ASSERTION_ERROR_MSG.equals(e.getMessage())) {
+ if (ERROR_MESSAGE.equals(e.getMessage())) {
throw e;
}
- // [SPR-10659] Ensure that the header name is included in the message
- //
- // We don't use assertEquals() since we cannot control the formatting
- // produced by JUnit or Hamcrest.
+ // SPR-10659: ensure header name is in the message
+ // Unfortunately, we can't control formatting from JUnit or Hamcrest.
assertMessageContains(e, "Response header " + LAST_MODIFIED);
assertMessageContains(e, unexpected);
assertMessageContains(e, now);
@@ -198,8 +215,6 @@ public class HeaderAssertionTests {
}
- // -------------------------------------------------------------------------
-
@Controller
private static class PersonController {
@@ -216,6 +231,7 @@ public class HeaderAssertionTests {
.ok()
.lastModified(calculateLastModified(id))
.header("X-Rate-Limiting", "42")
+ .header("Vary", "foo", "bar")
.body(new Person("Jason"));
}
diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/XpathAssertionTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/XpathAssertionTests.java
index 4ae88e7a..c1e25791 100644
--- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/XpathAssertionTests.java
+++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/XpathAssertionTests.java
@@ -158,7 +158,7 @@ public class XpathAssertionTests {
standaloneSetup(new BlogFeedController()).build()
.perform(get("/blog.atom").accept(MediaType.APPLICATION_ATOM_XML))
.andExpect(status().isOk())
- .andExpect(content().contentType(MediaType.APPLICATION_ATOM_XML))
+ .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_ATOM_XML))
.andExpect(xpath("//feed/title").string("Test Feed"))
.andExpect(xpath("//feed/icon").string("http://www.example.com/favicon.ico"));
}
diff --git a/spring-test/src/test/resources/log4j.properties b/spring-test/src/test/resources/log4j.properties
index 8b88c2e0..258e7b84 100644
--- a/spring-test/src/test/resources/log4j.properties
+++ b/spring-test/src/test/resources/log4j.properties
@@ -25,6 +25,9 @@ log4j.logger.org.springframework.test.context.web=WARN
#log4j.logger.org.springframework.test.context.support.AbstractGenericContextLoader=INFO
#log4j.logger.org.springframework.test.context.support.AnnotationConfigContextLoader=INFO
+# The following must be kept at DEBUG in order to test SPR-14363.
+log4j.logger.org.springframework.test.util=DEBUG
+
log4j.logger.org.springframework.test.web.servlet.result=DEBUG
#log4j.logger.org.springframework.test=TRACE
diff --git a/spring-tx/src/main/java/org/springframework/jca/endpoint/AbstractMessageEndpointFactory.java b/spring-tx/src/main/java/org/springframework/jca/endpoint/AbstractMessageEndpointFactory.java
index eae06de7..6b266172 100644
--- a/spring-tx/src/main/java/org/springframework/jca/endpoint/AbstractMessageEndpointFactory.java
+++ b/spring-tx/src/main/java/org/springframework/jca/endpoint/AbstractMessageEndpointFactory.java
@@ -58,7 +58,7 @@ public abstract class AbstractMessageEndpointFactory implements MessageEndpointF
/**
- * Set the the XA transaction manager to use for wrapping endpoint
+ * Set the XA transaction manager to use for wrapping endpoint
* invocations, enlisting the endpoint resource in each such transaction.
* <p>The passed-in object may be a transaction manager which implements
* Spring's {@link org.springframework.transaction.jta.TransactionFactory}
diff --git a/spring-tx/src/main/java/org/springframework/jca/endpoint/GenericMessageEndpointManager.java b/spring-tx/src/main/java/org/springframework/jca/endpoint/GenericMessageEndpointManager.java
index 57c356ae..61253e02 100644
--- a/spring-tx/src/main/java/org/springframework/jca/endpoint/GenericMessageEndpointManager.java
+++ b/spring-tx/src/main/java/org/springframework/jca/endpoint/GenericMessageEndpointManager.java
@@ -214,7 +214,7 @@ public class GenericMessageEndpointManager implements SmartLifecycle, Initializi
* Set whether to auto-start the endpoint activation after this endpoint
* manager has been initialized and the context has been refreshed.
* <p>Default is "true". Turn this flag off to defer the endpoint
- * activation until an explicit {#start()} call.
+ * activation until an explicit {@link #start()} call.
*/
public void setAutoStartup(boolean autoStartup) {
this.autoStartup = autoStartup;
diff --git a/spring-tx/src/main/java/org/springframework/jca/work/WorkManagerTaskExecutor.java b/spring-tx/src/main/java/org/springframework/jca/work/WorkManagerTaskExecutor.java
index a8391e3d..532ea23e 100644
--- a/spring-tx/src/main/java/org/springframework/jca/work/WorkManagerTaskExecutor.java
+++ b/spring-tx/src/main/java/org/springframework/jca/work/WorkManagerTaskExecutor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +30,7 @@ import javax.resource.spi.work.WorkRejectedException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.task.AsyncListenableTaskExecutor;
+import org.springframework.core.task.TaskDecorator;
import org.springframework.core.task.TaskRejectedException;
import org.springframework.core.task.TaskTimeoutException;
import org.springframework.jca.context.BootstrapContextAware;
@@ -84,6 +85,8 @@ public class WorkManagerTaskExecutor extends JndiLocatorSupport
private WorkListener workListener;
+ private TaskDecorator taskDecorator;
+
/**
* Create a new WorkManagerTaskExecutor, expecting bean-style configuration.
@@ -164,6 +167,20 @@ public class WorkManagerTaskExecutor extends JndiLocatorSupport
this.workListener = workListener;
}
+ /**
+ * Specify a custom {@link TaskDecorator} to be applied to any {@link Runnable}
+ * about to be executed.
+ * <p>Note that such a decorator is not necessarily being applied to the
+ * user-supplied {@code Runnable}/{@code Callable} but rather to the actual
+ * execution callback (which may be a wrapper around the user-supplied task).
+ * <p>The primary use case is to set some execution context around the task's
+ * invocation, or to provide some monitoring/statistics for task execution.
+ * @since 4.3
+ */
+ public void setTaskDecorator(TaskDecorator taskDecorator) {
+ this.taskDecorator = taskDecorator;
+ }
+
@Override
public void afterPropertiesSet() throws NamingException {
if (this.workManager == null) {
@@ -199,7 +216,7 @@ public class WorkManagerTaskExecutor extends JndiLocatorSupport
@Override
public void execute(Runnable task, long startTimeout) {
Assert.state(this.workManager != null, "No WorkManager specified");
- Work work = new DelegatingWork(task);
+ Work work = new DelegatingWork(this.taskDecorator != null ? this.taskDecorator.decorate(task) : task);
try {
if (this.blockUntilCompleted) {
if (startTimeout != TIMEOUT_INDEFINITE || this.workListener != null) {
diff --git a/spring-tx/src/main/java/org/springframework/transaction/event/ApplicationListenerMethodTransactionalAdapter.java b/spring-tx/src/main/java/org/springframework/transaction/event/ApplicationListenerMethodTransactionalAdapter.java
index 6228d01c..23db0455 100644
--- a/spring-tx/src/main/java/org/springframework/transaction/event/ApplicationListenerMethodTransactionalAdapter.java
+++ b/spring-tx/src/main/java/org/springframework/transaction/event/ApplicationListenerMethodTransactionalAdapter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -54,7 +54,10 @@ class ApplicationListenerMethodTransactionalAdapter extends ApplicationListenerM
public ApplicationListenerMethodTransactionalAdapter(String beanName, Class<?> targetClass, Method method) {
super(beanName, targetClass, method);
- this.annotation = findAnnotation(method);
+ this.annotation = AnnotatedElementUtils.findMergedAnnotation(method, TransactionalEventListener.class);
+ if (this.annotation == null) {
+ throw new IllegalStateException("No TransactionalEventListener annotation found on '" + method + "'");
+ }
}
@@ -81,14 +84,6 @@ class ApplicationListenerMethodTransactionalAdapter extends ApplicationListenerM
return new TransactionSynchronizationEventAdapter(this, event, this.annotation.phase());
}
- static TransactionalEventListener findAnnotation(Method method) {
- TransactionalEventListener annotation =
- AnnotatedElementUtils.findMergedAnnotation(method, TransactionalEventListener.class);
- if (annotation == null) {
- throw new IllegalStateException("No TransactionalEventListener annotation found on '" + method + "'");
- }
- return annotation;
- }
private static class TransactionSynchronizationEventAdapter extends TransactionSynchronizationAdapter {
diff --git a/spring-tx/src/main/java/org/springframework/transaction/interceptor/AbstractFallbackTransactionAttributeSource.java b/spring-tx/src/main/java/org/springframework/transaction/interceptor/AbstractFallbackTransactionAttributeSource.java
index 04f1bc2f..206f3c82 100644
--- a/spring-tx/src/main/java/org/springframework/transaction/interceptor/AbstractFallbackTransactionAttributeSource.java
+++ b/spring-tx/src/main/java/org/springframework/transaction/interceptor/AbstractFallbackTransactionAttributeSource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,8 +25,8 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.BridgeMethodResolver;
+import org.springframework.core.MethodClassKey;
import org.springframework.util.ClassUtils;
-import org.springframework.util.ObjectUtils;
/**
* Abstract implementation of {@link TransactionAttributeSource} that caches
@@ -65,7 +65,7 @@ public abstract class AbstractFallbackTransactionAttributeSource implements Tran
protected final Log logger = LogFactory.getLog(getClass());
/**
- * Cache of TransactionAttributes, keyed by DefaultCacheKey (Method + target Class).
+ * Cache of TransactionAttributes, keyed by method on a specific target class.
* <p>As this base class is not marked Serializable, the cache will be recreated
* after serialization - provided that the concrete subclass is Serializable.
*/
@@ -123,7 +123,7 @@ public abstract class AbstractFallbackTransactionAttributeSource implements Tran
* @return the cache key (never {@code null})
*/
protected Object getCacheKey(Method method, Class<?> targetClass) {
- return new DefaultCacheKey(method, targetClass);
+ return new MethodClassKey(method, targetClass);
}
/**
@@ -155,7 +155,7 @@ public abstract class AbstractFallbackTransactionAttributeSource implements Tran
// Second try is the transaction attribute on the target class.
txAtt = findTransactionAttribute(specificMethod.getDeclaringClass());
- if (txAtt != null) {
+ if (txAtt != null && ClassUtils.isUserLevelMethod(method)) {
return txAtt;
}
@@ -166,8 +166,12 @@ public abstract class AbstractFallbackTransactionAttributeSource implements Tran
return txAtt;
}
// Last fallback is the class of the original method.
- return findTransactionAttribute(method.getDeclaringClass());
+ txAtt = findTransactionAttribute(method.getDeclaringClass());
+ if (txAtt != null && ClassUtils.isUserLevelMethod(method)) {
+ return txAtt;
+ }
}
+
return null;
}
@@ -199,38 +203,4 @@ public abstract class AbstractFallbackTransactionAttributeSource implements Tran
return false;
}
-
- /**
- * Default cache key for the TransactionAttribute cache.
- */
- private static class DefaultCacheKey {
-
- private final Method method;
-
- private final Class<?> targetClass;
-
- public DefaultCacheKey(Method method, Class<?> targetClass) {
- this.method = method;
- this.targetClass = targetClass;
- }
-
- @Override
- public boolean equals(Object other) {
- if (this == other) {
- return true;
- }
- if (!(other instanceof DefaultCacheKey)) {
- return false;
- }
- DefaultCacheKey otherKey = (DefaultCacheKey) other;
- return (this.method.equals(otherKey.method) &&
- ObjectUtils.nullSafeEquals(this.targetClass, otherKey.targetClass));
- }
-
- @Override
- public int hashCode() {
- return this.method.hashCode() + (this.targetClass != null ? this.targetClass.hashCode() * 29 : 0);
- }
- }
-
}
diff --git a/spring-tx/src/main/java/org/springframework/transaction/interceptor/DefaultTransactionAttribute.java b/spring-tx/src/main/java/org/springframework/transaction/interceptor/DefaultTransactionAttribute.java
index 8e016f25..e253aabd 100644
--- a/spring-tx/src/main/java/org/springframework/transaction/interceptor/DefaultTransactionAttribute.java
+++ b/spring-tx/src/main/java/org/springframework/transaction/interceptor/DefaultTransactionAttribute.java
@@ -57,7 +57,7 @@ public class DefaultTransactionAttribute extends DefaultTransactionDefinition im
}
/**
- * Create a new DefaultTransactionAttribute with the the given
+ * Create a new DefaultTransactionAttribute with the given
* propagation behavior. Can be modified through bean property setters.
* @param propagationBehavior one of the propagation constants in the
* TransactionDefinition interface
diff --git a/spring-tx/src/main/java/org/springframework/transaction/interceptor/RuleBasedTransactionAttribute.java b/spring-tx/src/main/java/org/springframework/transaction/interceptor/RuleBasedTransactionAttribute.java
index 0d45ed9b..a4444aa1 100644
--- a/spring-tx/src/main/java/org/springframework/transaction/interceptor/RuleBasedTransactionAttribute.java
+++ b/spring-tx/src/main/java/org/springframework/transaction/interceptor/RuleBasedTransactionAttribute.java
@@ -82,7 +82,7 @@ public class RuleBasedTransactionAttribute extends DefaultTransactionAttribute i
}
/**
- * Create a new DefaultTransactionAttribute with the the given
+ * Create a new DefaultTransactionAttribute with the given
* propagation behavior. Can be modified through bean property setters.
* @param propagationBehavior one of the propagation constants in the
* TransactionDefinition interface
diff --git a/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java b/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java
index 6888d46f..7f196227 100644
--- a/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java
+++ b/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,7 @@ package org.springframework.transaction.interceptor;
import java.lang.reflect.Method;
import java.util.Properties;
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -34,6 +34,7 @@ import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.transaction.support.CallbackPreferringPlatformTransactionManager;
import org.springframework.transaction.support.TransactionCallback;
+import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.StringUtils;
/**
@@ -67,13 +68,14 @@ import org.springframework.util.StringUtils;
*/
public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {
+ // NOTE: This class must not implement Serializable because it serves as base
+ // class for AspectJ aspects (which are not allowed to implement Serializable)!
+
+
/**
* Key to use to store the default transaction manager.
*/
- private final Object DEFAULT_TRANSACTION_MANAGER_KEY = new Object();
-
- // NOTE: This class must not implement Serializable because it serves as base
- // class for AspectJ aspects (which are not allowed to implement Serializable)!
+ private static final Object DEFAULT_TRANSACTION_MANAGER_KEY = new Object();
/**
* Holder to support the {@code currentTransactionStatus()} method,
@@ -85,9 +87,6 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init
new NamedThreadLocal<TransactionInfo>("Current aspect-driven transaction");
- private final ConcurrentHashMap<Object, PlatformTransactionManager> transactionManagerCache =
- new ConcurrentHashMap<Object, PlatformTransactionManager>();
-
/**
* Subclasses can use this to return the current TransactionInfo.
* Only subclasses that cannot handle all operations in one method,
@@ -127,15 +126,15 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init
protected final Log logger = LogFactory.getLog(getClass());
- /**
- * Default transaction manager bean name.
- */
private String transactionManagerBeanName;
private TransactionAttributeSource transactionAttributeSource;
private BeanFactory beanFactory;
+ private final ConcurrentMap<Object, PlatformTransactionManager> transactionManagerCache =
+ new ConcurrentReferenceHashMap<Object, PlatformTransactionManager>(4);
+
/**
* Specify the name of the default transaction manager bean.
@@ -243,7 +242,7 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init
public void afterPropertiesSet() {
if (getTransactionManager() == null && this.beanFactory == null) {
throw new IllegalStateException(
- "Setting the property 'transactionManager' or running in a ListableBeanFactory is required");
+ "Setting the property 'transactionManager' or running in a BeanFactory is required");
}
if (this.transactionAttributeSource == null) {
throw new IllegalStateException(
@@ -449,17 +448,16 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init
TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification);
if (txAttr != null) {
- // We need a transaction for this method
+ // We need a transaction for this method...
if (logger.isTraceEnabled()) {
logger.trace("Getting transaction for [" + txInfo.getJoinpointIdentification() + "]");
}
- // The transaction manager will flag an error if an incompatible tx already exists
+ // The transaction manager will flag an error if an incompatible tx already exists.
txInfo.newTransactionStatus(status);
}
else {
- // The TransactionInfo.hasTransaction() method will return
- // false. We created it only to preserve the integrity of
- // the ThreadLocal stack maintained in this class.
+ // The TransactionInfo.hasTransaction() method will return false. We created it only
+ // to preserve the integrity of the ThreadLocal stack maintained in this class.
if (logger.isTraceEnabled())
logger.trace("Don't need to create transaction for [" + joinpointIdentification +
"]: This method isn't transactional.");
diff --git a/spring-tx/src/main/java/org/springframework/transaction/jta/JtaTransactionManager.java b/spring-tx/src/main/java/org/springframework/transaction/jta/JtaTransactionManager.java
index 453453aa..4d46ebbc 100644
--- a/spring-tx/src/main/java/org/springframework/transaction/jta/JtaTransactionManager.java
+++ b/spring-tx/src/main/java/org/springframework/transaction/jta/JtaTransactionManager.java
@@ -911,6 +911,9 @@ public class JtaTransactionManager extends AbstractPlatformTransactionManager
protected void applyTimeout(JtaTransactionObject txObject, int timeout) throws SystemException {
if (timeout > TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getUserTransaction().setTransactionTimeout(timeout);
+ if (timeout > 0) {
+ txObject.resetTransactionTimeout = true;
+ }
}
}
@@ -1168,6 +1171,19 @@ public class JtaTransactionManager extends AbstractPlatformTransactionManager
}
}
+ @Override
+ protected void doCleanupAfterCompletion(Object transaction) {
+ JtaTransactionObject txObject = (JtaTransactionObject) transaction;
+ if (txObject.resetTransactionTimeout) {
+ try {
+ txObject.getUserTransaction().setTransactionTimeout(0);
+ }
+ catch (SystemException ex) {
+ logger.debug("Failed to reset transaction timeout after JTA completion", ex);
+ }
+ }
+ }
+
//---------------------------------------------------------------------
// Implementation of TransactionFactory interface
diff --git a/spring-tx/src/main/java/org/springframework/transaction/jta/JtaTransactionObject.java b/spring-tx/src/main/java/org/springframework/transaction/jta/JtaTransactionObject.java
index 50318222..fb021cf8 100644
--- a/spring-tx/src/main/java/org/springframework/transaction/jta/JtaTransactionObject.java
+++ b/spring-tx/src/main/java/org/springframework/transaction/jta/JtaTransactionObject.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +39,8 @@ public class JtaTransactionObject implements SmartTransactionObject {
private final UserTransaction userTransaction;
+ boolean resetTransactionTimeout = false;
+
/**
* Create a new JtaTransactionObject for the given JTA UserTransaction.
diff --git a/spring-tx/src/main/java/org/springframework/transaction/support/DefaultTransactionDefinition.java b/spring-tx/src/main/java/org/springframework/transaction/support/DefaultTransactionDefinition.java
index b83b0169..830e37ad 100644
--- a/spring-tx/src/main/java/org/springframework/transaction/support/DefaultTransactionDefinition.java
+++ b/spring-tx/src/main/java/org/springframework/transaction/support/DefaultTransactionDefinition.java
@@ -91,7 +91,7 @@ public class DefaultTransactionDefinition implements TransactionDefinition, Seri
}
/**
- * Create a new DefaultTransactionDefinition with the the given
+ * Create a new DefaultTransactionDefinition with the given
* propagation behavior. Can be modified through bean property setters.
* @param propagationBehavior one of the propagation constants in the
* TransactionDefinition interface
diff --git a/spring-tx/src/main/resources/META-INF/spring.schemas b/spring-tx/src/main/resources/META-INF/spring.schemas
index 35f4c40b..8ee24855 100644
--- a/spring-tx/src/main/resources/META-INF/spring.schemas
+++ b/spring-tx/src/main/resources/META-INF/spring.schemas
@@ -6,4 +6,5 @@ http\://www.springframework.org/schema/tx/spring-tx-3.2.xsd=org/springframework/
http\://www.springframework.org/schema/tx/spring-tx-4.0.xsd=org/springframework/transaction/config/spring-tx-4.0.xsd
http\://www.springframework.org/schema/tx/spring-tx-4.1.xsd=org/springframework/transaction/config/spring-tx-4.1.xsd
http\://www.springframework.org/schema/tx/spring-tx-4.2.xsd=org/springframework/transaction/config/spring-tx-4.2.xsd
-http\://www.springframework.org/schema/tx/spring-tx.xsd=org/springframework/transaction/config/spring-tx-4.2.xsd
+http\://www.springframework.org/schema/tx/spring-tx-4.3.xsd=org/springframework/transaction/config/spring-tx-4.3.xsd
+http\://www.springframework.org/schema/tx/spring-tx.xsd=org/springframework/transaction/config/spring-tx-4.3.xsd
diff --git a/spring-tx/src/main/resources/org/springframework/transaction/config/spring-tx-4.3.xsd b/spring-tx/src/main/resources/org/springframework/transaction/config/spring-tx-4.3.xsd
new file mode 100644
index 00000000..e93e34c6
--- /dev/null
+++ b/spring-tx/src/main/resources/org/springframework/transaction/config/spring-tx-4.3.xsd
@@ -0,0 +1,247 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+
+<xsd:schema xmlns="http://www.springframework.org/schema/tx"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xmlns:tool="http://www.springframework.org/schema/tool"
+ targetNamespace="http://www.springframework.org/schema/tx"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified">
+
+ <xsd:import namespace="http://www.springframework.org/schema/beans" schemaLocation="http://www.springframework.org/schema/beans/spring-beans-4.3.xsd"/>
+ <xsd:import namespace="http://www.springframework.org/schema/tool" schemaLocation="http://www.springframework.org/schema/tool/spring-tool-4.3.xsd"/>
+
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Defines the elements used in the Spring Framework's declarative
+ transaction management infrastructure.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+
+ <xsd:element name="advice">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.transaction.interceptor.TransactionInterceptor"><![CDATA[
+ Defines the transactional semantics of the AOP advice that is to be
+ executed.
+
+ That is, this advice element is where the transactional semantics of
+ any number of methods are defined (where transactional semantics
+ includes the propagation settings, the isolation level, the rollback
+ rules, and suchlike).
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation>
+ <tool:exports type="org.springframework.transaction.interceptor.TransactionInterceptor"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:complexContent>
+ <xsd:extension base="beans:identifiedType">
+ <xsd:sequence>
+ <xsd:element name="attributes" type="attributesType" minOccurs="0" maxOccurs="1"/>
+ </xsd:sequence>
+ <xsd:attribute name="transaction-manager" type="xsd:string" default="transactionManager">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.transaction.PlatformTransactionManager"><![CDATA[
+ The bean name of the PlatformTransactionManager that is to be used
+ to drive transactions.
+
+ This attribute is not required, and only needs to be specified
+ explicitly if the bean name of the desired PlatformTransactionManager
+ is not 'transactionManager'.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="org.springframework.transaction.PlatformTransactionManager"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="annotation-driven">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"><![CDATA[
+ Indicates that transaction configuration is defined by Java 5
+ annotations on bean classes, and that proxies are automatically
+ to be created for the relevant annotated beans.
+
+ The default annotations supported are Spring's @Transactional
+ and EJB3's @TransactionAttribute (if available).
+
+ Transaction semantics such as propagation settings, the isolation level,
+ the rollback rules, etc are all defined in the annotation metadata.
+
+ See org.springframework.transaction.annotation.EnableTransactionManagement Javadoc
+ for information on code-based alternatives to this XML element.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="transaction-manager" type="xsd:string" default="transactionManager">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.transaction.PlatformTransactionManager"><![CDATA[
+ The bean name of the PlatformTransactionManager that is to be used
+ to drive transactions.
+
+ This attribute is not required, and only needs to be specified
+ explicitly if the bean name of the desired PlatformTransactionManager
+ is not 'transactionManager'.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="org.springframework.transaction.PlatformTransactionManager"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="mode" default="proxy">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Should annotated beans be proxied using Spring's AOP framework,
+ or should they rather be weaved with an AspectJ transaction aspect?
+
+ AspectJ weaving requires spring-aspects.jar on the classpath,
+ as well as load-time weaving (or compile-time weaving) enabled.
+
+ Note: The weaving-based aspect requires the @Transactional annotation to be
+ defined on the concrete class. Annotations in interfaces will not work
+ in that case (they will rather only work with interface-based proxies)!
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="proxy"/>
+ <xsd:enumeration value="aspectj"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="proxy-target-class" type="xsd:boolean" default="false">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Are class-based (CGLIB) proxies to be created? By default, standard
+ Java interface-based proxies are created.
+
+ Note: Class-based proxies require the @Transactional annotation to be
+ defined on the concrete class. Annotations in interfaces will not work
+ in that case (they will rather only work with interface-based proxies)!
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="order" type="xsd:token">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.core.Ordered"><![CDATA[
+ Controls the ordering of the execution of the transaction advisor
+ when multiple advice executes at a specific joinpoint.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="jta-transaction-manager">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Creates a default JtaTransactionManager bean with name "transactionManager",
+ matching the default bean name expected by the "annotation-driven" tag.
+ Automatically detects WebLogic and WebSphere: creating a WebLogicJtaTransactionManager
+ or WebSphereUowTransactionManager, respectively.
+
+ For customization needs, consider defining a JtaTransactionManager bean as a regular
+ Spring bean definition with name "transactionManager", replacing this element.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation>
+ <tool:exports type="org.springframework.transaction.jta.JtaTransactionManager"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:element>
+
+ <xsd:complexType name="attributesType">
+ <xsd:sequence>
+ <xsd:element name="method" minOccurs="1" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:attribute name="name" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The method name(s) with which the transaction attributes are to be
+ associated. The wildcard (*) character can be used to associate the
+ same transaction attribute settings with a number of methods; for
+ example, 'get*', 'handle*', '*Order', 'on*Event', etc.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="propagation" default="REQUIRED">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.transaction.annotation.Propagation"><![CDATA[
+ The transaction propagation behavior.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="REQUIRED"/>
+ <xsd:enumeration value="SUPPORTS"/>
+ <xsd:enumeration value="MANDATORY"/>
+ <xsd:enumeration value="REQUIRES_NEW"/>
+ <xsd:enumeration value="NOT_SUPPORTED"/>
+ <xsd:enumeration value="NEVER"/>
+ <xsd:enumeration value="NESTED"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="isolation" default="DEFAULT">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.transaction.annotation.Isolation"><![CDATA[
+ The transaction isolation level.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="DEFAULT"/>
+ <xsd:enumeration value="READ_UNCOMMITTED"/>
+ <xsd:enumeration value="READ_COMMITTED"/>
+ <xsd:enumeration value="REPEATABLE_READ"/>
+ <xsd:enumeration value="SERIALIZABLE"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="timeout" type="xsd:int" default="-1">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The transaction timeout value (in seconds).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="read-only" type="xsd:boolean" default="false">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Is this transaction read-only?
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="rollback-for" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The Exception(s) that will trigger rollback; comma-delimited.
+ For example, 'com.foo.MyBusinessException,ServletException'
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="no-rollback-for" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The Exception(s) that will *not* trigger rollback; comma-delimited.
+ For example, 'com.foo.MyBusinessException,ServletException'
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+</xsd:schema>
diff --git a/spring-tx/src/test/java/org/springframework/dao/annotation/PersistenceExceptionTranslationPostProcessorTests.java b/spring-tx/src/test/java/org/springframework/dao/annotation/PersistenceExceptionTranslationPostProcessorTests.java
index 39c2bf78..968eab46 100644
--- a/spring-tx/src/test/java/org/springframework/dao/annotation/PersistenceExceptionTranslationPostProcessorTests.java
+++ b/spring-tx/src/test/java/org/springframework/dao/annotation/PersistenceExceptionTranslationPostProcessorTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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 org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.junit.Test;
+
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator;
import org.springframework.aop.framework.Advised;
@@ -138,7 +139,7 @@ public class PersistenceExceptionTranslationPostProcessorTests {
@Aspect
public static class LogAllAspect {
- @Before("execution(void *.additionalMethod())")
+ @Before("execution(void *.additionalMethod(*))")
public void log(JoinPoint jp) {
System.out.println("Before " + jp.getSignature().getName());
}
diff --git a/spring-tx/src/test/java/org/springframework/transaction/annotation/AnnotationTransactionAttributeSourceTests.java b/spring-tx/src/test/java/org/springframework/transaction/annotation/AnnotationTransactionAttributeSourceTests.java
index ce346859..5e20987d 100644
--- a/spring-tx/src/test/java/org/springframework/transaction/annotation/AnnotationTransactionAttributeSourceTests.java
+++ b/spring-tx/src/test/java/org/springframework/transaction/annotation/AnnotationTransactionAttributeSourceTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,9 +21,10 @@ import java.io.Serializable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
-
import javax.ejb.TransactionAttributeType;
+import groovy.lang.GroovyObject;
+import groovy.lang.MetaClass;
import org.junit.Test;
import org.springframework.aop.framework.Advised;
@@ -54,7 +55,7 @@ public class AnnotationTransactionAttributeSourceTests {
TransactionInterceptor ti = new TransactionInterceptor(ptm, tas);
ProxyFactory proxyFactory = new ProxyFactory();
- proxyFactory.setInterfaces(new Class[] {ITestBean.class});
+ proxyFactory.setInterfaces(ITestBean.class);
proxyFactory.addAdvice(ti);
proxyFactory.setTarget(tb);
ITestBean proxy = (ITestBean) proxyFactory.getProxy();
@@ -369,6 +370,20 @@ public class AnnotationTransactionAttributeSourceTests {
assertEquals(TransactionAttribute.PROPAGATION_SUPPORTS, getNameAttr.getPropagationBehavior());
}
+ @Test
+ public void transactionAttributeDeclaredOnGroovyClass() throws Exception {
+ Method getAgeMethod = ITestBean.class.getMethod("getAge");
+ Method getNameMethod = ITestBean.class.getMethod("getName");
+ Method getMetaClassMethod = GroovyObject.class.getMethod("getMetaClass");
+
+ AnnotationTransactionAttributeSource atas = new AnnotationTransactionAttributeSource();
+ TransactionAttribute getAgeAttr = atas.getTransactionAttribute(getAgeMethod, GroovyTestBean.class);
+ assertEquals(TransactionAttribute.PROPAGATION_REQUIRED, getAgeAttr.getPropagationBehavior());
+ TransactionAttribute getNameAttr = atas.getTransactionAttribute(getNameMethod, GroovyTestBean.class);
+ assertEquals(TransactionAttribute.PROPAGATION_REQUIRED, getNameAttr.getPropagationBehavior());
+ assertNull(atas.getTransactionAttribute(getMetaClassMethod, GroovyTestBean.class));
+ }
+
interface ITestBean {
@@ -470,7 +485,7 @@ public class AnnotationTransactionAttributeSourceTests {
}
@Override
- @Transactional(rollbackFor=Exception.class)
+ @Transactional(rollbackFor = Exception.class)
public int getAge() {
return age;
}
@@ -543,8 +558,8 @@ public class AnnotationTransactionAttributeSourceTests {
}
@Override
- @Transactional(propagation=Propagation.REQUIRES_NEW, isolation=Isolation.REPEATABLE_READ, timeout=5,
- readOnly=true, rollbackFor=Exception.class, noRollbackFor={IOException.class})
+ @Transactional(propagation = Propagation.REQUIRES_NEW, isolation=Isolation.REPEATABLE_READ,
+ timeout = 5, readOnly = true, rollbackFor = Exception.class, noRollbackFor = IOException.class)
public int getAge() {
return age;
}
@@ -556,7 +571,7 @@ public class AnnotationTransactionAttributeSourceTests {
}
- @Transactional(rollbackFor=Exception.class, noRollbackFor={IOException.class})
+ @Transactional(rollbackFor = Exception.class, noRollbackFor = IOException.class)
static class TestBean4 implements ITestBean3 {
private String name;
@@ -594,7 +609,7 @@ public class AnnotationTransactionAttributeSourceTests {
@Retention(RetentionPolicy.RUNTIME)
- @Transactional(rollbackFor=Exception.class, noRollbackFor={IOException.class})
+ @Transactional(rollbackFor = Exception.class, noRollbackFor = IOException.class)
@interface Tx {
}
@@ -618,13 +633,13 @@ public class AnnotationTransactionAttributeSourceTests {
@Retention(RetentionPolicy.RUNTIME)
- @Transactional(rollbackFor=Exception.class, noRollbackFor={IOException.class})
+ @Transactional(rollbackFor = Exception.class, noRollbackFor = IOException.class)
@interface TxWithAttribute {
boolean readOnly();
}
- @TxWithAttribute(readOnly=true)
+ @TxWithAttribute(readOnly = true)
static class TestBean7 {
public int getAge() {
@@ -641,11 +656,14 @@ public class AnnotationTransactionAttributeSourceTests {
}
}
+
@TxWithAttribute(readOnly = true)
interface TestInterface9 {
+
int getAge();
}
+
static class TestBean9 implements TestInterface9 {
@Override
@@ -654,12 +672,14 @@ public class AnnotationTransactionAttributeSourceTests {
}
}
+
interface TestInterface10 {
- @TxWithAttribute(readOnly=true)
+ @TxWithAttribute(readOnly = true)
int getAge();
}
+
static class TestBean10 implements TestInterface10 {
@Override
@@ -888,4 +908,56 @@ public class AnnotationTransactionAttributeSourceTests {
}
}
+
+ @Transactional
+ static class GroovyTestBean implements ITestBean, GroovyObject {
+
+ private String name;
+
+ private int age;
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public int getAge() {
+ return age;
+ }
+
+ @Override
+ public void setAge(int age) {
+ this.age = age;
+ }
+
+ @Override
+ public Object invokeMethod(String name, Object args) {
+ return null;
+ }
+
+ @Override
+ public Object getProperty(String propertyName) {
+ return null;
+ }
+
+ @Override
+ public void setProperty(String propertyName, Object newValue) {
+ }
+
+ @Override
+ public MetaClass getMetaClass() {
+ return null;
+ }
+
+ @Override
+ public void setMetaClass(MetaClass metaClass) {
+ }
+ }
+
}
diff --git a/spring-tx/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementTests.java b/spring-tx/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementTests.java
index d413269d..4c301fea 100644
--- a/spring-tx/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementTests.java
+++ b/spring-tx/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementTests.java
@@ -94,6 +94,7 @@ public class EnableTransactionManagementTests {
* get loaded -- or in this case, attempted to be loaded at which point the test fails.
*/
@Test
+ @SuppressWarnings("resource")
public void proxyTypeAspectJCausesRegistrationOfAnnotationTransactionAspect() {
try {
new AnnotationConfigApplicationContext(EnableAspectJTxConfig.class, TxManagerConfig.class);
diff --git a/spring-tx/src/test/java/org/springframework/transaction/config/AnnotationDrivenTests.java b/spring-tx/src/test/java/org/springframework/transaction/config/AnnotationDrivenTests.java
index 3e561c04..f58d648c 100644
--- a/spring-tx/src/test/java/org/springframework/transaction/config/AnnotationDrivenTests.java
+++ b/spring-tx/src/test/java/org/springframework/transaction/config/AnnotationDrivenTests.java
@@ -20,10 +20,10 @@ import java.io.Serializable;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
-
import org.junit.Test;
import org.springframework.aop.support.AopUtils;
+import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
@@ -47,8 +47,16 @@ public class AnnotationDrivenTests {
@Test
public void withConfigurationClass() throws Exception {
+ ApplicationContext parent = new AnnotationConfigApplicationContext(TransactionManagerConfiguration.class);
+ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"annotationDrivenConfigurationClassTests.xml"}, getClass(), parent);
+ doTestWithMultipleTransactionManagers(context);
+ }
+
+ @Test
+ public void withAnnotatedTransactionManagers() throws Exception {
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext();
- parent.register(TransactionManagerConfiguration.class);
+ parent.registerBeanDefinition("transactionManager1", new RootBeanDefinition(SynchTransactionManager.class));
+ parent.registerBeanDefinition("transactionManager2", new RootBeanDefinition(NoSynchTransactionManager.class));
parent.refresh();
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"annotationDrivenConfigurationClassTests.xml"}, getClass(), parent);
doTestWithMultipleTransactionManagers(context);
diff --git a/spring-tx/src/test/java/org/springframework/transaction/config/NoSynch.java b/spring-tx/src/test/java/org/springframework/transaction/config/NoSynch.java
new file mode 100644
index 00000000..828e835e
--- /dev/null
+++ b/spring-tx/src/test/java/org/springframework/transaction/config/NoSynch.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2002-2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.transaction.config;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+
+/**
+ * @author Juergen Hoeller
+ */
+@Qualifier("noSynch")
+@Retention(RetentionPolicy.RUNTIME)
+public @interface NoSynch {
+
+}
diff --git a/spring-tx/src/test/java/org/springframework/transaction/config/NoSynchTransactionManager.java b/spring-tx/src/test/java/org/springframework/transaction/config/NoSynchTransactionManager.java
new file mode 100644
index 00000000..1f6ec382
--- /dev/null
+++ b/spring-tx/src/test/java/org/springframework/transaction/config/NoSynchTransactionManager.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2002-2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.transaction.config;
+
+import org.springframework.tests.transaction.CallCountingTransactionManager;
+
+/**
+ * @author Juergen Hoeller
+ */
+@NoSynch
+@SuppressWarnings("serial")
+public class NoSynchTransactionManager extends CallCountingTransactionManager {
+
+ public NoSynchTransactionManager() {
+ setTransactionSynchronization(CallCountingTransactionManager.SYNCHRONIZATION_NEVER);
+ }
+
+}
diff --git a/spring-tx/src/test/java/org/springframework/transaction/config/SynchTransactionManager.java b/spring-tx/src/test/java/org/springframework/transaction/config/SynchTransactionManager.java
new file mode 100644
index 00000000..65c4496b
--- /dev/null
+++ b/spring-tx/src/test/java/org/springframework/transaction/config/SynchTransactionManager.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2002-2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.transaction.config;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.tests.transaction.CallCountingTransactionManager;
+
+/**
+ * @author Juergen Hoeller
+ */
+@Qualifier("synch")
+@SuppressWarnings("serial")
+public class SynchTransactionManager extends CallCountingTransactionManager {
+
+}
diff --git a/spring-tx/src/test/java/org/springframework/transaction/config/TransactionManagerConfiguration.java b/spring-tx/src/test/java/org/springframework/transaction/config/TransactionManagerConfiguration.java
index 965a0db0..fb3b21f4 100644
--- a/spring-tx/src/test/java/org/springframework/transaction/config/TransactionManagerConfiguration.java
+++ b/spring-tx/src/test/java/org/springframework/transaction/config/TransactionManagerConfiguration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,7 +35,7 @@ public class TransactionManagerConfiguration {
}
@Bean
- @Qualifier("noSynch")
+ @NoSynch
public PlatformTransactionManager transactionManager2() {
CallCountingTransactionManager tm = new CallCountingTransactionManager();
tm.setTransactionSynchronization(CallCountingTransactionManager.SYNCHRONIZATION_NEVER);
diff --git a/spring-tx/src/test/java/org/springframework/transaction/event/ApplicationListenerMethodTransactionalAdapterTests.java b/spring-tx/src/test/java/org/springframework/transaction/event/ApplicationListenerMethodTransactionalAdapterTests.java
index 901398f3..5ed470b9 100644
--- a/spring-tx/src/test/java/org/springframework/transaction/event/ApplicationListenerMethodTransactionalAdapterTests.java
+++ b/spring-tx/src/test/java/org/springframework/transaction/event/ApplicationListenerMethodTransactionalAdapterTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +25,7 @@ import org.junit.rules.ExpectedException;
import org.springframework.context.PayloadApplicationEvent;
import org.springframework.context.event.ApplicationListenerMethodAdapter;
import org.springframework.core.ResolvableType;
+import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.util.ReflectionUtils;
import static org.junit.Assert.*;
@@ -37,15 +38,6 @@ public class ApplicationListenerMethodTransactionalAdapterTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
- @Test
- public void noAnnotation() {
- Method m = ReflectionUtils.findMethod(SampleEvents.class,
- "noAnnotation", String.class);
-
- thrown.expect(IllegalStateException.class);
- thrown.expectMessage("noAnnotation");
- ApplicationListenerMethodTransactionalAdapter.findAnnotation(m);
- }
@Test
public void defaultPhase() {
@@ -78,7 +70,8 @@ public class ApplicationListenerMethodTransactionalAdapterTests {
private void assertPhase(Method method, TransactionPhase expected) {
assertNotNull("Method must not be null", method);
- TransactionalEventListener annotation = ApplicationListenerMethodTransactionalAdapter.findAnnotation(method);
+ TransactionalEventListener annotation =
+ AnnotatedElementUtils.findMergedAnnotation(method, TransactionalEventListener.class);
assertEquals("Wrong phase for '" + method + "'", expected, annotation.phase());
}
@@ -96,10 +89,8 @@ public class ApplicationListenerMethodTransactionalAdapterTests {
return ResolvableType.forClassWithGenerics(PayloadApplicationEvent.class, payloadType);
}
- static class SampleEvents {
- public void noAnnotation(String data) {
- }
+ static class SampleEvents {
@TransactionalEventListener
public void defaultPhase(String data) {
@@ -117,7 +108,6 @@ public class ApplicationListenerMethodTransactionalAdapterTests {
@TransactionalEventListener(String.class)
public void valueSet() {
}
-
}
}
diff --git a/spring-web/src/main/java/org/springframework/http/CacheControl.java b/spring-web/src/main/java/org/springframework/http/CacheControl.java
index a291e7ae..c9933344 100644
--- a/spring-web/src/main/java/org/springframework/http/CacheControl.java
+++ b/spring-web/src/main/java/org/springframework/http/CacheControl.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -65,6 +65,10 @@ public class CacheControl {
private boolean proxyRevalidate = false;
+ private long staleWhileRevalidate = -1;
+
+ private long staleIfError = -1;
+
private long sMaxAge = -1;
@@ -110,7 +114,7 @@ public class CacheControl {
* clients sending conditional requests (with "ETag", "If-Modified-Since" headers) and the server responding
* with "304 - Not Modified" status.
* <p>In order to disable caching and minimize requests/responses exchanges, the {@link #noStore()} directive
- * should be used.
+ * should be used instead of {@link #noCache()}.
* @return {@code this}, to facilitate method chaining
* @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2.2.2">rfc7234 section 5.2.2.2</a>
*/
@@ -207,6 +211,36 @@ public class CacheControl {
return this;
}
+ /**
+ * Add a "stale-while-revalidate" directive.
+ * <p>This directive indicates that caches MAY serve the response in
+ * which it appears after it becomes stale, up to the indicated number of seconds.
+ * If a cached response is served stale due to the presence of this extension,
+ * the cache SHOULD attempt to revalidate it while still serving stale responses (i.e., without blocking).
+ * @param staleWhileRevalidate the maximum time the response should be used while being revalidated
+ * @param unit the time unit of the {@code staleWhileRevalidate} argument
+ * @return {@code this}, to facilitate method chaining
+ * @see <a href="https://tools.ietf.org/html/rfc5861#section-3">rfc5861 section 3</a>
+ */
+ public CacheControl staleWhileRevalidate(long staleWhileRevalidate, TimeUnit unit) {
+ this.staleWhileRevalidate = unit.toSeconds(staleWhileRevalidate);
+ return this;
+ }
+
+ /**
+ * Add a "stale-if-error" directive.
+ * <p>This directive indicates that when an error is encountered, a cached stale response MAY be used to satisfy
+ * the request, regardless of other freshness information.
+ * @param staleIfError the maximum time the response should be used when errors are encountered
+ * @param unit the time unit of the {@code staleIfError} argument
+ * @return {@code this}, to facilitate method chaining
+ * @see <a href="https://tools.ietf.org/html/rfc5861#section-4">rfc5861 section 4</a>
+ */
+ public CacheControl staleIfError(long staleIfError, TimeUnit unit) {
+ this.staleIfError = unit.toSeconds(staleIfError);
+ return this;
+ }
+
/**
* Return the "Cache-Control" header value.
@@ -241,6 +275,13 @@ public class CacheControl {
if (this.sMaxAge != -1) {
appendDirective(ccValue, "s-maxage=" + Long.toString(this.sMaxAge));
}
+ if (this.staleIfError != -1) {
+ appendDirective(ccValue, "stale-if-error=" + Long.toString(this.staleIfError));
+ }
+ if (this.staleWhileRevalidate != -1) {
+ appendDirective(ccValue, "stale-while-revalidate=" + Long.toString(this.staleWhileRevalidate));
+ }
+
String ccHeaderValue = ccValue.toString();
return (StringUtils.hasText(ccHeaderValue) ? ccHeaderValue : null);
}
diff --git a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java
index 1170bb06..0ba9a02a 100644
--- a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java
+++ b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java
@@ -34,6 +34,8 @@ import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import org.springframework.util.Assert;
import org.springframework.util.LinkedCaseInsensitiveMap;
@@ -55,6 +57,8 @@ import org.springframework.util.StringUtils;
*
* @author Arjen Poutsma
* @author Sebastien Deleuze
+ * @author Brian Clozel
+ * @author Juergen Hoeller
* @since 3.0
*/
public class HttpHeaders implements MultiValueMap<String, String>, Serializable {
@@ -372,6 +376,12 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
"EEE MMM dd HH:mm:ss yyyy"
};
+ /**
+ * Pattern matching ETag multiple field values in headers such as "If-Match", "If-None-Match"
+ * @see <a href="https://tools.ietf.org/html/rfc7232#section-2.3">Section 2.3 of RFC 7232</a>
+ */
+ private static final Pattern ETAG_HEADER_VALUE_PATTERN = Pattern.compile("\\*|\\s*((W\\/)?(\"[^\"]*\"))\\s*,?");
+
private static TimeZone GMT = TimeZone.getTimeZone("GMT");
@@ -419,19 +429,7 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
* <p>Returns an empty list when the acceptable media types are unspecified.
*/
public List<MediaType> getAccept() {
- String value = getFirst(ACCEPT);
- List<MediaType> result = (value != null ? MediaType.parseMediaTypes(value) : Collections.<MediaType>emptyList());
-
- // Some containers parse 'Accept' into multiple values
- if (result.size() == 1) {
- List<String> acceptHeader = get(ACCEPT);
- if (acceptHeader.size() > 1) {
- value = StringUtils.collectionToCommaDelimitedString(acceptHeader);
- result = MediaType.parseMediaTypes(value);
- }
- }
-
- return result;
+ return MediaType.parseMediaTypes(get(ACCEPT));
}
/**
@@ -442,10 +440,10 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
}
/**
- * Returns the value of the {@code Access-Control-Allow-Credentials} response header.
+ * Return the value of the {@code Access-Control-Allow-Credentials} response header.
*/
public boolean getAccessControlAllowCredentials() {
- return new Boolean(getFirst(ACCESS_CONTROL_ALLOW_CREDENTIALS));
+ return Boolean.parseBoolean(getFirst(ACCESS_CONTROL_ALLOW_CREDENTIALS));
}
/**
@@ -456,10 +454,10 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
}
/**
- * Returns the value of the {@code Access-Control-Allow-Headers} response header.
+ * Return the value of the {@code Access-Control-Allow-Headers} response header.
*/
public List<String> getAccessControlAllowHeaders() {
- return getFirstValueAsList(ACCESS_CONTROL_ALLOW_HEADERS);
+ return getValuesAsList(ACCESS_CONTROL_ALLOW_HEADERS);
}
/**
@@ -476,7 +474,7 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
List<HttpMethod> result = new ArrayList<HttpMethod>();
String value = getFirst(ACCESS_CONTROL_ALLOW_METHODS);
if (value != null) {
- String[] tokens = value.split(",\\s*");
+ String[] tokens = StringUtils.tokenizeToStringArray(value, ",", true, true);
for (String token : tokens) {
HttpMethod resolved = HttpMethod.resolve(token);
if (resolved != null) {
@@ -498,7 +496,7 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
* Return the value of the {@code Access-Control-Allow-Origin} response header.
*/
public String getAccessControlAllowOrigin() {
- return getFirst(ACCESS_CONTROL_ALLOW_ORIGIN);
+ return getFieldValues(ACCESS_CONTROL_ALLOW_ORIGIN);
}
/**
@@ -509,10 +507,10 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
}
/**
- * Returns the value of the {@code Access-Control-Expose-Headers} response header.
+ * Return the value of the {@code Access-Control-Expose-Headers} response header.
*/
public List<String> getAccessControlExposeHeaders() {
- return getFirstValueAsList(ACCESS_CONTROL_EXPOSE_HEADERS);
+ return getValuesAsList(ACCESS_CONTROL_EXPOSE_HEADERS);
}
/**
@@ -523,7 +521,7 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
}
/**
- * Returns the value of the {@code Access-Control-Max-Age} response header.
+ * Return the value of the {@code Access-Control-Max-Age} response header.
* <p>Returns -1 when the max age is unknown.
*/
public long getAccessControlMaxAge() {
@@ -539,10 +537,10 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
}
/**
- * Returns the value of the {@code Access-Control-Request-Headers} request header.
+ * Return the value of the {@code Access-Control-Request-Headers} request header.
*/
public List<String> getAccessControlRequestHeaders() {
- return getFirstValueAsList(ACCESS_CONTROL_REQUEST_HEADERS);
+ return getValuesAsList(ACCESS_CONTROL_REQUEST_HEADERS);
}
/**
@@ -643,7 +641,7 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
* Return the value of the {@code Cache-Control} header.
*/
public String getCacheControl() {
- return getFirst(CACHE_CONTROL);
+ return getFieldValues(CACHE_CONTROL);
}
/**
@@ -664,7 +662,7 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
* Return the value of the {@code Connection} header.
*/
public List<String> getConnection() {
- return getFirstValueAsList(CONNECTION);
+ return getValuesAsList(CONNECTION);
}
/**
@@ -783,6 +781,30 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
}
/**
+ * Set the (new) value of the {@code If-Match} header.
+ * @since 4.3
+ */
+ public void setIfMatch(String ifMatch) {
+ set(IF_MATCH, ifMatch);
+ }
+
+ /**
+ * Set the (new) value of the {@code If-Match} header.
+ * @since 4.3
+ */
+ public void setIfMatch(List<String> ifMatchList) {
+ set(IF_MATCH, toCommaDelimitedString(ifMatchList));
+ }
+
+ /**
+ * Return the value of the {@code If-Match} header.
+ * @since 4.3
+ */
+ public List<String> getIfMatch() {
+ return getETagValuesAsList(IF_MATCH);
+ }
+
+ /**
* Set the (new) value of the {@code If-Modified-Since} header.
* <p>The date should be specified as the number of milliseconds since
* January 1, 1970 GMT.
@@ -814,35 +836,31 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
set(IF_NONE_MATCH, toCommaDelimitedString(ifNoneMatchList));
}
- protected String toCommaDelimitedString(List<String> list) {
- StringBuilder builder = new StringBuilder();
- for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
- String ifNoneMatch = iterator.next();
- builder.append(ifNoneMatch);
- if (iterator.hasNext()) {
- builder.append(", ");
- }
- }
- return builder.toString();
- }
-
/**
* Return the value of the {@code If-None-Match} header.
*/
public List<String> getIfNoneMatch() {
- return getFirstValueAsList(IF_NONE_MATCH);
+ return getETagValuesAsList(IF_NONE_MATCH);
}
- protected List<String> getFirstValueAsList(String header) {
- List<String> result = new ArrayList<String>();
- String value = getFirst(header);
- if (value != null) {
- String[] tokens = value.split(",\\s*");
- for (String token : tokens) {
- result.add(token);
- }
- }
- return result;
+ /**
+ * Set the (new) value of the {@code If-Unmodified-Since} header.
+ * <p>The date should be specified as the number of milliseconds since
+ * January 1, 1970 GMT.
+ * @since 4.3
+ */
+ public void setIfUnmodifiedSince(long ifUnmodifiedSince) {
+ setDate(IF_UNMODIFIED_SINCE, ifUnmodifiedSince);
+ }
+
+ /**
+ * Return the value of the {@code If-Unmodified-Since} header.
+ * <p>The date is returned as the number of milliseconds since
+ * January 1, 1970 GMT. Returns -1 when the date is unknown.
+ * @since 4.3
+ */
+ public long getIfUnmodifiedSince() {
+ return getFirstDate(IF_UNMODIFIED_SINCE, false);
}
/**
@@ -943,11 +961,43 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
}
/**
+ * Set the request header names (e.g. "Accept-Language") for which the
+ * response is subject to content negotiation and variances based on the
+ * value of those request headers.
+ * @param requestHeaders the request header names
+ * @since 4.3
+ */
+ public void setVary(List<String> requestHeaders) {
+ set(VARY, toCommaDelimitedString(requestHeaders));
+ }
+
+ /**
+ * Return the request header names subject to content negotiation.
+ * @since 4.3
+ */
+ public List<String> getVary() {
+ return getValuesAsList(VARY);
+ }
+
+ /**
+ * Set the given date under the given header name after formatting it as a string
+ * using the pattern {@code "EEE, dd MMM yyyy HH:mm:ss zzz"}. The equivalent of
+ * {@link #set(String, String)} but for date headers.
+ * @since 3.2.4
+ */
+ public void setDate(String headerName, long date) {
+ SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMATS[0], Locale.US);
+ dateFormat.setTimeZone(GMT);
+ set(headerName, dateFormat.format(new Date(date)));
+ }
+
+ /**
* Parse the first header value for the given header name as a date,
* return -1 if there is no value, or raise {@link IllegalArgumentException}
* if the value cannot be parsed as a date.
* @param headerName the header name
* @return the parsed date header, or -1 if none
+ * @since 3.2.4
*/
public long getFirstDate(String headerName) {
return getFirstDate(headerName, true);
@@ -992,16 +1042,92 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
}
/**
- * Set the given date under the given header name after formatting it as a string
- * using the pattern {@code "EEE, dd MMM yyyy HH:mm:ss zzz"}. The equivalent of
- * {@link #set(String, String)} but for date headers.
+ * Return all values of a given header name,
+ * even if this header is set multiple times.
+ * @param headerName the header name
+ * @return all associated values
+ * @since 4.3
+ */
+ public List<String> getValuesAsList(String headerName) {
+ List<String> values = get(headerName);
+ if (values != null) {
+ List<String> result = new ArrayList<String>();
+ for (String value : values) {
+ if (value != null) {
+ String[] tokens = StringUtils.tokenizeToStringArray(value, ",");
+ for (String token : tokens) {
+ result.add(token);
+ }
+ }
+ }
+ return result;
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Retrieve a combined result from the field values of the ETag header.
+ * @param headerName the header name
+ * @return the combined result
+ * @since 4.3
+ */
+ protected List<String> getETagValuesAsList(String headerName) {
+ List<String> values = get(headerName);
+ if (values != null) {
+ List<String> result = new ArrayList<String>();
+ for (String value : values) {
+ if (value != null) {
+ Matcher matcher = ETAG_HEADER_VALUE_PATTERN.matcher(value);
+ while (matcher.find()) {
+ if ("*".equals(matcher.group())) {
+ result.add(matcher.group());
+ }
+ else {
+ result.add(matcher.group(1));
+ }
+ }
+ if (result.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Could not parse header '" + headerName + "' with value '" + value + "'");
+ }
+ }
+ }
+ return result;
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Retrieve a combined result from the field values of multi-valued headers.
+ * @param headerName the header name
+ * @return the combined result
+ * @since 4.3
*/
- public void setDate(String headerName, long date) {
- SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMATS[0], Locale.US);
- dateFormat.setTimeZone(GMT);
- set(headerName, dateFormat.format(new Date(date)));
+ protected String getFieldValues(String headerName) {
+ List<String> headerValues = get(headerName);
+ return (headerValues != null ? toCommaDelimitedString(headerValues) : null);
+ }
+
+ /**
+ * Turn the given list of header values into a comma-delimited result.
+ * @param headerValues the list of header values
+ * @return a combined result with comma delimitation
+ */
+ protected String toCommaDelimitedString(List<String> headerValues) {
+ StringBuilder builder = new StringBuilder();
+ for (Iterator<String> it = headerValues.iterator(); it.hasNext(); ) {
+ String val = it.next();
+ builder.append(val);
+ if (it.hasNext()) {
+ builder.append(", ");
+ }
+ }
+ return builder.toString();
}
+
+ // MultiValueMap implementation
+
/**
* Return the first header value for the given header name, if any.
* @param headerName the header name
diff --git a/spring-web/src/main/java/org/springframework/http/HttpMethod.java b/spring-web/src/main/java/org/springframework/http/HttpMethod.java
index 87173fd3..c38c46fc 100644
--- a/spring-web/src/main/java/org/springframework/http/HttpMethod.java
+++ b/spring-web/src/main/java/org/springframework/http/HttpMethod.java
@@ -61,7 +61,7 @@ public enum HttpMethod {
* @since 4.2.4
*/
public boolean matches(String method) {
- return name().equals(method);
+ return (this == resolve(method));
}
}
diff --git a/spring-web/src/main/java/org/springframework/http/HttpRange.java b/spring-web/src/main/java/org/springframework/http/HttpRange.java
index 29f2e675..63d40e69 100644
--- a/spring-web/src/main/java/org/springframework/http/HttpRange.java
+++ b/spring-web/src/main/java/org/springframework/http/HttpRange.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,12 +16,16 @@
package org.springframework.http;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
+import org.springframework.core.io.InputStreamResource;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.ResourceRegion;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
@@ -42,6 +46,30 @@ public abstract class HttpRange {
/**
+ * Turn a {@code Resource} into a {@link ResourceRegion} using the range
+ * information contained in the current {@code HttpRange}.
+ * @param resource the {@code Resource} to select the region from
+ * @return the selected region of the given {@code Resource}
+ * @since 4.3
+ */
+ public ResourceRegion toResourceRegion(Resource resource) {
+ // Don't try to determine contentLength on InputStreamResource - cannot be read afterwards...
+ // Note: custom InputStreamResource subclasses could provide a pre-calculated content length!
+ Assert.isTrue(InputStreamResource.class != resource.getClass(),
+ "Can't convert an InputStreamResource to a ResourceRegion");
+ try {
+ long contentLength = resource.contentLength();
+ Assert.isTrue(contentLength > 0, "Resource content length should be > 0");
+ long start = getRangeStart(contentLength);
+ long end = getRangeEnd(contentLength);
+ return new ResourceRegion(resource, start, end - start + 1);
+ }
+ catch (IOException ex) {
+ throw new IllegalArgumentException("Failed to convert Resource to ResourceRegion", ex);
+ }
+ }
+
+ /**
* Return the start of the range given the total length of a representation.
* @param length the length of the representation
* @return the start of this range for the representation
@@ -134,6 +162,25 @@ public abstract class HttpRange {
}
/**
+ * Convert each {@code HttpRange} into a {@code ResourceRegion},
+ * selecting the appropriate segment of the given {@code Resource}
+ * using the HTTP Range information.
+ * @param ranges the list of ranges
+ * @param resource the resource to select the regions from
+ * @return the list of regions for the given resource
+ */
+ public static List<ResourceRegion> toResourceRegions(List<HttpRange> ranges, Resource resource) {
+ if(ranges == null || ranges.size() == 0) {
+ return Collections.emptyList();
+ }
+ List<ResourceRegion> regions = new ArrayList<ResourceRegion>(ranges.size());
+ for(HttpRange range : ranges) {
+ regions.add(range.toResourceRegion(resource));
+ }
+ return regions;
+ }
+
+ /**
* Return a string representation of the given list of {@code HttpRange} objects.
* <p>This method can be used to for an {@code Range} header.
* @param ranges the ranges to create a string of
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 d0f9b7c4..2c864624 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-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,12 +17,14 @@
package org.springframework.http;
/**
- * Java 5 enumeration of HTTP status codes.
+ * Enumeration of HTTP status codes.
*
* <p>The HTTP status code series can be retrieved via {@link #series()}.
*
* @author Arjen Poutsma
* @author Sebastien Deleuze
+ * @author Brian Clozel
+ * @since 3.0
* @see HttpStatus.Series
* @see <a href="http://www.iana.org/assignments/http-status-codes">HTTP Status Code Registry</a>
* @see <a href="http://en.wikipedia.org/wiki/List_of_HTTP_status_codes">List of HTTP status codes - Wikipedia</a>
@@ -321,6 +323,13 @@ public enum HttpStatus {
* @see <a href="http://tools.ietf.org/html/rfc6585#section-5">Additional HTTP Status Codes</a>
*/
REQUEST_HEADER_FIELDS_TOO_LARGE(431, "Request Header Fields Too Large"),
+ /**
+ * {@code 451 Unavailable For Legal Reasons}.
+ * @see <a href="https://tools.ietf.org/html/draft-ietf-httpbis-legally-restricted-status-04">
+ * An HTTP Status Code to Report Legal Obstacles</a>
+ * @since 4.3
+ */
+ UNAVAILABLE_FOR_LEGAL_REASONS(451, "Unavailable For Legal Reasons"),
// --- 5xx Server Error ---
@@ -385,17 +394,17 @@ public enum HttpStatus {
NETWORK_AUTHENTICATION_REQUIRED(511, "Network Authentication Required");
-
private final int value;
private final String reasonPhrase;
- private HttpStatus(int value, String reasonPhrase) {
+ HttpStatus(int value, String reasonPhrase) {
this.value = value;
this.reasonPhrase = reasonPhrase;
}
+
/**
* Return the integer value of this status code.
*/
@@ -407,7 +416,7 @@ public enum HttpStatus {
* Return the reason phrase of this status code.
*/
public String getReasonPhrase() {
- return reasonPhrase;
+ return this.reasonPhrase;
}
/**
@@ -416,7 +425,7 @@ public enum HttpStatus {
* This is a shortcut for checking the value of {@link #series()}.
*/
public boolean is1xxInformational() {
- return (Series.INFORMATIONAL.equals(series()));
+ return Series.INFORMATIONAL.equals(series());
}
/**
@@ -425,7 +434,7 @@ public enum HttpStatus {
* This is a shortcut for checking the value of {@link #series()}.
*/
public boolean is2xxSuccessful() {
- return (Series.SUCCESSFUL.equals(series()));
+ return Series.SUCCESSFUL.equals(series());
}
/**
@@ -434,7 +443,7 @@ public enum HttpStatus {
* This is a shortcut for checking the value of {@link #series()}.
*/
public boolean is3xxRedirection() {
- return (Series.REDIRECTION.equals(series()));
+ return Series.REDIRECTION.equals(series());
}
@@ -444,7 +453,7 @@ public enum HttpStatus {
* This is a shortcut for checking the value of {@link #series()}.
*/
public boolean is4xxClientError() {
- return (Series.CLIENT_ERROR.equals(series()));
+ return Series.CLIENT_ERROR.equals(series());
}
/**
@@ -453,7 +462,7 @@ public enum HttpStatus {
* This is a shortcut for checking the value of {@link #series()}.
*/
public boolean is5xxServerError() {
- return (Series.SERVER_ERROR.equals(series()));
+ return Series.SERVER_ERROR.equals(series());
}
/**
@@ -469,7 +478,7 @@ public enum HttpStatus {
*/
@Override
public String toString() {
- return Integer.toString(value);
+ return Integer.toString(this.value);
}
@@ -490,10 +499,10 @@ public enum HttpStatus {
/**
- * Java 5 enumeration of HTTP status series.
+ * Enumeration of HTTP status series.
* <p>Retrievable via {@link HttpStatus#series()}.
*/
- public static enum Series {
+ public enum Series {
INFORMATIONAL(1),
SUCCESSFUL(2),
@@ -503,7 +512,7 @@ public enum HttpStatus {
private final int value;
- private Series(int value) {
+ Series(int value) {
this.value = value;
}
@@ -527,7 +536,6 @@ public enum HttpStatus {
public static Series valueOf(HttpStatus status) {
return valueOf(status.value);
}
-
}
}
diff --git a/spring-web/src/main/java/org/springframework/http/MediaType.java b/spring-web/src/main/java/org/springframework/http/MediaType.java
index 8b8e0485..924c1e4c 100644
--- a/spring-web/src/main/java/org/springframework/http/MediaType.java
+++ b/spring-web/src/main/java/org/springframework/http/MediaType.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@ import java.util.List;
import java.util.Map;
import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
import org.springframework.util.InvalidMimeTypeException;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
@@ -42,8 +43,7 @@ import org.springframework.util.comparator.CompoundComparator;
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
* @since 3.0
- * @see <a href="http://tools.ietf.org/html/rfc7231#section-3.1.1.1">HTTP 1.1: Semantics
- * and Content, section 3.1.1.1</a>
+ * @see <a href="http://tools.ietf.org/html/rfc7231#section-3.1.1.1">HTTP 1.1: Semantics and Content, section 3.1.1.1</a>
*/
public class MediaType extends MimeType implements Serializable {
@@ -71,7 +71,7 @@ public class MediaType extends MimeType implements Serializable {
/**
* Public constant media type for {@code application/x-www-form-urlencoded}.
- * */
+ */
public final static MediaType APPLICATION_FORM_URLENCODED;
/**
@@ -103,7 +103,7 @@ public class MediaType extends MimeType implements Serializable {
/**
* Public constant media type for {@code application/octet-stream}.
- * */
+ */
public final static MediaType APPLICATION_OCTET_STREAM;
/**
@@ -112,8 +112,18 @@ public class MediaType extends MimeType implements Serializable {
public final static String APPLICATION_OCTET_STREAM_VALUE = "application/octet-stream";
/**
+ * Public constant media type for {@code application/pdf}.
+ */
+ public final static MediaType APPLICATION_PDF;
+
+ /**
+ * A String equivalent of {@link MediaType#APPLICATION_PDF}.
+ */
+ public final static String APPLICATION_PDF_VALUE = "application/pdf";
+
+ /**
* Public constant media type for {@code application/xhtml+xml}.
- * */
+ */
public final static MediaType APPLICATION_XHTML_XML;
/**
@@ -163,7 +173,7 @@ public class MediaType extends MimeType implements Serializable {
/**
* Public constant media type for {@code multipart/form-data}.
- * */
+ */
public final static MediaType MULTIPART_FORM_DATA;
/**
@@ -173,7 +183,7 @@ public class MediaType extends MimeType implements Serializable {
/**
* Public constant media type for {@code text/html}.
- * */
+ */
public final static MediaType TEXT_HTML;
/**
@@ -182,8 +192,18 @@ public class MediaType extends MimeType implements Serializable {
public final static String TEXT_HTML_VALUE = "text/html";
/**
+ * Public constant media type for {@code text/markdown}.
+ */
+ public final static MediaType TEXT_MARKDOWN;
+
+ /**
+ * A String equivalent of {@link MediaType#TEXT_MARKDOWN}.
+ */
+ public final static String TEXT_MARKDOWN_VALUE = "text/markdown";
+
+ /**
* Public constant media type for {@code text/plain}.
- * */
+ */
public final static MediaType TEXT_PLAIN;
/**
@@ -193,7 +213,7 @@ public class MediaType extends MimeType implements Serializable {
/**
* Public constant media type for {@code text/xml}.
- * */
+ */
public final static MediaType TEXT_XML;
/**
@@ -212,6 +232,7 @@ public class MediaType extends MimeType implements Serializable {
APPLICATION_JSON = valueOf(APPLICATION_JSON_VALUE);
APPLICATION_JSON_UTF8 = valueOf(APPLICATION_JSON_UTF8_VALUE);
APPLICATION_OCTET_STREAM = valueOf(APPLICATION_OCTET_STREAM_VALUE);
+ APPLICATION_PDF = valueOf(APPLICATION_PDF_VALUE);
APPLICATION_XHTML_XML = valueOf(APPLICATION_XHTML_XML_VALUE);
APPLICATION_XML = valueOf(APPLICATION_XML_VALUE);
IMAGE_GIF = valueOf(IMAGE_GIF_VALUE);
@@ -219,6 +240,7 @@ public class MediaType extends MimeType implements Serializable {
IMAGE_PNG = valueOf(IMAGE_PNG_VALUE);
MULTIPART_FORM_DATA = valueOf(MULTIPART_FORM_DATA_VALUE);
TEXT_HTML = valueOf(TEXT_HTML_VALUE);
+ TEXT_MARKDOWN = valueOf(TEXT_MARKDOWN_VALUE);
TEXT_PLAIN = valueOf(TEXT_PLAIN_VALUE);
TEXT_XML = valueOf(TEXT_XML_VALUE);
}
@@ -268,6 +290,17 @@ public class MediaType extends MimeType implements Serializable {
}
/**
+ * Copy-constructor that copies the type, subtype and parameters of the given
+ * {@code MediaType}, and allows to set the specified character set.
+ * @param other the other media type
+ * @param charset the character set
+ * @throws IllegalArgumentException if any of the parameters contain illegal characters
+ */
+ public MediaType(MediaType other, Charset charset) {
+ super(other, charset);
+ }
+
+ /**
* Copy-constructor that copies the type and subtype of the given {@code MediaType},
* and allows for different parameter.
* @param other the other media type
@@ -364,6 +397,8 @@ public class MediaType extends MimeType implements Serializable {
* Parse the given String value into a {@code MediaType} object,
* with this method name following the 'valueOf' naming convention
* (as supported by {@link org.springframework.core.convert.ConversionService}.
+ * @param value the string to parse
+ * @throws InvalidMediaTypeException if the media type value cannot be parsed
* @see #parseMediaType(String)
*/
public static MediaType valueOf(String value) {
@@ -374,7 +409,7 @@ public class MediaType extends MimeType implements Serializable {
* Parse the given String into a single {@code MediaType}.
* @param mediaType the string to parse
* @return the media type
- * @throws InvalidMediaTypeException if the string cannot be parsed
+ * @throws InvalidMediaTypeException if the media type value cannot be parsed
*/
public static MediaType parseMediaType(String mediaType) {
MimeType type;
@@ -392,13 +427,12 @@ public class MediaType extends MimeType implements Serializable {
}
}
-
/**
- * Parse the given, comma-separated string into a list of {@code MediaType} objects.
+ * Parse the given comma-separated string into a list of {@code MediaType} objects.
* <p>This method can be used to parse an Accept or Content-Type header.
* @param mediaTypes the string to parse
* @return the list of media types
- * @throws IllegalArgumentException if the string cannot be parsed
+ * @throws InvalidMediaTypeException if the media type value cannot be parsed
*/
public static List<MediaType> parseMediaTypes(String mediaTypes) {
if (!StringUtils.hasLength(mediaTypes)) {
@@ -413,6 +447,31 @@ public class MediaType extends MimeType implements Serializable {
}
/**
+ * Parse the given list of (potentially) comma-separated strings into a
+ * list of {@code MediaType} objects.
+ * <p>This method can be used to parse an Accept or Content-Type header.
+ * @param mediaTypes the string to parse
+ * @return the list of media types
+ * @throws InvalidMediaTypeException if the media type value cannot be parsed
+ * @since 4.3.2
+ */
+ public static List<MediaType> parseMediaTypes(List<String> mediaTypes) {
+ if (CollectionUtils.isEmpty(mediaTypes)) {
+ return Collections.<MediaType>emptyList();
+ }
+ else if (mediaTypes.size() == 1) {
+ return parseMediaTypes(mediaTypes.get(0));
+ }
+ else {
+ List<MediaType> result = new ArrayList<MediaType>(8);
+ for (String mediaType : mediaTypes) {
+ result.addAll(parseMediaTypes(mediaType));
+ }
+ return result;
+ }
+ }
+
+ /**
* Return a string representation of the given list of {@code MediaType} objects.
* <p>This method can be used to for an {@code Accept} or {@code Content-Type} header.
* @param mediaTypes the media types to create a string representation for
diff --git a/spring-web/src/main/java/org/springframework/http/RequestEntity.java b/spring-web/src/main/java/org/springframework/http/RequestEntity.java
index 7e9ab88b..46dbba93 100644
--- a/spring-web/src/main/java/org/springframework/http/RequestEntity.java
+++ b/spring-web/src/main/java/org/springframework/http/RequestEntity.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
package org.springframework.http;
+import java.lang.reflect.Type;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.Arrays;
@@ -54,6 +55,7 @@ import org.springframework.util.ObjectUtils;
* </pre>
*
* @author Arjen Poutsma
+ * @author Sebastien Deleuze
* @since 4.1
* @see #getMethod()
* @see #getUrl()
@@ -64,6 +66,8 @@ public class RequestEntity<T> extends HttpEntity<T> {
private final URI url;
+ private final Type type;
+
/**
* Constructor with method and URL but without body nor headers.
@@ -81,7 +85,19 @@ public class RequestEntity<T> extends HttpEntity<T> {
* @param url the URL
*/
public RequestEntity(T body, HttpMethod method, URI url) {
- this(body, null, method, url);
+ this(body, null, method, url, null);
+ }
+
+ /**
+ * Constructor with method, URL, body and type but without headers.
+ * @param body the body
+ * @param method the method
+ * @param url the URL
+ * @param type the type used for generic type resolution
+ * @since 4.3
+ */
+ public RequestEntity(T body, HttpMethod method, URI url, Type type) {
+ this(body, null, method, url, type);
}
/**
@@ -91,7 +107,7 @@ public class RequestEntity<T> extends HttpEntity<T> {
* @param url the URL
*/
public RequestEntity(MultiValueMap<String, String> headers, HttpMethod method, URI url) {
- this(null, headers, method, url);
+ this(null, headers, method, url, null);
}
/**
@@ -102,9 +118,23 @@ public class RequestEntity<T> extends HttpEntity<T> {
* @param url the URL
*/
public RequestEntity(T body, MultiValueMap<String, String> headers, HttpMethod method, URI url) {
+ this(body, headers, method, url, null);
+ }
+
+ /**
+ * Constructor with method, URL, headers, body and type.
+ * @param body the body
+ * @param headers the headers
+ * @param method the method
+ * @param url the URL
+ * @param type the type used for generic type resolution
+ * @since 4.3
+ */
+ public RequestEntity(T body, MultiValueMap<String, String> headers, HttpMethod method, URI url, Type type) {
super(body, headers);
this.method = method;
this.url = url;
+ this.type = type;
}
@@ -124,6 +154,21 @@ public class RequestEntity<T> extends HttpEntity<T> {
return this.url;
}
+ /**
+ * Return the type of the request's body.
+ * @return the request's body type, or {@code null} if not known
+ * @since 4.3
+ */
+ public Type getType() {
+ if (this.type == null) {
+ T body = getBody();
+ if (body != null) {
+ return body.getClass();
+ }
+ }
+ return this.type;
+ }
+
@Override
public boolean equals(Object other) {
@@ -134,8 +179,8 @@ public class RequestEntity<T> extends HttpEntity<T> {
return false;
}
RequestEntity<?> otherEntity = (RequestEntity<?>) other;
- return (ObjectUtils.nullSafeEquals(this.method, otherEntity.method) &&
- ObjectUtils.nullSafeEquals(this.url, otherEntity.url));
+ return (ObjectUtils.nullSafeEquals(getMethod(), otherEntity.getMethod()) &&
+ ObjectUtils.nullSafeEquals(getUrl(), otherEntity.getUrl()));
}
@Override
@@ -149,9 +194,9 @@ public class RequestEntity<T> extends HttpEntity<T> {
@Override
public String toString() {
StringBuilder builder = new StringBuilder("<");
- builder.append(this.method);
+ builder.append(getMethod());
builder.append(' ');
- builder.append(this.url);
+ builder.append(getUrl());
builder.append(',');
T body = getBody();
HttpHeaders headers = getHeaders();
@@ -327,6 +372,16 @@ public class RequestEntity<T> extends HttpEntity<T> {
* @return the built request entity
*/
<T> RequestEntity<T> body(T body);
+
+ /**
+ * Set the body and type of the request entity and build the RequestEntity.
+ * @param <T> the type of the body
+ * @param body the body of the request entity
+ * @param type the type of the body, useful for generic type resolution
+ * @return the built request entity
+ * @since 4.3
+ */
+ <T> RequestEntity<T> body(T body, Type type);
}
@@ -396,6 +451,11 @@ public class RequestEntity<T> extends HttpEntity<T> {
public <T> RequestEntity<T> body(T body) {
return new RequestEntity<T>(body, this.headers, this.method, this.url);
}
+
+ @Override
+ public <T> RequestEntity<T> body(T body, Type type) {
+ return new RequestEntity<T>(body, this.headers, this.method, this.url, type);
+ }
}
}
diff --git a/spring-web/src/main/java/org/springframework/http/ResponseEntity.java b/spring-web/src/main/java/org/springframework/http/ResponseEntity.java
index 9eba72c0..7edb9ea2 100644
--- a/spring-web/src/main/java/org/springframework/http/ResponseEntity.java
+++ b/spring-web/src/main/java/org/springframework/http/ResponseEntity.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@ import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
+import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
@@ -65,45 +66,55 @@ import org.springframework.util.ObjectUtils;
*/
public class ResponseEntity<T> extends HttpEntity<T> {
- private final HttpStatus statusCode;
+ private final Object statusCode;
/**
* Create a new {@code ResponseEntity} with the given status code, and no body nor headers.
- * @param statusCode the status code
+ * @param status the status code
*/
- public ResponseEntity(HttpStatus statusCode) {
- super();
- this.statusCode = statusCode;
+ public ResponseEntity(HttpStatus status) {
+ this(null, null, status);
}
/**
* Create a new {@code ResponseEntity} with the given body and status code, and no headers.
* @param body the entity body
- * @param statusCode the status code
+ * @param status the status code
*/
- public ResponseEntity(T body, HttpStatus statusCode) {
- super(body);
- this.statusCode = statusCode;
+ public ResponseEntity(T body, HttpStatus status) {
+ this(body, null, status);
}
/**
* Create a new {@code HttpEntity} with the given headers and status code, and no body.
* @param headers the entity headers
- * @param statusCode the status code
+ * @param status the status code
*/
- public ResponseEntity(MultiValueMap<String, String> headers, HttpStatus statusCode) {
- super(headers);
- this.statusCode = statusCode;
+ public ResponseEntity(MultiValueMap<String, String> headers, HttpStatus status) {
+ this(null, headers, status);
}
/**
* Create a new {@code HttpEntity} with the given body, headers, and status code.
* @param body the entity body
* @param headers the entity headers
- * @param statusCode the status code
+ * @param status the status code
*/
- public ResponseEntity(T body, MultiValueMap<String, String> headers, HttpStatus statusCode) {
+ public ResponseEntity(T body, MultiValueMap<String, String> headers, HttpStatus status) {
+ super(body, headers);
+ Assert.notNull(status, "HttpStatus must not be null");
+ this.statusCode = status;
+ }
+
+ /**
+ * Create a new {@code HttpEntity} with the given body, headers, and status code.
+ * Just used behind the nested builder API.
+ * @param body the entity body
+ * @param headers the entity headers
+ * @param statusCode the status code (as {@code HttpStatus} or as {@code Integer} value)
+ */
+ private ResponseEntity(T body, MultiValueMap<String, String> headers, Object statusCode) {
super(body, headers);
this.statusCode = statusCode;
}
@@ -111,10 +122,29 @@ public class ResponseEntity<T> extends HttpEntity<T> {
/**
* Return the HTTP status code of the response.
- * @return the HTTP status as an HttpStatus enum value
+ * @return the HTTP status as an HttpStatus enum entry
*/
public HttpStatus getStatusCode() {
- return this.statusCode;
+ if (this.statusCode instanceof HttpStatus) {
+ return (HttpStatus) this.statusCode;
+ }
+ else {
+ return HttpStatus.valueOf((Integer) this.statusCode);
+ }
+ }
+
+ /**
+ * Return the HTTP status code of the response.
+ * @return the HTTP status as an int value
+ * @since 4.3
+ */
+ public int getStatusCodeValue() {
+ if (this.statusCode instanceof HttpStatus) {
+ return ((HttpStatus) this.statusCode).value();
+ }
+ else {
+ return (Integer) this.statusCode;
+ }
}
@@ -139,8 +169,10 @@ public class ResponseEntity<T> extends HttpEntity<T> {
public String toString() {
StringBuilder builder = new StringBuilder("<");
builder.append(this.statusCode.toString());
- builder.append(' ');
- builder.append(this.statusCode.getReasonPhrase());
+ if (this.statusCode instanceof HttpStatus) {
+ builder.append(' ');
+ builder.append(((HttpStatus) this.statusCode).getReasonPhrase());
+ }
builder.append(',');
T body = getBody();
HttpHeaders headers = getHeaders();
@@ -167,6 +199,7 @@ public class ResponseEntity<T> extends HttpEntity<T> {
* @since 4.1
*/
public static BodyBuilder status(HttpStatus status) {
+ Assert.notNull(status, "HttpStatus must not be null");
return new DefaultBuilder(status);
}
@@ -177,7 +210,7 @@ public class ResponseEntity<T> extends HttpEntity<T> {
* @since 4.1
*/
public static BodyBuilder status(int status) {
- return status(HttpStatus.valueOf(status));
+ return new DefaultBuilder(status);
}
/**
@@ -333,6 +366,17 @@ public class ResponseEntity<T> extends HttpEntity<T> {
B cacheControl(CacheControl cacheControl);
/**
+ * Configure one or more request header names (e.g. "Accept-Language") to
+ * add to the "Vary" response header to inform clients that the response is
+ * subject to content negotiation and variances based on the value of the
+ * given request headers. The configured request header names are added only
+ * if not already present in the response "Vary" header.
+ * @param requestHeaders request header names
+ * @since 4.3
+ */
+ B varyBy(String... requestHeaders);
+
+ /**
* Build the response entity with no body.
* @return the response entity
* @see BodyBuilder#body(Object)
@@ -377,12 +421,12 @@ public class ResponseEntity<T> extends HttpEntity<T> {
private static class DefaultBuilder implements BodyBuilder {
- private final HttpStatus status;
+ private final Object statusCode;
private final HttpHeaders headers = new HttpHeaders();
- public DefaultBuilder(HttpStatus status) {
- this.status = status;
+ public DefaultBuilder(Object statusCode) {
+ this.statusCode = statusCode;
}
@Override
@@ -455,13 +499,19 @@ public class ResponseEntity<T> extends HttpEntity<T> {
}
@Override
+ public BodyBuilder varyBy(String... requestHeaders) {
+ this.headers.setVary(Arrays.asList(requestHeaders));
+ return this;
+ }
+
+ @Override
public ResponseEntity<Void> build() {
- return new ResponseEntity<Void>(null, this.headers, this.status);
+ return body(null);
}
@Override
public <T> ResponseEntity<T> body(T body) {
- return new ResponseEntity<T>(body, this.headers, this.status);
+ return new ResponseEntity<T>(body, this.headers, this.statusCode);
}
}
diff --git a/spring-web/src/main/java/org/springframework/http/client/AsyncClientHttpRequestExecution.java b/spring-web/src/main/java/org/springframework/http/client/AsyncClientHttpRequestExecution.java
new file mode 100644
index 00000000..2f09014a
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/http/client/AsyncClientHttpRequestExecution.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.http.client;
+
+import java.io.IOException;
+
+import org.springframework.http.HttpRequest;
+import org.springframework.util.concurrent.ListenableFuture;
+
+/**
+ * Represents the context of a client-side HTTP request execution.
+ *
+ * <p>Used to invoke the next interceptor in the interceptor chain, or -
+ * if the calling interceptor is last - execute the request itself.
+ *
+ * @author Jakub Narloch
+ * @author Rossen Stoyanchev
+ * @since 4.3
+ * @see AsyncClientHttpRequestInterceptor
+ */
+public interface AsyncClientHttpRequestExecution {
+
+ /**
+ * Resume the request execution by invoking the next interceptor in the chain
+ * or executing the request to the remote service.
+ * @param request the HTTP request, containing the HTTP method and headers
+ * @param body the body of the request
+ * @return a corresponding future handle
+ * @throws IOException in case of I/O errors
+ */
+ ListenableFuture<ClientHttpResponse> executeAsync(HttpRequest request, byte[] body) throws IOException;
+
+}
diff --git a/spring-web/src/main/java/org/springframework/http/client/AsyncClientHttpRequestInterceptor.java b/spring-web/src/main/java/org/springframework/http/client/AsyncClientHttpRequestInterceptor.java
new file mode 100644
index 00000000..3fabe120
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/http/client/AsyncClientHttpRequestInterceptor.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.http.client;
+
+import java.io.IOException;
+
+import org.springframework.http.HttpRequest;
+import org.springframework.http.client.support.InterceptingAsyncHttpAccessor;
+import org.springframework.util.concurrent.ListenableFuture;
+
+/**
+ * Intercepts client-side HTTP requests. Implementations of this interface can be
+ * {@linkplain org.springframework.web.client.AsyncRestTemplate#setInterceptors registered}
+ * with the {@link org.springframework.web.client.AsyncRestTemplate} as to modify
+ * the outgoing {@link HttpRequest} and/or register to modify the incoming
+ * {@link ClientHttpResponse} with help of a
+ * {@link org.springframework.util.concurrent.ListenableFutureAdapter}.
+ *
+ * <p>The main entry point for interceptors is {@link #intercept}.
+ *
+ * @author Jakub Narloch
+ * @author Rossen Stoyanchev
+ * @since 4.3
+ * @see org.springframework.web.client.AsyncRestTemplate
+ * @see InterceptingAsyncHttpAccessor
+ */
+public interface AsyncClientHttpRequestInterceptor {
+
+ /**
+ * Intercept the given request, and return a response future. The given
+ * {@link AsyncClientHttpRequestExecution} allows the interceptor to pass on
+ * the request to the next entity in the chain.
+ * <p>An implementation might follow this pattern:
+ * <ol>
+ * <li>Examine the {@linkplain HttpRequest request} and body</li>
+ * <li>Optionally {@linkplain org.springframework.http.client.support.HttpRequestWrapper
+ * wrap} the request to filter HTTP attributes.</li>
+ * <li>Optionally modify the body of the request.</li>
+ * <li>One of the following:
+ * <ul>
+ * <li>execute the request through {@link ClientHttpRequestExecution}</li>
+ * <li>don't execute the request to block the execution altogether</li>
+ * </ul>
+ * <li>Optionally adapt the response to filter HTTP attributes with the help of
+ * {@link org.springframework.util.concurrent.ListenableFutureAdapter
+ * ListenableFutureAdapter}.</li>
+ * </ol>
+ * @param request the request, containing method, URI, and headers
+ * @param body the body of the request
+ * @param execution the request execution
+ * @return the response future
+ * @throws IOException in case of I/O errors
+ */
+ ListenableFuture<ClientHttpResponse> intercept(HttpRequest request, byte[] body,
+ AsyncClientHttpRequestExecution execution) throws IOException;
+
+}
diff --git a/spring-web/src/main/java/org/springframework/http/client/ClientHttpRequestExecution.java b/spring-web/src/main/java/org/springframework/http/client/ClientHttpRequestExecution.java
index df6e10c6..2fe64f52 100644
--- a/spring-web/src/main/java/org/springframework/http/client/ClientHttpRequestExecution.java
+++ b/spring-web/src/main/java/org/springframework/http/client/ClientHttpRequestExecution.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2016 the original author 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,22 +23,23 @@ import org.springframework.http.HttpRequest;
/**
* Represents the context of a client-side HTTP request execution.
*
- * <p>Used to invoke the next interceptor in the interceptor chain, or - if the calling interceptor is last - execute
- * the request itself.
+ * <p>Used to invoke the next interceptor in the interceptor chain,
+ * or - if the calling interceptor is last - execute the request itself.
*
* @author Arjen Poutsma
- * @see ClientHttpRequestInterceptor
* @since 3.1
+ * @see ClientHttpRequestInterceptor
*/
public interface ClientHttpRequestExecution {
/**
- * Execute the request with the given request attributes and body, and return the response.
- *
+ * Execute the request with the given request attributes and body,
+ * and return the response.
* @param request the request, containing method, URI, and headers
* @param body the body of the request to execute
* @return the response
* @throws IOException in case of I/O errors
*/
ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException;
+
}
diff --git a/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactory.java b/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactory.java
index 53f6faf0..d61231d9 100644
--- a/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactory.java
+++ b/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +39,7 @@ import org.apache.http.protocol.HttpContext;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.http.HttpMethod;
import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
/**
* {@link org.springframework.http.client.ClientHttpRequestFactory} implementation that
@@ -58,6 +59,20 @@ import org.springframework.util.Assert;
*/
public class HttpComponentsClientHttpRequestFactory implements ClientHttpRequestFactory, DisposableBean {
+ private static Class<?> abstractHttpClientClass;
+
+ static {
+ try {
+ // Looking for AbstractHttpClient class (deprecated as of HttpComponents 4.3)
+ abstractHttpClientClass = ClassUtils.forName("org.apache.http.impl.client.AbstractHttpClient",
+ HttpComponentsClientHttpRequestFactory.class.getClassLoader());
+ }
+ catch (ClassNotFoundException ex) {
+ // Probably removed from HttpComponents in the meantime...
+ }
+ }
+
+
private HttpClient httpClient;
private RequestConfig requestConfig;
@@ -130,7 +145,7 @@ public class HttpComponentsClientHttpRequestFactory implements ClientHttpRequest
*/
@SuppressWarnings("deprecation")
private void setLegacyConnectionTimeout(HttpClient client, int timeout) {
- if (org.apache.http.impl.client.AbstractHttpClient.class.isInstance(client)) {
+ if (abstractHttpClientClass != null && abstractHttpClientClass.isInstance(client)) {
client.getParams().setIntParameter(org.apache.http.params.CoreConnectionPNames.CONNECTION_TIMEOUT, timeout);
}
}
@@ -171,7 +186,7 @@ public class HttpComponentsClientHttpRequestFactory implements ClientHttpRequest
*/
@SuppressWarnings("deprecation")
private void setLegacySocketTimeout(HttpClient client, int timeout) {
- if (org.apache.http.impl.client.AbstractHttpClient.class.isInstance(client)) {
+ if (abstractHttpClientClass != null && abstractHttpClientClass.isInstance(client)) {
client.getParams().setIntParameter(org.apache.http.params.CoreConnectionPNames.SO_TIMEOUT, timeout);
}
}
diff --git a/spring-web/src/main/java/org/springframework/http/client/InterceptingAsyncClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/InterceptingAsyncClientHttpRequest.java
new file mode 100644
index 00000000..0b13181a
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/http/client/InterceptingAsyncClientHttpRequest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2002-2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.http.client;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Iterator;
+import java.util.List;
+
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpRequest;
+import org.springframework.util.StreamUtils;
+import org.springframework.util.concurrent.ListenableFuture;
+
+/**
+ * An {@link AsyncClientHttpRequest} wrapper that enriches it proceeds the actual
+ * request execution with calling the registered interceptors.
+ *
+ * @author Jakub Narloch
+ * @author Rossen Stoyanchev
+ * @see InterceptingAsyncClientHttpRequestFactory
+ */
+class InterceptingAsyncClientHttpRequest extends AbstractBufferingAsyncClientHttpRequest {
+
+ private AsyncClientHttpRequestFactory requestFactory;
+
+ private List<AsyncClientHttpRequestInterceptor> interceptors;
+
+ private URI uri;
+
+ private HttpMethod httpMethod;
+
+
+ /**
+ * Creates new instance of {@link InterceptingAsyncClientHttpRequest}.
+ *
+ * @param requestFactory the async request factory
+ * @param interceptors the list of interceptors
+ * @param uri the request URI
+ * @param httpMethod the HTTP method
+ */
+ public InterceptingAsyncClientHttpRequest(AsyncClientHttpRequestFactory requestFactory,
+ List<AsyncClientHttpRequestInterceptor> interceptors, URI uri, HttpMethod httpMethod) {
+
+ this.requestFactory = requestFactory;
+ this.interceptors = interceptors;
+ this.uri = uri;
+ this.httpMethod = httpMethod;
+ }
+
+
+ @Override
+ protected ListenableFuture<ClientHttpResponse> executeInternal(HttpHeaders headers, byte[] body)
+ throws IOException {
+
+ return new AsyncRequestExecution().executeAsync(this, body);
+ }
+
+ @Override
+ public HttpMethod getMethod() {
+ return httpMethod;
+ }
+
+ @Override
+ public URI getURI() {
+ return uri;
+ }
+
+
+ private class AsyncRequestExecution implements AsyncClientHttpRequestExecution {
+
+ private Iterator<AsyncClientHttpRequestInterceptor> iterator;
+
+ public AsyncRequestExecution() {
+ this.iterator = interceptors.iterator();
+ }
+
+ @Override
+ public ListenableFuture<ClientHttpResponse> executeAsync(HttpRequest request, byte[] body)
+ throws IOException {
+
+ if (this.iterator.hasNext()) {
+ AsyncClientHttpRequestInterceptor interceptor = this.iterator.next();
+ return interceptor.intercept(request, body, this);
+ }
+ else {
+ URI theUri = request.getURI();
+ HttpMethod theMethod = request.getMethod();
+ HttpHeaders theHeaders = request.getHeaders();
+
+ AsyncClientHttpRequest delegate = requestFactory.createAsyncRequest(theUri, theMethod);
+ delegate.getHeaders().putAll(theHeaders);
+ if (body.length > 0) {
+ StreamUtils.copy(body, delegate.getBody());
+ }
+
+ return delegate.executeAsync();
+ }
+ }
+ }
+
+}
diff --git a/spring-web/src/main/java/org/springframework/http/client/InterceptingAsyncClientHttpRequestFactory.java b/spring-web/src/main/java/org/springframework/http/client/InterceptingAsyncClientHttpRequestFactory.java
new file mode 100644
index 00000000..1ca68c52
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/http/client/InterceptingAsyncClientHttpRequestFactory.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.http.client;
+
+import java.net.URI;
+import java.util.Collections;
+import java.util.List;
+
+import org.springframework.http.HttpMethod;
+
+/**
+ * Wrapper for a {@link AsyncClientHttpRequestFactory} that has support for
+ * {@link AsyncClientHttpRequestInterceptor}s.
+ *
+ * @author Jakub Narloch
+ * @since 4.3
+ * @see InterceptingAsyncClientHttpRequest
+ */
+public class InterceptingAsyncClientHttpRequestFactory implements AsyncClientHttpRequestFactory {
+
+ private AsyncClientHttpRequestFactory delegate;
+
+ private List<AsyncClientHttpRequestInterceptor> interceptors;
+
+
+ /**
+ * Create new instance of {@link InterceptingAsyncClientHttpRequestFactory}
+ * with delegated request factory and list of interceptors.
+ * @param delegate the request factory to delegate to
+ * @param interceptors the list of interceptors to use
+ */
+ public InterceptingAsyncClientHttpRequestFactory(AsyncClientHttpRequestFactory delegate,
+ List<AsyncClientHttpRequestInterceptor> interceptors) {
+
+ this.delegate = delegate;
+ this.interceptors = (interceptors != null ? interceptors : Collections.<AsyncClientHttpRequestInterceptor>emptyList());
+ }
+
+
+ @Override
+ public AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod method) {
+ return new InterceptingAsyncClientHttpRequest(this.delegate, this.interceptors, uri, method);
+ }
+
+}
diff --git a/spring-web/src/main/java/org/springframework/http/client/Netty4ClientHttpResponse.java b/spring-web/src/main/java/org/springframework/http/client/Netty4ClientHttpResponse.java
index be619f1f..2baa53a4 100644
--- a/spring-web/src/main/java/org/springframework/http/client/Netty4ClientHttpResponse.java
+++ b/spring-web/src/main/java/org/springframework/http/client/Netty4ClientHttpResponse.java
@@ -56,11 +56,13 @@ class Netty4ClientHttpResponse extends AbstractClientHttpResponse {
@Override
+ @SuppressWarnings("deprecation")
public int getRawStatusCode() throws IOException {
return this.nettyResponse.getStatus().code();
}
@Override
+ @SuppressWarnings("deprecation")
public String getStatusText() throws IOException {
return this.nettyResponse.getStatus().reasonPhrase();
}
diff --git a/spring-web/src/main/java/org/springframework/http/client/OkHttp3AsyncClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/OkHttp3AsyncClientHttpRequest.java
new file mode 100644
index 00000000..057a1652
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/http/client/OkHttp3AsyncClientHttpRequest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.http.client;
+
+import java.io.IOException;
+import java.net.URI;
+
+import okhttp3.Call;
+import okhttp3.Callback;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.util.concurrent.ListenableFuture;
+import org.springframework.util.concurrent.SettableListenableFuture;
+
+/**
+ * {@link AsyncClientHttpRequest} implementation that uses OkHttp 3.x to execute requests.
+ *
+ * <p>Created via the {@link OkHttp3ClientHttpRequestFactory}.
+ *
+ * @author Luciano Leggieri
+ * @author Arjen Poutsma
+ * @author Roy Clarkson
+ * @since 4.3
+ */
+class OkHttp3AsyncClientHttpRequest extends AbstractBufferingAsyncClientHttpRequest {
+
+ private final OkHttpClient client;
+
+ private final URI uri;
+
+ private final HttpMethod method;
+
+
+ public OkHttp3AsyncClientHttpRequest(OkHttpClient client, URI uri, HttpMethod method) {
+ this.client = client;
+ this.uri = uri;
+ this.method = method;
+ }
+
+
+ @Override
+ public HttpMethod getMethod() {
+ return this.method;
+ }
+
+ @Override
+ public URI getURI() {
+ return this.uri;
+ }
+
+ @Override
+ protected ListenableFuture<ClientHttpResponse> executeInternal(HttpHeaders headers, byte[] content)
+ throws IOException {
+
+ Request request = OkHttp3ClientHttpRequestFactory.buildRequest(headers, content, this.uri, this.method);
+ return new OkHttpListenableFuture(this.client.newCall(request));
+ }
+
+
+ private static class OkHttpListenableFuture extends SettableListenableFuture<ClientHttpResponse> {
+
+ private final Call call;
+
+ public OkHttpListenableFuture(Call call) {
+ this.call = call;
+ this.call.enqueue(new Callback() {
+ @Override
+ public void onResponse(Call call, Response response) {
+ set(new OkHttp3ClientHttpResponse(response));
+ }
+ @Override
+ public void onFailure(Call call, IOException ex) {
+ setException(ex);
+ }
+ });
+ }
+
+ @Override
+ protected void interruptTask() {
+ this.call.cancel();
+ }
+ }
+
+}
diff --git a/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpRequest.java
new file mode 100644
index 00000000..e0ebaac3
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpRequest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.http.client;
+
+import java.io.IOException;
+import java.net.URI;
+
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+
+/**
+ * {@link ClientHttpRequest} implementation that uses OkHttp 3.x to execute requests.
+ *
+ * <p>Created via the {@link OkHttp3ClientHttpRequestFactory}.
+ *
+ * @author Luciano Leggieri
+ * @author Arjen Poutsma
+ * @author Roy Clarkson
+ * @since 4.3
+ */
+class OkHttp3ClientHttpRequest extends AbstractBufferingClientHttpRequest {
+
+ private final OkHttpClient client;
+
+ private final URI uri;
+
+ private final HttpMethod method;
+
+
+ public OkHttp3ClientHttpRequest(OkHttpClient client, URI uri, HttpMethod method) {
+ this.client = client;
+ this.uri = uri;
+ this.method = method;
+ }
+
+
+ @Override
+ public HttpMethod getMethod() {
+ return this.method;
+ }
+
+ @Override
+ public URI getURI() {
+ return this.uri;
+ }
+
+
+ @Override
+ protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] content) throws IOException {
+ Request request = OkHttp3ClientHttpRequestFactory.buildRequest(headers, content, this.uri, this.method);
+ return new OkHttp3ClientHttpResponse(this.client.newCall(request).execute());
+ }
+
+}
diff --git a/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpRequestFactory.java b/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpRequestFactory.java
new file mode 100644
index 00000000..546fde34
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpRequestFactory.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.http.client;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * {@link ClientHttpRequestFactory} implementation that uses
+ * <a href="http://square.github.io/okhttp/">OkHttp</a> 3.x to create requests.
+ *
+ * @author Luciano Leggieri
+ * @author Arjen Poutsma
+ * @author Roy Clarkson
+ * @since 4.3
+ */
+public class OkHttp3ClientHttpRequestFactory
+ implements ClientHttpRequestFactory, AsyncClientHttpRequestFactory, DisposableBean {
+
+ private OkHttpClient client;
+
+ private final boolean defaultClient;
+
+
+ /**
+ * Create a factory with a default {@link OkHttpClient} instance.
+ */
+ public OkHttp3ClientHttpRequestFactory() {
+ this.client = new OkHttpClient();
+ this.defaultClient = true;
+ }
+
+ /**
+ * Create a factory with the given {@link OkHttpClient} instance.
+ * @param client the client to use
+ */
+ public OkHttp3ClientHttpRequestFactory(OkHttpClient client) {
+ Assert.notNull(client, "OkHttpClient must not be null");
+ this.client = client;
+ this.defaultClient = false;
+ }
+
+
+ /**
+ * Sets the underlying read timeout in milliseconds.
+ * A value of 0 specifies an infinite timeout.
+ * @see okhttp3.OkHttpClient.Builder#readTimeout(long, TimeUnit)
+ */
+ public void setReadTimeout(int readTimeout) {
+ this.client = this.client.newBuilder()
+ .readTimeout(readTimeout, TimeUnit.MILLISECONDS)
+ .build();
+ }
+
+ /**
+ * Sets the underlying write timeout in milliseconds.
+ * A value of 0 specifies an infinite timeout.
+ * @see okhttp3.OkHttpClient.Builder#writeTimeout(long, TimeUnit)
+ */
+ public void setWriteTimeout(int writeTimeout) {
+ this.client = this.client.newBuilder()
+ .writeTimeout(writeTimeout, TimeUnit.MILLISECONDS)
+ .build();
+ }
+
+ /**
+ * Sets the underlying connect timeout in milliseconds.
+ * A value of 0 specifies an infinite timeout.
+ * @see okhttp3.OkHttpClient.Builder#connectTimeout(long, TimeUnit)
+ */
+ public void setConnectTimeout(int connectTimeout) {
+ this.client = this.client.newBuilder()
+ .connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
+ .build();
+ }
+
+
+ @Override
+ public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) {
+ return new OkHttp3ClientHttpRequest(this.client, uri, httpMethod);
+ }
+
+ @Override
+ public AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod) {
+ return new OkHttp3AsyncClientHttpRequest(this.client, uri, httpMethod);
+ }
+
+
+ @Override
+ public void destroy() throws IOException {
+ if (this.defaultClient) {
+ // Clean up the client if we created it in the constructor
+ if (this.client.cache() != null) {
+ this.client.cache().close();
+ }
+ this.client.dispatcher().executorService().shutdown();
+ }
+ }
+
+
+ static Request buildRequest(HttpHeaders headers, byte[] content, URI uri,
+ HttpMethod method) throws MalformedURLException {
+
+ okhttp3.MediaType contentType = getContentType(headers);
+ RequestBody body = (content.length > 0 ? RequestBody.create(contentType, content) : null);
+
+ URL url = uri.toURL();
+ String methodName = method.name();
+ Request.Builder builder = new Request.Builder().url(url).method(methodName, body);
+
+ for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
+ String headerName = entry.getKey();
+ for (String headerValue : entry.getValue()) {
+ builder.addHeader(headerName, headerValue);
+ }
+ }
+
+ return builder.build();
+ }
+
+ private static okhttp3.MediaType getContentType(HttpHeaders headers) {
+ String rawContentType = headers.getFirst(HttpHeaders.CONTENT_TYPE);
+ return (StringUtils.hasText(rawContentType) ? okhttp3.MediaType.parse(rawContentType) : null);
+ }
+
+}
diff --git a/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpResponse.java b/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpResponse.java
new file mode 100644
index 00000000..b6a7ed19
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpResponse.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.http.client;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import okhttp3.Response;
+
+import org.springframework.http.HttpHeaders;
+import org.springframework.util.Assert;
+
+/**
+ * {@link ClientHttpResponse} implementation based on OkHttp 3.x.
+ *
+ * @author Luciano Leggieri
+ * @author Arjen Poutsma
+ * @author Roy Clarkson
+ * @since 4.3
+ */
+class OkHttp3ClientHttpResponse extends AbstractClientHttpResponse {
+
+ private final Response response;
+
+ private HttpHeaders headers;
+
+
+ public OkHttp3ClientHttpResponse(Response response) {
+ Assert.notNull(response, "Response must not be null");
+ this.response = response;
+ }
+
+
+ @Override
+ public int getRawStatusCode() {
+ return this.response.code();
+ }
+
+ @Override
+ public String getStatusText() {
+ return this.response.message();
+ }
+
+ @Override
+ public InputStream getBody() throws IOException {
+ return this.response.body().byteStream();
+ }
+
+ @Override
+ public HttpHeaders getHeaders() {
+ if (this.headers == null) {
+ HttpHeaders headers = new HttpHeaders();
+ for (String headerName : this.response.headers().names()) {
+ for (String headerValue : this.response.headers(headerName)) {
+ headers.add(headerName, headerValue);
+ }
+ }
+ this.headers = headers;
+ }
+ return this.headers;
+ }
+
+ @Override
+ public void close() {
+ this.response.body().close();
+ }
+
+}
diff --git a/spring-web/src/main/java/org/springframework/http/client/OkHttpAsyncClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/OkHttpAsyncClientHttpRequest.java
new file mode 100644
index 00000000..6b042422
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/http/client/OkHttpAsyncClientHttpRequest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.http.client;
+
+import java.io.IOException;
+import java.net.URI;
+
+import com.squareup.okhttp.Call;
+import com.squareup.okhttp.Callback;
+import com.squareup.okhttp.OkHttpClient;
+import com.squareup.okhttp.Request;
+import com.squareup.okhttp.Response;
+
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.util.concurrent.ListenableFuture;
+import org.springframework.util.concurrent.SettableListenableFuture;
+
+/**
+ * {@link AsyncClientHttpRequest} implementation that uses OkHttp 2.x to execute requests.
+ *
+ * <p>Created via the {@link OkHttpClientHttpRequestFactory}.
+ *
+ * @author Luciano Leggieri
+ * @author Arjen Poutsma
+ * @since 4.3
+ * @see org.springframework.http.client.OkHttp3AsyncClientHttpRequest
+ */
+class OkHttpAsyncClientHttpRequest extends AbstractBufferingAsyncClientHttpRequest {
+
+ private final OkHttpClient client;
+
+ private final URI uri;
+
+ private final HttpMethod method;
+
+
+ public OkHttpAsyncClientHttpRequest(OkHttpClient client, URI uri, HttpMethod method) {
+ this.client = client;
+ this.uri = uri;
+ this.method = method;
+ }
+
+
+ @Override
+ public HttpMethod getMethod() {
+ return this.method;
+ }
+
+ @Override
+ public URI getURI() {
+ return this.uri;
+ }
+
+ @Override
+ protected ListenableFuture<ClientHttpResponse> executeInternal(HttpHeaders headers, byte[] content)
+ throws IOException {
+
+ Request request = OkHttpClientHttpRequestFactory.buildRequest(headers, content, this.uri, this.method);
+ return new OkHttpListenableFuture(this.client.newCall(request));
+ }
+
+
+ private static class OkHttpListenableFuture extends SettableListenableFuture<ClientHttpResponse> {
+
+ private final Call call;
+
+ public OkHttpListenableFuture(Call call) {
+ this.call = call;
+ this.call.enqueue(new Callback() {
+ @Override
+ public void onResponse(Response response) {
+ set(new OkHttpClientHttpResponse(response));
+ }
+ @Override
+ public void onFailure(Request request, IOException ex) {
+ setException(ex);
+ }
+ });
+ }
+
+ @Override
+ protected void interruptTask() {
+ this.call.cancel();
+ }
+ }
+
+}
diff --git a/spring-web/src/main/java/org/springframework/http/client/OkHttpClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/OkHttpClientHttpRequest.java
index fe519f06..a2f75be4 100644
--- a/spring-web/src/main/java/org/springframework/http/client/OkHttpClientHttpRequest.java
+++ b/spring-web/src/main/java/org/springframework/http/client/OkHttpClientHttpRequest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,35 +18,24 @@ package org.springframework.http.client;
import java.io.IOException;
import java.net.URI;
-import java.net.URL;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ExecutionException;
-
-import com.squareup.okhttp.Call;
-import com.squareup.okhttp.Callback;
-import com.squareup.okhttp.MediaType;
+
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
-import com.squareup.okhttp.RequestBody;
-import com.squareup.okhttp.Response;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
-import org.springframework.util.StringUtils;
-import org.springframework.util.concurrent.ListenableFuture;
-import org.springframework.util.concurrent.SettableListenableFuture;
/**
- * {@link ClientHttpRequest} implementation that uses OkHttp to execute requests.
+ * {@link ClientHttpRequest} implementation that uses OkHttp 2.x to execute requests.
*
* <p>Created via the {@link OkHttpClientHttpRequestFactory}.
*
* @author Luciano Leggieri
* @author Arjen Poutsma
* @since 4.2
+ * @see org.springframework.http.client.OkHttp3ClientHttpRequest
*/
-class OkHttpClientHttpRequest extends AbstractBufferingAsyncClientHttpRequest implements ClientHttpRequest {
+class OkHttpClientHttpRequest extends AbstractBufferingClientHttpRequest {
private final OkHttpClient client;
@@ -72,73 +61,11 @@ class OkHttpClientHttpRequest extends AbstractBufferingAsyncClientHttpRequest im
return this.uri;
}
- @Override
- protected ListenableFuture<ClientHttpResponse> executeInternal(HttpHeaders headers, byte[] content)
- throws IOException {
-
- MediaType contentType = getContentType(headers);
- RequestBody body = (content.length > 0 ? RequestBody.create(contentType, content) : null);
-
- URL url = this.uri.toURL();
- String methodName = this.method.name();
- Request.Builder builder = new Request.Builder().url(url).method(methodName, body);
-
- for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
- String headerName = entry.getKey();
- for (String headerValue : entry.getValue()) {
- builder.addHeader(headerName, headerValue);
- }
- }
- Request request = builder.build();
-
- return new OkHttpListenableFuture(this.client.newCall(request));
- }
-
- private MediaType getContentType(HttpHeaders headers) {
- String rawContentType = headers.getFirst("Content-Type");
- return (StringUtils.hasText(rawContentType) ? MediaType.parse(rawContentType) : null);
- }
@Override
- public ClientHttpResponse execute() throws IOException {
- try {
- return executeAsync().get();
- }
- catch (InterruptedException ex) {
- throw new IOException(ex.getMessage(), ex);
- }
- catch (ExecutionException ex) {
- Throwable cause = ex.getCause();
- if (cause instanceof IOException) {
- throw (IOException) cause;
- }
- throw new IOException(cause.getMessage(), cause);
- }
- }
-
-
- private static class OkHttpListenableFuture extends SettableListenableFuture<ClientHttpResponse> {
-
- private final Call call;
-
- public OkHttpListenableFuture(Call call) {
- this.call = call;
- this.call.enqueue(new Callback() {
- @Override
- public void onResponse(Response response) {
- set(new OkHttpClientHttpResponse(response));
- }
- @Override
- public void onFailure(Request request, IOException ex) {
- setException(ex);
- }
- });
- }
-
- @Override
- protected void interruptTask() {
- this.call.cancel();
- }
+ protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] content) throws IOException {
+ Request request = OkHttpClientHttpRequestFactory.buildRequest(headers, content, this.uri, this.method);
+ return new OkHttpClientHttpResponse(this.client.newCall(request).execute());
}
}
diff --git a/spring-web/src/main/java/org/springframework/http/client/OkHttpClientHttpRequestFactory.java b/spring-web/src/main/java/org/springframework/http/client/OkHttpClientHttpRequestFactory.java
index 9b2674dd..d5ae9e97 100644
--- a/spring-web/src/main/java/org/springframework/http/client/OkHttpClientHttpRequestFactory.java
+++ b/spring-web/src/main/java/org/springframework/http/client/OkHttpClientHttpRequestFactory.java
@@ -16,22 +16,32 @@
package org.springframework.http.client;
+import java.io.IOException;
+import java.net.MalformedURLException;
import java.net.URI;
+import java.net.URL;
+import java.util.List;
+import java.util.Map;
import java.util.concurrent.TimeUnit;
import com.squareup.okhttp.OkHttpClient;
+import com.squareup.okhttp.Request;
+import com.squareup.okhttp.RequestBody;
import org.springframework.beans.factory.DisposableBean;
+import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
/**
* {@link ClientHttpRequestFactory} implementation that uses
- * <a href="http://square.github.io/okhttp/">OkHttp</a> to create requests.
+ * <a href="http://square.github.io/okhttp/">OkHttp</a> 2.x to create requests.
*
* @author Luciano Leggieri
* @author Arjen Poutsma
* @since 4.2
+ * @see org.springframework.http.client.OkHttp3ClientHttpRequestFactory
*/
public class OkHttpClientHttpRequestFactory
implements ClientHttpRequestFactory, AsyncClientHttpRequestFactory, DisposableBean {
@@ -90,20 +100,17 @@ public class OkHttpClientHttpRequestFactory
@Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) {
- return createRequestInternal(uri, httpMethod);
+ return new OkHttpClientHttpRequest(this.client, uri, httpMethod);
}
@Override
public AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod) {
- return createRequestInternal(uri, httpMethod);
+ return new OkHttpAsyncClientHttpRequest(this.client, uri, httpMethod);
}
- private OkHttpClientHttpRequest createRequestInternal(URI uri, HttpMethod httpMethod) {
- return new OkHttpClientHttpRequest(this.client, uri, httpMethod);
- }
@Override
- public void destroy() throws Exception {
+ public void destroy() throws IOException {
if (this.defaultClient) {
// Clean up the client if we created it in the constructor
if (this.client.getCache() != null) {
@@ -113,4 +120,31 @@ public class OkHttpClientHttpRequestFactory
}
}
+
+ static Request buildRequest(HttpHeaders headers, byte[] content, URI uri,
+ HttpMethod method) throws MalformedURLException {
+
+ com.squareup.okhttp.MediaType contentType = getContentType(headers);
+ RequestBody body = (content.length > 0 ? RequestBody.create(contentType, content) : null);
+
+ URL url = uri.toURL();
+ String methodName = method.name();
+ Request.Builder builder = new Request.Builder().url(url).method(methodName, body);
+
+ for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
+ String headerName = entry.getKey();
+ for (String headerValue : entry.getValue()) {
+ builder.addHeader(headerName, headerValue);
+ }
+ }
+
+ return builder.build();
+ }
+
+ private static com.squareup.okhttp.MediaType getContentType(HttpHeaders headers) {
+ String rawContentType = headers.getFirst(HttpHeaders.CONTENT_TYPE);
+ return (StringUtils.hasText(rawContentType) ?
+ com.squareup.okhttp.MediaType.parse(rawContentType) : null);
+ }
+
}
diff --git a/spring-web/src/main/java/org/springframework/http/client/OkHttpClientHttpResponse.java b/spring-web/src/main/java/org/springframework/http/client/OkHttpClientHttpResponse.java
index 392a2d7d..6a0639f5 100644
--- a/spring-web/src/main/java/org/springframework/http/client/OkHttpClientHttpResponse.java
+++ b/spring-web/src/main/java/org/springframework/http/client/OkHttpClientHttpResponse.java
@@ -25,11 +25,12 @@ import org.springframework.http.HttpHeaders;
import org.springframework.util.Assert;
/**
- * {@link ClientHttpResponse} implementation based on OkHttp.
+ * {@link ClientHttpResponse} implementation based on OkHttp 2.x.
*
* @author Luciano Leggieri
* @author Arjen Poutsma
* @since 4.2
+ * @see org.springframework.http.client.OkHttp3ClientHttpResponse
*/
class OkHttpClientHttpResponse extends AbstractClientHttpResponse {
diff --git a/spring-web/src/main/java/org/springframework/http/client/SimpleBufferingAsyncClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/SimpleBufferingAsyncClientHttpRequest.java
index 015a2e42..bd439963 100644
--- a/spring-web/src/main/java/org/springframework/http/client/SimpleBufferingAsyncClientHttpRequest.java
+++ b/spring-web/src/main/java/org/springframework/http/client/SimpleBufferingAsyncClientHttpRequest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -89,6 +89,10 @@ final class SimpleBufferingAsyncClientHttpRequest extends AbstractBufferingAsync
if (connection.getDoOutput()) {
FileCopyUtils.copy(bufferedOutput, connection.getOutputStream());
}
+ else {
+ // Immediately trigger the request in a no-output scenario as well
+ connection.getResponseCode();
+ }
return new SimpleClientHttpResponse(connection);
}
});
diff --git a/spring-web/src/main/java/org/springframework/http/client/SimpleBufferingClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/SimpleBufferingClientHttpRequest.java
index 24195beb..9a992f33 100644
--- a/spring-web/src/main/java/org/springframework/http/client/SimpleBufferingClientHttpRequest.java
+++ b/spring-web/src/main/java/org/springframework/http/client/SimpleBufferingClientHttpRequest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -68,12 +68,10 @@ final class SimpleBufferingClientHttpRequest extends AbstractBufferingClientHttp
@Override
protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
addHeaders(this.connection, headers);
-
// JDK <1.8 doesn't support getOutputStream with HTTP DELETE
if (HttpMethod.DELETE == getMethod() && bufferedOutput.length == 0) {
this.connection.setDoOutput(false);
}
-
if (this.connection.getDoOutput() && this.outputStreaming) {
this.connection.setFixedLengthStreamingMode(bufferedOutput.length);
}
@@ -81,7 +79,10 @@ final class SimpleBufferingClientHttpRequest extends AbstractBufferingClientHttp
if (this.connection.getDoOutput()) {
FileCopyUtils.copy(bufferedOutput, this.connection.getOutputStream());
}
-
+ else {
+ // Immediately trigger the request in a no-output scenario as well
+ this.connection.getResponseCode();
+ }
return new SimpleClientHttpResponse(this.connection);
}
diff --git a/spring-web/src/main/java/org/springframework/http/client/SimpleClientHttpResponse.java b/spring-web/src/main/java/org/springframework/http/client/SimpleClientHttpResponse.java
index cc7e627d..f667cb2d 100644
--- a/spring-web/src/main/java/org/springframework/http/client/SimpleClientHttpResponse.java
+++ b/spring-web/src/main/java/org/springframework/http/client/SimpleClientHttpResponse.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@ import java.io.InputStream;
import java.net.HttpURLConnection;
import org.springframework.http.HttpHeaders;
+import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
/**
@@ -29,6 +30,7 @@ import org.springframework.util.StringUtils;
* {@link SimpleStreamingClientHttpRequest#execute()}.
*
* @author Arjen Poutsma
+ * @author Brian Clozel
* @since 3.0
*/
final class SimpleClientHttpResponse extends AbstractClientHttpResponse {
@@ -37,6 +39,8 @@ final class SimpleClientHttpResponse extends AbstractClientHttpResponse {
private HttpHeaders headers;
+ private InputStream responseStream;
+
SimpleClientHttpResponse(HttpURLConnection connection) {
this.connection = connection;
@@ -78,12 +82,19 @@ final class SimpleClientHttpResponse extends AbstractClientHttpResponse {
@Override
public InputStream getBody() throws IOException {
InputStream errorStream = this.connection.getErrorStream();
- return (errorStream != null ? errorStream : this.connection.getInputStream());
+ this.responseStream = (errorStream != null ? errorStream : this.connection.getInputStream());
+ return this.responseStream;
}
@Override
public void close() {
- this.connection.disconnect();
+ if (this.responseStream != null) {
+ try {
+ StreamUtils.drain(this.responseStream);
+ this.responseStream.close();
+ }
+ catch (IOException e) { }
+ }
}
}
diff --git a/spring-web/src/main/java/org/springframework/http/client/SimpleStreamingAsyncClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/SimpleStreamingAsyncClientHttpRequest.java
index 0417eef0..e3327d23 100644
--- a/spring-web/src/main/java/org/springframework/http/client/SimpleStreamingAsyncClientHttpRequest.java
+++ b/spring-web/src/main/java/org/springframework/http/client/SimpleStreamingAsyncClientHttpRequest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -108,6 +108,8 @@ final class SimpleStreamingAsyncClientHttpRequest extends AbstractAsyncClientHtt
else {
SimpleBufferingClientHttpRequest.addHeaders(connection, headers);
connection.connect();
+ // Immediately trigger the request in a no-output scenario as well
+ connection.getResponseCode();
}
}
catch (IOException ex) {
diff --git a/spring-web/src/main/java/org/springframework/http/client/SimpleStreamingClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/SimpleStreamingClientHttpRequest.java
index 5e871d00..98ca2902 100644
--- a/spring-web/src/main/java/org/springframework/http/client/SimpleStreamingClientHttpRequest.java
+++ b/spring-web/src/main/java/org/springframework/http/client/SimpleStreamingClientHttpRequest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +94,8 @@ final class SimpleStreamingClientHttpRequest extends AbstractClientHttpRequest {
else {
SimpleBufferingClientHttpRequest.addHeaders(this.connection, headers);
this.connection.connect();
+ // Immediately trigger the request in a no-output scenario as well
+ this.connection.getResponseCode();
}
}
catch (IOException ex) {
diff --git a/spring-web/src/main/java/org/springframework/http/client/support/BasicAuthorizationInterceptor.java b/spring-web/src/main/java/org/springframework/http/client/support/BasicAuthorizationInterceptor.java
new file mode 100644
index 00000000..ebf4a536
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/http/client/support/BasicAuthorizationInterceptor.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.http.client.support;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+
+import org.springframework.http.HttpRequest;
+import org.springframework.http.client.ClientHttpRequestExecution;
+import org.springframework.http.client.ClientHttpRequestInterceptor;
+import org.springframework.http.client.ClientHttpResponse;
+import org.springframework.util.Assert;
+import org.springframework.util.Base64Utils;
+
+/**
+ * {@link ClientHttpRequestInterceptor} to apply a BASIC authorization header.
+ *
+ * @author Phillip Webb
+ * @since 4.3.1
+ */
+public class BasicAuthorizationInterceptor implements ClientHttpRequestInterceptor {
+
+ private static final Charset UTF_8 = Charset.forName("UTF-8");
+
+ private final String username;
+
+ private final String password;
+
+
+ /**
+ * Create a new interceptor which adds a BASIC authorization header
+ * for the given username and password.
+ * @param username the username to use
+ * @param password the password to use
+ */
+ public BasicAuthorizationInterceptor(String username, String password) {
+ Assert.hasLength(username, "Username must not be empty");
+ this.username = username;
+ this.password = (password != null ? password : "");
+ }
+
+
+ @Override
+ public ClientHttpResponse intercept(HttpRequest request, byte[] body,
+ ClientHttpRequestExecution execution) throws IOException {
+
+ String token = Base64Utils.encodeToString((this.username + ":" + this.password).getBytes(UTF_8));
+ request.getHeaders().add("Authorization", "Basic " + token);
+ return execution.execute(request, body);
+ }
+
+}
diff --git a/spring-web/src/main/java/org/springframework/http/client/support/InterceptingAsyncHttpAccessor.java b/spring-web/src/main/java/org/springframework/http/client/support/InterceptingAsyncHttpAccessor.java
new file mode 100644
index 00000000..fd59f604
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/http/client/support/InterceptingAsyncHttpAccessor.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.http.client.support;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.http.client.AsyncClientHttpRequestFactory;
+import org.springframework.http.client.AsyncClientHttpRequestInterceptor;
+import org.springframework.http.client.InterceptingAsyncClientHttpRequestFactory;
+import org.springframework.util.CollectionUtils;
+
+/**
+ * The HTTP accessor that extends the base {@link AsyncHttpAccessor} with
+ * request intercepting functionality.
+ *
+ * @author Jakub Narloch
+ * @author Rossen Stoyanchev
+ * @since 4.3
+ */
+public abstract class InterceptingAsyncHttpAccessor extends AsyncHttpAccessor {
+
+ private List<AsyncClientHttpRequestInterceptor> interceptors =
+ new ArrayList<AsyncClientHttpRequestInterceptor>();
+
+
+ /**
+ * Set the request interceptors that this accessor should use.
+ * @param interceptors the list of interceptors
+ */
+ public void setInterceptors(List<AsyncClientHttpRequestInterceptor> interceptors) {
+ this.interceptors = interceptors;
+ }
+
+ /**
+ * Return the request interceptor that this accessor uses.
+ */
+ public List<AsyncClientHttpRequestInterceptor> getInterceptors() {
+ return this.interceptors;
+ }
+
+
+ @Override
+ public AsyncClientHttpRequestFactory getAsyncRequestFactory() {
+ AsyncClientHttpRequestFactory delegate = super.getAsyncRequestFactory();
+ if (!CollectionUtils.isEmpty(getInterceptors())) {
+ return new InterceptingAsyncClientHttpRequestFactory(delegate, getInterceptors());
+ }
+ else {
+ return delegate;
+ }
+ }
+
+}
diff --git a/spring-web/src/main/java/org/springframework/http/converter/AbstractHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/AbstractHttpMessageConverter.java
index d63d2870..e990fcd7 100644
--- a/spring-web/src/main/java/org/springframework/http/converter/AbstractHttpMessageConverter.java
+++ b/spring-web/src/main/java/org/springframework/http/converter/AbstractHttpMessageConverter.java
@@ -18,6 +18,7 @@ package org.springframework.http.converter;
import java.io.IOException;
import java.io.OutputStream;
+import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -42,6 +43,7 @@ import org.springframework.util.Assert;
*
* @author Arjen Poutsma
* @author Juergen Hoeller
+ * @author Sebastien Deleuze
* @since 3.0
*/
public abstract class AbstractHttpMessageConverter<T> implements HttpMessageConverter<T> {
@@ -51,6 +53,8 @@ public abstract class AbstractHttpMessageConverter<T> implements HttpMessageConv
private List<MediaType> supportedMediaTypes = Collections.emptyList();
+ private Charset defaultCharset;
+
/**
* Construct an {@code AbstractHttpMessageConverter} with no supported media types.
@@ -75,6 +79,18 @@ public abstract class AbstractHttpMessageConverter<T> implements HttpMessageConv
setSupportedMediaTypes(Arrays.asList(supportedMediaTypes));
}
+ /**
+ * Construct an {@code AbstractHttpMessageConverter} with a default charset and
+ * multiple supported media types.
+ * @param defaultCharset the default character set
+ * @param supportedMediaTypes the supported media types
+ * @since 4.3
+ */
+ protected AbstractHttpMessageConverter(Charset defaultCharset, MediaType... supportedMediaTypes) {
+ this.defaultCharset = defaultCharset;
+ setSupportedMediaTypes(Arrays.asList(supportedMediaTypes));
+ }
+
/**
* Set the list of {@link MediaType} objects supported by this converter.
@@ -89,6 +105,22 @@ public abstract class AbstractHttpMessageConverter<T> implements HttpMessageConv
return Collections.unmodifiableList(this.supportedMediaTypes);
}
+ /**
+ * Set the default character set, if any.
+ * @since 4.3
+ */
+ public void setDefaultCharset(Charset defaultCharset) {
+ this.defaultCharset = defaultCharset;
+ }
+
+ /**
+ * Return the default character set, if any.
+ * @since 4.3
+ */
+ public Charset getDefaultCharset() {
+ return this.defaultCharset;
+ }
+
/**
* This implementation checks if the given class is {@linkplain #supports(Class) supported},
@@ -200,7 +232,8 @@ public abstract class AbstractHttpMessageConverter<T> implements HttpMessageConv
/**
* Add default headers to the output message.
* <p>This implementation delegates to {@link #getDefaultContentType(Object)} if a content
- * type was not provided, calls {@link #getContentLength}, and sets the corresponding headers.
+ * type was not provided, set if necessary the default character set, calls
+ * {@link #getContentLength}, and sets the corresponding headers.
* @since 4.2
*/
protected void addDefaultHeaders(HttpHeaders headers, T t, MediaType contentType) throws IOException{
@@ -214,6 +247,12 @@ public abstract class AbstractHttpMessageConverter<T> implements HttpMessageConv
contentTypeToUse = (mediaType != null ? mediaType : contentTypeToUse);
}
if (contentTypeToUse != null) {
+ if (contentTypeToUse.getCharset() == null) {
+ Charset defaultCharset = getDefaultCharset();
+ if (defaultCharset != null) {
+ contentTypeToUse = new MediaType(contentTypeToUse, defaultCharset);
+ }
+ }
headers.setContentType(contentTypeToUse);
}
}
diff --git a/spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java
index fa2d0e4a..643a8713 100644
--- a/spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java
+++ b/spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java
@@ -82,6 +82,7 @@ import org.springframework.util.StringUtils;
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
+ * @author Juergen Hoeller
* @since 3.0
* @see MultiValueMap
*/
@@ -90,14 +91,14 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
- private Charset charset = DEFAULT_CHARSET;
-
- private Charset multipartCharset;
-
private List<MediaType> supportedMediaTypes = new ArrayList<MediaType>();
private List<HttpMessageConverter<?>> partConverters = new ArrayList<HttpMessageConverter<?>>();
+ private Charset charset = DEFAULT_CHARSET;
+
+ private Charset multipartCharset;
+
public FormHttpMessageConverter() {
this.supportedMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
@@ -108,30 +109,10 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
stringHttpMessageConverter.setWriteAcceptCharset(false);
this.partConverters.add(stringHttpMessageConverter);
this.partConverters.add(new ResourceHttpMessageConverter());
- }
-
- /**
- * Set the default character set to use for reading and writing form data when
- * the request or response Content-Type header does not explicitly specify it.
- * <p>By default this is set to "UTF-8".
- */
- public void setCharset(Charset charset) {
- this.charset = charset;
+ applyDefaultCharset();
}
- /**
- * Set the character set to use when writing multipart data to encode file
- * names. Encoding is based on the encoded-word syntax defined in RFC 2047
- * and relies on {@code MimeUtility} from "javax.mail".
- * <p>If not set file names will be encoded as US-ASCII.
- * @param multipartCharset the charset to use
- * @since 4.1.1
- * @see <a href="http://en.wikipedia.org/wiki/MIME#Encoded-Word">Encoded-Word</a>
- */
- public void setMultipartCharset(Charset multipartCharset) {
- this.multipartCharset = multipartCharset;
- }
/**
* Set the list of {@link MediaType} objects supported by this converter.
@@ -163,6 +144,49 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
this.partConverters.add(partConverter);
}
+ /**
+ * Set the default character set to use for reading and writing form data when
+ * the request or response Content-Type header does not explicitly specify it.
+ * <p>By default this is set to "UTF-8". As of 4.3, it will also be used as
+ * the default charset for the conversion of text bodies in a multipart request.
+ * In contrast to this, {@link #setMultipartCharset} only affects the encoding of
+ * <i>file names</i> in a multipart request according to the encoded-word syntax.
+ */
+ public void setCharset(Charset charset) {
+ if (charset != this.charset) {
+ this.charset = (charset != null ? charset : DEFAULT_CHARSET);
+ applyDefaultCharset();
+ }
+ }
+
+ /**
+ * Apply the configured charset as a default to registered part converters.
+ */
+ private void applyDefaultCharset() {
+ for (HttpMessageConverter<?> candidate : this.partConverters) {
+ if (candidate instanceof AbstractHttpMessageConverter) {
+ AbstractHttpMessageConverter<?> converter = (AbstractHttpMessageConverter<?>) candidate;
+ // Only override default charset if the converter operates with a charset to begin with...
+ if (converter.getDefaultCharset() != null) {
+ converter.setDefaultCharset(this.charset);
+ }
+ }
+ }
+ }
+
+ /**
+ * Set the character set to use when writing multipart data to encode file
+ * names. Encoding is based on the encoded-word syntax defined in RFC 2047
+ * and relies on {@code MimeUtility} from "javax.mail".
+ * <p>If not set file names will be encoded as US-ASCII.
+ * @param multipartCharset the charset to use
+ * @since 4.1.1
+ * @see <a href="http://en.wikipedia.org/wiki/MIME#Encoded-Word">Encoded-Word</a>
+ */
+ public void setMultipartCharset(Charset multipartCharset) {
+ this.multipartCharset = multipartCharset;
+ }
+
@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
@@ -202,7 +226,7 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
MediaType contentType = inputMessage.getHeaders().getContentType();
- Charset charset = (contentType.getCharSet() != null ? contentType.getCharSet() : this.charset);
+ Charset charset = (contentType.getCharset() != null ? contentType.getCharset() : this.charset);
String body = StreamUtils.copyToString(inputMessage.getBody(), charset);
String[] pairs = StringUtils.tokenizeToStringArray(body, "&");
@@ -255,7 +279,7 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
Charset charset;
if (contentType != null) {
outputMessage.getHeaders().setContentType(contentType);
- charset = (contentType.getCharSet() != null ? contentType.getCharSet() : this.charset);
+ charset = (contentType.getCharset() != null ? contentType.getCharset() : this.charset);
}
else {
outputMessage.getHeaders().setContentType(MediaType.APPLICATION_FORM_URLENCODED);
diff --git a/spring-web/src/main/java/org/springframework/http/converter/ObjectToStringHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/ObjectToStringHttpMessageConverter.java
index 5fab4fec..2e01e5eb 100644
--- a/spring-web/src/main/java/org/springframework/http/converter/ObjectToStringHttpMessageConverter.java
+++ b/spring-web/src/main/java/org/springframework/http/converter/ObjectToStringHttpMessageConverter.java
@@ -71,7 +71,7 @@ public class ObjectToStringHttpMessageConverter extends AbstractHttpMessageConve
* @param defaultCharset the default charset
*/
public ObjectToStringHttpMessageConverter(ConversionService conversionService, Charset defaultCharset) {
- super(new MediaType("text", "plain", defaultCharset));
+ super(defaultCharset, MediaType.TEXT_PLAIN);
Assert.notNull(conversionService, "ConversionService is required");
this.conversionService = conversionService;
diff --git a/spring-web/src/main/java/org/springframework/http/converter/ResourceHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/ResourceHttpMessageConverter.java
index cc8da360..93bd3a3a 100644
--- a/spring-web/src/main/java/org/springframework/http/converter/ResourceHttpMessageConverter.java
+++ b/spring-web/src/main/java/org/springframework/http/converter/ResourceHttpMessageConverter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
package org.springframework.http.converter;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import javax.activation.FileTypeMap;
@@ -33,12 +34,14 @@ import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
/**
- * Implementation of {@link HttpMessageConverter} that can read and write {@link Resource Resources}.
+ * Implementation of {@link HttpMessageConverter} that can read and write {@link Resource Resources}
+ * and supports byte range requests.
*
* <p>By default, this converter can read all media types. The Java Activation Framework (JAF) -
* if available - is used to determine the {@code Content-Type} of written resources.
* If JAF is not available, {@code application/octet-stream} is used.
*
+ *
* @author Arjen Poutsma
* @author Juergen Hoeller
* @author Kazuki Shimizu
@@ -64,7 +67,7 @@ public class ResourceHttpMessageConverter extends AbstractHttpMessageConverter<R
protected Resource readInternal(Class<? extends Resource> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
- if (InputStreamResource.class == clazz){
+ if (InputStreamResource.class == clazz) {
return new InputStreamResource(inputMessage.getBody());
}
else if (clazz.isAssignableFrom(ByteArrayResource.class)) {
@@ -90,25 +93,42 @@ public class ResourceHttpMessageConverter extends AbstractHttpMessageConverter<R
protected Long getContentLength(Resource resource, MediaType contentType) throws IOException {
// Don't try to determine contentLength on InputStreamResource - cannot be read afterwards...
// Note: custom InputStreamResource subclasses could provide a pre-calculated content length!
- return (InputStreamResource.class == resource.getClass() ? null : resource.contentLength());
+ if (InputStreamResource.class == resource.getClass()) {
+ return null;
+ }
+ long contentLength = resource.contentLength();
+ return (contentLength < 0 ? null : contentLength);
}
@Override
protected void writeInternal(Resource resource, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
- InputStream in = resource.getInputStream();
+ writeContent(resource, outputMessage);
+ }
+
+ protected void writeContent(Resource resource, HttpOutputMessage outputMessage)
+ throws IOException, HttpMessageNotWritableException {
try {
- StreamUtils.copy(in, outputMessage.getBody());
- }
- finally {
+ InputStream in = resource.getInputStream();
try {
- in.close();
+ StreamUtils.copy(in, outputMessage.getBody());
+ }
+ catch (NullPointerException ex) {
+ // ignore, see SPR-13620
}
- catch (IOException ex) {
+ finally {
+ try {
+ in.close();
+ }
+ catch (Throwable ex) {
+ // ignore, see SPR-12999
+ }
}
}
- outputMessage.getBody().flush();
+ catch (FileNotFoundException ex) {
+ // ignore, see SPR-12999
+ }
}
diff --git a/spring-web/src/main/java/org/springframework/http/converter/ResourceRegionHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/ResourceRegionHttpMessageConverter.java
new file mode 100644
index 00000000..5c12092e
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/http/converter/ResourceRegionHttpMessageConverter.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.http.converter;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Collection;
+
+import org.springframework.core.io.support.ResourceRegion;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpInputMessage;
+import org.springframework.http.HttpOutputMessage;
+import org.springframework.http.MediaType;
+import org.springframework.util.Assert;
+import org.springframework.util.MimeTypeUtils;
+import org.springframework.util.StreamUtils;
+
+/**
+ * Implementation of {@link HttpMessageConverter} that can write a single {@link ResourceRegion},
+ * or Collections of {@link ResourceRegion ResourceRegions}.
+ *
+ * @author Brian Clozel
+ * @since 4.3
+ */
+public class ResourceRegionHttpMessageConverter extends AbstractGenericHttpMessageConverter<Object> {
+
+ public ResourceRegionHttpMessageConverter() {
+ super(MediaType.ALL);
+ }
+
+
+ @Override
+ protected boolean supports(Class<?> clazz) {
+ // should not be called as we override canRead/canWrite
+ return false;
+ }
+
+ @Override
+ public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {
+ return false;
+ }
+
+ @Override
+ public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage)
+ throws IOException, HttpMessageNotReadableException {
+
+ return null;
+ }
+
+ @Override
+ protected ResourceRegion readInternal(Class<?> clazz, HttpInputMessage inputMessage)
+ throws IOException, HttpMessageNotReadableException {
+
+ return null;
+ }
+
+ @Override
+ public boolean canWrite(Class<?> clazz, MediaType mediaType) {
+ return canWrite(clazz, null, mediaType);
+ }
+
+ @Override
+ public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) {
+ if (!(type instanceof ParameterizedType)) {
+ return ResourceRegion.class.isAssignableFrom((Class) type);
+ }
+ ParameterizedType parameterizedType = (ParameterizedType) type;
+ if (!(parameterizedType.getRawType() instanceof Class)) {
+ return false;
+ }
+ Class<?> rawType = (Class<?>) parameterizedType.getRawType();
+ if (!(Collection.class.isAssignableFrom(rawType))) {
+ return false;
+ }
+ if (parameterizedType.getActualTypeArguments().length != 1) {
+ return false;
+ }
+ Type typeArgument = parameterizedType.getActualTypeArguments()[0];
+ if (!(typeArgument instanceof Class)) {
+ return false;
+ }
+ Class<?> typeArgumentClass = (Class<?>) typeArgument;
+ return typeArgumentClass.isAssignableFrom(ResourceRegion.class);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage)
+ throws IOException, HttpMessageNotWritableException {
+
+ if (object instanceof ResourceRegion) {
+ writeResourceRegion((ResourceRegion) object, outputMessage);
+ }
+ else {
+ Collection<ResourceRegion> regions = (Collection<ResourceRegion>) object;
+ if(regions.size() == 1) {
+ writeResourceRegion(regions.iterator().next(), outputMessage);
+ }
+ else {
+ writeResourceRegionCollection((Collection<ResourceRegion>) object, outputMessage);
+ }
+ }
+ }
+
+ protected void writeResourceRegion(ResourceRegion region, HttpOutputMessage outputMessage) throws IOException {
+ Assert.notNull(region, "ResourceRegion must not be null");
+ HttpHeaders responseHeaders = outputMessage.getHeaders();
+ long start = region.getPosition();
+ long end = start + region.getCount() - 1;
+ Long resourceLength = region.getResource().contentLength();
+ end = Math.min(end, resourceLength - 1);
+ long rangeLength = end - start + 1;
+ responseHeaders.add("Content-Range", "bytes " + start + "-" + end + "/" + resourceLength);
+ responseHeaders.setContentLength(rangeLength);
+ InputStream in = region.getResource().getInputStream();
+ try {
+ StreamUtils.copyRange(in, outputMessage.getBody(), start, end);
+ }
+ finally {
+ try {
+ in.close();
+ }
+ catch (IOException ex) {
+ // ignore
+ }
+ }
+ }
+
+ private void writeResourceRegionCollection(Collection<ResourceRegion> resourceRegions,
+ HttpOutputMessage outputMessage) throws IOException {
+
+ Assert.notNull(resourceRegions, "Collection of ResourceRegion should not be null");
+ HttpHeaders responseHeaders = outputMessage.getHeaders();
+ MediaType contentType = responseHeaders.getContentType();
+ String boundaryString = MimeTypeUtils.generateMultipartBoundaryString();
+ responseHeaders.set(HttpHeaders.CONTENT_TYPE, "multipart/byteranges; boundary=" + boundaryString);
+ OutputStream out = outputMessage.getBody();
+ for (ResourceRegion region : resourceRegions) {
+ long start = region.getPosition();
+ long end = start + region.getCount() - 1;
+ InputStream in = region.getResource().getInputStream();
+ // Writing MIME header.
+ println(out);
+ print(out, "--" + boundaryString);
+ println(out);
+ if (contentType != null) {
+ print(out, "Content-Type: " + contentType.toString());
+ println(out);
+ }
+ Long resourceLength = region.getResource().contentLength();
+ end = Math.min(end, resourceLength - 1);
+ print(out, "Content-Range: bytes " + start + "-" + end + "/" + resourceLength);
+ println(out);
+ println(out);
+ // Printing content
+ StreamUtils.copyRange(in, out, start, end);
+ }
+ println(out);
+ print(out, "--" + boundaryString + "--");
+ }
+
+
+
+ private static void println(OutputStream os) throws IOException {
+ os.write('\r');
+ os.write('\n');
+ }
+
+ private static void print(OutputStream os, String buf) throws IOException {
+ os.write(buf.getBytes("US-ASCII"));
+ }
+
+}
diff --git a/spring-web/src/main/java/org/springframework/http/converter/StringHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/StringHttpMessageConverter.java
index 15c2693c..dc150054 100644
--- a/spring-web/src/main/java/org/springframework/http/converter/StringHttpMessageConverter.java
+++ b/spring-web/src/main/java/org/springframework/http/converter/StringHttpMessageConverter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,6 +35,7 @@ import org.springframework.util.StreamUtils;
* by setting the {@link #setSupportedMediaTypes supportedMediaTypes} property.
*
* @author Arjen Poutsma
+ * @author Juergen Hoeller
* @since 3.0
*/
public class StringHttpMessageConverter extends AbstractHttpMessageConverter<String> {
@@ -42,8 +43,6 @@ public class StringHttpMessageConverter extends AbstractHttpMessageConverter<Str
public static final Charset DEFAULT_CHARSET = Charset.forName("ISO-8859-1");
- private final Charset defaultCharset;
-
private final List<Charset> availableCharsets;
private boolean writeAcceptCharset = true;
@@ -62,8 +61,7 @@ public class StringHttpMessageConverter extends AbstractHttpMessageConverter<Str
* type does not specify one.
*/
public StringHttpMessageConverter(Charset defaultCharset) {
- super(new MediaType("text", "plain", defaultCharset), MediaType.ALL);
- this.defaultCharset = defaultCharset;
+ super(defaultCharset, MediaType.TEXT_PLAIN, MediaType.ALL);
this.availableCharsets = new ArrayList<Charset>(Charset.availableCharsets().values());
}
@@ -121,11 +119,11 @@ public class StringHttpMessageConverter extends AbstractHttpMessageConverter<Str
}
private Charset getContentTypeCharset(MediaType contentType) {
- if (contentType != null && contentType.getCharSet() != null) {
- return contentType.getCharSet();
+ if (contentType != null && contentType.getCharset() != null) {
+ return contentType.getCharset();
}
else {
- return this.defaultCharset;
+ return getDefaultCharset();
}
}
diff --git a/spring-web/src/main/java/org/springframework/http/converter/feed/AbstractWireFeedHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/feed/AbstractWireFeedHttpMessageConverter.java
index 834fc820..9daa030f 100644
--- a/spring-web/src/main/java/org/springframework/http/converter/feed/AbstractWireFeedHttpMessageConverter.java
+++ b/spring-web/src/main/java/org/springframework/http/converter/feed/AbstractWireFeedHttpMessageConverter.java
@@ -66,7 +66,7 @@ public abstract class AbstractWireFeedHttpMessageConverter<T extends WireFeed> e
WireFeedInput feedInput = new WireFeedInput();
MediaType contentType = inputMessage.getHeaders().getContentType();
Charset charset =
- (contentType != null && contentType.getCharSet() != null? contentType.getCharSet() : DEFAULT_CHARSET);
+ (contentType != null && contentType.getCharset() != null? contentType.getCharset() : DEFAULT_CHARSET);
try {
Reader reader = new InputStreamReader(inputMessage.getBody(), charset);
return (T) feedInput.build(reader);
diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java
index f29dc806..2d3dd80e 100644
--- a/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java
+++ b/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java
@@ -17,22 +17,25 @@
package org.springframework.http.converter.json;
import java.io.IOException;
+import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
import java.nio.charset.Charset;
import java.util.concurrent.atomic.AtomicReference;
import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.core.PrettyPrinter;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.type.TypeFactory;
+import org.springframework.core.ResolvableType;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
@@ -41,14 +44,13 @@ import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.util.Assert;
-import org.springframework.util.ClassUtils;
import org.springframework.util.TypeUtils;
/**
* Abstract base class for Jackson based and content type independent
* {@link HttpMessageConverter} implementations.
*
- * <p>Compatible with Jackson 2.1 to 2.6.
+ * <p>Compatible with Jackson 2.6 and higher, as of Spring 4.3.
*
* @author Arjen Poutsma
* @author Keith Donald
@@ -61,14 +63,6 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
- // Check for Jackson 2.3's overloaded canDeserialize/canSerialize variants with cause reference
- private static final boolean jackson23Available = ClassUtils.hasMethod(ObjectMapper.class,
- "canDeserialize", JavaType.class, AtomicReference.class);
-
- // Check for Jackson 2.6+ for support of generic type aware serialization of polymorphic collections
- private static final boolean jackson26Available = ClassUtils.hasMethod(ObjectMapper.class,
- "setDefaultPrettyPrinter", PrettyPrinter.class);
-
protected ObjectMapper objectMapper;
@@ -77,16 +71,19 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener
protected AbstractJackson2HttpMessageConverter(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
+ setDefaultCharset(DEFAULT_CHARSET);
}
protected AbstractJackson2HttpMessageConverter(ObjectMapper objectMapper, MediaType supportedMediaType) {
super(supportedMediaType);
this.objectMapper = objectMapper;
+ setDefaultCharset(DEFAULT_CHARSET);
}
protected AbstractJackson2HttpMessageConverter(ObjectMapper objectMapper, MediaType... supportedMediaTypes) {
super(supportedMediaTypes);
this.objectMapper = objectMapper;
+ setDefaultCharset(DEFAULT_CHARSET);
}
@@ -146,23 +143,14 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener
return false;
}
JavaType javaType = getJavaType(type, contextClass);
- if (!jackson23Available || !logger.isWarnEnabled()) {
+ if (!logger.isWarnEnabled()) {
return this.objectMapper.canDeserialize(javaType);
}
AtomicReference<Throwable> causeRef = new AtomicReference<Throwable>();
if (this.objectMapper.canDeserialize(javaType, causeRef)) {
return true;
}
- Throwable cause = causeRef.get();
- if (cause != null) {
- String msg = "Failed to evaluate Jackson deserialization for type " + javaType;
- if (logger.isDebugEnabled()) {
- logger.warn(msg, cause);
- }
- else {
- logger.warn(msg + ": " + cause);
- }
- }
+ logWarningIfNecessary(javaType, causeRef.get());
return false;
}
@@ -171,16 +159,29 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener
if (!canWrite(mediaType)) {
return false;
}
- if (!jackson23Available || !logger.isWarnEnabled()) {
+ if (!logger.isWarnEnabled()) {
return this.objectMapper.canSerialize(clazz);
}
AtomicReference<Throwable> causeRef = new AtomicReference<Throwable>();
if (this.objectMapper.canSerialize(clazz, causeRef)) {
return true;
}
- Throwable cause = causeRef.get();
- if (cause != null) {
- String msg = "Failed to evaluate Jackson serialization for type [" + clazz + "]";
+ logWarningIfNecessary(clazz, causeRef.get());
+ return false;
+ }
+
+ /**
+ * Determine whether to log the given exception coming from a
+ * {@link ObjectMapper#canDeserialize} / {@link ObjectMapper#canSerialize} check.
+ * @param type the class that Jackson tested for (de-)serializability
+ * @param cause the Jackson-thrown exception to evaluate
+ * (typically a {@link JsonMappingException})
+ * @since 4.3
+ */
+ protected void logWarningIfNecessary(Type type, Throwable cause) {
+ if (cause != null && !(cause instanceof JsonMappingException && cause.getMessage().startsWith("Can not find"))) {
+ String msg = "Failed to evaluate Jackson " + (type instanceof JavaType ? "de" : "") +
+ "serialization for type [" + type + "]";
if (logger.isDebugEnabled()) {
logger.warn(msg, cause);
}
@@ -188,7 +189,6 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener
logger.warn(msg + ": " + cause);
}
}
- return false;
}
@Override
@@ -213,13 +213,12 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener
return readJavaType(javaType, inputMessage);
}
- @SuppressWarnings("deprecation")
private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) {
try {
if (inputMessage instanceof MappingJacksonInputMessage) {
Class<?> deserializationView = ((MappingJacksonInputMessage) inputMessage).getDeserializationView();
if (deserializationView != null) {
- return this.objectMapper.readerWithView(deserializationView).withType(javaType).
+ return this.objectMapper.readerWithView(deserializationView).forType(javaType).
readValue(inputMessage.getBody());
}
}
@@ -231,7 +230,6 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener
}
@Override
- @SuppressWarnings("deprecation")
protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
@@ -250,7 +248,7 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener
serializationView = container.getSerializationView();
filters = container.getFilters();
}
- if (jackson26Available && type != null && value != null && TypeUtils.isAssignable(type, value.getClass())) {
+ if (type != null && value != null && TypeUtils.isAssignable(type, value.getClass())) {
javaType = getJavaType(type, null);
}
ObjectWriter objectWriter;
@@ -264,7 +262,7 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener
objectWriter = this.objectMapper.writer();
}
if (javaType != null && javaType.isContainerType()) {
- objectWriter = objectWriter.withType(javaType);
+ objectWriter = objectWriter.forType(javaType);
}
objectWriter.writeValue(generator, value);
@@ -313,10 +311,62 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener
* @return the Jackson JavaType
*/
protected JavaType getJavaType(Type type, Class<?> contextClass) {
- TypeFactory tf = this.objectMapper.getTypeFactory();
- // Conditional call because Jackson 2.7 does not support null contextClass anymore
- // TypeVariable resolution will not work with Jackson 2.7, see SPR-13853 for more details
- return (contextClass != null ? tf.constructType(type, contextClass) : tf.constructType(type));
+ TypeFactory typeFactory = this.objectMapper.getTypeFactory();
+ if (contextClass != null) {
+ ResolvableType resolvedType = ResolvableType.forType(type);
+ if (type instanceof TypeVariable) {
+ ResolvableType resolvedTypeVariable = resolveVariable(
+ (TypeVariable<?>) type, ResolvableType.forClass(contextClass));
+ if (resolvedTypeVariable != ResolvableType.NONE) {
+ return typeFactory.constructType(resolvedTypeVariable.resolve());
+ }
+ }
+ else if (type instanceof ParameterizedType && resolvedType.hasUnresolvableGenerics()) {
+ ParameterizedType parameterizedType = (ParameterizedType) type;
+ Class<?>[] generics = new Class<?>[parameterizedType.getActualTypeArguments().length];
+ Type[] typeArguments = parameterizedType.getActualTypeArguments();
+ for (int i = 0; i < typeArguments.length; i++) {
+ Type typeArgument = typeArguments[i];
+ if (typeArgument instanceof TypeVariable) {
+ ResolvableType resolvedTypeArgument = resolveVariable(
+ (TypeVariable<?>) typeArgument, ResolvableType.forClass(contextClass));
+ if (resolvedTypeArgument != ResolvableType.NONE) {
+ generics[i] = resolvedTypeArgument.resolve();
+ }
+ else {
+ generics[i] = ResolvableType.forType(typeArgument).resolve();
+ }
+ }
+ else {
+ generics[i] = ResolvableType.forType(typeArgument).resolve();
+ }
+ }
+ return typeFactory.constructType(ResolvableType.
+ forClassWithGenerics(resolvedType.getRawClass(), generics).getType());
+ }
+ }
+ return typeFactory.constructType(type);
+ }
+
+ private ResolvableType resolveVariable(TypeVariable<?> typeVariable, ResolvableType contextType) {
+ ResolvableType resolvedType;
+ if (contextType.hasGenerics()) {
+ resolvedType = ResolvableType.forType(typeVariable, contextType);
+ if (resolvedType.resolve() != null) {
+ return resolvedType;
+ }
+ }
+ resolvedType = resolveVariable(typeVariable, contextType.getSuperType());
+ if (resolvedType.resolve() != null) {
+ return resolvedType;
+ }
+ for (ResolvableType ifc : contextType.getInterfaces()) {
+ resolvedType = resolveVariable(typeVariable, ifc);
+ if (resolvedType.resolve() != null) {
+ return resolvedType;
+ }
+ }
+ return ResolvableType.NONE;
}
/**
@@ -325,8 +375,8 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener
* @return the JSON encoding to use (never {@code null})
*/
protected JsonEncoding getJsonEncoding(MediaType contentType) {
- if (contentType != null && contentType.getCharSet() != null) {
- Charset charset = contentType.getCharSet();
+ if (contentType != null && contentType.getCharset() != null) {
+ Charset charset = contentType.getCharset();
for (JsonEncoding encoding : JsonEncoding.values()) {
if (charset.name().equals(encoding.getJavaName())) {
return encoding;
diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/GsonHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/json/GsonHttpMessageConverter.java
index 38f7f432..d82bd28d 100644
--- a/spring-web/src/main/java/org/springframework/http/converter/json/GsonHttpMessageConverter.java
+++ b/spring-web/src/main/java/org/springframework/http/converter/json/GsonHttpMessageConverter.java
@@ -68,7 +68,8 @@ public class GsonHttpMessageConverter extends AbstractGenericHttpMessageConverte
* Construct a new {@code GsonHttpMessageConverter}.
*/
public GsonHttpMessageConverter() {
- super(MediaType.APPLICATION_JSON_UTF8, new MediaType("application", "*+json", DEFAULT_CHARSET));
+ super(MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
+ this.setDefaultCharset(DEFAULT_CHARSET);
}
@@ -177,10 +178,10 @@ public class GsonHttpMessageConverter extends AbstractGenericHttpMessageConverte
}
private Charset getCharset(HttpHeaders headers) {
- if (headers == null || headers.getContentType() == null || headers.getContentType().getCharSet() == null) {
+ if (headers == null || headers.getContentType() == null || headers.getContentType().getCharset() == null) {
return DEFAULT_CHARSET;
}
- return headers.getContentType().getCharSet();
+ return headers.getContentType().getCharset();
}
@Override
diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java b/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java
index b2ee1e5b..046a3b1e 100644
--- a/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java
+++ b/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -47,6 +47,8 @@ import com.fasterxml.jackson.databind.cfg.HandlerInstantiator;
import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.FilterProvider;
+import com.fasterxml.jackson.dataformat.xml.JacksonXmlModule;
+import com.fasterxml.jackson.dataformat.xml.XmlFactory;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import org.springframework.beans.BeanUtils;
@@ -73,9 +75,10 @@ import org.springframework.util.StringUtils;
* <li><a href="https://github.com/FasterXML/jackson-datatype-jdk8">jackson-datatype-jdk8</a>: support for other Java 8 types like {@link java.util.Optional}</li>
* <li><a href="https://github.com/FasterXML/jackson-datatype-jsr310">jackson-datatype-jsr310</a>: support for Java 8 Date & Time API types</li>
* <li><a href="https://github.com/FasterXML/jackson-datatype-joda">jackson-datatype-joda</a>: support for Joda-Time types</li>
+ * <li><a href="https://github.com/FasterXML/jackson-module-kotlin">jackson-module-kotlin</a>: support for Kotlin classes and data classes</li>
* </ul>
*
- * <p>Tested against Jackson 2.4, 2.5, 2.6; compatible with Jackson 2.0 and higher.
+ * <p>Compatible with Jackson 2.6 and higher, as of Spring 4.3.
*
* @author Sebastien Deleuze
* @author Juergen Hoeller
@@ -127,6 +130,8 @@ public class Jackson2ObjectMapperBuilder {
private ApplicationContext applicationContext;
+ private Boolean defaultUseWrapper;
+
/**
* If set to {@code true}, an {@link XmlMapper} will be created using its
@@ -279,8 +284,7 @@ public class Jackson2ObjectMapperBuilder {
/**
* Configure custom serializers. Each serializer is registered for the type
- * returned by {@link JsonSerializer#handledType()}, which must not be
- * {@code null}.
+ * returned by {@link JsonSerializer#handledType()}, which must not be {@code null}.
* @see #serializersByType(Map)
*/
public Jackson2ObjectMapperBuilder serializers(JsonSerializer<?>... serializers) {
@@ -320,6 +324,25 @@ public class Jackson2ObjectMapperBuilder {
}
/**
+ * Configure custom deserializers. Each deserializer is registered for the type
+ * returned by {@link JsonDeserializer#handledType()}, which must not be {@code null}.
+ * @since 4.3
+ * @see #deserializersByType(Map)
+ */
+ public Jackson2ObjectMapperBuilder deserializers(JsonDeserializer<?>... deserializers) {
+ if (deserializers != null) {
+ for (JsonDeserializer<?> deserializer : deserializers) {
+ Class<?> handledType = deserializer.handledType();
+ if (handledType == null || handledType == Object.class) {
+ throw new IllegalArgumentException("Unknown handled type in " + deserializer.getClass().getName());
+ }
+ this.deserializers.put(deserializer.handledType(), deserializer);
+ }
+ }
+ return this;
+ }
+
+ /**
* Configure a custom deserializer for the given type.
* @since 4.1.2
*/
@@ -393,6 +416,16 @@ public class Jackson2ObjectMapperBuilder {
}
/**
+ * Define if a wrapper will be used for indexed (List, array) properties or not by
+ * default (only applies to {@link XmlMapper}).
+ * @since 4.3
+ */
+ public Jackson2ObjectMapperBuilder defaultUseWrapper(boolean defaultUseWrapper) {
+ this.defaultUseWrapper = defaultUseWrapper;
+ return this;
+ }
+
+ /**
* Specify features to enable.
* @see com.fasterxml.jackson.core.JsonParser.Feature
* @see com.fasterxml.jackson.core.JsonGenerator.Feature
@@ -547,7 +580,9 @@ public class Jackson2ObjectMapperBuilder {
public <T extends ObjectMapper> T build() {
ObjectMapper mapper;
if (this.createXmlMapper) {
- mapper = new XmlObjectMapperInitializer().create();
+ mapper = (this.defaultUseWrapper != null ?
+ new XmlObjectMapperInitializer().create(this.defaultUseWrapper) :
+ new XmlObjectMapperInitializer().create());
}
else {
mapper = new ObjectMapper();
@@ -561,7 +596,6 @@ public class Jackson2ObjectMapperBuilder {
* settings. This can be applied to any number of {@code ObjectMappers}.
* @param objectMapper the ObjectMapper to configure
*/
- @SuppressWarnings("deprecation")
public void configure(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "ObjectMapper must not be null");
@@ -609,13 +643,11 @@ public class Jackson2ObjectMapperBuilder {
}
if (this.filters != null) {
- // Deprecated as of Jackson 2.6, but just in favor of a fluent variant.
- objectMapper.setFilters(this.filters);
+ objectMapper.setFilterProvider(this.filters);
}
for (Class<?> target : this.mixIns.keySet()) {
- // Deprecated as of Jackson 2.5, but just in favor of a fluent variant.
- objectMapper.addMixInAnnotations(target, this.mixIns.get(target));
+ objectMapper.addMixIn(target, this.mixIns.get(target));
}
if (!this.serializers.isEmpty() || !this.deserializers.isEmpty()) {
@@ -693,7 +725,7 @@ public class Jackson2ObjectMapperBuilder {
try {
Class<? extends Module> jdk7Module = (Class<? extends Module>)
ClassUtils.forName("com.fasterxml.jackson.datatype.jdk7.Jdk7Module", this.moduleClassLoader);
- objectMapper.registerModule(BeanUtils.instantiate(jdk7Module));
+ objectMapper.registerModule(BeanUtils.instantiateClass(jdk7Module));
}
catch (ClassNotFoundException ex) {
// jackson-datatype-jdk7 not available
@@ -705,7 +737,7 @@ public class Jackson2ObjectMapperBuilder {
try {
Class<? extends Module> jdk8Module = (Class<? extends Module>)
ClassUtils.forName("com.fasterxml.jackson.datatype.jdk8.Jdk8Module", this.moduleClassLoader);
- objectMapper.registerModule(BeanUtils.instantiate(jdk8Module));
+ objectMapper.registerModule(BeanUtils.instantiateClass(jdk8Module));
}
catch (ClassNotFoundException ex) {
// jackson-datatype-jdk8 not available
@@ -717,18 +749,10 @@ public class Jackson2ObjectMapperBuilder {
try {
Class<? extends Module> javaTimeModule = (Class<? extends Module>)
ClassUtils.forName("com.fasterxml.jackson.datatype.jsr310.JavaTimeModule", this.moduleClassLoader);
- objectMapper.registerModule(BeanUtils.instantiate(javaTimeModule));
+ objectMapper.registerModule(BeanUtils.instantiateClass(javaTimeModule));
}
catch (ClassNotFoundException ex) {
- // jackson-datatype-jsr310 not available or older than 2.6
- try {
- Class<? extends Module> jsr310Module = (Class<? extends Module>)
- ClassUtils.forName("com.fasterxml.jackson.datatype.jsr310.JSR310Module", this.moduleClassLoader);
- objectMapper.registerModule(BeanUtils.instantiate(jsr310Module));
- }
- catch (ClassNotFoundException ex2) {
- // OK, jackson-datatype-jsr310 not available at all...
- }
+ // jackson-datatype-jsr310 not available
}
}
@@ -737,12 +761,24 @@ public class Jackson2ObjectMapperBuilder {
try {
Class<? extends Module> jodaModule = (Class<? extends Module>)
ClassUtils.forName("com.fasterxml.jackson.datatype.joda.JodaModule", this.moduleClassLoader);
- objectMapper.registerModule(BeanUtils.instantiate(jodaModule));
+ objectMapper.registerModule(BeanUtils.instantiateClass(jodaModule));
}
catch (ClassNotFoundException ex) {
// jackson-datatype-joda not available
}
}
+
+ // Kotlin present?
+ if (ClassUtils.isPresent("kotlin.Unit", this.moduleClassLoader)) {
+ try {
+ Class<? extends Module> kotlinModule = (Class<? extends Module>)
+ ClassUtils.forName("com.fasterxml.jackson.module.kotlin.KotlinModule", this.moduleClassLoader);
+ objectMapper.registerModule(BeanUtils.instantiateClass(kotlinModule));
+ }
+ catch (ClassNotFoundException ex) {
+ // jackson-module-kotlin not available
+ }
+ }
}
@@ -768,11 +804,21 @@ public class Jackson2ObjectMapperBuilder {
private static class XmlObjectMapperInitializer {
public ObjectMapper create() {
+ return new XmlMapper(xmlInputFactory());
+ }
+
+ public ObjectMapper create(boolean defaultUseWrapper) {
+ JacksonXmlModule module = new JacksonXmlModule();
+ module.setDefaultUseWrapper(defaultUseWrapper);
+ return new XmlMapper(new XmlFactory(xmlInputFactory()), module);
+ }
+
+ private static XMLInputFactory xmlInputFactory() {
XMLInputFactory inputFactory = XMLInputFactory.newInstance();
inputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
inputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
inputFactory.setXMLResolver(NO_OP_XML_RESOLVER);
- return new XmlMapper(inputFactory);
+ return inputFactory;
}
private static final XMLResolver NO_OP_XML_RESOLVER = new XMLResolver() {
diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperFactoryBean.java b/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperFactoryBean.java
index f17016e9..d945a7fb 100644
--- a/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperFactoryBean.java
+++ b/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperFactoryBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -116,6 +116,7 @@ import org.springframework.context.ApplicationContextAware;
* <li><a href="https://github.com/FasterXML/jackson-datatype-jdk8">jackson-datatype-jdk8</a>: support for other Java 8 types like {@link java.util.Optional}</li>
* <li><a href="https://github.com/FasterXML/jackson-datatype-jsr310">jackson-datatype-jsr310</a>: support for Java 8 Date & Time API types</li>
* <li><a href="https://github.com/FasterXML/jackson-datatype-joda">jackson-datatype-joda</a>: support for Joda-Time types</li>
+ * <li><a href="https://github.com/FasterXML/jackson-module-kotlin">jackson-module-kotlin</a>: support for Kotlin classes and data classes</li>
* </ul>
*
* <p>In case you want to configure Jackson's {@link ObjectMapper} with a custom {@link Module},
@@ -127,7 +128,7 @@ import org.springframework.context.ApplicationContextAware;
* &lt;/bean
* </pre>
*
- * <p>Tested against Jackson 2.4, 2.5, 2.6; compatible with Jackson 2.0 and higher.
+ * <p>Compatible with Jackson 2.6 and higher, as of Spring 4.3.
*
* @author <a href="mailto:dmitry.katsubo@gmail.com">Dmitry Katsubo</a>
* @author Rossen Stoyanchev
@@ -255,8 +256,7 @@ public class Jackson2ObjectMapperFactoryBean implements FactoryBean<ObjectMapper
/**
* Configure custom serializers. Each serializer is registered for the type
- * returned by {@link JsonSerializer#handledType()}, which must not be
- * {@code null}.
+ * returned by {@link JsonSerializer#handledType()}, which must not be {@code null}.
* @see #setSerializersByType(Map)
*/
public void setSerializers(JsonSerializer<?>... serializers) {
@@ -272,6 +272,16 @@ public class Jackson2ObjectMapperFactoryBean implements FactoryBean<ObjectMapper
}
/**
+ * Configure custom deserializers. Each deserializer is registered for the type
+ * returned by {@link JsonDeserializer#handledType()}, which must not be {@code null}.
+ * @since 4.3
+ * @see #setDeserializersByType(Map)
+ */
+ public void setDeserializers(JsonDeserializer<?>... deserializers) {
+ this.builder.deserializers(deserializers);
+ }
+
+ /**
* Configure custom deserializers for the given types.
*/
public void setDeserializersByType(Map<Class<?>, JsonDeserializer<?>> deserializers) {
@@ -325,6 +335,15 @@ public class Jackson2ObjectMapperFactoryBean implements FactoryBean<ObjectMapper
}
/**
+ * Define if a wrapper will be used for indexed (List, array) properties or not by
+ * default (only applies to {@link XmlMapper}).
+ * @since 4.3
+ */
+ public void setDefaultUseWrapper(boolean defaultUseWrapper) {
+ this.builder.defaultUseWrapper(defaultUseWrapper);
+ }
+
+ /**
* Specify features to enable.
* @see com.fasterxml.jackson.core.JsonParser.Feature
* @see com.fasterxml.jackson.core.JsonGenerator.Feature
diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverter.java
index e2ef12b9..4832ec0b 100644
--- a/spring-web/src/main/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverter.java
+++ b/spring-web/src/main/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverter.java
@@ -35,7 +35,7 @@ import org.springframework.http.MediaType;
*
* <p>The default constructor uses the default configuration provided by {@link Jackson2ObjectMapperBuilder}.
*
- * <p>Compatible with Jackson 2.1 to 2.6.
+ * <p>Compatible with Jackson 2.6 and higher, as of Spring 4.3.
*
* @author Arjen Poutsma
* @author Keith Donald
@@ -63,8 +63,7 @@ public class MappingJackson2HttpMessageConverter extends AbstractJackson2HttpMes
* @see Jackson2ObjectMapperBuilder#json()
*/
public MappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
- super(objectMapper, MediaType.APPLICATION_JSON_UTF8,
- new MediaType("application", "*+json", DEFAULT_CHARSET));
+ super(objectMapper, MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
}
/**
diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/SpringHandlerInstantiator.java b/spring-web/src/main/java/org/springframework/http/converter/json/SpringHandlerInstantiator.java
index f208c32a..ad4874c1 100644
--- a/spring-web/src/main/java/org/springframework/http/converter/json/SpringHandlerInstantiator.java
+++ b/spring-web/src/main/java/org/springframework/http/converter/json/SpringHandlerInstantiator.java
@@ -16,16 +16,22 @@
package org.springframework.http.converter.json;
+import com.fasterxml.jackson.annotation.ObjectIdGenerator;
+import com.fasterxml.jackson.annotation.ObjectIdResolver;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.KeyDeserializer;
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.cfg.HandlerInstantiator;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
+import com.fasterxml.jackson.databind.deser.ValueInstantiator;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
+import com.fasterxml.jackson.databind.ser.VirtualBeanPropertyWriter;
+import com.fasterxml.jackson.databind.util.Converter;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
@@ -36,6 +42,11 @@ import org.springframework.util.Assert;
* {@link KeyDeserializer}, {@link TypeResolverBuilder}, {@link TypeIdResolver})
* beans with autowiring against a Spring {@link ApplicationContext}.
*
+ * <p>As of Spring 4.3, this overrides all factory methods in {@link HandlerInstantiator},
+ * including non-abstract ones and recently introduced ones from Jackson 2.4 and 2.5:
+ * for {@link ValueInstantiator}, {@link ObjectIdGenerator}, {@link ObjectIdResolver},
+ * {@link PropertyNamingStrategy}, {@link Converter}, {@link VirtualBeanPropertyWriter}.
+ *
* @author Sebastien Deleuze
* @author Juergen Hoeller
* @since 4.1.3
@@ -83,4 +94,40 @@ public class SpringHandlerInstantiator extends HandlerInstantiator {
return (TypeIdResolver) this.beanFactory.createBean(implClass);
}
+ /** @since 4.3 */
+ @Override
+ public ValueInstantiator valueInstantiatorInstance(MapperConfig<?> config, Annotated annotated, Class<?> implClass) {
+ return (ValueInstantiator) this.beanFactory.createBean(implClass);
+ }
+
+ /** @since 4.3 */
+ @Override
+ public ObjectIdGenerator<?> objectIdGeneratorInstance(MapperConfig<?> config, Annotated annotated, Class<?> implClass) {
+ return (ObjectIdGenerator<?>) this.beanFactory.createBean(implClass);
+ }
+
+ /** @since 4.3 */
+ @Override
+ public ObjectIdResolver resolverIdGeneratorInstance(MapperConfig<?> config, Annotated annotated, Class<?> implClass) {
+ return (ObjectIdResolver) this.beanFactory.createBean(implClass);
+ }
+
+ /** @since 4.3 */
+ @Override
+ public PropertyNamingStrategy namingStrategyInstance(MapperConfig<?> config, Annotated annotated, Class<?> implClass) {
+ return (PropertyNamingStrategy) this.beanFactory.createBean(implClass);
+ }
+
+ /** @since 4.3 */
+ @Override
+ public Converter<?, ?> converterInstance(MapperConfig<?> config, Annotated annotated, Class<?> implClass) {
+ return (Converter<?, ?>) this.beanFactory.createBean(implClass);
+ }
+
+ /** @since 4.3 */
+ @Override
+ public VirtualBeanPropertyWriter virtualPropertyWriterInstance(MapperConfig<?> config, Class<?> implClass) {
+ return (VirtualBeanPropertyWriter) this.beanFactory.createBean(implClass);
+ }
+
}
diff --git a/spring-web/src/main/java/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverter.java
index 388b5533..9f023882 100644
--- a/spring-web/src/main/java/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverter.java
+++ b/spring-web/src/main/java/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverter.java
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -28,6 +28,7 @@ import com.google.protobuf.Message;
import com.google.protobuf.TextFormat;
import com.googlecode.protobuf.format.HtmlFormat;
import com.googlecode.protobuf.format.JsonFormat;
+import com.googlecode.protobuf.format.ProtobufFormatter;
import com.googlecode.protobuf.format.XmlFormat;
import org.springframework.http.HttpInputMessage;
@@ -38,7 +39,6 @@ import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.util.FileCopyUtils;
-
/**
* An {@code HttpMessageConverter} that reads and writes {@link com.google.protobuf.Message}s
* using <a href="https://developers.google.com/protocol-buffers/">Google Protocol Buffers</a>.
@@ -48,8 +48,7 @@ import org.springframework.util.FileCopyUtils;
*
* <p>To generate {@code Message} Java classes, you need to install the {@code protoc} binary.
*
- * <p>Requires Protobuf 2.5/2.6 and Protobuf Java Format 1.2.
- * (Note: Does not work with later Protobuf Java Format versions in Spring 4.2 yet.)
+ * <p>Requires Protobuf 2.6 and Protobuf Java Format 1.4, as of Spring 4.3.
*
* @author Alex Antonov
* @author Brian Clozel
@@ -67,6 +66,13 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter<M
public static final String X_PROTOBUF_MESSAGE_HEADER = "X-Protobuf-Message";
+ private static final ProtobufFormatter JSON_FORMAT = new JsonFormat();
+
+ private static final ProtobufFormatter XML_FORMAT = new XmlFormat();
+
+ private static final ProtobufFormatter HTML_FORMAT = new HtmlFormat();
+
+
private static final ConcurrentHashMap<Class<?>, Method> methodCache = new ConcurrentHashMap<Class<?>, Method>();
private final ExtensionRegistry extensionRegistry = ExtensionRegistry.newInstance();
@@ -109,7 +115,7 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter<M
if (contentType == null) {
contentType = PROTOBUF;
}
- Charset charset = contentType.getCharSet();
+ Charset charset = contentType.getCharset();
if (charset == null) {
charset = DEFAULT_CHARSET;
}
@@ -121,12 +127,10 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter<M
TextFormat.merge(reader, this.extensionRegistry, builder);
}
else if (MediaType.APPLICATION_JSON.isCompatibleWith(contentType)) {
- InputStreamReader reader = new InputStreamReader(inputMessage.getBody(), charset);
- JsonFormat.merge(reader, this.extensionRegistry, builder);
+ JSON_FORMAT.merge(inputMessage.getBody(), charset, this.extensionRegistry, builder);
}
else if (MediaType.APPLICATION_XML.isCompatibleWith(contentType)) {
- InputStreamReader reader = new InputStreamReader(inputMessage.getBody(), charset);
- XmlFormat.merge(reader, this.extensionRegistry, builder);
+ XML_FORMAT.merge(inputMessage.getBody(), charset, this.extensionRegistry, builder);
}
else {
builder.mergeFrom(inputMessage.getBody(), this.extensionRegistry);
@@ -155,7 +159,7 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter<M
if (contentType == null) {
contentType = getDefaultContentType(message);
}
- Charset charset = contentType.getCharSet();
+ Charset charset = contentType.getCharset();
if (charset == null) {
charset = DEFAULT_CHARSET;
}
@@ -166,19 +170,13 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter<M
outputStreamWriter.flush();
}
else if (MediaType.APPLICATION_JSON.isCompatibleWith(contentType)) {
- OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputMessage.getBody(), charset);
- JsonFormat.print(message, outputStreamWriter);
- outputStreamWriter.flush();
+ JSON_FORMAT.print(message, outputMessage.getBody(), charset);
}
else if (MediaType.APPLICATION_XML.isCompatibleWith(contentType)) {
- OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputMessage.getBody(), charset);
- XmlFormat.print(message, outputStreamWriter);
- outputStreamWriter.flush();
+ XML_FORMAT.print(message, outputMessage.getBody(), charset);
}
else if (MediaType.TEXT_HTML.isCompatibleWith(contentType)) {
- OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputMessage.getBody(), charset);
- HtmlFormat.print(message, outputStreamWriter);
- outputStreamWriter.flush();
+ HTML_FORMAT.print(message, outputMessage.getBody(), charset);
}
else if (PROTOBUF.isCompatibleWith(contentType)) {
setProtoHeader(outputMessage, message);
diff --git a/spring-web/src/main/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverter.java
index 53773a48..5be9e392 100644
--- a/spring-web/src/main/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverter.java
+++ b/spring-web/src/main/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverter.java
@@ -51,7 +51,7 @@ import org.springframework.util.ClassUtils;
* HttpMessageConverter} that can read and write XML using JAXB2.
*
* <p>This converter can read classes annotated with {@link XmlRootElement} and
- * {@link XmlType}, and write classes annotated with with {@link XmlRootElement},
+ * {@link XmlType}, and write classes annotated with {@link XmlRootElement},
* or subclasses thereof.
*
* <p>Note that if using Spring's Marshaller/Unmarshaller abstractions from the
@@ -195,8 +195,8 @@ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessa
}
private void setCharset(MediaType contentType, Marshaller marshaller) throws PropertyException {
- if (contentType != null && contentType.getCharSet() != null) {
- marshaller.setProperty(Marshaller.JAXB_ENCODING, contentType.getCharSet().name());
+ if (contentType != null && contentType.getCharset() != null) {
+ marshaller.setProperty(Marshaller.JAXB_ENCODING, contentType.getCharset().name());
}
}
diff --git a/spring-web/src/main/java/org/springframework/http/converter/xml/MappingJackson2XmlHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/xml/MappingJackson2XmlHttpMessageConverter.java
index 03084759..39eb13ef 100644
--- a/spring-web/src/main/java/org/springframework/http/converter/xml/MappingJackson2XmlHttpMessageConverter.java
+++ b/spring-web/src/main/java/org/springframework/http/converter/xml/MappingJackson2XmlHttpMessageConverter.java
@@ -35,7 +35,7 @@ import org.springframework.util.Assert;
*
* <p>The default constructor uses the default configuration provided by {@link Jackson2ObjectMapperBuilder}.
*
- * <p>Compatible with Jackson 2.1 to 2.6.
+ * <p>Compatible with Jackson 2.6 and higher, as of Spring 4.3.
*
* @author Sebastien Deleuze
* @since 4.1
@@ -57,9 +57,9 @@ public class MappingJackson2XmlHttpMessageConverter extends AbstractJackson2Http
* @see Jackson2ObjectMapperBuilder#xml()
*/
public MappingJackson2XmlHttpMessageConverter(ObjectMapper objectMapper) {
- super(objectMapper, new MediaType("application", "xml", DEFAULT_CHARSET),
- new MediaType("text", "xml", DEFAULT_CHARSET),
- new MediaType("application", "*+xml", DEFAULT_CHARSET));
+ super(objectMapper, new MediaType("application", "xml"),
+ new MediaType("text", "xml"),
+ new MediaType("application", "*+xml"));
Assert.isAssignable(XmlMapper.class, objectMapper.getClass());
}
diff --git a/spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java
index e9875107..8b411259 100644
--- a/spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java
+++ b/spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -175,9 +175,8 @@ public class SourceHttpMessageConverter<T extends Source> extends AbstractHttpMe
}
catch (NullPointerException ex) {
if (!isSupportDtd()) {
- throw new HttpMessageNotReadableException("NPE while unmarshalling. " +
- "This can happen on JDK 1.6 due to the presence of DTD " +
- "declarations, which are disabled.", ex);
+ throw new HttpMessageNotReadableException("NPE while unmarshalling: " +
+ "This can happen due to the presence of DTD declarations which are disabled.", ex);
}
throw ex;
}
@@ -191,14 +190,14 @@ public class SourceHttpMessageConverter<T extends Source> extends AbstractHttpMe
private SAXSource readSAXSource(InputStream body) throws IOException {
try {
- XMLReader reader = XMLReaderFactory.createXMLReader();
- reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", !isSupportDtd());
- reader.setFeature("http://xml.org/sax/features/external-general-entities", isProcessExternalEntities());
+ XMLReader xmlReader = XMLReaderFactory.createXMLReader();
+ xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", !isSupportDtd());
+ xmlReader.setFeature("http://xml.org/sax/features/external-general-entities", isProcessExternalEntities());
if (!isProcessExternalEntities()) {
- reader.setEntityResolver(NO_OP_ENTITY_RESOLVER);
+ xmlReader.setEntityResolver(NO_OP_ENTITY_RESOLVER);
}
byte[] bytes = StreamUtils.copyToByteArray(body);
- return new SAXSource(reader, new InputSource(new ByteArrayInputStream(bytes)));
+ return new SAXSource(xmlReader, new InputSource(new ByteArrayInputStream(bytes)));
}
catch (SAXException ex) {
throw new HttpMessageNotReadableException("Could not parse document: " + ex.getMessage(), ex);
diff --git a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java
index 4a8f342a..660044b8 100644
--- a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java
+++ b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java
@@ -125,7 +125,7 @@ public class ServletServerHttpRequest implements ServerHttpRequest {
this.headers.setContentType(contentType);
}
}
- if (contentType != null && contentType.getCharSet() == null) {
+ if (contentType != null && contentType.getCharset() == null) {
String requestEncoding = this.servletRequest.getCharacterEncoding();
if (StringUtils.hasLength(requestEncoding)) {
Charset charSet = Charset.forName(requestEncoding);
diff --git a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java
index 1aebe518..5944297f 100644
--- a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java
+++ b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -73,6 +73,7 @@ public class ServletServerHttpResponse implements ServerHttpResponse {
@Override
public void setStatusCode(HttpStatus status) {
+ Assert.notNull(status, "HttpStatus must not be null");
this.servletResponse.setStatus(status.value());
}
@@ -114,8 +115,8 @@ public class ServletServerHttpResponse implements ServerHttpResponse {
this.servletResponse.setContentType(this.headers.getContentType().toString());
}
if (this.servletResponse.getCharacterEncoding() == null && this.headers.getContentType() != null &&
- this.headers.getContentType().getCharSet() != null) {
- this.servletResponse.setCharacterEncoding(this.headers.getContentType().getCharSet().name());
+ this.headers.getContentType().getCharset() != null) {
+ this.servletResponse.setCharacterEncoding(this.headers.getContentType().getCharset().name());
}
this.headersWritten = true;
}
diff --git a/spring-web/src/main/java/org/springframework/remoting/httpinvoker/HttpComponentsHttpInvokerRequestExecutor.java b/spring-web/src/main/java/org/springframework/remoting/httpinvoker/HttpComponentsHttpInvokerRequestExecutor.java
index 7b0924ba..7f78a822 100644
--- a/spring-web/src/main/java/org/springframework/remoting/httpinvoker/HttpComponentsHttpInvokerRequestExecutor.java
+++ b/spring-web/src/main/java/org/springframework/remoting/httpinvoker/HttpComponentsHttpInvokerRequestExecutor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +43,7 @@ import org.springframework.context.i18n.LocaleContext;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.remoting.support.RemoteInvocationResult;
import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
@@ -69,6 +70,21 @@ public class HttpComponentsHttpInvokerRequestExecutor extends AbstractHttpInvoke
private static final int DEFAULT_READ_TIMEOUT_MILLISECONDS = (60 * 1000);
+
+ private static Class<?> abstractHttpClientClass;
+
+ static {
+ try {
+ // Looking for AbstractHttpClient class (deprecated as of HttpComponents 4.3)
+ abstractHttpClientClass = ClassUtils.forName("org.apache.http.impl.client.AbstractHttpClient",
+ HttpComponentsHttpInvokerRequestExecutor.class.getClassLoader());
+ }
+ catch (ClassNotFoundException ex) {
+ // Probably removed from HttpComponents in the meantime...
+ }
+ }
+
+
private HttpClient httpClient;
private RequestConfig requestConfig;
@@ -156,7 +172,7 @@ public class HttpComponentsHttpInvokerRequestExecutor extends AbstractHttpInvoke
*/
@SuppressWarnings("deprecation")
private void setLegacyConnectionTimeout(HttpClient client, int timeout) {
- if (org.apache.http.impl.client.AbstractHttpClient.class.isInstance(client)) {
+ if (abstractHttpClientClass != null && abstractHttpClientClass.isInstance(client)) {
client.getParams().setIntParameter(org.apache.http.params.CoreConnectionPNames.CONNECTION_TIMEOUT, timeout);
}
}
@@ -198,7 +214,7 @@ public class HttpComponentsHttpInvokerRequestExecutor extends AbstractHttpInvoke
*/
@SuppressWarnings("deprecation")
private void setLegacySocketTimeout(HttpClient client, int timeout) {
- if (org.apache.http.impl.client.AbstractHttpClient.class.isInstance(client)) {
+ if (abstractHttpClientClass != null && abstractHttpClientClass.isInstance(client)) {
client.getParams().setIntParameter(org.apache.http.params.CoreConnectionPNames.SO_TIMEOUT, timeout);
}
}
diff --git a/spring-web/src/main/java/org/springframework/remoting/httpinvoker/HttpInvokerServiceExporter.java b/spring-web/src/main/java/org/springframework/remoting/httpinvoker/HttpInvokerServiceExporter.java
index bcbc09f9..82bb00b6 100644
--- a/spring-web/src/main/java/org/springframework/remoting/httpinvoker/HttpInvokerServiceExporter.java
+++ b/spring-web/src/main/java/org/springframework/remoting/httpinvoker/HttpInvokerServiceExporter.java
@@ -16,6 +16,7 @@
package org.springframework.remoting.httpinvoker;
+import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
@@ -174,7 +175,8 @@ public class HttpInvokerServiceExporter extends RemoteInvocationSerializingExpor
HttpServletRequest request, HttpServletResponse response, RemoteInvocationResult result, OutputStream os)
throws IOException {
- ObjectOutputStream oos = createObjectOutputStream(decorateOutputStream(request, response, os));
+ ObjectOutputStream oos =
+ createObjectOutputStream(new FlushGuardedOutputStream(decorateOutputStream(request, response, os)));
try {
doWriteRemoteInvocationResult(result, oos);
}
@@ -200,4 +202,28 @@ public class HttpInvokerServiceExporter extends RemoteInvocationSerializingExpor
return os;
}
+
+ /**
+ * Decorate an {@code OutputStream} to guard against {@code flush()} calls,
+ * which are turned into no-ops.
+ *
+ * <p>Because {@link ObjectOutputStream#close()} will in fact flush/drain
+ * the underlying stream twice, this {@link FilterOutputStream} will
+ * guard against individual flush calls. Multiple flush calls can lead
+ * to performance issues, since writes aren't gathered as they should be.
+ *
+ * @see <a href="https://jira.spring.io/browse/SPR-14040">SPR-14040</a>
+ */
+ private static class FlushGuardedOutputStream extends FilterOutputStream {
+
+ public FlushGuardedOutputStream(OutputStream out) {
+ super(out);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ // Do nothing on flush
+ }
+ }
+
}
diff --git a/spring-web/src/main/java/org/springframework/remoting/jaxws/SimpleJaxWsServiceExporter.java b/spring-web/src/main/java/org/springframework/remoting/jaxws/SimpleJaxWsServiceExporter.java
index 08c47e61..8746786f 100644
--- a/spring-web/src/main/java/org/springframework/remoting/jaxws/SimpleJaxWsServiceExporter.java
+++ b/spring-web/src/main/java/org/springframework/remoting/jaxws/SimpleJaxWsServiceExporter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2009 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,9 +31,9 @@ import javax.xml.ws.WebServiceProvider;
* <p>Note that this exporter will only work if the JAX-WS runtime actually
* supports publishing with an address argument, i.e. if the JAX-WS runtime
* ships an internal HTTP server. This is the case with the JAX-WS runtime
- * that's inclued in Sun's JDK 1.6 but not with the standalone JAX-WS 2.1 RI.
+ * that's included in Sun's JDK 6 but not with the standalone JAX-WS 2.1 RI.
*
- * <p>For explicit configuration of JAX-WS endpoints with Sun's JDK 1.6
+ * <p>For explicit configuration of JAX-WS endpoints with Sun's JDK 6
* HTTP server, consider using {@link SimpleHttpServerJaxWsServiceExporter}!
*
* @author Juergen Hoeller
diff --git a/spring-web/src/main/java/org/springframework/web/HttpSessionRequiredException.java b/spring-web/src/main/java/org/springframework/web/HttpSessionRequiredException.java
index 448bd4ed..d17f8d39 100644
--- a/spring-web/src/main/java/org/springframework/web/HttpSessionRequiredException.java
+++ b/spring-web/src/main/java/org/springframework/web/HttpSessionRequiredException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,6 +27,9 @@ import javax.servlet.ServletException;
@SuppressWarnings("serial")
public class HttpSessionRequiredException extends ServletException {
+ private String expectedAttribute;
+
+
/**
* Create a new HttpSessionRequiredException.
* @param msg the detail message
@@ -35,4 +38,24 @@ public class HttpSessionRequiredException extends ServletException {
super(msg);
}
+ /**
+ * Create a new HttpSessionRequiredException.
+ * @param msg the detail message
+ * @param expectedAttribute the name of the expected session attribute
+ * @since 4.3
+ */
+ public HttpSessionRequiredException(String msg, String expectedAttribute) {
+ super(msg);
+ this.expectedAttribute = expectedAttribute;
+ }
+
+
+ /**
+ * Return the name of the expected session attribute, if any.
+ * @since 4.3
+ */
+ public String getExpectedAttribute() {
+ return this.expectedAttribute;
+ }
+
}
diff --git a/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManager.java b/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManager.java
index 3f6efab3..81553b81 100644
--- a/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManager.java
+++ b/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManager.java
@@ -93,6 +93,22 @@ public class ContentNegotiationManager implements ContentNegotiationStrategy, Me
}
/**
+ * Find a {@code ContentNegotiationStrategy} of the given type.
+ * @param strategyType the strategy type
+ * @return the first matching strategy or {@code null}.
+ * @since 4.3
+ */
+ @SuppressWarnings("unchecked")
+ public <T extends ContentNegotiationStrategy> T getStrategy(Class<T> strategyType) {
+ for (ContentNegotiationStrategy strategy : getStrategies()) {
+ if (strategyType.isInstance(strategy)) {
+ return (T) strategy;
+ }
+ }
+ return null;
+ }
+
+ /**
* Register more {@code MediaTypeFileExtensionResolver} instances in addition
* to those detected at construction.
* @param resolvers the resolvers to add
diff --git a/spring-web/src/main/java/org/springframework/web/accept/HeaderContentNegotiationStrategy.java b/spring-web/src/main/java/org/springframework/web/accept/HeaderContentNegotiationStrategy.java
index 2642853d..89dd23a2 100644
--- a/spring-web/src/main/java/org/springframework/web/accept/HeaderContentNegotiationStrategy.java
+++ b/spring-web/src/main/java/org/springframework/web/accept/HeaderContentNegotiationStrategy.java
@@ -16,13 +16,13 @@
package org.springframework.web.accept;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.springframework.http.HttpHeaders;
import org.springframework.http.InvalidMediaTypeException;
import org.springframework.http.MediaType;
-import org.springframework.util.StringUtils;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.context.request.NativeWebRequest;
@@ -30,6 +30,7 @@ import org.springframework.web.context.request.NativeWebRequest;
* A {@code ContentNegotiationStrategy} that checks the 'Accept' request header.
*
* @author Rossen Stoyanchev
+ * @author Juergen Hoeller
* @since 3.2
*/
public class HeaderContentNegotiationStrategy implements ContentNegotiationStrategy {
@@ -42,18 +43,20 @@ public class HeaderContentNegotiationStrategy implements ContentNegotiationStrat
public List<MediaType> resolveMediaTypes(NativeWebRequest request)
throws HttpMediaTypeNotAcceptableException {
- String header = request.getHeader(HttpHeaders.ACCEPT);
- if (!StringUtils.hasText(header)) {
- return Collections.emptyList();
+ String[] headerValueArray = request.getHeaderValues(HttpHeaders.ACCEPT);
+ if (headerValueArray == null) {
+ return Collections.<MediaType>emptyList();
}
+
+ List<String> headerValues = Arrays.asList(headerValueArray);
try {
- List<MediaType> mediaTypes = MediaType.parseMediaTypes(header);
+ List<MediaType> mediaTypes = MediaType.parseMediaTypes(headerValues);
MediaType.sortBySpecificityAndQuality(mediaTypes);
return mediaTypes;
}
catch (InvalidMediaTypeException ex) {
throw new HttpMediaTypeNotAcceptableException(
- "Could not parse 'Accept' header [" + header + "]: " + ex.getMessage());
+ "Could not parse 'Accept' header " + headerValues + ": " + ex.getMessage());
}
}
diff --git a/spring-web/src/main/java/org/springframework/web/accept/PathExtensionContentNegotiationStrategy.java b/spring-web/src/main/java/org/springframework/web/accept/PathExtensionContentNegotiationStrategy.java
index 9f0c2672..000b6379 100644
--- a/spring-web/src/main/java/org/springframework/web/accept/PathExtensionContentNegotiationStrategy.java
+++ b/spring-web/src/main/java/org/springframework/web/accept/PathExtensionContentNegotiationStrategy.java
@@ -30,12 +30,13 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
+import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.util.UriUtils;
import org.springframework.web.util.UrlPathHelper;
-import org.springframework.web.util.WebUtils;
/**
* A {@code ContentNegotiationStrategy} that resolves the file extension in the
@@ -51,20 +52,14 @@ import org.springframework.web.util.WebUtils;
* @author Rossen Stoyanchev
* @since 3.2
*/
-public class PathExtensionContentNegotiationStrategy
- extends AbstractMappingContentNegotiationStrategy {
-
- private static final Log logger = LogFactory.getLog(PathExtensionContentNegotiationStrategy.class);
+public class PathExtensionContentNegotiationStrategy extends AbstractMappingContentNegotiationStrategy {
private static final boolean JAF_PRESENT = ClassUtils.isPresent("javax.activation.FileTypeMap",
PathExtensionContentNegotiationStrategy.class.getClassLoader());
- private static final UrlPathHelper PATH_HELPER = new UrlPathHelper();
-
- static {
- PATH_HELPER.setUrlDecode(false);
- }
+ private static final Log logger = LogFactory.getLog(PathExtensionContentNegotiationStrategy.class);
+ private UrlPathHelper urlPathHelper = new UrlPathHelper();
private boolean useJaf = true;
@@ -72,21 +67,31 @@ public class PathExtensionContentNegotiationStrategy
/**
+ * Create an instance without any mappings to start with. Mappings may be added
+ * later on if any extensions are resolved through the Java Activation framework.
+ */
+ public PathExtensionContentNegotiationStrategy() {
+ this(null);
+ }
+
+ /**
* Create an instance with the given map of file extensions and media types.
*/
public PathExtensionContentNegotiationStrategy(Map<String, MediaType> mediaTypes) {
super(mediaTypes);
+ this.urlPathHelper.setUrlDecode(false);
}
+
/**
- * Create an instance without any mappings to start with. Mappings may be added
- * later on if any extensions are resolved through the Java Activation framework.
+ * Configure a {@code UrlPathHelper} to use in {@link #getMediaTypeKey}
+ * in order to derive the lookup path for a target request URL path.
+ * @since 4.2.8
*/
- public PathExtensionContentNegotiationStrategy() {
- super(null);
+ public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
+ this.urlPathHelper = urlPathHelper;
}
-
/**
* Whether to use the Java Activation Framework to look up file extensions.
* <p>By default this is set to "true" but depends on JAF being present.
@@ -112,10 +117,9 @@ public class PathExtensionContentNegotiationStrategy
logger.warn("An HttpServletRequest is required to determine the media type key");
return null;
}
- String path = PATH_HELPER.getLookupPathForRequest(request);
- String filename = WebUtils.extractFullFilenameFromUrlPath(path);
- String extension = StringUtils.getFilenameExtension(filename);
- return (StringUtils.hasText(extension)) ? extension.toLowerCase(Locale.ENGLISH) : null;
+ String path = this.urlPathHelper.getLookupPathForRequest(request);
+ String extension = UriUtils.extractFileExtension(path);
+ return (StringUtils.hasText(extension) ? extension.toLowerCase(Locale.ENGLISH) : null);
}
@Override
@@ -123,7 +127,7 @@ public class PathExtensionContentNegotiationStrategy
throws HttpMediaTypeNotAcceptableException {
if (this.useJaf && JAF_PRESENT) {
- MediaType mediaType = JafMediaTypeFactory.getMediaType("file." + extension);
+ MediaType mediaType = ActivationMediaTypeFactory.getMediaType("file." + extension);
if (mediaType != null && !MediaType.APPLICATION_OCTET_STREAM.equals(mediaType)) {
return mediaType;
}
@@ -134,11 +138,37 @@ public class PathExtensionContentNegotiationStrategy
throw new HttpMediaTypeNotAcceptableException(getAllMediaTypes());
}
+ /**
+ * A public method exposing the knowledge of the path extension strategy to
+ * resolve file extensions to a MediaType in this case for a given
+ * {@link Resource}. The method first looks up any explicitly registered
+ * file extensions first and then falls back on JAF if available.
+ * @param resource the resource to look up
+ * @return the MediaType for the extension or {@code null}.
+ * @since 4.3
+ */
+ public MediaType getMediaTypeForResource(Resource resource) {
+ Assert.notNull(resource);
+ MediaType mediaType = null;
+ String filename = resource.getFilename();
+ String extension = StringUtils.getFilenameExtension(filename);
+ if (extension != null) {
+ mediaType = lookupMediaType(extension);
+ }
+ if (mediaType == null && JAF_PRESENT) {
+ mediaType = ActivationMediaTypeFactory.getMediaType(filename);
+ }
+ if (MediaType.APPLICATION_OCTET_STREAM.equals(mediaType)) {
+ mediaType = null;
+ }
+ return mediaType;
+ }
+
/**
* Inner class to avoid hard-coded dependency on JAF.
*/
- private static class JafMediaTypeFactory {
+ private static class ActivationMediaTypeFactory {
private static final FileTypeMap fileTypeMap;
diff --git a/spring-web/src/main/java/org/springframework/web/accept/ServletPathExtensionContentNegotiationStrategy.java b/spring-web/src/main/java/org/springframework/web/accept/ServletPathExtensionContentNegotiationStrategy.java
index 615568b4..10d80b2b 100644
--- a/spring-web/src/main/java/org/springframework/web/accept/ServletPathExtensionContentNegotiationStrategy.java
+++ b/spring-web/src/main/java/org/springframework/web/accept/ServletPathExtensionContentNegotiationStrategy.java
@@ -19,6 +19,7 @@ package org.springframework.web.accept;
import java.util.Map;
import javax.servlet.ServletContext;
+import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -82,4 +83,29 @@ public class ServletPathExtensionContentNegotiationStrategy extends PathExtensio
return mediaType;
}
+ /**
+ * Extends the base class
+ * {@link PathExtensionContentNegotiationStrategy#getMediaTypeForResource}
+ * with the ability to also look up through the ServletContext.
+ * @param resource the resource to look up
+ * @return the MediaType for the extension or {@code null}.
+ * @since 4.3
+ */
+ public MediaType getMediaTypeForResource(Resource resource) {
+ MediaType mediaType = null;
+ if (this.servletContext != null) {
+ String mimeType = this.servletContext.getMimeType(resource.getFilename());
+ if (StringUtils.hasText(mimeType)) {
+ mediaType = MediaType.parseMediaType(mimeType);
+ }
+ }
+ if (mediaType == null || MediaType.APPLICATION_OCTET_STREAM.equals(mediaType)) {
+ MediaType superMediaType = super.getMediaTypeForResource(resource);
+ if (superMediaType != null) {
+ mediaType = superMediaType;
+ }
+ }
+ return mediaType;
+ }
+
}
diff --git a/spring-web/src/main/java/org/springframework/web/bind/WebDataBinder.java b/spring-web/src/main/java/org/springframework/web/bind/WebDataBinder.java
index 77562398..442b9718 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/WebDataBinder.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/WebDataBinder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,11 +17,13 @@
package org.springframework.web.bind;
import java.lang.reflect.Array;
+import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
+import org.springframework.core.CollectionFactory;
import org.springframework.validation.DataBinder;
import org.springframework.web.multipart.MultipartFile;
@@ -40,6 +42,7 @@ import org.springframework.web.multipart.MultipartFile;
*
* @author Juergen Hoeller
* @author Scott Andrews
+ * @author Brian Clozel
* @since 1.2
* @see #registerCustomEditor
* @see #setAllowedFields
@@ -243,26 +246,41 @@ public class WebDataBinder extends DataBinder {
/**
* Determine an empty value for the specified field.
- * <p>Default implementation returns {@code Boolean.FALSE}
- * for boolean fields and an empty array of array types.
- * Else, {@code null} is used as default.
+ * <p>Default implementation returns:
+ * <ul>
+ * <li>{@code Boolean.FALSE} for boolean fields
+ * <li>an empty array for array types
+ * <li>Collection implementations for Collection types
+ * <li>Map implementations for Map types
+ * <li>else, {@code null} is used as default
+ * </ul>
* @param field the name of the field
* @param fieldType the type of the field
* @return the empty value (for most fields: null)
*/
protected Object getEmptyValue(String field, Class<?> fieldType) {
- if (fieldType != null && boolean.class == fieldType || Boolean.class == fieldType) {
- // Special handling of boolean property.
- return Boolean.FALSE;
- }
- else if (fieldType != null && fieldType.isArray()) {
- // Special handling of array property.
- return Array.newInstance(fieldType.getComponentType(), 0);
- }
- else {
- // Default value: try null.
- return null;
+ if (fieldType != null) {
+ try {
+ if (boolean.class == fieldType || Boolean.class == fieldType) {
+ // Special handling of boolean property.
+ return Boolean.FALSE;
+ }
+ else if (fieldType.isArray()) {
+ // Special handling of array property.
+ return Array.newInstance(fieldType.getComponentType(), 0);
+ }
+ else if (Collection.class.isAssignableFrom(fieldType)) {
+ return CollectionFactory.createCollection(fieldType, 0);
+ }
+ else if (Map.class.isAssignableFrom(fieldType)) {
+ return CollectionFactory.createMap(fieldType, 0);
+ }
+ } catch (IllegalArgumentException exc) {
+ return null;
+ }
}
+ // Default value: try null.
+ return null;
}
/**
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/CrossOrigin.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/CrossOrigin.java
index 65e92007..76aacd0a 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/annotation/CrossOrigin.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/CrossOrigin.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,6 +29,14 @@ import org.springframework.core.annotation.AliasFor;
*
* <p>By default, all origins and headers are permitted.
*
+ * <p><b>NOTE:</b> {@code @CrossOrigin} is processed if an appropriate
+ * {@code HandlerMapping}-{@code HandlerAdapter} pair is configured such as the
+ * {@code RequestMappingHandlerMapping}-{@code RequestMappingHandlerAdapter}
+ * pair which are the default in the MVC Java config and the MVC namespace.
+ * In particular {@code @CrossOrigin} is not supported with the
+ * {@code DefaultAnnotationHandlerMapping}-{@code AnnotationMethodHandlerAdapter}
+ * pair both of which are also deprecated.
+ *
* @author Russell Allen
* @author Sebastien Deleuze
* @author Sam Brannen
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/DeleteMapping.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/DeleteMapping.java
new file mode 100644
index 00000000..a437f067
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/DeleteMapping.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.bind.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.core.annotation.AliasFor;
+
+/**
+ * Annotation for mapping HTTP {@code DELETE} requests onto specific handler
+ * methods.
+ *
+ * <p>Specifically, {@code @DeleteMapping} is a <em>composed annotation</em> that
+ * acts as a shortcut for {@code @RequestMapping(method = RequestMethod.DELETE)}.
+ *
+ * @author Sam Brannen
+ * @since 4.3
+ * @see GetMapping
+ * @see PostMapping
+ * @see PutMapping
+ * @see PatchMapping
+ * @see RequestMapping
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@RequestMapping(method = RequestMethod.DELETE)
+public @interface DeleteMapping {
+
+ /**
+ * Alias for {@link RequestMapping#name}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String name() default "";
+
+ /**
+ * Alias for {@link RequestMapping#value}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] value() default {};
+
+ /**
+ * Alias for {@link RequestMapping#path}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] path() default {};
+
+ /**
+ * Alias for {@link RequestMapping#params}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] params() default {};
+
+ /**
+ * Alias for {@link RequestMapping#headers}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] headers() default {};
+
+ /**
+ * Alias for {@link RequestMapping#consumes}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] consumes() default {};
+
+ /**
+ * Alias for {@link RequestMapping#produces}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] produces() default {};
+
+}
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/GetMapping.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/GetMapping.java
new file mode 100644
index 00000000..12c8a6cd
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/GetMapping.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.bind.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.core.annotation.AliasFor;
+
+/**
+ * Annotation for mapping HTTP {@code GET} requests onto specific handler
+ * methods.
+ *
+ * <p>Specifically, {@code @GetMapping} is a <em>composed annotation</em> that
+ * acts as a shortcut for {@code @RequestMapping(method = RequestMethod.GET)}.
+ *
+ * <h5>Difference between {@code @GetMapping} &amp; {@code @RequestMapping}</h5>
+ * <p>{@code @GetMapping} does not support the {@link RequestMapping#consumes consumes}
+ * attribute of {@code @RequestMapping}.
+ *
+ * @author Sam Brannen
+ * @since 4.3
+ * @see PostMapping
+ * @see PutMapping
+ * @see DeleteMapping
+ * @see PatchMapping
+ * @see RequestMapping
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@RequestMapping(method = RequestMethod.GET)
+public @interface GetMapping {
+
+ /**
+ * Alias for {@link RequestMapping#name}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String name() default "";
+
+ /**
+ * Alias for {@link RequestMapping#value}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] value() default {};
+
+ /**
+ * Alias for {@link RequestMapping#path}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] path() default {};
+
+ /**
+ * Alias for {@link RequestMapping#params}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] params() default {};
+
+ /**
+ * Alias for {@link RequestMapping#headers}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] headers() default {};
+
+ /**
+ * Alias for {@link RequestMapping#produces}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] produces() default {};
+
+}
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/ModelAttribute.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/ModelAttribute.java
index 39f31413..7d93e660 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/annotation/ModelAttribute.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/ModelAttribute.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author 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 java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import org.springframework.core.annotation.AliasFor;
import org.springframework.ui.Model;
/**
@@ -49,6 +50,7 @@ import org.springframework.ui.Model;
* access to a {@link Model} argument.
*
* @author Juergen Hoeller
+ * @author Rossen Stoyanchev
* @since 2.5
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@@ -57,13 +59,31 @@ import org.springframework.ui.Model;
public @interface ModelAttribute {
/**
+ * Alias for {@link #name}.
+ */
+ @AliasFor("name")
+ String value() default "";
+
+ /**
* The name of the model attribute to bind to.
* <p>The default model attribute name is inferred from the declared
* attribute type (i.e. the method parameter type or method return type),
* based on the non-qualified class name:
* e.g. "orderAddress" for class "mypackage.OrderAddress",
* or "orderAddressList" for "List&lt;mypackage.OrderAddress&gt;".
+ * @since 4.3
*/
- String value() default "";
+ @AliasFor("value")
+ String name() default "";
+
+ /**
+ * Allows declaring data binding disabled directly on an {@code @ModelAttribute}
+ * method parameter or on the attribute returned from an {@code @ModelAttribute}
+ * method, both of which would prevent data binding for that attribute.
+ * <p>By default this is set to {@code true} in which case data binding applies.
+ * Set this to {@code false} to disable data binding.
+ * @since 4.3
+ */
+ boolean binding() default true;
}
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/PatchMapping.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/PatchMapping.java
new file mode 100644
index 00000000..82eee4ac
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/PatchMapping.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.bind.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.core.annotation.AliasFor;
+
+/**
+ * Annotation for mapping HTTP {@code PATCH} requests onto specific handler
+ * methods.
+ *
+ * <p>Specifically, {@code @PatchMapping} is a <em>composed annotation</em> that
+ * acts as a shortcut for {@code @RequestMapping(method = RequestMethod.PATCH)}.
+ *
+ * @author Sam Brannen
+ * @since 4.3
+ * @see GetMapping
+ * @see PostMapping
+ * @see PutMapping
+ * @see DeleteMapping
+ * @see RequestMapping
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@RequestMapping(method = RequestMethod.PATCH)
+public @interface PatchMapping {
+
+ /**
+ * Alias for {@link RequestMapping#name}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String name() default "";
+
+ /**
+ * Alias for {@link RequestMapping#value}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] value() default {};
+
+ /**
+ * Alias for {@link RequestMapping#path}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] path() default {};
+
+ /**
+ * Alias for {@link RequestMapping#params}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] params() default {};
+
+ /**
+ * Alias for {@link RequestMapping#headers}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] headers() default {};
+
+ /**
+ * Alias for {@link RequestMapping#consumes}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] consumes() default {};
+
+ /**
+ * Alias for {@link RequestMapping#produces}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] produces() default {};
+
+}
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/PostMapping.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/PostMapping.java
new file mode 100644
index 00000000..0bc64bca
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/PostMapping.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.bind.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.core.annotation.AliasFor;
+
+/**
+ * Annotation for mapping HTTP {@code POST} requests onto specific handler
+ * methods.
+ *
+ * <p>Specifically, {@code @PostMapping} is a <em>composed annotation</em> that
+ * acts as a shortcut for {@code @RequestMapping(method = RequestMethod.POST)}.
+ *
+ * @author Sam Brannen
+ * @since 4.3
+ * @see GetMapping
+ * @see PutMapping
+ * @see DeleteMapping
+ * @see PatchMapping
+ * @see RequestMapping
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@RequestMapping(method = RequestMethod.POST)
+public @interface PostMapping {
+
+ /**
+ * Alias for {@link RequestMapping#name}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String name() default "";
+
+ /**
+ * Alias for {@link RequestMapping#value}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] value() default {};
+
+ /**
+ * Alias for {@link RequestMapping#path}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] path() default {};
+
+ /**
+ * Alias for {@link RequestMapping#params}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] params() default {};
+
+ /**
+ * Alias for {@link RequestMapping#headers}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] headers() default {};
+
+ /**
+ * Alias for {@link RequestMapping#consumes}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] consumes() default {};
+
+ /**
+ * Alias for {@link RequestMapping#produces}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] produces() default {};
+
+}
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/PutMapping.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/PutMapping.java
new file mode 100644
index 00000000..8e9e71f6
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/PutMapping.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.bind.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.core.annotation.AliasFor;
+
+/**
+ * Annotation for mapping HTTP {@code PUT} requests onto specific handler
+ * methods.
+ *
+ * <p>Specifically, {@code @PutMapping} is a <em>composed annotation</em> that
+ * acts as a shortcut for {@code @RequestMapping(method = RequestMethod.PUT)}.
+ *
+ * @author Sam Brannen
+ * @since 4.3
+ * @see GetMapping
+ * @see PostMapping
+ * @see DeleteMapping
+ * @see PatchMapping
+ * @see RequestMapping
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@RequestMapping(method = RequestMethod.PUT)
+public @interface PutMapping {
+
+ /**
+ * Alias for {@link RequestMapping#name}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String name() default "";
+
+ /**
+ * Alias for {@link RequestMapping#value}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] value() default {};
+
+ /**
+ * Alias for {@link RequestMapping#path}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] path() default {};
+
+ /**
+ * Alias for {@link RequestMapping#params}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] params() default {};
+
+ /**
+ * Alias for {@link RequestMapping#headers}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] headers() default {};
+
+ /**
+ * Alias for {@link RequestMapping#consumes}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] consumes() default {};
+
+ /**
+ * Alias for {@link RequestMapping#produces}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] produces() default {};
+
+}
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestAttribute.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestAttribute.java
new file mode 100644
index 00000000..8a51119e
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestAttribute.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.bind.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.core.annotation.AliasFor;
+
+/**
+ * Annotation to bind a method parameter to a request attribute.
+ *
+ * <p>The main motivation is to provide convenient access to request attributes
+ * from a controller method with an optional/required check and a cast to the
+ * target method parameter type.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3
+ * @see RequestMapping
+ * @see SessionAttribute
+ */
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RequestAttribute {
+
+ /**
+ * Alias for {@link #name}.
+ */
+ @AliasFor("name")
+ String value() default "";
+
+ /**
+ * The name of the request attribute to bind to.
+ * <p>The default name is inferred from the method parameter name.
+ */
+ @AliasFor("value")
+ String name() default "";
+
+ /**
+ * Whether the request attribute is required.
+ * <p>Defaults to {@code true}, leading to an exception being thrown if
+ * the attribute is missing. Switch this to {@code false} if you prefer
+ * a {@code null} or Java 8 {@code java.util.Optional} if the attribute
+ * doesn't exist.
+ */
+ boolean required() default true;
+
+}
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java
index c884775c..cda331c4 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -126,6 +126,12 @@ import org.springframework.core.annotation.AliasFor;
* {@link org.springframework.validation.Errors} argument.
* Instead a {@link org.springframework.web.bind.MethodArgumentNotValidException}
* exception is raised.
+ * <li>{@link SessionAttribute @SessionAttribute} annotated parameters for access
+ * to existing, permanent session attributes (e.g. user authentication object)
+ * as opposed to model attributes temporarily stored in the session as part of
+ * a controller workflow via {@link SessionAttributes}.
+ * <li>{@link RequestAttribute @RequestAttribute} annotated parameters for access
+ * to request attributes.
* <li>{@link org.springframework.http.HttpEntity HttpEntity&lt;?&gt;} parameters
* (Servlet-only) for access to the Servlet request HTTP headers and contents.
* The request stream will be converted to the entity body using
@@ -273,8 +279,16 @@ import org.springframework.core.annotation.AliasFor;
* @author Arjen Poutsma
* @author Sam Brannen
* @since 2.5
+ * @see GetMapping
+ * @see PostMapping
+ * @see PutMapping
+ * @see DeleteMapping
+ * @see PatchMapping
* @see RequestParam
+ * @see RequestAttribute
+ * @see PathVariable
* @see ModelAttribute
+ * @see SessionAttribute
* @see SessionAttributes
* @see InitBinder
* @see org.springframework.web.context.request.WebRequest
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/ResponseStatus.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/ResponseStatus.java
index 8ad9c1e9..8843eb12 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/annotation/ResponseStatus.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/ResponseStatus.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +44,10 @@ import org.springframework.http.HttpStatus;
* preferable to use a {@link org.springframework.http.ResponseEntity} as
* a return type and avoid the use of {@code @ResponseStatus} altogether.
*
+ * <p>Note that a controller class may also be annotated with
+ * {@code @ResponseStatus} and is then inherited by all {@code @RequestMapping}
+ * methods.
+ *
* @author Arjen Poutsma
* @author Sam Brannen
* @see org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/RestController.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/RestController.java
index cdd3772b..2287f652 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/annotation/RestController.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/RestController.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author 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,13 +25,21 @@ import java.lang.annotation.Target;
import org.springframework.stereotype.Controller;
/**
- * A convenience annotation that is itself annotated with {@link Controller @Controller}
- * and {@link ResponseBody @ResponseBody}.
+ * A convenience annotation that is itself annotated with
+ * {@link Controller @Controller} and {@link ResponseBody @ResponseBody}.
* <p>
* Types that carry this annotation are treated as controllers where
* {@link RequestMapping @RequestMapping} methods assume
* {@link ResponseBody @ResponseBody} semantics by default.
*
+ * <p><b>NOTE:</b> {@code @RestController} is processed if an appropriate
+ * {@code HandlerMapping}-{@code HandlerAdapter} pair is configured such as the
+ * {@code RequestMappingHandlerMapping}-{@code RequestMappingHandlerAdapter}
+ * pair which are the default in the MVC Java config and the MVC namespace.
+ * In particular {@code @RestController} is not supported with the
+ * {@code DefaultAnnotationHandlerMapping}-{@code AnnotationMethodHandlerAdapter}
+ * pair both of which are also deprecated.
+ *
* @author Rossen Stoyanchev
* @author Sam Brannen
* @since 4.0
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/RestControllerAdvice.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/RestControllerAdvice.java
new file mode 100644
index 00000000..29b08076
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/RestControllerAdvice.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.bind.annotation;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.core.annotation.AliasFor;
+
+/**
+ * A convenience annotation that is itself annotated with
+ * {@link ControllerAdvice @ControllerAdvice}
+ * and {@link ResponseBody @ResponseBody}.
+ *
+ * <p>Types that carry this annotation are treated as controller advice where
+ * {@link ExceptionHandler @ExceptionHandler} methods assume
+ * {@link ResponseBody @ResponseBody} semantics by default.
+ *
+ * <p><b>NOTE:</b> {@code @RestControllerAdvice} is processed if an appropriate
+ * {@code HandlerMapping}-{@code HandlerAdapter} pair is configured such as the
+ * {@code RequestMappingHandlerMapping}-{@code RequestMappingHandlerAdapter} pair
+ * which are the default in the MVC Java config and the MVC namespace.
+ * In particular {@code @RestControllerAdvice} is not supported with the
+ * {@code DefaultAnnotationHandlerMapping}-{@code AnnotationMethodHandlerAdapter}
+ * pair both of which are also deprecated.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@ControllerAdvice
+@ResponseBody
+public @interface RestControllerAdvice {
+
+ /**
+ * Alias for the {@link #basePackages} attribute.
+ * <p>Allows for more concise annotation declarations e.g.:
+ * {@code @ControllerAdvice("org.my.pkg")} is equivalent to
+ * {@code @ControllerAdvice(basePackages="org.my.pkg")}.
+ * @see #basePackages()
+ */
+ @AliasFor("basePackages")
+ String[] value() default {};
+
+ /**
+ * Array of base packages.
+ * <p>Controllers that belong to those base packages or sub-packages thereof
+ * will be included, e.g.: {@code @ControllerAdvice(basePackages="org.my.pkg")}
+ * or {@code @ControllerAdvice(basePackages={"org.my.pkg", "org.my.other.pkg"})}.
+ * <p>{@link #value} is an alias for this attribute, simply allowing for
+ * more concise use of the annotation.
+ * <p>Also consider using {@link #basePackageClasses()} as a type-safe
+ * alternative to String-based package names.
+ */
+ @AliasFor("value")
+ String[] basePackages() default {};
+
+ /**
+ * Type-safe alternative to {@link #value()} for specifying the packages
+ * to select Controllers to be assisted by the {@code @ControllerAdvice}
+ * annotated class.
+ * <p>Consider creating a special no-op marker class or interface in each package
+ * that serves no purpose other than being referenced by this attribute.
+ */
+ Class<?>[] basePackageClasses() default {};
+
+ /**
+ * Array of classes.
+ * <p>Controllers that are assignable to at least one of the given types
+ * will be assisted by the {@code @ControllerAdvice} annotated class.
+ */
+ Class<?>[] assignableTypes() default {};
+
+ /**
+ * Array of annotations.
+ * <p>Controllers that are annotated with this/one of those annotation(s)
+ * will be assisted by the {@code @ControllerAdvice} annotated class.
+ * <p>Consider creating a special annotation or use a predefined one,
+ * like {@link RestController @RestController}.
+ */
+ Class<? extends Annotation>[] annotations() default {};
+
+}
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/SessionAttribute.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/SessionAttribute.java
new file mode 100644
index 00000000..d02e4c01
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/SessionAttribute.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.bind.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.core.annotation.AliasFor;
+
+/**
+ * Annotation to bind a method parameter to a session attribute.
+ *
+ * <p>The main motivation is to provide convenient access to existing, permanent
+ * session attributes (e.g. user authentication object) with an optional/required
+ * check and a cast to the target method parameter type.
+ *
+ * <p>For use cases that require adding or removing session attributes consider
+ * injecting {@code org.springframework.web.context.request.WebRequest} or
+ * {@code javax.servlet.http.HttpSession} into the controller method.
+ *
+ * <p>For temporary storage of model attributes in the session as part of the
+ * workflow for a controller, consider using {@link SessionAttributes} instead.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3
+ * @see RequestMapping
+ * @see SessionAttributes
+ * @see RequestAttribute
+ */
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface SessionAttribute {
+
+ /**
+ * Alias for {@link #name}.
+ */
+ @AliasFor("name")
+ String value() default "";
+
+ /**
+ * The name of the session attribute to bind to.
+ * <p>The default name is inferred from the method parameter name.
+ */
+ @AliasFor("value")
+ String name() default "";
+
+ /**
+ * Whether the session attribute is required.
+ * <p>Defaults to {@code true}, leading to an exception being thrown
+ * if the attribute is missing in the session or there is no session.
+ * Switch this to {@code false} if you prefer a {@code null} or Java 8
+ * {@code java.util.Optional} if the attribute doesn't exist.
+ */
+ boolean required() default true;
+
+}
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvocationException.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvocationException.java
index 98ad9992..52850cd3 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvocationException.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvocationException.java
@@ -26,7 +26,9 @@ import org.springframework.core.NestedRuntimeException;
* @author Juergen Hoeller
* @since 2.5.6
* @see HandlerMethodInvoker#invokeHandlerMethod
+ * @deprecated as of 4.3, in favor of the {@code HandlerMethod}-based MVC infrastructure
*/
+@Deprecated
@SuppressWarnings("serial")
public class HandlerMethodInvocationException extends NestedRuntimeException {
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java
index eb869333..9287882b 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java
@@ -94,7 +94,9 @@ import org.springframework.web.multipart.MultipartRequest;
* @author Arjen Poutsma
* @since 2.5.2
* @see #invokeHandlerMethod
+ * @deprecated as of 4.3, in favor of the {@code HandlerMethod}-based MVC infrastructure
*/
+@Deprecated
public class HandlerMethodInvoker {
private static final String MODEL_KEY_PREFIX_STALE = SessionAttributeStore.class.getName() + ".STALE.";
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodResolver.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodResolver.java
index 9fd25c01..53d6584e 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodResolver.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodResolver.java
@@ -48,7 +48,9 @@ import org.springframework.web.bind.annotation.SessionAttributes;
* @see org.springframework.web.bind.annotation.InitBinder
* @see org.springframework.web.bind.annotation.ModelAttribute
* @see org.springframework.web.bind.annotation.SessionAttributes
+ * @deprecated as of 4.3, in favor of the {@code HandlerMethod}-based MVC infrastructure
*/
+@Deprecated
public class HandlerMethodResolver {
private final Set<Method> handlerMethods = new LinkedHashSet<Method>();
diff --git a/spring-web/src/main/java/org/springframework/web/bind/support/WebRequestDataBinder.java b/spring-web/src/main/java/org/springframework/web/bind/support/WebRequestDataBinder.java
index 94b40ae9..ea3c0ae8 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/support/WebRequestDataBinder.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/support/WebRequestDataBinder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -70,6 +70,9 @@ import org.springframework.web.multipart.MultipartRequest;
*/
public class WebRequestDataBinder extends WebDataBinder {
+ private static final boolean servlet3Parts = ClassUtils.hasMethod(HttpServletRequest.class, "getParts");
+
+
/**
* Create a new WebRequestDataBinder instance, with default object name.
* @param target the target object to bind onto (or {@code null}
@@ -116,7 +119,7 @@ public class WebRequestDataBinder extends WebDataBinder {
if (multipartRequest != null) {
bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
}
- else if (ClassUtils.hasMethod(HttpServletRequest.class, "getParts")) {
+ else if (servlet3Parts) {
HttpServletRequest serlvetRequest = ((NativeWebRequest) request).getNativeRequest(HttpServletRequest.class);
new Servlet3MultipartHelper(isBindEmptyMultipartFiles()).bindParts(serlvetRequest, mpvs);
}
diff --git a/spring-web/src/main/java/org/springframework/web/client/AsyncRestTemplate.java b/spring-web/src/main/java/org/springframework/web/client/AsyncRestTemplate.java
index eb6b5230..acbe7953 100644
--- a/spring-web/src/main/java/org/springframework/web/client/AsyncRestTemplate.java
+++ b/spring-web/src/main/java/org/springframework/web/client/AsyncRestTemplate.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,8 +24,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.task.AsyncListenableTaskExecutor;
@@ -41,15 +39,12 @@ import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
-import org.springframework.http.client.support.AsyncHttpAccessor;
+import org.springframework.http.client.support.InterceptingAsyncHttpAccessor;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.Assert;
-import org.springframework.util.concurrent.FailureCallback;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureAdapter;
-import org.springframework.util.concurrent.ListenableFutureCallback;
-import org.springframework.util.concurrent.SuccessCallback;
-import org.springframework.web.util.DefaultUriTemplateHandler;
+import org.springframework.web.util.AbstractUriTemplateHandler;
import org.springframework.web.util.UriTemplateHandler;
/**
@@ -74,7 +69,7 @@ import org.springframework.web.util.UriTemplateHandler;
* @since 4.0
* @see RestTemplate
*/
-public class AsyncRestTemplate extends AsyncHttpAccessor implements AsyncRestOperations {
+public class AsyncRestTemplate extends InterceptingAsyncHttpAccessor implements AsyncRestOperations {
private final RestTemplate syncTemplate;
@@ -157,8 +152,28 @@ public class AsyncRestTemplate extends AsyncHttpAccessor implements AsyncRestOpe
}
/**
- * Set a custom {@link UriTemplateHandler} for expanding URI templates.
- * <p>By default, RestTemplate uses {@link DefaultUriTemplateHandler}.
+ * Configure default URI variable values. This is a shortcut for:
+ * <pre class="code">
+ * DefaultUriTemplateHandler handler = new DefaultUriTemplateHandler();
+ * handler.setDefaultUriVariables(...);
+ *
+ * AsyncRestTemplate restTemplate = new AsyncRestTemplate();
+ * restTemplate.setUriTemplateHandler(handler);
+ * </pre>
+ * @param defaultUriVariables the default URI variable values
+ * @since 4.3
+ */
+ public void setDefaultUriVariables(Map<String, ?> defaultUriVariables) {
+ UriTemplateHandler handler = this.syncTemplate.getUriTemplateHandler();
+ Assert.isInstanceOf(AbstractUriTemplateHandler.class, handler,
+ "Can only use this property in conjunction with a DefaultUriTemplateHandler");
+ ((AbstractUriTemplateHandler) handler).setDefaultUriVariables(defaultUriVariables);
+ }
+
+ /**
+ * This property has the same purpose as the corresponding property on the
+ * {@code RestTemplate}. For more details see
+ * {@link RestTemplate#setUriTemplateHandler}.
* @param handler the URI template handler to use
*/
public void setUriTemplateHandler(UriTemplateHandler handler) {
@@ -245,75 +260,37 @@ public class AsyncRestTemplate extends AsyncHttpAccessor implements AsyncRestOpe
// POST
@Override
- public ListenableFuture<URI> postForLocation(String url, HttpEntity<?> request, Object... uriVariables)
+ public ListenableFuture<URI> postForLocation(String url, HttpEntity<?> request, Object... uriVars)
throws RestClientException {
- AsyncRequestCallback requestCallback = httpEntityCallback(request);
- ResponseExtractor<HttpHeaders> headersExtractor = headersExtractor();
- ListenableFuture<HttpHeaders> headersFuture =
- execute(url, HttpMethod.POST, requestCallback, headersExtractor, uriVariables);
- return extractLocationHeader(headersFuture);
+ AsyncRequestCallback callback = httpEntityCallback(request);
+ ResponseExtractor<HttpHeaders> extractor = headersExtractor();
+ ListenableFuture<HttpHeaders> future = execute(url, HttpMethod.POST, callback, extractor, uriVars);
+ return adaptToLocationHeader(future);
}
@Override
- public ListenableFuture<URI> postForLocation(String url, HttpEntity<?> request, Map<String, ?> uriVariables)
+ public ListenableFuture<URI> postForLocation(String url, HttpEntity<?> request, Map<String, ?> uriVars)
throws RestClientException {
- AsyncRequestCallback requestCallback = httpEntityCallback(request);
- ResponseExtractor<HttpHeaders> headersExtractor = headersExtractor();
- ListenableFuture<HttpHeaders> headersFuture =
- execute(url, HttpMethod.POST, requestCallback, headersExtractor, uriVariables);
- return extractLocationHeader(headersFuture);
+ AsyncRequestCallback callback = httpEntityCallback(request);
+ ResponseExtractor<HttpHeaders> extractor = headersExtractor();
+ ListenableFuture<HttpHeaders> future = execute(url, HttpMethod.POST, callback, extractor, uriVars);
+ return adaptToLocationHeader(future);
}
@Override
public ListenableFuture<URI> postForLocation(URI url, HttpEntity<?> request) throws RestClientException {
- AsyncRequestCallback requestCallback = httpEntityCallback(request);
- ResponseExtractor<HttpHeaders> headersExtractor = headersExtractor();
- ListenableFuture<HttpHeaders> headersFuture =
- execute(url, HttpMethod.POST, requestCallback, headersExtractor);
- return extractLocationHeader(headersFuture);
+ AsyncRequestCallback callback = httpEntityCallback(request);
+ ResponseExtractor<HttpHeaders> extractor = headersExtractor();
+ ListenableFuture<HttpHeaders> future = execute(url, HttpMethod.POST, callback, extractor);
+ return adaptToLocationHeader(future);
}
- private static ListenableFuture<URI> extractLocationHeader(final ListenableFuture<HttpHeaders> headersFuture) {
- return new ListenableFuture<URI>() {
- @Override
- public void addCallback(final ListenableFutureCallback<? super URI> callback) {
- addCallback(callback, callback);
- }
- @Override
- public void addCallback(final SuccessCallback<? super URI> successCallback, final FailureCallback failureCallback) {
- headersFuture.addCallback(new ListenableFutureCallback<HttpHeaders>() {
- @Override
- public void onSuccess(HttpHeaders result) {
- successCallback.onSuccess(result.getLocation());
- }
- @Override
- public void onFailure(Throwable ex) {
- failureCallback.onFailure(ex);
- }
- });
- }
- @Override
- public boolean cancel(boolean mayInterruptIfRunning) {
- return headersFuture.cancel(mayInterruptIfRunning);
- }
- @Override
- public boolean isCancelled() {
- return headersFuture.isCancelled();
- }
- @Override
- public boolean isDone() {
- return headersFuture.isDone();
- }
+ private static ListenableFuture<URI> adaptToLocationHeader(ListenableFuture<HttpHeaders> future) {
+ return new ListenableFutureAdapter<URI, HttpHeaders>(future) {
@Override
- public URI get() throws InterruptedException, ExecutionException {
- HttpHeaders headers = headersFuture.get();
- return headers.getLocation();
- }
- @Override
- public URI get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
- HttpHeaders headers = headersFuture.get(timeout, unit);
+ protected URI adapt(HttpHeaders headers) throws ExecutionException {
return headers.getLocation();
}
};
@@ -389,71 +366,35 @@ public class AsyncRestTemplate extends AsyncHttpAccessor implements AsyncRestOpe
// OPTIONS
@Override
- public ListenableFuture<Set<HttpMethod>> optionsForAllow(String url, Object... uriVariables) throws RestClientException {
- ResponseExtractor<HttpHeaders> headersExtractor = headersExtractor();
- ListenableFuture<HttpHeaders> headersFuture = execute(url, HttpMethod.OPTIONS, null, headersExtractor, uriVariables);
- return extractAllowHeader(headersFuture);
+ public ListenableFuture<Set<HttpMethod>> optionsForAllow(String url, Object... uriVars) throws RestClientException {
+ ResponseExtractor<HttpHeaders> extractor = headersExtractor();
+ ListenableFuture<HttpHeaders> future = execute(url, HttpMethod.OPTIONS, null, extractor, uriVars);
+ return adaptToAllowHeader(future);
}
@Override
- public ListenableFuture<Set<HttpMethod>> optionsForAllow(String url, Map<String, ?> uriVariables) throws RestClientException {
- ResponseExtractor<HttpHeaders> headersExtractor = headersExtractor();
- ListenableFuture<HttpHeaders> headersFuture = execute(url, HttpMethod.OPTIONS, null, headersExtractor, uriVariables);
- return extractAllowHeader(headersFuture);
+ public ListenableFuture<Set<HttpMethod>> optionsForAllow(String url, Map<String, ?> uriVars) throws RestClientException {
+ ResponseExtractor<HttpHeaders> extractor = headersExtractor();
+ ListenableFuture<HttpHeaders> future = execute(url, HttpMethod.OPTIONS, null, extractor, uriVars);
+ return adaptToAllowHeader(future);
}
@Override
public ListenableFuture<Set<HttpMethod>> optionsForAllow(URI url) throws RestClientException {
- ResponseExtractor<HttpHeaders> headersExtractor = headersExtractor();
- ListenableFuture<HttpHeaders> headersFuture = execute(url, HttpMethod.OPTIONS, null, headersExtractor);
- return extractAllowHeader(headersFuture);
+ ResponseExtractor<HttpHeaders> extractor = headersExtractor();
+ ListenableFuture<HttpHeaders> future = execute(url, HttpMethod.OPTIONS, null, extractor);
+ return adaptToAllowHeader(future);
}
- private static ListenableFuture<Set<HttpMethod>> extractAllowHeader(final ListenableFuture<HttpHeaders> headersFuture) {
- return new ListenableFuture<Set<HttpMethod>>() {
+ private static ListenableFuture<Set<HttpMethod>> adaptToAllowHeader(ListenableFuture<HttpHeaders> future) {
+ return new ListenableFutureAdapter<Set<HttpMethod>, HttpHeaders>(future) {
@Override
- public void addCallback(final ListenableFutureCallback<? super Set<HttpMethod>> callback) {
- addCallback(callback, callback);
- }
- @Override
- public void addCallback(final SuccessCallback<? super Set<HttpMethod>> successCallback, final FailureCallback failureCallback) {
- headersFuture.addCallback(new ListenableFutureCallback<HttpHeaders>() {
- @Override
- public void onSuccess(HttpHeaders result) {
- successCallback.onSuccess(result.getAllow());
- }
- @Override
- public void onFailure(Throwable ex) {
- failureCallback.onFailure(ex);
- }
- });
- }
- @Override
- public boolean cancel(boolean mayInterruptIfRunning) {
- return headersFuture.cancel(mayInterruptIfRunning);
- }
- @Override
- public boolean isCancelled() {
- return headersFuture.isCancelled();
- }
- @Override
- public boolean isDone() {
- return headersFuture.isDone();
- }
- @Override
- public Set<HttpMethod> get() throws InterruptedException, ExecutionException {
- HttpHeaders headers = headersFuture.get();
- return headers.getAllow();
- }
- @Override
- public Set<HttpMethod> get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
- HttpHeaders headers = headersFuture.get(timeout, unit);
+ protected Set<HttpMethod> adapt(HttpHeaders headers) throws ExecutionException {
return headers.getAllow();
}
};
}
-
// exchange
@Override
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 45eb677e..591b95e6 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
@@ -113,7 +113,7 @@ public class DefaultResponseErrorHandler implements ResponseErrorHandler {
private Charset getCharset(ClientHttpResponse response) {
HttpHeaders headers = response.getHeaders();
MediaType contentType = headers.getContentType();
- return contentType != null ? contentType.getCharSet() : null;
+ return contentType != null ? contentType.getCharset() : null;
}
}
diff --git a/spring-web/src/main/java/org/springframework/web/client/HttpMessageConverterExtractor.java b/spring-web/src/main/java/org/springframework/web/client/HttpMessageConverterExtractor.java
index 7eada90c..2f03908c 100644
--- a/spring-web/src/main/java/org/springframework/web/client/HttpMessageConverterExtractor.java
+++ b/spring-web/src/main/java/org/springframework/web/client/HttpMessageConverterExtractor.java
@@ -76,7 +76,7 @@ public class HttpMessageConverterExtractor<T> implements ResponseExtractor<T> {
@Override
- @SuppressWarnings({"unchecked", "rawtypes"})
+ @SuppressWarnings({"unchecked", "rawtypes", "resource"})
public T extractData(ClientHttpResponse response) throws IOException {
MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response);
if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) {
diff --git a/spring-web/src/main/java/org/springframework/web/client/HttpStatusCodeException.java b/spring-web/src/main/java/org/springframework/web/client/HttpStatusCodeException.java
index 93b3c110..0d5c2fbc 100644
--- a/spring-web/src/main/java/org/springframework/web/client/HttpStatusCodeException.java
+++ b/spring-web/src/main/java/org/springframework/web/client/HttpStatusCodeException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@
package org.springframework.web.client;
-import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import org.springframework.http.HttpHeaders;
@@ -27,29 +26,19 @@ import org.springframework.http.HttpStatus;
*
* @author Arjen Poutsma
* @author Chris Beams
+ * @author Rossen Stoyanchev
* @since 3.0
*/
-public abstract class HttpStatusCodeException extends RestClientException {
+public abstract class HttpStatusCodeException extends RestClientResponseException {
- private static final long serialVersionUID = -5807494703720513267L;
-
- private static final String DEFAULT_CHARSET = "ISO-8859-1";
+ private static final long serialVersionUID = 5696801857651587810L;
private final HttpStatus statusCode;
- private final String statusText;
-
- private final byte[] responseBody;
-
- private final HttpHeaders responseHeaders;
-
- private final String responseCharset;
-
/**
- * Construct a new instance of {@code HttpStatusCodeException} based on an
- * {@link HttpStatus}.
+ * Construct a new instance with an {@link HttpStatus}.
* @param statusCode the status code
*/
protected HttpStatusCodeException(HttpStatus statusCode) {
@@ -57,8 +46,7 @@ public abstract class HttpStatusCodeException extends RestClientException {
}
/**
- * Construct a new instance of {@code HttpStatusCodeException} based on an
- * {@link HttpStatus} and status text.
+ * Construct a new instance with an {@link HttpStatus} and status text.
* @param statusCode the status code
* @param statusText the status text
*/
@@ -67,23 +55,22 @@ public abstract class HttpStatusCodeException extends RestClientException {
}
/**
- * Construct a new instance of {@code HttpStatusCodeException} based on an
- * {@link HttpStatus}, status text, and response body content.
+ * Construct instance with an {@link HttpStatus}, status text, and content.
* @param statusCode the status code
* @param statusText the status text
* @param responseBody the response body content, may be {@code null}
* @param responseCharset the response body charset, may be {@code null}
* @since 3.0.5
*/
- protected HttpStatusCodeException(
- HttpStatus statusCode, String statusText, byte[] responseBody, Charset responseCharset) {
+ protected HttpStatusCodeException(HttpStatus statusCode, String statusText,
+ byte[] responseBody, Charset responseCharset) {
this(statusCode, statusText, null, responseBody, responseCharset);
}
/**
- * Construct a new instance of {@code HttpStatusCodeException} based on an
- * {@link HttpStatus}, status text, and response body content.
+ * Construct instance with an {@link HttpStatus}, status text, content, and
+ * a response charset.
* @param statusCode the status code
* @param statusText the status text
* @param responseHeaders the response headers, may be {@code null}
@@ -94,12 +81,9 @@ public abstract class HttpStatusCodeException extends RestClientException {
protected HttpStatusCodeException(HttpStatus statusCode, String statusText,
HttpHeaders responseHeaders, byte[] responseBody, Charset responseCharset) {
- super(statusCode.value() + " " + statusText);
+ super(statusCode.value() + " " + statusText, statusCode.value(), statusText,
+ responseHeaders, responseBody, responseCharset);
this.statusCode = statusCode;
- this.statusText = statusText;
- this.responseHeaders = responseHeaders;
- this.responseBody = responseBody != null ? responseBody : new byte[0];
- this.responseCharset = responseCharset != null ? responseCharset.name() : DEFAULT_CHARSET;
}
@@ -110,41 +94,4 @@ public abstract class HttpStatusCodeException extends RestClientException {
return this.statusCode;
}
- /**
- * Return the HTTP status text.
- */
- public String getStatusText() {
- return this.statusText;
- }
-
- /**
- * Return the HTTP response headers.
- * @since 3.1.2
- */
- public HttpHeaders getResponseHeaders() {
- return this.responseHeaders;
- }
-
- /**
- * Return the response body as a byte array.
- * @since 3.0.5
- */
- public byte[] getResponseBodyAsByteArray() {
- return this.responseBody;
- }
-
- /**
- * Return the response body as a string.
- * @since 3.0.5
- */
- public String getResponseBodyAsString() {
- try {
- return new String(this.responseBody, this.responseCharset);
- }
- catch (UnsupportedEncodingException ex) {
- // should not occur
- throw new IllegalStateException(ex);
- }
- }
-
}
diff --git a/spring-web/src/main/java/org/springframework/web/client/RestClientResponseException.java b/spring-web/src/main/java/org/springframework/web/client/RestClientResponseException.java
new file mode 100644
index 00000000..d9c1e6a0
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/client/RestClientResponseException.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.client;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+
+import org.springframework.http.HttpHeaders;
+
+/**
+ * Common base class for exceptions that contain actual HTTP response data.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3
+ */
+public class RestClientResponseException extends RestClientException {
+
+ private static final long serialVersionUID = -8803556342728481792L;
+
+ private static final String DEFAULT_CHARSET = "ISO-8859-1";
+
+
+ private final int rawStatusCode;
+
+ private final String statusText;
+
+ private final byte[] responseBody;
+
+ private final HttpHeaders responseHeaders;
+
+ private final String responseCharset;
+
+
+ /**
+ * Construct a new instance of with the given response data.
+ * @param statusCode the raw status code value
+ * @param statusText the status text
+ * @param responseHeaders the response headers (may be {@code null})
+ * @param responseBody the response body content (may be {@code null})
+ * @param responseCharset the response body charset (may be {@code null})
+ */
+ public RestClientResponseException(String message, int statusCode, String statusText,
+ HttpHeaders responseHeaders, byte[] responseBody, Charset responseCharset) {
+
+ super(message);
+ this.rawStatusCode = statusCode;
+ this.statusText = statusText;
+ this.responseHeaders = responseHeaders;
+ this.responseBody = (responseBody != null ? responseBody : new byte[0]);
+ this.responseCharset = (responseCharset != null ? responseCharset.name() : DEFAULT_CHARSET);
+ }
+
+
+ /**
+ * Return the raw HTTP status code value.
+ */
+ public int getRawStatusCode() {
+ return this.rawStatusCode;
+ }
+
+ /**
+ * Return the HTTP status text.
+ */
+ public String getStatusText() {
+ return this.statusText;
+ }
+
+ /**
+ * Return the HTTP response headers.
+ */
+ public HttpHeaders getResponseHeaders() {
+ return this.responseHeaders;
+ }
+
+ /**
+ * Return the response body as a byte array.
+ */
+ public byte[] getResponseBodyAsByteArray() {
+ return this.responseBody;
+ }
+
+ /**
+ * Return the response body as a string.
+ */
+ public String getResponseBodyAsString() {
+ try {
+ return new String(this.responseBody, this.responseCharset);
+ }
+ catch (UnsupportedEncodingException ex) {
+ // should not occur
+ throw new IllegalStateException(ex);
+ }
+ }
+
+}
diff --git a/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java b/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java
index 8c7c0433..8a7f79a2 100644
--- a/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java
+++ b/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -51,6 +51,7 @@ import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConve
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
+import org.springframework.web.util.AbstractUriTemplateHandler;
import org.springframework.web.util.DefaultUriTemplateHandler;
import org.springframework.web.util.UriTemplateHandler;
@@ -237,8 +238,30 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
}
/**
- * Set a custom {@link UriTemplateHandler} for expanding URI templates.
- * <p>By default, RestTemplate uses {@link DefaultUriTemplateHandler}.
+ * Configure default URI variable values. This is a shortcut for:
+ * <pre class="code">
+ * DefaultUriTemplateHandler handler = new DefaultUriTemplateHandler();
+ * handler.setDefaultUriVariables(...);
+ *
+ * RestTemplate restTemplate = new RestTemplate();
+ * restTemplate.setUriTemplateHandler(handler);
+ * </pre>
+ * @param defaultUriVariables the default URI variable values
+ * @since 4.3
+ */
+ public void setDefaultUriVariables(Map<String, ?> defaultUriVariables) {
+ Assert.isInstanceOf(AbstractUriTemplateHandler.class, this.uriTemplateHandler,
+ "Can only use this property in conjunction with an AbstractUriTemplateHandler");
+ ((AbstractUriTemplateHandler) this.uriTemplateHandler).setDefaultUriVariables(defaultUriVariables);
+ }
+
+ /**
+ * Configure the {@link UriTemplateHandler} to use to expand URI templates.
+ * By default the {@link DefaultUriTemplateHandler} is used which relies on
+ * Spring's URI template support and exposes several useful properties that
+ * customize its behavior for encoding and for prepending a common base URL.
+ * An alternative implementation may be used to plug an external URI
+ * template library.
* @param handler the URI template handler to use
*/
public void setUriTemplateHandler(UriTemplateHandler handler) {
@@ -603,8 +626,11 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
}
}
catch (IOException ex) {
+ String resource = url.toString();
+ String query = url.getRawQuery();
+ resource = (query != null ? resource.substring(0, resource.indexOf(query) - 1) : resource);
throw new ResourceAccessException("I/O error on " + method.name() +
- " request for \"" + url + "\": " + ex.getMessage(), ex);
+ " request for \"" + resource + "\": " + ex.getMessage(), ex);
}
finally {
if (response != null) {
@@ -728,7 +754,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
List<MediaType> supportedMediaTypes = messageConverter.getSupportedMediaTypes();
List<MediaType> result = new ArrayList<MediaType>(supportedMediaTypes.size());
for (MediaType supportedMediaType : supportedMediaTypes) {
- if (supportedMediaType.getCharSet() != null) {
+ if (supportedMediaType.getCharset() != null) {
supportedMediaType =
new MediaType(supportedMediaType.getType(), supportedMediaType.getSubtype());
}
@@ -779,11 +805,34 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
}
else {
Object requestBody = this.requestEntity.getBody();
- Class<?> requestType = requestBody.getClass();
+ Class<?> requestBodyClass = requestBody.getClass();
+ Type requestBodyType = (this.requestEntity instanceof RequestEntity ?
+ ((RequestEntity<?>)this.requestEntity).getType() : requestBodyClass);
HttpHeaders requestHeaders = this.requestEntity.getHeaders();
MediaType requestContentType = requestHeaders.getContentType();
for (HttpMessageConverter<?> messageConverter : getMessageConverters()) {
- if (messageConverter.canWrite(requestType, requestContentType)) {
+ if (messageConverter instanceof GenericHttpMessageConverter) {
+ GenericHttpMessageConverter<Object> genericMessageConverter = (GenericHttpMessageConverter<Object>) messageConverter;
+ if (genericMessageConverter.canWrite(requestBodyType, requestBodyClass, requestContentType)) {
+ if (!requestHeaders.isEmpty()) {
+ httpRequest.getHeaders().putAll(requestHeaders);
+ }
+ if (logger.isDebugEnabled()) {
+ if (requestContentType != null) {
+ logger.debug("Writing [" + requestBody + "] as \"" + requestContentType +
+ "\" using [" + messageConverter + "]");
+ }
+ else {
+ logger.debug("Writing [" + requestBody + "] using [" + messageConverter + "]");
+ }
+
+ }
+ genericMessageConverter.write(
+ requestBody, requestBodyType, requestContentType, httpRequest);
+ return;
+ }
+ }
+ else if (messageConverter.canWrite(requestBodyClass, requestContentType)) {
if (!requestHeaders.isEmpty()) {
httpRequest.getHeaders().putAll(requestHeaders);
}
@@ -803,7 +852,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
}
}
String message = "Could not write request: no suitable HttpMessageConverter found for request type [" +
- requestType.getName() + "]";
+ requestBodyClass.getName() + "]";
if (requestContentType != null) {
message += " and content type [" + requestContentType + "]";
}
diff --git a/spring-web/src/main/java/org/springframework/web/client/UnknownHttpStatusCodeException.java b/spring-web/src/main/java/org/springframework/web/client/UnknownHttpStatusCodeException.java
index 40ac5047..b8e894b9 100644
--- a/spring-web/src/main/java/org/springframework/web/client/UnknownHttpStatusCodeException.java
+++ b/spring-web/src/main/java/org/springframework/web/client/UnknownHttpStatusCodeException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@
package org.springframework.web.client;
-import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import org.springframework.http.HttpHeaders;
@@ -28,21 +27,9 @@ import org.springframework.http.HttpStatus;
* @author Rossen Stoyanchev
* @since 3.2
*/
-public class UnknownHttpStatusCodeException extends RestClientException {
+public class UnknownHttpStatusCodeException extends RestClientResponseException {
- private static final long serialVersionUID = 4702443689088991600L;
-
- private static final String DEFAULT_CHARSET = "ISO-8859-1";
-
- private final int rawStatusCode;
-
- private final String statusText;
-
- private final byte[] responseBody;
-
- private final HttpHeaders responseHeaders;
-
- private final String responseCharset;
+ private static final long serialVersionUID = 7103980251635005491L;
/**
@@ -57,54 +44,8 @@ public class UnknownHttpStatusCodeException extends RestClientException {
public UnknownHttpStatusCodeException(int rawStatusCode, String statusText,
HttpHeaders responseHeaders, byte[] responseBody, Charset responseCharset) {
- super("Unknown status code [" + String.valueOf(rawStatusCode) + "]" + " " + statusText);
- this.rawStatusCode = rawStatusCode;
- this.statusText = statusText;
- this.responseHeaders = responseHeaders;
- this.responseBody = responseBody != null ? responseBody : new byte[0];
- this.responseCharset = responseCharset != null ? responseCharset.name() : DEFAULT_CHARSET;
- }
-
-
- /**
- * Return the raw HTTP status code value.
- */
- public int getRawStatusCode() {
- return this.rawStatusCode;
- }
-
- /**
- * Return the HTTP status text.
- */
- public String getStatusText() {
- return this.statusText;
- }
-
- /**
- * Return the HTTP response headers.
- */
- public HttpHeaders getResponseHeaders() {
- return this.responseHeaders;
- }
-
- /**
- * Return the response body as a byte array.
- */
- public byte[] getResponseBodyAsByteArray() {
- return this.responseBody;
- }
-
- /**
- * Return the response body as a string.
- */
- public String getResponseBodyAsString() {
- try {
- return new String(this.responseBody, this.responseCharset);
- }
- catch (UnsupportedEncodingException ex) {
- // should not occur
- throw new IllegalStateException(ex);
- }
+ super("Unknown status code [" + String.valueOf(rawStatusCode) + "]" + " " + statusText,
+ rawStatusCode, statusText, responseHeaders, responseBody, responseCharset);
}
}
diff --git a/spring-web/src/main/java/org/springframework/web/context/annotation/ApplicationScope.java b/spring-web/src/main/java/org/springframework/web/context/annotation/ApplicationScope.java
new file mode 100644
index 00000000..fa7e897c
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/context/annotation/ApplicationScope.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.context.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.context.annotation.Scope;
+import org.springframework.context.annotation.ScopedProxyMode;
+import org.springframework.core.annotation.AliasFor;
+import org.springframework.web.context.WebApplicationContext;
+
+/**
+ * {@code @ApplicationScope} is a specialization of {@link Scope @Scope} for a
+ * component whose lifecycle is bound to the current web application.
+ *
+ * <p>Specifically, {@code @ApplicationScope} is a <em>composed annotation</em> that
+ * acts as a shortcut for {@code @Scope("application")} with the default
+ * {@link #proxyMode} set to {@link ScopedProxyMode#TARGET_CLASS TARGET_CLASS}.
+ *
+ * <p>{@code @ApplicationScope} may be used as a meta-annotation to create custom
+ * composed annotations.
+ *
+ * @author Sam Brannen
+ * @since 4.3
+ * @see RequestScope
+ * @see SessionScope
+ * @see org.springframework.context.annotation.Scope
+ * @see org.springframework.web.context.WebApplicationContext#SCOPE_APPLICATION
+ * @see org.springframework.web.context.support.ServletContextScope
+ * @see org.springframework.stereotype.Component
+ * @see org.springframework.context.annotation.Bean
+ */
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Scope(WebApplicationContext.SCOPE_APPLICATION)
+public @interface ApplicationScope {
+
+ /**
+ * Alias for {@link Scope#proxyMode}.
+ * <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
+ */
+ @AliasFor(annotation = Scope.class)
+ ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
+
+}
diff --git a/spring-web/src/main/java/org/springframework/web/context/annotation/RequestScope.java b/spring-web/src/main/java/org/springframework/web/context/annotation/RequestScope.java
new file mode 100644
index 00000000..f33a1dd7
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/context/annotation/RequestScope.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.context.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.context.annotation.Scope;
+import org.springframework.context.annotation.ScopedProxyMode;
+import org.springframework.core.annotation.AliasFor;
+import org.springframework.web.context.WebApplicationContext;
+
+/**
+ * {@code @RequestScope} is a specialization of {@link Scope @Scope} for a
+ * component whose lifecycle is bound to the current web request.
+ *
+ * <p>Specifically, {@code @RequestScope} is a <em>composed annotation</em> that
+ * acts as a shortcut for {@code @Scope("request")} with the default
+ * {@link #proxyMode} set to {@link ScopedProxyMode#TARGET_CLASS TARGET_CLASS}.
+ *
+ * <p>{@code @RequestScope} may be used as a meta-annotation to create custom
+ * composed annotations.
+ *
+ * @author Sam Brannen
+ * @since 4.3
+ * @see SessionScope
+ * @see ApplicationScope
+ * @see org.springframework.context.annotation.Scope
+ * @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST
+ * @see org.springframework.web.context.request.RequestScope
+ * @see org.springframework.stereotype.Component
+ * @see org.springframework.context.annotation.Bean
+ */
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Scope(WebApplicationContext.SCOPE_REQUEST)
+public @interface RequestScope {
+
+ /**
+ * Alias for {@link Scope#proxyMode}.
+ * <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
+ */
+ @AliasFor(annotation = Scope.class)
+ ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
+
+}
diff --git a/spring-web/src/main/java/org/springframework/web/context/annotation/SessionScope.java b/spring-web/src/main/java/org/springframework/web/context/annotation/SessionScope.java
new file mode 100644
index 00000000..45af3c6e
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/context/annotation/SessionScope.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.context.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.context.annotation.Scope;
+import org.springframework.context.annotation.ScopedProxyMode;
+import org.springframework.core.annotation.AliasFor;
+import org.springframework.web.context.WebApplicationContext;
+
+/**
+ * {@code @SessionScope} is a specialization of {@link Scope @Scope} for a
+ * component whose lifecycle is bound to the current web session.
+ *
+ * <p>Specifically, {@code @SessionScope} is a <em>composed annotation</em> that
+ * acts as a shortcut for {@code @Scope("session")} with the default
+ * {@link #proxyMode} set to {@link ScopedProxyMode#TARGET_CLASS TARGET_CLASS}.
+ *
+ * <p>{@code @SessionScope} may be used as a meta-annotation to create custom
+ * composed annotations.
+ *
+ * @author Sam Brannen
+ * @since 4.3
+ * @see RequestScope
+ * @see ApplicationScope
+ * @see org.springframework.context.annotation.Scope
+ * @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION
+ * @see org.springframework.web.context.request.SessionScope
+ * @see org.springframework.stereotype.Component
+ * @see org.springframework.context.annotation.Bean
+ */
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Scope(WebApplicationContext.SCOPE_SESSION)
+public @interface SessionScope {
+
+ /**
+ * Alias for {@link Scope#proxyMode}.
+ * <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
+ */
+ @AliasFor(annotation = Scope.class)
+ ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
+
+}
diff --git a/spring-web/src/main/java/org/springframework/web/context/annotation/package-info.java b/spring-web/src/main/java/org/springframework/web/context/annotation/package-info.java
new file mode 100644
index 00000000..fffce1b0
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/context/annotation/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Provides convenience annotations for web scopes.
+ */
+package org.springframework.web.context.annotation;
diff --git a/spring-web/src/main/java/org/springframework/web/context/request/FacesRequestAttributes.java b/spring-web/src/main/java/org/springframework/web/context/request/FacesRequestAttributes.java
index 73301c34..bfbf032b 100644
--- a/spring-web/src/main/java/org/springframework/web/context/request/FacesRequestAttributes.java
+++ b/spring-web/src/main/java/org/springframework/web/context/request/FacesRequestAttributes.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -217,7 +217,7 @@ public class FacesRequestAttributes implements RequestAttributes {
Object session = getExternalContext().getSession(true);
try {
// Both HttpSession and PortletSession have a getId() method.
- Method getIdMethod = session.getClass().getMethod("getId", new Class<?>[0]);
+ Method getIdMethod = session.getClass().getMethod("getId");
return ReflectionUtils.invokeMethod(getIdMethod, session).toString();
}
catch (NoSuchMethodException ex) {
@@ -227,12 +227,12 @@ public class FacesRequestAttributes implements RequestAttributes {
@Override
public Object getSessionMutex() {
- // Enforce presence of a session first to allow listeners
- // to create the mutex attribute, if any.
- Object session = getExternalContext().getSession(true);
- Object mutex = getExternalContext().getSessionMap().get(WebUtils.SESSION_MUTEX_ATTRIBUTE);
+ // Enforce presence of a session first to allow listeners to create the mutex attribute
+ ExternalContext externalContext = getExternalContext();
+ Object session = externalContext.getSession(true);
+ Object mutex = externalContext.getSessionMap().get(WebUtils.SESSION_MUTEX_ATTRIBUTE);
if (mutex == null) {
- mutex = session;
+ mutex = (session != null ? session : externalContext);
}
return mutex;
}
diff --git a/spring-web/src/main/java/org/springframework/web/context/request/ServletRequestAttributes.java b/spring-web/src/main/java/org/springframework/web/context/request/ServletRequestAttributes.java
index 4182342a..6eb5de18 100644
--- a/spring-web/src/main/java/org/springframework/web/context/request/ServletRequestAttributes.java
+++ b/spring-web/src/main/java/org/springframework/web/context/request/ServletRequestAttributes.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -108,15 +108,24 @@ public class ServletRequestAttributes extends AbstractRequestAttributes {
*/
protected final HttpSession getSession(boolean allowCreate) {
if (isRequestActive()) {
- return this.request.getSession(allowCreate);
+ HttpSession session = this.request.getSession(allowCreate);
+ this.session = session;
+ return session;
}
else {
// Access through stored session reference, if any...
- if (this.session == null && allowCreate) {
- throw new IllegalStateException(
- "No session found and request already completed - cannot create new session!");
+ HttpSession session = this.session;
+ if (session == null) {
+ if (allowCreate) {
+ throw new IllegalStateException(
+ "No session found and request already completed - cannot create new session!");
+ }
+ else {
+ session = this.request.getSession(false);
+ this.session = session;
+ }
}
- return this.session;
+ return session;
}
}
@@ -251,25 +260,26 @@ public class ServletRequestAttributes extends AbstractRequestAttributes {
*/
@Override
protected void updateAccessedSessionAttributes() {
- // Store session reference for access after request completion.
- this.session = this.request.getSession(false);
- // Update all affected session attributes.
- if (this.session != null) {
- try {
- for (Map.Entry<String, Object> entry : this.sessionAttributesToUpdate.entrySet()) {
- String name = entry.getKey();
- Object newValue = entry.getValue();
- Object oldValue = this.session.getAttribute(name);
- if (oldValue == newValue && !isImmutableSessionAttribute(name, newValue)) {
- this.session.setAttribute(name, newValue);
+ if (!this.sessionAttributesToUpdate.isEmpty()) {
+ // Update all affected session attributes.
+ HttpSession session = getSession(false);
+ if (session != null) {
+ try {
+ for (Map.Entry<String, Object> entry : this.sessionAttributesToUpdate.entrySet()) {
+ String name = entry.getKey();
+ Object newValue = entry.getValue();
+ Object oldValue = session.getAttribute(name);
+ if (oldValue == newValue && !isImmutableSessionAttribute(name, newValue)) {
+ session.setAttribute(name, newValue);
+ }
}
}
+ catch (IllegalStateException ex) {
+ // Session invalidated - shouldn't usually happen.
+ }
}
- catch (IllegalStateException ex) {
- // Session invalidated - shouldn't usually happen.
- }
+ this.sessionAttributesToUpdate.clear();
}
- this.sessionAttributesToUpdate.clear();
}
/**
diff --git a/spring-web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java b/spring-web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java
index 6cc8e052..97a227af 100644
--- a/spring-web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java
+++ b/spring-web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,8 @@ import java.util.Date;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@@ -47,6 +49,8 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
private static final String HEADER_IF_MODIFIED_SINCE = "If-Modified-Since";
+ private static final String HEADER_IF_UNMODIFIED_SINCE = "If-Unmodified-Since";
+
private static final String HEADER_IF_NONE_MATCH = "If-None-Match";
private static final String HEADER_LAST_MODIFIED = "Last-Modified";
@@ -55,6 +59,18 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
private static final String METHOD_HEAD = "HEAD";
+ private static final String METHOD_POST = "POST";
+
+ private static final String METHOD_PUT = "PUT";
+
+ private static final String METHOD_DELETE = "DELETE";
+
+ /**
+ * Pattern matching ETag multiple field values in headers such as "If-Match", "If-None-Match"
+ * @see <a href="https://tools.ietf.org/html/rfc7232#section-2.3">Section 2.3 of RFC 7232</a>
+ */
+ private static final Pattern ETAG_HEADER_VALUE_PATTERN = Pattern.compile("\\*|\\s*((W\\/)?(\"[^\"]*\"))\\s*,?");
+
/** Checking for Servlet 3.0+ HttpServletResponse.getHeader(String) */
private static final boolean servlet3Present =
@@ -183,11 +199,18 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
if (isCompatibleWithConditionalRequests(response)) {
this.notModified = isTimestampNotModified(lastModifiedTimestamp);
if (response != null) {
- if (this.notModified && supportsNotModifiedStatus()) {
- response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+ if (supportsNotModifiedStatus()) {
+ if (this.notModified) {
+ response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+ }
+ if (isHeaderAbsent(response, HEADER_LAST_MODIFIED)) {
+ response.setDateHeader(HEADER_LAST_MODIFIED, lastModifiedTimestamp);
+ }
}
- if (isHeaderAbsent(response, HEADER_LAST_MODIFIED)) {
- response.setDateHeader(HEADER_LAST_MODIFIED, lastModifiedTimestamp);
+ else if (supportsConditionalUpdate()) {
+ if (this.notModified) {
+ response.setStatus(HttpServletResponse.SC_PRECONDITION_FAILED);
+ }
}
}
}
@@ -201,7 +224,9 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
if (StringUtils.hasLength(etag) && !this.notModified) {
if (isCompatibleWithConditionalRequests(response)) {
etag = addEtagPadding(etag);
- this.notModified = isEtagNotModified(etag);
+ if (hasRequestHeader(HEADER_IF_NONE_MATCH)) {
+ this.notModified = isEtagNotModified(etag);
+ }
if (response != null) {
if (this.notModified && supportsNotModifiedStatus()) {
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
@@ -221,16 +246,28 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
if (StringUtils.hasLength(etag) && !this.notModified) {
if (isCompatibleWithConditionalRequests(response)) {
etag = addEtagPadding(etag);
- this.notModified = isEtagNotModified(etag) && isTimestampNotModified(lastModifiedTimestamp);
+ if (hasRequestHeader(HEADER_IF_NONE_MATCH)) {
+ this.notModified = isEtagNotModified(etag);
+ }
+ else if (hasRequestHeader(HEADER_IF_MODIFIED_SINCE)) {
+ this.notModified = isTimestampNotModified(lastModifiedTimestamp);
+ }
if (response != null) {
- if (this.notModified && supportsNotModifiedStatus()) {
- response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
- }
- if (isHeaderAbsent(response, HEADER_ETAG)) {
- response.setHeader(HEADER_ETAG, etag);
+ if (supportsNotModifiedStatus()) {
+ if (this.notModified) {
+ response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+ }
+ if (isHeaderAbsent(response, HEADER_ETAG)) {
+ response.setHeader(HEADER_ETAG, etag);
+ }
+ if (isHeaderAbsent(response, HEADER_LAST_MODIFIED)) {
+ response.setDateHeader(HEADER_LAST_MODIFIED, lastModifiedTimestamp);
+ }
}
- if (isHeaderAbsent(response, HEADER_LAST_MODIFIED)) {
- response.setDateHeader(HEADER_LAST_MODIFIED, lastModifiedTimestamp);
+ else if (supportsConditionalUpdate()) {
+ if (this.notModified) {
+ response.setStatus(HttpServletResponse.SC_PRECONDITION_FAILED);
+ }
}
}
}
@@ -250,7 +287,8 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
return true;
}
return HttpStatus.valueOf(response.getStatus()).is2xxSuccessful();
- } catch (IllegalArgumentException e) {
+ }
+ catch (IllegalArgumentException e) {
return true;
}
}
@@ -263,47 +301,65 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
return (response.getHeader(header) == null);
}
+ private boolean hasRequestHeader(String headerName) {
+ return StringUtils.hasLength(getHeader(headerName));
+ }
+
private boolean supportsNotModifiedStatus() {
String method = getRequest().getMethod();
return (METHOD_GET.equals(method) || METHOD_HEAD.equals(method));
}
- @SuppressWarnings("deprecation")
+ private boolean supportsConditionalUpdate() {
+ String method = getRequest().getMethod();
+ return (METHOD_POST.equals(method) || METHOD_PUT.equals(method) || METHOD_DELETE.equals(method))
+ && hasRequestHeader(HEADER_IF_UNMODIFIED_SINCE);
+ }
+
private boolean isTimestampNotModified(long lastModifiedTimestamp) {
- long ifModifiedSince = -1;
+ long ifModifiedSince = parseDateHeader(HEADER_IF_MODIFIED_SINCE);
+ if (ifModifiedSince != -1) {
+ return (ifModifiedSince >= (lastModifiedTimestamp / 1000 * 1000));
+ }
+ long ifUnmodifiedSince = parseDateHeader(HEADER_IF_UNMODIFIED_SINCE);
+ if (ifUnmodifiedSince != -1) {
+ return (ifUnmodifiedSince < (lastModifiedTimestamp / 1000 * 1000));
+ }
+ return false;
+ }
+
+ @SuppressWarnings("deprecation")
+ private long parseDateHeader(String headerName) {
+ long dateValue = -1;
try {
- ifModifiedSince = getRequest().getDateHeader(HEADER_IF_MODIFIED_SINCE);
+ dateValue = getRequest().getDateHeader(headerName);
}
catch (IllegalArgumentException ex) {
- String headerValue = getRequest().getHeader(HEADER_IF_MODIFIED_SINCE);
+ String headerValue = getHeader(headerName);
// Possibly an IE 10 style value: "Wed, 09 Apr 2014 09:57:42 GMT; length=13774"
int separatorIndex = headerValue.indexOf(';');
if (separatorIndex != -1) {
String datePart = headerValue.substring(0, separatorIndex);
try {
- ifModifiedSince = Date.parse(datePart);
+ dateValue = Date.parse(datePart);
}
catch (IllegalArgumentException ex2) {
// Giving up
}
}
}
- return (ifModifiedSince >= (lastModifiedTimestamp / 1000 * 1000));
+ return dateValue;
}
private boolean isEtagNotModified(String etag) {
- if (StringUtils.hasLength(etag)) {
- String ifNoneMatch = getRequest().getHeader(HEADER_IF_NONE_MATCH);
- if (StringUtils.hasLength(ifNoneMatch)) {
- String[] clientEtags = StringUtils.delimitedListToStringArray(ifNoneMatch, ",", " ");
- for (String clientEtag : clientEtags) {
- // compare weak/strong ETag as per https://tools.ietf.org/html/rfc7232#section-2.3
- if (StringUtils.hasLength(clientEtag) &&
- (clientEtag.replaceFirst("^W/", "").equals(etag.replaceFirst("^W/", "")) ||
- clientEtag.equals("*"))) {
- return true;
- }
- }
+ String ifNoneMatch = getHeader(HEADER_IF_NONE_MATCH);
+ // compare weak/strong ETag as per https://tools.ietf.org/html/rfc7232#section-2.3
+ String serverETag = etag.replaceFirst("^W/", "");
+ Matcher eTagMatcher = ETAG_HEADER_VALUE_PATTERN.matcher(ifNoneMatch);
+ while (eTagMatcher.find()) {
+ if ("*".equals(eTagMatcher.group())
+ || serverETag.equals(eTagMatcher.group(3))) {
+ return true;
}
}
return false;
@@ -316,7 +372,6 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
return etag;
}
-
@Override
public String getDescription(boolean includeClientInfo) {
HttpServletRequest request = getRequest();
diff --git a/spring-web/src/main/java/org/springframework/web/context/request/WebRequest.java b/spring-web/src/main/java/org/springframework/web/context/request/WebRequest.java
index cea9fa70..c4c11eb4 100644
--- a/spring-web/src/main/java/org/springframework/web/context/request/WebRequest.java
+++ b/spring-web/src/main/java/org/springframework/web/context/request/WebRequest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -126,10 +126,10 @@ public interface WebRequest extends RequestAttributes {
boolean isSecure();
/**
- * Check whether the request qualifies as not modified given the
+ * Check whether the requested resource has been modified given the
* supplied last-modified timestamp (as determined by the application).
- * <p>This will also transparently set the appropriate response headers,
- * for both the modified case and the not-modified case.
+ * <p>This will also transparently set the "Last-Modified" response header
+ * and HTTP status when applicable.
* <p>Typical usage:
* <pre class="code">
* public String myHandleMethod(WebRequest webRequest, Model model) {
@@ -142,6 +142,8 @@ public interface WebRequest extends RequestAttributes {
* model.addAttribute(...);
* return "myViewName";
* }</pre>
+ * <p>This method works with conditional GET/HEAD requests, but
+ * also with conditional POST/PUT/DELETE requests.
* <p><strong>Note:</strong> you can use either
* this {@code #checkNotModified(long)} method; or
* {@link #checkNotModified(String)}. If you want enforce both
@@ -151,8 +153,9 @@ public interface WebRequest extends RequestAttributes {
* <p>If the "If-Modified-Since" header is set but cannot be parsed
* to a date value, this method will ignore the header and proceed
* with setting the last-modified timestamp on the response.
- * @param lastModifiedTimestamp the last-modified timestamp that
- * the application determined for the underlying resource
+ * @param lastModifiedTimestamp the last-modified timestamp in
+ * milliseconds that the application determined for the underlying
+ * resource
* @return whether the request qualifies as not modified,
* allowing to abort request processing and relying on the response
* telling the client that the content has not been modified
@@ -160,10 +163,10 @@ public interface WebRequest extends RequestAttributes {
boolean checkNotModified(long lastModifiedTimestamp);
/**
- * Check whether the request qualifies as not modified given the
+ * Check whether the requested resource has been modified given the
* supplied {@code ETag} (entity tag), as determined by the application.
- * <p>This will also transparently set the appropriate response headers,
- * for both the modified case and the not-modified case.
+ * <p>This will also transparently set the "ETag" response header
+ * and HTTP status when applicable.
* <p>Typical usage:
* <pre class="code">
* public String myHandleMethod(WebRequest webRequest, Model model) {
@@ -185,18 +188,16 @@ public interface WebRequest extends RequestAttributes {
* @param etag the entity tag that the application determined
* for the underlying resource. This parameter will be padded
* with quotes (") if necessary.
- * @return whether the request qualifies as not modified,
- * allowing to abort request processing and relying on the response
- * telling the client that the content has not been modified
+ * @return true if the request does not require further processing.
*/
boolean checkNotModified(String etag);
/**
- * Check whether the request qualifies as not modified given the
+ * Check whether the requested resource has been modified given the
* supplied {@code ETag} (entity tag) and last-modified timestamp,
* as determined by the application.
* <p>This will also transparently set the "ETag" and "Last-Modified"
- * response headers, for both the modified case and the not-modified case.
+ * response headers, and HTTP status when applicable.
* <p>Typical usage:
* <pre class="code">
* public String myHandleMethod(WebRequest webRequest, Model model) {
@@ -210,6 +211,8 @@ public interface WebRequest extends RequestAttributes {
* model.addAttribute(...);
* return "myViewName";
* }</pre>
+ * <p>This method works with conditional GET/HEAD requests, but
+ * also with conditional POST/PUT/DELETE requests.
* <p><strong>Note:</strong> The HTTP specification recommends
* setting both ETag and Last-Modified values, but you can also
* use {@code #checkNotModified(String)} or
@@ -217,11 +220,10 @@ public interface WebRequest extends RequestAttributes {
* @param etag the entity tag that the application determined
* for the underlying resource. This parameter will be padded
* with quotes (") if necessary.
- * @param lastModifiedTimestamp the last-modified timestamp that
- * the application determined for the underlying resource
- * @return whether the request qualifies as not modified,
- * allowing to abort request processing and relying on the response
- * telling the client that the content has not been modified
+ * @param lastModifiedTimestamp the last-modified timestamp in
+ * milliseconds that the application determined for the underlying
+ * resource
+ * @return true if the request does not require further processing.
* @since 4.2
*/
boolean checkNotModified(String etag, long lastModifiedTimestamp);
diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResultProcessingInterceptor.java b/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResultProcessingInterceptor.java
index 638068a3..89e5745f 100644
--- a/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResultProcessingInterceptor.java
+++ b/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResultProcessingInterceptor.java
@@ -25,7 +25,7 @@ import org.springframework.web.context.request.NativeWebRequest;
*
* <p>A {@code DeferredResultProcessingInterceptor} is invoked before the start
* of async processing, after the {@code DeferredResult} is set as well as on
- * timeout, or or after completing for any reason including a timeout or network
+ * timeout, or after completing for any reason including a timeout or network
* error.
*
* <p>As a general rule exceptions raised by interceptor methods will cause
diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequest.java b/spring-web/src/main/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequest.java
index fc0bb983..9bd4ac5c 100644
--- a/spring-web/src/main/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequest.java
+++ b/spring-web/src/main/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequest.java
@@ -34,7 +34,7 @@ import org.springframework.web.context.request.ServletWebRequest;
*
* <p>The servlet and all filters involved in an async request must have async
* support enabled using the Servlet API or by adding an
- * {@code <async-support>true</async-support>} element to servlet and filter
+ * {@code <async-supported>true</async-supported>} element to servlet and filter
* declarations in {@code web.xml}.
*
* @author Rossen Stoyanchev
diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncManager.java b/spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncManager.java
index a20cf23d..83913f02 100644
--- a/spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncManager.java
+++ b/spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -105,7 +105,6 @@ public final class WebAsyncManager {
*/
public void setAsyncWebRequest(final AsyncWebRequest asyncWebRequest) {
Assert.notNull(asyncWebRequest, "AsyncWebRequest must not be null");
- Assert.state(!isConcurrentHandlingStarted(), "Can't set AsyncWebRequest with concurrent handling in progress");
this.asyncWebRequest = asyncWebRequest;
this.asyncWebRequest.addCompletionHandler(new Runnable() {
@Override
diff --git a/spring-web/src/main/java/org/springframework/web/context/support/ServletContextResource.java b/spring-web/src/main/java/org/springframework/web/context/support/ServletContextResource.java
index a05b782f..a13890b0 100644
--- a/spring-web/src/main/java/org/springframework/web/context/support/ServletContextResource.java
+++ b/spring-web/src/main/java/org/springframework/web/context/support/ServletContextResource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -77,6 +77,7 @@ public class ServletContextResource extends AbstractFileResolvingResource implem
this.path = pathToUse;
}
+
/**
* Return the ServletContext for this resource.
*/
@@ -91,7 +92,6 @@ public class ServletContextResource extends AbstractFileResolvingResource implem
return this.path;
}
-
/**
* This implementation checks {@code ServletContext.getResource}.
* @see javax.servlet.ServletContext#getResource(String)
diff --git a/spring-web/src/main/java/org/springframework/web/cors/CorsConfiguration.java b/spring-web/src/main/java/org/springframework/web/cors/CorsConfiguration.java
index 76daa33b..016a355b 100644
--- a/spring-web/src/main/java/org/springframework/web/cors/CorsConfiguration.java
+++ b/spring-web/src/main/java/org/springframework/web/cors/CorsConfiguration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -340,6 +340,7 @@ public class CorsConfiguration {
}
if (allowedMethods.isEmpty()) {
allowedMethods.add(HttpMethod.GET.name());
+ allowedMethods.add(HttpMethod.HEAD.name());
}
List<HttpMethod> result = new ArrayList<HttpMethod>(allowedMethods.size());
boolean allowed = false;
diff --git a/spring-web/src/main/java/org/springframework/web/filter/AbstractRequestLoggingFilter.java b/spring-web/src/main/java/org/springframework/web/filter/AbstractRequestLoggingFilter.java
index 4062bf6d..6a9af746 100644
--- a/spring-web/src/main/java/org/springframework/web/filter/AbstractRequestLoggingFilter.java
+++ b/spring-web/src/main/java/org/springframework/web/filter/AbstractRequestLoggingFilter.java
@@ -24,6 +24,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
+import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.util.ContentCachingRequestWrapper;
@@ -72,6 +73,8 @@ public abstract class AbstractRequestLoggingFilter extends OncePerRequestFilter
private boolean includeClientInfo = false;
+ private boolean includeHeaders = false;
+
private boolean includePayload = false;
private int maxPayloadLength = DEFAULT_MAX_PAYLOAD_LENGTH;
@@ -120,6 +123,24 @@ public abstract class AbstractRequestLoggingFilter extends OncePerRequestFilter
}
/**
+ * Set whether the request headers should be included in the log message.
+ * <p>Should be configured using an {@code <init-param>} for parameter name
+ * "includeHeaders" in the filter definition in {@code web.xml}.
+ * @since 4.3
+ */
+ public void setIncludeHeaders(boolean includeHeaders) {
+ this.includeHeaders = includeHeaders;
+ }
+
+ /**
+ * Return whether the request headers should be included in the log message.
+ * @since 4.3
+ */
+ public boolean isIncludeHeaders() {
+ return this.includeHeaders;
+ }
+
+ /**
* Set whether the request payload (body) should be included in the log message.
* <p>Should be configured using an {@code <init-param>} for parameter name
* "includePayload" in the filter definition in {@code web.xml}.
@@ -276,6 +297,10 @@ public abstract class AbstractRequestLoggingFilter extends OncePerRequestFilter
}
}
+ if (isIncludeHeaders()) {
+ msg.append(";headers=").append(new ServletServerHttpRequest(request).getHeaders());
+ }
+
if (isIncludePayload()) {
ContentCachingRequestWrapper wrapper =
WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
diff --git a/spring-web/src/main/java/org/springframework/web/filter/CharacterEncodingFilter.java b/spring-web/src/main/java/org/springframework/web/filter/CharacterEncodingFilter.java
index 56a7c899..fc564f3f 100644
--- a/spring-web/src/main/java/org/springframework/web/filter/CharacterEncodingFilter.java
+++ b/spring-web/src/main/java/org/springframework/web/filter/CharacterEncodingFilter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -46,7 +46,9 @@ public class CharacterEncodingFilter extends OncePerRequestFilter {
private String encoding;
- private boolean forceEncoding = false;
+ private boolean forceRequestEncoding = false;
+
+ private boolean forceResponseEncoding = false;
/**
@@ -77,9 +79,26 @@ public class CharacterEncodingFilter extends OncePerRequestFilter {
* @see #setForceEncoding
*/
public CharacterEncodingFilter(String encoding, boolean forceEncoding) {
+ this(encoding, forceEncoding, forceEncoding);
+ }
+
+ /**
+ * Create a {@code CharacterEncodingFilter} for the given encoding.
+ * @param encoding the encoding to apply
+ * @param forceRequestEncoding whether the specified encoding is supposed to
+ * override existing request encodings
+ * @param forceResponseEncoding whether the specified encoding is supposed to
+ * override existing response encodings
+ * @since 4.3
+ * @see #setEncoding
+ * @see #setForceRequestEncoding(boolean)
+ * @see #setForceResponseEncoding(boolean)
+ */
+ public CharacterEncodingFilter(String encoding, boolean forceRequestEncoding, boolean forceResponseEncoding) {
Assert.hasLength(encoding, "Encoding must not be empty");
this.encoding = encoding;
- this.forceEncoding = forceEncoding;
+ this.forceRequestEncoding = forceRequestEncoding;
+ this.forceResponseEncoding = forceResponseEncoding;
}
@@ -95,15 +114,69 @@ public class CharacterEncodingFilter extends OncePerRequestFilter {
}
/**
+ * Return the configured encoding for requests and/or responses
+ * @since 4.3
+ */
+ public String getEncoding() {
+ return this.encoding;
+ }
+
+ /**
* Set whether the configured {@link #setEncoding encoding} of this filter
* is supposed to override existing request and response encodings.
* <p>Default is "false", i.e. do not modify the encoding if
* {@link javax.servlet.http.HttpServletRequest#getCharacterEncoding()}
* returns a non-null value. Switch this to "true" to enforce the specified
* encoding in any case, applying it as default response encoding as well.
+ * <p>This is the equivalent to setting both {@link #setForceRequestEncoding(boolean)}
+ * and {@link #setForceResponseEncoding(boolean)}.
+ * @see #setForceRequestEncoding(boolean)
+ * @see #setForceResponseEncoding(boolean)
*/
public void setForceEncoding(boolean forceEncoding) {
- this.forceEncoding = forceEncoding;
+ this.forceRequestEncoding = forceEncoding;
+ this.forceResponseEncoding = forceEncoding;
+ }
+
+ /**
+ * Set whether the configured {@link #setEncoding encoding} of this filter
+ * is supposed to override existing request encodings.
+ * <p>Default is "false", i.e. do not modify the encoding if
+ * {@link javax.servlet.http.HttpServletRequest#getCharacterEncoding()}
+ * returns a non-null value. Switch this to "true" to enforce the specified
+ * encoding in any case.
+ * @since 4.3
+ */
+ public void setForceRequestEncoding(boolean forceRequestEncoding) {
+ this.forceRequestEncoding = forceRequestEncoding;
+ }
+
+ /**
+ * Return whether the encoding should be forced on requests
+ * @since 4.3
+ */
+ public boolean isForceRequestEncoding() {
+ return this.forceRequestEncoding;
+ }
+
+ /**
+ * Set whether the configured {@link #setEncoding encoding} of this filter
+ * is supposed to override existing response encodings.
+ * <p>Default is "false", i.e. do not modify the encoding.
+ * Switch this to "true" to enforce the specified encoding
+ * for responses in any case.
+ * @since 4.3
+ */
+ public void setForceResponseEncoding(boolean forceResponseEncoding) {
+ this.forceResponseEncoding = forceResponseEncoding;
+ }
+
+ /**
+ * Return whether the encoding should be forced on responses.
+ * @since 4.3
+ */
+ public boolean isForceResponseEncoding() {
+ return this.forceResponseEncoding;
}
@@ -112,10 +185,13 @@ public class CharacterEncodingFilter extends OncePerRequestFilter {
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
- if (this.encoding != null && (this.forceEncoding || request.getCharacterEncoding() == null)) {
- request.setCharacterEncoding(this.encoding);
- if (this.forceEncoding) {
- response.setCharacterEncoding(this.encoding);
+ String encoding = getEncoding();
+ if (encoding != null) {
+ if (isForceRequestEncoding() || request.getCharacterEncoding() == null) {
+ request.setCharacterEncoding(encoding);
+ }
+ if (isForceResponseEncoding()) {
+ response.setCharacterEncoding(encoding);
}
}
filterChain.doFilter(request, response);
diff --git a/spring-web/src/main/java/org/springframework/web/filter/ForwardedHeaderFilter.java b/spring-web/src/main/java/org/springframework/web/filter/ForwardedHeaderFilter.java
new file mode 100644
index 00000000..a1196ec2
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/filter/ForwardedHeaderFilter.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.filter;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.http.HttpRequest;
+import org.springframework.http.server.ServletServerHttpRequest;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.LinkedCaseInsensitiveMap;
+import org.springframework.web.util.UriComponents;
+import org.springframework.web.util.UriComponentsBuilder;
+import org.springframework.web.util.UrlPathHelper;
+
+/**
+ * Filter that wraps the request in order to override its
+ * {@link HttpServletRequest#getServerName() getServerName()},
+ * {@link HttpServletRequest#getServerPort() getServerPort()},
+ * {@link HttpServletRequest#getScheme() getScheme()}, and
+ * {@link HttpServletRequest#isSecure() isSecure()} methods with values derived
+ * from "Forwarded" or "X-Forwarded-*" headers. In effect the wrapped request
+ * reflects the client-originated protocol and address.
+ *
+ * @author Rossen Stoyanchev
+ * @author Eddú Meléndez
+ * @since 4.3
+ */
+public class ForwardedHeaderFilter extends OncePerRequestFilter {
+
+ private static final Set<String> FORWARDED_HEADER_NAMES =
+ Collections.newSetFromMap(new LinkedCaseInsensitiveMap<Boolean>(5, Locale.ENGLISH));
+
+ static {
+ FORWARDED_HEADER_NAMES.add("Forwarded");
+ FORWARDED_HEADER_NAMES.add("X-Forwarded-Host");
+ FORWARDED_HEADER_NAMES.add("X-Forwarded-Port");
+ FORWARDED_HEADER_NAMES.add("X-Forwarded-Proto");
+ FORWARDED_HEADER_NAMES.add("X-Forwarded-Prefix");
+ }
+
+
+ private final UrlPathHelper pathHelper = new UrlPathHelper();
+
+
+ @Override
+ protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
+ Enumeration<String> names = request.getHeaderNames();
+ while (names.hasMoreElements()) {
+ String name = names.nextElement();
+ if (FORWARDED_HEADER_NAMES.contains(name)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ protected boolean shouldNotFilterAsyncDispatch() {
+ return false;
+ }
+
+ @Override
+ protected boolean shouldNotFilterErrorDispatch() {
+ return false;
+ }
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
+ FilterChain filterChain) throws ServletException, IOException {
+
+ filterChain.doFilter(new ForwardedHeaderRequestWrapper(request, this.pathHelper), response);
+ }
+
+
+ private static class ForwardedHeaderRequestWrapper extends HttpServletRequestWrapper {
+
+ private final String scheme;
+
+ private final boolean secure;
+
+ private final String host;
+
+ private final int port;
+
+ private final String contextPath;
+
+ private final String requestUri;
+
+ private final StringBuffer requestUrl;
+
+ private final Map<String, List<String>> headers;
+
+ public ForwardedHeaderRequestWrapper(HttpServletRequest request, UrlPathHelper pathHelper) {
+ super(request);
+
+ HttpRequest httpRequest = new ServletServerHttpRequest(request);
+ UriComponents uriComponents = UriComponentsBuilder.fromHttpRequest(httpRequest).build();
+ int port = uriComponents.getPort();
+
+ this.scheme = uriComponents.getScheme();
+ this.secure = "https".equals(scheme);
+ this.host = uriComponents.getHost();
+ this.port = (port == -1 ? (this.secure ? 443 : 80) : port);
+
+ String prefix = getForwardedPrefix(request);
+ this.contextPath = (prefix != null ? prefix : request.getContextPath());
+ this.requestUri = this.contextPath + pathHelper.getPathWithinApplication(request);
+ this.requestUrl = new StringBuffer(this.scheme + "://" + this.host +
+ (port == -1 ? "" : ":" + port) + this.requestUri);
+ this.headers = initHeaders(request);
+ }
+
+ private static String getForwardedPrefix(HttpServletRequest request) {
+ String prefix = null;
+ Enumeration<String> names = request.getHeaderNames();
+ while (names.hasMoreElements()) {
+ String name = names.nextElement();
+ if ("X-Forwarded-Prefix".equalsIgnoreCase(name)) {
+ prefix = request.getHeader(name);
+ }
+ }
+ if (prefix != null) {
+ while (prefix.endsWith("/")) {
+ prefix = prefix.substring(0, prefix.length() - 1);
+ }
+ }
+ return prefix;
+ }
+
+ /**
+ * Copy the headers excluding any {@link #FORWARDED_HEADER_NAMES}.
+ */
+ private static Map<String, List<String>> initHeaders(HttpServletRequest request) {
+ Map<String, List<String>> headers = new LinkedCaseInsensitiveMap<List<String>>(Locale.ENGLISH);
+ Enumeration<String> names = request.getHeaderNames();
+ while (names.hasMoreElements()) {
+ String name = names.nextElement();
+ if (!FORWARDED_HEADER_NAMES.contains(name)) {
+ headers.put(name, Collections.list(request.getHeaders(name)));
+ }
+ }
+ return headers;
+ }
+
+ @Override
+ public String getScheme() {
+ return this.scheme;
+ }
+
+ @Override
+ public String getServerName() {
+ return this.host;
+ }
+
+ @Override
+ public int getServerPort() {
+ return this.port;
+ }
+
+ @Override
+ public boolean isSecure() {
+ return this.secure;
+ }
+
+ @Override
+ public String getContextPath() {
+ return this.contextPath;
+ }
+
+ @Override
+ public String getRequestURI() {
+ return this.requestUri;
+ }
+
+ @Override
+ public StringBuffer getRequestURL() {
+ return this.requestUrl;
+ }
+
+ // Override header accessors to not expose forwarded headers
+
+ @Override
+ public String getHeader(String name) {
+ List<String> value = this.headers.get(name);
+ return (CollectionUtils.isEmpty(value) ? null : value.get(0));
+ }
+
+ @Override
+ public Enumeration<String> getHeaders(String name) {
+ List<String> value = this.headers.get(name);
+ return (Collections.enumeration(value != null ? value : Collections.<String>emptySet()));
+ }
+
+ @Override
+ public Enumeration<String> getHeaderNames() {
+ return Collections.enumeration(this.headers.keySet());
+ }
+ }
+
+}
diff --git a/spring-web/src/main/java/org/springframework/web/filter/ShallowEtagHeaderFilter.java b/spring-web/src/main/java/org/springframework/web/filter/ShallowEtagHeaderFilter.java
index b3fbe838..c94791a5 100644
--- a/spring-web/src/main/java/org/springframework/web/filter/ShallowEtagHeaderFilter.java
+++ b/spring-web/src/main/java/org/springframework/web/filter/ShallowEtagHeaderFilter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@ package org.springframework.web.filter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
+
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
@@ -60,11 +61,28 @@ public class ShallowEtagHeaderFilter extends OncePerRequestFilter {
private static final String STREAMING_ATTRIBUTE = ShallowEtagHeaderFilter.class.getName() + ".STREAMING";
-
/** Checking for Servlet 3.0+ HttpServletResponse.getHeader(String) */
private static final boolean servlet3Present =
ClassUtils.hasMethod(HttpServletResponse.class, "getHeader", String.class);
+ private boolean writeWeakETag = false;
+
+ /**
+ * Set whether the ETag value written to the response should be weak, as per rfc7232.
+ * <p>Should be configured using an {@code <init-param>} for parameter name
+ * "writeWeakETag" in the filter definition in {@code web.xml}.
+ * @see <a href="https://tools.ietf.org/html/rfc7232#section-2.3">rfc7232 section-2.3</a>
+ */
+ public boolean isWriteWeakETag() {
+ return writeWeakETag;
+ }
+
+ /**
+ * Return whether the ETag value written to the response should be weak, as per rfc7232.
+ */
+ public void setWriteWeakETag(boolean writeWeakETag) {
+ this.writeWeakETag = writeWeakETag;
+ }
/**
* The default value is "false" so that the filter may delay the generation of
@@ -102,10 +120,13 @@ public class ShallowEtagHeaderFilter extends OncePerRequestFilter {
responseWrapper.copyBodyToResponse();
}
else if (isEligibleForEtag(request, responseWrapper, statusCode, responseWrapper.getContentInputStream())) {
- String responseETag = generateETagHeaderValue(responseWrapper.getContentInputStream());
+ String responseETag = generateETagHeaderValue(responseWrapper.getContentInputStream(), this.writeWeakETag);
rawResponse.setHeader(HEADER_ETAG, responseETag);
String requestETag = request.getHeader(HEADER_IF_NONE_MATCH);
- if (responseETag.equals(requestETag)) {
+ if (requestETag != null
+ && (responseETag.equals(requestETag)
+ || responseETag.replaceFirst("^W/", "").equals(requestETag.replaceFirst("^W/", ""))
+ || "*".equals(requestETag))) {
if (logger.isTraceEnabled()) {
logger.trace("ETag [" + responseETag + "] equal to If-None-Match, sending 304");
}
@@ -144,7 +165,10 @@ public class ShallowEtagHeaderFilter extends OncePerRequestFilter {
protected boolean isEligibleForEtag(HttpServletRequest request, HttpServletResponse response,
int responseStatusCode, InputStream inputStream) {
- if (responseStatusCode >= 200 && responseStatusCode < 300 && HttpMethod.GET.matches(request.getMethod())) {
+ String method = request.getMethod();
+ if (responseStatusCode >= 200 && responseStatusCode < 300 &&
+ (HttpMethod.GET.matches(method) || HttpMethod.HEAD.matches(method))) {
+
String cacheControl = null;
if (servlet3Present) {
cacheControl = response.getHeader(HEADER_CACHE_CONTROL);
@@ -160,11 +184,17 @@ public class ShallowEtagHeaderFilter extends OncePerRequestFilter {
* Generate the ETag header value from the given response body byte array.
* <p>The default implementation generates an MD5 hash.
* @param inputStream the response body as an InputStream
+ * @param isWeak whether the generated ETag should be weak
* @return the ETag header value
* @see org.springframework.util.DigestUtils
*/
- protected String generateETagHeaderValue(InputStream inputStream) throws IOException {
- StringBuilder builder = new StringBuilder("\"0");
+ protected String generateETagHeaderValue(InputStream inputStream, boolean isWeak) throws IOException {
+ // length of W/ + 0 + " + 32bits md5 hash + "
+ StringBuilder builder = new StringBuilder(37);
+ if (isWeak) {
+ builder.append("W/");
+ }
+ builder.append("\"0");
DigestUtils.appendMd5DigestAsHex(inputStream, builder);
builder.append('"');
return builder.toString();
diff --git a/spring-web/src/main/java/org/springframework/web/method/ControllerAdviceBean.java b/spring-web/src/main/java/org/springframework/web/method/ControllerAdviceBean.java
index 0f857952..1567b39b 100644
--- a/spring-web/src/main/java/org/springframework/web/method/ControllerAdviceBean.java
+++ b/spring-web/src/main/java/org/springframework/web/method/ControllerAdviceBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@ import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.core.Ordered;
+import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.OrderUtils;
import org.springframework.util.Assert;
@@ -102,7 +103,9 @@ public class ControllerAdviceBean implements Ordered {
this.order = initOrderFromBean(bean);
}
- ControllerAdvice annotation = AnnotationUtils.findAnnotation(beanType, ControllerAdvice.class);
+ ControllerAdvice annotation =
+ AnnotatedElementUtils.findMergedAnnotation(beanType, ControllerAdvice.class);
+
if (annotation != null) {
this.basePackages = initBasePackages(annotation);
this.assignableTypes = Arrays.asList(annotation.assignableTypes());
diff --git a/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java b/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java
index 33a9b291..74f99557 100644
--- a/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java
+++ b/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -226,7 +226,7 @@ public class HandlerMethod {
* if no annotation can be found on the given method itself.
* <p>Also supports <em>merged</em> composed annotations with attribute
* overrides as of Spring Framework 4.2.2.
- * @param annotationType the type of annotation to introspect the method for.
+ * @param annotationType the type of annotation to introspect the method for
* @return the annotation, or {@code null} if none found
* @see AnnotatedElementUtils#findMergedAnnotation
*/
@@ -235,6 +235,16 @@ public class HandlerMethod {
}
/**
+ * Return whether the parameter is declared with the given annotation type.
+ * @param annotationType the annotation type to look for
+ * @since 4.3
+ * @see AnnotatedElementUtils#hasAnnotation
+ */
+ public <A extends Annotation> boolean hasMethodAnnotation(Class<A> annotationType) {
+ return AnnotatedElementUtils.hasAnnotation(this.method, annotationType);
+ }
+
+ /**
* If the provided instance contains a bean name rather than an object instance,
* the bean name is resolved before a {@link HandlerMethod} is created and returned.
*/
@@ -247,6 +257,15 @@ public class HandlerMethod {
return new HandlerMethod(this, handler);
}
+ /**
+ * Return a short representation of this handler method for log message purposes.
+ * @since 4.3
+ */
+ public String getShortLogMessage() {
+ int args = this.method.getParameterTypes().length;
+ return getBeanType().getName() + "#" + this.method.getName() + "[" + args + " args]";
+ }
+
@Override
public boolean equals(Object other) {
@@ -280,6 +299,10 @@ public class HandlerMethod {
super(HandlerMethod.this.bridgedMethod, index);
}
+ protected HandlerMethodParameter(HandlerMethodParameter original) {
+ super(original);
+ }
+
@Override
public Class<?> getContainingClass() {
return HandlerMethod.this.getBeanType();
@@ -289,6 +312,16 @@ public class HandlerMethod {
public <T extends Annotation> T getMethodAnnotation(Class<T> annotationType) {
return HandlerMethod.this.getMethodAnnotation(annotationType);
}
+
+ @Override
+ public <T extends Annotation> boolean hasMethodAnnotation(Class<T> annotationType) {
+ return HandlerMethod.this.hasMethodAnnotation(annotationType);
+ }
+
+ @Override
+ public HandlerMethodParameter clone() {
+ return new HandlerMethodParameter(this);
+ }
}
@@ -304,10 +337,20 @@ public class HandlerMethod {
this.returnValue = returnValue;
}
+ protected ReturnValueMethodParameter(ReturnValueMethodParameter original) {
+ super(original);
+ this.returnValue = original.returnValue;
+ }
+
@Override
public Class<?> getParameterType() {
return (this.returnValue != null ? this.returnValue.getClass() : super.getParameterType());
}
+
+ @Override
+ public ReturnValueMethodParameter clone() {
+ return new ReturnValueMethodParameter(this);
+ }
}
}
diff --git a/spring-web/src/main/java/org/springframework/web/method/HandlerMethodSelector.java b/spring-web/src/main/java/org/springframework/web/method/HandlerMethodSelector.java
index 82acd2c3..4fc0545b 100644
--- a/spring-web/src/main/java/org/springframework/web/method/HandlerMethodSelector.java
+++ b/spring-web/src/main/java/org/springframework/web/method/HandlerMethodSelector.java
@@ -39,7 +39,7 @@ public abstract class HandlerMethodSelector {
* @param handlerType the handler type to search handler methods on
* @param handlerMethodFilter a {@link MethodFilter} to help recognize handler methods of interest
* @return the selected methods, or an empty set
- * @see MethodIntrospector#selectMethods(Class, MethodFilter)
+ * @see MethodIntrospector#selectMethods
*/
public static Set<Method> selectMethods(Class<?> handlerType, MethodFilter handlerMethodFilter) {
return MethodIntrospector.selectMethods(handlerType, handlerMethodFilter);
diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractCookieValueMethodArgumentResolver.java b/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractCookieValueMethodArgumentResolver.java
index aa724396..9d938367 100644
--- a/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractCookieValueMethodArgumentResolver.java
+++ b/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractCookieValueMethodArgumentResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -63,7 +63,7 @@ public abstract class AbstractCookieValueMethodArgumentResolver extends Abstract
@Override
protected void handleMissingValue(String name, MethodParameter parameter) throws ServletRequestBindingException {
throw new ServletRequestBindingException("Missing cookie '" + name +
- "' for method parameter of type " + parameter.getParameterType().getSimpleName());
+ "' for method parameter of type " + parameter.getNestedParameterType().getSimpleName());
}
diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java b/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java
index 78a3a345..e2e92d64 100644
--- a/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java
+++ b/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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 org.springframework.beans.factory.config.BeanExpressionContext;
import org.springframework.beans.factory.config.BeanExpressionResolver;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.MethodParameter;
+import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ValueConstants;
import org.springframework.web.bind.support.WebDataBinderFactory;
@@ -53,6 +54,7 @@ import org.springframework.web.method.support.ModelAndViewContainer;
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
+ * @author Juergen Hoeller
* @since 3.1
*/
public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver {
@@ -61,7 +63,7 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle
private final BeanExpressionContext expressionContext;
- private Map<MethodParameter, NamedValueInfo> namedValueInfoCache = new ConcurrentHashMap<MethodParameter, NamedValueInfo>(256);
+ private final Map<MethodParameter, NamedValueInfo> namedValueInfoCache = new ConcurrentHashMap<MethodParameter, NamedValueInfo>(256);
public AbstractNamedValueMethodArgumentResolver() {
@@ -84,27 +86,33 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle
public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
- Class<?> paramType = parameter.getParameterType();
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
+ MethodParameter nestedParameter = parameter.nestedIfOptional();
- Object arg = resolveName(namedValueInfo.name, parameter, webRequest);
+ Object resolvedName = resolveStringValue(namedValueInfo.name);
+ if (resolvedName == null) {
+ throw new IllegalArgumentException(
+ "Specified name must not resolve to null: [" + namedValueInfo.name + "]");
+ }
+
+ Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
if (arg == null) {
if (namedValueInfo.defaultValue != null) {
- arg = resolveDefaultValue(namedValueInfo.defaultValue);
+ arg = resolveStringValue(namedValueInfo.defaultValue);
}
- else if (namedValueInfo.required && !parameter.getParameterType().getName().equals("java.util.Optional")) {
- handleMissingValue(namedValueInfo.name, parameter);
+ else if (namedValueInfo.required && !nestedParameter.isOptional()) {
+ handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
- arg = handleNullValue(namedValueInfo.name, arg, paramType);
+ arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
- arg = resolveDefaultValue(namedValueInfo.defaultValue);
+ arg = resolveStringValue(namedValueInfo.defaultValue);
}
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
try {
- arg = binder.convertIfNecessary(arg, paramType, parameter);
+ arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
}
catch (ConversionNotSupportedException ex) {
throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
@@ -151,7 +159,8 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle
if (info.name.length() == 0) {
name = parameter.getParameterName();
if (name == null) {
- throw new IllegalArgumentException("Name for argument type [" + parameter.getParameterType().getName() +
+ throw new IllegalArgumentException(
+ "Name for argument type [" + parameter.getNestedParameterType().getName() +
"] not available, and parameter name information not found in class file either.");
}
}
@@ -160,29 +169,43 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle
}
/**
- * Resolves the given parameter type and value name into an argument value.
+ * Resolve the given annotation-specified value,
+ * potentially containing placeholders and expressions.
+ */
+ private Object resolveStringValue(String value) {
+ if (this.configurableBeanFactory == null) {
+ return value;
+ }
+ String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(value);
+ BeanExpressionResolver exprResolver = this.configurableBeanFactory.getBeanExpressionResolver();
+ if (exprResolver == null) {
+ return value;
+ }
+ return exprResolver.evaluate(placeholdersResolved, this.expressionContext);
+ }
+
+ /**
+ * Resolve the given parameter type and value name into an argument value.
* @param name the name of the value being resolved
* @param parameter the method parameter to resolve to an argument value
+ * (pre-nested in case of a {@link java.util.Optional} declaration)
* @param request the current request
- * @return the resolved argument. May be {@code null}
+ * @return the resolved argument (may be {@code null})
* @throws Exception in case of errors
*/
protected abstract Object resolveName(String name, MethodParameter parameter, NativeWebRequest request)
throws Exception;
/**
- * Resolves the given default value into an argument value.
+ * Invoked when a named value is required, but {@link #resolveName(String, MethodParameter, NativeWebRequest)}
+ * returned {@code null} and there is no default value. Subclasses typically throw an exception in this case.
+ * @param name the name for the value
+ * @param parameter the method parameter
+ * @param request the current request
+ * @since 4.3
*/
- private Object resolveDefaultValue(String defaultValue) {
- if (this.configurableBeanFactory == null) {
- return defaultValue;
- }
- String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(defaultValue);
- BeanExpressionResolver exprResolver = this.configurableBeanFactory.getBeanExpressionResolver();
- if (exprResolver == null) {
- return defaultValue;
- }
- return exprResolver.evaluate(placeholdersResolved, this.expressionContext);
+ protected void handleMissingValue(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
+ handleMissingValue(name, parameter);
}
/**
@@ -191,7 +214,10 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle
* @param name the name for the value
* @param parameter the method parameter
*/
- protected abstract void handleMissingValue(String name, MethodParameter parameter) throws ServletException;
+ protected void handleMissingValue(String name, MethodParameter parameter) throws ServletException {
+ throw new ServletRequestBindingException("Missing argument '" + name +
+ "' for method parameter of type " + parameter.getNestedParameterType().getSimpleName());
+ }
/**
* A {@code null} results in a {@code false} value for {@code boolean}s or an exception for other primitives.
@@ -202,7 +228,7 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle
return Boolean.FALSE;
}
else if (paramType.isPrimitive()) {
- throw new IllegalStateException("Optional " + paramType + " parameter '" + name +
+ throw new IllegalStateException("Optional " + paramType.getSimpleName() + " parameter '" + name +
"' is present but cannot be translated into a null value due to being declared as a " +
"primitive type. Consider declaring it as object wrapper for the corresponding primitive type.");
}
diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/ExceptionHandlerMethodResolver.java b/spring-web/src/main/java/org/springframework/web/method/annotation/ExceptionHandlerMethodResolver.java
index 5d307cba..c121f575 100644
--- a/spring-web/src/main/java/org/springframework/web/method/annotation/ExceptionHandlerMethodResolver.java
+++ b/spring-web/src/main/java/org/springframework/web/method/annotation/ExceptionHandlerMethodResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -38,6 +38,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
* to the exception types supported by a given {@link Method}.
*
* @author Rossen Stoyanchev
+ * @author Juergen Hoeller
* @since 3.1
*/
public class ExceptionHandlerMethodResolver {
@@ -98,8 +99,8 @@ public class ExceptionHandlerMethodResolver {
}
protected void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) {
- ExceptionHandler annot = AnnotationUtils.findAnnotation(method, ExceptionHandler.class);
- result.addAll(Arrays.asList(annot.value()));
+ ExceptionHandler ann = AnnotationUtils.findAnnotation(method, ExceptionHandler.class);
+ result.addAll(Arrays.asList(ann.value()));
}
private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
@@ -124,7 +125,14 @@ public class ExceptionHandlerMethodResolver {
* @return a Method to handle the exception, or {@code null} if none found
*/
public Method resolveMethod(Exception exception) {
- return resolveMethodByExceptionType(exception.getClass());
+ Method method = resolveMethodByExceptionType(exception.getClass());
+ if (method == null) {
+ Throwable cause = exception.getCause();
+ if (cause != null) {
+ method = resolveMethodByExceptionType(cause.getClass());
+ }
+ }
+ return method;
}
/**
@@ -133,7 +141,7 @@ public class ExceptionHandlerMethodResolver {
* @param exceptionType the exception type
* @return a Method to handle the exception, or {@code null} if none found
*/
- public Method resolveMethodByExceptionType(Class<? extends Exception> exceptionType) {
+ public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {
Method method = this.exceptionLookupCache.get(exceptionType);
if (method == null) {
method = getMappedMethod(exceptionType);
@@ -145,7 +153,7 @@ public class ExceptionHandlerMethodResolver {
/**
* Return the {@link Method} mapped to the given exception type, or {@code null} if none.
*/
- private Method getMappedMethod(Class<? extends Exception> exceptionType) {
+ private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
List<Class<? extends Throwable>> matches = new ArrayList<Class<? extends Throwable>>();
for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
if (mappedException.isAssignableFrom(exceptionType)) {
diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessor.java b/spring-web/src/main/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessor.java
index 72e43cd1..8c82eefd 100644
--- a/spring-web/src/main/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessor.java
+++ b/spring-web/src/main/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -38,23 +38,24 @@ import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
- * Resolves method arguments annotated with {@code @ModelAttribute} and handles
- * return values from methods annotated with {@code @ModelAttribute}.
+ * Resolve {@code @ModelAttribute} annotated method arguments and handle
+ * return values from {@code @ModelAttribute} annotated methods.
*
- * <p>Model attributes are obtained from the model or if not found possibly
- * created with a default constructor if it is available. Once created, the
- * attributed is populated with request data via data binding and also
- * validation may be applied if the argument is annotated with
- * {@code @javax.validation.Valid}.
+ * <p>Model attributes are obtained from the model or created with a default
+ * constructor (and then added to the model). Once created the attribute is
+ * populated via data binding to Servlet request parameters. Validation may be
+ * applied if the argument is annotated with {@code @javax.validation.Valid}.
+ * or Spring's own {@code @org.springframework.validation.annotation.Validated}.
*
- * <p>When this handler is created with {@code annotationNotRequired=true},
+ * <p>When this handler is created with {@code annotationNotRequired=true}
* any non-simple type argument and return value is regarded as a model
* attribute with or without the presence of an {@code @ModelAttribute}.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
-public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
+public class ModelAttributeMethodProcessor
+ implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
protected final Log logger = LogFactory.getLog(getClass());
@@ -62,6 +63,7 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
/**
+ * Class constructor.
* @param annotationNotRequired if "true", non-simple method arguments and
* return values are considered model attributes with or without a
* {@code @ModelAttribute} annotation.
@@ -72,20 +74,14 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
/**
- * Returns {@code true} if the parameter is annotated with {@link ModelAttribute}
- * or in default resolution mode, and also if it is not a simple type.
+ * Returns {@code true} if the parameter is annotated with
+ * {@link ModelAttribute} or, if in default resolution mode, for any
+ * method parameter that is not a simple type.
*/
@Override
public boolean supportsParameter(MethodParameter parameter) {
- if (parameter.hasParameterAnnotation(ModelAttribute.class)) {
- return true;
- }
- else if (this.annotationNotRequired) {
- return !BeanUtils.isSimpleProperty(parameter.getParameterType());
- }
- else {
- return false;
- }
+ return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
+ (this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}
/**
@@ -102,12 +98,21 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
String name = ModelFactory.getNameForParameter(parameter);
- Object attribute = (mavContainer.containsAttribute(name) ?
- mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, webRequest));
+ Object attribute = (mavContainer.containsAttribute(name) ? mavContainer.getModel().get(name) :
+ createAttribute(name, parameter, binderFactory, webRequest));
+
+ if (!mavContainer.isBindingDisabled(name)) {
+ ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
+ if (ann != null && !ann.binding()) {
+ mavContainer.setBindingDisabled(name);
+ }
+ }
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
- bindRequestParameters(binder, webRequest);
+ if (!mavContainer.isBindingDisabled(name)) {
+ bindRequestParameters(binder, webRequest);
+ }
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
@@ -182,19 +187,13 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
/**
* Return {@code true} if there is a method-level {@code @ModelAttribute}
- * or if it is a non-simple type when {@code annotationNotRequired=true}.
+ * or, in default resolution mode, for any return value type that is not
+ * a simple type.
*/
@Override
public boolean supportsReturnType(MethodParameter returnType) {
- if (returnType.getMethodAnnotation(ModelAttribute.class) != null) {
- return true;
- }
- else if (this.annotationNotRequired) {
- return !BeanUtils.isSimpleProperty(returnType.getParameterType());
- }
- else {
- return false;
- }
+ return (returnType.hasMethodAnnotation(ModelAttribute.class) ||
+ (this.annotationNotRequired && !BeanUtils.isSimpleProperty(returnType.getParameterType())));
}
/**
diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java b/spring-web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java
index 9eb31c0d..be4472d3 100644
--- a/spring-web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java
+++ b/spring-web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java
@@ -112,7 +112,7 @@ public final class ModelFactory {
if (!container.containsAttribute(name)) {
Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
if (value == null) {
- throw new HttpSessionRequiredException("Expected session attribute '" + name + "'");
+ throw new HttpSessionRequiredException("Expected session attribute '" + name + "'", name);
}
container.addAttribute(name, value);
}
@@ -128,14 +128,20 @@ public final class ModelFactory {
while (!this.modelMethods.isEmpty()) {
InvocableHandlerMethod modelMethod = getNextModelMethod(container).getHandlerMethod();
- String modelName = modelMethod.getMethodAnnotation(ModelAttribute.class).value();
- if (container.containsAttribute(modelName)) {
+ ModelAttribute ann = modelMethod.getMethodAnnotation(ModelAttribute.class);
+ if (container.containsAttribute(ann.name())) {
+ if (!ann.binding()) {
+ container.setBindingDisabled(ann.name());
+ }
continue;
}
Object returnValue = modelMethod.invokeForRequest(request, container);
if (!modelMethod.isVoid()){
String returnValueName = getNameForReturnValue(returnValue, modelMethod.getReturnType());
+ if (!ann.binding()) {
+ container.setBindingDisabled(returnValueName);
+ }
if (!container.containsAttribute(returnValueName)) {
container.addAttribute(returnValueName, returnValue);
}
diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/RequestHeaderMethodArgumentResolver.java b/spring-web/src/main/java/org/springframework/web/method/annotation/RequestHeaderMethodArgumentResolver.java
index 3ef1b4fe..c8575251 100644
--- a/spring-web/src/main/java/org/springframework/web/method/annotation/RequestHeaderMethodArgumentResolver.java
+++ b/spring-web/src/main/java/org/springframework/web/method/annotation/RequestHeaderMethodArgumentResolver.java
@@ -56,7 +56,7 @@ public class RequestHeaderMethodArgumentResolver extends AbstractNamedValueMetho
@Override
public boolean supportsParameter(MethodParameter parameter) {
return (parameter.hasParameterAnnotation(RequestHeader.class) &&
- !Map.class.isAssignableFrom(parameter.getParameterType()));
+ !Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType()));
}
@Override
@@ -79,7 +79,7 @@ public class RequestHeaderMethodArgumentResolver extends AbstractNamedValueMetho
@Override
protected void handleMissingValue(String name, MethodParameter parameter) throws ServletRequestBindingException {
throw new ServletRequestBindingException("Missing request header '" + name +
- "' for method parameter of type " + parameter.getParameterType().getSimpleName());
+ "' for method parameter of type " + parameter.getNestedParameterType().getSimpleName());
}
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 c481b152..bc065619 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
@@ -17,22 +17,17 @@
package org.springframework.web.method.annotation;
import java.beans.PropertyEditor;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
-import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.Part;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
-import org.springframework.core.GenericCollectionTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter;
-import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.WebDataBinder;
@@ -45,6 +40,8 @@ import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.MultipartResolver;
+import org.springframework.web.multipart.support.MissingServletRequestPartException;
+import org.springframework.web.multipart.support.MultipartResolutionDelegate;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.WebUtils;
@@ -85,7 +82,7 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod
/**
* @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 it isn't annotated, the
+ * is treated as a request parameter even if it isn't annotated, the
* request parameter name is derived from the method parameter name.
*/
public RequestParamMethodArgumentResolver(boolean useDefaultResolution) {
@@ -98,7 +95,7 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod
* values are not expected to contain expressions
* @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 it isn't annotated, the
+ * is treated as a request parameter even if it isn't annotated, the
* request parameter name is derived from the method parameter name.
*/
public RequestParamMethodArgumentResolver(ConfigurableBeanFactory beanFactory, boolean useDefaultResolution) {
@@ -124,9 +121,8 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod
*/
@Override
public boolean supportsParameter(MethodParameter parameter) {
- Class<?> paramType = parameter.getParameterType();
if (parameter.hasParameterAnnotation(RequestParam.class)) {
- if (Map.class.isAssignableFrom(paramType)) {
+ if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
String paramName = parameter.getParameterAnnotation(RequestParam.class).name();
return StringUtils.hasText(paramName);
}
@@ -138,11 +134,12 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod
if (parameter.hasParameterAnnotation(RequestPart.class)) {
return false;
}
- else if (MultipartFile.class == paramType || "javax.servlet.http.Part".equals(paramType.getName())) {
+ parameter = parameter.nestedIfOptional();
+ if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
return true;
}
else if (this.useDefaultResolution) {
- return BeanUtils.isSimpleProperty(paramType);
+ return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
}
else {
return false;
@@ -157,105 +154,53 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod
}
@Override
- protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest webRequest) throws Exception {
- HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
+ protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
+ HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
MultipartHttpServletRequest multipartRequest =
WebUtils.getNativeRequest(servletRequest, MultipartHttpServletRequest.class);
- Object arg;
- if (MultipartFile.class == parameter.getParameterType()) {
- assertIsMultipartRequest(servletRequest);
- Assert.notNull(multipartRequest, "Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
- arg = multipartRequest.getFile(name);
+ Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
+ if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
+ return mpArg;
}
- else if (isMultipartFileCollection(parameter)) {
- assertIsMultipartRequest(servletRequest);
- Assert.notNull(multipartRequest, "Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
- arg = multipartRequest.getFiles(name);
- }
- else if (isMultipartFileArray(parameter)) {
- assertIsMultipartRequest(servletRequest);
- Assert.notNull(multipartRequest, "Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
- List<MultipartFile> multipartFiles = multipartRequest.getFiles(name);
- arg = multipartFiles.toArray(new MultipartFile[multipartFiles.size()]);
- }
- else if ("javax.servlet.http.Part".equals(parameter.getParameterType().getName())) {
- assertIsMultipartRequest(servletRequest);
- arg = servletRequest.getPart(name);
- }
- else if (isPartCollection(parameter)) {
- assertIsMultipartRequest(servletRequest);
- arg = new ArrayList<Object>(servletRequest.getParts());
- }
- else if (isPartArray(parameter)) {
- assertIsMultipartRequest(servletRequest);
- arg = RequestPartResolver.resolvePart(servletRequest);
- }
- else {
- arg = null;
- if (multipartRequest != null) {
- List<MultipartFile> files = multipartRequest.getFiles(name);
- if (!files.isEmpty()) {
- arg = (files.size() == 1 ? files.get(0) : files);
- }
+
+ Object arg = null;
+ if (multipartRequest != null) {
+ List<MultipartFile> files = multipartRequest.getFiles(name);
+ if (!files.isEmpty()) {
+ arg = (files.size() == 1 ? files.get(0) : files);
}
- if (arg == null) {
- String[] paramValues = webRequest.getParameterValues(name);
- if (paramValues != null) {
- arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
- }
+ }
+ if (arg == null) {
+ String[] paramValues = request.getParameterValues(name);
+ if (paramValues != null) {
+ arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
}
}
-
return arg;
}
- private void assertIsMultipartRequest(HttpServletRequest request) {
- String contentType = request.getContentType();
- if (contentType == null || !contentType.toLowerCase().startsWith("multipart/")) {
- throw new MultipartException("The current request is not a multipart request");
- }
- }
-
- private boolean isMultipartFileCollection(MethodParameter parameter) {
- return (MultipartFile.class == getCollectionParameterType(parameter));
- }
-
- private boolean isMultipartFileArray(MethodParameter parameter) {
- return (MultipartFile.class == parameter.getParameterType().getComponentType());
- }
-
- private boolean isPartCollection(MethodParameter parameter) {
- Class<?> collectionType = getCollectionParameterType(parameter);
- return (collectionType != null && "javax.servlet.http.Part".equals(collectionType.getName()));
- }
-
- private boolean isPartArray(MethodParameter parameter) {
- Class<?> paramType = parameter.getParameterType().getComponentType();
- return (paramType != null && "javax.servlet.http.Part".equals(paramType.getName()));
- }
-
- private Class<?> getCollectionParameterType(MethodParameter parameter) {
- Class<?> paramType = parameter.getParameterType();
- if (Collection.class == paramType || List.class.isAssignableFrom(paramType)){
- Class<?> valueType = GenericCollectionTypeResolver.getCollectionParameterType(parameter);
- if (valueType != null) {
- return valueType;
+ @Override
+ protected void handleMissingValue(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
+ HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
+ if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
+ if (!MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
+ throw new MultipartException("Current request is not a multipart request");
+ }
+ else {
+ throw new MissingServletRequestPartException(name);
}
}
- return null;
- }
-
- @Override
- protected void handleMissingValue(String name, MethodParameter parameter) throws ServletException {
- throw new MissingServletRequestParameterException(name, parameter.getParameterType().getSimpleName());
+ else {
+ throw new MissingServletRequestParameterException(name, parameter.getNestedParameterType().getSimpleName());
+ }
}
@Override
public void contributeMethodArgument(MethodParameter parameter, Object value,
UriComponentsBuilder builder, Map<String, Object> uriVariables, ConversionService conversionService) {
- Class<?> paramType = parameter.getParameterType();
+ Class<?> paramType = parameter.getNestedParameterType();
if (Map.class.isAssignableFrom(paramType) || MultipartFile.class == paramType ||
"javax.servlet.http.Part".equals(paramType.getName())) {
return;
@@ -266,6 +211,11 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod
parameter.getParameterName() : requestParam.name());
if (value == null) {
+ if (requestParam != null) {
+ if (!requestParam.required() || !requestParam.defaultValue().equals(ValueConstants.DEFAULT_NONE)) {
+ return;
+ }
+ }
builder.queryParam(name);
}
else if (value instanceof Collection) {
@@ -306,12 +256,4 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod
}
}
-
- private static class RequestPartResolver {
-
- public static Object resolvePart(HttpServletRequest servletRequest) throws Exception {
- return servletRequest.getParts().toArray(new Part[servletRequest.getParts().size()]);
- }
- }
-
}
diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/SessionAttributesHandler.java b/spring-web/src/main/java/org/springframework/web/method/annotation/SessionAttributesHandler.java
index 3ff2b315..8cc07e46 100644
--- a/spring-web/src/main/java/org/springframework/web/method/annotation/SessionAttributesHandler.java
+++ b/spring-web/src/main/java/org/springframework/web/method/annotation/SessionAttributesHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,7 +24,7 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
-import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionAttributeStore;
@@ -65,10 +65,11 @@ public class SessionAttributesHandler {
* @param sessionAttributeStore used for session access
*/
public SessionAttributesHandler(Class<?> handlerType, SessionAttributeStore sessionAttributeStore) {
- Assert.notNull(sessionAttributeStore, "SessionAttributeStore may not be null.");
+ Assert.notNull(sessionAttributeStore, "SessionAttributeStore may not be null");
this.sessionAttributeStore = sessionAttributeStore;
- SessionAttributes annotation = AnnotationUtils.findAnnotation(handlerType, SessionAttributes.class);
+ SessionAttributes annotation =
+ AnnotatedElementUtils.findMergedAnnotation(handlerType, SessionAttributes.class);
if (annotation != null) {
this.attributeNames.addAll(Arrays.asList(annotation.names()));
this.attributeTypes.addAll(Arrays.asList(annotation.types()));
@@ -84,7 +85,7 @@ public class SessionAttributesHandler {
* session attributes through an {@link SessionAttributes} annotation.
*/
public boolean hasSessionAttributes() {
- return ((this.attributeNames.size() > 0) || (this.attributeTypes.size() > 0));
+ return (this.attributeNames.size() > 0 || this.attributeTypes.size() > 0);
}
/**
diff --git a/spring-web/src/main/java/org/springframework/web/method/support/HandlerMethodArgumentResolverComposite.java b/spring-web/src/main/java/org/springframework/web/method/support/HandlerMethodArgumentResolverComposite.java
index 5492582e..e80655a9 100644
--- a/spring-web/src/main/java/org/springframework/web/method/support/HandlerMethodArgumentResolverComposite.java
+++ b/spring-web/src/main/java/org/springframework/web/method/support/HandlerMethodArgumentResolverComposite.java
@@ -34,6 +34,7 @@ import org.springframework.web.context.request.NativeWebRequest;
* Previously resolved method parameters are cached for faster lookups.
*
* @author Rossen Stoyanchev
+ * @author Juergen Hoeller
* @since 3.1
*/
public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
@@ -57,6 +58,19 @@ public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgu
/**
* Add the given {@link HandlerMethodArgumentResolver}s.
+ * @since 4.3
+ */
+ public HandlerMethodArgumentResolverComposite addResolvers(HandlerMethodArgumentResolver... resolvers) {
+ if (resolvers != null) {
+ for (HandlerMethodArgumentResolver resolver : resolvers) {
+ this.argumentResolvers.add(resolver);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Add the given {@link HandlerMethodArgumentResolver}s.
*/
public HandlerMethodArgumentResolverComposite addResolvers(List<? extends HandlerMethodArgumentResolver> resolvers) {
if (resolvers != null) {
@@ -74,6 +88,14 @@ public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgu
return Collections.unmodifiableList(this.argumentResolvers);
}
+ /**
+ * Clear the list of configured resolvers.
+ * @since 4.3
+ */
+ public void clear() {
+ this.argumentResolvers.clear();
+ }
+
/**
* Whether the given {@linkplain MethodParameter method parameter} is supported by any registered
diff --git a/spring-web/src/main/java/org/springframework/web/method/support/ModelAndViewContainer.java b/spring-web/src/main/java/org/springframework/web/method/support/ModelAndViewContainer.java
index 536d53b0..ec976824 100644
--- a/spring-web/src/main/java/org/springframework/web/method/support/ModelAndViewContainer.java
+++ b/spring-web/src/main/java/org/springframework/web/method/support/ModelAndViewContainer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author 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,8 +16,11 @@
package org.springframework.web.method.support;
+import java.util.HashSet;
import java.util.Map;
+import java.util.Set;
+import org.springframework.http.HttpStatus;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.validation.support.BindingAwareModelMap;
@@ -54,6 +57,11 @@ public class ModelAndViewContainer {
private boolean redirectModelScenario = false;
+ /* Names of attributes with binding disabled */
+ private final Set<String> bindingDisabledAttributes = new HashSet<String>(4);
+
+ private HttpStatus status;
+
private final SessionStatus sessionStatus = new SimpleSessionStatus();
private boolean requestHandled = false;
@@ -134,6 +142,24 @@ public class ModelAndViewContainer {
}
/**
+ * Register an attribute for which data binding should not occur, for example
+ * corresponding to an {@code @ModelAttribute(binding=false)} declaration.
+ * @param attributeName the name of the attribute
+ * @since 4.3
+ */
+ public void setBindingDisabled(String attributeName) {
+ this.bindingDisabledAttributes.add(attributeName);
+ }
+
+ /**
+ * Whether binding is disabled for the given model attribute.
+ * @since 4.3
+ */
+ public boolean isBindingDisabled(String name) {
+ return this.bindingDisabledAttributes.contains(name);
+ }
+
+ /**
* Whether to use the default model or the redirect model.
*/
private boolean useDefaultModel() {
@@ -156,7 +182,7 @@ public class ModelAndViewContainer {
/**
* Provide a separate model instance to use in a redirect scenario.
- * The provided additional model however is not used used unless
+ * The provided additional model however is not used unless
* {@link #setRedirectModelScenario(boolean)} gets set to {@code true} to signal
* a redirect scenario.
*/
@@ -181,6 +207,23 @@ public class ModelAndViewContainer {
}
/**
+ * Provide a HTTP status that will be passed on to with the
+ * {@code ModelAndView} used for view rendering purposes.
+ * @since 4.3
+ */
+ public void setStatus(HttpStatus status) {
+ this.status = status;
+ }
+
+ /**
+ * Return the configured HTTP status, if any.
+ * @since 4.3
+ */
+ public HttpStatus getStatus() {
+ return this.status;
+ }
+
+ /**
* Whether the request has been handled fully within the handler, e.g.
* {@code @ResponseBody} method, and therefore view resolution is not
* necessary. This flag can also be set when controller methods declare an
diff --git a/spring-web/src/main/java/org/springframework/web/multipart/commons/CommonsFileUploadSupport.java b/spring-web/src/main/java/org/springframework/web/multipart/commons/CommonsFileUploadSupport.java
index 622b1844..727bc92c 100644
--- a/spring-web/src/main/java/org/springframework/web/multipart/commons/CommonsFileUploadSupport.java
+++ b/spring-web/src/main/java/org/springframework/web/multipart/commons/CommonsFileUploadSupport.java
@@ -304,7 +304,7 @@ public abstract class CommonsFileUploadSupport {
return defaultEncoding;
}
MediaType contentType = MediaType.parseMediaType(contentTypeHeader);
- Charset charset = contentType.getCharSet();
+ Charset charset = contentType.getCharset();
return (charset != null ? charset.name() : defaultEncoding);
}
diff --git a/spring-web/src/main/java/org/springframework/web/multipart/support/MultipartFilter.java b/spring-web/src/main/java/org/springframework/web/multipart/support/MultipartFilter.java
index b9ac26ed..577b4e3b 100644
--- a/spring-web/src/main/java/org/springframework/web/multipart/support/MultipartFilter.java
+++ b/spring-web/src/main/java/org/springframework/web/multipart/support/MultipartFilter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +39,10 @@ import org.springframework.web.multipart.MultipartResolver;
* <p>If no MultipartResolver bean is found, this filter falls back to a default
* MultipartResolver: {@link StandardServletMultipartResolver} for Servlet 3.0,
* based on a multipart-config section in {@code web.xml}.
+ * Note however that at present the Servlet specification only defines how to
+ * enable multipart configuration on a Servlet and as a result multipart request
+ * processing is likely not possible in a Filter unless the Servlet container
+ * provides a workaround such as Tomcat's "allowCasualMultipartParsing" property.
*
* <p>MultipartResolver lookup is customizable: Override this filter's
* {@code lookupMultipartResolver} method to use a custom MultipartResolver
diff --git a/spring-web/src/main/java/org/springframework/web/multipart/support/MultipartResolutionDelegate.java b/spring-web/src/main/java/org/springframework/web/multipart/support/MultipartResolutionDelegate.java
new file mode 100644
index 00000000..42248276
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/multipart/support/MultipartResolutionDelegate.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.multipart.support;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.Part;
+
+import org.springframework.core.GenericCollectionTypeResolver;
+import org.springframework.core.MethodParameter;
+import org.springframework.util.ClassUtils;
+import org.springframework.web.multipart.MultipartException;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.multipart.MultipartHttpServletRequest;
+import org.springframework.web.util.WebUtils;
+
+/**
+ * A common delegate for {@code HandlerMethodArgumentResolver} implementations
+ * which need to resolve {@link MultipartFile} and {@link Part} arguments.
+ *
+ * @author Juergen Hoeller
+ * @since 4.3
+ */
+public abstract class MultipartResolutionDelegate {
+
+ public static final Object UNRESOLVABLE = new Object();
+
+
+ private static Class<?> servletPartClass = null;
+
+ static {
+ try {
+ servletPartClass = ClassUtils.forName("javax.servlet.http.Part",
+ MultipartResolutionDelegate.class.getClassLoader());
+ }
+ catch (ClassNotFoundException ex) {
+ // Servlet 3.0 javax.servlet.http.Part type not available -
+ // Part references simply not supported then.
+ }
+ }
+
+
+ public static boolean isMultipartRequest(HttpServletRequest request) {
+ return (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null ||
+ isMultipartContent(request));
+ }
+
+ private static boolean isMultipartContent(HttpServletRequest request) {
+ String contentType = request.getContentType();
+ return (contentType != null && contentType.toLowerCase().startsWith("multipart/"));
+ }
+
+ static MultipartHttpServletRequest asMultipartHttpServletRequest(HttpServletRequest request) {
+ MultipartHttpServletRequest unwrapped = WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);
+ if (unwrapped != null) {
+ return unwrapped;
+ }
+ return adaptToMultipartHttpServletRequest(request);
+ }
+
+ private static MultipartHttpServletRequest adaptToMultipartHttpServletRequest(HttpServletRequest request) {
+ if (servletPartClass != null) {
+ // Servlet 3.0 available ..
+ return new StandardMultipartHttpServletRequest(request);
+ }
+ throw new MultipartException("Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
+ }
+
+
+ public static boolean isMultipartArgument(MethodParameter parameter) {
+ Class<?> paramType = parameter.getNestedParameterType();
+ return (MultipartFile.class == paramType ||
+ isMultipartFileCollection(parameter) || isMultipartFileArray(parameter) ||
+ (servletPartClass != null && (servletPartClass == paramType ||
+ isPartCollection(parameter) || isPartArray(parameter))));
+ }
+
+ public static Object resolveMultipartArgument(String name, MethodParameter parameter, HttpServletRequest request)
+ throws Exception {
+
+ MultipartHttpServletRequest multipartRequest =
+ WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);
+ boolean isMultipart = (multipartRequest != null || isMultipartContent(request));
+
+ if (MultipartFile.class == parameter.getNestedParameterType()) {
+ if (multipartRequest == null && isMultipart) {
+ multipartRequest = adaptToMultipartHttpServletRequest(request);
+ }
+ return (multipartRequest != null ? multipartRequest.getFile(name) : null);
+ }
+ else if (isMultipartFileCollection(parameter)) {
+ if (multipartRequest == null && isMultipart) {
+ multipartRequest = adaptToMultipartHttpServletRequest(request);
+ }
+ return (multipartRequest != null ? multipartRequest.getFiles(name) : null);
+ }
+ else if (isMultipartFileArray(parameter)) {
+ if (multipartRequest == null && isMultipart) {
+ multipartRequest = adaptToMultipartHttpServletRequest(request);
+ }
+ if (multipartRequest != null) {
+ List<MultipartFile> multipartFiles = multipartRequest.getFiles(name);
+ return multipartFiles.toArray(new MultipartFile[multipartFiles.size()]);
+ }
+ else {
+ return null;
+ }
+ }
+ else if (servletPartClass != null) {
+ if (servletPartClass == parameter.getNestedParameterType()) {
+ return (isMultipart ? RequestPartResolver.resolvePart(request, name) : null);
+ }
+ else if (isPartCollection(parameter)) {
+ return (isMultipart ? RequestPartResolver.resolvePartList(request, name) : null);
+ }
+ else if (isPartArray(parameter)) {
+ return (isMultipart ? RequestPartResolver.resolvePartArray(request, name) : null);
+ }
+ }
+ return UNRESOLVABLE;
+ }
+
+ private static boolean isMultipartFileCollection(MethodParameter methodParam) {
+ return (MultipartFile.class == getCollectionParameterType(methodParam));
+ }
+
+ private static boolean isMultipartFileArray(MethodParameter methodParam) {
+ return (MultipartFile.class == methodParam.getNestedParameterType().getComponentType());
+ }
+
+ private static boolean isPartCollection(MethodParameter methodParam) {
+ return (servletPartClass == getCollectionParameterType(methodParam));
+ }
+
+ private static boolean isPartArray(MethodParameter methodParam) {
+ return (servletPartClass == methodParam.getNestedParameterType().getComponentType());
+ }
+
+ private static Class<?> getCollectionParameterType(MethodParameter methodParam) {
+ Class<?> paramType = methodParam.getNestedParameterType();
+ if (Collection.class == paramType || List.class.isAssignableFrom(paramType)){
+ Class<?> valueType = GenericCollectionTypeResolver.getCollectionParameterType(methodParam);
+ if (valueType != null) {
+ return valueType;
+ }
+ }
+ return null;
+ }
+
+
+ /**
+ * Inner class to avoid hard-coded dependency on Servlet 3.0 Part type...
+ */
+ private static class RequestPartResolver {
+
+ public static Object resolvePart(HttpServletRequest servletRequest, String name) throws Exception {
+ return servletRequest.getPart(name);
+ }
+
+ public static Object resolvePartList(HttpServletRequest servletRequest, String name) throws Exception {
+ Collection<Part> parts = servletRequest.getParts();
+ List<Part> result = new ArrayList<Part>(parts.size());
+ for (Part part : parts) {
+ if (part.getName().equals(name)) {
+ result.add(part);
+ }
+ }
+ return result;
+ }
+
+ public static Object resolvePartArray(HttpServletRequest servletRequest, String name) throws Exception {
+ Collection<Part> parts = servletRequest.getParts();
+ List<Part> result = new ArrayList<Part>(parts.size());
+ for (Part part : parts) {
+ if (part.getName().equals(name)) {
+ result.add(part);
+ }
+ }
+ return result.toArray(new Part[result.size()]);
+ }
+ }
+
+}
diff --git a/spring-web/src/main/java/org/springframework/web/multipart/support/RequestPartServletServerHttpRequest.java b/spring-web/src/main/java/org/springframework/web/multipart/support/RequestPartServletServerHttpRequest.java
index 78eab918..ae817e4b 100644
--- a/spring-web/src/main/java/org/springframework/web/multipart/support/RequestPartServletServerHttpRequest.java
+++ b/spring-web/src/main/java/org/springframework/web/multipart/support/RequestPartServletServerHttpRequest.java
@@ -26,12 +26,10 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServletServerHttpRequest;
-import org.springframework.util.ClassUtils;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.MultipartResolver;
-import org.springframework.web.util.WebUtils;
/**
* {@link ServerHttpRequest} implementation that accesses one part of a multipart
@@ -57,40 +55,20 @@ public class RequestPartServletServerHttpRequest extends ServletServerHttpReques
* @param request the current servlet request
* @param partName the name of the part to adapt to the {@link ServerHttpRequest} contract
* @throws MissingServletRequestPartException if the request part cannot be found
- * @throws IllegalArgumentException if MultipartHttpServletRequest cannot be initialized
+ * @throws MultipartException if MultipartHttpServletRequest cannot be initialized
*/
public RequestPartServletServerHttpRequest(HttpServletRequest request, String partName)
throws MissingServletRequestPartException {
super(request);
- this.multipartRequest = asMultipartRequest(request);
+ this.multipartRequest = MultipartResolutionDelegate.asMultipartHttpServletRequest(request);
this.partName = partName;
this.headers = this.multipartRequest.getMultipartHeaders(this.partName);
if (this.headers == null) {
- if (request instanceof MultipartHttpServletRequest) {
- throw new MissingServletRequestPartException(partName);
- }
- else {
- throw new IllegalArgumentException(
- "Failed to obtain request part: " + partName + ". " +
- "The part is missing or multipart processing is not configured. " +
- "Check for a MultipartResolver bean or if Servlet 3.0 multipart processing is enabled.");
- }
- }
- }
-
- private static MultipartHttpServletRequest asMultipartRequest(HttpServletRequest request) {
- MultipartHttpServletRequest unwrapped = WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);
- if (unwrapped != null) {
- return unwrapped;
- }
- else if (ClassUtils.hasMethod(HttpServletRequest.class, "getParts")) {
- // Servlet 3.0 available ..
- return new StandardMultipartHttpServletRequest(request);
+ throw new MissingServletRequestPartException(partName);
}
- throw new IllegalArgumentException("Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
}
@@ -125,7 +103,7 @@ public class RequestPartServletServerHttpRequest extends ServletServerHttpReques
private String determineEncoding() {
MediaType contentType = getHeaders().getContentType();
if (contentType != null) {
- Charset charset = contentType.getCharSet();
+ Charset charset = contentType.getCharset();
if (charset != null) {
return charset.name();
}
diff --git a/spring-web/src/main/java/org/springframework/web/util/AbstractUriTemplateHandler.java b/spring-web/src/main/java/org/springframework/web/util/AbstractUriTemplateHandler.java
new file mode 100644
index 00000000..1323bc94
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/util/AbstractUriTemplateHandler.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.util;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.util.Assert;
+
+/**
+ * Abstract base class for {@link UriTemplateHandler} implementations.
+ *
+ * <p>Support {@link #setBaseUrl} and {@link #setDefaultUriVariables} properties
+ * that should be relevant regardless of the URI template expand and encode
+ * mechanism used in sub-classes.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3
+ */
+public abstract class AbstractUriTemplateHandler implements UriTemplateHandler {
+
+ private String baseUrl;
+
+ private final Map<String, Object> defaultUriVariables = new HashMap<String, Object>();
+
+
+ /**
+ * Configure a base URL to prepend URI templates with. The base URL must
+ * have a scheme and host but may optionally contain a port and a path.
+ * The base URL must be fully expanded and encoded which can be done via
+ * {@link UriComponentsBuilder}.
+ * @param baseUrl the base URL.
+ */
+ public void setBaseUrl(String baseUrl) {
+ if (baseUrl != null) {
+ UriComponents uriComponents = UriComponentsBuilder.fromUriString(baseUrl).build();
+ Assert.hasText(uriComponents.getScheme(), "'baseUrl' must have a scheme");
+ Assert.hasText(uriComponents.getHost(), "'baseUrl' must have a host");
+ Assert.isNull(uriComponents.getQuery(), "'baseUrl' cannot have a query");
+ Assert.isNull(uriComponents.getFragment(), "'baseUrl' cannot have a fragment");
+ }
+ this.baseUrl = baseUrl;
+ }
+
+ /**
+ * Return the configured base URL.
+ */
+ public String getBaseUrl() {
+ return this.baseUrl;
+ }
+
+ /**
+ * Configure default URI variable values to use with every expanded URI
+ * template. These default values apply only when expanding with a Map, and
+ * not with an array, where the Map supplied to {@link #expand(String, Map)}
+ * can override the default values.
+ * @param defaultUriVariables the default URI variable values
+ * @since 4.3
+ */
+ public void setDefaultUriVariables(Map<String, ?> defaultUriVariables) {
+ this.defaultUriVariables.clear();
+ if (defaultUriVariables != null) {
+ this.defaultUriVariables.putAll(defaultUriVariables);
+ }
+ }
+
+ /**
+ * Return a read-only copy of the configured default URI variables.
+ */
+ public Map<String, ?> getDefaultUriVariables() {
+ return Collections.unmodifiableMap(this.defaultUriVariables);
+ }
+
+
+ @Override
+ public URI expand(String uriTemplate, Map<String, ?> uriVariables) {
+ if (!getDefaultUriVariables().isEmpty()) {
+ Map<String, Object> map = new HashMap<String, Object>();
+ map.putAll(getDefaultUriVariables());
+ map.putAll(uriVariables);
+ uriVariables = map;
+ }
+ URI url = expandInternal(uriTemplate, uriVariables);
+ return insertBaseUrl(url);
+ }
+
+ @Override
+ public URI expand(String uriTemplate, Object... uriVariables) {
+ URI url = expandInternal(uriTemplate, uriVariables);
+ return insertBaseUrl(url);
+ }
+
+
+ /**
+ * Actually expand and encode the URI template.
+ */
+ protected abstract URI expandInternal(String uriTemplate, Map<String, ?> uriVariables);
+
+ /**
+ * Actually expand and encode the URI template.
+ */
+ protected abstract URI expandInternal(String uriTemplate, Object... uriVariables);
+
+
+ /**
+ * Insert a base URL (if configured) unless the given URL has a host already.
+ */
+ private URI insertBaseUrl(URI url) {
+ try {
+ String baseUrl = getBaseUrl();
+ if (baseUrl != null && url.getHost() == null) {
+ url = new URI(baseUrl + url.toString());
+ }
+ return url;
+ }
+ catch (URISyntaxException ex) {
+ throw new IllegalArgumentException("Invalid URL after inserting base URL: " + url, ex);
+ }
+ }
+
+}
diff --git a/spring-web/src/main/java/org/springframework/web/util/ContentCachingResponseWrapper.java b/spring-web/src/main/java/org/springframework/web/util/ContentCachingResponseWrapper.java
index 7bc23949..214b97ad 100644
--- a/spring-web/src/main/java/org/springframework/web/util/ContentCachingResponseWrapper.java
+++ b/spring-web/src/main/java/org/springframework/web/util/ContentCachingResponseWrapper.java
@@ -138,7 +138,7 @@ public class ContentCachingResponseWrapper extends HttpServletResponseWrapper {
// Overrides Servlet 3.1 setContentLengthLong(long) at runtime
public void setContentLengthLong(long len) {
if (len > Integer.MAX_VALUE) {
- throw new IllegalArgumentException("Content-Length exceeds ShallowEtagHeaderFilter's maximum (" +
+ throw new IllegalArgumentException("Content-Length exceeds ContentCachingResponseWrapper's maximum (" +
Integer.MAX_VALUE + "): " + len);
}
int lenInt = (int) len;
diff --git a/spring-web/src/main/java/org/springframework/web/util/DefaultUriTemplateHandler.java b/spring-web/src/main/java/org/springframework/web/util/DefaultUriTemplateHandler.java
index e4f79832..1116df9e 100644
--- a/spring-web/src/main/java/org/springframework/web/util/DefaultUriTemplateHandler.java
+++ b/spring-web/src/main/java/org/springframework/web/util/DefaultUriTemplateHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,62 +16,39 @@
package org.springframework.web.util;
+import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import org.springframework.util.Assert;
-
/**
- * Default implementation of {@link UriTemplateHandler} that relies on
- * {@link UriComponentsBuilder} internally.
+ * Default implementation of {@link UriTemplateHandler} based on the use of
+ * {@link UriComponentsBuilder} for expanding and encoding variables.
+ *
+ * <p>There are also several properties to customize how URI template handling
+ * is performed, including a {@link #setBaseUrl baseUrl} to be used as a prefix
+ * for all URI templates and a couple of encoding related options &mdash;
+ * {@link #setParsePath parsePath} and {@link #setStrictEncoding strictEncoding}
+ * respectively.
*
* @author Rossen Stoyanchev
* @since 4.2
*/
-public class DefaultUriTemplateHandler implements UriTemplateHandler {
-
- private String baseUrl;
+public class DefaultUriTemplateHandler extends AbstractUriTemplateHandler {
private boolean parsePath;
+ private boolean strictEncoding;
- /**
- * Configure a base URL to prepend URI templates with. The base URL should
- * have a scheme and host but may also contain a port and a partial path.
- * Individual URI templates then may provide the remaining part of the URL
- * including additional path, query and fragment.
- * <p><strong>Note: </strong>Individual URI templates are expanded and
- * encoded before being appended to the base URL. Therefore the base URL is
- * expected to be fully expanded and encoded, which can be done with the help
- * of {@link UriComponentsBuilder}.
- * @param baseUrl the base URL.
- */
- public void setBaseUrl(String baseUrl) {
- if (baseUrl != null) {
- UriComponents uriComponents = UriComponentsBuilder.fromUriString(baseUrl).build();
- Assert.hasText(uriComponents.getScheme(), "'baseUrl' must have a scheme");
- Assert.hasText(uriComponents.getHost(), "'baseUrl' must have a host");
- Assert.isNull(uriComponents.getQuery(), "'baseUrl' cannot have a query");
- Assert.isNull(uriComponents.getFragment(), "'baseUrl' cannot have a fragment");
- }
- this.baseUrl = baseUrl;
- }
-
- /**
- * Return the configured base URL.
- */
- public String getBaseUrl() {
- return this.baseUrl;
- }
/**
* Whether to parse the path of a URI template string into path segments.
- * <p>If set to {@code true} the path of parsed URI templates is decomposed
- * into path segments so that URI variables expanded into the path are
- * treated according to path segment encoding rules. In effect that means the
- * "/" character is percent encoded.
+ * <p>If set to {@code true} the URI template path is immediately decomposed
+ * into path segments any URI variables expanded into it are then subject to
+ * path segment encoding rules. In effect URI variables in the path have any
+ * "/" characters percent encoded.
* <p>By default this is set to {@code false} in which case the path is kept
* as a full path and expanded URI variables will preserve "/" characters.
* @param parsePath whether to parse the path into path segments
@@ -87,24 +64,55 @@ public class DefaultUriTemplateHandler implements UriTemplateHandler {
return this.parsePath;
}
+ /**
+ * Whether to encode characters outside the unreserved set as defined in
+ * <a href="https://tools.ietf.org/html/rfc3986#section-2">RFC 3986 Section 2</a>.
+ * This ensures a URI variable value will not contain any characters with a
+ * reserved purpose.
+ * <p>By default this is set to {@code false} in which case only characters
+ * illegal for the given URI component are encoded. For example when expanding
+ * a URI variable into a path segment the "/" character is illegal and
+ * encoded. The ";" character however is legal and not encoded even though
+ * it has a reserved purpose.
+ * <p><strong>Note:</strong> this property supersedes the need to also set
+ * the {@link #setParsePath parsePath} property.
+ * @param strictEncoding whether to perform strict encoding
+ * @since 4.3
+ */
+ public void setStrictEncoding(boolean strictEncoding) {
+ this.strictEncoding = strictEncoding;
+ }
+
+ /**
+ * Whether to strictly encode any character outside the unreserved set.
+ */
+ public boolean isStrictEncoding() {
+ return this.strictEncoding;
+ }
+
@Override
- public URI expand(String uriTemplate, Map<String, ?> uriVariables) {
+ protected URI expandInternal(String uriTemplate, Map<String, ?> uriVariables) {
UriComponentsBuilder uriComponentsBuilder = initUriComponentsBuilder(uriTemplate);
- UriComponents uriComponents = uriComponentsBuilder.build().expand(uriVariables).encode();
- return insertBaseUrl(uriComponents);
+ UriComponents uriComponents = expandAndEncode(uriComponentsBuilder, uriVariables);
+ return createUri(uriComponents);
}
@Override
- public URI expand(String uriTemplate, Object... uriVariableValues) {
+ protected URI expandInternal(String uriTemplate, Object... uriVariables) {
UriComponentsBuilder uriComponentsBuilder = initUriComponentsBuilder(uriTemplate);
- UriComponents uriComponents = uriComponentsBuilder.build().expand(uriVariableValues).encode();
- return insertBaseUrl(uriComponents);
+ UriComponents uriComponents = expandAndEncode(uriComponentsBuilder, uriVariables);
+ return createUri(uriComponents);
}
+ /**
+ * Create a {@code UriComponentsBuilder} from the URI template string.
+ * This implementation also breaks up the path into path segments depending
+ * on whether {@link #setParsePath parsePath} is enabled.
+ */
protected UriComponentsBuilder initUriComponentsBuilder(String uriTemplate) {
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(uriTemplate);
- if (shouldParsePath()) {
+ if (shouldParsePath() && !isStrictEncoding()) {
List<String> pathSegments = builder.build().getPathSegments();
builder.replacePath(null);
for (String pathSegment : pathSegments) {
@@ -114,16 +122,50 @@ public class DefaultUriTemplateHandler implements UriTemplateHandler {
return builder;
}
- protected URI insertBaseUrl(UriComponents uriComponents) {
- if (getBaseUrl() == null || uriComponents.getHost() != null) {
- return uriComponents.toUri();
+ protected UriComponents expandAndEncode(UriComponentsBuilder builder, Map<String, ?> uriVariables) {
+ if (!isStrictEncoding()) {
+ return builder.buildAndExpand(uriVariables).encode();
+ }
+ else {
+ Map<String, Object> encodedUriVars = new HashMap<String, Object>(uriVariables.size());
+ for (Map.Entry<String, ?> entry : uriVariables.entrySet()) {
+ encodedUriVars.put(entry.getKey(), applyStrictEncoding(entry.getValue()));
+ }
+ return builder.buildAndExpand(encodedUriVars);
}
- String url = getBaseUrl() + uriComponents.toUriString();
+ }
+
+ protected UriComponents expandAndEncode(UriComponentsBuilder builder, Object[] uriVariables) {
+ if (!isStrictEncoding()) {
+ return builder.buildAndExpand(uriVariables).encode();
+ }
+ else {
+ Object[] encodedUriVars = new Object[uriVariables.length];
+ for (int i = 0; i < uriVariables.length; i++) {
+ encodedUriVars[i] = applyStrictEncoding(uriVariables[i]);
+ }
+ return builder.buildAndExpand(encodedUriVars);
+ }
+ }
+
+ private String applyStrictEncoding(Object value) {
+ String stringValue = (value != null ? value.toString() : "");
+ try {
+ return UriUtils.encode(stringValue, "UTF-8");
+ }
+ catch (UnsupportedEncodingException ex) {
+ // Should never happen
+ throw new IllegalStateException("Failed to encode URI variable", ex);
+ }
+ }
+
+ private URI createUri(UriComponents uriComponents) {
try {
- return new URI(url);
+ // Avoid further encoding (in the case of strictEncoding=true)
+ return new URI(uriComponents.toUriString());
}
catch (URISyntaxException ex) {
- throw new IllegalArgumentException("Invalid URL after inserting base URL: " + url, ex);
+ throw new IllegalStateException("Could not create URI object: " + ex.getMessage(), ex);
}
}
diff --git a/spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java b/spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java
index e25466ec..2aceca89 100644
--- a/spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java
+++ b/spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java
@@ -61,6 +61,7 @@ final class HierarchicalUriComponents extends UriComponents {
private final boolean encoded;
+
/**
* Package-private constructor. All arguments are optional, and can be {@code null}.
* @param scheme the scheme
@@ -336,6 +337,7 @@ final class HierarchicalUriComponents extends UriComponents {
private MultiValueMap<String, String> expandQueryParams(UriTemplateVariables variables) {
int size = this.queryParams.size();
MultiValueMap<String, String> result = new LinkedMultiValueMap<String, String>(size);
+ variables = new QueryUriTemplateVariables(variables);
for (Map.Entry<String, List<String>> entry : this.queryParams.entrySet()) {
String name = expandUriComponent(entry.getKey(), variables);
List<String> values = new ArrayList<String>(entry.getValue().size());
@@ -882,4 +884,23 @@ final class HierarchicalUriComponents extends UriComponents {
}
};
+
+ private static class QueryUriTemplateVariables implements UriTemplateVariables {
+
+ private final UriTemplateVariables delegate;
+
+ public QueryUriTemplateVariables(UriTemplateVariables delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public Object getValue(String name) {
+ Object value = this.delegate.getValue(name);
+ if (ObjectUtils.isArray(value)) {
+ value = StringUtils.arrayToCommaDelimitedString(ObjectUtils.toObjectArray(value));
+ }
+ return value;
+ }
+ }
+
}
diff --git a/spring-web/src/main/java/org/springframework/web/util/HtmlUtils.java b/spring-web/src/main/java/org/springframework/web/util/HtmlUtils.java
index cb43aa9e..3546efd1 100644
--- a/spring-web/src/main/java/org/springframework/web/util/HtmlUtils.java
+++ b/spring-web/src/main/java/org/springframework/web/util/HtmlUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,7 +36,6 @@ import org.springframework.util.Assert;
* @author Martin Kersten
* @author Craig Andrews
* @since 01.03.2003
- * @see org.apache.commons.lang.StringEscapeUtils
*/
public abstract class HtmlUtils {
@@ -75,7 +74,7 @@ public abstract class HtmlUtils {
* http://www.w3.org/TR/html4/sgml/entities.html
* </a>
* @param input the (unescaped) input string
- * @param encoding The name of a supported {@link java.nio.charset.Charset charset}
+ * @param encoding the name of a supported {@link java.nio.charset.Charset charset}
* @return the escaped string
* @since 4.1.2
*/
@@ -126,7 +125,7 @@ public abstract class HtmlUtils {
* http://www.w3.org/TR/html4/sgml/entities.html
* </a>
* @param input the (unescaped) input string
- * @param encoding The name of a supported {@link java.nio.charset.Charset charset}
+ * @param encoding the name of a supported {@link java.nio.charset.Charset charset}
* @return the escaped string
* @since 4.1.2
*/
@@ -178,7 +177,7 @@ public abstract class HtmlUtils {
* http://www.w3.org/TR/html4/sgml/entities.html
* </a>
* @param input the (unescaped) input string
- * @param encoding The name of a supported {@link java.nio.charset.Charset charset}
+ * @param encoding the name of a supported {@link java.nio.charset.Charset charset}
* @return the escaped string
* @since 4.1.2
*/
diff --git a/spring-web/src/main/java/org/springframework/web/util/UriTemplate.java b/spring-web/src/main/java/org/springframework/web/util/UriTemplate.java
index 43d60943..659030af 100644
--- a/spring-web/src/main/java/org/springframework/web/util/UriTemplate.java
+++ b/spring-web/src/main/java/org/springframework/web/util/UriTemplate.java
@@ -105,7 +105,7 @@ public class UriTemplate implements Serializable {
* <p>Example:
* <pre class="code">
* UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}");
- * System.out.println(template.expand("Rest & Relax", "42));
+ * System.out.println(template.expand("Rest & Relax", 42));
* </pre>
* will print: <blockquote>{@code http://example.com/hotels/Rest%20%26%20Relax/bookings/42}</blockquote>
* @param uriVariableValues the array of URI variables
diff --git a/spring-web/src/main/java/org/springframework/web/util/UriTemplateHandler.java b/spring-web/src/main/java/org/springframework/web/util/UriTemplateHandler.java
index ac04e31e..c03eb191 100644
--- a/spring-web/src/main/java/org/springframework/web/util/UriTemplateHandler.java
+++ b/spring-web/src/main/java/org/springframework/web/util/UriTemplateHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,15 +20,23 @@ import java.net.URI;
import java.util.Map;
/**
- * A strategy for expanding a URI template with URI variables into a {@link URI}.
+ * Strategy for expanding a URI template with full control over the URI template
+ * syntax and the encoding of variables. Also a convenient central point for
+ * pre-processing all URI templates for example to insert a common base path.
+ *
+ * <p>Supported as a property on the {@code RestTemplate} as well as the
+ * {@code AsyncRestTemplate}. The {@link DefaultUriTemplateHandler} is built
+ * on Spring's URI template support via {@link UriComponentsBuilder}. An
+ * alternative implementation may be used to plug external URI template libraries.
*
* @author Rossen Stoyanchev
* @since 4.2
+ * @see org.springframework.web.client.RestTemplate#setUriTemplateHandler
*/
public interface UriTemplateHandler {
/**
- * Expand the give URI template with a map of URI variables.
+ * Expand the given URI template from a map of URI variables.
* @param uriTemplate the URI template string
* @param uriVariables the URI variables
* @return the resulting URI
@@ -36,11 +44,11 @@ public interface UriTemplateHandler {
URI expand(String uriTemplate, Map<String, ?> uriVariables);
/**
- * Expand the give URI template with an array of URI variable values.
+ * Expand the given URI template from an array of URI variables.
* @param uriTemplate the URI template string
- * @param uriVariableValues the URI variable values
+ * @param uriVariables the URI variable values
* @return the resulting URI
*/
- URI expand(String uriTemplate, Object... uriVariableValues);
+ URI expand(String uriTemplate, Object... uriVariables);
}
diff --git a/spring-web/src/main/java/org/springframework/web/util/UriUtils.java b/spring-web/src/main/java/org/springframework/web/util/UriUtils.java
index c6809297..6b2f3e50 100644
--- a/spring-web/src/main/java/org/springframework/web/util/UriUtils.java
+++ b/spring-web/src/main/java/org/springframework/web/util/UriUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +34,7 @@ import org.springframework.util.Assert;
* </ul>
*
* @author Arjen Poutsma
+ * @author Juergen Hoeller
* @since 3.0
* @see <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>
*/
@@ -213,4 +214,28 @@ public abstract class UriUtils {
return (changed ? new String(bos.toByteArray(), encoding) : source);
}
+ /**
+ * Extract the file extension from the given URI path.
+ * @param path the URI path (e.g. "/products/index.html")
+ * @return the extracted file extension (e.g. "html")
+ * @since 4.3.2
+ */
+ public static String extractFileExtension(String path) {
+ int end = path.indexOf('?');
+ if (end == -1) {
+ end = path.indexOf('#');
+ if (end == -1) {
+ end = path.length();
+ }
+ }
+ int begin = path.lastIndexOf('/', end) + 1;
+ int paramIndex = path.indexOf(';', begin);
+ end = (paramIndex != -1 && paramIndex < end ? paramIndex : end);
+ int extIndex = path.lastIndexOf('.', end);
+ if (extIndex != -1 && extIndex > begin) {
+ return path.substring(extIndex + 1, end);
+ }
+ return null;
+ }
+
}
diff --git a/spring-web/src/main/java/org/springframework/web/util/UrlPathHelper.java b/spring-web/src/main/java/org/springframework/web/util/UrlPathHelper.java
index 1a7a4fe3..a1cd6bf6 100644
--- a/spring-web/src/main/java/org/springframework/web/util/UrlPathHelper.java
+++ b/spring-web/src/main/java/org/springframework/web/util/UrlPathHelper.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -179,8 +179,8 @@ public class UrlPathHelper {
String sanitizedPathWithinApp = getSanitizedPath(pathWithinApp);
String path;
- // if the app container sanitized the servletPath, check against the sanitized version
- if (servletPath.indexOf(sanitizedPathWithinApp) != -1) {
+ // If the app container sanitized the servletPath, check against the sanitized version
+ if (servletPath.contains(sanitizedPathWithinApp)) {
path = getRemainingPath(sanitizedPathWithinApp, servletPath, false);
}
else {
@@ -485,8 +485,8 @@ public class UrlPathHelper {
* @return the updated URI string
*/
public String removeSemicolonContent(String requestUri) {
- return this.removeSemicolonContent ?
- removeSemicolonContentInternal(requestUri) : removeJsessionid(requestUri);
+ return (this.removeSemicolonContent ?
+ removeSemicolonContentInternal(requestUri) : removeJsessionid(requestUri));
}
private String removeSemicolonContentInternal(String requestUri) {
diff --git a/spring-web/src/main/java/org/springframework/web/util/WebUtils.java b/spring-web/src/main/java/org/springframework/web/util/WebUtils.java
index 6d8062fb..13195a76 100644
--- a/spring-web/src/main/java/org/springframework/web/util/WebUtils.java
+++ b/spring-web/src/main/java/org/springframework/web/util/WebUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -278,7 +278,6 @@ public abstract class WebUtils {
return realPath;
}
-
/**
* Determine the session id of the given request, if any.
* @param request current HTTP request
@@ -353,7 +352,9 @@ public abstract class WebUtils {
* @param clazz the class to instantiate for a new attribute
* @return the value of the session attribute, newly created if not found
* @throws IllegalArgumentException if the session attribute could not be instantiated
+ * @deprecated as of Spring 4.3.2, in favor of custom code for such purposes
*/
+ @Deprecated
public static Object getOrCreateSessionAttribute(HttpSession session, String name, Class<?> clazz)
throws IllegalArgumentException {
@@ -527,7 +528,9 @@ public abstract class WebUtils {
* and the values as corresponding attribute values. Keys need to be Strings.
* @param request current HTTP request
* @param attributes the attributes Map
+ * @deprecated as of Spring 4.3.2, in favor of custom code for such purposes
*/
+ @Deprecated
public static void exposeRequestAttributes(ServletRequest request, Map<String, ?> attributes) {
Assert.notNull(request, "Request must not be null");
Assert.notNull(attributes, "Attributes Map must not be null");
@@ -689,7 +692,9 @@ public abstract class WebUtils {
* @param currentPage the current page, to be returned as fallback
* if no target page specified
* @return the page specified in the request, or current page if not found
+ * @deprecated as of Spring 4.3.2, in favor of custom code for such purposes
*/
+ @Deprecated
public static int getTargetPage(ServletRequest request, String paramPrefix, int currentPage) {
Enumeration<String> paramNames = request.getParameterNames();
while (paramNames.hasMoreElements()) {
@@ -713,7 +718,9 @@ public abstract class WebUtils {
* Correctly resolves nested paths such as "/products/view.html" as well.
* @param urlPath the request URL path (e.g. "/index.html")
* @return the extracted URI filename (e.g. "index")
+ * @deprecated as of Spring 4.3.2, in favor of custom code for such purposes
*/
+ @Deprecated
public static String extractFilenameFromUrlPath(String urlPath) {
String filename = extractFullFilenameFromUrlPath(urlPath);
int dotIndex = filename.lastIndexOf('.');
@@ -729,7 +736,10 @@ public abstract class WebUtils {
* "/products/view.html" and remove any path and or query parameters.
* @param urlPath the request URL path (e.g. "/products/index.html")
* @return the extracted URI filename (e.g. "index.html")
+ * @deprecated as of Spring 4.3.2, in favor of custom code for such purposes
+ * (or {@link UriUtils#extractFileExtension} for the file extension use case)
*/
+ @Deprecated
public static String extractFullFilenameFromUrlPath(String urlPath) {
int end = urlPath.indexOf('?');
if (end == -1) {
diff --git a/spring-web/src/test/java/org/springframework/http/CacheControlTests.java b/spring-web/src/test/java/org/springframework/http/CacheControlTests.java
index 43de2c0b..2111d2f1 100644
--- a/spring-web/src/test/java/org/springframework/http/CacheControlTests.java
+++ b/spring-web/src/test/java/org/springframework/http/CacheControlTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -63,4 +63,17 @@ public class CacheControlTests {
CacheControl cc = CacheControl.noStore();
assertThat(cc.getHeaderValue(), Matchers.equalTo("no-store"));
}
+
+ @Test
+ public void staleIfError() throws Exception {
+ CacheControl cc = CacheControl.maxAge(1, TimeUnit.HOURS).staleIfError(2, TimeUnit.HOURS);
+ assertThat(cc.getHeaderValue(), Matchers.equalTo("max-age=3600, stale-if-error=7200"));
+ }
+
+ @Test
+ public void staleWhileRevalidate() throws Exception {
+ CacheControl cc = CacheControl.maxAge(1, TimeUnit.HOURS).staleWhileRevalidate(2, TimeUnit.HOURS);
+ assertThat(cc.getHeaderValue(), Matchers.equalTo("max-age=3600, stale-while-revalidate=7200"));
+ }
+
}
diff --git a/spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java b/spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java
index 9fa07041..77c79da4 100644
--- a/spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java
+++ b/spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java
@@ -32,6 +32,7 @@ import java.util.TimeZone;
import org.hamcrest.Matchers;
import org.junit.Test;
+import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
/**
@@ -39,6 +40,7 @@ import static org.junit.Assert.*;
*
* @author Arjen Poutsma
* @author Sebastien Deleuze
+ * @author Brian Clozel
*/
public class HttpHeadersTests {
@@ -46,6 +48,13 @@ public class HttpHeadersTests {
@Test
+ public void getFirst() {
+ headers.add(HttpHeaders.CACHE_CONTROL, "max-age=1000, public");
+ headers.add(HttpHeaders.CACHE_CONTROL, "s-maxage=1000");
+ assertThat(headers.getFirst(HttpHeaders.CACHE_CONTROL), is("max-age=1000, public"));
+ }
+
+ @Test
public void accept() {
MediaType mediaType1 = new MediaType("text", "html");
MediaType mediaType2 = new MediaType("text", "plain");
@@ -58,13 +67,22 @@ public class HttpHeadersTests {
}
@Test // SPR-9655
- public void acceptIPlanet() {
+ public void acceptWithMultipleHeaderValues() {
headers.add("Accept", "text/html");
headers.add("Accept", "text/plain");
List<MediaType> expected = Arrays.asList(new MediaType("text", "html"), new MediaType("text", "plain"));
assertEquals("Invalid Accept header", expected, headers.getAccept());
}
+ @Test // SPR-14506
+ public void acceptWithMultipleCommaSeparatedHeaderValues() {
+ headers.add("Accept", "text/html,text/pdf");
+ headers.add("Accept", "text/plain,text/csv");
+ List<MediaType> expected = Arrays.asList(new MediaType("text", "html"), new MediaType("text", "pdf"),
+ new MediaType("text", "plain"), new MediaType("text", "csv"));
+ assertEquals("Invalid Accept header", expected, headers.getAccept());
+ }
+
@Test
public void acceptCharsets() {
Charset charset1 = Charset.forName("UTF-8");
@@ -133,6 +151,29 @@ public class HttpHeadersTests {
}
@Test
+ public void ifMatch() {
+ String ifMatch = "\"v2.6\"";
+ headers.setIfMatch(ifMatch);
+ assertEquals("Invalid If-Match header", ifMatch, headers.getIfMatch().get(0));
+ assertEquals("Invalid If-Match header", "\"v2.6\"", headers.getFirst("If-Match"));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void ifMatchIllegalHeader() {
+ headers.setIfMatch("Illegal");
+ headers.getIfMatch();
+ }
+
+ @Test
+ public void ifMatchMultipleHeaders() {
+ headers.add(HttpHeaders.IF_MATCH, "\"v2,0\"");
+ headers.add(HttpHeaders.IF_MATCH, "W/\"v2,1\", \"v2,2\"");
+ assertEquals("Invalid If-Match header", "\"v2,0\"", headers.get(HttpHeaders.IF_MATCH).get(0));
+ assertEquals("Invalid If-Match header", "W/\"v2,1\", \"v2,2\"", headers.get(HttpHeaders.IF_MATCH).get(1));
+ assertThat(headers.getIfMatch(), Matchers.contains("\"v2,0\"", "W/\"v2,1\"", "\"v2,2\""));
+ }
+
+ @Test
public void ifNoneMatch() {
String ifNoneMatch = "\"v2.6\"";
headers.setIfNoneMatch(ifNoneMatch);
@@ -141,15 +182,23 @@ public class HttpHeadersTests {
}
@Test
+ public void ifNoneMatchWildCard() {
+ String ifNoneMatch = "*";
+ headers.setIfNoneMatch(ifNoneMatch);
+ assertEquals("Invalid If-None-Match header", ifNoneMatch, headers.getIfNoneMatch().get(0));
+ assertEquals("Invalid If-None-Match header", "*", headers.getFirst("If-None-Match"));
+ }
+
+ @Test
public void ifNoneMatchList() {
String ifNoneMatch1 = "\"v2.6\"";
- String ifNoneMatch2 = "\"v2.7\"";
+ String ifNoneMatch2 = "\"v2.7\", \"v2.8\"";
List<String> ifNoneMatchList = new ArrayList<String>(2);
ifNoneMatchList.add(ifNoneMatch1);
ifNoneMatchList.add(ifNoneMatch2);
headers.setIfNoneMatch(ifNoneMatchList);
- assertEquals("Invalid If-None-Match header", ifNoneMatchList, headers.getIfNoneMatch());
- assertEquals("Invalid If-None-Match header", "\"v2.6\", \"v2.7\"", headers.getFirst("If-None-Match"));
+ assertThat(headers.getIfNoneMatch(), Matchers.contains("\"v2.6\"", "\"v2.7\"", "\"v2.8\""));
+ assertEquals("Invalid If-None-Match header", "\"v2.6\", \"v2.7\", \"v2.8\"", headers.getFirst("If-None-Match"));
}
@Test
@@ -256,6 +305,13 @@ public class HttpHeadersTests {
}
@Test
+ public void cacheControlAllValues() {
+ headers.add(HttpHeaders.CACHE_CONTROL, "max-age=1000, public");
+ headers.add(HttpHeaders.CACHE_CONTROL, "s-maxage=1000");
+ assertThat(headers.getCacheControl(), is("max-age=1000, public, s-maxage=1000"));
+ }
+
+ @Test
public void contentDisposition() {
headers.setContentDispositionFormData("name", null);
assertEquals("Invalid Content-Disposition header", "form-data; name=\"name\"",
@@ -291,6 +347,16 @@ public class HttpHeadersTests {
}
@Test
+ public void accessControlAllowHeadersMultipleValues() {
+ List<String> allowedHeaders = headers.getAccessControlAllowHeaders();
+ assertThat(allowedHeaders, Matchers.emptyCollectionOf(String.class));
+ headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "header1, header2");
+ headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "header3");
+ allowedHeaders = headers.getAccessControlAllowHeaders();
+ assertEquals(Arrays.asList("header1", "header2", "header3"), allowedHeaders);
+ }
+
+ @Test
public void accessControlAllowMethods() {
List<HttpMethod> allowedMethods = headers.getAccessControlAllowMethods();
assertThat(allowedMethods, Matchers.emptyCollectionOf(HttpMethod.class));
diff --git a/spring-web/src/test/java/org/springframework/http/HttpRangeTests.java b/spring-web/src/test/java/org/springframework/http/HttpRangeTests.java
index 85870b3c..5ddec37a 100644
--- a/spring-web/src/test/java/org/springframework/http/HttpRangeTests.java
+++ b/spring-web/src/test/java/org/springframework/http/HttpRangeTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,19 +13,29 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package org.springframework.http;
+import java.io.IOException;
+import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
+import org.springframework.core.io.ByteArrayResource;
+import org.springframework.core.io.InputStreamResource;
+import org.springframework.core.io.support.ResourceRegion;
+
import static org.junit.Assert.*;
+import static org.mockito.BDDMockito.*;
+import static org.mockito.Mockito.mock;
/**
* Unit tests for {@link HttpRange}.
*
* @author Rossen Stoyanchev
+ * @author Brian Clozel
*/
public class HttpRangeTests {
@@ -100,4 +110,39 @@ public class HttpRangeTests {
assertEquals("Invalid Range header", "bytes=0-499, 9500-, -500", HttpRange.toString(ranges));
}
+ @Test
+ public void toResourceRegion() {
+ byte[] bytes = "Spring Framework".getBytes(Charset.forName("UTF-8"));
+ ByteArrayResource resource = new ByteArrayResource(bytes);
+ HttpRange range = HttpRange.createByteRange(0, 5);
+ ResourceRegion region = range.toResourceRegion(resource);
+ assertEquals(resource, region.getResource());
+ assertEquals(0L, region.getPosition());
+ assertEquals(6L, region.getCount());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void toResourceRegionInputStreamResource() {
+ InputStreamResource resource = mock(InputStreamResource.class);
+ HttpRange range = HttpRange.createByteRange(0, 9);
+ range.toResourceRegion(resource);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void toResourceRegionIllegalLength() {
+ ByteArrayResource resource = mock(ByteArrayResource.class);
+ given(resource.contentLength()).willReturn(-1L);
+ HttpRange range = HttpRange.createByteRange(0, 9);
+ range.toResourceRegion(resource);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ @SuppressWarnings("unchecked")
+ public void toResourceRegionExceptionLength() {
+ ByteArrayResource resource = mock(ByteArrayResource.class);
+ given(resource.contentLength()).willThrow(IOException.class);
+ HttpRange range = HttpRange.createByteRange(0, 9);
+ range.toResourceRegion(resource);
+ }
+
}
diff --git a/spring-web/src/test/java/org/springframework/http/HttpStatusTests.java b/spring-web/src/test/java/org/springframework/http/HttpStatusTests.java
index 574f02bd..3624ece7 100644
--- a/spring-web/src/test/java/org/springframework/http/HttpStatusTests.java
+++ b/spring-web/src/test/java/org/springframework/http/HttpStatusTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -85,6 +85,7 @@ public class HttpStatusTests {
statusCodes.put(428, "PRECONDITION_REQUIRED");
statusCodes.put(429, "TOO_MANY_REQUESTS");
statusCodes.put(431, "REQUEST_HEADER_FIELDS_TOO_LARGE");
+ statusCodes.put(451, "UNAVAILABLE_FOR_LEGAL_REASONS");
statusCodes.put(500, "INTERNAL_SERVER_ERROR");
statusCodes.put(501, "NOT_IMPLEMENTED");
diff --git a/spring-web/src/test/java/org/springframework/http/MediaTypeTests.java b/spring-web/src/test/java/org/springframework/http/MediaTypeTests.java
index 99665b93..ca31a975 100644
--- a/spring-web/src/test/java/org/springframework/http/MediaTypeTests.java
+++ b/spring-web/src/test/java/org/springframework/http/MediaTypeTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -138,7 +138,7 @@ public class MediaTypeTests {
assertNotNull("No media types returned", mediaTypes);
assertEquals("Invalid amount of media types", 4, mediaTypes.size());
- mediaTypes = MediaType.parseMediaTypes(null);
+ mediaTypes = MediaType.parseMediaTypes("");
assertNotNull("No media types returned", mediaTypes);
assertEquals("Invalid amount of media types", 0, mediaTypes.size());
}
diff --git a/spring-web/src/test/java/org/springframework/http/RequestEntityTests.java b/spring-web/src/test/java/org/springframework/http/RequestEntityTests.java
index de235366..9b8268d5 100644
--- a/spring-web/src/test/java/org/springframework/http/RequestEntityTests.java
+++ b/spring-web/src/test/java/org/springframework/http/RequestEntityTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,11 +19,14 @@ package org.springframework.http;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
+import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import org.junit.Test;
+import org.springframework.core.ParameterizedTypeReference;
import org.springframework.web.util.UriTemplate;
import static org.junit.Assert.*;
@@ -148,4 +151,14 @@ public class RequestEntityTests {
}
+ @Test // SPR-13154
+ public void types() throws URISyntaxException {
+ URI url = new URI("http://example.com");
+ List<String> body = Arrays.asList("foo", "bar");
+ ParameterizedTypeReference<?> typeReference = new ParameterizedTypeReference<List<String>>() {};
+
+ RequestEntity<?> entity = RequestEntity.post(url).body(body, typeReference.getType());
+ assertEquals(typeReference.getType(), entity.getType());
+ }
+
}
diff --git a/spring-web/src/test/java/org/springframework/http/ResponseEntityTests.java b/spring-web/src/test/java/org/springframework/http/ResponseEntityTests.java
index dca351a4..5bc9d3f7 100644
--- a/spring-web/src/test/java/org/springframework/http/ResponseEntityTests.java
+++ b/spring-web/src/test/java/org/springframework/http/ResponseEntityTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -252,4 +252,22 @@ public class ResponseEntityTests {
assertThat(cacheControlHeader, Matchers.equalTo("no-store"));
}
+ @Test
+ public void statusCodeAsInt() {
+ Integer entity = new Integer(42);
+ ResponseEntity<Integer> responseEntity = ResponseEntity.status(200).body(entity);
+
+ assertEquals(200, responseEntity.getStatusCode().value());
+ assertEquals(entity, responseEntity.getBody());
+ }
+
+ @Test
+ public void customStatusCode() {
+ Integer entity = new Integer(42);
+ ResponseEntity<Integer> responseEntity = ResponseEntity.status(299).body(entity);
+
+ assertEquals(299, responseEntity.getStatusCodeValue());
+ assertEquals(entity, responseEntity.getBody());
+ }
+
}
diff --git a/spring-web/src/test/java/org/springframework/http/client/BufferedSimpleHttpRequestFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/BufferedSimpleHttpRequestFactoryTests.java
index 8b70e32f..1f97af02 100644
--- a/spring-web/src/test/java/org/springframework/http/client/BufferedSimpleHttpRequestFactoryTests.java
+++ b/spring-web/src/test/java/org/springframework/http/client/BufferedSimpleHttpRequestFactoryTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,16 +16,19 @@
package org.springframework.http.client;
-import static org.junit.Assert.assertEquals;
-
+import java.io.ByteArrayInputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.URL;
import org.junit.Test;
+
import org.springframework.http.HttpMethod;
+import static org.junit.Assert.*;
+
public class BufferedSimpleHttpRequestFactoryTests extends AbstractHttpRequestFactoryTestCase {
@Override
@@ -89,5 +92,11 @@ public class BufferedSimpleHttpRequestFactoryTests extends AbstractHttpRequestFa
public boolean usingProxy() {
return false;
}
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return new ByteArrayInputStream(new byte[0]);
+ }
}
+
}
diff --git a/spring-web/src/test/java/org/springframework/http/client/OkHttp3AsyncClientHttpRequestFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/OkHttp3AsyncClientHttpRequestFactoryTests.java
new file mode 100644
index 00000000..12e857e6
--- /dev/null
+++ b/spring-web/src/test/java/org/springframework/http/client/OkHttp3AsyncClientHttpRequestFactoryTests.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.http.client;
+
+import org.junit.Test;
+
+import org.springframework.http.HttpMethod;
+
+/**
+ * @author Roy Clarkson
+ */
+public class OkHttp3AsyncClientHttpRequestFactoryTests extends AbstractAsyncHttpRequestFactoryTestCase {
+
+ @Override
+ protected AsyncClientHttpRequestFactory createRequestFactory() {
+ return new OkHttp3ClientHttpRequestFactory();
+ }
+
+ @Override
+ @Test
+ public void httpMethods() throws Exception {
+ super.httpMethods();
+ assertHttpMethod("patch", HttpMethod.PATCH);
+ }
+
+}
diff --git a/spring-web/src/test/java/org/springframework/http/client/OkHttp3ClientHttpRequestFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/OkHttp3ClientHttpRequestFactoryTests.java
new file mode 100644
index 00000000..dd1874ad
--- /dev/null
+++ b/spring-web/src/test/java/org/springframework/http/client/OkHttp3ClientHttpRequestFactoryTests.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.http.client;
+
+import org.junit.Test;
+
+import org.springframework.http.HttpMethod;
+
+/**
+ * @author Roy Clarkson
+ */
+public class OkHttp3ClientHttpRequestFactoryTests extends AbstractHttpRequestFactoryTestCase {
+
+ @Override
+ protected ClientHttpRequestFactory createRequestFactory() {
+ return new OkHttp3ClientHttpRequestFactory();
+ }
+
+ @Override
+ @Test
+ public void httpMethods() throws Exception {
+ super.httpMethods();
+ assertHttpMethod("patch", HttpMethod.PATCH);
+ }
+
+}
diff --git a/spring-web/src/test/java/org/springframework/http/client/OkHttpAsyncClientHttpRequestFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/OkHttpAsyncClientHttpRequestFactoryTests.java
index f7c367e6..f508027d 100644
--- a/spring-web/src/test/java/org/springframework/http/client/OkHttpAsyncClientHttpRequestFactoryTests.java
+++ b/spring-web/src/test/java/org/springframework/http/client/OkHttpAsyncClientHttpRequestFactoryTests.java
@@ -24,7 +24,7 @@ import org.springframework.http.HttpMethod;
* @author Luciano Leggieri
*/
public class OkHttpAsyncClientHttpRequestFactoryTests extends AbstractAsyncHttpRequestFactoryTestCase {
-
+
@Override
protected AsyncClientHttpRequestFactory createRequestFactory() {
return new OkHttpClientHttpRequestFactory();
diff --git a/spring-web/src/test/java/org/springframework/http/client/SimpleClientHttpResponseTests.java b/spring-web/src/test/java/org/springframework/http/client/SimpleClientHttpResponseTests.java
new file mode 100644
index 00000000..4be26a26
--- /dev/null
+++ b/spring-web/src/test/java/org/springframework/http/client/SimpleClientHttpResponseTests.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.http.client;
+
+import static org.hamcrest.MatcherAssert.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.BDDMockito.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.nio.charset.Charset;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.springframework.util.StreamUtils;
+
+/**
+ * @author Brian Clozel
+ */
+public class SimpleClientHttpResponseTests {
+
+ private final Charset UTF8 = Charset.forName("UTF-8");
+
+ private SimpleClientHttpResponse response;
+
+ private HttpURLConnection connection;
+
+ @Before
+ public void setup() throws Exception {
+ this.connection = mock(HttpURLConnection.class);
+ this.response = new SimpleClientHttpResponse(this.connection);
+ }
+
+ // SPR-14040
+ @Test
+ public void shouldNotCloseConnectionWhenResponseClosed() throws Exception {
+ TestByteArrayInputStream is = new TestByteArrayInputStream("Spring".getBytes(UTF8));
+ given(this.connection.getErrorStream()).willReturn(null);
+ given(this.connection.getInputStream()).willReturn(is);
+
+ InputStream responseStream = this.response.getBody();
+ assertThat(StreamUtils.copyToString(responseStream, UTF8), is("Spring"));
+
+ this.response.close();
+ assertTrue(is.isClosed());
+ verify(this.connection, never()).disconnect();
+ }
+
+ // SPR-14040
+ @Test
+ public void shouldDrainStreamWhenResponseClosed() throws Exception {
+ byte[] buf = new byte[6];
+ TestByteArrayInputStream is = new TestByteArrayInputStream("SpringSpring".getBytes(UTF8));
+ given(this.connection.getErrorStream()).willReturn(null);
+ given(this.connection.getInputStream()).willReturn(is);
+
+ InputStream responseStream = this.response.getBody();
+ responseStream.read(buf);
+ assertThat(new String(buf, UTF8), is("Spring"));
+ assertThat(is.available(), is(6));
+
+ this.response.close();
+ assertThat(is.available(), is(0));
+ assertTrue(is.isClosed());
+ verify(this.connection, never()).disconnect();
+ }
+
+ // SPR-14040
+ @Test
+ public void shouldDrainErrorStreamWhenResponseClosed() throws Exception {
+ byte[] buf = new byte[6];
+ TestByteArrayInputStream is = new TestByteArrayInputStream("SpringSpring".getBytes(UTF8));
+ given(this.connection.getErrorStream()).willReturn(is);
+
+ InputStream responseStream = this.response.getBody();
+ responseStream.read(buf);
+ assertThat(new String(buf, UTF8), is("Spring"));
+ assertThat(is.available(), is(6));
+
+ this.response.close();
+ assertThat(is.available(), is(0));
+ assertTrue(is.isClosed());
+ verify(this.connection, never()).disconnect();
+ }
+
+
+ class TestByteArrayInputStream extends ByteArrayInputStream {
+
+ private boolean closed;
+
+ public TestByteArrayInputStream(byte[] buf) {
+ super(buf);
+ this.closed = false;
+ }
+
+ public boolean isClosed() {
+ return closed;
+ }
+
+ @Override
+ public void close() throws IOException {
+ super.close();
+ this.closed = true;
+ }
+ }
+
+}
diff --git a/spring-web/src/test/java/org/springframework/http/client/support/BasicAuthorizationInterceptorTests.java b/spring-web/src/test/java/org/springframework/http/client/support/BasicAuthorizationInterceptorTests.java
new file mode 100644
index 00000000..ce96460d
--- /dev/null
+++ b/spring-web/src/test/java/org/springframework/http/client/support/BasicAuthorizationInterceptorTests.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.http.client.support;
+
+import java.net.URI;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import org.springframework.beans.DirectFieldAccessor;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.client.ClientHttpRequest;
+import org.springframework.http.client.ClientHttpRequestExecution;
+import org.springframework.http.client.SimpleClientHttpRequestFactory;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * Tests for {@link BasicAuthorizationInterceptor}.
+ *
+ * @author Phillip Webb
+ * @author Stephane Nicoll
+ */
+public class BasicAuthorizationInterceptorTests {
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void createWhenUsernameIsNullShouldThrowException() {
+ this.thrown.expect(IllegalArgumentException.class);
+ this.thrown.expectMessage("Username must not be empty");
+ new BasicAuthorizationInterceptor(null, "password");
+ }
+
+ @Test
+ public void createWhenUsernameIsEmptyShouldThrowException() throws Exception {
+ this.thrown.expect(IllegalArgumentException.class);
+ this.thrown.expectMessage("Username must not be empty");
+ new BasicAuthorizationInterceptor("", "password");
+ }
+
+ @Test
+ public void createWhenPasswordIsNullShouldUseEmptyPassword() throws Exception {
+ BasicAuthorizationInterceptor interceptor = new BasicAuthorizationInterceptor(
+ "username", null);
+ assertEquals("", new DirectFieldAccessor(interceptor).getPropertyValue("password"));
+ }
+
+ @Test
+ public void interceptShouldAddHeader() throws Exception {
+ SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
+ ClientHttpRequest request = requestFactory.createRequest(new URI("http://example.com"), HttpMethod.GET);
+ ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class);
+ byte[] body = new byte[] {};
+ new BasicAuthorizationInterceptor("spring", "boot").intercept(request, body,
+ execution);
+ verify(execution).execute(request, body);
+ assertEquals("Basic c3ByaW5nOmJvb3Q=", request.getHeaders().getFirst("Authorization"));
+ }
+
+}
diff --git a/spring-web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java
index 488e1826..aaa03b59 100644
--- a/spring-web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java
+++ b/spring-web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java
@@ -261,7 +261,7 @@ public class FormHttpMessageConverterTests {
@Override
public String getCharacterEncoding() {
MediaType type = this.outputMessage.getHeaders().getContentType();
- return (type != null && type.getCharSet() != null ? type.getCharSet().name() : null);
+ return (type != null && type.getCharset() != null ? type.getCharset().name() : null);
}
@Override
diff --git a/spring-web/src/test/java/org/springframework/http/converter/ResourceHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/ResourceHttpMessageConverterTests.java
index 10752f22..d3a3d96f 100644
--- a/spring-web/src/test/java/org/springframework/http/converter/ResourceHttpMessageConverterTests.java
+++ b/spring-web/src/test/java/org/springframework/http/converter/ResourceHttpMessageConverterTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,15 @@
package org.springframework.http.converter;
+import static org.hamcrest.core.Is.*;
+import static org.hamcrest.core.IsInstanceOf.*;
+import static org.junit.Assert.*;
+import static org.mockito.BDDMockito.*;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
@@ -31,13 +40,10 @@ import org.springframework.http.MockHttpInputMessage;
import org.springframework.http.MockHttpOutputMessage;
import org.springframework.util.FileCopyUtils;
-import static org.hamcrest.core.Is.*;
-import static org.hamcrest.core.IsInstanceOf.*;
-import static org.junit.Assert.*;
-
/**
* @author Arjen Poutsma
* @author Kazuki Shimizu
+ * @author Brian Clozel
*/
public class ResourceHttpMessageConverterTests {
@@ -45,18 +51,18 @@ public class ResourceHttpMessageConverterTests {
@Test
- public void canRead() {
+ public void canReadResource() {
assertTrue(converter.canRead(Resource.class, new MediaType("application", "octet-stream")));
}
@Test
- public void canWrite() {
+ public void canWriteResource() {
assertTrue(converter.canWrite(Resource.class, new MediaType("application", "octet-stream")));
assertTrue(converter.canWrite(Resource.class, MediaType.ALL));
}
@Test
- public void read() throws IOException {
+ public void shouldReadImageResource() throws IOException {
byte[] body = FileCopyUtils.copyToByteArray(getClass().getResourceAsStream("logo.jpg"));
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body);
inputMessage.getHeaders().setContentType(MediaType.IMAGE_JPEG);
@@ -65,7 +71,7 @@ public class ResourceHttpMessageConverterTests {
}
@Test // SPR-13443
- public void readWithInputStreamResource() throws IOException {
+ public void shouldReadInputStreamResource() throws IOException {
try (InputStream body = getClass().getResourceAsStream("logo.jpg") ) {
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body);
inputMessage.getHeaders().setContentType(MediaType.IMAGE_JPEG);
@@ -76,7 +82,7 @@ public class ResourceHttpMessageConverterTests {
}
@Test
- public void write() throws IOException {
+ public void shouldWriteImageResource() throws IOException {
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
Resource body = new ClassPathResource("logo.jpg", getClass());
converter.write(body, null, outputMessage);
@@ -94,4 +100,45 @@ public class ResourceHttpMessageConverterTests {
assertTrue(Arrays.equals(byteArray, outputMessage.getBodyAsBytes()));
}
+ // SPR-12999
+ @Test @SuppressWarnings("unchecked")
+ public void writeContentNotGettingInputStream() throws Exception {
+ MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
+ Resource resource = mock(Resource.class);
+ given(resource.getInputStream()).willThrow(FileNotFoundException.class);
+
+ converter.write(resource, MediaType.APPLICATION_OCTET_STREAM, outputMessage);
+
+ assertEquals(0, outputMessage.getHeaders().getContentLength());
+ }
+
+ // SPR-12999
+ @Test
+ public void writeContentNotClosingInputStream() throws Exception {
+ MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
+ Resource resource = mock(Resource.class);
+ InputStream inputStream = mock(InputStream.class);
+ given(resource.getInputStream()).willReturn(inputStream);
+ given(inputStream.read(any())).willReturn(-1);
+ doThrow(new NullPointerException()).when(inputStream).close();
+
+ converter.write(resource, MediaType.APPLICATION_OCTET_STREAM, outputMessage);
+
+ assertEquals(0, outputMessage.getHeaders().getContentLength());
+ }
+
+ // SPR-13620
+ @Test @SuppressWarnings("unchecked")
+ public void writeContentInputStreamThrowingNullPointerException() throws Exception {
+ MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
+ Resource resource = mock(Resource.class);
+ InputStream in = mock(InputStream.class);
+ given(resource.getInputStream()).willReturn(in);
+ given(in.read(any())).willThrow(NullPointerException.class);
+
+ converter.write(resource, MediaType.APPLICATION_OCTET_STREAM, outputMessage);
+
+ assertEquals(0, outputMessage.getHeaders().getContentLength());
+ }
+
}
diff --git a/spring-web/src/test/java/org/springframework/http/converter/ResourceRegionHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/ResourceRegionHttpMessageConverterTests.java
new file mode 100644
index 00000000..a4f9c465
--- /dev/null
+++ b/spring-web/src/test/java/org/springframework/http/converter/ResourceRegionHttpMessageConverterTests.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.http.converter;
+
+import java.lang.reflect.Type;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.hamcrest.Matchers;
+import org.junit.Test;
+
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.ResourceRegion;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpRange;
+import org.springframework.http.MediaType;
+import org.springframework.http.MockHttpOutputMessage;
+import org.springframework.util.StringUtils;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * Test cases for {@link ResourceRegionHttpMessageConverter} class.
+ *
+ * @author Brian Clozel
+ */
+public class ResourceRegionHttpMessageConverterTests {
+
+ private final ResourceRegionHttpMessageConverter converter = new ResourceRegionHttpMessageConverter();
+
+ @Test
+ public void canReadResource() {
+ assertFalse(converter.canRead(Resource.class, MediaType.APPLICATION_OCTET_STREAM));
+ assertFalse(converter.canRead(Resource.class, MediaType.ALL));
+ assertFalse(converter.canRead(List.class, MediaType.APPLICATION_OCTET_STREAM));
+ assertFalse(converter.canRead(List.class, MediaType.ALL));
+ }
+
+ @Test
+ public void canWriteResource() {
+ assertTrue(converter.canWrite(ResourceRegion.class, null, MediaType.APPLICATION_OCTET_STREAM));
+ assertTrue(converter.canWrite(ResourceRegion.class, null, MediaType.ALL));
+ }
+
+ @Test
+ public void canWriteResourceCollection() {
+ Type resourceRegionList = new ParameterizedTypeReference<List<ResourceRegion>>() {}.getType();
+ assertTrue(converter.canWrite(resourceRegionList, null, MediaType.APPLICATION_OCTET_STREAM));
+ assertTrue(converter.canWrite(resourceRegionList, null, MediaType.ALL));
+
+ assertFalse(converter.canWrite(List.class, MediaType.APPLICATION_OCTET_STREAM));
+ assertFalse(converter.canWrite(List.class, MediaType.ALL));
+ }
+
+ @Test
+ public void shouldWritePartialContentByteRange() throws Exception {
+ MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
+ Resource body = new ClassPathResource("byterangeresource.txt", getClass());
+ ResourceRegion region = HttpRange.createByteRange(0, 5).toResourceRegion(body);
+ converter.write(region, MediaType.TEXT_PLAIN, outputMessage);
+
+ HttpHeaders headers = outputMessage.getHeaders();
+ assertThat(headers.getContentType(), is(MediaType.TEXT_PLAIN));
+ assertThat(headers.getContentLength(), is(6L));
+ assertThat(headers.get(HttpHeaders.CONTENT_RANGE).size(), is(1));
+ assertThat(headers.get(HttpHeaders.CONTENT_RANGE).get(0), is("bytes 0-5/39"));
+ assertThat(outputMessage.getBodyAsString(Charset.forName("UTF-8")), is("Spring"));
+ }
+
+ @Test
+ public void shouldWritePartialContentByteRangeNoEnd() throws Exception {
+ MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
+ Resource body = new ClassPathResource("byterangeresource.txt", getClass());
+ ResourceRegion region = HttpRange.createByteRange(7).toResourceRegion(body);
+ converter.write(region, MediaType.TEXT_PLAIN, outputMessage);
+
+ HttpHeaders headers = outputMessage.getHeaders();
+ assertThat(headers.getContentType(), is(MediaType.TEXT_PLAIN));
+ assertThat(headers.getContentLength(), is(32L));
+ assertThat(headers.get(HttpHeaders.CONTENT_RANGE).size(), is(1));
+ assertThat(headers.get(HttpHeaders.CONTENT_RANGE).get(0), is("bytes 7-38/39"));
+ assertThat(outputMessage.getBodyAsString(Charset.forName("UTF-8")), is("Framework test resource content."));
+ }
+
+ @Test
+ public void partialContentMultipleByteRanges() throws Exception {
+ MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
+ Resource body = new ClassPathResource("byterangeresource.txt", getClass());
+ List<HttpRange> rangeList = HttpRange.parseRanges("bytes=0-5,7-15,17-20,22-38");
+ List<ResourceRegion> regions = new ArrayList<ResourceRegion>();
+ for(HttpRange range : rangeList) {
+ regions.add(range.toResourceRegion(body));
+ }
+
+ converter.write(regions, MediaType.TEXT_PLAIN, outputMessage);
+
+ HttpHeaders headers = outputMessage.getHeaders();
+ assertThat(headers.getContentType().toString(), Matchers.startsWith("multipart/byteranges;boundary="));
+ String boundary = "--" + headers.getContentType().toString().substring(30);
+ String content = outputMessage.getBodyAsString(Charset.forName("UTF-8"));
+ String[] ranges = StringUtils.tokenizeToStringArray(content, "\r\n", false, true);
+
+ assertThat(ranges[0], is(boundary));
+ assertThat(ranges[1], is("Content-Type: text/plain"));
+ assertThat(ranges[2], is("Content-Range: bytes 0-5/39"));
+ assertThat(ranges[3], is("Spring"));
+
+ assertThat(ranges[4], is(boundary));
+ assertThat(ranges[5], is("Content-Type: text/plain"));
+ assertThat(ranges[6], is("Content-Range: bytes 7-15/39"));
+ assertThat(ranges[7], is("Framework"));
+
+ assertThat(ranges[8], is(boundary));
+ assertThat(ranges[9], is("Content-Type: text/plain"));
+ assertThat(ranges[10], is("Content-Range: bytes 17-20/39"));
+ assertThat(ranges[11], is("test"));
+
+ assertThat(ranges[12], is(boundary));
+ assertThat(ranges[13], is("Content-Type: text/plain"));
+ assertThat(ranges[14], is("Content-Range: bytes 22-38/39"));
+ assertThat(ranges[15], is("resource content."));
+ }
+
+} \ No newline at end of file
diff --git a/spring-web/src/test/java/org/springframework/http/converter/StringHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/StringHttpMessageConverterTests.java
index 6dcdb25f..2fa2a574 100644
--- a/spring-web/src/test/java/org/springframework/http/converter/StringHttpMessageConverterTests.java
+++ b/spring-web/src/test/java/org/springframework/http/converter/StringHttpMessageConverterTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author 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,86 +22,97 @@ import java.nio.charset.Charset;
import org.junit.Before;
import org.junit.Test;
+import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.MockHttpInputMessage;
import org.springframework.http.MockHttpOutputMessage;
import static org.junit.Assert.*;
-/** @author Arjen Poutsma */
+/**
+ * @author Arjen Poutsma
+ * @author Rossen Stoyanchev
+ */
public class StringHttpMessageConverterTests {
+ public static final Charset UTF_8 = Charset.forName("UTF-8");
+
+ public static final MediaType TEXT_PLAIN_UTF_8 = new MediaType("text", "plain", UTF_8);
+
+
private StringHttpMessageConverter converter;
+ private MockHttpOutputMessage outputMessage;
+
+
@Before
public void setUp() {
- converter = new StringHttpMessageConverter();
+ this.converter = new StringHttpMessageConverter();
+ this.outputMessage = new MockHttpOutputMessage();
}
+
@Test
public void canRead() {
- assertTrue(converter.canRead(String.class, new MediaType("text", "plain")));
+ assertTrue(this.converter.canRead(String.class, MediaType.TEXT_PLAIN));
}
@Test
public void canWrite() {
- assertTrue(converter.canWrite(String.class, new MediaType("text", "plain")));
- assertTrue(converter.canWrite(String.class, MediaType.ALL));
+ assertTrue(this.converter.canWrite(String.class, MediaType.TEXT_PLAIN));
+ assertTrue(this.converter.canWrite(String.class, MediaType.ALL));
}
@Test
public void read() throws IOException {
String body = "Hello World";
- Charset charset = Charset.forName("UTF-8");
- MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(charset));
- inputMessage.getHeaders().setContentType(new MediaType("text", "plain", charset));
- String result = converter.read(String.class, inputMessage);
+ MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(UTF_8));
+ inputMessage.getHeaders().setContentType(TEXT_PLAIN_UTF_8);
+ String result = this.converter.read(String.class, inputMessage);
+
assertEquals("Invalid result", body, result);
}
@Test
public void writeDefaultCharset() throws IOException {
Charset iso88591 = Charset.forName("ISO-8859-1");
- MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
String body = "H\u00e9llo W\u00f6rld";
- converter.write(body, null, outputMessage);
- assertEquals("Invalid result", body, outputMessage.getBodyAsString(iso88591));
- assertEquals("Invalid content-type", new MediaType("text", "plain", iso88591),
- outputMessage.getHeaders().getContentType());
- assertEquals("Invalid content-length", body.getBytes(iso88591).length,
- outputMessage.getHeaders().getContentLength());
- assertFalse("Invalid accept-charset", outputMessage.getHeaders().getAcceptCharset().isEmpty());
+ this.converter.write(body, null, this.outputMessage);
+
+ HttpHeaders headers = this.outputMessage.getHeaders();
+ assertEquals(body, this.outputMessage.getBodyAsString(iso88591));
+ assertEquals(new MediaType("text", "plain", iso88591), headers.getContentType());
+ assertEquals(body.getBytes(iso88591).length, headers.getContentLength());
+ assertFalse(headers.getAcceptCharset().isEmpty());
}
@Test
public void writeUTF8() throws IOException {
- Charset utf8 = Charset.forName("UTF-8");
- MediaType contentType = new MediaType("text", "plain", utf8);
- MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
String body = "H\u00e9llo W\u00f6rld";
- converter.write(body, contentType, outputMessage);
- assertEquals("Invalid result", body, outputMessage.getBodyAsString(utf8));
- assertEquals("Invalid content-type", contentType, outputMessage.getHeaders().getContentType());
- assertEquals("Invalid content-length", body.getBytes(utf8).length,
- outputMessage.getHeaders().getContentLength());
- assertFalse("Invalid accept-charset", outputMessage.getHeaders().getAcceptCharset().isEmpty());
+ this.converter.write(body, TEXT_PLAIN_UTF_8, this.outputMessage);
+
+ HttpHeaders headers = this.outputMessage.getHeaders();
+ assertEquals(body, this.outputMessage.getBodyAsString(UTF_8));
+ assertEquals(TEXT_PLAIN_UTF_8, headers.getContentType());
+ assertEquals(body.getBytes(UTF_8).length, headers.getContentLength());
+ assertFalse(headers.getAcceptCharset().isEmpty());
}
// SPR-8867
@Test
public void writeOverrideRequestedContentType() throws IOException {
- Charset utf8 = Charset.forName("UTF-8");
- MediaType requestedContentType = new MediaType("text", "html");
- MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
- MediaType contentType = new MediaType("text", "plain", utf8);
- outputMessage.getHeaders().setContentType(contentType);
String body = "H\u00e9llo W\u00f6rld";
- converter.write(body, requestedContentType, outputMessage);
- assertEquals("Invalid result", body, outputMessage.getBodyAsString(utf8));
- assertEquals("Invalid content-type", contentType, outputMessage.getHeaders().getContentType());
- assertEquals("Invalid content-length", body.getBytes(utf8).length,
- outputMessage.getHeaders().getContentLength());
- assertFalse("Invalid accept-charset", outputMessage.getHeaders().getAcceptCharset().isEmpty());
+ MediaType requestedContentType = new MediaType("text", "html");
+
+ HttpHeaders headers = this.outputMessage.getHeaders();
+ headers.setContentType(TEXT_PLAIN_UTF_8);
+ this.converter.write(body, requestedContentType, this.outputMessage);
+
+ assertEquals(body, this.outputMessage.getBodyAsString(UTF_8));
+ assertEquals(TEXT_PLAIN_UTF_8, headers.getContentType());
+ assertEquals(body.getBytes(UTF_8).length, headers.getContentLength());
+ assertFalse(headers.getAcceptCharset().isEmpty());
}
+
}
diff --git a/spring-web/src/test/java/org/springframework/http/converter/feed/AtomFeedHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/feed/AtomFeedHttpMessageConverterTests.java
index bcaa76b9..54d83074 100644
--- a/spring-web/src/test/java/org/springframework/http/converter/feed/AtomFeedHttpMessageConverterTests.java
+++ b/spring-web/src/test/java/org/springframework/http/converter/feed/AtomFeedHttpMessageConverterTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -42,13 +42,13 @@ import static org.junit.Assert.assertTrue;
*/
public class AtomFeedHttpMessageConverterTests {
+ private static final Charset UTF_8 = Charset.forName("UTF-8");
+
private AtomFeedHttpMessageConverter converter;
- private Charset utf8;
@Before
public void setUp() {
- utf8 = Charset.forName("UTF-8");
converter = new AtomFeedHttpMessageConverter();
XMLUnit.setIgnoreWhitespace(true);
}
@@ -56,20 +56,20 @@ public class AtomFeedHttpMessageConverterTests {
@Test
public void canRead() {
assertTrue(converter.canRead(Feed.class, new MediaType("application", "atom+xml")));
- assertTrue(converter.canRead(Feed.class, new MediaType("application", "atom+xml", utf8)));
+ assertTrue(converter.canRead(Feed.class, new MediaType("application", "atom+xml", UTF_8)));
}
@Test
public void canWrite() {
assertTrue(converter.canWrite(Feed.class, new MediaType("application", "atom+xml")));
- assertTrue(converter.canWrite(Feed.class, new MediaType("application", "atom+xml", Charset.forName("UTF-8"))));
+ assertTrue(converter.canWrite(Feed.class, new MediaType("application", "atom+xml", UTF_8)));
}
@Test
public void read() throws IOException {
InputStream is = getClass().getResourceAsStream("atom.xml");
MockHttpInputMessage inputMessage = new MockHttpInputMessage(is);
- inputMessage.getHeaders().setContentType(new MediaType("application", "atom+xml", utf8));
+ inputMessage.getHeaders().setContentType(new MediaType("application", "atom+xml", UTF_8));
Feed result = converter.read(Feed.class, inputMessage);
assertEquals("title", result.getTitle());
assertEquals("subtitle", result.getSubtitle().getValue());
@@ -106,12 +106,12 @@ public class AtomFeedHttpMessageConverterTests {
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
converter.write(feed, null, outputMessage);
- assertEquals("Invalid content-type", new MediaType("application", "atom+xml", utf8),
+ assertEquals("Invalid content-type", new MediaType("application", "atom+xml", UTF_8),
outputMessage.getHeaders().getContentType());
String expected = "<feed xmlns=\"http://www.w3.org/2005/Atom\">" + "<title>title</title>" +
"<entry><id>id1</id><title>title1</title></entry>" +
"<entry><id>id2</id><title>title2</title></entry></feed>";
- assertXMLEqual(expected, outputMessage.getBodyAsString(utf8));
+ assertXMLEqual(expected, outputMessage.getBodyAsString(UTF_8));
}
@Test
diff --git a/spring-web/src/test/java/org/springframework/http/converter/feed/RssChannelHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/feed/RssChannelHttpMessageConverterTests.java
index 16fcfc00..a8ceeb3b 100644
--- a/spring-web/src/test/java/org/springframework/http/converter/feed/RssChannelHttpMessageConverterTests.java
+++ b/spring-web/src/test/java/org/springframework/http/converter/feed/RssChannelHttpMessageConverterTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -42,34 +42,35 @@ import static org.junit.Assert.assertTrue;
*/
public class RssChannelHttpMessageConverterTests {
+ private static final Charset UTF_8 = Charset.forName("UTF-8");
+
private RssChannelHttpMessageConverter converter;
- private Charset utf8;
@Before
public void setUp() {
- utf8 = Charset.forName("UTF-8");
converter = new RssChannelHttpMessageConverter();
XMLUnit.setIgnoreWhitespace(true);
}
+
@Test
public void canRead() {
assertTrue(converter.canRead(Channel.class, new MediaType("application", "rss+xml")));
- assertTrue(converter.canRead(Channel.class, new MediaType("application", "rss+xml", utf8)));
+ assertTrue(converter.canRead(Channel.class, new MediaType("application", "rss+xml", UTF_8)));
}
@Test
public void canWrite() {
assertTrue(converter.canWrite(Channel.class, new MediaType("application", "rss+xml")));
- assertTrue(converter.canWrite(Channel.class, new MediaType("application", "rss+xml", Charset.forName("UTF-8"))));
+ assertTrue(converter.canWrite(Channel.class, new MediaType("application", "rss+xml", UTF_8)));
}
@Test
public void read() throws IOException {
InputStream is = getClass().getResourceAsStream("rss.xml");
MockHttpInputMessage inputMessage = new MockHttpInputMessage(is);
- inputMessage.getHeaders().setContentType(new MediaType("application", "rss+xml", utf8));
+ inputMessage.getHeaders().setContentType(new MediaType("application", "rss+xml", UTF_8));
Channel result = converter.read(Channel.class, inputMessage);
assertEquals("title", result.getTitle());
assertEquals("http://example.com", result.getLink());
@@ -106,14 +107,14 @@ public class RssChannelHttpMessageConverterTests {
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
converter.write(channel, null, outputMessage);
- assertEquals("Invalid content-type", new MediaType("application", "rss+xml", utf8),
+ assertEquals("Invalid content-type", new MediaType("application", "rss+xml", UTF_8),
outputMessage.getHeaders().getContentType());
String expected = "<rss version=\"2.0\">" +
"<channel><title>title</title><link>http://example.com</link><description>description</description>" +
"<item><title>title1</title></item>" +
"<item><title>title2</title></item>" +
"</channel></rss>";
- assertXMLEqual(expected, outputMessage.getBodyAsString(utf8));
+ assertXMLEqual(expected, outputMessage.getBodyAsString(UTF_8));
}
@Test
diff --git a/spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilderTests.java b/spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilderTests.java
index 0f02ad21..ddb9e1e6 100644
--- a/spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilderTests.java
+++ b/spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilderTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,9 +22,11 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
+import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
@@ -62,7 +64,7 @@ import com.fasterxml.jackson.databind.ser.std.ClassSerializer;
import com.fasterxml.jackson.databind.ser.std.NumberSerializer;
import com.fasterxml.jackson.databind.type.SimpleType;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
-
+import kotlin.ranges.IntRange;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.junit.Test;
@@ -77,6 +79,7 @@ import static org.junit.Assert.*;
*
* @author Sebastien Deleuze
*/
+@SuppressWarnings("deprecation")
public class Jackson2ObjectMapperBuilderTests {
private static final String DATE_FORMAT = "yyyy-MM-dd";
@@ -249,13 +252,17 @@ public class Jackson2ObjectMapperBuilderTests {
assertEquals(timestamp.toString(), new String(objectMapper.writeValueAsBytes(dateTime), "UTF-8"));
Path file = Paths.get("foo");
- assertEquals("\"foo\"", new String(objectMapper.writeValueAsBytes(file), "UTF-8"));
+ assertTrue(new String(objectMapper.writeValueAsBytes(file), "UTF-8").endsWith("foo\""));
Optional<String> optional = Optional.of("test");
assertEquals("\"test\"", new String(objectMapper.writeValueAsBytes(optional), "UTF-8"));
+
+ // Kotlin module
+ IntRange range = new IntRange(1, 3);
+ assertEquals("{\"start\":1,\"end\":3}", new String(objectMapper.writeValueAsBytes(range), "UTF-8"));
}
- @Test // SPR-12634
+ @Test // SPR-12634
public void customizeWellKnownModulesWithModule() throws JsonProcessingException, UnsupportedEncodingException {
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json()
.modulesToInstall(new CustomIntegerModule()).build();
@@ -264,7 +271,7 @@ public class Jackson2ObjectMapperBuilderTests {
assertThat(new String(objectMapper.writeValueAsBytes(new Integer(4)), "UTF-8"), containsString("customid"));
}
- @Test // SPR-12634
+ @Test // SPR-12634
@SuppressWarnings("unchecked")
public void customizeWellKnownModulesWithModuleClass() throws JsonProcessingException, UnsupportedEncodingException {
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().modulesToInstall(CustomIntegerModule.class).build();
@@ -273,7 +280,7 @@ public class Jackson2ObjectMapperBuilderTests {
assertThat(new String(objectMapper.writeValueAsBytes(new Integer(4)), "UTF-8"), containsString("customid"));
}
- @Test // SPR-12634
+ @Test // SPR-12634
public void customizeWellKnownModulesWithSerializer() throws JsonProcessingException, UnsupportedEncodingException {
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json()
.serializerByType(Integer.class, new CustomIntegerSerializer()).build();
@@ -326,8 +333,8 @@ public class Jackson2ObjectMapperBuilderTests {
Class<?> target = String.class;
Class<?> mixInSource = Object.class;
- ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().mixIn(target,
- mixInSource).build();
+ ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().modules()
+ .mixIn(target, mixInSource).build();
assertEquals(1, objectMapper.mixInCount());
assertSame(mixInSource, objectMapper.findMixInClassFor(target));
@@ -340,7 +347,8 @@ public class Jackson2ObjectMapperBuilderTests {
Map<Class<?>, Class<?>> mixIns = new HashMap<Class<?>, Class<?>>();
mixIns.put(target, mixInSource);
- ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().mixIns(mixIns).build();
+ ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().modules()
+ .mixIns(mixIns).build();
assertEquals(1, objectMapper.mixInCount());
assertSame(mixInSource, objectMapper.findMixInClassFor(target));
@@ -437,6 +445,16 @@ public class Jackson2ObjectMapperBuilderTests {
assertTrue(xmlObjectMapper.getClass().isAssignableFrom(XmlMapper.class));
}
+ @Test // SPR-13975
+ public void defaultUseWrapper() throws JsonProcessingException {
+ ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.xml().defaultUseWrapper(false).build();
+ assertNotNull(objectMapper);
+ assertEquals(XmlMapper.class, objectMapper.getClass());
+ ListContainer<String> container = new ListContainer<>(Arrays.asList("foo", "bar"));
+ String output = objectMapper.writeValueAsString(container);
+ assertThat(output, containsString("<list>foo</list><list>bar</list></ListContainer>"));
+ }
+
public static class CustomIntegerModule extends Module {
@@ -501,4 +519,25 @@ public class Jackson2ObjectMapperBuilderTests {
}
}
+
+ public static class ListContainer<T> {
+
+ private List<T> list;
+
+ public ListContainer() {
+ }
+
+ public ListContainer(List<T> list) {
+ this.list = list;
+ }
+
+ public List<T> getList() {
+ return list;
+ }
+
+ public void setList(List<T> list) {
+ this.list = list;
+ }
+ }
+
}
diff --git a/spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperFactoryBeanTests.java b/spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperFactoryBeanTests.java
index 005623f6..d678c660 100644
--- a/spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperFactoryBeanTests.java
+++ b/spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperFactoryBeanTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -76,6 +76,7 @@ import static org.junit.Assert.*;
* @author Sebastien Deleuze
* @author Sam Brannen
*/
+@SuppressWarnings("deprecation")
public class Jackson2ObjectMapperFactoryBeanTests {
private static final String DATE_FORMAT = "yyyy-MM-dd";
@@ -285,6 +286,7 @@ public class Jackson2ObjectMapperFactoryBeanTests {
Map<Class<?>, Class<?>> mixIns = new HashMap<Class<?>, Class<?>>();
mixIns.put(target, mixinSource);
+ this.factory.setModules(Collections.emptyList());
this.factory.setMixIns(mixIns);
this.factory.afterPropertiesSet();
ObjectMapper objectMapper = this.factory.getObject();
diff --git a/spring-web/src/test/java/org/springframework/http/converter/json/SpringHandlerInstantiatorTests.java b/spring-web/src/test/java/org/springframework/http/converter/json/SpringHandlerInstantiatorTests.java
index 687df9c9..2f334ea9 100644
--- a/spring-web/src/test/java/org/springframework/http/converter/json/SpringHandlerInstantiatorTests.java
+++ b/spring-web/src/test/java/org/springframework/http/converter/json/SpringHandlerInstantiatorTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -47,7 +47,6 @@ import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder;
import com.fasterxml.jackson.databind.type.TypeFactory;
-
import org.junit.Before;
import org.junit.Test;
@@ -197,7 +196,7 @@ public class SpringHandlerInstantiatorTests {
return JsonTypeInfo.Id.CUSTOM;
}
- @Override
+ // Only needed when compiling against Jackson 2.7; gone in 2.8
@SuppressWarnings("deprecation")
public JavaType typeFromId(String s) {
return TypeFactory.defaultInstance().constructFromCanonical(s);
@@ -218,10 +217,15 @@ public class SpringHandlerInstantiatorTests {
return null;
}
- // New in Jackson 2.5
+ @Override
public JavaType typeFromId(DatabindContext context, String id) {
return null;
}
+
+ // New in Jackson 2.7
+ public String getDescForKnownTypeIds() {
+ return null;
+ }
}
diff --git a/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpResponseTests.java b/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpResponseTests.java
index 4b8e60fe..b592a045 100644
--- a/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpResponseTests.java
+++ b/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpResponseTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,13 +29,12 @@ import org.springframework.http.MediaType;
import org.springframework.mock.web.test.MockHttpServletResponse;
import org.springframework.util.FileCopyUtils;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
/**
* @author Arjen Poutsma
* @author Rossen Stoyanchev
+ * @author Juergen Hoeller
*/
public class ServletServerHttpResponseTests {
@@ -79,7 +78,6 @@ public class ServletServerHttpResponseTests {
@Test
public void preExistingHeadersFromHttpServletResponse() {
-
String headerName = "Access-Control-Allow-Origin";
String headerValue = "localhost:8080";
@@ -89,6 +87,8 @@ public class ServletServerHttpResponseTests {
assertEquals(headerValue, this.response.getHeaders().getFirst(headerName));
assertEquals(Collections.singletonList(headerValue), this.response.getHeaders().get(headerName));
assertTrue(this.response.getHeaders().containsKey(headerName));
+ assertEquals(headerValue, this.response.getHeaders().getFirst(headerName));
+ assertEquals(headerValue, this.response.getHeaders().getAccessControlAllowOrigin());
}
@Test
@@ -98,4 +98,5 @@ public class ServletServerHttpResponseTests {
assertArrayEquals("Invalid content written", content, mockResponse.getContentAsByteArray());
}
-} \ No newline at end of file
+
+}
diff --git a/spring-web/src/test/java/org/springframework/mock/web/test/MockHttpServletRequest.java b/spring-web/src/test/java/org/springframework/mock/web/test/MockHttpServletRequest.java
index 48b03112..da21c6e2 100644
--- a/spring-web/src/test/java/org/springframework/mock/web/test/MockHttpServletRequest.java
+++ b/spring-web/src/test/java/org/springframework/mock/web/test/MockHttpServletRequest.java
@@ -390,8 +390,8 @@ public class MockHttpServletRequest implements HttpServletRequest {
if (contentType != null) {
try {
MediaType mediaType = MediaType.parseMediaType(contentType);
- if (mediaType.getCharSet() != null) {
- this.characterEncoding = mediaType.getCharSet().name();
+ if (mediaType.getCharset() != null) {
+ this.characterEncoding = mediaType.getCharset().name();
}
}
catch (Exception ex) {
diff --git a/spring-web/src/test/java/org/springframework/mock/web/test/MockHttpServletResponse.java b/spring-web/src/test/java/org/springframework/mock/web/test/MockHttpServletResponse.java
index 413b3321..c0986b29 100644
--- a/spring-web/src/test/java/org/springframework/mock/web/test/MockHttpServletResponse.java
+++ b/spring-web/src/test/java/org/springframework/mock/web/test/MockHttpServletResponse.java
@@ -236,8 +236,8 @@ public class MockHttpServletResponse implements HttpServletResponse {
if (contentType != null) {
try {
MediaType mediaType = MediaType.parseMediaType(contentType);
- if (mediaType.getCharSet() != null) {
- this.characterEncoding = mediaType.getCharSet().name();
+ if (mediaType.getCharset() != null) {
+ this.characterEncoding = mediaType.getCharset().name();
this.charset = true;
}
}
diff --git a/spring-web/src/test/java/org/springframework/mock/web/test/MockServletContext.java b/spring-web/src/test/java/org/springframework/mock/web/test/MockServletContext.java
index 224374c0..acaef598 100644
--- a/spring-web/src/test/java/org/springframework/mock/web/test/MockServletContext.java
+++ b/spring-web/src/test/java/org/springframework/mock/web/test/MockServletContext.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,7 +25,6 @@ import java.util.Collections;
import java.util.Enumeration;
import java.util.EventListener;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
@@ -144,7 +143,7 @@ public class MockServletContext implements ServletContext {
private String servletContextName = "MockServletContext";
- private final Set<String> declaredRoles = new HashSet<String>();
+ private final Set<String> declaredRoles = new LinkedHashSet<String>();
private Set<SessionTrackingMode> sessionTrackingModes;
@@ -370,7 +369,6 @@ public class MockServletContext implements ServletContext {
/**
* Register a {@link RequestDispatcher} (typically a {@link MockRequestDispatcher})
* that acts as a wrapper for the named Servlet.
- *
* @param name the name of the wrapped Servlet
* @param requestDispatcher the dispatcher that wraps the named Servlet
* @see #getNamedDispatcher
@@ -384,7 +382,6 @@ public class MockServletContext implements ServletContext {
/**
* Unregister the {@link RequestDispatcher} with the given name.
- *
* @param name the name of the dispatcher to unregister
* @see #getNamedDispatcher
* @see #registerNamedDispatcher
@@ -429,13 +426,13 @@ public class MockServletContext implements ServletContext {
@Override
@Deprecated
public Enumeration<Servlet> getServlets() {
- return Collections.enumeration(new HashSet<Servlet>());
+ return Collections.enumeration(Collections.<Servlet>emptySet());
}
@Override
@Deprecated
public Enumeration<String> getServletNames() {
- return Collections.enumeration(new HashSet<String>());
+ return Collections.enumeration(Collections.<String>emptySet());
}
@Override
diff --git a/spring-web/src/test/java/org/springframework/remoting/jaxws/JaxWsSupportTests.java b/spring-web/src/test/java/org/springframework/remoting/jaxws/JaxWsSupportTests.java
index 3a887601..1376734f 100644
--- a/spring-web/src/test/java/org/springframework/remoting/jaxws/JaxWsSupportTests.java
+++ b/spring-web/src/test/java/org/springframework/remoting/jaxws/JaxWsSupportTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -127,7 +127,7 @@ public class JaxWsSupportTests {
}
catch (BeanCreationException ex) {
if ("exporter".equals(ex.getBeanName()) && ex.getRootCause() instanceof ClassNotFoundException) {
- // ignore - probably running on JDK < 1.6 without the JAX-WS impl present
+ // ignore - probably running on JDK without the JAX-WS impl present
}
else {
throw ex;
@@ -146,7 +146,7 @@ public class JaxWsSupportTests {
public OrderService myService;
- @WebServiceRef(value=OrderServiceService.class, wsdlLocation = "http://localhost:9999/OrderService?wsdl")
+ @WebServiceRef(value = OrderServiceService.class, wsdlLocation = "http://localhost:9999/OrderService?wsdl")
public void setMyService(OrderService myService) {
this.myService = myService;
}
diff --git a/spring-web/src/test/java/org/springframework/web/accept/ContentNegotiationManagerFactoryBeanTests.java b/spring-web/src/test/java/org/springframework/web/accept/ContentNegotiationManagerFactoryBeanTests.java
index e9d95c2a..baabc764 100644
--- a/spring-web/src/test/java/org/springframework/web/accept/ContentNegotiationManagerFactoryBeanTests.java
+++ b/spring-web/src/test/java/org/springframework/web/accept/ContentNegotiationManagerFactoryBeanTests.java
@@ -30,10 +30,11 @@ import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
-import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.*;
/**
* Test fixture for {@link ContentNegotiationManagerFactoryBean} tests.
+ *
* @author Rossen Stoyanchev
*/
public class ContentNegotiationManagerFactoryBeanTests {
@@ -119,9 +120,7 @@ public class ContentNegotiationManagerFactoryBeanTests {
assertEquals(Collections.emptyList(), manager.resolveMediaTypes(this.webRequest));
}
- // SPR-10170
-
- @Test(expected = HttpMediaTypeNotAcceptableException.class)
+ @Test(expected = HttpMediaTypeNotAcceptableException.class) // SPR-10170
public void favorPathWithIgnoreUnknownPathExtensionTurnedOff() throws Exception {
this.factoryBean.setFavorPathExtension(true);
this.factoryBean.setIgnoreUnknownPathExtensions(false);
@@ -152,9 +151,7 @@ public class ContentNegotiationManagerFactoryBeanTests {
manager.resolveMediaTypes(this.webRequest));
}
- // SPR-10170
-
- @Test(expected = HttpMediaTypeNotAcceptableException.class)
+ @Test(expected = HttpMediaTypeNotAcceptableException.class) // SPR-10170
public void favorParameterWithUnknownMediaType() throws HttpMediaTypeNotAcceptableException {
this.factoryBean.setFavorParameter(true);
this.factoryBean.afterPropertiesSet();
@@ -188,16 +185,12 @@ public class ContentNegotiationManagerFactoryBeanTests {
manager.resolveMediaTypes(this.webRequest));
// SPR-10513
-
this.servletRequest.addHeader("Accept", MediaType.ALL_VALUE);
-
assertEquals(Collections.singletonList(MediaType.APPLICATION_JSON),
manager.resolveMediaTypes(this.webRequest));
}
- // SPR-12286
-
- @Test
+ @Test // SPR-12286
public void setDefaultContentTypeWithStrategy() throws Exception {
this.factoryBean.setDefaultContentTypeStrategy(new FixedContentNegotiationStrategy(MediaType.APPLICATION_JSON));
this.factoryBean.afterPropertiesSet();
@@ -216,7 +209,6 @@ public class ContentNegotiationManagerFactoryBeanTests {
private final Map<String, String> mimeTypes = new HashMap<>();
-
public Map<String, String> getMimeTypes() {
return this.mimeTypes;
}
diff --git a/spring-web/src/test/java/org/springframework/web/accept/HeaderContentNegotiationStrategyTests.java b/spring-web/src/test/java/org/springframework/web/accept/HeaderContentNegotiationStrategyTests.java
index 72f8aca9..f6fa0d6b 100644
--- a/spring-web/src/test/java/org/springframework/web/accept/HeaderContentNegotiationStrategyTests.java
+++ b/spring-web/src/test/java/org/springframework/web/accept/HeaderContentNegotiationStrategyTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package org.springframework.web.accept;
import java.util.List;
-import org.junit.Before;
import org.junit.Test;
import org.springframework.http.MediaType;
@@ -32,21 +32,16 @@ import static org.junit.Assert.*;
* Test fixture for HeaderContentNegotiationStrategy tests.
*
* @author Rossen Stoyanchev
+ * @author Juergen Hoeller
*/
public class HeaderContentNegotiationStrategyTests {
- private HeaderContentNegotiationStrategy strategy;
+ private final HeaderContentNegotiationStrategy strategy = new HeaderContentNegotiationStrategy();
- private NativeWebRequest webRequest;
+ private final MockHttpServletRequest servletRequest = new MockHttpServletRequest();
- private MockHttpServletRequest servletRequest;
+ private final NativeWebRequest webRequest = new ServletWebRequest(this.servletRequest);
- @Before
- public void setup() {
- this.strategy = new HeaderContentNegotiationStrategy();
- this.servletRequest = new MockHttpServletRequest();
- this.webRequest = new ServletWebRequest(servletRequest );
- }
@Test
public void resolveMediaTypes() throws Exception {
@@ -60,7 +55,20 @@ public class HeaderContentNegotiationStrategyTests {
assertEquals("text/plain;q=0.5", mediaTypes.get(3).toString());
}
- @Test(expected=HttpMediaTypeNotAcceptableException.class)
+ @Test // SPR-14506
+ public void resolveMediaTypesFromMultipleHeaderValues() throws Exception {
+ this.servletRequest.addHeader("Accept", "text/plain; q=0.5, text/html");
+ this.servletRequest.addHeader("Accept", "text/x-dvi; q=0.8, text/x-c");
+ List<MediaType> mediaTypes = this.strategy.resolveMediaTypes(this.webRequest);
+
+ assertEquals(4, mediaTypes.size());
+ assertEquals("text/html", mediaTypes.get(0).toString());
+ assertEquals("text/x-c", mediaTypes.get(1).toString());
+ assertEquals("text/x-dvi;q=0.8", mediaTypes.get(2).toString());
+ assertEquals("text/plain;q=0.5", mediaTypes.get(3).toString());
+ }
+
+ @Test(expected = HttpMediaTypeNotAcceptableException.class)
public void resolveMediaTypesParseError() throws Exception {
this.servletRequest.addHeader("Accept", "textplain; q=0.5");
this.strategy.resolveMediaTypes(this.webRequest);
diff --git a/spring-web/src/test/java/org/springframework/web/bind/support/WebRequestDataBinderTests.java b/spring-web/src/test/java/org/springframework/web/bind/support/WebRequestDataBinderTests.java
index 32b4c0e1..508be6d3 100644
--- a/spring-web/src/test/java/org/springframework/web/bind/support/WebRequestDataBinderTests.java
+++ b/spring-web/src/test/java/org/springframework/web/bind/support/WebRequestDataBinderTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,10 +16,15 @@
package org.springframework.web.bind.support;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
import java.beans.PropertyEditorSupport;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
+import java.util.Set;
import org.junit.Test;
@@ -34,8 +39,6 @@ import org.springframework.web.bind.ServletRequestParameterPropertyValues;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.multipart.support.StringMultipartFileEditor;
-import static org.junit.Assert.*;
-
/**
* @author Juergen Hoeller
*/
@@ -126,6 +129,31 @@ public class WebRequestDataBinderTests {
assertFalse(target.isPostProcessed());
}
+ // SPR-13502
+ @Test
+ public void testCollectionFieldsDefault() throws Exception {
+ TestBean target = new TestBean();
+ target.setSomeSet(null);
+ target.setSomeList(null);
+ target.setSomeMap(null);
+ WebRequestDataBinder binder = new WebRequestDataBinder(target);
+
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.addParameter("_someSet", "visible");
+ request.addParameter("_someList", "visible");
+ request.addParameter("_someMap", "visible");
+
+ binder.bind(new ServletWebRequest(request));
+ assertThat(target.getSomeSet(), notNullValue());
+ assertThat(target.getSomeSet(), isA(Set.class));
+
+ assertThat(target.getSomeList(), notNullValue());
+ assertThat(target.getSomeList(), isA(List.class));
+
+ assertThat(target.getSomeMap(), notNullValue());
+ assertThat(target.getSomeMap(), isA(Map.class));
+ }
+
@Test
public void testFieldDefaultPreemptsFieldMarker() throws Exception {
TestBean target = new TestBean();
diff --git a/spring-web/src/test/java/org/springframework/web/client/AsyncRestTemplateIntegrationTests.java b/spring-web/src/test/java/org/springframework/web/client/AsyncRestTemplateIntegrationTests.java
index 6210cec1..258a9cc4 100644
--- a/spring-web/src/test/java/org/springframework/web/client/AsyncRestTemplateIntegrationTests.java
+++ b/spring-web/src/test/java/org/springframework/web/client/AsyncRestTemplateIntegrationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,8 +16,11 @@
package org.springframework.web.client;
+import java.io.IOException;
import java.net.URI;
+import java.net.URISyntaxException;
import java.nio.charset.Charset;
+import java.util.Collections;
import java.util.EnumSet;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
@@ -25,6 +28,7 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
+import org.junit.Assert;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
@@ -32,16 +36,28 @@ import org.springframework.core.io.Resource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
+import org.springframework.http.client.AsyncClientHttpRequestExecution;
+import org.springframework.http.client.AsyncClientHttpRequestInterceptor;
+import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.HttpComponentsAsyncClientHttpRequestFactory;
+import org.springframework.http.client.support.HttpRequestWrapper;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
/**
* @author Arjen Poutsma
@@ -49,14 +65,14 @@ import static org.junit.Assert.*;
*/
public class AsyncRestTemplateIntegrationTests extends AbstractJettyServerTestCase {
- private final AsyncRestTemplate template = new AsyncRestTemplate(new HttpComponentsAsyncClientHttpRequestFactory());
+ private final AsyncRestTemplate template = new AsyncRestTemplate(
+ new HttpComponentsAsyncClientHttpRequestFactory());
@Test
public void getEntity() throws Exception {
- Future<ResponseEntity<String>> futureEntity =
- template.getForEntity(baseUrl + "/{method}", String.class, "get");
- ResponseEntity<String> entity = futureEntity.get();
+ Future<ResponseEntity<String>> future = template.getForEntity(baseUrl + "/{method}", String.class, "get");
+ ResponseEntity<String> entity = future.get();
assertEquals("Invalid content", helloWorld, entity.getBody());
assertFalse("No headers", entity.getHeaders().isEmpty());
assertEquals("Invalid content-type", textContentType, entity.getHeaders().getContentType());
@@ -65,10 +81,9 @@ public class AsyncRestTemplateIntegrationTests extends AbstractJettyServerTestCa
@Test
public void multipleFutureGets() throws Exception {
- Future<ResponseEntity<String>> futureEntity =
- template.getForEntity(baseUrl + "/{method}", String.class, "get");
- futureEntity.get();
- futureEntity.get();
+ Future<ResponseEntity<String>> future = template.getForEntity(baseUrl + "/{method}", String.class, "get");
+ future.get();
+ future.get();
}
@Test
@@ -88,9 +103,7 @@ public class AsyncRestTemplateIntegrationTests extends AbstractJettyServerTestCa
fail(ex.getMessage());
}
});
- // wait till done
- while (!futureEntity.isDone()) {
- }
+ waitTillDone(futureEntity);
}
@Test
@@ -103,9 +116,7 @@ public class AsyncRestTemplateIntegrationTests extends AbstractJettyServerTestCa
assertEquals("Invalid content-type", textContentType, entity.getHeaders().getContentType());
assertEquals("Invalid status code", HttpStatus.OK, entity.getStatusCode());
}, ex -> fail(ex.getMessage()));
- // wait till done
- while (!futureEntity.isDone()) {
- }
+ waitTillDone(futureEntity);
}
@Test
@@ -160,8 +171,7 @@ public class AsyncRestTemplateIntegrationTests extends AbstractJettyServerTestCa
fail(ex.getMessage());
}
});
- while (!headersFuture.isDone()) {
- }
+ waitTillDone(headersFuture);
}
@Test
@@ -169,15 +179,14 @@ public class AsyncRestTemplateIntegrationTests extends AbstractJettyServerTestCa
ListenableFuture<HttpHeaders> headersFuture = template.headForHeaders(baseUrl + "/get");
headersFuture.addCallback(result -> assertTrue("No Content-Type header",
result.containsKey("Content-Type")), ex -> fail(ex.getMessage()));
- while (!headersFuture.isDone()) {
- }
+ waitTillDone(headersFuture);
}
@Test
public void postForLocation() throws Exception {
HttpHeaders entityHeaders = new HttpHeaders();
entityHeaders.setContentType(new MediaType("text", "plain", Charset.forName("ISO-8859-15")));
- HttpEntity<String> entity = new HttpEntity<String>(helloWorld, entityHeaders);
+ HttpEntity<String> entity = new HttpEntity<>(helloWorld, entityHeaders);
Future<URI> locationFuture = template.postForLocation(baseUrl + "/{method}", entity, "post");
URI location = locationFuture.get();
assertEquals("Invalid location", new URI(baseUrl + "/post/1"), location);
@@ -187,7 +196,7 @@ public class AsyncRestTemplateIntegrationTests extends AbstractJettyServerTestCa
public void postForLocationCallback() throws Exception {
HttpHeaders entityHeaders = new HttpHeaders();
entityHeaders.setContentType(new MediaType("text", "plain", Charset.forName("ISO-8859-15")));
- HttpEntity<String> entity = new HttpEntity<String>(helloWorld, entityHeaders);
+ HttpEntity<String> entity = new HttpEntity<>(helloWorld, entityHeaders);
final URI expected = new URI(baseUrl + "/post/1");
ListenableFuture<URI> locationFuture = template.postForLocation(baseUrl + "/{method}", entity, "post");
locationFuture.addCallback(new ListenableFutureCallback<URI>() {
@@ -200,21 +209,19 @@ public class AsyncRestTemplateIntegrationTests extends AbstractJettyServerTestCa
fail(ex.getMessage());
}
});
- while (!locationFuture.isDone()) {
- }
+ waitTillDone(locationFuture);
}
@Test
public void postForLocationCallbackWithLambdas() throws Exception {
HttpHeaders entityHeaders = new HttpHeaders();
entityHeaders.setContentType(new MediaType("text", "plain", Charset.forName("ISO-8859-15")));
- HttpEntity<String> entity = new HttpEntity<String>(helloWorld, entityHeaders);
+ HttpEntity<String> entity = new HttpEntity<>(helloWorld, entityHeaders);
final URI expected = new URI(baseUrl + "/post/1");
ListenableFuture<URI> locationFuture = template.postForLocation(baseUrl + "/{method}", entity, "post");
locationFuture.addCallback(result -> assertEquals("Invalid location", expected, result),
ex -> fail(ex.getMessage()));
- while (!locationFuture.isDone()) {
- }
+ waitTillDone(locationFuture);
}
@Test
@@ -241,8 +248,7 @@ public class AsyncRestTemplateIntegrationTests extends AbstractJettyServerTestCa
fail(ex.getMessage());
}
});
- while (!responseEntityFuture.isDone()) {
- }
+ waitTillDone(responseEntityFuture);
}
@Test
@@ -250,10 +256,10 @@ public class AsyncRestTemplateIntegrationTests extends AbstractJettyServerTestCa
HttpEntity<String> requestEntity = new HttpEntity<>(helloWorld);
ListenableFuture<ResponseEntity<String>> responseEntityFuture =
template.postForEntity(baseUrl + "/{method}", requestEntity, String.class, "post");
- responseEntityFuture.addCallback(result -> assertEquals("Invalid content", helloWorld, result.getBody()),
+ responseEntityFuture.addCallback(
+ result -> assertEquals("Invalid content", helloWorld, result.getBody()),
ex -> fail(ex.getMessage()));
- while (!responseEntityFuture.isDone()) {
- }
+ waitTillDone(responseEntityFuture);
}
@Test
@@ -277,8 +283,7 @@ public class AsyncRestTemplateIntegrationTests extends AbstractJettyServerTestCa
fail(ex.getMessage());
}
});
- while (!responseEntityFuture.isDone()) {
- }
+ waitTillDone(responseEntityFuture);
}
@Test
@@ -300,16 +305,14 @@ public class AsyncRestTemplateIntegrationTests extends AbstractJettyServerTestCa
fail(ex.getMessage());
}
});
- while (!deletedFuture.isDone()) {
- }
+ waitTillDone(deletedFuture);
}
@Test
public void deleteCallbackWithLambdas() throws Exception {
ListenableFuture<?> deletedFuture = template.delete(new URI(baseUrl + "/delete"));
- deletedFuture.addCallback(result -> assertNull(result), ex -> fail(ex.getMessage()));
- while (!deletedFuture.isDone()) {
- }
+ deletedFuture.addCallback(Assert::assertNull, ex -> fail(ex.getMessage()));
+ waitTillDone(deletedFuture);
}
@Test
@@ -377,8 +380,7 @@ public class AsyncRestTemplateIntegrationTests extends AbstractJettyServerTestCa
assertNotNull(ex.getResponseBodyAsString());
}
});
- while (!future.isDone()) {
- }
+ waitTillDone(future);
}
@Test
@@ -391,8 +393,7 @@ public class AsyncRestTemplateIntegrationTests extends AbstractJettyServerTestCa
assertNotNull(hcex.getStatusText());
assertNotNull(hcex.getResponseBodyAsString());
});
- while (!future.isDone()) {
- }
+ waitTillDone(future);
}
@Test
@@ -429,8 +430,7 @@ public class AsyncRestTemplateIntegrationTests extends AbstractJettyServerTestCa
assertNotNull(hsex.getResponseBodyAsString());
}
});
- while (!future.isDone()) {
- }
+ waitTillDone(future);
}
@Test
@@ -443,8 +443,7 @@ public class AsyncRestTemplateIntegrationTests extends AbstractJettyServerTestCa
assertNotNull(hsex.getStatusText());
assertNotNull(hsex.getResponseBodyAsString());
});
- while (!future.isDone()) {
- }
+ waitTillDone(future);
}
@Test
@@ -469,8 +468,7 @@ public class AsyncRestTemplateIntegrationTests extends AbstractJettyServerTestCa
fail(ex.getMessage());
}
});
- while (!allowedFuture.isDone()) {
- }
+ waitTillDone(allowedFuture);
}
@Test
@@ -479,8 +477,7 @@ public class AsyncRestTemplateIntegrationTests extends AbstractJettyServerTestCa
allowedFuture.addCallback(result -> assertEquals("Invalid response",
EnumSet.of(HttpMethod.GET, HttpMethod.OPTIONS, HttpMethod.HEAD,HttpMethod.TRACE), result),
ex -> fail(ex.getMessage()));
- while (!allowedFuture.isDone()) {
- }
+ waitTillDone(allowedFuture);
}
@Test
@@ -513,8 +510,7 @@ public class AsyncRestTemplateIntegrationTests extends AbstractJettyServerTestCa
fail(ex.getMessage());
}
});
- while (!responseFuture.isDone()) {
- }
+ waitTillDone(responseFuture);
}
@Test
@@ -527,8 +523,7 @@ public class AsyncRestTemplateIntegrationTests extends AbstractJettyServerTestCa
template.exchange(baseUrl + "/{method}", HttpMethod.GET, requestEntity, String.class, "get");
responseFuture.addCallback(result -> assertEquals("Invalid content", helloWorld,
result.getBody()), ex -> fail(ex.getMessage()));
- while (!responseFuture.isDone()) {
- }
+ waitTillDone(responseFuture);
}
@Test
@@ -536,7 +531,7 @@ public class AsyncRestTemplateIntegrationTests extends AbstractJettyServerTestCa
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("MyHeader", "MyValue");
requestHeaders.setContentType(MediaType.TEXT_PLAIN);
- HttpEntity<String> requestEntity = new HttpEntity<String>(helloWorld, requestHeaders);
+ HttpEntity<String> requestEntity = new HttpEntity<>(helloWorld, requestHeaders);
Future<ResponseEntity<Void>> resultFuture =
template.exchange(baseUrl + "/{method}", HttpMethod.POST, requestEntity, Void.class, "post");
ResponseEntity<Void> result = resultFuture.get();
@@ -550,7 +545,7 @@ public class AsyncRestTemplateIntegrationTests extends AbstractJettyServerTestCa
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("MyHeader", "MyValue");
requestHeaders.setContentType(MediaType.TEXT_PLAIN);
- HttpEntity<String> requestEntity = new HttpEntity<String>(helloWorld, requestHeaders);
+ HttpEntity<String> requestEntity = new HttpEntity<>(helloWorld, requestHeaders);
ListenableFuture<ResponseEntity<Void>> resultFuture =
template.exchange(baseUrl + "/{method}", HttpMethod.POST, requestEntity, Void.class, "post");
final URI expected =new URI(baseUrl + "/post/1");
@@ -565,8 +560,7 @@ public class AsyncRestTemplateIntegrationTests extends AbstractJettyServerTestCa
fail(ex.getMessage());
}
});
- while (!resultFuture.isDone()) {
- }
+ waitTillDone(resultFuture);
}
@Test
@@ -574,7 +568,7 @@ public class AsyncRestTemplateIntegrationTests extends AbstractJettyServerTestCa
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("MyHeader", "MyValue");
requestHeaders.setContentType(MediaType.TEXT_PLAIN);
- HttpEntity<String> requestEntity = new HttpEntity<String>(helloWorld, requestHeaders);
+ HttpEntity<String> requestEntity = new HttpEntity<>(helloWorld, requestHeaders);
ListenableFuture<ResponseEntity<Void>> resultFuture =
template.exchange(baseUrl + "/{method}", HttpMethod.POST, requestEntity, Void.class, "post");
final URI expected =new URI(baseUrl + "/post/1");
@@ -582,13 +576,12 @@ public class AsyncRestTemplateIntegrationTests extends AbstractJettyServerTestCa
assertEquals("Invalid location", expected, result.getHeaders().getLocation());
assertFalse(result.hasBody());
}, ex -> fail(ex.getMessage()));
- while (!resultFuture.isDone()) {
- }
+ waitTillDone(resultFuture);
}
@Test
public void multipart() throws Exception {
- MultiValueMap<String, Object> parts = new LinkedMultiValueMap<String, Object>();
+ MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
parts.add("name 1", "value 1");
parts.add("name 2", "value 2+1");
parts.add("name 2", "value 2+2");
@@ -600,4 +593,73 @@ public class AsyncRestTemplateIntegrationTests extends AbstractJettyServerTestCa
future.get();
}
+ @Test
+ public void getAndInterceptResponse() throws Exception {
+ RequestInterceptor interceptor = new RequestInterceptor();
+ template.setInterceptors(Collections.singletonList(interceptor));
+ ListenableFuture<ResponseEntity<String>> future = template.getForEntity("/get", String.class);
+
+ interceptor.latch.await(5, TimeUnit.SECONDS);
+ assertNotNull(interceptor.response);
+ assertEquals(HttpStatus.OK, interceptor.response.getStatusCode());
+ assertNull(interceptor.exception);
+ assertEquals(helloWorld, future.get().getBody());
+ }
+
+ @Test
+ public void getAndInterceptError() throws Exception {
+ RequestInterceptor interceptor = new RequestInterceptor();
+ template.setInterceptors(Collections.singletonList(interceptor));
+ template.getForEntity("/status/notfound", String.class);
+
+ interceptor.latch.await(5, TimeUnit.SECONDS);
+ assertNotNull(interceptor.response);
+ assertEquals(HttpStatus.NOT_FOUND, interceptor.response.getStatusCode());
+ assertNull(interceptor.exception);
+ }
+
+ private void waitTillDone(ListenableFuture<?> future) {
+ while (!future.isDone()) {
+ }
+ }
+
+
+ private static class RequestInterceptor implements AsyncClientHttpRequestInterceptor {
+
+ private final CountDownLatch latch = new CountDownLatch(1);
+
+ private volatile ClientHttpResponse response;
+
+ private volatile Throwable exception;
+
+ @Override
+ public ListenableFuture<ClientHttpResponse> intercept(HttpRequest request, byte[] body,
+ AsyncClientHttpRequestExecution execution) throws IOException {
+
+ request = new HttpRequestWrapper(request) {
+
+ @Override
+ public URI getURI() {
+ try {
+ return new URI(baseUrl + super.getURI().toString());
+ }
+ catch (URISyntaxException ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
+ };
+
+ ListenableFuture<ClientHttpResponse> future = execution.executeAsync(request, body);
+ future.addCallback(
+ resp -> {
+ response = resp;
+ this.latch.countDown();
+ },
+ ex -> {
+ exception = ex;
+ this.latch.countDown();
+ });
+ return future;
+ }
+ }
}
diff --git a/spring-web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java b/spring-web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java
index 149340b6..d5f00fc2 100644
--- a/spring-web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java
+++ b/spring-web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,11 +20,16 @@ import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
+import java.util.ArrayList;
import java.util.EnumSet;
+import java.util.List;
import java.util.Set;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.JsonTypeName;
import org.junit.Test;
+import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpEntity;
@@ -32,6 +37,7 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
+import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.json.MappingJacksonValue;
@@ -215,7 +221,7 @@ public class RestTemplateIntegrationTests extends AbstractJettyServerTestCase {
bean.setWith2("with");
bean.setWithout("without");
HttpEntity<MySampleBean> entity = new HttpEntity<MySampleBean>(bean, entityHeaders);
- String s = template.postForObject(baseUrl + "/jsonpost", entity, String.class, "post");
+ String s = template.postForObject(baseUrl + "/jsonpost", entity, String.class);
assertTrue(s.contains("\"with1\":\"with\""));
assertTrue(s.contains("\"with2\":\"with\""));
assertTrue(s.contains("\"without\":\"without\""));
@@ -229,7 +235,7 @@ public class RestTemplateIntegrationTests extends AbstractJettyServerTestCase {
MappingJacksonValue jacksonValue = new MappingJacksonValue(bean);
jacksonValue.setSerializationView(MyJacksonView1.class);
HttpEntity<MappingJacksonValue> entity = new HttpEntity<MappingJacksonValue>(jacksonValue, entityHeaders);
- String s = template.postForObject(baseUrl + "/jsonpost", entity, String.class, "post");
+ String s = template.postForObject(baseUrl + "/jsonpost", entity, String.class);
assertTrue(s.contains("\"with1\":\"with\""));
assertFalse(s.contains("\"with2\":\"with\""));
assertFalse(s.contains("\"without\":\"without\""));
@@ -243,6 +249,21 @@ public class RestTemplateIntegrationTests extends AbstractJettyServerTestCase {
assertEquals("Invalid content", helloWorld, s);
}
+ @Test // SPR-13154
+ public void jsonPostForObjectWithJacksonTypeInfoList() throws URISyntaxException {
+ List<ParentClass> list = new ArrayList<>();
+ list.add(new Foo("foo"));
+ list.add(new Bar("bar"));
+ ParameterizedTypeReference<?> typeReference = new ParameterizedTypeReference<List<ParentClass>>() {};
+ RequestEntity<List<ParentClass>> entity = RequestEntity
+ .post(new URI(baseUrl + "/jsonpost"))
+ .contentType(new MediaType("application", "json", Charset.forName("UTF-8")))
+ .body(list, typeReference.getType());
+ String content = template.exchange(entity, String.class).getBody();
+ assertTrue(content.contains("\"type\":\"foo\""));
+ assertTrue(content.contains("\"type\":\"bar\""));
+ }
+
public interface MyJacksonView1 {};
public interface MyJacksonView2 {};
@@ -290,4 +311,47 @@ public class RestTemplateIntegrationTests extends AbstractJettyServerTestCase {
}
}
+ @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
+ public static class ParentClass {
+
+ private String parentProperty;
+
+ public ParentClass() {
+ }
+
+ public ParentClass(String parentProperty) {
+ this.parentProperty = parentProperty;
+ }
+
+ public String getParentProperty() {
+ return parentProperty;
+ }
+
+ public void setParentProperty(String parentProperty) {
+ this.parentProperty = parentProperty;
+ }
+ }
+
+ @JsonTypeName("foo")
+ public static class Foo extends ParentClass {
+
+ public Foo() {
+ }
+
+ public Foo(String parentProperty) {
+ super(parentProperty);
+ }
+ }
+
+ @JsonTypeName("bar")
+ public static class Bar extends ParentClass {
+
+ public Bar() {
+ }
+
+ public Bar(String parentProperty) {
+ super(parentProperty);
+ }
+ }
+
}
diff --git a/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java b/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java
index 1ecfc48e..5674ad55 100644
--- a/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java
+++ b/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java
@@ -1,11 +1,11 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -49,6 +49,7 @@ import static org.mockito.BDDMockito.*;
/**
* @author Arjen Poutsma
+ * @author Rossen Stoyanchev
*/
@SuppressWarnings("unchecked")
public class RestTemplateTests {
@@ -135,7 +136,7 @@ public class RestTemplateTests {
given(response.getStatusCode()).willReturn(status);
given(response.getStatusText()).willReturn(status.getReasonPhrase());
- Map<String, String> vars = new HashMap<String, String>(2);
+ Map<String, String> vars = new HashMap<>(2);
vars.put("first", null);
vars.put("last", "foo");
template.execute("http://example.com/{first}-{last}", HttpMethod.GET, null, null, vars);
@@ -278,7 +279,7 @@ public class RestTemplateTests {
given(response.getHeaders()).willReturn(new HttpHeaders());
given(response.getBody()).willReturn(null);
- Map<String, String> uriVariables = new HashMap<String, String>(2);
+ Map<String, String> uriVariables = new HashMap<>(2);
uriVariables.put("hotel", "1");
uriVariables.put("publicpath", "pics/logo.png");
uriVariables.put("scale", "150x150");
@@ -351,7 +352,7 @@ public class RestTemplateTests {
HttpHeaders entityHeaders = new HttpHeaders();
entityHeaders.setContentType(contentType);
- HttpEntity<String> entity = new HttpEntity<String>(helloWorld, entityHeaders);
+ HttpEntity<String> entity = new HttpEntity<>(helloWorld, entityHeaders);
URI result = template.postForLocation("http://example.com", entity);
assertEquals("Invalid POST result", expected, result);
@@ -379,7 +380,7 @@ public class RestTemplateTests {
HttpHeaders entityHeaders = new HttpHeaders();
entityHeaders.set("MyHeader", "MyValue");
- HttpEntity<String> entity = new HttpEntity<String>(helloWorld, entityHeaders);
+ HttpEntity<String> entity = new HttpEntity<>(helloWorld, entityHeaders);
URI result = template.postForLocation("http://example.com", entity);
assertEquals("Invalid POST result", expected, result);
@@ -622,21 +623,27 @@ public class RestTemplateTests {
verify(response).close();
}
+ // Issue: SPR-9325, SPR-13860
+
@Test
public void ioException() throws Exception {
+ String url = "http://example.com/resource?access_token=123";
+
given(converter.canRead(String.class, null)).willReturn(true);
MediaType mediaType = new MediaType("foo", "bar");
given(converter.getSupportedMediaTypes()).willReturn(Collections.singletonList(mediaType));
- given(requestFactory.createRequest(new URI("http://example.com/resource"), HttpMethod.GET)).willReturn(request);
+ given(requestFactory.createRequest(new URI(url), HttpMethod.GET)).willReturn(request);
given(request.getHeaders()).willReturn(new HttpHeaders());
- given(request.execute()).willThrow(new IOException());
+ given(request.execute()).willThrow(new IOException("Socket failure"));
try {
- template.getForObject("http://example.com/resource", String.class);
+ template.getForObject(url, String.class);
fail("RestClientException expected");
}
catch (ResourceAccessException ex) {
- // expected
+ assertEquals("I/O error on GET request for \"http://example.com/resource\": " +
+ "Socket failure; nested exception is java.io.IOException: Socket failure",
+ ex.getMessage());
}
}
@@ -669,7 +676,7 @@ public class RestTemplateTests {
HttpHeaders entityHeaders = new HttpHeaders();
entityHeaders.set("MyHeader", "MyValue");
- HttpEntity<String> requestEntity = new HttpEntity<String>(body, entityHeaders);
+ HttpEntity<String> requestEntity = new HttpEntity<>(body, entityHeaders);
ResponseEntity<Integer> result = template.exchange("http://example.com", HttpMethod.POST, requestEntity, Integer.class);
assertEquals("Invalid POST result", expected, result.getBody());
assertEquals("Invalid Content-Type", MediaType.TEXT_PLAIN, result.getHeaders().getContentType());
@@ -692,9 +699,9 @@ public class RestTemplateTests {
given(requestFactory.createRequest(new URI("http://example.com"), HttpMethod.POST)).willReturn(this.request);
HttpHeaders requestHeaders = new HttpHeaders();
given(this.request.getHeaders()).willReturn(requestHeaders);
- given(converter.canWrite(String.class, null)).willReturn(true);
+ given(converter.canWrite(String.class, String.class, null)).willReturn(true);
String requestBody = "Hello World";
- converter.write(requestBody, null, this.request);
+ converter.write(requestBody, String.class, null, this.request);
given(this.request.execute()).willReturn(response);
given(errorHandler.hasError(response)).willReturn(false);
List<Integer> expected = Collections.singletonList(42);
@@ -703,7 +710,7 @@ public class RestTemplateTests {
responseHeaders.setContentLength(10);
given(response.getStatusCode()).willReturn(HttpStatus.OK);
given(response.getHeaders()).willReturn(responseHeaders);
- given(response.getBody()).willReturn(new ByteArrayInputStream(new Integer(42).toString().getBytes()));
+ given(response.getBody()).willReturn(new ByteArrayInputStream(Integer.toString(42).getBytes()));
given(converter.canRead(intList.getType(), null, MediaType.TEXT_PLAIN)).willReturn(true);
given(converter.read(eq(intList.getType()), eq(null), any(HttpInputMessage.class))).willReturn(expected);
given(response.getStatusCode()).willReturn(HttpStatus.OK);
@@ -713,7 +720,7 @@ public class RestTemplateTests {
HttpHeaders entityHeaders = new HttpHeaders();
entityHeaders.set("MyHeader", "MyValue");
- HttpEntity<String> requestEntity = new HttpEntity<String>(requestBody, entityHeaders);
+ HttpEntity<String> requestEntity = new HttpEntity<>(requestBody, entityHeaders);
ResponseEntity<List<Integer>> result = template.exchange("http://example.com", HttpMethod.POST, requestEntity, intList);
assertEquals("Invalid POST result", expected, result.getBody());
assertEquals("Invalid Content-Type", MediaType.TEXT_PLAIN, result.getHeaders().getContentType());
diff --git a/spring-web/src/test/java/org/springframework/web/context/request/ServletRequestAttributesTests.java b/spring-web/src/test/java/org/springframework/web/context/request/ServletRequestAttributesTests.java
index ef1afaad..603e2642 100644
--- a/spring-web/src/test/java/org/springframework/web/context/request/ServletRequestAttributesTests.java
+++ b/spring-web/src/test/java/org/springframework/web/context/request/ServletRequestAttributesTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -78,8 +78,7 @@ public class ServletRequestAttributesTests {
request.setSession(session);
ServletRequestAttributes attrs = new ServletRequestAttributes(request);
attrs.setAttribute(KEY, VALUE, RequestAttributes.SCOPE_SESSION);
- Object value = session.getAttribute(KEY);
- assertSame(VALUE, value);
+ assertSame(VALUE, session.getAttribute(KEY));
}
@Test
@@ -89,11 +88,11 @@ public class ServletRequestAttributesTests {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setSession(session);
ServletRequestAttributes attrs = new ServletRequestAttributes(request);
+ assertSame(VALUE, attrs.getAttribute(KEY, RequestAttributes.SCOPE_SESSION));
attrs.requestCompleted();
request.close();
attrs.setAttribute(KEY, VALUE, RequestAttributes.SCOPE_SESSION);
- Object value = session.getAttribute(KEY);
- assertSame(VALUE, value);
+ assertSame(VALUE, session.getAttribute(KEY));
}
@Test
@@ -104,8 +103,7 @@ public class ServletRequestAttributesTests {
request.setSession(session);
ServletRequestAttributes attrs = new ServletRequestAttributes(request);
attrs.setAttribute(KEY, VALUE, RequestAttributes.SCOPE_GLOBAL_SESSION);
- Object value = session.getAttribute(KEY);
- assertSame(VALUE, value);
+ assertSame(VALUE, session.getAttribute(KEY));
}
@Test
@@ -115,11 +113,11 @@ public class ServletRequestAttributesTests {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setSession(session);
ServletRequestAttributes attrs = new ServletRequestAttributes(request);
+ assertSame(VALUE, attrs.getAttribute(KEY, RequestAttributes.SCOPE_GLOBAL_SESSION));
attrs.requestCompleted();
request.close();
attrs.setAttribute(KEY, VALUE, RequestAttributes.SCOPE_GLOBAL_SESSION);
- Object value = session.getAttribute(KEY);
- assertSame(VALUE, value);
+ assertSame(VALUE, session.getAttribute(KEY));
}
@Test
diff --git a/spring-web/src/test/java/org/springframework/web/context/request/ServletWebRequestHttpMethodsTests.java b/spring-web/src/test/java/org/springframework/web/context/request/ServletWebRequestHttpMethodsTests.java
index 3832ae3a..a5a8b07c 100644
--- a/spring-web/src/test/java/org/springframework/web/context/request/ServletWebRequestHttpMethodsTests.java
+++ b/spring-web/src/test/java/org/springframework/web/context/request/ServletWebRequestHttpMethodsTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
package org.springframework.web.context.request;
+import static org.junit.Assert.*;
+
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
@@ -32,8 +34,6 @@ import org.junit.runners.Parameterized.Parameters;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse;
-import static org.junit.Assert.*;
-
/**
* Parameterized tests for ServletWebRequest
* @author Juergen Hoeller
@@ -145,6 +145,18 @@ public class ServletWebRequestHttpMethodsTests {
}
@Test
+ public void checkNotModifiedETagWithSeparatorChars() {
+ String eTag = "\"Foo, Bar\"";
+ servletRequest.addHeader("If-None-Match", eTag);
+
+ assertTrue(request.checkNotModified(eTag));
+
+ assertEquals(304, servletResponse.getStatus());
+ assertEquals(eTag, servletResponse.getHeader("ETag"));
+ }
+
+
+ @Test
public void checkModifiedETag() {
String currentETag = "\"Foo\"";
String oldEtag = "Bar";
@@ -204,6 +216,7 @@ public class ServletWebRequestHttpMethodsTests {
assertEquals(dateFormat.format(currentDate.getTime()), servletResponse.getHeader("Last-Modified"));
}
+ // SPR-14224
@Test
public void checkNotModifiedETagAndModifiedTimestamp() {
String eTag = "\"Foo\"";
@@ -212,9 +225,9 @@ public class ServletWebRequestHttpMethodsTests {
long oneMinuteAgo = currentEpoch - (1000 * 60);
servletRequest.addHeader("If-Modified-Since", oneMinuteAgo);
- assertFalse(request.checkNotModified(eTag, currentEpoch));
+ assertTrue(request.checkNotModified(eTag, currentEpoch));
- assertEquals(200, servletResponse.getStatus());
+ assertEquals(304, servletResponse.getStatus());
assertEquals(eTag, servletResponse.getHeader("ETag"));
assertEquals(dateFormat.format(currentEpoch), servletResponse.getHeader("Last-Modified"));
}
@@ -293,4 +306,28 @@ public class ServletWebRequestHttpMethodsTests {
assertEquals(dateFormat.format(epochTime), servletResponse.getHeader("Last-Modified"));
}
+ @Test
+ public void checkNotModifiedTimestampConditionalPut() throws Exception {
+ long currentEpoch = currentDate.getTime();
+ long oneMinuteAgo = currentEpoch - (1000 * 60);
+ servletRequest.setMethod("PUT");
+ servletRequest.addHeader("If-UnModified-Since", currentEpoch);
+
+ assertFalse(request.checkNotModified(oneMinuteAgo));
+ assertEquals(200, servletResponse.getStatus());
+ assertEquals(null, servletResponse.getHeader("Last-Modified"));
+ }
+
+ @Test
+ public void checkNotModifiedTimestampConditionalPutConflict() throws Exception {
+ long currentEpoch = currentDate.getTime();
+ long oneMinuteAgo = currentEpoch - (1000 * 60);
+ servletRequest.setMethod("PUT");
+ servletRequest.addHeader("If-UnModified-Since", oneMinuteAgo);
+
+ assertTrue(request.checkNotModified(currentEpoch));
+ assertEquals(412, servletResponse.getStatus());
+ assertEquals(null, servletResponse.getHeader("Last-Modified"));
+ }
+
}
diff --git a/spring-web/src/test/java/org/springframework/web/context/request/SessionScopeTests.java b/spring-web/src/test/java/org/springframework/web/context/request/SessionScopeTests.java
index ab198238..a44318fc 100644
--- a/spring-web/src/test/java/org/springframework/web/context/request/SessionScopeTests.java
+++ b/spring-web/src/test/java/org/springframework/web/context/request/SessionScopeTests.java
@@ -172,6 +172,11 @@ public class SessionScopeTests {
@Override
public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
}
+
+ @Override
+ public boolean requiresDestruction(Object bean) {
+ return true;
+ }
}
@@ -195,6 +200,11 @@ public class SessionScopeTests {
((BeanNameAware) bean).setBeanName(null);
}
}
+
+ @Override
+ public boolean requiresDestruction(Object bean) {
+ return true;
+ }
}
}
diff --git a/spring-web/src/test/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequestTests.java b/spring-web/src/test/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequestTests.java
index 50d84230..2596796e 100644
--- a/spring-web/src/test/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequestTests.java
+++ b/spring-web/src/test/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequestTests.java
@@ -147,7 +147,7 @@ public class StandardServletAsyncWebRequestTests {
}
// SPR-13292
-
+
@Test
public void onCompletionHandlerAfterOnErrorEvent() throws Exception {
Runnable handler = mock(Runnable.class);
diff --git a/spring-web/src/test/java/org/springframework/web/context/request/async/WebAsyncManagerTests.java b/spring-web/src/test/java/org/springframework/web/context/request/async/WebAsyncManagerTests.java
index c763eeec..d4a2ad47 100644
--- a/spring-web/src/test/java/org/springframework/web/context/request/async/WebAsyncManagerTests.java
+++ b/spring-web/src/test/java/org/springframework/web/context/request/async/WebAsyncManagerTests.java
@@ -150,8 +150,9 @@ public class WebAsyncManagerTests {
try {
this.asyncManager.startCallableProcessing(task);
fail("Expected Exception");
- }catch(Exception e) {
- assertEquals(exception, e);
+ }
+ catch (Exception ex) {
+ assertEquals(exception, ex);
}
assertFalse(this.asyncManager.hasConcurrentResult());
@@ -162,7 +163,6 @@ public class WebAsyncManagerTests {
@Test
public void startCallableProcessingPreProcessException() throws Exception {
-
Callable<Object> task = new StubCallable(21);
Exception exception = new Exception();
@@ -183,7 +183,6 @@ public class WebAsyncManagerTests {
@Test
public void startCallableProcessingPostProcessException() throws Exception {
-
Callable<Object> task = new StubCallable(21);
Exception exception = new Exception();
@@ -205,7 +204,6 @@ public class WebAsyncManagerTests {
@Test
public void startCallableProcessingPostProcessContinueAfterException() throws Exception {
-
Callable<Object> task = new StubCallable(21);
Exception exception = new Exception();
@@ -231,7 +229,6 @@ public class WebAsyncManagerTests {
@Test
public void startCallableProcessingWithAsyncTask() throws Exception {
-
AsyncTaskExecutor executor = mock(AsyncTaskExecutor.class);
given(this.asyncWebRequest.getNativeRequest(HttpServletRequest.class)).willReturn(this.servletRequest);
@@ -259,7 +256,6 @@ public class WebAsyncManagerTests {
@Test
public void startDeferredResultProcessing() throws Exception {
-
DeferredResult<String> deferredResult = new DeferredResult<String>(1000L);
String concurrentResult = "abc";
@@ -282,7 +278,6 @@ public class WebAsyncManagerTests {
@Test
public void startDeferredResultProcessingBeforeConcurrentHandlingException() throws Exception {
-
DeferredResult<Integer> deferredResult = new DeferredResult<Integer>();
Exception exception = new Exception();
@@ -295,7 +290,7 @@ public class WebAsyncManagerTests {
this.asyncManager.startDeferredResultProcessing(deferredResult);
fail("Expected Exception");
}
- catch(Exception success) {
+ catch (Exception success) {
assertEquals(exception, success);
}
@@ -328,7 +323,6 @@ public class WebAsyncManagerTests {
@Test
public void startDeferredResultProcessingPostProcessException() throws Exception {
-
DeferredResult<Integer> deferredResult = new DeferredResult<Integer>();
Exception exception = new Exception();
@@ -371,6 +365,7 @@ public class WebAsyncManagerTests {
verify(this.asyncWebRequest).dispatch();
}
+
private final class StubCallable implements Callable<Object> {
private Object value;
@@ -388,6 +383,7 @@ public class WebAsyncManagerTests {
}
}
+
@SuppressWarnings("serial")
private static class SyncTaskExecutor extends SimpleAsyncTaskExecutor {
diff --git a/spring-web/src/test/java/org/springframework/web/context/support/Spr8510Tests.java b/spring-web/src/test/java/org/springframework/web/context/support/Spr8510Tests.java
index a712e525..dd56ff00 100644
--- a/spring-web/src/test/java/org/springframework/web/context/support/Spr8510Tests.java
+++ b/spring-web/src/test/java/org/springframework/web/context/support/Spr8510Tests.java
@@ -49,7 +49,8 @@ public class Spr8510Tests {
try {
cll.contextInitialized(new ServletContextEvent(sc));
fail("expected exception");
- } catch (Throwable t) {
+ }
+ catch (Throwable t) {
// assert that an attempt was made to load the correct XML
assertTrue(t.getMessage(), t.getMessage().endsWith(
"Could not open ServletContext resource [/programmatic.xml]"));
@@ -75,7 +76,8 @@ public class Spr8510Tests {
try {
cll.contextInitialized(new ServletContextEvent(sc));
fail("expected exception");
- } catch (Throwable t) {
+ }
+ catch (Throwable t) {
// assert that an attempt was made to load the correct XML
assertTrue(t.getMessage(), t.getMessage().endsWith(
"Could not open ServletContext resource [/from-init-param.xml]"));
@@ -98,7 +100,8 @@ public class Spr8510Tests {
try {
cll.contextInitialized(new ServletContextEvent(sc));
fail("expected exception");
- } catch (Throwable t) {
+ }
+ catch (Throwable t) {
// assert that an attempt was made to load the correct XML
assertTrue(t.getMessage().endsWith(
"Could not open ServletContext resource [/from-init-param.xml]"));
@@ -125,7 +128,8 @@ public class Spr8510Tests {
try {
cll.contextInitialized(new ServletContextEvent(sc));
fail("expected exception");
- } catch (Throwable t) {
+ }
+ catch (Throwable t) {
// assert that an attempt was made to load the correct XML
System.out.println(t.getMessage());
assertTrue(t.getMessage().endsWith(
@@ -150,7 +154,8 @@ public class Spr8510Tests {
try {
cll.contextInitialized(new ServletContextEvent(sc));
fail("expected exception");
- } catch (Throwable t) {
+ }
+ catch (Throwable t) {
// assert that an attempt was made to load the correct XML
System.out.println(t.getMessage());
assertTrue(t.getMessage().endsWith(
diff --git a/spring-web/src/test/java/org/springframework/web/cors/CorsConfigurationTests.java b/spring-web/src/test/java/org/springframework/web/cors/CorsConfigurationTests.java
index 8d3651c5..32f68af3 100644
--- a/spring-web/src/test/java/org/springframework/web/cors/CorsConfigurationTests.java
+++ b/spring-web/src/test/java/org/springframework/web/cors/CorsConfigurationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -176,7 +176,7 @@ public class CorsConfigurationTests {
@Test
public void checkMethodAllowed() {
- assertEquals(Arrays.asList(HttpMethod.GET), config.checkHttpMethod(HttpMethod.GET));
+ assertEquals(Arrays.asList(HttpMethod.GET, HttpMethod.HEAD), config.checkHttpMethod(HttpMethod.GET));
config.addAllowedMethod("GET");
assertEquals(Arrays.asList(HttpMethod.GET), config.checkHttpMethod(HttpMethod.GET));
config.addAllowedMethod("POST");
@@ -189,7 +189,7 @@ public class CorsConfigurationTests {
assertNull(config.checkHttpMethod(null));
assertNull(config.checkHttpMethod(HttpMethod.DELETE));
config.setAllowedMethods(new ArrayList<>());
- assertNull(config.checkHttpMethod(HttpMethod.HEAD));
+ assertNull(config.checkHttpMethod(HttpMethod.POST));
}
@Test
diff --git a/spring-web/src/test/java/org/springframework/web/cors/DefaultCorsProcessorTests.java b/spring-web/src/test/java/org/springframework/web/cors/DefaultCorsProcessorTests.java
index 56ab6166..30a93e30 100644
--- a/spring-web/src/test/java/org/springframework/web/cors/DefaultCorsProcessorTests.java
+++ b/spring-web/src/test/java/org/springframework/web/cors/DefaultCorsProcessorTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -171,7 +171,7 @@ public class DefaultCorsProcessorTests {
this.conf.addAllowedOrigin("*");
this.processor.processRequest(this.conf, request, response);
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
- assertEquals("GET", response.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS));
+ assertEquals("GET,HEAD", response.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS));
}
@Test
diff --git a/spring-web/src/test/java/org/springframework/web/filter/CharacterEncodingFilterTests.java b/spring-web/src/test/java/org/springframework/web/filter/CharacterEncodingFilterTests.java
index 64392f91..5f0006c9 100644
--- a/spring-web/src/test/java/org/springframework/web/filter/CharacterEncodingFilterTests.java
+++ b/spring-web/src/test/java/org/springframework/web/filter/CharacterEncodingFilterTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -145,4 +145,26 @@ public class CharacterEncodingFilterTests {
verify(filterChain).doFilter(request, response);
}
+ // SPR-14240
+ @Test
+ public void setForceEncodingOnRequestOnly() throws Exception {
+ HttpServletRequest request = mock(HttpServletRequest.class);
+ request.setCharacterEncoding(ENCODING);
+ given(request.getAttribute(WebUtils.ERROR_REQUEST_URI_ATTRIBUTE)).willReturn(null);
+ given(request.getAttribute(FILTER_NAME + OncePerRequestFilter.ALREADY_FILTERED_SUFFIX)).willReturn(null);
+
+ HttpServletResponse response = mock(HttpServletResponse.class);
+ FilterChain filterChain = mock(FilterChain.class);
+
+ CharacterEncodingFilter filter = new CharacterEncodingFilter(ENCODING, true, false);
+ filter.init(new MockFilterConfig(FILTER_NAME));
+ filter.doFilter(request, response, filterChain);
+
+ verify(request).setAttribute(FILTER_NAME + OncePerRequestFilter.ALREADY_FILTERED_SUFFIX, Boolean.TRUE);
+ verify(request).removeAttribute(FILTER_NAME + OncePerRequestFilter.ALREADY_FILTERED_SUFFIX);
+ verify(request, times(2)).setCharacterEncoding(ENCODING);
+ verify(response, never()).setCharacterEncoding(ENCODING);
+ verify(filterChain).doFilter(request, response);
+ }
+
}
diff --git a/spring-web/src/test/java/org/springframework/web/filter/ForwardedHeaderFilterTests.java b/spring-web/src/test/java/org/springframework/web/filter/ForwardedHeaderFilterTests.java
new file mode 100644
index 00000000..56d93f8c
--- /dev/null
+++ b/spring-web/src/test/java/org/springframework/web/filter/ForwardedHeaderFilterTests.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.web.filter;
+
+import java.io.IOException;
+import java.util.Enumeration;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.springframework.mock.web.test.MockFilterChain;
+import org.springframework.mock.web.test.MockHttpServletRequest;
+import org.springframework.mock.web.test.MockHttpServletResponse;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Unit tests for {@link ForwardedHeaderFilter}.
+ * @author Rossen Stoyanchev
+ * @author Eddú Meléndez
+ */
+public class ForwardedHeaderFilterTests {
+
+ private static final String X_FORWARDED_PROTO = "x-forwarded-proto"; // SPR-14372 (case insensitive)
+ private static final String X_FORWARDED_HOST = "x-forwarded-host";
+ private static final String X_FORWARDED_PORT = "x-forwarded-port";
+ private static final String X_FORWARDED_PREFIX = "x-forwarded-prefix";
+
+
+ private final ForwardedHeaderFilter filter = new ForwardedHeaderFilter();
+
+ private MockHttpServletRequest request;
+
+ private MockFilterChain filterChain;
+
+
+ @Before
+ @SuppressWarnings("serial")
+ public void setUp() throws Exception {
+ this.request = new MockHttpServletRequest();
+ this.request.setScheme("http");
+ this.request.setServerName("localhost");
+ this.request.setServerPort(80);
+ this.filterChain = new MockFilterChain(new HttpServlet() {});
+ }
+
+
+ @Test
+ public void contextPathEmpty() throws Exception {
+ this.request.addHeader(X_FORWARDED_PREFIX, "");
+ assertEquals("", filterAndGetContextPath());
+ }
+
+ @Test
+ public void contextPathWithTrailingSlash() throws Exception {
+ this.request.addHeader(X_FORWARDED_PREFIX, "/foo/bar/");
+ assertEquals("/foo/bar", filterAndGetContextPath());
+ }
+
+ @Test
+ public void contextPathWithTrailingSlashes() throws Exception {
+ this.request.addHeader(X_FORWARDED_PREFIX, "/foo/bar/baz///");
+ assertEquals("/foo/bar/baz", filterAndGetContextPath());
+ }
+
+ @Test
+ public void requestUri() throws Exception {
+ this.request.addHeader(X_FORWARDED_PREFIX, "/");
+ this.request.setContextPath("/app");
+ this.request.setRequestURI("/app/path");
+ HttpServletRequest actual = filterAndGetWrappedRequest();
+
+ assertEquals("", actual.getContextPath());
+ assertEquals("/path", actual.getRequestURI());
+ }
+
+ @Test
+ public void requestUriWithTrailingSlash() throws Exception {
+ this.request.addHeader(X_FORWARDED_PREFIX, "/");
+ this.request.setContextPath("/app");
+ this.request.setRequestURI("/app/path/");
+ HttpServletRequest actual = filterAndGetWrappedRequest();
+
+ assertEquals("", actual.getContextPath());
+ assertEquals("/path/", actual.getRequestURI());
+ }
+ @Test
+ public void requestUriEqualsContextPath() throws Exception {
+ this.request.addHeader(X_FORWARDED_PREFIX, "/");
+ this.request.setContextPath("/app");
+ this.request.setRequestURI("/app");
+ HttpServletRequest actual = filterAndGetWrappedRequest();
+
+ assertEquals("", actual.getContextPath());
+ assertEquals("/", actual.getRequestURI());
+ }
+
+ @Test
+ public void requestUriRootUrl() throws Exception {
+ this.request.addHeader(X_FORWARDED_PREFIX, "/");
+ this.request.setContextPath("/app");
+ this.request.setRequestURI("/app/");
+ HttpServletRequest actual = filterAndGetWrappedRequest();
+
+ assertEquals("", actual.getContextPath());
+ assertEquals("/", actual.getRequestURI());
+ }
+
+ @Test
+ public void caseInsensitiveForwardedPrefix() throws Exception {
+ this.request = new MockHttpServletRequest() {
+
+ // Make it case-sensitive (SPR-14372)
+
+ @Override
+ public String getHeader(String header) {
+ Enumeration<String> names = getHeaderNames();
+ while (names.hasMoreElements()) {
+ String name = names.nextElement();
+ if (name.equals(header)) {
+ return super.getHeader(header);
+ }
+ }
+ return null;
+ }
+ };
+ this.request.addHeader(X_FORWARDED_PREFIX, "/prefix");
+ this.request.setRequestURI("/path");
+ HttpServletRequest actual = filterAndGetWrappedRequest();
+
+ assertEquals("/prefix/path", actual.getRequestURI());
+ }
+
+ @Test
+ public void shouldFilter() throws Exception {
+ testShouldFilter("Forwarded");
+ testShouldFilter(X_FORWARDED_HOST);
+ testShouldFilter(X_FORWARDED_PORT);
+ testShouldFilter(X_FORWARDED_PROTO);
+ }
+
+ @Test
+ public void shouldNotFilter() throws Exception {
+ assertTrue(this.filter.shouldNotFilter(new MockHttpServletRequest()));
+ }
+
+ @Test
+ public void forwardedRequest() throws Exception {
+ this.request.setRequestURI("/mvc-showcase");
+ this.request.addHeader(X_FORWARDED_PROTO, "https");
+ this.request.addHeader(X_FORWARDED_HOST, "84.198.58.199");
+ this.request.addHeader(X_FORWARDED_PORT, "443");
+ this.request.addHeader("foo", "bar");
+
+ this.filter.doFilter(this.request, new MockHttpServletResponse(), this.filterChain);
+ HttpServletRequest actual = (HttpServletRequest) this.filterChain.getRequest();
+
+ assertEquals("https://84.198.58.199/mvc-showcase", actual.getRequestURL().toString());
+ assertEquals("https", actual.getScheme());
+ assertEquals("84.198.58.199", actual.getServerName());
+ assertEquals(443, actual.getServerPort());
+ assertTrue(actual.isSecure());
+
+ assertNull(actual.getHeader(X_FORWARDED_PROTO));
+ assertNull(actual.getHeader(X_FORWARDED_HOST));
+ assertNull(actual.getHeader(X_FORWARDED_PORT));
+ assertEquals("bar", actual.getHeader("foo"));
+ }
+
+ @Test
+ public void requestUriWithForwardedPrefix() throws Exception {
+ this.request.addHeader(X_FORWARDED_PREFIX, "/prefix");
+ this.request.setRequestURI("/mvc-showcase");
+
+ HttpServletRequest actual = filterAndGetWrappedRequest();
+ assertEquals("http://localhost/prefix/mvc-showcase", actual.getRequestURL().toString());
+ }
+
+ @Test
+ public void requestUriWithForwardedPrefixTrailingSlash() throws Exception {
+ this.request.addHeader(X_FORWARDED_PREFIX, "/prefix/");
+ this.request.setRequestURI("/mvc-showcase");
+
+ HttpServletRequest actual = filterAndGetWrappedRequest();
+ assertEquals("http://localhost/prefix/mvc-showcase", actual.getRequestURL().toString());
+ }
+
+ @Test
+ public void contextPathWithForwardedPrefix() throws Exception {
+ this.request.addHeader(X_FORWARDED_PREFIX, "/prefix");
+ this.request.setContextPath("/mvc-showcase");
+
+ String actual = filterAndGetContextPath();
+ assertEquals("/prefix", actual);
+ }
+
+ @Test
+ public void contextPathWithForwardedPrefixTrailingSlash() throws Exception {
+ this.request.addHeader(X_FORWARDED_PREFIX, "/prefix/");
+ this.request.setContextPath("/mvc-showcase");
+
+ String actual = filterAndGetContextPath();
+ assertEquals("/prefix", actual);
+ }
+
+ private String filterAndGetContextPath() throws ServletException, IOException {
+ return filterAndGetWrappedRequest().getContextPath();
+ }
+
+ private HttpServletRequest filterAndGetWrappedRequest() throws ServletException, IOException {
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ this.filter.doFilterInternal(this.request, response, this.filterChain);
+ return (HttpServletRequest) this.filterChain.getRequest();
+ }
+
+ private void testShouldFilter(String headerName) throws ServletException {
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.addHeader(headerName, "1");
+ assertFalse(this.filter.shouldNotFilter(request));
+ }
+
+}
diff --git a/spring-web/src/test/java/org/springframework/web/filter/ShallowEtagHeaderFilterTests.java b/spring-web/src/test/java/org/springframework/web/filter/ShallowEtagHeaderFilterTests.java
index 0c5af485..893501b2 100644
--- a/spring-web/src/test/java/org/springframework/web/filter/ShallowEtagHeaderFilterTests.java
+++ b/spring-web/src/test/java/org/springframework/web/filter/ShallowEtagHeaderFilterTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -74,6 +74,26 @@ public class ShallowEtagHeaderFilterTests {
}
@Test
+ public void filterNoMatchWeakETag() throws Exception {
+ this.filter.setWriteWeakETag(true);
+ final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");
+ MockHttpServletResponse response = new MockHttpServletResponse();
+
+ final byte[] responseBody = "Hello World".getBytes("UTF-8");
+ FilterChain filterChain = (filterRequest, filterResponse) -> {
+ assertEquals("Invalid request passed", request, filterRequest);
+ ((HttpServletResponse) filterResponse).setStatus(HttpServletResponse.SC_OK);
+ FileCopyUtils.copy(responseBody, filterResponse.getOutputStream());
+ };
+ filter.doFilter(request, response, filterChain);
+
+ assertEquals("Invalid status", 200, response.getStatus());
+ assertEquals("Invalid ETag header", "W/\"0b10a8db164e0754105b7a99be72e3fe5\"", response.getHeader("ETag"));
+ assertTrue("Invalid Content-Length header", response.getContentLength() > 0);
+ assertArrayEquals("Invalid content", responseBody, response.getContentAsByteArray());
+ }
+
+ @Test
public void filterMatch() throws Exception {
final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");
String etag = "\"0b10a8db164e0754105b7a99be72e3fe5\"";
@@ -95,6 +115,27 @@ public class ShallowEtagHeaderFilterTests {
}
@Test
+ public void filterMatchWeakEtag() throws Exception {
+ final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");
+ String etag = "\"0b10a8db164e0754105b7a99be72e3fe5\"";
+ request.addHeader("If-None-Match", "W/" + etag);
+ MockHttpServletResponse response = new MockHttpServletResponse();
+
+ FilterChain filterChain = (filterRequest, filterResponse) -> {
+ assertEquals("Invalid request passed", request, filterRequest);
+ byte[] responseBody = "Hello World".getBytes("UTF-8");
+ FileCopyUtils.copy(responseBody, filterResponse.getOutputStream());
+ filterResponse.setContentLength(responseBody.length);
+ };
+ filter.doFilter(request, response, filterChain);
+
+ assertEquals("Invalid status", 304, response.getStatus());
+ assertEquals("Invalid ETag header", "\"0b10a8db164e0754105b7a99be72e3fe5\"", response.getHeader("ETag"));
+ assertFalse("Response has Content-Length header", response.containsHeader("Content-Length"));
+ assertArrayEquals("Invalid content", new byte[0], response.getContentAsByteArray());
+ }
+
+ @Test
public void filterWriter() throws Exception {
final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");
String etag = "\"0b10a8db164e0754105b7a99be72e3fe5\"";
diff --git a/spring-web/src/test/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessorTests.java b/spring-web/src/test/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessorTests.java
index 21fc00e8..2010ba9d 100644
--- a/spring-web/src/test/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessorTests.java
+++ b/spring-web/src/test/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessorTests.java
@@ -24,6 +24,7 @@ import org.junit.Before;
import org.junit.Test;
import org.springframework.core.MethodParameter;
+import org.springframework.core.annotation.SynthesizingMethodParameter;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.tests.sample.beans.TestBean;
import org.springframework.validation.BindException;
@@ -51,56 +52,56 @@ import static org.mockito.BDDMockito.*;
*/
public class ModelAttributeMethodProcessorTests {
+ private NativeWebRequest request;
+
+ private ModelAndViewContainer container;
+
private ModelAttributeMethodProcessor processor;
private MethodParameter paramNamedValidModelAttr;
-
private MethodParameter paramErrors;
-
private MethodParameter paramInt;
-
private MethodParameter paramModelAttr;
-
+ private MethodParameter paramBindingDisabledAttr;
private MethodParameter paramNonSimpleType;
private MethodParameter returnParamNamedModelAttr;
-
private MethodParameter returnParamNonSimpleType;
- private ModelAndViewContainer mavContainer;
-
- private NativeWebRequest webRequest;
@Before
public void setUp() throws Exception {
- processor = new ModelAttributeMethodProcessor(false);
+ this.request = new ServletWebRequest(new MockHttpServletRequest());
+ this.container = new ModelAndViewContainer();
+ this.processor = new ModelAttributeMethodProcessor(false);
Method method = ModelAttributeHandler.class.getDeclaredMethod("modelAttribute",
- TestBean.class, Errors.class, int.class, TestBean.class, TestBean.class);
+ TestBean.class, Errors.class, int.class, TestBean.class,
+ TestBean.class, TestBean.class);
- paramNamedValidModelAttr = new MethodParameter(method, 0);
- paramErrors = new MethodParameter(method, 1);
- paramInt = new MethodParameter(method, 2);
- paramModelAttr = new MethodParameter(method, 3);
- paramNonSimpleType = new MethodParameter(method, 4);
+ this.paramNamedValidModelAttr = new SynthesizingMethodParameter(method, 0);
+ this.paramErrors = new SynthesizingMethodParameter(method, 1);
+ this.paramInt = new SynthesizingMethodParameter(method, 2);
+ this.paramModelAttr = new SynthesizingMethodParameter(method, 3);
+ this.paramBindingDisabledAttr = new SynthesizingMethodParameter(method, 4);
+ this.paramNonSimpleType = new SynthesizingMethodParameter(method, 5);
- returnParamNamedModelAttr = new MethodParameter(getClass().getDeclaredMethod("annotatedReturnValue"), -1);
- returnParamNonSimpleType = new MethodParameter(getClass().getDeclaredMethod("notAnnotatedReturnValue"), -1);
+ method = getClass().getDeclaredMethod("annotatedReturnValue");
+ this.returnParamNamedModelAttr = new MethodParameter(method, -1);
- mavContainer = new ModelAndViewContainer();
-
- webRequest = new ServletWebRequest(new MockHttpServletRequest());
+ method = getClass().getDeclaredMethod("notAnnotatedReturnValue");
+ this.returnParamNonSimpleType = new MethodParameter(method, -1);
}
+
@Test
public void supportedParameters() throws Exception {
- // Only @ModelAttribute arguments
- assertTrue(processor.supportsParameter(paramNamedValidModelAttr));
- assertTrue(processor.supportsParameter(paramModelAttr));
+ assertTrue(this.processor.supportsParameter(this.paramNamedValidModelAttr));
+ assertTrue(this.processor.supportsParameter(this.paramModelAttr));
- assertFalse(processor.supportsParameter(paramErrors));
- assertFalse(processor.supportsParameter(paramInt));
- assertFalse(processor.supportsParameter(paramNonSimpleType));
+ assertFalse(this.processor.supportsParameter(this.paramErrors));
+ assertFalse(this.processor.supportsParameter(this.paramInt));
+ assertFalse(this.processor.supportsParameter(this.paramNonSimpleType));
}
@Test
@@ -108,135 +109,162 @@ public class ModelAttributeMethodProcessorTests {
processor = new ModelAttributeMethodProcessor(true);
// Only non-simple types, even if not annotated
- assertTrue(processor.supportsParameter(paramNamedValidModelAttr));
- assertTrue(processor.supportsParameter(paramErrors));
- assertTrue(processor.supportsParameter(paramModelAttr));
- assertTrue(processor.supportsParameter(paramNonSimpleType));
+ assertTrue(this.processor.supportsParameter(this.paramNamedValidModelAttr));
+ assertTrue(this.processor.supportsParameter(this.paramErrors));
+ assertTrue(this.processor.supportsParameter(this.paramModelAttr));
+ assertTrue(this.processor.supportsParameter(this.paramNonSimpleType));
- assertFalse(processor.supportsParameter(paramInt));
+ assertFalse(this.processor.supportsParameter(this.paramInt));
}
@Test
public void supportedReturnTypes() throws Exception {
processor = new ModelAttributeMethodProcessor(false);
- assertTrue(processor.supportsReturnType(returnParamNamedModelAttr));
- assertFalse(processor.supportsReturnType(returnParamNonSimpleType));
+ assertTrue(this.processor.supportsReturnType(returnParamNamedModelAttr));
+ assertFalse(this.processor.supportsReturnType(returnParamNonSimpleType));
}
@Test
public void supportedReturnTypesInDefaultResolutionMode() throws Exception {
processor = new ModelAttributeMethodProcessor(true);
- assertTrue(processor.supportsReturnType(returnParamNamedModelAttr));
- assertTrue(processor.supportsReturnType(returnParamNonSimpleType));
+ assertTrue(this.processor.supportsReturnType(returnParamNamedModelAttr));
+ assertTrue(this.processor.supportsReturnType(returnParamNonSimpleType));
}
@Test
public void bindExceptionRequired() throws Exception {
- assertTrue(processor.isBindExceptionRequired(null, paramNonSimpleType));
+ assertTrue(this.processor.isBindExceptionRequired(null, this.paramNonSimpleType));
+ assertFalse(this.processor.isBindExceptionRequired(null, this.paramNamedValidModelAttr));
}
@Test
- public void bindExceptionNotRequired() throws Exception {
- assertFalse(processor.isBindExceptionRequired(null, paramNamedValidModelAttr));
+ public void resolveArgumentFromModel() throws Exception {
+ testGetAttributeFromModel("attrName", this.paramNamedValidModelAttr);
+ testGetAttributeFromModel("testBean", this.paramModelAttr);
+ testGetAttributeFromModel("testBean", this.paramNonSimpleType);
}
@Test
- public void resovleArgumentFromModel() throws Exception {
- getAttributeFromModel("attrName", paramNamedValidModelAttr);
- getAttributeFromModel("testBean", paramModelAttr);
- getAttributeFromModel("testBean", paramNonSimpleType);
+ public void resovleArgumentViaDefaultConstructor() throws Exception {
+ WebDataBinder dataBinder = new WebRequestDataBinder(null);
+ WebDataBinderFactory factory = mock(WebDataBinderFactory.class);
+ given(factory.createBinder(anyObject(), notNull(), eq("attrName"))).willReturn(dataBinder);
+
+ this.processor.resolveArgument(this.paramNamedValidModelAttr, this.container, this.request, factory);
+ verify(factory).createBinder(anyObject(), notNull(), eq("attrName"));
}
- private void getAttributeFromModel(String expectedAttributeName, MethodParameter param) throws Exception {
+ @Test
+ public void resolveArgumentValidation() throws Exception {
+ String name = "attrName";
Object target = new TestBean();
- mavContainer.addAttribute(expectedAttributeName, target);
+ this.container.addAttribute(name, target);
- WebDataBinder dataBinder = new WebRequestDataBinder(target);
+ StubRequestDataBinder dataBinder = new StubRequestDataBinder(target, name);
WebDataBinderFactory factory = mock(WebDataBinderFactory.class);
- given(factory.createBinder(webRequest, target, expectedAttributeName)).willReturn(dataBinder);
+ given(factory.createBinder(this.request, target, name)).willReturn(dataBinder);
- processor.resolveArgument(param, mavContainer, webRequest, factory);
- verify(factory).createBinder(webRequest, target, expectedAttributeName);
+ this.processor.resolveArgument(this.paramNamedValidModelAttr, this.container, this.request, factory);
+
+ assertTrue(dataBinder.isBindInvoked());
+ assertTrue(dataBinder.isValidateInvoked());
}
@Test
- public void resovleArgumentViaDefaultConstructor() throws Exception {
- WebDataBinder dataBinder = new WebRequestDataBinder(null);
+ public void resolveArgumentBindingDisabledPreviously() throws Exception {
+ String name = "attrName";
+ Object target = new TestBean();
+ this.container.addAttribute(name, target);
+
+ // Declare binding disabled (e.g. via @ModelAttribute method)
+ this.container.setBindingDisabled(name);
+ StubRequestDataBinder dataBinder = new StubRequestDataBinder(target, name);
WebDataBinderFactory factory = mock(WebDataBinderFactory.class);
- given(factory.createBinder((NativeWebRequest) anyObject(), notNull(), eq("attrName"))).willReturn(dataBinder);
+ given(factory.createBinder(this.request, target, name)).willReturn(dataBinder);
- processor.resolveArgument(paramNamedValidModelAttr, mavContainer, webRequest, factory);
+ this.processor.resolveArgument(this.paramNamedValidModelAttr, this.container, this.request, factory);
- verify(factory).createBinder((NativeWebRequest) anyObject(), notNull(), eq("attrName"));
+ assertFalse(dataBinder.isBindInvoked());
+ assertTrue(dataBinder.isValidateInvoked());
}
@Test
- public void resolveArgumentValidation() throws Exception {
- String name = "attrName";
+ public void resolveArgumentBindingDisabled() throws Exception {
+ String name = "noBindAttr";
Object target = new TestBean();
- mavContainer.addAttribute(name, target);
+ this.container.addAttribute(name, target);
StubRequestDataBinder dataBinder = new StubRequestDataBinder(target, name);
- WebDataBinderFactory binderFactory = mock(WebDataBinderFactory.class);
- given(binderFactory.createBinder(webRequest, target, name)).willReturn(dataBinder);
+ WebDataBinderFactory factory = mock(WebDataBinderFactory.class);
+ given(factory.createBinder(this.request, target, name)).willReturn(dataBinder);
- processor.resolveArgument(paramNamedValidModelAttr, mavContainer, webRequest, binderFactory);
+ this.processor.resolveArgument(this.paramBindingDisabledAttr, this.container, this.request, factory);
- assertTrue(dataBinder.isBindInvoked());
+ assertFalse(dataBinder.isBindInvoked());
assertTrue(dataBinder.isValidateInvoked());
}
@Test(expected = BindException.class)
- public void resovleArgumentBindException() throws Exception {
+ public void resolveArgumentBindException() throws Exception {
String name = "testBean";
Object target = new TestBean();
- mavContainer.getModel().addAttribute(target);
+ this.container.getModel().addAttribute(target);
StubRequestDataBinder dataBinder = new StubRequestDataBinder(target, name);
dataBinder.getBindingResult().reject("error");
-
WebDataBinderFactory binderFactory = mock(WebDataBinderFactory.class);
- given(binderFactory.createBinder(webRequest, target, name)).willReturn(dataBinder);
+ given(binderFactory.createBinder(this.request, target, name)).willReturn(dataBinder);
- processor.resolveArgument(paramNonSimpleType, mavContainer, webRequest, binderFactory);
- verify(binderFactory).createBinder(webRequest, target, name);
+ this.processor.resolveArgument(this.paramNonSimpleType, this.container, this.request, binderFactory);
+ verify(binderFactory).createBinder(this.request, target, name);
}
@Test // SPR-9378
public void resolveArgumentOrdering() throws Exception {
String name = "testBean";
Object testBean = new TestBean(name);
- mavContainer.addAttribute(name, testBean);
- mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, testBean);
+ this.container.addAttribute(name, testBean);
+ this.container.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, testBean);
Object anotherTestBean = new TestBean();
- mavContainer.addAttribute("anotherTestBean", anotherTestBean);
+ this.container.addAttribute("anotherTestBean", anotherTestBean);
StubRequestDataBinder dataBinder = new StubRequestDataBinder(testBean, name);
WebDataBinderFactory binderFactory = mock(WebDataBinderFactory.class);
- given(binderFactory.createBinder(webRequest, testBean, name)).willReturn(dataBinder);
+ given(binderFactory.createBinder(this.request, testBean, name)).willReturn(dataBinder);
- processor.resolveArgument(paramModelAttr, mavContainer, webRequest, binderFactory);
+ this.processor.resolveArgument(this.paramModelAttr, this.container, this.request, binderFactory);
- assertSame("Resolved attribute should be updated to be last in the order",
- testBean, mavContainer.getModel().values().toArray()[1]);
- assertSame("BindingResult of resolved attribute should be last in the order",
- dataBinder.getBindingResult(), mavContainer.getModel().values().toArray()[2]);
+ Object[] values = this.container.getModel().values().toArray();
+ assertSame("Resolved attribute should be updated to be last", testBean, values[1]);
+ assertSame("BindingResult of resolved attr should be last", dataBinder.getBindingResult(), values[2]);
}
@Test
public void handleAnnotatedReturnValue() throws Exception {
- processor.handleReturnValue("expected", returnParamNamedModelAttr, mavContainer, webRequest);
- assertEquals("expected", mavContainer.getModel().get("modelAttrName"));
+ this.processor.handleReturnValue("expected", this.returnParamNamedModelAttr, this.container, this.request);
+ assertEquals("expected", this.container.getModel().get("modelAttrName"));
}
@Test
public void handleNotAnnotatedReturnValue() throws Exception {
TestBean testBean = new TestBean("expected");
- processor.handleReturnValue(testBean, returnParamNonSimpleType, mavContainer, webRequest);
+ this.processor.handleReturnValue(testBean, this.returnParamNonSimpleType, this.container, this.request);
+ assertSame(testBean, this.container.getModel().get("testBean"));
+ }
+
- assertSame(testBean, mavContainer.getModel().get("testBean"));
+ private void testGetAttributeFromModel(String expectedAttrName, MethodParameter param) throws Exception {
+ Object target = new TestBean();
+ this.container.addAttribute(expectedAttrName, target);
+
+ WebDataBinder dataBinder = new WebRequestDataBinder(target);
+ WebDataBinderFactory factory = mock(WebDataBinderFactory.class);
+ given(factory.createBinder(this.request, target, expectedAttrName)).willReturn(dataBinder);
+
+ this.processor.resolveArgument(param, this.container, this.request, factory);
+ verify(factory).createBinder(this.request, target, expectedAttrName);
}
@@ -246,6 +274,7 @@ public class ModelAttributeMethodProcessorTests {
private boolean validateInvoked;
+
public StubRequestDataBinder(Object target, String objectName) {
super(target, objectName);
}
@@ -285,13 +314,18 @@ public class ModelAttributeMethodProcessorTests {
private static class ModelAttributeHandler {
@SuppressWarnings("unused")
- public void modelAttribute(@ModelAttribute("attrName") @Valid TestBean annotatedAttr, Errors errors,
- int intArg, @ModelAttribute TestBean defaultNameAttr, TestBean notAnnotatedAttr) {
+ public void modelAttribute(
+ @ModelAttribute("attrName") @Valid TestBean annotatedAttr,
+ Errors errors,
+ int intArg,
+ @ModelAttribute TestBean defaultNameAttr,
+ @ModelAttribute(name="noBindAttr", binding=false) @Valid TestBean noBindAttr,
+ TestBean notAnnotatedAttr) {
}
}
- @ModelAttribute("modelAttrName")
+ @ModelAttribute("modelAttrName") @SuppressWarnings("unused")
private String annotatedReturnValue() {
return null;
}
diff --git a/spring-web/src/test/java/org/springframework/web/method/annotation/ModelFactoryTests.java b/spring-web/src/test/java/org/springframework/web/method/annotation/ModelFactoryTests.java
index e0a3c69f..24c61825 100644
--- a/spring-web/src/test/java/org/springframework/web/method/annotation/ModelFactoryTests.java
+++ b/spring-web/src/test/java/org/springframework/web/method/annotation/ModelFactoryTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author 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,19 +16,12 @@
package org.springframework.web.method.annotation;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.BDDMockito.given;
-import static org.mockito.BDDMockito.mock;
-
import java.lang.reflect.Method;
-import java.util.Arrays;
+import java.util.Collections;
import org.junit.Before;
import org.junit.Test;
+
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.ui.Model;
@@ -43,10 +36,19 @@ import org.springframework.web.bind.support.SessionAttributeStore;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
+import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
import org.springframework.web.method.support.InvocableHandlerMethod;
import org.springframework.web.method.support.ModelAndViewContainer;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.BDDMockito.mock;
+
/**
* Text fixture for {@link ModelFactory} tests.
@@ -55,103 +57,116 @@ import org.springframework.web.method.support.ModelAndViewContainer;
*/
public class ModelFactoryTests {
- private TestController controller = new TestController();
+ private NativeWebRequest webRequest;
- private InvocableHandlerMethod handleMethod;
+ private SessionAttributesHandler attributeHandler;
- private InvocableHandlerMethod handleSessionAttrMethod;
+ private SessionAttributeStore attributeStore;
- private SessionAttributesHandler sessionAttrsHandler;
+ private TestController controller = new TestController();
- private SessionAttributeStore sessionAttributeStore;
-
- private NativeWebRequest webRequest;
+ private ModelAndViewContainer mavContainer;
@Before
public void setUp() throws Exception {
- this.controller = new TestController();
-
- Method method = TestController.class.getDeclaredMethod("handle");
- this.handleMethod = new InvocableHandlerMethod(this.controller, method);
-
- method = TestController.class.getDeclaredMethod("handleSessionAttr", String.class);
- this.handleSessionAttrMethod = new InvocableHandlerMethod(this.controller, method);
-
- this.sessionAttributeStore = new DefaultSessionAttributeStore();
- this.sessionAttrsHandler = new SessionAttributesHandler(TestController.class, this.sessionAttributeStore);
this.webRequest = new ServletWebRequest(new MockHttpServletRequest());
+ this.attributeStore = new DefaultSessionAttributeStore();
+ this.attributeHandler = new SessionAttributesHandler(TestController.class, this.attributeStore);
+ this.controller = new TestController();
+ this.mavContainer = new ModelAndViewContainer();
}
@Test
public void modelAttributeMethod() throws Exception {
ModelFactory modelFactory = createModelFactory("modelAttr", Model.class);
- ModelAndViewContainer mavContainer = new ModelAndViewContainer();
- modelFactory.initModel(this.webRequest, mavContainer, this.handleMethod);
+ HandlerMethod handlerMethod = createHandlerMethod("handle");
+ modelFactory.initModel(this.webRequest, this.mavContainer, handlerMethod);
- assertEquals(Boolean.TRUE, mavContainer.getModel().get("modelAttr"));
+ assertEquals(Boolean.TRUE, this.mavContainer.getModel().get("modelAttr"));
}
@Test
public void modelAttributeMethodWithExplicitName() throws Exception {
ModelFactory modelFactory = createModelFactory("modelAttrWithName");
- ModelAndViewContainer mavContainer = new ModelAndViewContainer();
- modelFactory.initModel(this.webRequest, mavContainer, this.handleMethod);
+ HandlerMethod handlerMethod = createHandlerMethod("handle");
+ modelFactory.initModel(this.webRequest, this.mavContainer, handlerMethod);
- assertEquals(Boolean.TRUE, mavContainer.getModel().get("name"));
+ assertEquals(Boolean.TRUE, this.mavContainer.getModel().get("name"));
}
@Test
public void modelAttributeMethodWithNameByConvention() throws Exception {
ModelFactory modelFactory = createModelFactory("modelAttrConvention");
- ModelAndViewContainer mavContainer = new ModelAndViewContainer();
- modelFactory.initModel(this.webRequest, mavContainer, this.handleMethod);
+ HandlerMethod handlerMethod = createHandlerMethod("handle");
+ modelFactory.initModel(this.webRequest, this.mavContainer, handlerMethod);
- assertEquals(Boolean.TRUE, mavContainer.getModel().get("boolean"));
+ assertEquals(Boolean.TRUE, this.mavContainer.getModel().get("boolean"));
}
@Test
public void modelAttributeMethodWithNullReturnValue() throws Exception {
ModelFactory modelFactory = createModelFactory("nullModelAttr");
- ModelAndViewContainer mavContainer = new ModelAndViewContainer();
- modelFactory.initModel(this.webRequest, mavContainer, this.handleMethod);
+ HandlerMethod handlerMethod = createHandlerMethod("handle");
+ modelFactory.initModel(this.webRequest, this.mavContainer, handlerMethod);
- assertTrue(mavContainer.containsAttribute("name"));
- assertNull(mavContainer.getModel().get("name"));
+ assertTrue(this.mavContainer.containsAttribute("name"));
+ assertNull(this.mavContainer.getModel().get("name"));
}
@Test
- public void sessionAttribute() throws Exception {
- this.sessionAttributeStore.storeAttribute(this.webRequest, "sessionAttr", "sessionAttrValue");
+ public void modelAttributeWithBindingDisabled() throws Exception {
+ ModelFactory modelFactory = createModelFactory("modelAttrWithBindingDisabled");
+ HandlerMethod handlerMethod = createHandlerMethod("handle");
+ modelFactory.initModel(this.webRequest, this.mavContainer, handlerMethod);
+
+ assertTrue(this.mavContainer.containsAttribute("foo"));
+ assertTrue(this.mavContainer.isBindingDisabled("foo"));
+ }
+
+ @Test
+ public void modelAttributeFromSessionWithBindingDisabled() throws Exception {
+ Foo foo = new Foo();
+ this.attributeStore.storeAttribute(this.webRequest, "foo", foo);
- // Resolve successfully handler session attribute once
- assertTrue(sessionAttrsHandler.isHandlerSessionAttribute("sessionAttr", null));
+ ModelFactory modelFactory = createModelFactory("modelAttrWithBindingDisabled");
+ HandlerMethod handlerMethod = createHandlerMethod("handle");
+ modelFactory.initModel(this.webRequest, this.mavContainer, handlerMethod);
+
+ assertTrue(this.mavContainer.containsAttribute("foo"));
+ assertSame(foo, this.mavContainer.getModel().get("foo"));
+ assertTrue(this.mavContainer.isBindingDisabled("foo"));
+ }
+
+ @Test
+ public void sessionAttribute() throws Exception {
+ this.attributeStore.storeAttribute(this.webRequest, "sessionAttr", "sessionAttrValue");
ModelFactory modelFactory = createModelFactory("modelAttr", Model.class);
- ModelAndViewContainer mavContainer = new ModelAndViewContainer();
- modelFactory.initModel(this.webRequest, mavContainer, this.handleMethod);
+ HandlerMethod handlerMethod = createHandlerMethod("handle");
+ modelFactory.initModel(this.webRequest, this.mavContainer, handlerMethod);
- assertEquals("sessionAttrValue", mavContainer.getModel().get("sessionAttr"));
+ assertEquals("sessionAttrValue", this.mavContainer.getModel().get("sessionAttr"));
}
@Test
public void sessionAttributeNotPresent() throws Exception {
- ModelFactory modelFactory = new ModelFactory(null, null, this.sessionAttrsHandler);
-
+ ModelFactory modelFactory = new ModelFactory(null, null, this.attributeHandler);
+ HandlerMethod handlerMethod = createHandlerMethod("handleSessionAttr", String.class);
try {
- modelFactory.initModel(this.webRequest, new ModelAndViewContainer(), this.handleSessionAttrMethod);
+ modelFactory.initModel(this.webRequest, this.mavContainer, handlerMethod);
fail("Expected HttpSessionRequiredException");
}
catch (HttpSessionRequiredException e) {
// expected
}
- this.sessionAttributeStore.storeAttribute(this.webRequest, "sessionAttr", "sessionAttrValue");
- ModelAndViewContainer mavContainer = new ModelAndViewContainer();
- modelFactory.initModel(this.webRequest, mavContainer, this.handleSessionAttrMethod);
+ // Now add attribute and try again
+ this.attributeStore.storeAttribute(this.webRequest, "sessionAttr", "sessionAttrValue");
- assertEquals("sessionAttrValue", mavContainer.getModel().get("sessionAttr"));
+ modelFactory.initModel(this.webRequest, this.mavContainer, handlerMethod);
+ assertEquals("sessionAttrValue", this.mavContainer.getModel().get("sessionAttr"));
}
@Test
@@ -165,11 +180,12 @@ public class ModelFactoryTests {
WebDataBinderFactory binderFactory = mock(WebDataBinderFactory.class);
given(binderFactory.createBinder(this.webRequest, command, commandName)).willReturn(dataBinder);
- ModelFactory modelFactory = new ModelFactory(null, binderFactory, this.sessionAttrsHandler);
+ ModelFactory modelFactory = new ModelFactory(null, binderFactory, this.attributeHandler);
modelFactory.updateModel(this.webRequest, container);
assertEquals(command, container.getModel().get(commandName));
- assertSame(dataBinder.getBindingResult(), container.getModel().get(bindingResultKey(commandName)));
+ String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + commandName;
+ assertSame(dataBinder.getBindingResult(), container.getModel().get(bindingResultKey));
assertEquals(2, container.getModel().size());
}
@@ -184,11 +200,11 @@ public class ModelFactoryTests {
WebDataBinderFactory binderFactory = mock(WebDataBinderFactory.class);
given(binderFactory.createBinder(this.webRequest, attribute, attributeName)).willReturn(dataBinder);
- ModelFactory modelFactory = new ModelFactory(null, binderFactory, this.sessionAttrsHandler);
+ ModelFactory modelFactory = new ModelFactory(null, binderFactory, this.attributeHandler);
modelFactory.updateModel(this.webRequest, container);
assertEquals(attribute, container.getModel().get(attributeName));
- assertEquals(attribute, this.sessionAttributeStore.retrieveAttribute(this.webRequest, attributeName));
+ assertEquals(attribute, this.attributeStore.retrieveAttribute(this.webRequest, attributeName));
}
@Test
@@ -198,9 +214,7 @@ public class ModelFactoryTests {
ModelAndViewContainer container = new ModelAndViewContainer();
container.addAttribute(attributeName, attribute);
- // Store and resolve once (to be "remembered")
- this.sessionAttributeStore.storeAttribute(this.webRequest, attributeName, attribute);
- this.sessionAttrsHandler.isHandlerSessionAttribute(attributeName, null);
+ this.attributeStore.storeAttribute(this.webRequest, attributeName, attribute);
WebDataBinder dataBinder = new WebDataBinder(attribute, attributeName);
WebDataBinderFactory binderFactory = mock(WebDataBinderFactory.class);
@@ -208,11 +222,11 @@ public class ModelFactoryTests {
container.getSessionStatus().setComplete();
- ModelFactory modelFactory = new ModelFactory(null, binderFactory, this.sessionAttrsHandler);
+ ModelFactory modelFactory = new ModelFactory(null, binderFactory, this.attributeHandler);
modelFactory.updateModel(this.webRequest, container);
assertEquals(attribute, container.getModel().get(attributeName));
- assertNull(this.sessionAttributeStore.retrieveAttribute(this.webRequest, attributeName));
+ assertNull(this.attributeStore.retrieveAttribute(this.webRequest, attributeName));
}
// SPR-12542
@@ -233,34 +247,34 @@ public class ModelFactoryTests {
WebDataBinderFactory binderFactory = mock(WebDataBinderFactory.class);
given(binderFactory.createBinder(this.webRequest, attribute, attributeName)).willReturn(dataBinder);
- ModelFactory modelFactory = new ModelFactory(null, binderFactory, this.sessionAttrsHandler);
+ ModelFactory modelFactory = new ModelFactory(null, binderFactory, this.attributeHandler);
modelFactory.updateModel(this.webRequest, container);
assertEquals(queryParam, container.getModel().get(queryParamName));
assertEquals(1, container.getModel().size());
- assertEquals(attribute, this.sessionAttributeStore.retrieveAttribute(this.webRequest, attributeName));
+ assertEquals(attribute, this.attributeStore.retrieveAttribute(this.webRequest, attributeName));
}
- private String bindingResultKey(String key) {
- return BindingResult.MODEL_KEY_PREFIX + key;
- }
-
- private ModelFactory createModelFactory(String methodName, Class<?>... parameterTypes) throws Exception{
- Method method = TestController.class.getMethod(methodName, parameterTypes);
+ private ModelFactory createModelFactory(String methodName, Class<?>... parameterTypes) throws Exception {
+ HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite();
+ resolvers.addResolver(new ModelMethodProcessor());
- HandlerMethodArgumentResolverComposite argResolvers = new HandlerMethodArgumentResolverComposite();
- argResolvers.addResolver(new ModelMethodProcessor());
+ InvocableHandlerMethod modelMethod = createHandlerMethod(methodName, parameterTypes);
+ modelMethod.setHandlerMethodArgumentResolvers(resolvers);
+ modelMethod.setDataBinderFactory(null);
+ modelMethod.setParameterNameDiscoverer(new LocalVariableTableParameterNameDiscoverer());
- InvocableHandlerMethod handlerMethod = new InvocableHandlerMethod(this.controller, method);
- handlerMethod.setHandlerMethodArgumentResolvers(argResolvers);
- handlerMethod.setDataBinderFactory(null);
- handlerMethod.setParameterNameDiscoverer(new LocalVariableTableParameterNameDiscoverer());
+ return new ModelFactory(Collections.singletonList(modelMethod), null, this.attributeHandler);
+ }
- return new ModelFactory(Arrays.asList(handlerMethod), null, this.sessionAttrsHandler);
+ private InvocableHandlerMethod createHandlerMethod(String methodName, Class<?>... paramTypes) throws Exception {
+ Method method = this.controller.getClass().getMethod(methodName, paramTypes);
+ return new InvocableHandlerMethod(this.controller, method);
}
- @SessionAttributes("sessionAttr") @SuppressWarnings("unused")
+
+ @SessionAttributes({"sessionAttr", "foo"}) @SuppressWarnings("unused")
private static class TestController {
@ModelAttribute
@@ -283,6 +297,11 @@ public class ModelFactoryTests {
return null;
}
+ @ModelAttribute(name="foo", binding=false)
+ public Foo modelAttrWithBindingDisabled() {
+ return new Foo();
+ }
+
public void handle() {
}
@@ -290,4 +309,7 @@ public class ModelFactoryTests {
}
}
+ private static class Foo {
+ }
+
}
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 b484e2ff..a453abb0 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,9 @@
package org.springframework.web.method.annotation;
import java.lang.reflect.Method;
+import java.time.Instant;
+import java.time.format.DateTimeFormatter;
+import java.util.Date;
import java.util.Map;
import org.junit.After;
@@ -25,10 +28,14 @@ import org.junit.Test;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.SynthesizingMethodParameter;
+import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse;
+import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
+import org.springframework.web.bind.support.DefaultDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletWebRequest;
@@ -50,7 +57,11 @@ public class RequestHeaderMethodArgumentResolverTests {
private MethodParameter paramNamedValueStringArray;
private MethodParameter paramSystemProperty;
private MethodParameter paramContextPath;
+ private MethodParameter paramResolvedNameWithExpression;
+ private MethodParameter paramResolvedNameWithPlaceholder;
private MethodParameter paramNamedValueMap;
+ private MethodParameter paramDate;
+ private MethodParameter paramInstant;
private MockHttpServletRequest servletRequest;
@@ -64,12 +75,16 @@ public class RequestHeaderMethodArgumentResolverTests {
context.refresh();
resolver = new RequestHeaderMethodArgumentResolver(context.getBeanFactory());
- Method method = getClass().getMethod("params", String.class, String[].class, String.class, String.class, Map.class);
+ Method method = ReflectionUtils.findMethod(getClass(), "params", (Class<?>[]) null);
paramNamedDefaultValueStringHeader = new SynthesizingMethodParameter(method, 0);
paramNamedValueStringArray = new SynthesizingMethodParameter(method, 1);
paramSystemProperty = new SynthesizingMethodParameter(method, 2);
paramContextPath = new SynthesizingMethodParameter(method, 3);
- paramNamedValueMap = new SynthesizingMethodParameter(method, 4);
+ paramResolvedNameWithExpression = new SynthesizingMethodParameter(method, 4);
+ paramResolvedNameWithPlaceholder = new SynthesizingMethodParameter(method, 5);
+ paramNamedValueMap = new SynthesizingMethodParameter(method, 6);
+ paramDate = new SynthesizingMethodParameter(method, 7);
+ paramInstant = new SynthesizingMethodParameter(method, 8);
servletRequest = new MockHttpServletRequest();
webRequest = new ServletWebRequest(servletRequest, new MockHttpServletResponse());
@@ -97,45 +112,77 @@ public class RequestHeaderMethodArgumentResolverTests {
servletRequest.addHeader("name", expected);
Object result = resolver.resolveArgument(paramNamedDefaultValueStringHeader, null, webRequest, null);
-
assertTrue(result instanceof String);
- assertEquals("Invalid result", expected, result);
+ assertEquals(expected, result);
}
@Test
public void resolveStringArrayArgument() throws Exception {
- String[] expected = new String[]{"foo", "bar"};
+ String[] expected = new String[] {"foo", "bar"};
servletRequest.addHeader("name", expected);
Object result = resolver.resolveArgument(paramNamedValueStringArray, null, webRequest, null);
-
assertTrue(result instanceof String[]);
- assertArrayEquals("Invalid result", expected, (String[]) result);
+ assertArrayEquals(expected, (String[]) result);
}
@Test
public void resolveDefaultValue() throws Exception {
Object result = resolver.resolveArgument(paramNamedDefaultValueStringHeader, null, webRequest, null);
-
assertTrue(result instanceof String);
- assertEquals("Invalid result", "bar", result);
+ assertEquals("bar", result);
}
@Test
public void resolveDefaultValueFromSystemProperty() throws Exception {
System.setProperty("systemProperty", "bar");
- Object result = resolver.resolveArgument(paramSystemProperty, null, webRequest, null);
- System.clearProperty("systemProperty");
+ try {
+ Object result = resolver.resolveArgument(paramSystemProperty, null, webRequest, null);
+ assertTrue(result instanceof String);
+ assertEquals("bar", result);
+ }
+ finally {
+ System.clearProperty("systemProperty");
+ }
+ }
- assertTrue(result instanceof String);
- assertEquals("bar", result);
+ @Test
+ public void resolveNameFromSystemPropertyThroughExpression() throws Exception {
+ String expected = "foo";
+ servletRequest.addHeader("bar", expected);
+
+ System.setProperty("systemProperty", "bar");
+ try {
+ Object result = resolver.resolveArgument(paramResolvedNameWithExpression, null, webRequest, null);
+ assertTrue(result instanceof String);
+ assertEquals(expected, result);
+ }
+ finally {
+ System.clearProperty("systemProperty");
+ }
+ }
+
+ @Test
+ public void resolveNameFromSystemPropertyThroughPlaceholder() throws Exception {
+ String expected = "foo";
+ servletRequest.addHeader("bar", expected);
+
+ System.setProperty("systemProperty", "bar");
+ try {
+ Object result = resolver.resolveArgument(paramResolvedNameWithPlaceholder, null, webRequest, null);
+ assertTrue(result instanceof String);
+ assertEquals(expected, result);
+ }
+ finally {
+ System.clearProperty("systemProperty");
+ }
}
@Test
public void resolveDefaultValueFromRequest() throws Exception {
servletRequest.setContextPath("/bar");
- Object result = resolver.resolveArgument(paramContextPath, null, webRequest, null);
+ Object result = resolver.resolveArgument(paramContextPath, null, webRequest, null);
assertTrue(result instanceof String);
assertEquals("/bar", result);
}
@@ -145,12 +192,46 @@ public class RequestHeaderMethodArgumentResolverTests {
resolver.resolveArgument(paramNamedValueStringArray, null, webRequest, null);
}
+ @Test
+ @SuppressWarnings("deprecation")
+ public void dateConversion() throws Exception {
+ String rfc1123val = "Thu, 21 Apr 2016 17:11:08 +0100";
+ servletRequest.addHeader("name", rfc1123val);
+
+ ConfigurableWebBindingInitializer bindingInitializer = new ConfigurableWebBindingInitializer();
+ bindingInitializer.setConversionService(new DefaultFormattingConversionService());
+ Object result = resolver.resolveArgument(paramDate, null, webRequest,
+ new DefaultDataBinderFactory(bindingInitializer));
+
+ assertTrue(result instanceof Date);
+ assertEquals(new Date(rfc1123val), result);
+ }
+
+ @Test
+ public void instantConversion() throws Exception {
+ String rfc1123val = "Thu, 21 Apr 2016 17:11:08 +0100";
+ servletRequest.addHeader("name", rfc1123val);
+
+ ConfigurableWebBindingInitializer bindingInitializer = new ConfigurableWebBindingInitializer();
+ bindingInitializer.setConversionService(new DefaultFormattingConversionService());
+ Object result = resolver.resolveArgument(paramInstant, null, webRequest,
+ new DefaultDataBinderFactory(bindingInitializer));
+
+ assertTrue(result instanceof Instant);
+ assertEquals(Instant.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(rfc1123val)), result);
+ }
+
- public void params(@RequestHeader(name = "name", defaultValue = "bar") String param1,
- @RequestHeader("name") String[] param2,
- @RequestHeader(name = "name", defaultValue="#{systemProperties.systemProperty}") String param3,
- @RequestHeader(name = "name", defaultValue="#{request.contextPath}") String param4,
- @RequestHeader("name") Map<?, ?> unsupported) {
+ public void params(
+ @RequestHeader(name = "name", defaultValue = "bar") String param1,
+ @RequestHeader("name") String[] param2,
+ @RequestHeader(name = "name", defaultValue="#{systemProperties.systemProperty}") String param3,
+ @RequestHeader(name = "name", defaultValue="#{request.contextPath}") String param4,
+ @RequestHeader("#{systemProperties.systemProperty}") String param5,
+ @RequestHeader("${systemProperty}") String param6,
+ @RequestHeader("name") Map<?, ?> unsupported,
+ @RequestHeader("name") Date dateParam,
+ @RequestHeader("name") Instant instantParam) {
}
}
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 00b61b5f..f77ce2bc 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
@@ -37,6 +37,7 @@ import org.springframework.mock.web.test.MockHttpServletResponse;
import org.springframework.mock.web.test.MockMultipartFile;
import org.springframework.mock.web.test.MockMultipartHttpServletRequest;
import org.springframework.mock.web.test.MockPart;
+import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.RequestParam;
@@ -49,6 +50,7 @@ import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.multipart.support.MissingServletRequestPartException;
import static org.junit.Assert.*;
import static org.mockito.BDDMockito.*;
@@ -82,6 +84,7 @@ public class RequestParamMethodArgumentResolverTests {
private MethodParameter paramRequired;
private MethodParameter paramNotRequired;
private MethodParameter paramOptional;
+ private MethodParameter multipartFileOptional;
private NativeWebRequest webRequest;
@@ -92,12 +95,7 @@ public class RequestParamMethodArgumentResolverTests {
public void setUp() throws Exception {
resolver = new RequestParamMethodArgumentResolver(null, true);
ParameterNameDiscoverer paramNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
-
- Method method = getClass().getMethod("params", String.class, String[].class,
- Map.class, MultipartFile.class, List.class, MultipartFile[].class,
- Part.class, List.class, Part[].class, Map.class,
- String.class, MultipartFile.class, List.class, Part.class,
- MultipartFile.class, String.class, String.class, Optional.class);
+ Method method = ReflectionUtils.findMethod(getClass(), "handle", (Class<?>[]) null);
paramNamedDefaultValueString = new SynthesizingMethodParameter(method, 0);
paramNamedStringArray = new SynthesizingMethodParameter(method, 1);
@@ -121,6 +119,7 @@ public class RequestParamMethodArgumentResolverTests {
paramRequired = new SynthesizingMethodParameter(method, 15);
paramNotRequired = new SynthesizingMethodParameter(method, 16);
paramOptional = new SynthesizingMethodParameter(method, 17);
+ multipartFileOptional = new SynthesizingMethodParameter(method, 18);
request = new MockHttpServletRequest();
webRequest = new ServletWebRequest(request, new MockHttpServletResponse());
@@ -130,19 +129,25 @@ public class RequestParamMethodArgumentResolverTests {
@Test
public void supportsParameter() {
resolver = new RequestParamMethodArgumentResolver(null, true);
- assertTrue("String parameter not supported", resolver.supportsParameter(paramNamedDefaultValueString));
- assertTrue("String array parameter not supported", resolver.supportsParameter(paramNamedStringArray));
- assertTrue("Named map not parameter supported", resolver.supportsParameter(paramNamedMap));
- assertTrue("MultipartFile parameter not supported", resolver.supportsParameter(paramMultipartFile));
- assertTrue("List<MultipartFile> parameter not supported", resolver.supportsParameter(paramMultipartFileList));
- assertTrue("MultipartFile[] parameter not supported", resolver.supportsParameter(paramMultipartFileArray));
- assertTrue("Part parameter not supported", resolver.supportsParameter(paramPart));
- assertTrue("List<Part> parameter not supported", resolver.supportsParameter(paramPartList));
- assertTrue("Part[] parameter not supported", resolver.supportsParameter(paramPartArray));
- assertFalse("non-@RequestParam parameter supported", resolver.supportsParameter(paramMap));
- assertTrue("Simple type params supported w/o annotations", resolver.supportsParameter(paramStringNotAnnot));
- assertTrue("MultipartFile parameter not supported", resolver.supportsParameter(paramMultipartFileNotAnnot));
- assertTrue("Part parameter not supported", resolver.supportsParameter(paramPartNotAnnot));
+ assertTrue(resolver.supportsParameter(paramNamedDefaultValueString));
+ assertTrue(resolver.supportsParameter(paramNamedStringArray));
+ assertTrue(resolver.supportsParameter(paramNamedMap));
+ assertTrue(resolver.supportsParameter(paramMultipartFile));
+ assertTrue(resolver.supportsParameter(paramMultipartFileList));
+ assertTrue(resolver.supportsParameter(paramMultipartFileArray));
+ assertTrue(resolver.supportsParameter(paramPart));
+ assertTrue(resolver.supportsParameter(paramPartList));
+ assertTrue(resolver.supportsParameter(paramPartArray));
+ assertFalse(resolver.supportsParameter(paramMap));
+ assertTrue(resolver.supportsParameter(paramStringNotAnnot));
+ assertTrue(resolver.supportsParameter(paramMultipartFileNotAnnot));
+ assertTrue(resolver.supportsParameter(paramMultipartFileListNotAnnot));
+ assertTrue(resolver.supportsParameter(paramPartNotAnnot));
+ assertFalse(resolver.supportsParameter(paramRequestPartAnnot));
+ assertTrue(resolver.supportsParameter(paramRequired));
+ assertTrue(resolver.supportsParameter(paramNotRequired));
+ assertTrue(resolver.supportsParameter(paramOptional));
+ assertTrue(resolver.supportsParameter(multipartFileOptional));
resolver = new RequestParamMethodArgumentResolver(null, false);
assertFalse(resolver.supportsParameter(paramStringNotAnnot));
@@ -188,6 +193,7 @@ public class RequestParamMethodArgumentResolverTests {
MultipartFile expected2 = new MockMultipartFile("mfilelist", "Hello World 2".getBytes());
request.addFile(expected1);
request.addFile(expected2);
+ request.addFile(new MockMultipartFile("other", "Hello World 3".getBytes()));
webRequest = new ServletWebRequest(request);
Object result = resolver.resolveArgument(paramMultipartFileList, null, webRequest, null);
@@ -202,6 +208,7 @@ public class RequestParamMethodArgumentResolverTests {
MultipartFile expected2 = new MockMultipartFile("mfilearray", "Hello World 2".getBytes());
request.addFile(expected1);
request.addFile(expected2);
+ request.addFile(new MockMultipartFile("other", "Hello World 3".getBytes()));
webRequest = new ServletWebRequest(request);
Object result = resolver.resolveArgument(paramMultipartFileArray, null, webRequest, null);
@@ -222,7 +229,6 @@ public class RequestParamMethodArgumentResolverTests {
webRequest = new ServletWebRequest(request);
Object result = resolver.resolveArgument(paramPart, null, webRequest, null);
-
assertTrue(result instanceof Part);
assertEquals("Invalid result", expected, result);
}
@@ -230,12 +236,13 @@ public class RequestParamMethodArgumentResolverTests {
@Test
public void resolvePartList() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
- MockPart expected1 = new MockPart("pfilelist", "Hello World 1".getBytes());
- MockPart expected2 = new MockPart("pfilelist", "Hello World 2".getBytes());
request.setMethod("POST");
request.setContentType("multipart/form-data");
+ MockPart expected1 = new MockPart("pfilelist", "Hello World 1".getBytes());
+ MockPart expected2 = new MockPart("pfilelist", "Hello World 2".getBytes());
request.addPart(expected1);
request.addPart(expected2);
+ request.addPart(new MockPart("other", "Hello World 3".getBytes()));
webRequest = new ServletWebRequest(request);
Object result = resolver.resolveArgument(paramPartList, null, webRequest, null);
@@ -252,6 +259,7 @@ public class RequestParamMethodArgumentResolverTests {
request.setContentType("multipart/form-data");
request.addPart(expected1);
request.addPart(expected2);
+ request.addPart(new MockPart("other", "Hello World 3".getBytes()));
webRequest = new ServletWebRequest(request);
Object result = resolver.resolveArgument(paramPartArray, null, webRequest, null);
@@ -307,12 +315,19 @@ public class RequestParamMethodArgumentResolverTests {
assertEquals(expected, ((List<?>) actual).get(0));
}
- @Test(expected = IllegalArgumentException.class)
+ @Test(expected = MultipartException.class)
+ public void noMultipartContent() throws Exception {
+ request.setMethod("POST");
+ resolver.resolveArgument(paramMultipartFile, null, webRequest, null);
+ fail("Expected exception: no multipart content");
+ }
+
+ @Test(expected = MissingServletRequestPartException.class)
public void missingMultipartFile() throws Exception {
request.setMethod("POST");
request.setContentType("multipart/form-data");
resolver.resolveArgument(paramMultipartFile, null, webRequest, null);
- fail("Expected exception: request is not MultiPartHttpServletRequest but param is MultipartFile");
+ fail("Expected exception: no such part found");
}
@Test
@@ -422,8 +437,45 @@ public class RequestParamMethodArgumentResolverTests {
assertEquals(123, ((Optional) result).get());
}
+ @Test
+ public void resolveOptionalMultipartFile() throws Exception {
+ ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
+ initializer.setConversionService(new DefaultConversionService());
+ WebDataBinderFactory binderFactory = new DefaultDataBinderFactory(initializer);
+
+ MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
+ MultipartFile expected = new MockMultipartFile("mfile", "Hello World".getBytes());
+ request.addFile(expected);
+ webRequest = new ServletWebRequest(request);
+
+ Object result = resolver.resolveArgument(multipartFileOptional, null, webRequest, binderFactory);
+ assertTrue(result instanceof Optional);
+ assertEquals("Invalid result", expected, ((Optional<?>) result).get());
+ }
+
+ @Test
+ public void missingOptionalMultipartFile() throws Exception {
+ ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
+ initializer.setConversionService(new DefaultConversionService());
+ WebDataBinderFactory binderFactory = new DefaultDataBinderFactory(initializer);
+
+ request.setMethod("POST");
+ request.setContentType("multipart/form-data");
+ assertEquals(Optional.empty(), resolver.resolveArgument(multipartFileOptional, null, webRequest, binderFactory));
+ }
+
+ @Test
+ public void optionalMultipartFileWithoutMultipartRequest() throws Exception {
+ ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
+ initializer.setConversionService(new DefaultConversionService());
+ WebDataBinderFactory binderFactory = new DefaultDataBinderFactory(initializer);
+
+ assertEquals(Optional.empty(), resolver.resolveArgument(multipartFileOptional, null, webRequest, binderFactory));
+ }
+
- public void params(@RequestParam(name = "name", defaultValue = "bar") String param1,
+ public void handle(
+ @RequestParam(name = "name", defaultValue = "bar") String param1,
@RequestParam("name") String[] param2,
@RequestParam("name") Map<?, ?> param3,
@RequestParam("mfile") MultipartFile param4,
@@ -440,7 +492,8 @@ public class RequestParamMethodArgumentResolverTests {
@RequestPart MultipartFile requestPartAnnot,
@RequestParam("name") String paramRequired,
@RequestParam(name = "name", required = false) String paramNotRequired,
- @RequestParam("name") Optional<Integer> paramOptional) {
+ @RequestParam("name") Optional<Integer> paramOptional,
+ @RequestParam("mfile") Optional<MultipartFile> multipartFileOptional) {
}
}
diff --git a/spring-web/src/test/java/org/springframework/web/util/DefaultUriTemplateHandlerTests.java b/spring-web/src/test/java/org/springframework/web/util/DefaultUriTemplateHandlerTests.java
index 1642351e..09f3700a 100644
--- a/spring-web/src/test/java/org/springframework/web/util/DefaultUriTemplateHandlerTests.java
+++ b/spring-web/src/test/java/org/springframework/web/util/DefaultUriTemplateHandlerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,74 +15,135 @@
*/
package org.springframework.web.util;
-import static org.junit.Assert.assertEquals;
-
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
-import org.junit.Before;
import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
/**
* Unit tests for {@link DefaultUriTemplateHandler}.
+ *
* @author Rossen Stoyanchev
*/
public class DefaultUriTemplateHandlerTests {
- private DefaultUriTemplateHandler handler;
-
-
- @Before
- public void setUp() throws Exception {
- this.handler = new DefaultUriTemplateHandler();
- }
+ private final DefaultUriTemplateHandler handler = new DefaultUriTemplateHandler();
@Test
- public void baseUrl() throws Exception {
+ public void baseUrlWithoutPath() throws Exception {
this.handler.setBaseUrl("http://localhost:8080");
URI actual = this.handler.expand("/myapiresource");
- URI expected = new URI("http://localhost:8080/myapiresource");
- assertEquals(expected, actual);
+ assertEquals("http://localhost:8080/myapiresource", actual.toString());
}
@Test
- public void baseUrlWithPartialPath() throws Exception {
+ public void baseUrlWithPath() throws Exception {
this.handler.setBaseUrl("http://localhost:8080/context");
URI actual = this.handler.expand("/myapiresource");
- URI expected = new URI("http://localhost:8080/context/myapiresource");
- assertEquals(expected, actual);
+ assertEquals("http://localhost:8080/context/myapiresource", actual.toString());
+ }
+
+ @Test // SPR-14147
+ public void defaultUriVariables() throws Exception {
+ Map<String, String> defaultVars = new HashMap<>(2);
+ defaultVars.put("host", "api.example.com");
+ defaultVars.put("port", "443");
+ this.handler.setDefaultUriVariables(defaultVars);
+
+ Map<String, Object> vars = new HashMap<>(1);
+ vars.put("id", 123L);
+
+ String template = "https://{host}:{port}/v42/customers/{id}";
+ URI actual = this.handler.expand(template, vars);
+
+ assertEquals("https://api.example.com:443/v42/customers/123", actual.toString());
}
@Test
- public void expandWithFullPath() throws Exception {
- Map<String, String> vars = new HashMap<String, String>(2);
+ public void parsePathIsOff() throws Exception {
+ this.handler.setParsePath(false);
+ Map<String, String> vars = new HashMap<>(2);
vars.put("hotel", "1");
vars.put("publicpath", "pics/logo.png");
String template = "http://example.com/hotels/{hotel}/pic/{publicpath}";
-
URI actual = this.handler.expand(template, vars);
- URI expected = new URI("http://example.com/hotels/1/pic/pics/logo.png");
- assertEquals(expected, actual);
+ assertEquals("http://example.com/hotels/1/pic/pics/logo.png", actual.toString());
}
@Test
- public void expandWithFullPathAndParsePathEnabled() throws Exception {
- Map<String, String> vars = new HashMap<String, String>(2);
+ public void parsePathIsOn() throws Exception {
+ this.handler.setParsePath(true);
+ Map<String, String> vars = new HashMap<>(2);
vars.put("hotel", "1");
vars.put("publicpath", "pics/logo.png");
vars.put("scale", "150x150");
String template = "http://example.com/hotels/{hotel}/pic/{publicpath}/size/{scale}";
+ URI actual = this.handler.expand(template, vars);
- this.handler.setParsePath(true);
+ assertEquals("http://example.com/hotels/1/pic/pics%2Flogo.png/size/150x150", actual.toString());
+ }
+
+ @Test
+ public void strictEncodingIsOffWithMap() throws Exception {
+ this.handler.setStrictEncoding(false);
+ Map<String, String> vars = new HashMap<>(2);
+ vars.put("userId", "john;doe");
+ String template = "http://www.example.com/user/{userId}/dashboard";
+ URI actual = this.handler.expand(template, vars);
+
+ assertEquals("http://www.example.com/user/john;doe/dashboard", actual.toString());
+ }
+
+ @Test
+ public void strictEncodingOffWithArray() throws Exception {
+ this.handler.setStrictEncoding(false);
+ String template = "http://www.example.com/user/{userId}/dashboard";
+ URI actual = this.handler.expand(template, "john;doe");
+
+ assertEquals("http://www.example.com/user/john;doe/dashboard", actual.toString());
+ }
+
+ @Test
+ public void strictEncodingOnWithMap() throws Exception {
+ this.handler.setStrictEncoding(true);
+ Map<String, String> vars = new HashMap<>(2);
+ vars.put("userId", "john;doe");
+ String template = "http://www.example.com/user/{userId}/dashboard";
+ URI actual = this.handler.expand(template, vars);
+
+ assertEquals("http://www.example.com/user/john%3Bdoe/dashboard", actual.toString());
+ }
+
+ @Test
+ public void strictEncodingOnWithArray() throws Exception {
+ this.handler.setStrictEncoding(true);
+ String template = "http://www.example.com/user/{userId}/dashboard";
+ URI actual = this.handler.expand(template, "john;doe");
+
+ assertEquals("http://www.example.com/user/john%3Bdoe/dashboard", actual.toString());
+ }
+
+ @Test // SPR-14147
+ public void strictEncodingAndDefaultUriVariables() throws Exception {
+ Map<String, String> defaultVars = new HashMap<>(1);
+ defaultVars.put("host", "www.example.com");
+ this.handler.setDefaultUriVariables(defaultVars);
+ this.handler.setStrictEncoding(true);
+
+ Map<String, Object> vars = new HashMap<>(1);
+ vars.put("userId", "john;doe");
+
+ String template = "http://{host}/user/{userId}/dashboard";
URI actual = this.handler.expand(template, vars);
- URI expected = new URI("http://example.com/hotels/1/pic/pics%2Flogo.png/size/150x150");
- assertEquals(expected, actual);
+ assertEquals("http://www.example.com/user/john%3Bdoe/dashboard", actual.toString());
}
}
diff --git a/spring-web/src/test/java/org/springframework/web/util/Log4jWebConfigurerTests.java b/spring-web/src/test/java/org/springframework/web/util/Log4jWebConfigurerTests.java
index 5001f9fb..c32405e4 100644
--- a/spring-web/src/test/java/org/springframework/web/util/Log4jWebConfigurerTests.java
+++ b/spring-web/src/test/java/org/springframework/web/util/Log4jWebConfigurerTests.java
@@ -101,7 +101,8 @@ public class Log4jWebConfigurerTests {
try {
assertLogOutput();
- } finally {
+ }
+ finally {
Log4jWebConfigurer.shutdownLogging(sc);
}
assertTrue(MockLog4jAppender.closeCalled);
@@ -132,7 +133,8 @@ public class Log4jWebConfigurerTests {
try {
assertLogOutput();
- } finally {
+ }
+ finally {
listener.contextDestroyed(new ServletContextEvent(sc));
}
assertTrue(MockLog4jAppender.closeCalled);
diff --git a/spring-web/src/test/java/org/springframework/web/util/UriTemplateTests.java b/spring-web/src/test/java/org/springframework/web/util/UriTemplateTests.java
index a047af03..99c642f0 100644
--- a/spring-web/src/test/java/org/springframework/web/util/UriTemplateTests.java
+++ b/spring-web/src/test/java/org/springframework/web/util/UriTemplateTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,116 +30,126 @@ import static org.junit.Assert.*;
/**
* @author Arjen Poutsma
* @author Juergen Hoeller
+ * @author Rossen Stoyanchev
*/
public class UriTemplateTests {
@Test
public void getVariableNames() throws Exception {
- UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}");
+ UriTemplate template = new UriTemplate("/hotels/{hotel}/bookings/{booking}");
List<String> variableNames = template.getVariableNames();
assertEquals("Invalid variable names", Arrays.asList("hotel", "booking"), variableNames);
}
@Test
public void expandVarArgs() throws Exception {
- UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}");
+ UriTemplate template = new UriTemplate("/hotels/{hotel}/bookings/{booking}");
URI result = template.expand("1", "42");
- assertEquals("Invalid expanded template", new URI("http://example.com/hotels/1/bookings/42"), result);
+ assertEquals("Invalid expanded template", new URI("/hotels/1/bookings/42"), result);
+ }
+
+ // SPR-9712
+
+ @Test
+ public void expandVarArgsWithArrayValue() throws Exception {
+ UriTemplate template = new UriTemplate("/sum?numbers={numbers}");
+ URI result = template.expand(new int[] {1, 2, 3});
+ assertEquals(new URI("/sum?numbers=1,2,3"), result);
}
@Test(expected = IllegalArgumentException.class)
public void expandVarArgsNotEnoughVariables() throws Exception {
- UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}");
+ UriTemplate template = new UriTemplate("/hotels/{hotel}/bookings/{booking}");
template.expand("1");
}
@Test
public void expandMap() throws Exception {
- Map<String, String> uriVariables = new HashMap<String, String>(2);
+ Map<String, String> uriVariables = new HashMap<>(2);
uriVariables.put("booking", "42");
uriVariables.put("hotel", "1");
- UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}");
+ UriTemplate template = new UriTemplate("/hotels/{hotel}/bookings/{booking}");
URI result = template.expand(uriVariables);
- assertEquals("Invalid expanded template", new URI("http://example.com/hotels/1/bookings/42"), result);
+ assertEquals("Invalid expanded template", new URI("/hotels/1/bookings/42"), result);
}
@Test
public void expandMapDuplicateVariables() throws Exception {
UriTemplate template = new UriTemplate("/order/{c}/{c}/{c}");
- assertEquals("Invalid variable names", Arrays.asList("c", "c", "c"), template.getVariableNames());
+ assertEquals(Arrays.asList("c", "c", "c"), template.getVariableNames());
URI result = template.expand(Collections.singletonMap("c", "cheeseburger"));
- assertEquals("Invalid expanded template", new URI("/order/cheeseburger/cheeseburger/cheeseburger"), result);
+ assertEquals(new URI("/order/cheeseburger/cheeseburger/cheeseburger"), result);
}
@Test
public void expandMapNonString() throws Exception {
- Map<String, Integer> uriVariables = new HashMap<String, Integer>(2);
+ Map<String, Integer> uriVariables = new HashMap<>(2);
uriVariables.put("booking", 42);
uriVariables.put("hotel", 1);
- UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}");
+ UriTemplate template = new UriTemplate("/hotels/{hotel}/bookings/{booking}");
URI result = template.expand(uriVariables);
- assertEquals("Invalid expanded template", new URI("http://example.com/hotels/1/bookings/42"), result);
+ assertEquals("Invalid expanded template", new URI("/hotels/1/bookings/42"), result);
}
@Test
public void expandMapEncoded() throws Exception {
Map<String, String> uriVariables = Collections.singletonMap("hotel", "Z\u00fcrich");
- UriTemplate template = new UriTemplate("http://example.com/hotel list/{hotel}");
+ UriTemplate template = new UriTemplate("/hotel list/{hotel}");
URI result = template.expand(uriVariables);
- assertEquals("Invalid expanded template", new URI("http://example.com/hotel%20list/Z%C3%BCrich"), result);
+ assertEquals("Invalid expanded template", new URI("/hotel%20list/Z%C3%BCrich"), result);
}
@Test(expected = IllegalArgumentException.class)
public void expandMapUnboundVariables() throws Exception {
- Map<String, String> uriVariables = new HashMap<String, String>(2);
+ Map<String, String> uriVariables = new HashMap<>(2);
uriVariables.put("booking", "42");
uriVariables.put("bar", "1");
- UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}");
+ UriTemplate template = new UriTemplate("/hotels/{hotel}/bookings/{booking}");
template.expand(uriVariables);
}
@Test
public void expandEncoded() throws Exception {
- UriTemplate template = new UriTemplate("http://example.com/hotel list/{hotel}");
+ UriTemplate template = new UriTemplate("/hotel list/{hotel}");
URI result = template.expand("Z\u00fcrich");
- assertEquals("Invalid expanded template", new URI("http://example.com/hotel%20list/Z%C3%BCrich"), result);
+ assertEquals("Invalid expanded template", new URI("/hotel%20list/Z%C3%BCrich"), result);
}
@Test
public void matches() throws Exception {
- UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}");
- assertTrue("UriTemplate does not match", template.matches("http://example.com/hotels/1/bookings/42"));
- assertFalse("UriTemplate matches", template.matches("http://example.com/hotels/bookings"));
+ UriTemplate template = new UriTemplate("/hotels/{hotel}/bookings/{booking}");
+ assertTrue("UriTemplate does not match", template.matches("/hotels/1/bookings/42"));
+ assertFalse("UriTemplate matches", template.matches("/hotels/bookings"));
assertFalse("UriTemplate matches", template.matches(""));
assertFalse("UriTemplate matches", template.matches(null));
}
@Test
public void matchesCustomRegex() throws Exception {
- UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel:\\d+}");
- assertTrue("UriTemplate does not match", template.matches("http://example.com/hotels/42"));
- assertFalse("UriTemplate matches", template.matches("http://example.com/hotels/foo"));
+ UriTemplate template = new UriTemplate("/hotels/{hotel:\\d+}");
+ assertTrue("UriTemplate does not match", template.matches("/hotels/42"));
+ assertFalse("UriTemplate matches", template.matches("/hotels/foo"));
}
@Test
public void match() throws Exception {
- Map<String, String> expected = new HashMap<String, String>(2);
+ Map<String, String> expected = new HashMap<>(2);
expected.put("booking", "42");
expected.put("hotel", "1");
- UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}");
- Map<String, String> result = template.match("http://example.com/hotels/1/bookings/42");
+ UriTemplate template = new UriTemplate("/hotels/{hotel}/bookings/{booking}");
+ Map<String, String> result = template.match("/hotels/1/bookings/42");
assertEquals("Invalid match", expected, result);
}
@Test
public void matchCustomRegex() throws Exception {
- Map<String, String> expected = new HashMap<String, String>(2);
+ Map<String, String> expected = new HashMap<>(2);
expected.put("booking", "42");
expected.put("hotel", "1");
- UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel:\\d}/bookings/{booking:\\d+}");
- Map<String, String> result = template.match("http://example.com/hotels/1/bookings/42");
+ UriTemplate template = new UriTemplate("/hotels/{hotel:\\d}/bookings/{booking:\\d+}");
+ Map<String, String> result = template.match("/hotels/1/bookings/42");
assertEquals("Invalid match", expected, result);
}
@@ -164,7 +174,7 @@ public class UriTemplateTests {
public void matchMultipleInOneSegment() throws Exception {
UriTemplate template = new UriTemplate("/{foo}-{bar}");
Map<String, String> result = template.match("/12-34");
- Map<String, String> expected = new HashMap<String, String>(2);
+ Map<String, String> expected = new HashMap<>(2);
expected.put("foo", "12");
expected.put("bar", "34");
assertEquals("Invalid match", expected, result);
diff --git a/spring-web/src/test/java/org/springframework/web/util/UriUtilsTests.java b/spring-web/src/test/java/org/springframework/web/util/UriUtilsTests.java
index 6480c5fa..2227674a 100644
--- a/spring-web/src/test/java/org/springframework/web/util/UriUtilsTests.java
+++ b/spring-web/src/test/java/org/springframework/web/util/UriUtilsTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@ import static org.junit.Assert.*;
/**
* @author Arjen Poutsma
+ * @author Juergen Hoeller
*/
public class UriUtilsTests {
@@ -104,4 +105,22 @@ public class UriUtilsTests {
UriUtils.decode("foo%2", ENC);
}
+ @Test
+ public void extractFileExtension() {
+ assertEquals("html", UriUtils.extractFileExtension("index.html"));
+ assertEquals("html", UriUtils.extractFileExtension("/index.html"));
+ assertEquals("html", UriUtils.extractFileExtension("/products/view.html"));
+ assertEquals("html", UriUtils.extractFileExtension("/products/view.html#/a"));
+ assertEquals("html", UriUtils.extractFileExtension("/products/view.html#/path/a"));
+ assertEquals("html", UriUtils.extractFileExtension("/products/view.html#/path/a.do"));
+ assertEquals("html", UriUtils.extractFileExtension("/products/view.html?param=a"));
+ assertEquals("html", UriUtils.extractFileExtension("/products/view.html?param=/path/a"));
+ assertEquals("html", UriUtils.extractFileExtension("/products/view.html?param=/path/a.do"));
+ assertEquals("html", UriUtils.extractFileExtension("/products/view.html?param=/path/a#/path/a"));
+ assertEquals("html", UriUtils.extractFileExtension("/products/view.html?param=/path/a.do#/path/a.do"));
+ assertEquals("html", UriUtils.extractFileExtension("/products;q=11/view.html?param=/path/a.do"));
+ assertEquals("html", UriUtils.extractFileExtension("/products;q=11/view.html;r=22?param=/path/a.do"));
+ assertEquals("html", UriUtils.extractFileExtension("/products;q=11/view.html;r=22;s=33?param=/path/a.do"));
+ }
+
}
diff --git a/spring-web/src/test/resources/org/springframework/http/converter/byterangeresource.txt b/spring-web/src/test/resources/org/springframework/http/converter/byterangeresource.txt
new file mode 100644
index 00000000..84bbb9dd
--- /dev/null
+++ b/spring-web/src/test/resources/org/springframework/http/converter/byterangeresource.txt
@@ -0,0 +1 @@
+Spring Framework test resource content. \ No newline at end of file
diff --git a/spring-web/src/test/resources/org/springframework/web/util/HtmlCharacterEntityReferences.dtd b/spring-web/src/test/resources/org/springframework/web/util/HtmlCharacterEntityReferences.dtd
index 31aa2524..86e8cbab 100644
--- a/spring-web/src/test/resources/org/springframework/web/util/HtmlCharacterEntityReferences.dtd
+++ b/spring-web/src/test/resources/org/springframework/web/util/HtmlCharacterEntityReferences.dtd
@@ -1,11 +1,9 @@
-<!-- File containing all charcter entity references definied
+<!-- File containing all character entity references defined
by the HTML 4.0 standard. -->
-<!-- Valuable informations and a complete description of the
+<!-- Valuable information and a complete description of the
HTML 4.0 character set can be found at
- http://www.w3.org/TR/html4/charset.html.
- -->
-
-
+ http://www.w3.org/TR/html4/charset.html. -->
+
<!-- Portions © International Organization for Standardization 1986
Permission to copy in any form is granted for use with
conforming SGML systems and applications as defined in
diff --git a/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/PortletContextResource.java b/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/PortletContextResource.java
index 492d1432..a1099f6f 100644
--- a/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/PortletContextResource.java
+++ b/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/PortletContextResource.java
@@ -77,6 +77,7 @@ public class PortletContextResource extends AbstractFileResolvingResource implem
this.path = pathToUse;
}
+
/**
* Return the PortletContext for this resource.
*/
@@ -91,7 +92,6 @@ public class PortletContextResource extends AbstractFileResolvingResource implem
return this.path;
}
-
/**
* This implementation checks {@code PortletContext.getResource}.
* @see javax.portlet.PortletContext#getResource(String)
diff --git a/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/PortletRequestAttributes.java b/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/PortletRequestAttributes.java
index c1f5b1eb..936dfdec 100644
--- a/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/PortletRequestAttributes.java
+++ b/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/PortletRequestAttributes.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -107,15 +107,24 @@ public class PortletRequestAttributes extends AbstractRequestAttributes {
*/
protected final PortletSession getSession(boolean allowCreate) {
if (isRequestActive()) {
- return this.request.getPortletSession(allowCreate);
+ PortletSession session = this.request.getPortletSession(allowCreate);
+ this.session = session;
+ return session;
}
else {
// Access through stored session reference, if any...
- if (this.session == null && allowCreate) {
- throw new IllegalStateException(
- "No session found and request already completed - cannot create new session!");
+ PortletSession session = this.session;
+ if (session == null) {
+ if (allowCreate) {
+ throw new IllegalStateException(
+ "No session found and request already completed - cannot create new session!");
+ }
+ else {
+ session = this.request.getPortletSession(false);
+ this.session = session;
+ }
}
- return this.session;
+ return session;
}
}
@@ -147,9 +156,7 @@ public class PortletRequestAttributes extends AbstractRequestAttributes {
return value;
}
}
- else {
- return null;
- }
+ return null;
}
}
@@ -217,9 +224,7 @@ public class PortletRequestAttributes extends AbstractRequestAttributes {
return StringUtils.toStringArray(session.getAttributeNames());
}
}
- else {
- return new String[0];
- }
+ return new String[0];
}
}
@@ -263,32 +268,34 @@ public class PortletRequestAttributes extends AbstractRequestAttributes {
*/
@Override
protected void updateAccessedSessionAttributes() {
- this.session = this.request.getPortletSession(false);
- if (this.session != null) {
- try {
- for (Map.Entry<String, Object> entry : this.sessionAttributesToUpdate.entrySet()) {
- String name = entry.getKey();
- Object newValue = entry.getValue();
- Object oldValue = this.session.getAttribute(name);
- if (oldValue == newValue) {
- this.session.setAttribute(name, newValue);
+ if (!this.sessionAttributesToUpdate.isEmpty() || !this.globalSessionAttributesToUpdate.isEmpty()) {
+ PortletSession session = getSession(false);
+ if (session != null) {
+ try {
+ for (Map.Entry<String, Object> entry : this.sessionAttributesToUpdate.entrySet()) {
+ String name = entry.getKey();
+ Object newValue = entry.getValue();
+ Object oldValue = session.getAttribute(name);
+ if (oldValue == newValue) {
+ session.setAttribute(name, newValue);
+ }
}
- }
- for (Map.Entry<String, Object> entry : this.globalSessionAttributesToUpdate.entrySet()) {
- String name = entry.getKey();
- Object newValue = entry.getValue();
- Object oldValue = this.session.getAttribute(name, PortletSession.APPLICATION_SCOPE);
- if (oldValue == newValue) {
- this.session.setAttribute(name, newValue, PortletSession.APPLICATION_SCOPE);
+ for (Map.Entry<String, Object> entry : this.globalSessionAttributesToUpdate.entrySet()) {
+ String name = entry.getKey();
+ Object newValue = entry.getValue();
+ Object oldValue = session.getAttribute(name, PortletSession.APPLICATION_SCOPE);
+ if (oldValue == newValue) {
+ session.setAttribute(name, newValue, PortletSession.APPLICATION_SCOPE);
+ }
}
}
+ catch (IllegalStateException ex) {
+ // Session invalidated - shouldn't usually happen.
+ }
}
- catch (IllegalStateException ex) {
- // Session invalidated - shouldn't usually happen.
- }
+ this.sessionAttributesToUpdate.clear();
+ this.globalSessionAttributesToUpdate.clear();
}
- this.sessionAttributesToUpdate.clear();
- this.globalSessionAttributesToUpdate.clear();
}
/**
diff --git a/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/PortletRequestHandledEvent.java b/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/PortletRequestHandledEvent.java
index cf52db05..ce047869 100644
--- a/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/PortletRequestHandledEvent.java
+++ b/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/PortletRequestHandledEvent.java
@@ -100,7 +100,7 @@ public class PortletRequestHandledEvent extends RequestHandledEvent {
}
/**
- * Return the the type of Portlet Request ('action' or 'render').
+ * Return the type of Portlet Request ('action' or 'render').
*/
public String getRequestType() {
return this.requestType;
diff --git a/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/handler/SimplePortletPostProcessor.java b/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/handler/SimplePortletPostProcessor.java
index 62a79e4b..ba393948 100644
--- a/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/handler/SimplePortletPostProcessor.java
+++ b/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/handler/SimplePortletPostProcessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@ package org.springframework.web.portlet.handler;
import java.util.Collections;
import java.util.Enumeration;
-import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
@@ -36,8 +35,9 @@ import org.springframework.web.portlet.context.PortletConfigAware;
import org.springframework.web.portlet.context.PortletContextAware;
/**
- * Bean post-processor that applies initialization and destruction callbacks
- * to beans that implement the Portlet interface.
+ * {@link org.springframework.beans.factory.config.BeanPostProcessor}
+ * that applies initialization and destruction callbacks to beans that
+ * implement the {@link javax.portlet.Portlet} interface.
*
* <p>After initialization of the bean instance, the Portlet {@code init}
* method will be called with a PortletConfig that contains the bean name
@@ -51,15 +51,16 @@ import org.springframework.web.portlet.context.PortletContextAware;
* supposed to be configured like any other Spring bean, that is, through
* constructor arguments or bean properties.
*
- * <p>For reuse of a Portlet implementation in a plain Portlet container and as
- * a bean in a Spring context, consider deriving from Spring's GenericPortletBean
- * base class that applies Portlet initialization parameters as bean properties,
- * supporting both initialization styles.
+ * <p>For reuse of a Portlet implementation in a plain Portlet container
+ * and as a bean in a Spring context, consider deriving from Spring's
+ * {@link org.springframework.web.portlet.GenericPortletBean} base class that
+ * applies Portlet initialization parameters as bean properties, supporting
+ * both the standard Portlet and the Spring bean initialization style.
*
* <p><b>Alternatively, consider wrapping a Portlet with Spring's
- * PortletWrappingController.</b> This is particularly appropriate for
- * existing Portlet classes, allowing to specify Portlet initialization
- * parameters etc.
+ * {@link org.springframework.web.portlet.mvc.PortletWrappingController}.</b>
+ * This is particularly appropriate for existing Portlet classes,
+ * allowing to specify Portlet initialization parameters etc.
*
* @author Juergen Hoeller
* @author John A. Lewis
@@ -132,6 +133,11 @@ public class SimplePortletPostProcessor
}
}
+ @Override
+ public boolean requiresDestruction(Object bean) {
+ return (bean instanceof Portlet);
+ }
+
/**
* Internal implementation of the PortletConfig interface, to be passed
@@ -168,7 +174,7 @@ public class SimplePortletPostProcessor
@Override
public Enumeration<String> getInitParameterNames() {
- return Collections.enumeration(new HashSet<String>());
+ return Collections.enumeration(Collections.<String>emptySet());
}
@Override
@@ -178,7 +184,7 @@ public class SimplePortletPostProcessor
@Override
public Enumeration<String> getPublicRenderParameterNames() {
- return Collections.enumeration(new HashSet<String>());
+ return Collections.enumeration(Collections.<String>emptySet());
}
@Override
@@ -188,17 +194,17 @@ public class SimplePortletPostProcessor
@Override
public Enumeration<QName> getPublishingEventQNames() {
- return Collections.enumeration(new HashSet<QName>());
+ return Collections.enumeration(Collections.<QName>emptySet());
}
@Override
public Enumeration<QName> getProcessingEventQNames() {
- return Collections.enumeration(new HashSet<QName>());
+ return Collections.enumeration(Collections.<QName>emptySet());
}
@Override
public Enumeration<Locale> getSupportedLocales() {
- return Collections.enumeration(new HashSet<Locale>());
+ return Collections.enumeration(Collections.<Locale>emptySet());
}
@Override
diff --git a/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/mvc/PortletWrappingController.java b/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/mvc/PortletWrappingController.java
index 57d1d123..e14cf7ea 100644
--- a/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/mvc/PortletWrappingController.java
+++ b/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/mvc/PortletWrappingController.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@ package org.springframework.web.portlet.mvc;
import java.util.Collections;
import java.util.Enumeration;
-import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
@@ -288,7 +287,7 @@ public class PortletWrappingController extends AbstractController
@Override
public Enumeration<String> getPublicRenderParameterNames() {
- return Collections.enumeration(new HashSet<String>());
+ return Collections.enumeration(Collections.<String>emptySet());
}
@Override
@@ -298,17 +297,17 @@ public class PortletWrappingController extends AbstractController
@Override
public Enumeration<QName> getPublishingEventQNames() {
- return Collections.enumeration(new HashSet<QName>());
+ return Collections.enumeration(Collections.<QName>emptySet());
}
@Override
public Enumeration<QName> getProcessingEventQNames() {
- return Collections.enumeration(new HashSet<QName>());
+ return Collections.enumeration(Collections.<QName>emptySet());
}
@Override
public Enumeration<Locale> getSupportedLocales() {
- return Collections.enumeration(new HashSet<Locale>());
+ return Collections.enumeration(Collections.<Locale>emptySet());
}
@Override
diff --git a/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/AnnotationMethodHandlerAdapter.java b/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/AnnotationMethodHandlerAdapter.java
index 6476c54d..017bc18a 100644
--- a/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/AnnotationMethodHandlerAdapter.java
+++ b/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/AnnotationMethodHandlerAdapter.java
@@ -76,8 +76,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
-import org.springframework.web.bind.annotation.support.HandlerMethodInvoker;
-import org.springframework.web.bind.annotation.support.HandlerMethodResolver;
import org.springframework.web.bind.support.DefaultSessionAttributeStore;
import org.springframework.web.bind.support.SessionAttributeStore;
import org.springframework.web.bind.support.WebArgumentResolver;
@@ -434,9 +432,10 @@ public class AnnotationMethodHandlerAdapter extends PortletContentGenerator
/**
- * Portlet-specific subclass of {@link HandlerMethodResolver}.
+ * Portlet-specific subclass of {@code HandlerMethodResolver}.
*/
- private static class PortletHandlerMethodResolver extends HandlerMethodResolver {
+ @SuppressWarnings("deprecation")
+ private static class PortletHandlerMethodResolver extends org.springframework.web.bind.annotation.support.HandlerMethodResolver {
private final Map<Method, RequestMappingInfo> mappings = new HashMap<Method, RequestMappingInfo>();
@@ -545,11 +544,12 @@ public class AnnotationMethodHandlerAdapter extends PortletContentGenerator
/**
- * Portlet-specific subclass of {@link HandlerMethodInvoker}.
+ * Portlet-specific subclass of {@code HandlerMethodInvoker}.
*/
- private class PortletHandlerMethodInvoker extends HandlerMethodInvoker {
+ @SuppressWarnings("deprecation")
+ private class PortletHandlerMethodInvoker extends org.springframework.web.bind.annotation.support.HandlerMethodInvoker {
- public PortletHandlerMethodInvoker(HandlerMethodResolver resolver) {
+ public PortletHandlerMethodInvoker(org.springframework.web.bind.annotation.support.HandlerMethodResolver resolver) {
super(resolver, webBindingInitializer, sessionAttributeStore,
parameterNameDiscoverer, customArgumentResolvers, null);
}
diff --git a/spring-webmvc-portlet/src/test/java/org/springframework/mock/web/portlet/ServletWrappingPortletContext.java b/spring-webmvc-portlet/src/test/java/org/springframework/mock/web/portlet/ServletWrappingPortletContext.java
index 545f159e..716f71b0 100644
--- a/spring-webmvc-portlet/src/test/java/org/springframework/mock/web/portlet/ServletWrappingPortletContext.java
+++ b/spring-webmvc-portlet/src/test/java/org/springframework/mock/web/portlet/ServletWrappingPortletContext.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,6 @@ import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.Enumeration;
-import java.util.HashSet;
import java.util.Set;
import javax.portlet.PortletContext;
import javax.portlet.PortletRequestDispatcher;
@@ -35,7 +34,7 @@ import org.springframework.util.Assert;
*
* @author Juergen Hoeller
* @since 3.0
- * @see org.springframework.mock.web.portlet.MockPortletContext
+ * @see MockPortletContext
*/
public class ServletWrappingPortletContext implements PortletContext {
@@ -156,7 +155,7 @@ public class ServletWrappingPortletContext implements PortletContext {
@Override
public Enumeration<String> getContainerRuntimeOptions() {
- return Collections.enumeration(new HashSet<String>());
+ return Collections.enumeration(Collections.<String>emptySet());
}
}
diff --git a/spring-webmvc-portlet/src/test/java/org/springframework/web/portlet/context/PortletRequestAttributesTests.java b/spring-webmvc-portlet/src/test/java/org/springframework/web/portlet/context/PortletRequestAttributesTests.java
index 4875a2d6..40237e81 100644
--- a/spring-webmvc-portlet/src/test/java/org/springframework/web/portlet/context/PortletRequestAttributesTests.java
+++ b/spring-webmvc-portlet/src/test/java/org/springframework/web/portlet/context/PortletRequestAttributesTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.web.portlet.context;
import java.io.Serializable;
import javax.portlet.PortletRequest;
+import javax.portlet.PortletSession;
import org.junit.Test;
@@ -37,12 +38,12 @@ public class PortletRequestAttributesTests {
private static final String KEY = "ThatThingThatThing";
-
@SuppressWarnings("serial")
- private static final Serializable VALUE = new Serializable() { };
+ private static final Serializable VALUE = new Serializable() {
+ };
- @Test(expected=IllegalArgumentException.class)
+ @Test(expected = IllegalArgumentException.class)
public void testCtorRejectsNullArg() throws Exception {
new PortletRequestAttributes(null);
}
@@ -85,53 +86,51 @@ public class PortletRequestAttributesTests {
@Test
public void testSetSessionScopedAttribute() throws Exception {
MockPortletSession session = new MockPortletSession();
- session.setAttribute(KEY, VALUE);
+ session.setAttribute(KEY, VALUE, PortletSession.PORTLET_SCOPE);
MockPortletRequest request = new MockPortletRequest();
request.setSession(session);
PortletRequestAttributes attrs = new PortletRequestAttributes(request);
attrs.setAttribute(KEY, VALUE, RequestAttributes.SCOPE_SESSION);
- Object value = session.getAttribute(KEY);
- assertSame(VALUE, value);
+ assertSame(VALUE, session.getAttribute(KEY, PortletSession.PORTLET_SCOPE));
}
@Test
public void testSetSessionScopedAttributeAfterCompletion() throws Exception {
MockPortletSession session = new MockPortletSession();
- session.setAttribute(KEY, VALUE);
+ session.setAttribute(KEY, VALUE, PortletSession.PORTLET_SCOPE);
MockPortletRequest request = new MockPortletRequest();
request.setSession(session);
PortletRequestAttributes attrs = new PortletRequestAttributes(request);
+ assertSame(VALUE, attrs.getAttribute(KEY, RequestAttributes.SCOPE_SESSION));
attrs.requestCompleted();
request.close();
attrs.setAttribute(KEY, VALUE, RequestAttributes.SCOPE_SESSION);
- Object value = session.getAttribute(KEY);
- assertSame(VALUE, value);
+ assertSame(VALUE, session.getAttribute(KEY, PortletSession.PORTLET_SCOPE));
}
@Test
public void testSetGlobalSessionScopedAttribute() throws Exception {
MockPortletSession session = new MockPortletSession();
- session.setAttribute(KEY, VALUE);
+ session.setAttribute(KEY, VALUE, PortletSession.APPLICATION_SCOPE);
MockPortletRequest request = new MockPortletRequest();
request.setSession(session);
PortletRequestAttributes attrs = new PortletRequestAttributes(request);
attrs.setAttribute(KEY, VALUE, RequestAttributes.SCOPE_GLOBAL_SESSION);
- Object value = session.getAttribute(KEY);
- assertSame(VALUE, value);
+ assertSame(VALUE, session.getAttribute(KEY, PortletSession.APPLICATION_SCOPE));
}
@Test
public void testSetGlobalSessionScopedAttributeAfterCompletion() throws Exception {
MockPortletSession session = new MockPortletSession();
- session.setAttribute(KEY, VALUE);
+ session.setAttribute(KEY, VALUE, PortletSession.APPLICATION_SCOPE);
MockPortletRequest request = new MockPortletRequest();
request.setSession(session);
PortletRequestAttributes attrs = new PortletRequestAttributes(request);
+ assertSame(VALUE, attrs.getAttribute(KEY, RequestAttributes.SCOPE_GLOBAL_SESSION));
attrs.requestCompleted();
request.close();
attrs.setAttribute(KEY, VALUE, RequestAttributes.SCOPE_GLOBAL_SESSION);
- Object value = session.getAttribute(KEY);
- assertSame(VALUE, value);
+ assertSame(VALUE, session.getAttribute(KEY, PortletSession.APPLICATION_SCOPE));
}
@Test
diff --git a/spring-webmvc-portlet/src/test/java/org/springframework/web/portlet/context/XmlPortletApplicationContextTests.java b/spring-webmvc-portlet/src/test/java/org/springframework/web/portlet/context/XmlPortletApplicationContextTests.java
index 67a4530e..b4f3e8e9 100644
--- a/spring-webmvc-portlet/src/test/java/org/springframework/web/portlet/context/XmlPortletApplicationContextTests.java
+++ b/spring-webmvc-portlet/src/test/java/org/springframework/web/portlet/context/XmlPortletApplicationContextTests.java
@@ -58,7 +58,7 @@ public class XmlPortletApplicationContextTests extends AbstractXmlWebApplication
beanFactory.addBeanPostProcessor(new BeanPostProcessor() {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
- if(bean instanceof TestBean) {
+ if (bean instanceof TestBean) {
((TestBean) bean).getFriends().add("myFriend");
}
return bean;
diff --git a/spring-webmvc-portlet/src/test/java/org/springframework/web/portlet/mvc/PortletWrappingControllerTests.java b/spring-webmvc-portlet/src/test/java/org/springframework/web/portlet/mvc/PortletWrappingControllerTests.java
index 3a5df549..62532a76 100644
--- a/spring-webmvc-portlet/src/test/java/org/springframework/web/portlet/mvc/PortletWrappingControllerTests.java
+++ b/spring-webmvc-portlet/src/test/java/org/springframework/web/portlet/mvc/PortletWrappingControllerTests.java
@@ -162,9 +162,11 @@ public final class PortletWrappingControllerTests {
public void processAction(ActionRequest request, ActionResponse response) throws PortletException {
if (request.getParameter("test") != null) {
response.setRenderParameter(RESULT_RENDER_PARAMETER_NAME, "myPortlet-action");
- } else if (request.getParameter(PORTLET_NAME_ACTION_REQUEST_PARAMETER_NAME) != null) {
+ }
+ else if (request.getParameter(PORTLET_NAME_ACTION_REQUEST_PARAMETER_NAME) != null) {
response.setRenderParameter(RESULT_RENDER_PARAMETER_NAME, getPortletConfig().getPortletName());
- } else {
+ }
+ else {
throw new IllegalArgumentException("no request parameters");
}
}
diff --git a/spring-webmvc-portlet/src/test/java/org/springframework/web/portlet/util/PortletUtilsTests.java b/spring-webmvc-portlet/src/test/java/org/springframework/web/portlet/util/PortletUtilsTests.java
index 52c80665..7ab898b2 100644
--- a/spring-webmvc-portlet/src/test/java/org/springframework/web/portlet/util/PortletUtilsTests.java
+++ b/spring-webmvc-portlet/src/test/java/org/springframework/web/portlet/util/PortletUtilsTests.java
@@ -435,12 +435,13 @@ public final class PortletUtilsTests {
public void testGetRequiredSessionAttributeWithExistingSessionAndNoAttribute() throws Exception {
MockPortletSession session = new MockPortletSession();
- final PortletRequest request = mock(PortletRequest.class);
+ PortletRequest request = mock(PortletRequest.class);
given(request.getPortletSession(false)).willReturn(session);
try {
PortletUtils.getRequiredSessionAttribute(request, "foo");
fail("expected IllegalStateException");
- } catch (IllegalStateException ex) { /* expected */ }
+ }
+ catch (IllegalStateException ex) { /* expected */ }
}
diff --git a/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/AbstractSpringPreparerFactory.java b/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/AbstractSpringPreparerFactory.java
index 561be3d9..0801804d 100644
--- a/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/AbstractSpringPreparerFactory.java
+++ b/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/AbstractSpringPreparerFactory.java
@@ -25,10 +25,13 @@ import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
/**
- * Abstract implementation of the Tiles2 {@link org.apache.tiles.preparer.PreparerFactory}
+ * Abstract implementation of the Tiles {@link org.apache.tiles.preparer.PreparerFactory}
* interface, obtaining the current Spring WebApplicationContext and delegating to
* {@link #getPreparer(String, org.springframework.web.context.WebApplicationContext)}.
*
+ * <p><b>NOTE: Tiles 2 support is deprecated in favor of Tiles 3 and will be removed
+ * as of Spring Framework 5.0.</b>.
+ *
* @author Juergen Hoeller
* @since 2.5
* @see #getPreparer(String, org.springframework.web.context.WebApplicationContext)
diff --git a/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/SimpleSpringPreparerFactory.java b/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/SimpleSpringPreparerFactory.java
index df596415..05292d6d 100644
--- a/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/SimpleSpringPreparerFactory.java
+++ b/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/SimpleSpringPreparerFactory.java
@@ -27,11 +27,14 @@ import org.apache.tiles.preparer.ViewPreparer;
import org.springframework.web.context.WebApplicationContext;
/**
- * Tiles2 {@link org.apache.tiles.preparer.PreparerFactory} implementation
+ * Tiles {@link org.apache.tiles.preparer.PreparerFactory} implementation
* that expects preparer class names and builds preparer instances for those,
* creating them through the Spring ApplicationContext in order to apply
* Spring container callbacks and configured Spring BeanPostProcessors.
*
+ * <p><b>NOTE: Tiles 2 support is deprecated in favor of Tiles 3 and will be removed
+ * as of Spring Framework 5.0.</b>.
+ *
* @author Juergen Hoeller
* @since 2.5
* @see SpringBeanPreparerFactory
diff --git a/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/SpringBeanPreparerFactory.java b/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/SpringBeanPreparerFactory.java
index 1bd0e6b2..fc9ff0a8 100644
--- a/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/SpringBeanPreparerFactory.java
+++ b/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/SpringBeanPreparerFactory.java
@@ -22,12 +22,15 @@ import org.apache.tiles.preparer.ViewPreparer;
import org.springframework.web.context.WebApplicationContext;
/**
- * Tiles2 {@link org.apache.tiles.preparer.PreparerFactory} implementation
+ * Tiles {@link org.apache.tiles.preparer.PreparerFactory} implementation
* that expects preparer bean names and obtains preparer beans from the
* Spring ApplicationContext. The full bean creation process will be in
* the control of the Spring application context in this case, allowing
* for the use of scoped beans etc.
*
+ * <p><b>NOTE: Tiles 2 support is deprecated in favor of Tiles 3 and will be removed
+ * as of Spring Framework 5.0.</b>.
+ *
* @author Juergen Hoeller
* @since 2.5
* @see SimpleSpringPreparerFactory
diff --git a/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/SpringLocaleResolver.java b/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/SpringLocaleResolver.java
index c3f0218f..39daf47b 100644
--- a/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/SpringLocaleResolver.java
+++ b/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/SpringLocaleResolver.java
@@ -36,6 +36,9 @@ import org.springframework.web.servlet.support.RequestContextUtils;
* If you are using standard Tiles bootstrap, specify the name of this class
* as value for the init-param "org.apache.tiles.locale.LocaleResolver".
*
+ * <p><b>NOTE: Tiles 2 support is deprecated in favor of Tiles 3 and will be removed
+ * as of Spring Framework 5.0.</b>.
+ *
* @author Juergen Hoeller
* @since 2.5
* @see org.apache.tiles.definition.UrlDefinitionsFactory#LOCALE_RESOLVER_IMPL_PROPERTY
diff --git a/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/SpringWildcardServletTilesApplicationContext.java b/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/SpringWildcardServletTilesApplicationContext.java
index ed5d1d5c..488dde4d 100644
--- a/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/SpringWildcardServletTilesApplicationContext.java
+++ b/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/SpringWildcardServletTilesApplicationContext.java
@@ -33,6 +33,9 @@ import org.springframework.web.context.support.ServletContextResourcePatternReso
/**
* Spring-specific subclass of the Tiles ServletTilesApplicationContext.
*
+ * <p><b>NOTE: Tiles 2 support is deprecated in favor of Tiles 3 and will be removed
+ * as of Spring Framework 5.0.</b>.
+ *
* @author Juergen Hoeller
* @since 4.0.1
* @deprecated as of Spring 4.2, in favor of Tiles 3
diff --git a/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/TilesConfigurer.java b/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/TilesConfigurer.java
index 92b88a4f..719c4510 100644
--- a/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/TilesConfigurer.java
+++ b/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/TilesConfigurer.java
@@ -102,6 +102,9 @@ import org.springframework.web.context.ServletContextAware;
* The values in the list are the actual Tiles XML files containing the definitions.
* If the list is not specified, the default is {@code "/WEB-INF/tiles.xml"}.
*
+ * <p><b>NOTE: Tiles 2 support is deprecated in favor of Tiles 3 and will be removed
+ * as of Spring Framework 5.0.</b>.
+ *
* @author Juergen Hoeller
* @since 2.5
* @see TilesView
diff --git a/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/TilesView.java b/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/TilesView.java
index 16233561..60a42109 100644
--- a/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/TilesView.java
+++ b/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/TilesView.java
@@ -39,7 +39,7 @@ import org.springframework.web.servlet.view.AbstractUrlBasedView;
* {@link org.springframework.web.servlet.View} implementation that retrieves a
* Tiles definition. The "url" property is interpreted as name of a Tiles definition.
*
- * <p>This class builds on Tiles2, which requires JSP 2.0.
+ * <p>This class builds on Tiles, which requires JSP 2.0.
* JSTL support is integrated out of the box due to JSTL's inclusion in JSP 2.0.
* <b>Note: Spring 4.0 requires Tiles 2.2.2.</b>
*
@@ -47,6 +47,9 @@ import org.springframework.web.servlet.view.AbstractUrlBasedView;
* the ServletContext. This container is typically set up via a
* {@link TilesConfigurer} bean definition in the application context.
*
+ * <p><b>NOTE: Tiles 2 support is deprecated in favor of Tiles 3 and will be removed
+ * as of Spring Framework 5.0.</b>.
+ *
* @author Juergen Hoeller
* @author Sebastien Deleuze
* @since 2.5
diff --git a/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/TilesViewResolver.java b/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/TilesViewResolver.java
index 8c179ce5..f5c8fb3a 100644
--- a/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/TilesViewResolver.java
+++ b/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/TilesViewResolver.java
@@ -30,6 +30,9 @@ import org.springframework.web.servlet.view.UrlBasedViewResolver;
* check for the existence of the specified template resources and only return
* a non-null View object if the template was actually found.
*
+ * <p><b>NOTE: Tiles 2 support is deprecated in favor of Tiles 3 and will be removed
+ * as of Spring Framework 5.0.</b>.
+ *
* @author Juergen Hoeller
* @author Sebastien Deleuze
* @since 3.0
diff --git a/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/package-info.java b/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/package-info.java
index 6911a970..2bb31405 100644
--- a/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/package-info.java
+++ b/spring-webmvc-tiles2/src/main/java/org/springframework/web/servlet/view/tiles2/package-info.java
@@ -1,7 +1,10 @@
/**
* Support classes for the integration of
- * <a href="http://tiles.apache.org">Tiles2</a>
+ * <a href="http://tiles.apache.org">Tiles 2</a>
* (the standalone version of Tiles) as Spring web view technology.
* Contains a View implementation for Tiles definitions.
+ *
+ * <p><b>NOTE: Tiles 2 support is deprecated in favor of Tiles 3
+ * and will be removed as of Spring Framework 5.0.</b>.
*/
package org.springframework.web.servlet.view.tiles2;
diff --git a/spring-webmvc-tiles2/src/test/resources/jasperreports.properties b/spring-webmvc-tiles2/src/test/resources/jasperreports.properties
deleted file mode 100644
index 967f217c..00000000
--- a/spring-webmvc-tiles2/src/test/resources/jasperreports.properties
+++ /dev/null
@@ -1 +0,0 @@
-net.sf.jasperreports.awt.ignore.missing.font=true \ No newline at end of file
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java
index 38d16f9e..e18f57e2 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java
@@ -348,6 +348,7 @@ public class DispatcherServlet extends FrameworkServlet {
*/
public DispatcherServlet() {
super();
+ setDispatchOptionsRequest(true);
}
/**
@@ -391,6 +392,7 @@ public class DispatcherServlet extends FrameworkServlet {
*/
public DispatcherServlet(WebApplicationContext webApplicationContext) {
super(webApplicationContext);
+ setDispatchOptionsRequest(true);
}
@@ -970,13 +972,19 @@ public class DispatcherServlet extends FrameworkServlet {
catch (Exception ex) {
dispatchException = ex;
}
+ catch (Throwable err) {
+ // As of 4.3, we're processing Errors thrown from handler methods as well,
+ // making them available for @ExceptionHandler methods and other scenarios.
+ dispatchException = new NestedServletException("Handler dispatch failed", err);
+ }
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
- triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
+ triggerAfterCompletion(processedRequest, response, mappedHandler,
+ new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
@@ -1243,6 +1251,9 @@ public class DispatcherServlet extends FrameworkServlet {
logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
}
try {
+ if (mv.getStatus() != null) {
+ response.setStatus(mv.getStatus().value());
+ }
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
@@ -1299,16 +1310,6 @@ public class DispatcherServlet extends FrameworkServlet {
throw ex;
}
- private void triggerAfterCompletionWithError(HttpServletRequest request, HttpServletResponse response,
- HandlerExecutionChain mappedHandler, Throwable error) throws Exception {
-
- ServletException ex = new NestedServletException("Handler processing failed", error);
- if (mappedHandler != null) {
- mappedHandler.triggerAfterCompletion(request, response, ex);
- }
- throw ex;
- }
-
/**
* Restore the request attributes after an include.
* @param request current HTTP request
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java
index 0cf705a9..5b56e210 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java
@@ -426,9 +426,11 @@ public abstract class FrameworkServlet extends HttpServletBean implements Applic
/**
* Set whether this servlet should dispatch an HTTP OPTIONS request to
* the {@link #doService} method.
- * <p>Default is "false", applying {@link javax.servlet.http.HttpServlet}'s
- * default behavior (i.e. enumerating all standard HTTP request methods
- * as a response to the OPTIONS request).
+ * <p>Default in the {@code FrameworkServlet} is "false", applying
+ * {@link javax.servlet.http.HttpServlet}'s default behavior (i.e.enumerating
+ * all standard HTTP request methods as a response to the OPTIONS request).
+ * Note however that as of 4.3 the {@code DispatcherServlet} sets this
+ * property to "true" by default due to its built-in support for OPTIONS.
* <p>Turn this flag on if you prefer OPTIONS requests to go through the
* regular dispatching chain, just like other HTTP requests. This usually
* means that your controllers will receive those requests; make sure
@@ -836,7 +838,8 @@ public abstract class FrameworkServlet extends HttpServletBean implements Applic
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
- if (HttpMethod.PATCH.matches(request.getMethod())) {
+ HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
+ if (HttpMethod.PATCH == httpMethod || httpMethod == null) {
processRequest(request, response);
}
else {
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerInterceptor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerInterceptor.java
index 6f8e8036..e49eeafb 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerInterceptor.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerInterceptor.java
@@ -93,7 +93,7 @@ public interface HandlerInterceptor {
* @throws Exception in case of errors
*/
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
- throws Exception;
+ throws Exception;
/**
* Intercept the execution of a handler. Called after HandlerAdapter actually
@@ -114,7 +114,8 @@ public interface HandlerInterceptor {
* (can also be {@code null})
* @throws Exception in case of errors
*/
- void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
+ void postHandle(
+ HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception;
/**
@@ -136,7 +137,8 @@ public interface HandlerInterceptor {
* @param ex exception thrown on handler execution, if any
* @throws Exception in case of errors
*/
- void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
+ void afterCompletion(
+ HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception;
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/LocaleResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/LocaleResolver.java
index 80998d0f..2d0f9540 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/LocaleResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/LocaleResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -51,8 +51,8 @@ import javax.servlet.http.HttpServletResponse;
public interface LocaleResolver {
/**
- * Resolve the current locale via the given request. Can return a default locale as
- * fallback in any case.
+ * Resolve the current locale via the given request.
+ * Can return a default locale as fallback in any case.
* @param request the request to resolve the locale for
* @return the current locale (never {@code null})
*/
@@ -63,8 +63,8 @@ public interface LocaleResolver {
* @param request the request to be used for locale modification
* @param response the response to be used for locale modification
* @param locale the new locale, or {@code null} to clear the locale
- * @throws UnsupportedOperationException if the LocaleResolver implementation does not
- * support dynamic changing of the locale
+ * @throws UnsupportedOperationException if the LocaleResolver
+ * implementation does not support dynamic changing of the locale
*/
void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale);
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/ModelAndView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/ModelAndView.java
index d58ea7de..66fd7429 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/ModelAndView.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/ModelAndView.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.web.servlet;
import java.util.Map;
+import org.springframework.http.HttpStatus;
import org.springframework.ui.ModelMap;
import org.springframework.util.CollectionUtils;
@@ -36,6 +37,7 @@ import org.springframework.util.CollectionUtils;
* @author Rod Johnson
* @author Juergen Hoeller
* @author Rob Harrop
+ * @author Rossen Stoyanchev
* @see DispatcherServlet
* @see ViewResolver
* @see HandlerAdapter#handle
@@ -49,6 +51,9 @@ public class ModelAndView {
/** Model Map */
private ModelMap model;
+ /** Optional HTTP status for the response */
+ private HttpStatus status;
+
/** Indicates whether or not this instance has been cleared with a call to {@link #clear()} */
private boolean cleared = false;
@@ -116,6 +121,24 @@ public class ModelAndView {
}
/**
+ * Creates new ModelAndView given a view name, model, and status.
+ * @param viewName name of the View to render, to be resolved
+ * by the DispatcherServlet's ViewResolver
+ * @param model Map of model names (Strings) to model objects
+ * (Objects). Model entries may not be {@code null}, but the
+ * model Map may be {@code null} if there is no model data.
+ * @param status an alternative status code to use for the response.
+ * @since 4.3
+ */
+ public ModelAndView(String viewName, Map<String, ?> model, HttpStatus status) {
+ this.view = viewName;
+ if (model != null) {
+ getModelMap().addAllAttributes(model);
+ }
+ this.status = status;
+ }
+
+ /**
* Convenient constructor to take a single model object.
* @param viewName name of the View to render, to be resolved
* by the DispatcherServlet's ViewResolver
@@ -215,6 +238,22 @@ public class ModelAndView {
return getModelMap();
}
+ /**
+ * Set the HTTP status to use for the response.
+ * @since 4.3
+ */
+ public void setStatus(HttpStatus status) {
+ this.status = status;
+ }
+
+ /**
+ * Return the configured HTTP status for the response, if any.
+ * @since 4.3
+ */
+ public HttpStatus getStatus() {
+ return this.status;
+ }
+
/**
* Add an attribute to the model.
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java
index 7c8cbcb4..dd80a15a 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -286,6 +286,13 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
exceptionHandlerExceptionResolver.getPropertyValues().add("order", 0);
addResponseBodyAdvice(exceptionHandlerExceptionResolver);
+ if (argumentResolvers != null) {
+ exceptionHandlerExceptionResolver.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
+ }
+ if (returnValueHandlers != null) {
+ exceptionHandlerExceptionResolver.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
+ }
+
String methodExceptionResolverName = readerContext.registerWithGeneratedName(exceptionHandlerExceptionResolver);
RootBeanDefinition responseStatusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceUtils.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceUtils.java
index e6684136..fb787d1a 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceUtils.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceUtils.java
@@ -178,4 +178,23 @@ abstract class MvcNamespaceUtils {
return new RuntimeBeanReference(CORS_CONFIGURATION_BEAN_NAME);
}
+ /**
+ * Find the {@code ContentNegotiationManager} bean created by or registered
+ * with the {@code annotation-driven} element.
+ * @return a bean definition, bean reference, or null.
+ */
+ public static Object getContentNegotiationManager(ParserContext context) {
+ String name = AnnotationDrivenBeanDefinitionParser.HANDLER_MAPPING_BEAN_NAME;
+ if (context.getRegistry().containsBeanDefinition(name)) {
+ BeanDefinition handlerMappingBeanDef = context.getRegistry().getBeanDefinition(name);
+ return handlerMappingBeanDef.getPropertyValues().get("contentNegotiationManager");
+ }
+ name = AnnotationDrivenBeanDefinitionParser.CONTENT_NEGOTIATION_MANAGER_BEAN_NAME;
+ if (context.getRegistry().containsBeanDefinition(name)) {
+ return new RuntimeBeanReference(name);
+ }
+ return null;
+ }
+
+
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java
index f55e287a..e8059186 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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 java.util.concurrent.TimeUnit;
import org.w3c.dom.Element;
+import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.RuntimeBeanReference;
@@ -165,17 +166,19 @@ class ResourcesBeanDefinitionParser implements BeanDefinitionParser {
RootBeanDefinition resourceHandlerDef = new RootBeanDefinition(ResourceHttpRequestHandler.class);
resourceHandlerDef.setSource(source);
resourceHandlerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
- resourceHandlerDef.getPropertyValues().add("locations", locations);
+
+ MutablePropertyValues values = resourceHandlerDef.getPropertyValues();
+ values.add("locations", locations);
String cacheSeconds = element.getAttribute("cache-period");
if (StringUtils.hasText(cacheSeconds)) {
- resourceHandlerDef.getPropertyValues().add("cacheSeconds", cacheSeconds);
+ values.add("cacheSeconds", cacheSeconds);
}
Element cacheControlElement = DomUtils.getChildElementByTagName(element, "cache-control");
if (cacheControlElement != null) {
CacheControl cacheControl = parseCacheControl(cacheControlElement);
- resourceHandlerDef.getPropertyValues().add("cacheControl", cacheControl);
+ values.add("cacheControl", cacheControl);
}
Element resourceChainElement = DomUtils.getChildElementByTagName(element, "resource-chain");
@@ -183,6 +186,11 @@ class ResourcesBeanDefinitionParser implements BeanDefinitionParser {
parseResourceChain(resourceHandlerDef, parserContext, resourceChainElement, source);
}
+ Object manager = MvcNamespaceUtils.getContentNegotiationManager(parserContext);
+ if (manager != null) {
+ values.add("contentNegotiationManager", manager);
+ }
+
String beanName = parserContext.getReaderContext().generateBeanName(resourceHandlerDef);
parserContext.getRegistry().registerBeanDefinition(beanName, resourceHandlerDef);
parserContext.registerComponent(new BeanComponentDefinition(resourceHandlerDef, beanName));
@@ -242,6 +250,14 @@ class ResourcesBeanDefinitionParser implements BeanDefinitionParser {
if (element.hasAttribute("s-maxage")) {
cacheControl = cacheControl.sMaxAge(Long.parseLong(element.getAttribute("s-maxage")), TimeUnit.SECONDS);
}
+ if (element.hasAttribute("stale-while-revalidate")) {
+ cacheControl = cacheControl.staleWhileRevalidate(
+ Long.parseLong(element.getAttribute("stale-while-revalidate")), TimeUnit.SECONDS);
+ }
+ if (element.hasAttribute("stale-if-error")) {
+ cacheControl = cacheControl.staleIfError(
+ Long.parseLong(element.getAttribute("stale-if-error")), TimeUnit.SECONDS);
+ }
return cacheControl;
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ViewResolversBeanDefinitionParser.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ViewResolversBeanDefinitionParser.java
index 06e645f5..a91c937d 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ViewResolversBeanDefinitionParser.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ViewResolversBeanDefinitionParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,7 +22,6 @@ import org.w3c.dom.Element;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
-import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
import org.springframework.beans.factory.support.ManagedList;
@@ -39,7 +38,6 @@ import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;
import org.springframework.web.servlet.view.groovy.GroovyMarkupViewResolver;
import org.springframework.web.servlet.view.script.ScriptTemplateViewResolver;
import org.springframework.web.servlet.view.tiles3.TilesViewResolver;
-import org.springframework.web.servlet.view.velocity.VelocityViewResolver;
/**
* Parse the {@code view-resolvers} MVC namespace element and register
@@ -69,6 +67,7 @@ public class ViewResolversBeanDefinitionParser implements BeanDefinitionParser {
public static final String VIEW_RESOLVER_BEAN_NAME = "mvcViewResolver";
+ @SuppressWarnings("deprecation")
public BeanDefinition parse(Element element, ParserContext context) {
Object source = context.extractSource(element);
context.pushContainingComponent(new CompositeComponentDefinition(element.getTagName(), source));
@@ -100,7 +99,7 @@ public class ViewResolversBeanDefinitionParser implements BeanDefinitionParser {
addUrlBasedViewResolverProperties(resolverElement, resolverBeanDef);
}
else if ("velocity".equals(name)) {
- resolverBeanDef = new RootBeanDefinition(VelocityViewResolver.class);
+ resolverBeanDef = new RootBeanDefinition(org.springframework.web.servlet.view.velocity.VelocityViewResolver.class);
resolverBeanDef.getPropertyValues().add("suffix", ".vm");
addUrlBasedViewResolverProperties(resolverElement, resolverBeanDef);
}
@@ -192,24 +191,11 @@ public class ViewResolversBeanDefinitionParser implements BeanDefinitionParser {
if (resolverElement.hasAttribute("use-not-acceptable")) {
values.add("useNotAcceptableStatusCode", resolverElement.getAttribute("use-not-acceptable"));
}
- Object manager = getContentNegotiationManager(context);
+ Object manager = MvcNamespaceUtils.getContentNegotiationManager(context);
if (manager != null) {
values.add("contentNegotiationManager", manager);
}
return beanDef;
}
- private Object getContentNegotiationManager(ParserContext context) {
- String name = AnnotationDrivenBeanDefinitionParser.HANDLER_MAPPING_BEAN_NAME;
- if (context.getRegistry().containsBeanDefinition(name)) {
- BeanDefinition handlerMappingBeanDef = context.getRegistry().getBeanDefinition(name);
- return handlerMappingBeanDef.getPropertyValues().get("contentNegotiationManager");
- }
- name = AnnotationDrivenBeanDefinitionParser.CONTENT_NEGOTIATION_MANAGER_BEAN_NAME;
- if (context.getRegistry().containsBeanDefinition(name)) {
- return new RuntimeBeanReference(name);
- }
- return null;
- }
-
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceChainRegistration.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceChainRegistration.java
index 92a75bed..93a21b89 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceChainRegistration.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceChainRegistration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +56,8 @@ public class ResourceChainRegistration {
private boolean hasCssLinkTransformer;
+ private boolean hasWebjarsResolver;
+
public ResourceChainRegistration(boolean cacheResources) {
this(cacheResources, cacheResources ? new ConcurrentMapCache(DEFAULT_CACHE_NAME) : null);
@@ -84,6 +86,9 @@ public class ResourceChainRegistration {
else if (resolver instanceof PathResourceResolver) {
this.hasPathResolver = true;
}
+ else if (resolver instanceof WebJarsResourceResolver) {
+ this.hasWebjarsResolver = true;
+ }
return this;
}
@@ -104,7 +109,7 @@ public class ResourceChainRegistration {
protected List<ResourceResolver> getResourceResolvers() {
if (!this.hasPathResolver) {
List<ResourceResolver> result = new ArrayList<ResourceResolver>(this.resolvers);
- if (isWebJarsAssetLocatorPresent) {
+ if (isWebJarsAssetLocatorPresent && !this.hasWebjarsResolver) {
result.add(new WebJarsResourceResolver());
}
result.add(new PathResourceResolver());
@@ -114,7 +119,7 @@ public class ResourceChainRegistration {
}
protected List<ResourceTransformer> getResourceTransformers() {
- if (this.hasVersionResolver && !this.hasCssLinkTransformer) {
+ if (this.hasVersionResolver && !this.hasCssLinkTransformer) {
List<ResourceTransformer> result = new ArrayList<ResourceTransformer>(this.transformers);
boolean hasTransformers = !this.transformers.isEmpty();
boolean hasCaching = hasTransformers && this.transformers.get(0) instanceof CachingResourceTransformer;
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistry.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistry.java
index f9145d4a..a4c7c02a 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistry.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistry.java
@@ -27,6 +27,7 @@ import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.context.ApplicationContext;
import org.springframework.util.Assert;
import org.springframework.web.HttpRequestHandler;
+import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
@@ -55,15 +56,23 @@ public class ResourceHandlerRegistry {
private final ApplicationContext appContext;
+ private final ContentNegotiationManager contentNegotiationManager;
+
private final List<ResourceHandlerRegistration> registrations = new ArrayList<ResourceHandlerRegistration>();
private int order = Integer.MAX_VALUE -1;
public ResourceHandlerRegistry(ApplicationContext applicationContext, ServletContext servletContext) {
+ this(applicationContext, servletContext, null);
+ }
+
+ public ResourceHandlerRegistry(ApplicationContext applicationContext, ServletContext servletContext,
+ ContentNegotiationManager contentNegotiationManager) {
Assert.notNull(applicationContext, "ApplicationContext is required");
this.appContext = applicationContext;
this.servletContext = servletContext;
+ this.contentNegotiationManager = contentNegotiationManager;
}
@@ -113,6 +122,7 @@ public class ResourceHandlerRegistry {
ResourceHttpRequestHandler handler = registration.getRequestHandler();
handler.setServletContext(this.servletContext);
handler.setApplicationContext(this.appContext);
+ handler.setContentNegotiationManager(this.contentNegotiationManager);
try {
handler.afterPropertiesSet();
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ViewResolverRegistry.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ViewResolverRegistry.java
index c4fedda8..bc21e6e0 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ViewResolverRegistry.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ViewResolverRegistry.java
@@ -41,8 +41,6 @@ import org.springframework.web.servlet.view.script.ScriptTemplateConfigurer;
import org.springframework.web.servlet.view.script.ScriptTemplateViewResolver;
import org.springframework.web.servlet.view.tiles3.TilesConfigurer;
import org.springframework.web.servlet.view.tiles3.TilesViewResolver;
-import org.springframework.web.servlet.view.velocity.VelocityConfigurer;
-import org.springframework.web.servlet.view.velocity.VelocityViewResolver;
/**
* Assist with the configuration of a chain of
@@ -86,10 +84,8 @@ public class ViewResolverRegistry {
* Enable use of a {@link ContentNegotiatingViewResolver} to front all other
* configured view resolvers and select among all selected Views based on
* media types requested by the client (e.g. in the Accept header).
- *
* <p>If invoked multiple times the provided default views will be added to
* any other default views that may have been configured already.
- *
* @see ContentNegotiatingViewResolver#setDefaultViews
*/
public void enableContentNegotiation(View... defaultViews) {
@@ -100,7 +96,6 @@ public class ViewResolverRegistry {
* Enable use of a {@link ContentNegotiatingViewResolver} to front all other
* configured view resolvers and select among all selected Views based on
* media types requested by the client (e.g. in the Accept header).
- *
* <p>If invoked multiple times the provided default views will be added to
* any other default views that may have been configured already.
*
@@ -112,9 +107,8 @@ public class ViewResolverRegistry {
}
private void initContentNegotiatingViewResolver(View[] defaultViews) {
-
// ContentNegotiatingResolver in the registry: elevate its precedence!
- this.order = (this.order == null ? Ordered.HIGHEST_PRECEDENCE : this.order);
+ this.order = (this.order != null ? this.order : Ordered.HIGHEST_PRECEDENCE);
if (this.contentNegotiatingResolver != null) {
if (!ObjectUtils.isEmpty(defaultViews)) {
@@ -136,7 +130,6 @@ public class ViewResolverRegistry {
/**
* Register JSP view resolver using a default view name prefix of "/WEB-INF/"
* and a default suffix of ".jsp".
- *
* <p>When this method is invoked more than once, each call will register a
* new ViewResolver instance. Note that since it's not easy to determine
* if a JSP exists without forwarding to it, using multiple JSP-based view
@@ -149,7 +142,6 @@ public class ViewResolverRegistry {
/**
* Register JSP view resolver with the specified prefix and suffix.
- *
* <p>When this method is invoked more than once, each call will register a
* new ViewResolver instance. Note that since it's not easy to determine
* if a JSP exists without forwarding to it, using multiple JSP-based view
@@ -166,7 +158,6 @@ public class ViewResolverRegistry {
/**
* Register Tiles 3.x view resolver.
- *
* <p><strong>Note</strong> that you must also configure Tiles by adding a
* {@link org.springframework.web.servlet.view.tiles3.TilesConfigurer} bean.
*/
@@ -184,7 +175,6 @@ public class ViewResolverRegistry {
/**
* Register a FreeMarker view resolver with an empty default view name
* prefix and a default suffix of ".ftl".
- *
* <p><strong>Note</strong> that you must also configure FreeMarker by adding a
* {@link org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer} bean.
*/
@@ -203,12 +193,13 @@ public class ViewResolverRegistry {
/**
* Register Velocity view resolver with an empty default view name
* prefix and a default suffix of ".vm".
- *
* <p><strong>Note</strong> that you must also configure Velocity by adding a
* {@link org.springframework.web.servlet.view.velocity.VelocityConfigurer} bean.
+ * @deprecated as of Spring 4.3, in favor of FreeMarker
*/
+ @Deprecated
public UrlBasedViewResolverRegistration velocity() {
- if (this.applicationContext != null && !hasBeanOfType(VelocityConfigurer.class)) {
+ if (this.applicationContext != null && !hasBeanOfType(org.springframework.web.servlet.view.velocity.VelocityConfigurer.class)) {
throw new BeanInitializationException("In addition to a Velocity view resolver " +
"there must also be a single VelocityConfig bean in this web application context " +
"(or its parent): VelocityConfigurer is the usual implementation. " +
@@ -313,22 +304,23 @@ public class ViewResolverRegistry {
private static class TilesRegistration extends UrlBasedViewResolverRegistration {
- private TilesRegistration() {
+ public TilesRegistration() {
super(new TilesViewResolver());
}
}
private static class VelocityRegistration extends UrlBasedViewResolverRegistration {
- private VelocityRegistration() {
- super(new VelocityViewResolver());
+ @SuppressWarnings("deprecation")
+ public VelocityRegistration() {
+ super(new org.springframework.web.servlet.view.velocity.VelocityViewResolver());
getViewResolver().setSuffix(".vm");
}
}
private static class FreeMarkerRegistration extends UrlBasedViewResolverRegistration {
- private FreeMarkerRegistration() {
+ public FreeMarkerRegistration() {
super(new FreeMarkerViewResolver());
getViewResolver().setSuffix(".ftl");
}
@@ -336,7 +328,7 @@ public class ViewResolverRegistry {
private static class GroovyMarkupRegistration extends UrlBasedViewResolverRegistration {
- private GroovyMarkupRegistration() {
+ public GroovyMarkupRegistration() {
super(new GroovyMarkupViewResolver());
getViewResolver().setSuffix(".tpl");
}
@@ -344,7 +336,7 @@ public class ViewResolverRegistry {
private static class ScriptRegistration extends UrlBasedViewResolverRegistration {
- private ScriptRegistration() {
+ public ScriptRegistration() {
super(new ScriptTemplateViewResolver());
getViewResolver();
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java
index 37976905..3a114972 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -199,6 +199,10 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
private ContentNegotiationManager contentNegotiationManager;
+ private List<HandlerMethodArgumentResolver> argumentResolvers;
+
+ private List<HandlerMethodReturnValueHandler> returnValueHandlers;
+
private List<HttpMessageConverter<?>> messageConverters;
private Map<String, CorsConfiguration> corsConfigurations;
@@ -403,7 +407,8 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
*/
@Bean
public HandlerMapping resourceHandlerMapping() {
- ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext, this.servletContext);
+ ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext,
+ this.servletContext, mvcContentNegotiationManager());
addResourceHandlers(registry);
AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
@@ -474,18 +479,12 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
*/
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
- List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<HandlerMethodArgumentResolver>();
- addArgumentResolvers(argumentResolvers);
-
- List<HandlerMethodReturnValueHandler> returnValueHandlers = new ArrayList<HandlerMethodReturnValueHandler>();
- addReturnValueHandlers(returnValueHandlers);
-
- RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();
+ RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
adapter.setContentNegotiationManager(mvcContentNegotiationManager());
adapter.setMessageConverters(getMessageConverters());
adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer());
- adapter.setCustomArgumentResolvers(argumentResolvers);
- adapter.setCustomReturnValueHandlers(returnValueHandlers);
+ adapter.setCustomArgumentResolvers(getArgumentResolvers());
+ adapter.setCustomReturnValueHandlers(getReturnValueHandlers());
if (jackson2Present) {
List<RequestBodyAdvice> requestBodyAdvices = new ArrayList<RequestBodyAdvice>();
@@ -513,6 +512,14 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
}
/**
+ * Protected method for plugging in a custom sub-class of
+ * {@link RequestMappingHandlerAdapter}.
+ */
+ protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() {
+ return new RequestMappingHandlerAdapter();
+ }
+
+ /**
* Return the {@link ConfigurableWebBindingInitializer} to use for
* initializing all {@link WebDataBinder} instances.
*/
@@ -618,6 +625,20 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
}
/**
+ * Provide access to the shared custom argument resolvers used by the
+ * {@link RequestMappingHandlerAdapter} and the
+ * {@link ExceptionHandlerExceptionResolver}. This method cannot be
+ * overridden, use {@link #addArgumentResolvers(List)} instead.
+ */
+ protected final List<HandlerMethodArgumentResolver> getArgumentResolvers() {
+ if (this.argumentResolvers == null) {
+ this.argumentResolvers = new ArrayList<HandlerMethodArgumentResolver>();
+ addArgumentResolvers(this.argumentResolvers);
+ }
+ return this.argumentResolvers;
+ }
+
+ /**
* Add custom {@link HandlerMethodArgumentResolver}s to use in addition to
* the ones registered by default.
* <p>Custom argument resolvers are invoked before built-in resolvers
@@ -632,6 +653,20 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
}
/**
+ * Provide access to the shared return value handlers used by the
+ * {@link RequestMappingHandlerAdapter} and the
+ * {@link ExceptionHandlerExceptionResolver}. This method cannot be
+ * overridden, use {@link #addReturnValueHandlers(List)} instead.
+ */
+ protected final List<HandlerMethodReturnValueHandler> getReturnValueHandlers() {
+ if (this.returnValueHandlers == null) {
+ this.returnValueHandlers = new ArrayList<HandlerMethodReturnValueHandler>();
+ addReturnValueHandlers(this.returnValueHandlers);
+ }
+ return this.returnValueHandlers;
+ }
+
+ /**
* Add custom {@link HandlerMethodReturnValueHandler}s in addition to the
* ones registered by default.
* <p>Custom return value handlers are invoked before built-in ones except
@@ -679,6 +714,14 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
}
+ /**
+ * Override this method to extend or modify the list of converters after it
+ * has been configured. This may be useful for example to allow default
+ * converters to be registered and then insert a custom converter through
+ * this method.
+ * @param converters the list of configured converters to extend.
+ * @since 4.1.3
+ */
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
}
@@ -779,6 +822,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
addDefaultHandlerExceptionResolvers(exceptionResolvers);
}
+ extendHandlerExceptionResolvers(exceptionResolvers);
HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
composite.setOrder(0);
composite.setExceptionResolvers(exceptionResolvers);
@@ -798,6 +842,17 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
}
/**
+ * Override this method to extend or modify the list of
+ * {@link HandlerExceptionResolver}s after it has been configured. This may
+ * be useful for example to allow default resolvers to be registered and then
+ * insert a custom one through this method.
+ * @param exceptionResolvers the list of configured resolvers to extend.
+ * @since 4.3
+ */
+ protected void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
+ }
+
+ /**
* A method available to subclasses for adding default {@link HandlerExceptionResolver}s.
* <p>Adds the following exception resolvers:
* <ul>
@@ -810,26 +865,36 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
* </ul>
*/
protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
- ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver = new ExceptionHandlerExceptionResolver();
- exceptionHandlerExceptionResolver.setContentNegotiationManager(mvcContentNegotiationManager());
- exceptionHandlerExceptionResolver.setMessageConverters(getMessageConverters());
+ ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver();
+ exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager());
+ exceptionHandlerResolver.setMessageConverters(getMessageConverters());
+ exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers());
+ exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers());
if (jackson2Present) {
List<ResponseBodyAdvice<?>> interceptors = new ArrayList<ResponseBodyAdvice<?>>();
interceptors.add(new JsonViewResponseBodyAdvice());
- exceptionHandlerExceptionResolver.setResponseBodyAdvice(interceptors);
+ exceptionHandlerResolver.setResponseBodyAdvice(interceptors);
}
- exceptionHandlerExceptionResolver.setApplicationContext(this.applicationContext);
- exceptionHandlerExceptionResolver.afterPropertiesSet();
- exceptionResolvers.add(exceptionHandlerExceptionResolver);
+ exceptionHandlerResolver.setApplicationContext(this.applicationContext);
+ exceptionHandlerResolver.afterPropertiesSet();
+ exceptionResolvers.add(exceptionHandlerResolver);
- ResponseStatusExceptionResolver responseStatusExceptionResolver = new ResponseStatusExceptionResolver();
- responseStatusExceptionResolver.setMessageSource(this.applicationContext);
- exceptionResolvers.add(responseStatusExceptionResolver);
+ ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver();
+ responseStatusResolver.setMessageSource(this.applicationContext);
+ exceptionResolvers.add(responseStatusResolver);
exceptionResolvers.add(new DefaultHandlerExceptionResolver());
}
/**
+ * Protected method for plugging in a custom sub-class of
+ * {@link ExceptionHandlerExceptionResolver}.
+ */
+ protected ExceptionHandlerExceptionResolver createExceptionHandlerExceptionResolver() {
+ return new ExceptionHandlerExceptionResolver();
+ }
+
+ /**
* Register a {@link ViewResolverComposite} that contains a chain of view resolvers
* to use for view resolution.
* By default this resolver is ordered at 0 unless content negotiation view
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurer.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurer.java
index 3e9c509f..6ab3eb41 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurer.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -131,6 +131,16 @@ public interface WebMvcConfigurer {
void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers);
/**
+ * A hook for extending or modifying the list of
+ * {@link HandlerExceptionResolver}s after it has been configured. This may
+ * be useful for example to allow default resolvers to be registered and then
+ * insert a custom one through this method.
+ * @param exceptionResolvers the list of configured resolvers to extend.
+ * @since 4.3
+ */
+ void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers);
+
+ /**
* Add Spring MVC lifecycle interceptors for pre- and post-processing of
* controller method invocations. Interceptors can be registered to apply
* to all requests or be limited to a subset of URL patterns.
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurerAdapter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurerAdapter.java
index 90d4b6ae..1c9e3e0c 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurerAdapter.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurerAdapter.java
@@ -121,6 +121,14 @@ public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {
* <p>This implementation is empty.
*/
@Override
+ public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>This implementation is empty.
+ */
+ @Override
public MessageCodesResolver getMessageCodesResolver() {
return null;
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurerComposite.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurerComposite.java
index c88ba0ff..1b6d6e53 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurerComposite.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurerComposite.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@ import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
/**
- * An {@link WebMvcConfigurer} implementation that delegates to other {@link WebMvcConfigurer} instances.
+ * A {@link WebMvcConfigurer} that delegates to one or more others.
*
* @author Rossen Stoyanchev
* @since 3.1
@@ -107,6 +107,13 @@ class WebMvcConfigurerComposite implements WebMvcConfigurer {
}
@Override
+ public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
+ for (WebMvcConfigurer delegate : this.delegates) {
+ delegate.configureHandlerExceptionResolvers(exceptionResolvers);
+ }
+ }
+
+ @Override
public void addInterceptors(InterceptorRegistry registry) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.addInterceptors(registry);
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 9ae1cb42..50997f55 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
package org.springframework.web.servlet.handler;
import java.util.Set;
+
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -128,13 +129,15 @@ public abstract class AbstractHandlerExceptionResolver implements HandlerExcepti
Object handler, Exception ex) {
if (shouldApplyTo(request, handler)) {
- // Log exception, both at debug log level and at warn level, if desired.
if (this.logger.isDebugEnabled()) {
this.logger.debug("Resolving exception from handler [" + handler + "]: " + ex);
}
- logException(ex, request);
prepareResponse(ex, response);
- return doResolveException(request, response, handler, ex);
+ ModelAndView result = doResolveException(request, response, handler, ex);
+ if (result != null) {
+ logException(ex, request);
+ }
+ return result;
}
else {
return null;
@@ -194,7 +197,7 @@ public abstract class AbstractHandlerExceptionResolver implements HandlerExcepti
* @return the log message to use
*/
protected String buildLogMessage(Exception ex, HttpServletRequest request) {
- return "Handler execution resulted in exception: " + ex;
+ return "Resolved exception caused by Handler execution: " + ex;
}
/**
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java
index 1877f194..1af14127 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java
@@ -27,21 +27,21 @@ import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.core.Ordered;
-import org.springframework.web.HttpRequestHandler;
-import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
-import org.springframework.web.cors.CorsProcessor;
-import org.springframework.web.cors.CorsConfiguration;
-import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import org.springframework.util.PathMatcher;
+import org.springframework.web.HttpRequestHandler;
import org.springframework.web.context.request.WebRequestInterceptor;
import org.springframework.web.context.support.WebApplicationObjectSupport;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.CorsConfigurationSource;
+import org.springframework.web.cors.CorsProcessor;
+import org.springframework.web.cors.CorsUtils;
+import org.springframework.web.cors.DefaultCorsProcessor;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.HandlerMapping;
-import org.springframework.web.cors.DefaultCorsProcessor;
-import org.springframework.web.cors.CorsUtils;
import org.springframework.web.util.UrlPathHelper;
/**
@@ -471,7 +471,7 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
}
- private class PreFlightHandler implements HttpRequestHandler {
+ private class PreFlightHandler implements HttpRequestHandler, CorsConfigurationSource {
private final CorsConfiguration config;
@@ -485,10 +485,15 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
corsProcessor.processRequest(this.config, request, response);
}
+
+ @Override
+ public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
+ return this.config;
+ }
}
- private class CorsInterceptor extends HandlerInterceptorAdapter {
+ private class CorsInterceptor extends HandlerInterceptorAdapter implements CorsConfigurationSource {
private final CorsConfiguration config;
@@ -502,6 +507,11 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
return corsProcessor.processRequest(this.config, request, response);
}
+
+ @Override
+ public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
+ return this.config;
+ }
}
}
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 d84d1601..65a8db26 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
@@ -31,6 +31,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
+import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.MethodIntrospector;
@@ -230,7 +231,13 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
new MethodIntrospector.MetadataLookup<T>() {
@Override
public T inspect(Method method) {
- return getMappingForMethod(method, userType);
+ try {
+ return getMappingForMethod(method, userType);
+ }
+ catch (Throwable ex) {
+ throw new IllegalStateException("Invalid mapping on handler class [" +
+ userType.getName() + "]: " + method, ex);
+ }
}
});
@@ -238,7 +245,9 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
}
for (Map.Entry<Method, T> entry : methods.entrySet()) {
- registerHandlerMethod(handler, entry.getKey(), entry.getValue());
+ Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
+ T mapping = entry.getValue();
+ registerHandlerMethod(handler, invocableMethod, mapping);
}
}
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 5d6406e5..51bed445 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,7 +29,6 @@ import org.springframework.beans.BeansException;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.servlet.HandlerExecutionChain;
-import org.springframework.web.servlet.HandlerMapping;
/**
* Abstract base class for URL-mapped {@link org.springframework.web.servlet.HandlerMapping}
@@ -50,7 +49,7 @@ import org.springframework.web.servlet.HandlerMapping;
* @author Arjen Poutsma
* @since 16.04.2003
*/
-public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
+public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping implements MatchableHandlerMapping {
private Object rootHandler;
@@ -265,8 +264,8 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
* @see #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE
*/
protected void exposePathWithinMapping(String bestMatchingPattern, String pathWithinMapping, HttpServletRequest request) {
- request.setAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, bestMatchingPattern);
- request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, pathWithinMapping);
+ request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestMatchingPattern);
+ request.setAttribute(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, pathWithinMapping);
}
/**
@@ -276,7 +275,21 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
* @see #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE
*/
protected void exposeUriTemplateVariables(Map<String, String> uriTemplateVariables, HttpServletRequest request) {
- request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);
+ request.setAttribute(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);
+ }
+
+ @Override
+ public RequestMatchResult match(HttpServletRequest request, String pattern) {
+ String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
+ if (getPathMatcher().match(pattern, lookupPath)) {
+ return new RequestMatchResult(pattern, lookupPath, getPathMatcher());
+ }
+ else if (useTrailingSlashMatch()) {
+ if (!pattern.endsWith("/") && getPathMatcher().match(pattern + "/", lookupPath)) {
+ return new RequestMatchResult(pattern + "/", lookupPath, getPathMatcher());
+ }
+ }
+ return null;
}
/**
@@ -386,7 +399,7 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
exposePathWithinMapping(this.bestMatchingPattern, this.pathWithinMapping, request);
- request.setAttribute(HandlerMapping.INTROSPECT_TYPE_LEVEL_MAPPING, supportsTypeLevelMappings());
+ request.setAttribute(INTROSPECT_TYPE_LEVEL_MAPPING, supportsTypeLevelMappings());
return true;
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/BeanNameUrlHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/BeanNameUrlHandlerMapping.java
index 397c179b..44376cbd 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/BeanNameUrlHandlerMapping.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/BeanNameUrlHandlerMapping.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2010 the original author or authors.
+ * Copyright 2002-2016 the original author 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,9 +28,9 @@ import org.springframework.util.StringUtils;
*
* <p>This is the default implementation used by the
* {@link org.springframework.web.servlet.DispatcherServlet}, along with
- * {@link org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping}
- * (on Java 5 and higher). Alternatively, {@link SimpleUrlHandlerMapping} allows for
- * customizing a handler mapping declaratively.
+ * {@link org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping}.
+ * Alternatively, {@link SimpleUrlHandlerMapping} allows for customizing a
+ * handler mapping declaratively.
*
* <p>The mapping is from URL to bean name. Thus an incoming URL "/foo" would map
* to a handler named "/foo", or to "/foo /foo2" in case of multiple mappings to
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerInterceptorAdapter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerInterceptorAdapter.java
index 602886ba..b00d4689 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerInterceptorAdapter.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerInterceptorAdapter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,7 +23,7 @@ import org.springframework.web.servlet.AsyncHandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
/**
- * Abstract adapter class for the HandlerInterceptor interface,
+ * Abstract adapter class for the {@link AsyncHandlerInterceptor} interface,
* for simplified implementation of pre-only/post-only interceptors.
*
* @author Juergen Hoeller
@@ -36,7 +36,8 @@ public abstract class HandlerInterceptorAdapter implements AsyncHandlerIntercept
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
- throws Exception {
+ throws Exception {
+
return true;
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMappingIntrospector.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMappingIntrospector.java
new file mode 100644
index 00000000..1c7fa9b6
--- /dev/null
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMappingIntrospector.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.servlet.handler;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+
+import org.springframework.beans.factory.BeanFactoryUtils;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.annotation.AnnotationAwareOrderComparator;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PropertiesLoaderUtils;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.StringUtils;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.CorsConfigurationSource;
+import org.springframework.web.servlet.DispatcherServlet;
+import org.springframework.web.servlet.HandlerExecutionChain;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.HandlerMapping;
+
+/**
+ * Helper class to get information from the {@code HandlerMapping} that would
+ * serve a specific request.
+ *
+ * <p>Provides the following methods:
+ * <ul>
+ * <li>{@link #getMatchableHandlerMapping} &mdash; obtain a {@code HandlerMapping}
+ * to check request-matching criteria against.
+ * <li>{@link #getCorsConfiguration} &mdash; obtain the CORS configuration for the
+ * request.
+ * </ul>
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3.1
+ */
+public class HandlerMappingIntrospector implements CorsConfigurationSource {
+
+ private final List<HandlerMapping> handlerMappings;
+
+
+ /**
+ * Constructor that detects the configured {@code HandlerMapping}s in the
+ * given {@code ApplicationContext} or falls back on
+ * "DispatcherServlet.properties" like the {@code DispatcherServlet}.
+ */
+ public HandlerMappingIntrospector(ApplicationContext context) {
+ this.handlerMappings = initHandlerMappings(context);
+ }
+
+
+ private static List<HandlerMapping> initHandlerMappings(ApplicationContext context) {
+ Map<String, HandlerMapping> beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
+ context, HandlerMapping.class, true, false);
+ if (!beans.isEmpty()) {
+ List<HandlerMapping> mappings = new ArrayList<HandlerMapping>(beans.values());
+ AnnotationAwareOrderComparator.sort(mappings);
+ return mappings;
+ }
+ return initDefaultHandlerMappings(context);
+ }
+
+ private static List<HandlerMapping> initDefaultHandlerMappings(ApplicationContext context) {
+ Properties props;
+ String path = "DispatcherServlet.properties";
+ try {
+ Resource resource = new ClassPathResource(path, DispatcherServlet.class);
+ props = PropertiesLoaderUtils.loadProperties(resource);
+ }
+ catch (IOException ex) {
+ throw new IllegalStateException("Could not load '" + path + "': " + ex.getMessage());
+ }
+
+ String value = props.getProperty(HandlerMapping.class.getName());
+ String[] names = StringUtils.commaDelimitedListToStringArray(value);
+ List<HandlerMapping> result = new ArrayList<HandlerMapping>(names.length);
+ for (String name : names) {
+ try {
+ Class<?> clazz = ClassUtils.forName(name, DispatcherServlet.class.getClassLoader());
+ Object mapping = context.getAutowireCapableBeanFactory().createBean(clazz);
+ result.add((HandlerMapping) mapping);
+ }
+ catch (ClassNotFoundException ex) {
+ throw new IllegalStateException("Could not find default HandlerMapping [" + name + "]");
+ }
+ }
+ return result;
+ }
+
+
+ /**
+ * Return the configured HandlerMapping's.
+ */
+ public List<HandlerMapping> getHandlerMappings() {
+ return this.handlerMappings;
+ }
+
+ /**
+ * Find the {@link HandlerMapping} that would handle the given request and
+ * return it as a {@link MatchableHandlerMapping} that can be used to test
+ * request-matching criteria.
+ * <p>If the matching HandlerMapping is not an instance of
+ * {@link MatchableHandlerMapping}, an IllegalStateException is raised.
+ * @param request the current request
+ * @return the resolved matcher, or {@code null}
+ * @throws Exception if any of the HandlerMapping's raise an exception
+ */
+ public MatchableHandlerMapping getMatchableHandlerMapping(HttpServletRequest request) throws Exception {
+ HttpServletRequest wrapper = new RequestAttributeChangeIgnoringWrapper(request);
+ for (HandlerMapping handlerMapping : this.handlerMappings) {
+ Object handler = handlerMapping.getHandler(wrapper);
+ if (handler == null) {
+ continue;
+ }
+ if (handlerMapping instanceof MatchableHandlerMapping) {
+ return ((MatchableHandlerMapping) handlerMapping);
+ }
+ throw new IllegalStateException("HandlerMapping is not a MatchableHandlerMapping");
+ }
+ return null;
+ }
+
+ @Override
+ public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
+ HttpServletRequest wrapper = new RequestAttributeChangeIgnoringWrapper(request);
+ for (HandlerMapping handlerMapping : this.handlerMappings) {
+ HandlerExecutionChain handler = null;
+ try {
+ handler = handlerMapping.getHandler(wrapper);
+ }
+ catch (Exception ex) {
+ // Ignore
+ }
+ if (handler == null) {
+ continue;
+ }
+ if (handler.getInterceptors() != null) {
+ for (HandlerInterceptor interceptor : handler.getInterceptors()) {
+ if (interceptor instanceof CorsConfigurationSource) {
+ return ((CorsConfigurationSource) interceptor).getCorsConfiguration(wrapper);
+ }
+ }
+ }
+ if (handler.getHandler() instanceof CorsConfigurationSource) {
+ return ((CorsConfigurationSource) handler.getHandler()).getCorsConfiguration(wrapper);
+ }
+ }
+ return null;
+ }
+
+
+ /**
+ * Request wrapper that ignores request attribute changes.
+ */
+ private static class RequestAttributeChangeIgnoringWrapper extends HttpServletRequestWrapper {
+
+ public RequestAttributeChangeIgnoringWrapper(HttpServletRequest request) {
+ super(request);
+ }
+
+ @Override
+ public void setAttribute(String name, Object value) {
+ // Ignore attribute change
+ }
+ }
+
+}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/MatchableHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/MatchableHandlerMapping.java
new file mode 100644
index 00000000..af5788c7
--- /dev/null
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/MatchableHandlerMapping.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.servlet.handler;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.web.servlet.HandlerMapping;
+
+/**
+ * Additional interface that a {@link HandlerMapping} can implement to expose
+ * a request matching API aligned with its internal request matching
+ * configuration and implementation.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3.1
+ * @see HandlerMappingIntrospector
+ */
+public interface MatchableHandlerMapping extends HandlerMapping {
+
+ /**
+ * Determine whether the given request matches the request criteria.
+ * @param request the current request
+ * @param pattern the pattern to match
+ * @return the result from request matching, or {@code null} if none
+ */
+ RequestMatchResult match(HttpServletRequest request, String pattern);
+
+}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/RequestMatchResult.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/RequestMatchResult.java
new file mode 100644
index 00000000..380be418
--- /dev/null
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/RequestMatchResult.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.servlet.handler;
+
+import java.util.Map;
+
+import org.springframework.util.Assert;
+import org.springframework.util.PathMatcher;
+
+/**
+ * Container for the result from request pattern matching via
+ * {@link MatchableHandlerMapping} with a method to further extract
+ * URI template variables from the pattern.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3.1
+ */
+public class RequestMatchResult {
+
+ private final String matchingPattern;
+
+ private final String lookupPath;
+
+ private final PathMatcher pathMatcher;
+
+
+ /**
+ * Create an instance with a matching pattern.
+ * @param matchingPattern the matching pattern, possibly not the same as the
+ * input pattern, e.g. inputPattern="/foo" and matchingPattern="/foo/".
+ * @param lookupPath the lookup path extracted from the request
+ * @param pathMatcher the PathMatcher used
+ */
+ public RequestMatchResult(String matchingPattern, String lookupPath, PathMatcher pathMatcher) {
+ Assert.hasText(matchingPattern, "'matchingPattern' is required");
+ Assert.hasText(lookupPath, "'lookupPath' is required");
+ Assert.notNull(pathMatcher, "'pathMatcher' is required");
+ this.matchingPattern = matchingPattern;
+ this.lookupPath = lookupPath;
+ this.pathMatcher = pathMatcher;
+ }
+
+
+ /**
+ * Extract URI template variables from the matching pattern as defined in
+ * {@link PathMatcher#extractUriTemplateVariables}.
+ * @return a map with URI template variables
+ */
+ public Map<String, String> extractUriTemplateVariables() {
+ return this.pathMatcher.extractUriTemplateVariables(this.matchingPattern, this.lookupPath);
+ }
+
+}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/SimpleMappingExceptionResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/SimpleMappingExceptionResolver.java
index b24363f1..7bf2c47e 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/SimpleMappingExceptionResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/SimpleMappingExceptionResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -46,6 +46,7 @@ public class SimpleMappingExceptionResolver extends AbstractHandlerExceptionReso
/** The default name of the exception attribute: "exception". */
public static final String DEFAULT_EXCEPTION_ATTRIBUTE = "exception";
+
private Properties exceptionMappings;
private Class<?>[] excludedExceptions;
@@ -108,7 +109,7 @@ public class SimpleMappingExceptionResolver extends AbstractHandlerExceptionReso
public void setStatusCodes(Properties statusCodes) {
for (Enumeration<?> enumeration = statusCodes.propertyNames(); enumeration.hasMoreElements();) {
String viewName = (String) enumeration.nextElement();
- Integer statusCode = new Integer(statusCodes.getProperty(viewName));
+ Integer statusCode = Integer.valueOf(statusCodes.getProperty(viewName));
this.statusCodes.put(viewName, statusCode);
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/SimpleServletPostProcessor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/SimpleServletPostProcessor.java
index 49e2e3c8..d6fef3b6 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/SimpleServletPostProcessor.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/SimpleServletPostProcessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@ package org.springframework.web.servlet.handler;
import java.util.Collections;
import java.util.Enumeration;
-import java.util.HashSet;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
@@ -126,6 +125,11 @@ public class SimpleServletPostProcessor implements
}
}
+ @Override
+ public boolean requiresDestruction(Object bean) {
+ return (bean instanceof Servlet);
+ }
+
/**
* Internal implementation of the {@link ServletConfig} interface,
@@ -159,7 +163,7 @@ public class SimpleServletPostProcessor implements
@Override
public Enumeration<String> getInitParameterNames() {
- return Collections.enumeration(new HashSet<String>());
+ return Collections.enumeration(Collections.<String>emptySet());
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/AcceptHeaderLocaleResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/AcceptHeaderLocaleResolver.java
index c28f686e..c8bfa23c 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/AcceptHeaderLocaleResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/AcceptHeaderLocaleResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author 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,9 @@
package org.springframework.web.servlet.i18n;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -31,14 +34,88 @@ import org.springframework.web.servlet.LocaleResolver;
* can only be changed through changing the client's locale settings.
*
* @author Juergen Hoeller
+ * @author Rossen Stoyanchev
* @since 27.02.2003
* @see javax.servlet.http.HttpServletRequest#getLocale()
*/
public class AcceptHeaderLocaleResolver implements LocaleResolver {
+ private final List<Locale> supportedLocales = new ArrayList<Locale>(4);
+
+ private Locale defaultLocale;
+
+
+ /**
+ * Configure supported locales to check against the requested locales
+ * determined via {@link HttpServletRequest#getLocales()}. If this is not
+ * configured then {@link HttpServletRequest#getLocale()} is used instead.
+ * @param locales the supported locales
+ * @since 4.3
+ */
+ public void setSupportedLocales(List<Locale> locales) {
+ this.supportedLocales.clear();
+ if (locales != null) {
+ this.supportedLocales.addAll(locales);
+ }
+ }
+
+ /**
+ * Return the configured list of supported locales.
+ * @since 4.3
+ */
+ public List<Locale> getSupportedLocales() {
+ return this.supportedLocales;
+ }
+
+ /**
+ * Configure a fixed default locale to fall back on if the request does not
+ * have an "Accept-Language" header.
+ * <p>By default this is not set in which case when there is "Accept-Language"
+ * header, the default locale for the server is used as defined in
+ * {@link HttpServletRequest#getLocale()}.
+ * @param defaultLocale the default locale to use
+ * @since 4.3
+ */
+ public void setDefaultLocale(Locale defaultLocale) {
+ this.defaultLocale = defaultLocale;
+ }
+
+ /**
+ * The configured default locale, if any.
+ * @since 4.3
+ */
+ public Locale getDefaultLocale() {
+ return this.defaultLocale;
+ }
+
+
@Override
public Locale resolveLocale(HttpServletRequest request) {
- return request.getLocale();
+ Locale defaultLocale = getDefaultLocale();
+ if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
+ return defaultLocale;
+ }
+ Locale locale = request.getLocale();
+ if (!isSupportedLocale(locale)) {
+ locale = findSupportedLocale(request, locale);
+ }
+ return locale;
+ }
+
+ private boolean isSupportedLocale(Locale locale) {
+ List<Locale> supportedLocales = getSupportedLocales();
+ return (supportedLocales.isEmpty() || supportedLocales.contains(locale));
+ }
+
+ private Locale findSupportedLocale(HttpServletRequest request, Locale fallback) {
+ Enumeration<Locale> requestLocales = request.getLocales();
+ while (requestLocales.hasMoreElements()) {
+ Locale locale = requestLocales.nextElement();
+ if (getSupportedLocales().contains(locale)) {
+ return locale;
+ }
+ }
+ return fallback;
}
@Override
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/CookieLocaleResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/CookieLocaleResolver.java
index 547a91fc..f216e9f6 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/CookieLocaleResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/CookieLocaleResolver.java
@@ -25,6 +25,7 @@ import javax.servlet.http.HttpServletResponse;
import org.springframework.context.i18n.LocaleContext;
import org.springframework.context.i18n.SimpleLocaleContext;
import org.springframework.context.i18n.TimeZoneAwareLocaleContext;
+import org.springframework.lang.UsesJava7;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleContextResolver;
import org.springframework.web.servlet.LocaleResolver;
@@ -81,6 +82,8 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte
public static final String DEFAULT_COOKIE_NAME = CookieLocaleResolver.class.getName() + ".LOCALE";
+ private boolean languageTagCompliant = false;
+
private Locale defaultLocale;
private TimeZone defaultTimeZone;
@@ -94,6 +97,30 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte
setCookieName(DEFAULT_COOKIE_NAME);
}
+
+ /**
+ * Specify whether this resolver's cookies should be compliant with BCP 47
+ * language tags instead of Java's legacy locale specification format.
+ * The default is {@code false}.
+ * <p>Note: This mode requires JDK 7 or higher. Set this flag to {@code true}
+ * for BCP 47 compliance on JDK 7+ only.
+ * @since 4.3
+ * @see Locale#forLanguageTag(String)
+ * @see Locale#toLanguageTag()
+ */
+ public void setLanguageTagCompliant(boolean languageTagCompliant) {
+ this.languageTagCompliant = languageTagCompliant;
+ }
+
+ /**
+ * Return whether this resolver's cookies should be compliant with BCP 47
+ * language tags instead of Java's legacy locale specification format.
+ * @since 4.3
+ */
+ public boolean isLanguageTagCompliant() {
+ return this.languageTagCompliant;
+ }
+
/**
* Set a fixed Locale that this resolver will return if no cookie found.
*/
@@ -163,7 +190,7 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte
localePart = value.substring(0, spaceIndex);
timeZonePart = value.substring(spaceIndex + 1);
}
- locale = (!"-".equals(localePart) ? StringUtils.parseLocaleString(localePart) : null);
+ locale = (!"-".equals(localePart) ? parseLocaleValue(localePart) : null);
if (timeZonePart != null) {
timeZone = StringUtils.parseTimeZoneString(timeZonePart);
}
@@ -193,7 +220,8 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte
if (localeContext instanceof TimeZoneAwareLocaleContext) {
timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone();
}
- addCookie(response, (locale != null ? locale : "-") + (timeZone != null ? ' ' + timeZone.getID() : ""));
+ addCookie(response,
+ (locale != null ? toLocaleValue(locale) : "-") + (timeZone != null ? ' ' + timeZone.getID() : ""));
}
else {
removeCookie(response);
@@ -206,6 +234,34 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte
/**
+ * Parse the given locale value coming from an incoming cookie.
+ * <p>The default implementation calls {@link StringUtils#parseLocaleString(String)}
+ * or JDK 7's {@link Locale#forLanguageTag(String)}, depending on the
+ * {@link #setLanguageTagCompliant "languageTagCompliant"} configuration property.
+ * @param locale the locale value to parse
+ * @return the corresponding {@code Locale} instance
+ * @since 4.3
+ */
+ @UsesJava7
+ protected Locale parseLocaleValue(String locale) {
+ return (isLanguageTagCompliant() ? Locale.forLanguageTag(locale) : StringUtils.parseLocaleString(locale));
+ }
+
+ /**
+ * Render the given locale as a text value for inclusion in a cookie.
+ * <p>The default implementation calls {@link Locale#toString()}
+ * or JDK 7's {@link Locale#toLanguageTag()}, depending on the
+ * {@link #setLanguageTagCompliant "languageTagCompliant"} configuration property.
+ * @param locale the locale to stringify
+ * @return a String representation for the given locale
+ * @since 4.3
+ */
+ @UsesJava7
+ protected String toLocaleValue(Locale locale) {
+ return (isLanguageTagCompliant() ? locale.toLanguageTag() : locale.toString());
+ }
+
+ /**
* Determine the default locale for the given request,
* Called if no locale cookie has been found.
* <p>The default implementation returns the specified default locale,
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/LocaleChangeInterceptor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/LocaleChangeInterceptor.java
index 099e4b46..7642bcd0 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/LocaleChangeInterceptor.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/LocaleChangeInterceptor.java
@@ -16,6 +16,7 @@
package org.springframework.web.servlet.i18n;
+import java.util.Locale;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -23,6 +24,7 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import org.springframework.lang.UsesJava7;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;
@@ -54,6 +56,8 @@ public class LocaleChangeInterceptor extends HandlerInterceptorAdapter {
private boolean ignoreInvalidLocale = false;
+ private boolean languageTagCompliant = false;
+
/**
* Set the name of the parameter that contains a locale specification
@@ -104,6 +108,29 @@ public class LocaleChangeInterceptor extends HandlerInterceptorAdapter {
return this.ignoreInvalidLocale;
}
+ /**
+ * Specify whether to parse request parameter values as BCP 47 language tags
+ * instead of Java's legacy locale specification format.
+ * The default is {@code false}.
+ * <p>Note: This mode requires JDK 7 or higher. Set this flag to {@code true}
+ * for BCP 47 compliance on JDK 7+ only.
+ * @since 4.3
+ * @see Locale#forLanguageTag(String)
+ * @see Locale#toLanguageTag()
+ */
+ public void setLanguageTagCompliant(boolean languageTagCompliant) {
+ this.languageTagCompliant = languageTagCompliant;
+ }
+
+ /**
+ * Return whether to use BCP 47 language tags instead of Java's legacy
+ * locale specification format.
+ * @since 4.3
+ */
+ public boolean isLanguageTagCompliant() {
+ return this.languageTagCompliant;
+ }
+
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
@@ -118,7 +145,7 @@ public class LocaleChangeInterceptor extends HandlerInterceptorAdapter {
"No LocaleResolver found: not in a DispatcherServlet request?");
}
try {
- localeResolver.setLocale(request, response, StringUtils.parseLocaleString(newLocale));
+ localeResolver.setLocale(request, response, parseLocaleValue(newLocale));
}
catch (IllegalArgumentException ex) {
if (isIgnoreInvalidLocale()) {
@@ -147,4 +174,18 @@ public class LocaleChangeInterceptor extends HandlerInterceptorAdapter {
return false;
}
+ /**
+ * Parse the given locale value as coming from a request parameter.
+ * <p>The default implementation calls {@link StringUtils#parseLocaleString(String)}
+ * or JDK 7's {@link Locale#forLanguageTag(String)}, depending on the
+ * {@link #setLanguageTagCompliant "languageTagCompliant"} configuration property.
+ * @param locale the locale value to parse
+ * @return the corresponding {@code Locale} instance
+ * @since 4.3
+ */
+ @UsesJava7
+ protected Locale parseLocaleValue(String locale) {
+ return (isLanguageTagCompliant() ? Locale.forLanguageTag(locale) : StringUtils.parseLocaleString(locale));
+ }
+
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/AbstractController.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/AbstractController.java
index 46fab982..c37e656a 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/AbstractController.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/AbstractController.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
+import org.springframework.http.HttpMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.support.WebContentGenerator;
import org.springframework.web.util.WebUtils;
@@ -87,6 +88,7 @@ import org.springframework.web.util.WebUtils;
*
* @author Rod Johnson
* @author Juergen Hoeller
+ * @author Rossen Stoyanchev
* @see WebContentInterceptor
*/
public abstract class AbstractController extends WebContentGenerator implements Controller {
@@ -95,6 +97,26 @@ public abstract class AbstractController extends WebContentGenerator implements
/**
+ * Create a new AbstractController which supports
+ * HTTP methods GET, HEAD and POST by default.
+ */
+ public AbstractController() {
+ this(true);
+ }
+
+ /**
+ * Create a new AbstractController.
+ * @param restrictDefaultSupportedMethods {@code true} if this
+ * controller should support HTTP methods GET, HEAD and POST by default,
+ * or {@code false} if it should be unrestricted
+ * @since 4.3
+ */
+ public AbstractController(boolean restrictDefaultSupportedMethods) {
+ super(restrictDefaultSupportedMethods);
+ }
+
+
+ /**
* Set if controller execution should be synchronized on the session,
* to serialize parallel invocations from the same client.
* <p>More specifically, the execution of the {@code handleRequestInternal}
@@ -129,6 +151,11 @@ public abstract class AbstractController extends WebContentGenerator implements
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
throws Exception {
+ if (HttpMethod.OPTIONS.matches(request.getMethod())) {
+ response.setHeader("Allow", getAllowHeader());
+ return null;
+ }
+
// Delegate to WebContentGenerator for checking and preparing.
checkRequest(request);
prepareResponse(response);
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ParameterizableViewController.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ParameterizableViewController.java
index bee18861..5d512887 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ParameterizableViewController.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ParameterizableViewController.java
@@ -19,6 +19,7 @@ package org.springframework.web.servlet.mvc;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
@@ -43,6 +44,11 @@ public class ParameterizableViewController extends AbstractController {
private boolean statusOnly;
+ public ParameterizableViewController() {
+ super(false);
+ setSupportedMethods(HttpMethod.GET.name(), HttpMethod.HEAD.name());
+ }
+
/**
* Set a view name for the ModelAndView to return, to be resolved by the
* DispatcherServlet via a ViewResolver. Will override any pre-existing
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ServletForwardingController.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ServletForwardingController.java
index 50cf151b..3df9b642 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ServletForwardingController.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ServletForwardingController.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -90,6 +90,11 @@ public class ServletForwardingController extends AbstractController implements B
private String beanName;
+ public ServletForwardingController() {
+ super(false);
+ }
+
+
/**
* Set the name of the servlet to forward to,
* i.e. the "servlet-name" of the target servlet in web.xml.
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ServletWrappingController.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ServletWrappingController.java
index 8ca0ef0a..c16534ff 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ServletWrappingController.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ServletWrappingController.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -78,13 +78,11 @@ import org.springframework.web.servlet.ModelAndView;
* @author Juergen Hoeller
* @since 1.1.1
* @see ServletForwardingController
- * @see org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor
- * @see org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter
*/
public class ServletWrappingController extends AbstractController
implements BeanNameAware, InitializingBean, DisposableBean {
- private Class<?> servletClass;
+ private Class<? extends Servlet> servletClass;
private String servletName;
@@ -95,12 +93,17 @@ public class ServletWrappingController extends AbstractController
private Servlet servletInstance;
+ public ServletWrappingController() {
+ super(false);
+ }
+
+
/**
* Set the class of the servlet to wrap.
* Needs to implement {@code javax.servlet.Servlet}.
* @see javax.servlet.Servlet
*/
- public void setServletClass(Class<?> servletClass) {
+ public void setServletClass(Class<? extends Servlet> servletClass) {
this.servletClass = servletClass;
}
@@ -133,27 +136,23 @@ public class ServletWrappingController extends AbstractController
@Override
public void afterPropertiesSet() throws Exception {
if (this.servletClass == null) {
- throw new IllegalArgumentException("servletClass is required");
- }
- if (!Servlet.class.isAssignableFrom(this.servletClass)) {
- throw new IllegalArgumentException("servletClass [" + this.servletClass.getName() +
- "] needs to implement interface [javax.servlet.Servlet]");
+ throw new IllegalArgumentException("'servletClass' is required");
}
if (this.servletName == null) {
this.servletName = this.beanName;
}
- this.servletInstance = (Servlet) this.servletClass.newInstance();
+ this.servletInstance = this.servletClass.newInstance();
this.servletInstance.init(new DelegatingServletConfig());
}
/**
- * Invoke the the wrapped Servlet instance.
+ * Invoke the wrapped Servlet instance.
* @see javax.servlet.Servlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
*/
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
- throws Exception {
+ throws Exception {
this.servletInstance.service(request, response);
return null;
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/WebContentInterceptor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/WebContentInterceptor.java
index 038667f4..5e5810e5 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/WebContentInterceptor.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/WebContentInterceptor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -102,7 +102,6 @@ public class WebContentInterceptor extends WebContentGenerator implements Handle
* <p>Only relevant for the "cacheMappings" setting.
* @see #setCacheMappings
* @see org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#setUrlPathHelper
- * @see org.springframework.web.servlet.mvc.multiaction.AbstractUrlMethodNameResolver#setUrlPathHelper
*/
public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
Assert.notNull(urlPathHelper, "UrlPathHelper must not be null");
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java
index f3cdd873..2a26985c 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java
@@ -96,8 +96,6 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.SessionAttributes;
-import org.springframework.web.bind.annotation.support.HandlerMethodInvoker;
-import org.springframework.web.bind.annotation.support.HandlerMethodResolver;
import org.springframework.web.bind.support.DefaultSessionAttributeStore;
import org.springframework.web.bind.support.SessionAttributeStore;
import org.springframework.web.bind.support.WebArgumentResolver;
@@ -110,9 +108,6 @@ import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
-import org.springframework.web.servlet.mvc.multiaction.InternalPathMethodNameResolver;
-import org.springframework.web.servlet.mvc.multiaction.MethodNameResolver;
-import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
import org.springframework.web.servlet.support.RequestContextUtils;
import org.springframework.web.servlet.support.WebContentGenerator;
import org.springframework.web.util.UrlPathHelper;
@@ -164,7 +159,8 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator
private PathMatcher pathMatcher = new AntPathMatcher();
- private MethodNameResolver methodNameResolver = new InternalPathMethodNameResolver();
+ private org.springframework.web.servlet.mvc.multiaction.MethodNameResolver methodNameResolver =
+ new org.springframework.web.servlet.mvc.multiaction.InternalPathMethodNameResolver();
private WebBindingInitializer webBindingInitializer;
@@ -255,7 +251,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator
* <p>Will only kick in when the handler method cannot be resolved uniquely
* through the annotation metadata already.
*/
- public void setMethodNameResolver(MethodNameResolver methodNameResolver) {
+ public void setMethodNameResolver(org.springframework.web.servlet.mvc.multiaction.MethodNameResolver methodNameResolver) {
this.methodNameResolver = methodNameResolver;
}
@@ -524,9 +520,10 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator
/**
- * Servlet-specific subclass of {@link HandlerMethodResolver}.
+ * Servlet-specific subclass of {@code HandlerMethodResolver}.
*/
- private class ServletHandlerMethodResolver extends HandlerMethodResolver {
+ @SuppressWarnings("deprecation")
+ private class ServletHandlerMethodResolver extends org.springframework.web.bind.annotation.support.HandlerMethodResolver {
private final Map<Method, RequestMappingInfo> mappings = new HashMap<Method, RequestMappingInfo>();
@@ -674,7 +671,8 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator
if (!allowedMethods.isEmpty()) {
throw new HttpRequestMethodNotSupportedException(request.getMethod(), StringUtils.toStringArray(allowedMethods));
}
- throw new NoSuchRequestHandlingMethodException(lookupPath, request.getMethod(), request.getParameterMap());
+ throw new org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException(
+ lookupPath, request.getMethod(), request.getParameterMap());
}
}
@@ -768,13 +766,14 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator
/**
- * Servlet-specific subclass of {@link HandlerMethodInvoker}.
+ * Servlet-specific subclass of {@code HandlerMethodInvoker}.
*/
- private class ServletHandlerMethodInvoker extends HandlerMethodInvoker {
+ @SuppressWarnings("deprecation")
+ private class ServletHandlerMethodInvoker extends org.springframework.web.bind.annotation.support.HandlerMethodInvoker {
private boolean responseArgumentUsed = false;
- private ServletHandlerMethodInvoker(HandlerMethodResolver resolver) {
+ private ServletHandlerMethodInvoker(org.springframework.web.bind.annotation.support.HandlerMethodResolver resolver) {
super(resolver, webBindingInitializer, sessionAttributeStore, parameterNameDiscoverer,
customArgumentResolvers, messageConverters);
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationMappingUtils.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationMappingUtils.java
index 6c1288fc..331da205 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationMappingUtils.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationMappingUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +30,7 @@ import org.springframework.web.util.WebUtils;
*
* @author Juergen Hoeller
* @author Arjen Poutsma
+ * @author Rossen Stoyanchev
* @since 2.5.2
* @deprecated as of Spring 3.2, together with {@link DefaultAnnotationHandlerMapping},
* {@link AnnotationMethodHandlerAdapter}, and {@link AnnotationMethodHandlerExceptionResolver}.
@@ -43,11 +44,12 @@ abstract class ServletAnnotationMappingUtils {
* @param request the current HTTP request to check
*/
public static boolean checkRequestMethod(RequestMethod[] methods, HttpServletRequest request) {
- if (ObjectUtils.isEmpty(methods)) {
+ String inputMethod = request.getMethod();
+ if (ObjectUtils.isEmpty(methods) && !RequestMethod.OPTIONS.name().equals(inputMethod)) {
return true;
}
for (RequestMethod method : methods) {
- if (method.name().equals(request.getMethod())) {
+ if (method.name().equals(inputMethod)) {
return true;
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/AbstractMediaTypeExpression.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/AbstractMediaTypeExpression.java
index e6708f27..d3114c13 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/AbstractMediaTypeExpression.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/AbstractMediaTypeExpression.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,13 +16,10 @@
package org.springframework.web.servlet.mvc.condition;
-import javax.servlet.http.HttpServletRequest;
-
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.MediaType;
-import org.springframework.web.HttpMediaTypeException;
import org.springframework.web.bind.annotation.RequestMapping;
/**
@@ -33,7 +30,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
* @author Rossen Stoyanchev
* @since 3.1
*/
-abstract class AbstractMediaTypeExpression implements Comparable<AbstractMediaTypeExpression>, MediaTypeExpression {
+abstract class AbstractMediaTypeExpression implements MediaTypeExpression, Comparable<AbstractMediaTypeExpression> {
protected final Log logger = LogFactory.getLog(getClass());
@@ -70,19 +67,6 @@ abstract class AbstractMediaTypeExpression implements Comparable<AbstractMediaTy
}
- public final boolean match(HttpServletRequest request) {
- try {
- boolean match = matchMediaType(request);
- return (!this.isNegated ? match : !match);
- }
- catch (HttpMediaTypeException ex) {
- return false;
- }
- }
-
- protected abstract boolean matchMediaType(HttpServletRequest request) throws HttpMediaTypeException;
-
-
@Override
public int compareTo(AbstractMediaTypeExpression other) {
return MediaType.SPECIFICITY_COMPARATOR.compare(this.getMediaType(), other.getMediaType());
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/AbstractRequestCondition.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/AbstractRequestCondition.java
index 92dd4e87..26fb5641 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/AbstractRequestCondition.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/AbstractRequestCondition.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/ConsumesRequestCondition.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/ConsumesRequestCondition.java
index f53e34b9..e7a6af41 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/ConsumesRequestCondition.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/ConsumesRequestCondition.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author 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,8 +28,10 @@ import javax.servlet.http.HttpServletRequest;
import org.springframework.http.InvalidMediaTypeException;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
+import org.springframework.web.HttpMediaTypeException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.cors.CorsUtils;
import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition.HeaderExpression;
/**
@@ -46,6 +48,9 @@ import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition.Hea
*/
public final class ConsumesRequestCondition extends AbstractRequestCondition<ConsumesRequestCondition> {
+ private final static ConsumesRequestCondition PRE_FLIGHT_MATCH = new ConsumesRequestCondition();
+
+
private final List<ConsumeMediaTypeExpression> expressions;
@@ -160,13 +165,25 @@ public final class ConsumesRequestCondition extends AbstractRequestCondition<Con
*/
@Override
public ConsumesRequestCondition getMatchingCondition(HttpServletRequest request) {
+ if (CorsUtils.isPreFlightRequest(request)) {
+ return PRE_FLIGHT_MATCH;
+ }
if (isEmpty()) {
return this;
}
- Set<ConsumeMediaTypeExpression> result = new LinkedHashSet<ConsumeMediaTypeExpression>(expressions);
+ MediaType contentType;
+ try {
+ contentType = StringUtils.hasLength(request.getContentType()) ?
+ MediaType.parseMediaType(request.getContentType()) :
+ MediaType.APPLICATION_OCTET_STREAM;
+ }
+ catch (InvalidMediaTypeException ex) {
+ return null;
+ }
+ Set<ConsumeMediaTypeExpression> result = new LinkedHashSet<ConsumeMediaTypeExpression>(this.expressions);
for (Iterator<ConsumeMediaTypeExpression> iterator = result.iterator(); iterator.hasNext();) {
ConsumeMediaTypeExpression expression = iterator.next();
- if (!expression.match(request)) {
+ if (!expression.match(contentType)) {
iterator.remove();
}
}
@@ -214,18 +231,9 @@ public final class ConsumesRequestCondition extends AbstractRequestCondition<Con
super(mediaType, negated);
}
- @Override
- protected boolean matchMediaType(HttpServletRequest request) throws HttpMediaTypeNotSupportedException {
- try {
- MediaType contentType = StringUtils.hasLength(request.getContentType()) ?
- MediaType.parseMediaType(request.getContentType()) :
- MediaType.APPLICATION_OCTET_STREAM;
- return getMediaType().includes(contentType);
- }
- catch (InvalidMediaTypeException ex) {
- throw new HttpMediaTypeNotSupportedException(
- "Can't parse Content-Type [" + request.getContentType() + "]: " + ex.getMessage());
- }
+ public final boolean match(MediaType contentType) {
+ boolean match = getMediaType().includes(contentType);
+ return (!isNegated() ? match : !match);
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/HeadersRequestCondition.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/HeadersRequestCondition.java
index ffa47be0..aaa0847c 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/HeadersRequestCondition.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/HeadersRequestCondition.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@ import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.cors.CorsUtils;
/**
* A logical conjunction (' && ') request condition that matches a request against
@@ -38,6 +39,9 @@ import org.springframework.web.bind.annotation.RequestMapping;
*/
public final class HeadersRequestCondition extends AbstractRequestCondition<HeadersRequestCondition> {
+ private final static HeadersRequestCondition PRE_FLIGHT_MATCH = new HeadersRequestCondition();
+
+
private final Set<HeaderExpression> expressions;
@@ -105,6 +109,9 @@ public final class HeadersRequestCondition extends AbstractRequestCondition<Head
*/
@Override
public HeadersRequestCondition getMatchingCondition(HttpServletRequest request) {
+ if (CorsUtils.isPreFlightRequest(request)) {
+ return PRE_FLIGHT_MATCH;
+ }
for (HeaderExpression expression : expressions) {
if (!expression.match(request)) {
return null;
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/ProducesRequestCondition.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/ProducesRequestCondition.java
index bc99eae4..55931d4b 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/ProducesRequestCondition.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/ProducesRequestCondition.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author 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,10 +26,12 @@ import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType;
+import org.springframework.web.HttpMediaTypeException;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.ServletWebRequest;
+import org.springframework.web.cors.CorsUtils;
import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition.HeaderExpression;
/**
@@ -45,6 +47,11 @@ import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition.Hea
*/
public final class ProducesRequestCondition extends AbstractRequestCondition<ProducesRequestCondition> {
+ private final static ProducesRequestCondition PRE_FLIGHT_MATCH = new ProducesRequestCondition();
+
+ private static final ProducesRequestCondition EMPTY_CONDITION = new ProducesRequestCondition();
+
+
private final List<ProduceMediaTypeExpression> MEDIA_TYPE_ALL_LIST =
Collections.singletonList(new ProduceMediaTypeExpression("*/*"));
@@ -176,17 +183,35 @@ public final class ProducesRequestCondition extends AbstractRequestCondition<Pro
*/
@Override
public ProducesRequestCondition getMatchingCondition(HttpServletRequest request) {
+ if (CorsUtils.isPreFlightRequest(request)) {
+ return PRE_FLIGHT_MATCH;
+ }
if (isEmpty()) {
return this;
}
+ List<MediaType> acceptedMediaTypes;
+ try {
+ acceptedMediaTypes = getAcceptedMediaTypes(request);
+ }
+ catch (HttpMediaTypeException ex) {
+ return null;
+ }
Set<ProduceMediaTypeExpression> result = new LinkedHashSet<ProduceMediaTypeExpression>(expressions);
for (Iterator<ProduceMediaTypeExpression> iterator = result.iterator(); iterator.hasNext();) {
ProduceMediaTypeExpression expression = iterator.next();
- if (!expression.match(request)) {
+ if (!expression.match(acceptedMediaTypes)) {
iterator.remove();
}
}
- return (result.isEmpty()) ? null : new ProducesRequestCondition(result, this.contentNegotiationManager);
+ if (!result.isEmpty()) {
+ return new ProducesRequestCondition(result, this.contentNegotiationManager);
+ }
+ else if (acceptedMediaTypes.contains(MediaType.ALL)) {
+ return EMPTY_CONDITION;
+ }
+ else {
+ return null;
+ }
}
/**
@@ -295,9 +320,12 @@ public final class ProducesRequestCondition extends AbstractRequestCondition<Pro
super(expression);
}
- @Override
- protected boolean matchMediaType(HttpServletRequest request) throws HttpMediaTypeNotAcceptableException {
- List<MediaType> acceptedMediaTypes = getAcceptedMediaTypes(request);
+ public final boolean match(List<MediaType> acceptedMediaTypes) {
+ boolean match = matchMediaType(acceptedMediaTypes);
+ return (!isNegated() ? match : !match);
+ }
+
+ private boolean matchMediaType(List<MediaType> acceptedMediaTypes) {
for (MediaType acceptedMediaType : acceptedMediaTypes) {
if (getMediaType().isCompatibleWith(acceptedMediaType)) {
return true;
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/RequestCondition.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/RequestCondition.java
index 1487381c..542e1a2d 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/RequestCondition.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/RequestCondition.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,27 +18,25 @@ package org.springframework.web.servlet.mvc.condition;
import javax.servlet.http.HttpServletRequest;
-import org.springframework.web.bind.annotation.RequestMapping;
-
/**
- * The contract for request conditions in Spring MVC's mapping infrastructure.
+ * Contract for request mapping conditions.
*
* <p>Request conditions can be combined via {@link #combine(Object)}, matched to
* a request via {@link #getMatchingCondition(HttpServletRequest)}, and compared
* to each other via {@link #compareTo(Object, HttpServletRequest)} to determine
- * which matches a request more closely.
+ * which is a closer match for a given request.
*
* @author Rossen Stoyanchev
* @author Arjen Poutsma
* @since 3.1
- * @param <T> the type of objects that this RequestCondition can be combined with and compared to
+ * @param <T> the type of objects that this RequestCondition can be combined
+ * with and compared to
*/
public interface RequestCondition<T> {
/**
- * Defines the rules for combining this condition (i.e. the current instance)
- * with another condition. For example combining type- and method-level
- * {@link RequestMapping} conditions.
+ * Combine this condition with another such as conditions from a
+ * type-level and method-level {@code @RequestMapping} annotation.
* @param other the condition to combine with.
* @return a request condition instance that is the result of combining
* the two condition instances.
@@ -46,18 +44,21 @@ public interface RequestCondition<T> {
T combine(T other);
/**
- * Checks if this condition matches the given request and returns a
- * potentially new request condition with content tailored to the
- * current request. For example a condition with URL patterns might
- * return a new condition that contains matching patterns sorted
- * with best matching patterns on top.
- * @return a condition instance in case of a match;
- * or {@code null} if there is no match
+ * Check if the condition matches the request returning a potentially new
+ * instance created for the current request. For example a condition with
+ * multiple URL patterns may return a new instance only with those patterns
+ * that match the request.
+ * <p>For CORS pre-flight requests, conditions should match to the would-be,
+ * actual request (e.g. URL pattern, query parameters, and the HTTP method
+ * from the "Access-Control-Request-Method" header). If a condition cannot
+ * be matched to a pre-flight request it should return an instance with
+ * empty content thus not causing a failure to match.
+ * @return a condition instance in case of a match or {@code null} otherwise.
*/
T getMatchingCondition(HttpServletRequest request);
/**
- * Compares this condition to another condition in the context of
+ * Compare this condition to another condition in the context of
* a specific request. This method assumes both instances have
* been obtained via {@link #getMatchingCondition(HttpServletRequest)}
* to ensure they have content relevant to current request only.
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/RequestMethodsRequestCondition.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/RequestMethodsRequestCondition.java
index 6d18fcf2..b044ae5b 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/RequestMethodsRequestCondition.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/RequestMethodsRequestCondition.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author 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,9 +22,13 @@ import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
+import javax.servlet.DispatcherType;
import javax.servlet.http.HttpServletRequest;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.cors.CorsUtils;
/**
* A logical disjunction (' || ') request condition that matches a request
@@ -36,6 +40,10 @@ import org.springframework.web.bind.annotation.RequestMethod;
*/
public final class RequestMethodsRequestCondition extends AbstractRequestCondition<RequestMethodsRequestCondition> {
+ private static final RequestMethodsRequestCondition GET_CONDITION =
+ new RequestMethodsRequestCondition(RequestMethod.GET);
+
+
private final Set<RequestMethod> methods;
@@ -90,32 +98,55 @@ public final class RequestMethodsRequestCondition extends AbstractRequestConditi
* Check if any of the HTTP request methods match the given request and
* return an instance that contains the matching HTTP request method only.
* @param request the current request
- * @return the same instance if the condition is empty, a new condition with
- * the matched request method, or {@code null} if no request methods match
+ * @return the same instance if the condition is empty (unless the request
+ * method is HTTP OPTIONS), a new condition with the matched request method,
+ * or {@code null} if there is no match or the condition is empty and the
+ * request method is OPTIONS.
*/
@Override
public RequestMethodsRequestCondition getMatchingCondition(HttpServletRequest request) {
- if (this.methods.isEmpty()) {
- return this;
+ if (CorsUtils.isPreFlightRequest(request)) {
+ return matchPreFlight(request);
}
- RequestMethod incomingRequestMethod = getRequestMethod(request);
- if (incomingRequestMethod != null) {
- for (RequestMethod method : this.methods) {
- if (method.equals(incomingRequestMethod)) {
- return new RequestMethodsRequestCondition(method);
- }
+
+ if (getMethods().isEmpty()) {
+ if (RequestMethod.OPTIONS.name().equals(request.getMethod()) &&
+ !DispatcherType.ERROR.equals(request.getDispatcherType())) {
+
+ return null; // No implicit match for OPTIONS (we handle it)
}
+ return this;
}
- return null;
+
+ return matchRequestMethod(request.getMethod());
}
- private RequestMethod getRequestMethod(HttpServletRequest request) {
- try {
- return RequestMethod.valueOf(request.getMethod());
+ /**
+ * On a pre-flight request match to the would-be, actual request.
+ * Hence empty conditions is a match, otherwise try to match to the HTTP
+ * method in the "Access-Control-Request-Method" header.
+ */
+ private RequestMethodsRequestCondition matchPreFlight(HttpServletRequest request) {
+ if (getMethods().isEmpty()) {
+ return this;
}
- catch (IllegalArgumentException ex) {
- return null;
+ String expectedMethod = request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD);
+ return matchRequestMethod(expectedMethod);
+ }
+
+ private RequestMethodsRequestCondition matchRequestMethod(String httpMethodValue) {
+ HttpMethod httpMethod = HttpMethod.resolve(httpMethodValue);
+ if (httpMethod != null) {
+ for (RequestMethod method : getMethods()) {
+ if (httpMethod.matches(method.name())) {
+ return new RequestMethodsRequestCondition(method);
+ }
+ }
+ if (httpMethod == HttpMethod.HEAD && getMethods().contains(RequestMethod.GET)) {
+ return GET_CONDITION;
+ }
}
+ return null;
}
/**
@@ -131,7 +162,18 @@ public final class RequestMethodsRequestCondition extends AbstractRequestConditi
*/
@Override
public int compareTo(RequestMethodsRequestCondition other, HttpServletRequest request) {
- return (other.methods.size() - this.methods.size());
+ if (other.methods.size() != this.methods.size()) {
+ return other.methods.size() - this.methods.size();
+ }
+ else if (this.methods.size() == 1) {
+ if (this.methods.contains(RequestMethod.HEAD) && other.methods.contains(RequestMethod.GET)) {
+ return -1;
+ }
+ else if (this.methods.contains(RequestMethod.GET) && other.methods.contains(RequestMethod.HEAD)) {
+ return 1;
+ }
+ }
+ return 0;
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfo.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfo.java
index 395ecb9c..6ecc7687 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfo.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfo.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,12 +19,11 @@ package org.springframework.web.servlet.mvc.method;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
-import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.cors.CorsUtils;
import org.springframework.web.servlet.mvc.condition.ConsumesRequestCondition;
import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition;
import org.springframework.web.servlet.mvc.condition.ParamsRequestCondition;
@@ -36,7 +35,7 @@ import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondit
import org.springframework.web.util.UrlPathHelper;
/**
- * Encapsulates the following request mapping conditions:
+ * A {@link RequestCondition} that consists of the following other conditions:
* <ol>
* <li>{@link PatternsRequestCondition}
* <li>{@link RequestMethodsRequestCondition}
@@ -215,15 +214,7 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);
if (methods == null || params == null || headers == null || consumes == null || produces == null) {
- if (CorsUtils.isPreFlightRequest(request)) {
- methods = getAccessControlRequestMethodCondition(request);
- if (methods == null || params == null) {
- return null;
- }
- }
- else {
- return null;
- }
+ return null;
}
PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);
@@ -241,22 +232,6 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
}
/**
- * Return a matching RequestMethodsRequestCondition based on the expected
- * HTTP method specified in a CORS pre-flight request.
- */
- private RequestMethodsRequestCondition getAccessControlRequestMethodCondition(HttpServletRequest request) {
- String expectedMethod = request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD);
- if (StringUtils.hasText(expectedMethod)) {
- for (RequestMethod method : getMethodsCondition().getMethods()) {
- if (expectedMethod.equalsIgnoreCase(method.name())) {
- return new RequestMethodsRequestCondition(method);
- }
- }
- }
- return null;
- }
-
- /**
* Compares "this" info (i.e. the current instance) with another info in the context of a request.
* <p>Note: It is assumed both instances have been obtained via
* {@link #getMatchingCondition(HttpServletRequest)} to ensure they have conditions with
@@ -264,7 +239,15 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
*/
@Override
public int compareTo(RequestMappingInfo other, HttpServletRequest request) {
- int result = this.patternsCondition.compareTo(other.getPatternsCondition(), request);
+ int result;
+ // Automatic vs explicit HTTP HEAD mapping
+ if (HttpMethod.HEAD.matches(request.getMethod())) {
+ result = this.methodsCondition.compareTo(other.getMethodsCondition(), request);
+ if (result != 0) {
+ return result;
+ }
+ }
+ result = this.patternsCondition.compareTo(other.getPatternsCondition(), request);
if (result != 0) {
return result;
}
@@ -284,6 +267,7 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
if (result != 0) {
return result;
}
+ // Implicit (no method) vs explicit HTTP method mappings
result = this.methodsCondition.compareTo(other.getMethodsCondition(), request);
if (result != 0) {
return result;
@@ -537,13 +521,25 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
private ContentNegotiationManager contentNegotiationManager;
/**
- * Set a custom UrlPathHelper to use for the PatternsRequestCondition.
- * <p>By default this is not set.
+ * @deprecated as of Spring 4.2.8, in favor of {@link #setUrlPathHelper}
*/
+ @Deprecated
public void setPathHelper(UrlPathHelper pathHelper) {
this.urlPathHelper = pathHelper;
}
+ /**
+ * Set a custom UrlPathHelper to use for the PatternsRequestCondition.
+ * <p>By default this is not set.
+ * @since 4.2.8
+ */
+ public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
+ this.urlPathHelper = urlPathHelper;
+ }
+
+ /**
+ * Return a custom UrlPathHelper to use for the PatternsRequestCondition, if any.
+ */
public UrlPathHelper getUrlPathHelper() {
return this.urlPathHelper;
}
@@ -556,24 +552,30 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
this.pathMatcher = pathMatcher;
}
+ /**
+ * Return a custom PathMatcher to use for the PatternsRequestCondition, if any.
+ */
public PathMatcher getPathMatcher() {
return this.pathMatcher;
}
/**
- * Whether to apply trailing slash matching in PatternsRequestCondition.
+ * Set whether to apply trailing slash matching in PatternsRequestCondition.
* <p>By default this is set to 'true'.
*/
public void setTrailingSlashMatch(boolean trailingSlashMatch) {
this.trailingSlashMatch = trailingSlashMatch;
}
+ /**
+ * Return whether to apply trailing slash matching in PatternsRequestCondition.
+ */
public boolean useTrailingSlashMatch() {
return this.trailingSlashMatch;
}
/**
- * Whether to apply suffix pattern matching in PatternsRequestCondition.
+ * Set whether to apply suffix pattern matching in PatternsRequestCondition.
* <p>By default this is set to 'true'.
* @see #setRegisteredSuffixPatternMatch(boolean)
*/
@@ -581,14 +583,17 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
this.suffixPatternMatch = suffixPatternMatch;
}
+ /**
+ * Return whether to apply suffix pattern matching in PatternsRequestCondition.
+ */
public boolean useSuffixPatternMatch() {
return this.suffixPatternMatch;
}
/**
- * Whether suffix pattern matching should be restricted to registered
+ * Set whether suffix pattern matching should be restricted to registered
* file extensions only. Setting this property also sets
- * suffixPatternMatch=true and requires that a
+ * {@code suffixPatternMatch=true} and requires that a
* {@link #setContentNegotiationManager} is also configured in order to
* obtain the registered file extensions.
*/
@@ -597,6 +602,10 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
this.suffixPatternMatch = (registeredSuffixPatternMatch || this.suffixPatternMatch);
}
+ /**
+ * Return whether suffix pattern matching should be restricted to registered
+ * file extensions only.
+ */
public boolean useRegisteredSuffixPatternMatch() {
return this.registeredSuffixPatternMatch;
}
@@ -617,10 +626,14 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
* Set the ContentNegotiationManager to use for the ProducesRequestCondition.
* <p>By default this is not set.
*/
- public void setContentNegotiationManager(ContentNegotiationManager manager) {
- this.contentNegotiationManager = manager;
+ public void setContentNegotiationManager(ContentNegotiationManager contentNegotiationManager) {
+ this.contentNegotiationManager = contentNegotiationManager;
}
+ /**
+ * Return the ContentNegotiationManager to use for the ProducesRequestCondition,
+ * if any.
+ */
public ContentNegotiationManager getContentNegotiationManager() {
return this.contentNegotiationManager;
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java
index 5c18540a..2c57650d 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,10 +16,10 @@
package org.springframework.web.servlet.mvc.method;
+import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
-import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
@@ -29,6 +29,8 @@ import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
import org.springframework.http.InvalidMediaTypeException;
import org.springframework.http.MediaType;
import org.springframework.util.CollectionUtils;
@@ -43,7 +45,6 @@ import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping;
import org.springframework.web.servlet.mvc.condition.NameValueExpression;
-import org.springframework.web.servlet.mvc.condition.ParamsRequestCondition;
import org.springframework.web.util.WebUtils;
/**
@@ -56,6 +57,19 @@ import org.springframework.web.util.WebUtils;
*/
public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping<RequestMappingInfo> {
+ private static final Method HTTP_OPTIONS_HANDLE_METHOD;
+
+ static {
+ try {
+ HTTP_OPTIONS_HANDLE_METHOD = HttpOptionsHandler.class.getMethod("handle");
+ }
+ catch (NoSuchMethodException ex) {
+ // Should never happen
+ throw new IllegalStateException("Failed to retrieve internal handler method for HTTP OPTIONS", ex);
+ }
+ }
+
+
protected RequestMappingInfoHandlerMapping() {
setHandlerMethodMappingNamingStrategy(new RequestMappingInfoHandlerMethodMappingNamingStrategy());
}
@@ -167,116 +181,284 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
}
/**
- * Iterate all RequestMappingInfos once again, look if any match by URL at
- * least and raise exceptions accordingly.
+ * Iterate all RequestMappingInfo's once again, look if any match by URL at
+ * least and raise exceptions according to what doesn't match.
* @throws HttpRequestMethodNotSupportedException if there are matches by URL
* but not by HTTP method
* @throws HttpMediaTypeNotAcceptableException if there are matches by URL
* but not by consumable/producible media types
*/
@Override
- protected HandlerMethod handleNoMatch(Set<RequestMappingInfo> requestMappingInfos,
- String lookupPath, HttpServletRequest request) throws ServletException {
+ protected HandlerMethod handleNoMatch(Set<RequestMappingInfo> infos, String lookupPath,
+ HttpServletRequest request) throws ServletException {
- Set<String> allowedMethods = new LinkedHashSet<String>(4);
+ PartialMatchHelper helper = new PartialMatchHelper(infos, request);
- Set<RequestMappingInfo> patternMatches = new HashSet<RequestMappingInfo>();
- Set<RequestMappingInfo> patternAndMethodMatches = new HashSet<RequestMappingInfo>();
+ if (helper.isEmpty()) {
+ return null;
+ }
+
+ if (helper.hasMethodsMismatch()) {
+ Set<String> methods = helper.getAllowedMethods();
+ if (HttpMethod.OPTIONS.matches(request.getMethod())) {
+ HttpOptionsHandler handler = new HttpOptionsHandler(methods);
+ return new HandlerMethod(handler, HTTP_OPTIONS_HANDLE_METHOD);
+ }
+ throw new HttpRequestMethodNotSupportedException(request.getMethod(), methods);
+ }
- for (RequestMappingInfo info : requestMappingInfos) {
- if (info.getPatternsCondition().getMatchingCondition(request) != null) {
- patternMatches.add(info);
- if (info.getMethodsCondition().getMatchingCondition(request) != null) {
- patternAndMethodMatches.add(info);
+ if (helper.hasConsumesMismatch()) {
+ Set<MediaType> mediaTypes = helper.getConsumableMediaTypes();
+ MediaType contentType = null;
+ if (StringUtils.hasLength(request.getContentType())) {
+ try {
+ contentType = MediaType.parseMediaType(request.getContentType());
}
- else {
- for (RequestMethod method : info.getMethodsCondition().getMethods()) {
- allowedMethods.add(method.name());
- }
+ catch (InvalidMediaTypeException ex) {
+ throw new HttpMediaTypeNotSupportedException(ex.getMessage());
}
}
+ throw new HttpMediaTypeNotSupportedException(contentType, new ArrayList<MediaType>(mediaTypes));
}
- if (patternMatches.isEmpty()) {
- return null;
+ if (helper.hasProducesMismatch()) {
+ Set<MediaType> mediaTypes = helper.getProducibleMediaTypes();
+ throw new HttpMediaTypeNotAcceptableException(new ArrayList<MediaType>(mediaTypes));
}
- else if (patternAndMethodMatches.isEmpty() && !allowedMethods.isEmpty()) {
- throw new HttpRequestMethodNotSupportedException(request.getMethod(), allowedMethods);
+
+ if (helper.hasParamsMismatch()) {
+ List<String[]> conditions = helper.getParamConditions();
+ throw new UnsatisfiedServletRequestParameterException(conditions, request.getParameterMap());
}
- Set<MediaType> consumableMediaTypes;
- Set<MediaType> producibleMediaTypes;
- List<String[]> paramConditions;
+ return null;
+ }
+
+
+ /**
+ * Aggregate all partial matches and expose methods checking across them.
+ */
+ private static class PartialMatchHelper {
+
+ private final List<PartialMatch> partialMatches = new ArrayList<PartialMatch>();
- if (patternAndMethodMatches.isEmpty()) {
- consumableMediaTypes = getConsumableMediaTypes(request, patternMatches);
- producibleMediaTypes = getProducibleMediaTypes(request, patternMatches);
- paramConditions = getRequestParams(request, patternMatches);
+ public PartialMatchHelper(Set<RequestMappingInfo> infos, HttpServletRequest request) {
+ for (RequestMappingInfo info : infos) {
+ if (info.getPatternsCondition().getMatchingCondition(request) != null) {
+ this.partialMatches.add(new PartialMatch(info, request));
+ }
+ }
}
- else {
- consumableMediaTypes = getConsumableMediaTypes(request, patternAndMethodMatches);
- producibleMediaTypes = getProducibleMediaTypes(request, patternAndMethodMatches);
- paramConditions = getRequestParams(request, patternAndMethodMatches);
+
+ /**
+ * Whether there any partial matches.
+ */
+ public boolean isEmpty() {
+ return this.partialMatches.isEmpty();
}
- if (!consumableMediaTypes.isEmpty()) {
- MediaType contentType = null;
- if (StringUtils.hasLength(request.getContentType())) {
- try {
- contentType = MediaType.parseMediaType(request.getContentType());
+ /**
+ * Any partial matches for "methods"?
+ */
+ public boolean hasMethodsMismatch() {
+ for (PartialMatch match : this.partialMatches) {
+ if (match.hasMethodsMatch()) {
+ return false;
}
- catch (InvalidMediaTypeException ex) {
- throw new HttpMediaTypeNotSupportedException(ex.getMessage());
+ }
+ return true;
+ }
+
+ /**
+ * Any partial matches for "methods" and "consumes"?
+ */
+ public boolean hasConsumesMismatch() {
+ for (PartialMatch match : this.partialMatches) {
+ if (match.hasConsumesMatch()) {
+ return false;
}
}
- throw new HttpMediaTypeNotSupportedException(contentType, new ArrayList<MediaType>(consumableMediaTypes));
+ return true;
}
- else if (!producibleMediaTypes.isEmpty()) {
- throw new HttpMediaTypeNotAcceptableException(new ArrayList<MediaType>(producibleMediaTypes));
+
+ /**
+ * Any partial matches for "methods", "consumes", and "produces"?
+ */
+ public boolean hasProducesMismatch() {
+ for (PartialMatch match : this.partialMatches) {
+ if (match.hasProducesMatch()) {
+ return false;
+ }
+ }
+ return true;
}
- else if (!CollectionUtils.isEmpty(paramConditions)) {
- throw new UnsatisfiedServletRequestParameterException(paramConditions, request.getParameterMap());
+
+ /**
+ * Any partial matches for "methods", "consumes", "produces", and "params"?
+ */
+ public boolean hasParamsMismatch() {
+ for (PartialMatch match : this.partialMatches) {
+ if (match.hasParamsMatch()) {
+ return false;
+ }
+ }
+ return true;
}
- else {
- return null;
+
+ /**
+ * Return declared HTTP methods.
+ */
+ public Set<String> getAllowedMethods() {
+ Set<String> result = new LinkedHashSet<String>();
+ for (PartialMatch match : this.partialMatches) {
+ for (RequestMethod method : match.getInfo().getMethodsCondition().getMethods()) {
+ result.add(method.name());
+ }
+ }
+ return result;
}
- }
- private Set<MediaType> getConsumableMediaTypes(HttpServletRequest request, Set<RequestMappingInfo> partialMatches) {
- Set<MediaType> result = new HashSet<MediaType>();
- for (RequestMappingInfo partialMatch : partialMatches) {
- if (partialMatch.getConsumesCondition().getMatchingCondition(request) == null) {
- result.addAll(partialMatch.getConsumesCondition().getConsumableMediaTypes());
+ /**
+ * Return declared "consumable" types but only among those that also
+ * match the "methods" condition.
+ */
+ public Set<MediaType> getConsumableMediaTypes() {
+ Set<MediaType> result = new LinkedHashSet<MediaType>();
+ for (PartialMatch match : this.partialMatches) {
+ if (match.hasMethodsMatch()) {
+ result.addAll(match.getInfo().getConsumesCondition().getConsumableMediaTypes());
+ }
}
+ return result;
}
- return result;
- }
- private Set<MediaType> getProducibleMediaTypes(HttpServletRequest request, Set<RequestMappingInfo> partialMatches) {
- Set<MediaType> result = new HashSet<MediaType>();
- for (RequestMappingInfo partialMatch : partialMatches) {
- if (partialMatch.getProducesCondition().getMatchingCondition(request) == null) {
- result.addAll(partialMatch.getProducesCondition().getProducibleMediaTypes());
+ /**
+ * Return declared "producible" types but only among those that also
+ * match the "methods" and "consumes" conditions.
+ */
+ public Set<MediaType> getProducibleMediaTypes() {
+ Set<MediaType> result = new LinkedHashSet<MediaType>();
+ for (PartialMatch match : this.partialMatches) {
+ if (match.hasConsumesMatch()) {
+ result.addAll(match.getInfo().getProducesCondition().getProducibleMediaTypes());
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Return declared "params" conditions but only among those that also
+ * match the "methods", "consumes", and "params" conditions.
+ */
+ public List<String[]> getParamConditions() {
+ List<String[]> result = new ArrayList<String[]>();
+ for (PartialMatch match : this.partialMatches) {
+ if (match.hasProducesMatch()) {
+ Set<NameValueExpression<String>> set = match.getInfo().getParamsCondition().getExpressions();
+ if (!CollectionUtils.isEmpty(set)) {
+ int i = 0;
+ String[] array = new String[set.size()];
+ for (NameValueExpression<String> expression : set) {
+ array[i++] = expression.toString();
+ }
+ result.add(array);
+ }
+ }
+ }
+ return result;
+ }
+
+
+ /**
+ * Container for a RequestMappingInfo that matches the URL path at least.
+ */
+ private static class PartialMatch {
+
+ private final RequestMappingInfo info;
+
+ private final boolean methodsMatch;
+
+ private final boolean consumesMatch;
+
+ private final boolean producesMatch;
+
+ private final boolean paramsMatch;
+
+ /**
+ * @param info RequestMappingInfo that matches the URL path.
+ * @param request the current request
+ */
+ public PartialMatch(RequestMappingInfo info, HttpServletRequest request) {
+ this.info = info;
+ this.methodsMatch = (info.getMethodsCondition().getMatchingCondition(request) != null);
+ this.consumesMatch = (info.getConsumesCondition().getMatchingCondition(request) != null);
+ this.producesMatch = (info.getProducesCondition().getMatchingCondition(request) != null);
+ this.paramsMatch = (info.getParamsCondition().getMatchingCondition(request) != null);
+ }
+
+ public RequestMappingInfo getInfo() {
+ return this.info;
+ }
+
+ public boolean hasMethodsMatch() {
+ return this.methodsMatch;
+ }
+
+ public boolean hasConsumesMatch() {
+ return (hasMethodsMatch() && this.consumesMatch);
+ }
+
+ public boolean hasProducesMatch() {
+ return (hasConsumesMatch() && this.producesMatch);
+ }
+
+ public boolean hasParamsMatch() {
+ return (hasProducesMatch() && this.paramsMatch);
+ }
+
+ @Override
+ public String toString() {
+ return this.info.toString();
}
}
- return result;
}
- private List<String[]> getRequestParams(HttpServletRequest request, Set<RequestMappingInfo> partialMatches) {
- List<String[]> result = new ArrayList<String[]>();
- for (RequestMappingInfo partialMatch : partialMatches) {
- ParamsRequestCondition condition = partialMatch.getParamsCondition();
- Set<NameValueExpression<String>> expressions = condition.getExpressions();
- if (!CollectionUtils.isEmpty(expressions) && condition.getMatchingCondition(request) == null) {
- int i = 0;
- String[] array = new String[expressions.size()];
- for (NameValueExpression<String> expression : expressions) {
- array[i++] = expression.toString();
+
+ /**
+ * Default handler for HTTP OPTIONS.
+ */
+ private static class HttpOptionsHandler {
+
+ private final HttpHeaders headers = new HttpHeaders();
+
+ public HttpOptionsHandler(Set<String> declaredMethods) {
+ this.headers.setAllow(initAllowedHttpMethods(declaredMethods));
+ }
+
+ private static Set<HttpMethod> initAllowedHttpMethods(Set<String> declaredMethods) {
+ Set<HttpMethod> result = new LinkedHashSet<HttpMethod>(declaredMethods.size());
+ if (declaredMethods.isEmpty()) {
+ for (HttpMethod method : HttpMethod.values()) {
+ if (!HttpMethod.TRACE.equals(method)) {
+ result.add(method);
+ }
+ }
+ }
+ else {
+ boolean hasHead = declaredMethods.contains("HEAD");
+ for (String method : declaredMethods) {
+ result.add(HttpMethod.valueOf(method));
+ if (!hasHead && "GET".equals(method)) {
+ result.add(HttpMethod.HEAD);
+ }
}
- result.add(array);
}
+ return result;
+ }
+
+ public HttpHeaders handle() {
+ return this.headers;
}
- return result;
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java
index 296caaf8..c0510172 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java
@@ -44,7 +44,6 @@ import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.accept.ContentNegotiationManager;
-import org.springframework.web.accept.ContentNegotiationStrategy;
import org.springframework.web.accept.PathExtensionContentNegotiationStrategy;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
@@ -123,12 +122,9 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
}
private static PathExtensionContentNegotiationStrategy initPathStrategy(ContentNegotiationManager manager) {
- for (ContentNegotiationStrategy strategy : manager.getStrategies()) {
- if (strategy instanceof PathExtensionContentNegotiationStrategy) {
- return (PathExtensionContentNegotiationStrategy) strategy;
- }
- }
- return new PathExtensionContentNegotiationStrategy();
+ Class<PathExtensionContentNegotiationStrategy> clazz = PathExtensionContentNegotiationStrategy.class;
+ PathExtensionContentNegotiationStrategy strategy = manager.getStrategy(clazz);
+ return (strategy != null ? strategy : new PathExtensionContentNegotiationStrategy());
}
@@ -169,13 +165,26 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
- Class<?> valueType = getReturnValueType(value, returnType);
- Type declaredType = getGenericType(returnType);
+ Object outputValue;
+ Class<?> valueType;
+ Type declaredType;
+
+ if (value instanceof CharSequence) {
+ outputValue = value.toString();
+ valueType = String.class;
+ declaredType = String.class;
+ }
+ else {
+ outputValue = value;
+ valueType = getReturnValueType(outputValue, returnType);
+ declaredType = getGenericType(returnType);
+ }
+
HttpServletRequest request = inputMessage.getServletRequest();
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);
- if (value != null && producibleMediaTypes.isEmpty()) {
+ if (outputValue != null && producibleMediaTypes.isEmpty()) {
throw new IllegalArgumentException("No converter found for return value of type: " + valueType);
}
@@ -188,7 +197,7 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
}
}
if (compatibleMediaTypes.isEmpty()) {
- if (value != null) {
+ if (outputValue != null) {
throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
}
return;
@@ -213,17 +222,17 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
if (messageConverter instanceof GenericHttpMessageConverter) {
- if (((GenericHttpMessageConverter<T>) messageConverter).canWrite(
+ if (((GenericHttpMessageConverter) messageConverter).canWrite(
declaredType, valueType, selectedMediaType)) {
- value = (T) getAdvice().beforeBodyWrite(value, returnType, selectedMediaType,
+ outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
inputMessage, outputMessage);
- if (value != null) {
+ if (outputValue != null) {
addContentDispositionHeader(inputMessage, outputMessage);
- ((GenericHttpMessageConverter<T>) messageConverter).write(
- value, declaredType, selectedMediaType, outputMessage);
+ ((GenericHttpMessageConverter) messageConverter).write(
+ outputValue, declaredType, selectedMediaType, outputMessage);
if (logger.isDebugEnabled()) {
- logger.debug("Written [" + value + "] as \"" + selectedMediaType +
+ logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
"\" using [" + messageConverter + "]");
}
}
@@ -231,14 +240,14 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
}
}
else if (messageConverter.canWrite(valueType, selectedMediaType)) {
- value = (T) getAdvice().beforeBodyWrite(value, returnType, selectedMediaType,
+ outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
inputMessage, outputMessage);
- if (value != null) {
+ if (outputValue != null) {
addContentDispositionHeader(inputMessage, outputMessage);
- ((HttpMessageConverter<T>) messageConverter).write(value, selectedMediaType, outputMessage);
+ ((HttpMessageConverter) messageConverter).write(outputValue, selectedMediaType, outputMessage);
if (logger.isDebugEnabled()) {
- logger.debug("Written [" + value + "] as \"" + selectedMediaType +
+ logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
"\" using [" + messageConverter + "]");
}
}
@@ -247,7 +256,7 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
}
}
- if (value != null) {
+ if (outputValue != null) {
throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/CompletionStageReturnValueHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/CompletionStageReturnValueHandler.java
index 6543b76c..c3c7b1d0 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/CompletionStageReturnValueHandler.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/CompletionStageReturnValueHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,10 @@ import org.springframework.web.method.support.ModelAndViewContainer;
*
* @author Sebastien Deleuze
* @since 4.2
+ * @deprecated as of 4.3 {@link DeferredResultMethodReturnValueHandler} supports
+ * CompletionStage return values via an adapter mechanism.
*/
+@Deprecated
@UsesJava8
public class CompletionStageReturnValueHandler implements AsyncHandlerMethodReturnValueHandler {
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/DeferredResultAdapter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/DeferredResultAdapter.java
new file mode 100644
index 00000000..a743b09e
--- /dev/null
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/DeferredResultAdapter.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.servlet.mvc.method.annotation;
+
+import org.springframework.web.context.request.async.DeferredResult;
+
+/**
+ * Contract to adapt a single-value async return value to {@code DeferredResult}.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3
+ */
+public interface DeferredResultAdapter {
+
+ /**
+ * Create a {@code DeferredResult} for the given return value.
+ * @param returnValue the return value (never {@code null})
+ * @return the DeferredResult
+ */
+ DeferredResult<?> adaptToDeferredResult(Object returnValue);
+
+}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/DeferredResultMethodReturnValueHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/DeferredResultMethodReturnValueHandler.java
index 147e6aff..789606dc 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/DeferredResultMethodReturnValueHandler.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/DeferredResultMethodReturnValueHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,17 @@
package org.springframework.web.servlet.mvc.method.annotation;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CompletionStage;
+import java.util.function.BiFunction;
+
import org.springframework.core.MethodParameter;
+import org.springframework.lang.UsesJava8;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.concurrent.ListenableFuture;
+import org.springframework.util.concurrent.ListenableFutureCallback;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.context.request.async.WebAsyncUtils;
@@ -24,21 +34,56 @@ import org.springframework.web.method.support.AsyncHandlerMethodReturnValueHandl
import org.springframework.web.method.support.ModelAndViewContainer;
/**
- * Handles return values of type {@link DeferredResult}.
+ * Handler for return values of type {@link DeferredResult}, {@link ListenableFuture},
+ * {@link CompletionStage} and any other async type with a {@link #getAdapterMap()
+ * registered adapter}.
*
* @author Rossen Stoyanchev
* @since 3.2
*/
public class DeferredResultMethodReturnValueHandler implements AsyncHandlerMethodReturnValueHandler {
+ private final Map<Class<?>, DeferredResultAdapter> adapterMap;
+
+
+ public DeferredResultMethodReturnValueHandler() {
+ this.adapterMap = new HashMap<Class<?>, DeferredResultAdapter>(5);
+ this.adapterMap.put(DeferredResult.class, new SimpleDeferredResultAdapter());
+ this.adapterMap.put(ListenableFuture.class, new ListenableFutureAdapter());
+ if (ClassUtils.isPresent("java.util.concurrent.CompletionStage", getClass().getClassLoader())) {
+ this.adapterMap.put(CompletionStage.class, new CompletionStageAdapter());
+ }
+ }
+
+
+ /**
+ * Return the map with {@code DeferredResult} adapters.
+ * <p>By default the map contains adapters for {@code DeferredResult}, which
+ * simply downcasts, {@link ListenableFuture}, and {@link CompletionStage}.
+ * @return the map of adapters
+ */
+ public Map<Class<?>, DeferredResultAdapter> getAdapterMap() {
+ return this.adapterMap;
+ }
+
+ private DeferredResultAdapter getAdapterFor(Class<?> type) {
+ for (Class<?> adapteeType : getAdapterMap().keySet()) {
+ if (adapteeType.isAssignableFrom(type)) {
+ return getAdapterMap().get(adapteeType);
+ }
+ }
+ return null;
+ }
+
+
@Override
public boolean supportsReturnType(MethodParameter returnType) {
- return DeferredResult.class.isAssignableFrom(returnType.getParameterType());
+ return (getAdapterFor(returnType.getParameterType()) != null);
}
@Override
public boolean isAsyncReturnValue(Object returnValue, MethodParameter returnType) {
- return (returnValue != null && returnValue instanceof DeferredResult);
+ return (returnValue != null && (getAdapterFor(returnValue.getClass()) != null));
}
@Override
@@ -50,8 +95,76 @@ public class DeferredResultMethodReturnValueHandler implements AsyncHandlerMetho
return;
}
- DeferredResult<?> deferredResult = (DeferredResult<?>) returnValue;
- WebAsyncUtils.getAsyncManager(webRequest).startDeferredResultProcessing(deferredResult, mavContainer);
+ DeferredResultAdapter adapter = getAdapterFor(returnValue.getClass());
+ Assert.notNull(adapter);
+ DeferredResult<?> result = adapter.adaptToDeferredResult(returnValue);
+ WebAsyncUtils.getAsyncManager(webRequest).startDeferredResultProcessing(result, mavContainer);
+ }
+
+
+ /**
+ * Adapter for {@code DeferredResult} return values.
+ */
+ private static class SimpleDeferredResultAdapter implements DeferredResultAdapter {
+
+ @Override
+ public DeferredResult<?> adaptToDeferredResult(Object returnValue) {
+ Assert.isInstanceOf(DeferredResult.class, returnValue);
+ return (DeferredResult<?>) returnValue;
+ }
+ }
+
+
+ /**
+ * Adapter for {@code ListenableFuture} return values.
+ */
+ private static class ListenableFutureAdapter implements DeferredResultAdapter {
+
+ @Override
+ public DeferredResult<?> adaptToDeferredResult(Object returnValue) {
+ Assert.isInstanceOf(ListenableFuture.class, returnValue);
+ final DeferredResult<Object> result = new DeferredResult<Object>();
+ ((ListenableFuture<?>) returnValue).addCallback(new ListenableFutureCallback<Object>() {
+ @Override
+ public void onSuccess(Object value) {
+ result.setResult(value);
+ }
+ @Override
+ public void onFailure(Throwable ex) {
+ result.setErrorResult(ex);
+ }
+ });
+ return result;
+ }
+ }
+
+
+ /**
+ * Adapter for {@code CompletionStage} return values.
+ */
+ @UsesJava8
+ private static class CompletionStageAdapter implements DeferredResultAdapter {
+
+ @Override
+ public DeferredResult<?> adaptToDeferredResult(Object returnValue) {
+ Assert.isInstanceOf(CompletionStage.class, returnValue);
+ final DeferredResult<Object> result = new DeferredResult<Object>();
+ @SuppressWarnings("unchecked")
+ CompletionStage<?> future = (CompletionStage<?>) returnValue;
+ future.handle(new BiFunction<Object, Throwable, Object>() {
+ @Override
+ public Object apply(Object value, Throwable ex) {
+ if (ex != null) {
+ result.setErrorResult(ex);
+ }
+ else {
+ result.setResult(value);
+ }
+ return null;
+ }
+ });
+ return result;
+ }
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java
index 572f0b7c..8e554773 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java
@@ -65,6 +65,7 @@ import org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionRes
* {@link #setArgumentResolvers} and {@link #setReturnValueHandlers(List)}.
*
* @author Rossen Stoyanchev
+ * @author Juergen Hoeller
* @since 3.1
*/
public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver
@@ -294,6 +295,10 @@ public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExce
protected List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
+ // Annotation-based argument resolution
+ resolvers.add(new SessionAttributeMethodArgumentResolver());
+ resolvers.add(new RequestAttributeMethodArgumentResolver());
+
// Type-based argument resolution
resolvers.add(new ServletRequestMethodArgumentResolver());
resolvers.add(new ServletResponseMethodArgumentResolver());
@@ -364,7 +369,15 @@ public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExce
if (logger.isDebugEnabled()) {
logger.debug("Invoking @ExceptionHandler method: " + exceptionHandlerMethod);
}
- exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
+ Throwable cause = exception.getCause();
+ if (cause != null) {
+ // Expose cause as provided argument as well
+ exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);
+ }
+ else {
+ // Otherwise, just the given exception as-is
+ exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
+ }
}
catch (Exception invocationEx) {
if (logger.isDebugEnabled()) {
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessor.java
index 7741b1c4..6545d4a8 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessor.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,7 +19,10 @@ package org.springframework.web.servlet.mvc.method.annotation;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
+import java.util.Map;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
@@ -162,15 +165,27 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro
Assert.isInstanceOf(HttpEntity.class, returnValue);
HttpEntity<?> responseEntity = (HttpEntity<?>) returnValue;
+ HttpHeaders outputHeaders = outputMessage.getHeaders();
HttpHeaders entityHeaders = responseEntity.getHeaders();
+ if (outputHeaders.containsKey(HttpHeaders.VARY) && entityHeaders.containsKey(HttpHeaders.VARY)) {
+ List<String> values = getVaryRequestHeadersToAdd(outputHeaders, entityHeaders);
+ if (!values.isEmpty()) {
+ outputHeaders.setVary(values);
+ }
+ }
if (!entityHeaders.isEmpty()) {
- outputMessage.getHeaders().putAll(entityHeaders);
+ for (Map.Entry<String, List<String>> entry : entityHeaders.entrySet()) {
+ if (!outputHeaders.containsKey(entry.getKey())) {
+ outputHeaders.put(entry.getKey(), entry.getValue());
+ }
+ }
}
- Object body = responseEntity.getBody();
if (responseEntity instanceof ResponseEntity) {
- outputMessage.setStatusCode(((ResponseEntity<?>) responseEntity).getStatusCode());
- if (HttpMethod.GET == inputMessage.getMethod() && isResourceNotModified(inputMessage, outputMessage)) {
+ outputMessage.getServletResponse().setStatus(((ResponseEntity<?>) responseEntity).getStatusCodeValue());
+ HttpMethod method = inputMessage.getMethod();
+ boolean isGetOrHead = (HttpMethod.GET == method || HttpMethod.HEAD == method);
+ if (isGetOrHead && isResourceNotModified(inputMessage, outputMessage)) {
outputMessage.setStatusCode(HttpStatus.NOT_MODIFIED);
// Ensure headers are flushed, no body should be written.
outputMessage.flush();
@@ -180,12 +195,33 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro
}
// Try even with null body. ResponseBodyAdvice could get involved.
- writeWithMessageConverters(body, returnType, inputMessage, outputMessage);
+ writeWithMessageConverters(responseEntity.getBody(), returnType, inputMessage, outputMessage);
// Ensure headers are flushed even if no body was written.
outputMessage.flush();
}
+ private List<String> getVaryRequestHeadersToAdd(HttpHeaders responseHeaders, HttpHeaders entityHeaders) {
+ if (!responseHeaders.containsKey(HttpHeaders.VARY)) {
+ return entityHeaders.getVary();
+ }
+ List<String> entityHeadersVary = entityHeaders.getVary();
+ List<String> result = new ArrayList<String>(entityHeadersVary);
+ for (String header : responseHeaders.get(HttpHeaders.VARY)) {
+ for (String existing : StringUtils.tokenizeToStringArray(header, ",")) {
+ if ("*".equals(existing)) {
+ return Collections.emptyList();
+ }
+ for (String value : entityHeadersVary) {
+ if (value.equalsIgnoreCase(existing)) {
+ result.remove(value);
+ }
+ }
+ }
+ }
+ return result;
+ }
+
private boolean isResourceNotModified(ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) {
List<String> ifNoneMatch = inputMessage.getHeaders().getIfNoneMatch();
long ifModifiedSince = inputMessage.getHeaders().getIfModifiedSince();
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/JsonViewResponseBodyAdvice.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/JsonViewResponseBodyAdvice.java
index 3ed6bb8f..2cb56bc2 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/JsonViewResponseBodyAdvice.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/JsonViewResponseBodyAdvice.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -47,7 +47,7 @@ public class JsonViewResponseBodyAdvice extends AbstractMappingJacksonResponseBo
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
- return (super.supports(returnType, converterType) && returnType.getMethodAnnotation(JsonView.class) != null);
+ return super.supports(returnType, converterType) && returnType.hasMethodAnnotation(JsonView.class);
}
@Override
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ListenableFutureReturnValueHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ListenableFutureReturnValueHandler.java
index 18d25a4f..d4a8fe62 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ListenableFutureReturnValueHandler.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ListenableFutureReturnValueHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,7 +31,10 @@ import org.springframework.web.method.support.ModelAndViewContainer;
*
* @author Rossen Stoyanchev
* @since 4.1
+ * @deprecated as of 4.3 {@link DeferredResultMethodReturnValueHandler} supports
+ * ListenableFuture return values via an adapter mechanism.
*/
+@Deprecated
public class ListenableFutureReturnValueHandler implements AsyncHandlerMethodReturnValueHandler {
@Override
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MatrixVariableMapMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MatrixVariableMapMethodArgumentResolver.java
index 59a8e987..212a8269 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MatrixVariableMapMethodArgumentResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MatrixVariableMapMethodArgumentResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,9 +17,11 @@
package org.springframework.web.servlet.mvc.method.annotation;
import java.util.Collections;
+import java.util.List;
import java.util.Map;
import org.springframework.core.MethodParameter;
+import org.springframework.core.ResolvableType;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
@@ -68,23 +70,37 @@ public class MatrixVariableMapMethodArgumentResolver implements HandlerMethodArg
return Collections.emptyMap();
}
+ MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
String pathVariable = parameter.getParameterAnnotation(MatrixVariable.class).pathVar();
if (!pathVariable.equals(ValueConstants.DEFAULT_NONE)) {
- MultiValueMap<String, String> map = matrixVariables.get(pathVariable);
- return (map != null) ? map : Collections.emptyMap();
+ MultiValueMap<String, String> mapForPathVariable = matrixVariables.get(pathVariable);
+ if (mapForPathVariable == null) {
+ return Collections.emptyMap();
+ }
+ map.putAll(mapForPathVariable);
}
-
- MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
- for (MultiValueMap<String, String> vars : matrixVariables.values()) {
- for (String name : vars.keySet()) {
- for (String value : vars.get(name)) {
- map.add(name, value);
+ else {
+ for (MultiValueMap<String, String> vars : matrixVariables.values()) {
+ for (String name : vars.keySet()) {
+ for (String value : vars.get(name)) {
+ map.add(name, value);
+ }
}
}
}
- return map;
+ return (isSingleValueMap(parameter) ? map.toSingleValueMap() : map);
+ }
+
+ private boolean isSingleValueMap(MethodParameter parameter) {
+ if (!MultiValueMap.class.isAssignableFrom(parameter.getParameterType())) {
+ ResolvableType[] genericTypes = ResolvableType.forMethodParameter(parameter).getGenerics();
+ if (genericTypes.length == 2) {
+ return !List.class.isAssignableFrom(genericTypes[1].getRawClass());
+ }
+ }
+ return false;
}
} \ No newline at end of file
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MatrixVariableMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MatrixVariableMethodArgumentResolver.java
index 6e971176..1dd9c354 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MatrixVariableMethodArgumentResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MatrixVariableMethodArgumentResolver.java
@@ -54,7 +54,7 @@ public class MatrixVariableMethodArgumentResolver extends AbstractNamedValueMeth
if (!parameter.hasParameterAnnotation(MatrixVariable.class)) {
return false;
}
- if (Map.class.isAssignableFrom(parameter.getParameterType())) {
+ if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
String variableName = parameter.getParameterAnnotation(MatrixVariable.class).name();
return StringUtils.hasText(variableName);
}
@@ -90,7 +90,7 @@ public class MatrixVariableMethodArgumentResolver extends AbstractNamedValueMeth
for (MultiValueMap<String, String> params : pathParameters.values()) {
if (params.containsKey(name)) {
if (found) {
- String paramType = parameter.getParameterType().getName();
+ String paramType = parameter.getNestedParameterType().getName();
throw new ServletRequestBindingException(
"Found more than one match for URI path parameter '" + name +
"' for parameter type [" + paramType + "]. Use 'pathVar' attribute to disambiguate.");
@@ -115,7 +115,7 @@ public class MatrixVariableMethodArgumentResolver extends AbstractNamedValueMeth
@Override
protected void handleMissingValue(String name, MethodParameter parameter) throws ServletRequestBindingException {
throw new ServletRequestBindingException("Missing matrix variable '" + name +
- "' for method parameter of type " + parameter.getParameterType().getSimpleName());
+ "' for method parameter of type " + parameter.getNestedParameterType().getSimpleName());
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandler.java
index a848a970..10ea4028 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandler.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -98,6 +98,7 @@ public class ModelAndViewMethodReturnValueHandler implements HandlerMethodReturn
}
}
}
+ mavContainer.setStatus(mav.getStatus());
mavContainer.addAllAttributes(mav.getModel());
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java
index 25018ff9..e9e302a5 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java
@@ -38,8 +38,8 @@ import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.Factory;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.core.DefaultParameterNameDiscoverer;
-import org.springframework.core.MethodParameter;
import org.springframework.core.MethodIntrospector;
+import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.SynthesizingMethodParameter;
@@ -118,7 +118,7 @@ public class MvcUriComponentsBuilder {
* @see #fromMethodName(Class, String, Object...)
* @see #fromMethodCall(Object)
* @see #fromMappingName(String)
- * @see #fromMethod(java.lang.reflect.Method, Object...)
+ * @see #fromMethod(Class, Method, Object...)
*/
protected MvcUriComponentsBuilder(UriComponentsBuilder baseUrl) {
Assert.notNull(baseUrl, "'baseUrl' is required");
@@ -168,7 +168,7 @@ public class MvcUriComponentsBuilder {
/**
* Create a {@link UriComponentsBuilder} from the mapping of a controller
* method and an array of method argument values. This method delegates
- * to {@link #fromMethod(java.lang.reflect.Method, Object...)}.
+ * to {@link #fromMethod(Class, Method, Object...)}.
* @param controllerType the controller
* @param methodName the method name
* @param args the argument values
@@ -207,7 +207,7 @@ public class MvcUriComponentsBuilder {
/**
* Create a {@link UriComponentsBuilder} by invoking a "mock" controller method.
* The controller method and the supplied argument values are then used to
- * delegate to {@link #fromMethod(java.lang.reflect.Method, Object...)}.
+ * delegate to {@link #fromMethod(Class, Method, Object...)}.
* <p>For example, given this controller:
* <pre class="code">
* &#064;RequestMapping("/people/{id}/addresses")
@@ -304,7 +304,7 @@ public class MvcUriComponentsBuilder {
* <p>Note that it's not necessary to specify all arguments. Only the ones
* required to prepare the URL, mainly {@code @RequestParam} and {@code @PathVariable}).
* @param mappingName the mapping name
- * @return a builder to to prepare the URI String
+ * @return a builder to prepare the URI String
* @throws IllegalArgumentException if the mapping name is not found or
* if there is no unique match
* @since 4.1
@@ -321,7 +321,7 @@ public class MvcUriComponentsBuilder {
* @param builder the builder for the base URL; the builder will be cloned
* and therefore not modified and may be re-used for further calls.
* @param name the mapping name
- * @return a builder to to prepare the URI String
+ * @return a builder to prepare the URI String
* @throws IllegalArgumentException if the mapping name is not found or
* if there is no unique match
* @since 4.2
@@ -361,7 +361,7 @@ public class MvcUriComponentsBuilder {
}
/**
- * An alternative to {@link #fromMethod(java.lang.reflect.Method, Object...)}
+ * An alternative to {@link #fromMethod(Class, Method, Object...)}
* that accepts a {@code UriComponentsBuilder} representing the base URL.
* This is useful when using MvcUriComponentsBuilder outside the context of
* processing a request or to apply a custom baseUrl not matching the
@@ -557,8 +557,7 @@ public class MvcUriComponentsBuilder {
* on the controller is invoked, the supplied argument values are remembered
* and the result can then be used to create a {@code UriComponentsBuilder}
* via {@link #fromMethodCall(Object)}.
- * <p>
- * Note that this is a shorthand version of {@link #controller(Class)} intended
+ * <p>Note that this is a shorthand version of {@link #controller(Class)} intended
* for inline use (with a static import), for example:
* <pre class="code">
* MvcUriComponentsBuilder.fromMethodCall(on(FooController.class).getFoo(1)).build();
@@ -574,8 +573,7 @@ public class MvcUriComponentsBuilder {
* on the controller is invoked, the supplied argument values are remembered
* and the result can then be used to create {@code UriComponentsBuilder} via
* {@link #fromMethodCall(Object)}.
- * <p>
- * This is a longer version of {@link #on(Class)}. It is needed with controller
+ * <p>This is a longer version of {@link #on(Class)}. It is needed with controller
* methods returning void as well for repeated invocations.
* <pre class="code">
* FooController fooController = controller(FooController.class);
@@ -627,7 +625,7 @@ public class MvcUriComponentsBuilder {
try {
proxy = proxyClass.newInstance();
}
- catch (Exception ex) {
+ catch (Throwable ex) {
throw new IllegalStateException("Unable to instantiate controller proxy using Objenesis, " +
"and regular controller instantiation via default constructor fails as well", ex);
}
@@ -778,7 +776,6 @@ public class MvcUriComponentsBuilder {
}
/**
- * @see #MethodArgumentBuilder(Class, Method)
* @deprecated as of 4.2, this is deprecated in favor of alternative constructors
* that accept a controllerType argument
*/
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/PathVariableMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/PathVariableMethodArgumentResolver.java
index 0475e03b..d7b1cb52 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/PathVariableMethodArgumentResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/PathVariableMethodArgumentResolver.java
@@ -74,7 +74,7 @@ public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethod
if (!parameter.hasParameterAnnotation(PathVariable.class)) {
return false;
}
- if (Map.class.isAssignableFrom(parameter.getParameterType())) {
+ if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
String paramName = parameter.getParameterAnnotation(PathVariable.class).value();
return StringUtils.hasText(paramName);
}
@@ -121,7 +121,7 @@ public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethod
public void contributeMethodArgument(MethodParameter parameter, Object value,
UriComponentsBuilder builder, Map<String, Object> uriVariables, ConversionService conversionService) {
- if (Map.class.isAssignableFrom(parameter.getParameterType())) {
+ if (Map.class.isAssignableFrom(parameter.getNestedParameterType())) {
return;
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestAttributeMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestAttributeMethodArgumentResolver.java
new file mode 100644
index 00000000..def67fde
--- /dev/null
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestAttributeMethodArgumentResolver.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.servlet.mvc.method.annotation;
+
+import javax.servlet.ServletException;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.web.bind.ServletRequestBindingException;
+import org.springframework.web.bind.annotation.RequestAttribute;
+import org.springframework.web.bind.annotation.ValueConstants;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver;
+
+/**
+ * Resolves method arguments annotated with an @{@link RequestAttribute}.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3
+ */
+public class RequestAttributeMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
+
+ @Override
+ public boolean supportsParameter(MethodParameter parameter) {
+ return parameter.hasParameterAnnotation(RequestAttribute.class);
+ }
+
+ @Override
+ protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
+ RequestAttribute ann = parameter.getParameterAnnotation(RequestAttribute.class);
+ return new NamedValueInfo(ann.name(), ann.required(), ValueConstants.DEFAULT_NONE);
+ }
+
+ @Override
+ protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request){
+ return request.getAttribute(name, RequestAttributes.SCOPE_REQUEST);
+ }
+
+ @Override
+ protected void handleMissingValue(String name, MethodParameter parameter) throws ServletException {
+ throw new ServletRequestBindingException("Missing request attribute '" + name +
+ "' of type " + parameter.getNestedParameterType().getSimpleName());
+ }
+
+}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java
index ce031bd5..a48efb1b 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java
@@ -48,7 +48,6 @@ import org.springframework.http.converter.support.AllEncompassingFormHttpMessage
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
import org.springframework.ui.ModelMap;
import org.springframework.util.Assert;
-import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils.MethodFilter;
import org.springframework.web.accept.ContentNegotiationManager;
@@ -117,10 +116,6 @@ import org.springframework.web.util.WebUtils;
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
implements BeanFactoryAware, InitializingBean {
- private static final boolean completionStagePresent = ClassUtils.isPresent(
- "java.util.concurrent.CompletionStage", RequestMappingHandlerAdapter.class.getClassLoader());
-
-
private List<HandlerMethodArgumentResolver> customArgumentResolvers;
private HandlerMethodArgumentResolverComposite argumentResolvers;
@@ -599,6 +594,8 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
resolvers.add(new RequestHeaderMapMethodArgumentResolver());
resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
+ resolvers.add(new SessionAttributeMethodArgumentResolver());
+ resolvers.add(new RequestAttributeMethodArgumentResolver());
// Type-based argument resolution
resolvers.add(new ServletRequestMethodArgumentResolver());
@@ -638,6 +635,8 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
resolvers.add(new MatrixVariableMethodArgumentResolver());
resolvers.add(new MatrixVariableMapMethodArgumentResolver());
resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
+ resolvers.add(new SessionAttributeMethodArgumentResolver());
+ resolvers.add(new RequestAttributeMethodArgumentResolver());
// Type-based argument resolution
resolvers.add(new ServletRequestMethodArgumentResolver());
@@ -673,10 +672,6 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
handlers.add(new CallableMethodReturnValueHandler());
handlers.add(new DeferredResultMethodReturnValueHandler());
handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));
- handlers.add(new ListenableFutureReturnValueHandler());
- if (completionStagePresent) {
- handlers.add(new CompletionStageReturnValueHandler());
- }
// Annotation-based return value types
handlers.add(new ModelAttributeMethodProcessor(false));
@@ -795,46 +790,50 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
+ try {
+ WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
+ ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
+
+ ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
+ invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
+ invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
+ invocableMethod.setDataBinderFactory(binderFactory);
+ invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
+
+ ModelAndViewContainer mavContainer = new ModelAndViewContainer();
+ mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
+ modelFactory.initModel(webRequest, mavContainer, invocableMethod);
+ mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
+
+ AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
+ asyncWebRequest.setTimeout(this.asyncRequestTimeout);
+
+ WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
+ asyncManager.setTaskExecutor(this.taskExecutor);
+ asyncManager.setAsyncWebRequest(asyncWebRequest);
+ asyncManager.registerCallableInterceptors(this.callableInterceptors);
+ asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
+
+ if (asyncManager.hasConcurrentResult()) {
+ Object result = asyncManager.getConcurrentResult();
+ mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
+ asyncManager.clearConcurrentResult();
+ if (logger.isDebugEnabled()) {
+ logger.debug("Found concurrent result value [" + result + "]");
+ }
+ invocableMethod = invocableMethod.wrapConcurrentResult(result);
+ }
- WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
- ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
-
- ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
- invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
- invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
- invocableMethod.setDataBinderFactory(binderFactory);
- invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
-
- ModelAndViewContainer mavContainer = new ModelAndViewContainer();
- mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
- modelFactory.initModel(webRequest, mavContainer, invocableMethod);
- mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
-
- AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
- asyncWebRequest.setTimeout(this.asyncRequestTimeout);
-
- WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
- asyncManager.setTaskExecutor(this.taskExecutor);
- asyncManager.setAsyncWebRequest(asyncWebRequest);
- asyncManager.registerCallableInterceptors(this.callableInterceptors);
- asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
-
- if (asyncManager.hasConcurrentResult()) {
- Object result = asyncManager.getConcurrentResult();
- mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
- asyncManager.clearConcurrentResult();
- if (logger.isDebugEnabled()) {
- logger.debug("Found concurrent result value [" + result + "]");
+ invocableMethod.invokeAndHandle(webRequest, mavContainer);
+ if (asyncManager.isConcurrentHandlingStarted()) {
+ return null;
}
- invocableMethod = invocableMethod.wrapConcurrentResult(result);
- }
- invocableMethod.invokeAndHandle(webRequest, mavContainer);
- if (asyncManager.isConcurrentHandlingStarted()) {
- return null;
+ return getModelAndView(mavContainer, modelFactory, webRequest);
+ }
+ finally {
+ webRequest.requestCompleted();
}
-
- return getModelAndView(mavContainer, modelFactory, webRequest);
}
/**
@@ -934,7 +933,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
return null;
}
ModelMap model = mavContainer.getModel();
- ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model);
+ ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java
index 5ffb6aaf..b4abdc16 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,10 +20,11 @@ import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
+import java.util.Set;
+import javax.servlet.http.HttpServletRequest;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.core.annotation.AnnotatedElementUtils;
-import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
@@ -34,6 +35,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.handler.MatchableHandlerMapping;
+import org.springframework.web.servlet.handler.RequestMatchResult;
import org.springframework.web.servlet.mvc.condition.AbstractRequestCondition;
import org.springframework.web.servlet.mvc.condition.CompositeRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
@@ -51,7 +54,7 @@ import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMappi
* @since 3.1
*/
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
- implements EmbeddedValueResolverAware {
+ implements MatchableHandlerMapping, EmbeddedValueResolverAware {
private boolean useSuffixPatternMatch = true;
@@ -109,13 +112,13 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
- this.embeddedValueResolver = resolver;
+ this.embeddedValueResolver = resolver;
}
@Override
public void afterPropertiesSet() {
this.config = new RequestMappingInfo.BuilderConfiguration();
- this.config.setPathHelper(getUrlPathHelper());
+ this.config.setUrlPathHelper(getUrlPathHelper());
this.config.setPathMatcher(getPathMatcher());
this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
@@ -168,8 +171,8 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
*/
@Override
protected boolean isHandler(Class<?> beanType) {
- return ((AnnotationUtils.findAnnotation(beanType, Controller.class) != null) ||
- (AnnotationUtils.findAnnotation(beanType, RequestMapping.class) != null));
+ return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
+ AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
/**
@@ -276,6 +279,18 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
}
@Override
+ public RequestMatchResult match(HttpServletRequest request, String pattern) {
+ RequestMappingInfo info = RequestMappingInfo.paths(pattern).options(this.config).build();
+ RequestMappingInfo matchingInfo = info.getMatchingCondition(request);
+ if (matchingInfo == null) {
+ return null;
+ }
+ Set<String> patterns = matchingInfo.getPatternsCondition().getPatterns();
+ String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
+ return new RequestMatchResult(patterns.iterator().next(), lookupPath, getPathMatcher());
+ }
+
+ @Override
protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) {
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
CrossOrigin typeAnnotation = AnnotatedElementUtils.findMergedAnnotation(handlerMethod.getBeanType(), CrossOrigin.class);
@@ -314,19 +329,19 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
return;
}
for (String origin : annotation.origins()) {
- config.addAllowedOrigin(origin);
+ config.addAllowedOrigin(resolveCorsAnnotationValue(origin));
}
for (RequestMethod method : annotation.methods()) {
config.addAllowedMethod(method.name());
}
for (String header : annotation.allowedHeaders()) {
- config.addAllowedHeader(header);
+ config.addAllowedHeader(resolveCorsAnnotationValue(header));
}
for (String header : annotation.exposedHeaders()) {
- config.addExposedHeader(header);
+ config.addExposedHeader(resolveCorsAnnotationValue(header));
}
- String allowCredentials = annotation.allowCredentials();
+ String allowCredentials = resolveCorsAnnotationValue(annotation.allowCredentials());
if ("true".equalsIgnoreCase(allowCredentials)) {
config.setAllowCredentials(true);
}
@@ -334,8 +349,8 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
config.setAllowCredentials(false);
}
else if (!allowCredentials.isEmpty()) {
- throw new IllegalStateException("@CrossOrigin's allowCredentials value must be \"true\", \"false\", "
- + "or an empty string (\"\"); current value is [" + allowCredentials + "].");
+ throw new IllegalStateException("@CrossOrigin's allowCredentials value must be \"true\", \"false\", " +
+ "or an empty string (\"\"): current value is [" + allowCredentials + "]");
}
if (annotation.maxAge() >= 0 && config.getMaxAge() == null) {
@@ -343,4 +358,8 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
}
}
+ private String resolveCorsAnnotationValue(String value) {
+ return (this.embeddedValueResolver != null ? this.embeddedValueResolver.resolveStringValue(value) : value);
+ }
+
}
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 ba34c2e4..d78d77de 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
@@ -16,19 +16,15 @@
package org.springframework.web.servlet.mvc.method.annotation;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.Part;
-import org.springframework.core.GenericCollectionTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.lang.UsesJava8;
-import org.springframework.util.Assert;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.WebDataBinder;
@@ -40,12 +36,10 @@ import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.MultipartFile;
-import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
+import org.springframework.web.multipart.support.MultipartResolutionDelegate;
import org.springframework.web.multipart.support.RequestPartServletServerHttpRequest;
-import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
-import org.springframework.web.util.WebUtils;
/**
* Resolves the following method arguments:
@@ -64,12 +58,13 @@ import org.springframework.web.util.WebUtils;
* it is derived from the name of the method argument.
*
* <p>Automatic validation may be applied if the argument is annotated with
- * {@code @javax.validation.Valid}. In case of validation failure, a
- * {@link MethodArgumentNotValidException} is raised and a 400 response status
- * code returned if {@link DefaultHandlerExceptionResolver} is configured.
+ * {@code @javax.validation.Valid}. In case of validation failure, a {@link MethodArgumentNotValidException}
+ * is raised and a 400 response status code returned if
+ * {@link org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver} is configured.
*
* @author Rossen Stoyanchev
* @author Brian Clozel
+ * @author Juergen Hoeller
* @since 3.1
*/
public class RequestPartMethodArgumentResolver extends AbstractMessageConverterMethodArgumentResolver {
@@ -106,18 +101,10 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM
return true;
}
else {
- if (parameter.hasParameterAnnotation(RequestParam.class)){
- return false;
- }
- else if (MultipartFile.class == parameter.getParameterType()) {
- return true;
- }
- else if ("javax.servlet.http.Part".equals(parameter.getParameterType().getName())) {
- return true;
- }
- else {
+ if (parameter.hasParameterAnnotation(RequestParam.class)) {
return false;
}
+ return MultipartResolutionDelegate.isMultipartArgument(parameter.nestedIfOptional());
}
}
@@ -126,87 +113,58 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM
NativeWebRequest request, WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
- assertIsMultipartRequest(servletRequest);
- MultipartHttpServletRequest multipartRequest =
- WebUtils.getNativeRequest(servletRequest, MultipartHttpServletRequest.class);
-
- String partName = getPartName(parameter);
- Class<?> paramType = parameter.getParameterType();
- boolean optional = paramType.getName().equals("java.util.Optional");
- if (optional) {
- parameter = new MethodParameter(parameter);
- parameter.increaseNestingLevel();
- paramType = parameter.getNestedParameterType();
- }
+ RequestPart requestPart = parameter.getParameterAnnotation(RequestPart.class);
+ boolean isRequired = ((requestPart == null || requestPart.required()) && !parameter.isOptional());
- Object arg;
+ String name = getPartName(parameter, requestPart);
+ parameter = parameter.nestedIfOptional();
+ Object arg = null;
- if (MultipartFile.class == paramType) {
- Assert.notNull(multipartRequest, "Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
- arg = multipartRequest.getFile(partName);
- }
- else if (isMultipartFileCollection(parameter)) {
- Assert.notNull(multipartRequest, "Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
- arg = multipartRequest.getFiles(partName);
- }
- else if (isMultipartFileArray(parameter)) {
- Assert.notNull(multipartRequest, "Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
- List<MultipartFile> files = multipartRequest.getFiles(partName);
- arg = files.toArray(new MultipartFile[files.size()]);
- }
- else if ("javax.servlet.http.Part".equals(paramType.getName())) {
- assertIsMultipartRequest(servletRequest);
- arg = servletRequest.getPart(partName);
- }
- else if (isPartCollection(parameter)) {
- assertIsMultipartRequest(servletRequest);
- arg = new ArrayList<Object>(servletRequest.getParts());
- }
- else if (isPartArray(parameter)) {
- assertIsMultipartRequest(servletRequest);
- arg = RequestPartResolver.resolvePart(servletRequest);
+ Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
+ if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
+ arg = mpArg;
}
else {
try {
- HttpInputMessage inputMessage = new RequestPartServletServerHttpRequest(servletRequest, partName);
+ HttpInputMessage inputMessage = new RequestPartServletServerHttpRequest(servletRequest, name);
arg = readWithMessageConverters(inputMessage, parameter, parameter.getNestedGenericParameterType());
- WebDataBinder binder = binderFactory.createBinder(request, arg, partName);
+ WebDataBinder binder = binderFactory.createBinder(request, arg, name);
if (arg != null) {
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
- mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + partName, binder.getBindingResult());
+ mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
}
catch (MissingServletRequestPartException ex) {
- // handled below
- arg = null;
+ if (isRequired) {
+ throw ex;
+ }
+ }
+ catch (MultipartException ex) {
+ if (isRequired) {
+ throw ex;
+ }
}
}
- RequestPart requestPart = parameter.getParameterAnnotation(RequestPart.class);
- boolean isRequired = ((requestPart == null || requestPart.required()) && !optional);
-
if (arg == null && isRequired) {
- throw new MissingServletRequestPartException(partName);
+ if (!MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
+ throw new MultipartException("Current request is not a multipart request");
+ }
+ else {
+ throw new MissingServletRequestPartException(name);
+ }
}
- if (optional) {
+ if (parameter.isOptional()) {
arg = OptionalResolver.resolveValue(arg);
}
return arg;
}
- private static void assertIsMultipartRequest(HttpServletRequest request) {
- String contentType = request.getContentType();
- if (contentType == null || !contentType.toLowerCase().startsWith("multipart/")) {
- throw new MultipartException("The current request is not a multipart request");
- }
- }
-
- private String getPartName(MethodParameter methodParam) {
- RequestPart requestPart = methodParam.getParameterAnnotation(RequestPart.class);
+ private String getPartName(MethodParameter methodParam, RequestPart requestPart) {
String partName = (requestPart != null ? requestPart.name() : "");
if (partName.length() == 0) {
partName = methodParam.getParameterName();
@@ -219,49 +177,6 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM
return partName;
}
- private boolean isMultipartFileCollection(MethodParameter methodParam) {
- Class<?> collectionType = getCollectionParameterType(methodParam);
- return MultipartFile.class == collectionType;
- }
-
- private boolean isMultipartFileArray(MethodParameter methodParam) {
- Class<?> paramType = methodParam.getNestedParameterType().getComponentType();
- return MultipartFile.class == paramType;
- }
-
- private boolean isPartCollection(MethodParameter methodParam) {
- Class<?> collectionType = getCollectionParameterType(methodParam);
- return (collectionType != null && "javax.servlet.http.Part".equals(collectionType.getName()));
- }
-
- private boolean isPartArray(MethodParameter methodParam) {
- Class<?> paramType = methodParam.getNestedParameterType().getComponentType();
- return (paramType != null && "javax.servlet.http.Part".equals(paramType.getName()));
- }
-
- private Class<?> getCollectionParameterType(MethodParameter methodParam) {
- Class<?> paramType = methodParam.getNestedParameterType();
- if (Collection.class == paramType || List.class.isAssignableFrom(paramType)){
- Class<?> valueType = GenericCollectionTypeResolver.getCollectionParameterType(methodParam);
- if (valueType != null) {
- return valueType;
- }
- }
- return null;
- }
-
-
- /**
- * Inner class to avoid hard-coded dependency on Servlet 3.0 Part type...
- */
- private static class RequestPartResolver {
-
- public static Object resolvePart(HttpServletRequest servletRequest) throws Exception {
- Collection<Part> parts = servletRequest.getParts();
- return parts.toArray(new Part[parts.size()]);
- }
- }
-
/**
* Inner class to avoid hard-coded dependency on Java 8 Optional type...
@@ -270,7 +185,11 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM
private static class OptionalResolver {
public static Object resolveValue(Object value) {
- return Optional.ofNullable(value);
+ if (value == null || (value instanceof Collection && ((Collection) value).isEmpty()) ||
+ (value instanceof Object[] && ((Object[]) value).length == 0)) {
+ return Optional.empty();
+ }
+ return Optional.of(value);
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessor.java
index bc187962..e8de9e85 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessor.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,15 +19,17 @@ package org.springframework.web.servlet.mvc.method.annotation;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.List;
+
import javax.servlet.http.HttpServletRequest;
import org.springframework.core.Conventions;
import org.springframework.core.MethodParameter;
-import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.server.ServletServerHttpRequest;
+import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.validation.BindingResult;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
@@ -108,8 +110,8 @@ public class RequestResponseBodyMethodProcessor extends AbstractMessageConverter
@Override
public boolean supportsReturnType(MethodParameter returnType) {
- return (AnnotationUtils.findAnnotation(returnType.getContainingClass(), ResponseBody.class) != null ||
- returnType.getMethodAnnotation(ResponseBody.class) != null);
+ return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
+ returnType.hasMethodAnnotation(ResponseBody.class));
}
/**
@@ -146,7 +148,7 @@ public class RequestResponseBodyMethodProcessor extends AbstractMessageConverter
Object arg = readWithMessageConverters(inputMessage, methodParam, paramType);
if (arg == null) {
- if (methodParam.getParameterAnnotation(RequestBody.class).required()) {
+ if (checkRequired(methodParam)) {
throw new HttpMessageNotReadableException("Required request body is missing: " +
methodParam.getMethod().toGenericString());
}
@@ -154,15 +156,21 @@ public class RequestResponseBodyMethodProcessor extends AbstractMessageConverter
return arg;
}
+ protected boolean checkRequired(MethodParameter methodParam) {
+ return methodParam.getParameterAnnotation(RequestBody.class).required();
+ }
+
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
mavContainer.setRequestHandled(true);
+ ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
+ ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// Try even with null return value. ResponseBodyAdvice could get involved.
- writeWithMessageConverters(returnValue, returnType, webRequest);
+ writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterAdapter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterAdapter.java
new file mode 100644
index 00000000..fc827cc2
--- /dev/null
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterAdapter.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.servlet.mvc.method.annotation;
+
+import org.springframework.http.server.ServerHttpResponse;
+
+/**
+ * Contract to adapt streaming async types to {@code ResponseBodyEmitter}.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3
+ */
+public interface ResponseBodyEmitterAdapter {
+
+ /**
+ * Obtain a {@code ResponseBodyEmitter} for the given return value.
+ * If the return is the body {@code ResponseEntity} then the given
+ * {@code ServerHttpResponse} contains its status and headers.
+ * @param returnValue the return value (never {@code null})
+ * @param response the response
+ * @return the return value adapted to a {@code ResponseBodyEmitter}
+ */
+ ResponseBodyEmitter adaptToEmitter(Object returnValue, ServerHttpResponse response);
+
+}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java
index 9a216058..264ca73c 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,8 +18,9 @@ package org.springframework.web.servlet.mvc.method.annotation;
import java.io.IOException;
import java.io.OutputStream;
+import java.util.HashMap;
import java.util.List;
-
+import java.util.Map;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -44,8 +45,9 @@ import org.springframework.web.method.support.AsyncHandlerMethodReturnValueHandl
import org.springframework.web.method.support.ModelAndViewContainer;
/**
- * Supports return values of type {@link ResponseBodyEmitter} and also
- * {@code ResponseEntity<ResponseBodyEmitter>}.
+ * Handler for return values of type {@link ResponseBodyEmitter} (and the
+ * {@code ResponseEntity<ResponseBodyEmitter>} sub-class) as well as any other
+ * async type with a {@link #getAdapterMap() registered adapter}.
*
* @author Rossen Stoyanchev
* @since 4.2
@@ -54,36 +56,63 @@ public class ResponseBodyEmitterReturnValueHandler implements AsyncHandlerMethod
private static final Log logger = LogFactory.getLog(ResponseBodyEmitterReturnValueHandler.class);
+
private final List<HttpMessageConverter<?>> messageConverters;
+ private final Map<Class<?>, ResponseBodyEmitterAdapter> adapterMap;
+
public ResponseBodyEmitterReturnValueHandler(List<HttpMessageConverter<?>> messageConverters) {
Assert.notEmpty(messageConverters, "'messageConverters' must not be empty");
this.messageConverters = messageConverters;
+ this.adapterMap = new HashMap<Class<?>, ResponseBodyEmitterAdapter>(3);
+ this.adapterMap.put(ResponseBodyEmitter.class, new SimpleResponseBodyEmitterAdapter());
+ }
+
+
+ /**
+ * Return the map with {@code ResponseBodyEmitter} adapters.
+ * By default the map contains a single adapter {@code ResponseBodyEmitter}
+ * that simply downcasts the return value.
+ * @return the map of adapters
+ */
+ public Map<Class<?>, ResponseBodyEmitterAdapter> getAdapterMap() {
+ return this.adapterMap;
+ }
+
+ private ResponseBodyEmitterAdapter getAdapterFor(Class<?> type) {
+ if (type != null) {
+ for (Class<?> adapteeType : getAdapterMap().keySet()) {
+ if (adapteeType.isAssignableFrom(type)) {
+ return getAdapterMap().get(adapteeType);
+ }
+ }
+ }
+ return null;
}
@Override
public boolean supportsReturnType(MethodParameter returnType) {
- if (ResponseBodyEmitter.class.isAssignableFrom(returnType.getParameterType())) {
- return true;
+ Class<?> bodyType;
+ if (ResponseEntity.class.isAssignableFrom(returnType.getParameterType())) {
+ bodyType = ResolvableType.forMethodParameter(returnType).getGeneric(0).resolve();
}
- else if (ResponseEntity.class.isAssignableFrom(returnType.getParameterType())) {
- Class<?> bodyType = ResolvableType.forMethodParameter(returnType).getGeneric(0).resolve();
- return (bodyType != null && ResponseBodyEmitter.class.isAssignableFrom(bodyType));
+ else {
+ bodyType = returnType.getParameterType();
}
- return false;
+ return (getAdapterFor(bodyType) != null);
}
@Override
public boolean isAsyncReturnValue(Object returnValue, MethodParameter returnType) {
if (returnValue != null) {
- if (returnValue instanceof ResponseBodyEmitter) {
- return true;
+ Object adaptFrom = returnValue;
+ if (returnValue instanceof ResponseEntity) {
+ adaptFrom = ((ResponseEntity) returnValue).getBody();
}
- else if (returnValue instanceof ResponseEntity) {
- Object body = ((ResponseEntity) returnValue).getBody();
- return (body != null && body instanceof ResponseBodyEmitter);
+ if (adaptFrom != null) {
+ return (getAdapterFor(adaptFrom.getClass()) != null);
}
}
return false;
@@ -101,13 +130,14 @@ public class ResponseBodyEmitterReturnValueHandler implements AsyncHandlerMethod
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
ServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
- if (ResponseEntity.class.isAssignableFrom(returnValue.getClass())) {
+ if (returnValue instanceof ResponseEntity) {
ResponseEntity<?> responseEntity = (ResponseEntity<?>) returnValue;
- outputMessage.setStatusCode(responseEntity.getStatusCode());
+ response.setStatus(responseEntity.getStatusCodeValue());
outputMessage.getHeaders().putAll(responseEntity.getHeaders());
returnValue = responseEntity.getBody();
if (returnValue == null) {
mavContainer.setRequestHandled(true);
+ outputMessage.flush();
return;
}
}
@@ -115,8 +145,9 @@ public class ResponseBodyEmitterReturnValueHandler implements AsyncHandlerMethod
ServletRequest request = webRequest.getNativeRequest(ServletRequest.class);
ShallowEtagHeaderFilter.disableContentCaching(request);
- Assert.isInstanceOf(ResponseBodyEmitter.class, returnValue);
- ResponseBodyEmitter emitter = (ResponseBodyEmitter) returnValue;
+ ResponseBodyEmitterAdapter adapter = getAdapterFor(returnValue.getClass());
+ Assert.notNull(adapter);
+ ResponseBodyEmitter emitter = adapter.adaptToEmitter(returnValue, outputMessage);
emitter.extendResponse(outputMessage);
// Commit the response and wrap to ignore further header changes
@@ -133,6 +164,18 @@ public class ResponseBodyEmitterReturnValueHandler implements AsyncHandlerMethod
/**
+ * Adapter for {@code ResponseBodyEmitter} return values.
+ */
+ private static class SimpleResponseBodyEmitterAdapter implements ResponseBodyEmitterAdapter {
+
+ @Override
+ public ResponseBodyEmitter adaptToEmitter(Object returnValue, ServerHttpResponse response) {
+ Assert.isInstanceOf(ResponseBodyEmitter.class, returnValue);
+ return (ResponseBodyEmitter) returnValue;
+ }
+ }
+
+ /**
* ResponseBodyEmitter.Handler that writes with HttpMessageConverter's.
*/
private class HttpMessageConvertingHandler implements ResponseBodyEmitter.Handler {
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandler.java
index 0be4cb9b..267f1959 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandler.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandler.java
@@ -46,7 +46,6 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.NoHandlerFoundException;
-import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
import org.springframework.web.util.WebUtils;
/**
@@ -99,8 +98,9 @@ public abstract class ResponseEntityExceptionHandler {
* @param ex the target exception
* @param request the current request
*/
+ @SuppressWarnings("deprecation")
@ExceptionHandler({
- NoSuchRequestHandlingMethodException.class,
+ org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException.class,
HttpRequestMethodNotSupportedException.class,
HttpMediaTypeNotSupportedException.class,
HttpMediaTypeNotAcceptableException.class,
@@ -118,9 +118,9 @@ public abstract class ResponseEntityExceptionHandler {
})
public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) {
HttpHeaders headers = new HttpHeaders();
- if (ex instanceof NoSuchRequestHandlingMethodException) {
+ if (ex instanceof org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException) {
HttpStatus status = HttpStatus.NOT_FOUND;
- return handleNoSuchRequestHandlingMethod((NoSuchRequestHandlingMethodException) ex, headers, status, request);
+ return handleNoSuchRequestHandlingMethod((org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException) ex, headers, status, request);
}
else if (ex instanceof HttpRequestMethodNotSupportedException) {
HttpStatus status = HttpStatus.METHOD_NOT_ALLOWED;
@@ -213,8 +213,10 @@ public abstract class ResponseEntityExceptionHandler {
* @param status the selected response status
* @param request the current request
* @return a {@code ResponseEntity} instance
+ * @deprecated as of 4.3, along with {@link org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException}
*/
- protected ResponseEntity<Object> handleNoSuchRequestHandlingMethod(NoSuchRequestHandlingMethodException ex,
+ @Deprecated
+ protected ResponseEntity<Object> handleNoSuchRequestHandlingMethod(org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
pageNotFoundLogger.warn(ex.getMessage());
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletCookieValueMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletCookieValueMethodArgumentResolver.java
index 8c51d5d8..cf174ea1 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletCookieValueMethodArgumentResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletCookieValueMethodArgumentResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -52,7 +52,7 @@ public class ServletCookieValueMethodArgumentResolver extends AbstractCookieValu
protected Object resolveName(String cookieName, MethodParameter parameter, NativeWebRequest webRequest) throws Exception {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
Cookie cookieValue = WebUtils.getCookie(servletRequest, cookieName);
- if (Cookie.class.isAssignableFrom(parameter.getParameterType())) {
+ if (Cookie.class.isAssignableFrom(parameter.getNestedParameterType())) {
return cookieValue;
}
else if (cookieValue != null) {
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java
index f843a581..d8c19445 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java
@@ -24,6 +24,7 @@ import java.util.concurrent.Callable;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
+import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.HttpStatus;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
@@ -82,6 +83,9 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
private void initResponseStatus() {
ResponseStatus annotation = getMethodAnnotation(ResponseStatus.class);
+ if (annotation == null) {
+ annotation = AnnotatedElementUtils.findMergedAnnotation(getBeanType(), ResponseStatus.class);
+ }
if (annotation != null) {
this.responseStatus = annotation.code();
this.responseReason = annotation.reason();
@@ -238,6 +242,14 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
public <A extends Annotation> A getMethodAnnotation(Class<A> annotationType) {
return ServletInvocableHandlerMethod.this.getMethodAnnotation(annotationType);
}
+
+ /**
+ * Bridge to controller method-level annotations.
+ */
+ @Override
+ public <A extends Annotation> boolean hasMethodAnnotation(Class<A> annotationType) {
+ return ServletInvocableHandlerMethod.this.hasMethodAnnotation(annotationType);
+ }
}
@@ -258,6 +270,12 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
this.returnType = ResolvableType.forType(super.getGenericParameterType()).getGeneric(0);
}
+ public ConcurrentResultMethodParameter(ConcurrentResultMethodParameter original) {
+ super(original);
+ this.returnValue = original.returnValue;
+ this.returnType = original.returnType;
+ }
+
@Override
public Class<?> getParameterType() {
if (this.returnValue != null) {
@@ -273,6 +291,11 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
public Type getGenericParameterType() {
return this.returnType.getType();
}
+
+ @Override
+ public ConcurrentResultMethodParameter clone() {
+ return new ConcurrentResultMethodParameter(this);
+ }
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/SessionAttributeMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/SessionAttributeMethodArgumentResolver.java
new file mode 100644
index 00000000..7c7d0811
--- /dev/null
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/SessionAttributeMethodArgumentResolver.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.servlet.mvc.method.annotation;
+
+import javax.servlet.ServletException;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.web.bind.ServletRequestBindingException;
+import org.springframework.web.bind.annotation.SessionAttribute;
+import org.springframework.web.bind.annotation.ValueConstants;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver;
+
+/**
+ * Resolves method arguments annotated with an @{@link SessionAttribute}.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3
+ */
+public class SessionAttributeMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
+
+ @Override
+ public boolean supportsParameter(MethodParameter parameter) {
+ return parameter.hasParameterAnnotation(SessionAttribute.class);
+ }
+
+ @Override
+ protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
+ SessionAttribute ann = parameter.getParameterAnnotation(SessionAttribute.class);
+ return new NamedValueInfo(ann.name(), ann.required(), ValueConstants.DEFAULT_NONE);
+ }
+
+ @Override
+ protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request){
+ return request.getAttribute(name, RequestAttributes.SCOPE_SESSION);
+ }
+
+ @Override
+ protected void handleMissingValue(String name, MethodParameter parameter) throws ServletException {
+ throw new ServletRequestBindingException("Missing session attribute '" + name +
+ "' of type " + parameter.getNestedParameterType().getSimpleName());
+ }
+
+}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/SseEmitter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/SseEmitter.java
index 4bc3267e..72b283f0 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/SseEmitter.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/SseEmitter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -38,6 +38,8 @@ public class SseEmitter extends ResponseBodyEmitter {
static final MediaType TEXT_PLAIN = new MediaType("text", "plain", Charset.forName("UTF-8"));
+ static final MediaType UTF8_TEXT_EVENTSTREAM = new MediaType("text", "event-stream", Charset.forName("UTF-8"));
+
/**
* Create a new SseEmitter instance.
@@ -65,7 +67,7 @@ public class SseEmitter extends ResponseBodyEmitter {
HttpHeaders headers = outputMessage.getHeaders();
if (headers.getContentType() == null) {
- headers.setContentType(new MediaType("text", "event-stream"));
+ headers.setContentType(UTF8_TEXT_EVENTSTREAM);
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/StreamingResponseBodyReturnValueHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/StreamingResponseBodyReturnValueHandler.java
index 40852899..a3b64612 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/StreamingResponseBodyReturnValueHandler.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/StreamingResponseBodyReturnValueHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package org.springframework.web.servlet.mvc.method.annotation;
import java.io.OutputStream;
import java.util.concurrent.Callable;
-
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -33,7 +33,6 @@ import org.springframework.web.filter.ShallowEtagHeaderFilter;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
-
/**
* Supports return values of type
* {@link org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody}
@@ -68,14 +67,14 @@ public class StreamingResponseBodyReturnValueHandler implements HandlerMethodRet
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
ServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
- if (ResponseEntity.class.isAssignableFrom(returnValue.getClass())) {
+ if (returnValue instanceof ResponseEntity) {
ResponseEntity<?> responseEntity = (ResponseEntity<?>) returnValue;
- outputMessage.setStatusCode(responseEntity.getStatusCode());
+ response.setStatus(responseEntity.getStatusCodeValue());
outputMessage.getHeaders().putAll(responseEntity.getHeaders());
-
returnValue = responseEntity.getBody();
if (returnValue == null) {
mavContainer.setRequestHandled(true);
+ outputMessage.flush();
return;
}
}
@@ -97,7 +96,6 @@ public class StreamingResponseBodyReturnValueHandler implements HandlerMethodRet
private final StreamingResponseBody streamingBody;
-
public StreamingResponseBodyTask(OutputStream outputStream, StreamingResponseBody streamingBody) {
this.outputStream = outputStream;
this.streamingBody = streamingBody;
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/AbstractUrlMethodNameResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/AbstractUrlMethodNameResolver.java
index 3ee748d4..0cc4fd1d 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/AbstractUrlMethodNameResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/AbstractUrlMethodNameResolver.java
@@ -34,7 +34,9 @@ import org.springframework.web.util.UrlPathHelper;
*
* @author Juergen Hoeller
* @since 14.01.2004
+ * @deprecated as of 4.3, in favor of annotation-driven handler methods
*/
+@Deprecated
public abstract class AbstractUrlMethodNameResolver implements MethodNameResolver {
/** Logger available to subclasses */
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/InternalPathMethodNameResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/InternalPathMethodNameResolver.java
index f81e806b..4f0845e2 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/InternalPathMethodNameResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/InternalPathMethodNameResolver.java
@@ -35,7 +35,9 @@ import org.springframework.web.util.WebUtils;
*
* @author Rod Johnson
* @author Juergen Hoeller
-*/
+ * @deprecated as of 4.3, in favor of annotation-driven handler methods
+ */
+@Deprecated
public class InternalPathMethodNameResolver extends AbstractUrlMethodNameResolver {
private String prefix = "";
@@ -97,7 +99,7 @@ public class InternalPathMethodNameResolver extends AbstractUrlMethodNameResolve
/**
* Extract the handler method name from the given request URI.
- * Delegates to {@code WebUtils.extractViewNameFromUrlPath(String)}.
+ * Delegates to {@code WebUtils.extractFilenameFromUrlPath(String)}.
* @param uri the request URI (e.g. "/index.html")
* @return the extracted URI filename (e.g. "index")
* @see org.springframework.web.util.WebUtils#extractFilenameFromUrlPath
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/MethodNameResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/MethodNameResolver.java
index d67f3019..8d4751f8 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/MethodNameResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/MethodNameResolver.java
@@ -28,7 +28,9 @@ import javax.servlet.http.HttpServletRequest;
*
* @author Rod Johnson
* @see MultiActionController#setMethodNameResolver
+ * @deprecated as of 4.3, in favor of annotation-driven handler methods
*/
+@Deprecated
public interface MethodNameResolver {
/**
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/MultiActionController.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/MultiActionController.java
index 188c9667..6ef9b7bf 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/MultiActionController.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/MultiActionController.java
@@ -127,7 +127,9 @@ import org.springframework.web.servlet.mvc.LastModified;
* @see ParameterMethodNameResolver
* @see org.springframework.web.servlet.mvc.LastModified#getLastModified
* @see org.springframework.web.bind.ServletRequestDataBinder
+ * @deprecated as of 4.3, in favor of annotation-driven handler methods
*/
+@Deprecated
public class MultiActionController extends AbstractController implements LastModified {
/** Suffix for last-modified methods */
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/NoSuchRequestHandlingMethodException.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/NoSuchRequestHandlingMethodException.java
index 1038c800..7d7d547f 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/NoSuchRequestHandlingMethodException.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/NoSuchRequestHandlingMethodException.java
@@ -30,7 +30,9 @@ import org.springframework.web.util.UrlPathHelper;
* @author Rod Johnson
* @author Juergen Hoeller
* @see MethodNameResolver#getHandlerMethodName(javax.servlet.http.HttpServletRequest)
+ * @deprecated as of 4.3, in favor of annotation-driven handler methods
*/
+@Deprecated
@SuppressWarnings("serial")
public class NoSuchRequestHandlingMethodException extends ServletException {
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/ParameterMethodNameResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/ParameterMethodNameResolver.java
index ff1b4547..55971724 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/ParameterMethodNameResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/ParameterMethodNameResolver.java
@@ -79,7 +79,9 @@ import org.springframework.web.util.WebUtils;
* @see #setMethodParamNames
* @see #setLogicalMappings
* @see #setDefaultMethodName
+ * @deprecated as of 4.3, in favor of annotation-driven handler methods
*/
+@Deprecated
public class ParameterMethodNameResolver implements MethodNameResolver {
/**
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/PropertiesMethodNameResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/PropertiesMethodNameResolver.java
index 0f458a9e..8bc14b06 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/PropertiesMethodNameResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/PropertiesMethodNameResolver.java
@@ -45,7 +45,9 @@ import org.springframework.util.PathMatcher;
* @author Juergen Hoeller
* @see java.util.Properties
* @see org.springframework.util.AntPathMatcher
+ * @deprecated as of 4.3, in favor of annotation-driven handler methods
*/
+@Deprecated
public class PropertiesMethodNameResolver extends AbstractUrlMethodNameResolver
implements InitializingBean {
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/AbstractControllerUrlHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/AbstractControllerUrlHandlerMapping.java
index 7893bc59..5604ed32 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/AbstractControllerUrlHandlerMapping.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/AbstractControllerUrlHandlerMapping.java
@@ -31,7 +31,9 @@ import org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMappin
* @since 2.5.3
* @see ControllerClassNameHandlerMapping
* @see ControllerBeanNameHandlerMapping
+ * @deprecated as of 4.3, in favor of annotation-driven handler methods
*/
+@Deprecated
public abstract class AbstractControllerUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping {
private ControllerTypePredicate predicate = new AnnotationControllerTypePredicate();
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/AnnotationControllerTypePredicate.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/AnnotationControllerTypePredicate.java
index ef0d574d..d9c3cfae 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/AnnotationControllerTypePredicate.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/AnnotationControllerTypePredicate.java
@@ -25,7 +25,9 @@ import org.springframework.stereotype.Controller;
*
* @author Juergen Hoeller
* @since 2.5.3
+ * @deprecated as of 4.3, in favor of annotation-driven handler methods
*/
+@Deprecated
class AnnotationControllerTypePredicate extends ControllerTypePredicate {
@Override
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/ControllerBeanNameHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/ControllerBeanNameHandlerMapping.java
index dc88ef32..90fa2159 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/ControllerBeanNameHandlerMapping.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/ControllerBeanNameHandlerMapping.java
@@ -37,7 +37,9 @@ import org.springframework.util.StringUtils;
* @since 2.5.3
* @see ControllerClassNameHandlerMapping
* @see org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping
+ * @deprecated as of 4.3, in favor of annotation-driven handler methods
*/
+@Deprecated
public class ControllerBeanNameHandlerMapping extends AbstractControllerUrlHandlerMapping {
private String urlPrefix = "";
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/ControllerClassNameHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/ControllerClassNameHandlerMapping.java
index 752554cc..b47545dd 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/ControllerClassNameHandlerMapping.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/ControllerClassNameHandlerMapping.java
@@ -18,7 +18,6 @@ package org.springframework.web.servlet.mvc.support;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
-import org.springframework.web.servlet.mvc.multiaction.MultiActionController;
/**
* Implementation of {@link org.springframework.web.servlet.HandlerMapping} that
@@ -36,7 +35,7 @@ import org.springframework.web.servlet.mvc.multiaction.MultiActionController;
* <li>{@code HomeController} -> {@code /home*}</li>
* </ul>
*
- * <p>For {@link MultiActionController MultiActionControllers} and {@code @Controller}
+ * <p>For {@code MultiActionController MultiActionControllers} and {@code @Controller}
* beans, a similar mapping is registered, except that all sub-paths are registered
* using the trailing wildcard pattern {@code /*}. For example:
* <ul>
@@ -44,7 +43,7 @@ import org.springframework.web.servlet.mvc.multiaction.MultiActionController;
* <li>{@code CatalogController} -> {@code /catalog}, {@code /catalog/*}</li>
* </ul>
*
- * <p>For {@link MultiActionController} it is often useful to use
+ * <p>For {@code MultiActionController} it is often useful to use
* this mapping strategy in conjunction with the
* {@link org.springframework.web.servlet.mvc.multiaction.InternalPathMethodNameResolver}.
*
@@ -56,7 +55,9 @@ import org.springframework.web.servlet.mvc.multiaction.MultiActionController;
* @since 2.0
* @see org.springframework.web.servlet.mvc.Controller
* @see org.springframework.web.servlet.mvc.multiaction.MultiActionController
+ * @deprecated as of 4.3, in favor of annotation-driven handler methods
*/
+@Deprecated
public class ControllerClassNameHandlerMapping extends AbstractControllerUrlHandlerMapping {
/**
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/ControllerTypePredicate.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/ControllerTypePredicate.java
index 33571f46..20f690d4 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/ControllerTypePredicate.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/ControllerTypePredicate.java
@@ -17,22 +17,24 @@
package org.springframework.web.servlet.mvc.support;
import org.springframework.web.servlet.mvc.Controller;
-import org.springframework.web.servlet.mvc.multiaction.MultiActionController;
/**
* Internal helper class that identifies controller types.
*
* @author Juergen Hoeller
* @since 2.5.3
+ * @deprecated as of 4.3, in favor of annotation-driven handler methods
*/
+@Deprecated
class ControllerTypePredicate {
public boolean isControllerType(Class<?> beanClass) {
return Controller.class.isAssignableFrom(beanClass);
}
+ @SuppressWarnings("deprecation")
public boolean isMultiActionControllerType(Class<?> beanClass) {
- return MultiActionController.class.isAssignableFrom(beanClass);
+ return org.springframework.web.servlet.mvc.multiaction.MultiActionController.class.isAssignableFrom(beanClass);
}
}
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 77290e56..6dea3daa 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
@@ -49,7 +49,6 @@ import org.springframework.web.multipart.support.MissingServletRequestPartExcept
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
-import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
/**
* Default implementation of the {@link org.springframework.web.servlet.HandlerExceptionResolver
@@ -101,13 +100,14 @@ public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionRes
@Override
+ @SuppressWarnings("deprecation")
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
try {
- if (ex instanceof NoSuchRequestHandlingMethodException) {
- return handleNoSuchRequestHandlingMethod((NoSuchRequestHandlingMethodException) ex, request, response,
- handler);
+ if (ex instanceof org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException) {
+ return handleNoSuchRequestHandlingMethod((org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException) ex,
+ request, response, handler);
}
else if (ex instanceof HttpRequestMethodNotSupportedException) {
return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, request,
@@ -180,8 +180,10 @@ public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionRes
* at the time of the exception (for example, if multipart resolution failed)
* @return an empty ModelAndView indicating the exception was handled
* @throws IOException potentially thrown from response.sendError()
+ * @deprecated as of 4.3, along with {@link org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException}
*/
- protected ModelAndView handleNoSuchRequestHandlingMethod(NoSuchRequestHandlingMethodException ex,
+ @Deprecated
+ protected ModelAndView handleNoSuchRequestHandlingMethod(org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException ex,
HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
pageNotFoundLogger.warn(ex.getMessage());
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/CssLinkResourceTransformer.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/CssLinkResourceTransformer.java
index 4380246e..fa884f7a 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/CssLinkResourceTransformer.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/CssLinkResourceTransformer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -49,12 +49,11 @@ import org.springframework.util.StringUtils;
*/
public class CssLinkResourceTransformer extends ResourceTransformerSupport {
- private static final Log logger = LogFactory.getLog(CssLinkResourceTransformer.class);
-
private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
+ private static final Log logger = LogFactory.getLog(CssLinkResourceTransformer.class);
- private final List<CssLinkParser> linkParsers = new ArrayList<CssLinkParser>();
+ private final List<CssLinkParser> linkParsers = new ArrayList<CssLinkParser>(2);
public CssLinkResourceTransformer() {
@@ -81,7 +80,7 @@ public class CssLinkResourceTransformer extends ResourceTransformerSupport {
byte[] bytes = FileCopyUtils.copyToByteArray(resource.getInputStream());
String content = new String(bytes, DEFAULT_CHARSET);
- Set<CssLinkInfo> infos = new HashSet<CssLinkInfo>(5);
+ Set<CssLinkInfo> infos = new HashSet<CssLinkInfo>(8);
for (CssLinkParser parser : this.linkParsers) {
parser.parseLink(content, infos);
}
@@ -123,17 +122,16 @@ public class CssLinkResourceTransformer extends ResourceTransformerSupport {
private boolean hasScheme(String link) {
int schemeIndex = link.indexOf(":");
- return (schemeIndex > 0 && !link.substring(0, schemeIndex).contains("/"))
- || link.indexOf("//") == 0;
+ return (schemeIndex > 0 && !link.substring(0, schemeIndex).contains("/")) || link.indexOf("//") == 0;
}
- protected static interface CssLinkParser {
+ protected interface CssLinkParser {
void parseLink(String content, Set<CssLinkInfo> linkInfos);
-
}
+
protected static abstract class AbstractCssLinkParser implements CssLinkParser {
/**
@@ -189,6 +187,7 @@ public class CssLinkResourceTransformer extends ResourceTransformerSupport {
}
+
private static class ImportStatementCssLinkParser extends AbstractCssLinkParser {
@Override
@@ -208,6 +207,7 @@ public class CssLinkResourceTransformer extends ResourceTransformerSupport {
}
}
+
private static class UrlFunctionCssLinkParser extends AbstractCssLinkParser {
@Override
@@ -229,8 +229,7 @@ public class CssLinkResourceTransformer extends ResourceTransformerSupport {
private final int end;
-
- private CssLinkInfo(int start, int end) {
+ public CssLinkInfo(int start, int end) {
this.start = start;
this.end = end;
}
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 fd25c306..cf9c020e 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
@@ -16,17 +16,13 @@
package org.springframework.web.servlet.resource;
-import java.io.FileNotFoundException;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
-import javax.activation.FileTypeMap;
-import javax.activation.MimetypesFileTypeMap;
+import javax.servlet.ServletContext;
import javax.servlet.ServletException;
-import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -34,21 +30,27 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
-import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.ResourceRegion;
import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRange;
import org.springframework.http.MediaType;
+import org.springframework.http.converter.ResourceHttpMessageConverter;
+import org.springframework.http.converter.ResourceRegionHttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest;
+import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
-import org.springframework.util.MimeTypeUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ResourceUtils;
-import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
+import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpRequestHandler;
+import org.springframework.web.accept.ContentNegotiationManager;
+import org.springframework.web.accept.ContentNegotiationManagerFactoryBean;
+import org.springframework.web.accept.PathExtensionContentNegotiationStrategy;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
@@ -56,49 +58,45 @@ import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.support.WebContentGenerator;
/**
- * {@link HttpRequestHandler} that serves static resources optimized for superior browser performance
- * (according to the guidelines of Page Speed, YSlow, etc.) by allowing for flexible cache settings
- * ({@linkplain #setCacheSeconds "cacheSeconds" property}, last-modified support).
+ * {@code HttpRequestHandler} that serves static resources in an optimized way
+ * according to the guidelines of Page Speed, YSlow, etc.
*
- * <p>The {@linkplain #setLocations "locations" property} takes a list of Spring {@link Resource}
- * locations from which static resources are allowed to be served by this handler. For a given request,
- * the list of locations will be consulted in order for the presence of the requested resource, and the
- * first found match will be written to the response, with a HTTP Caching headers
- * set as configured. The handler also properly evaluates the {@code Last-Modified} header
- * (if present) so that a {@code 304} status code will be returned as appropriate, avoiding unnecessary
- * overhead for resources that are already cached by the client. The use of {@code Resource} locations
- * allows resource requests to easily be mapped to locations other than the web application root.
- * For example, resources could be served from a classpath location such as
- * "classpath:/META-INF/public-web-resources/", allowing convenient packaging and serving of resources
- * such as a JavaScript library from within jar files.
+ * <p>The {@linkplain #setLocations "locations"} property takes a list of Spring
+ * {@link Resource} locations from which static resources are allowed to
+ * be served by this handler. Resources could be served from a classpath location,
+ * e.g. "classpath:/META-INF/public-web-resources/", allowing convenient packaging
+ * and serving of resources such as .js, .css, and others in jar files.
*
- * <p>To ensure that users with a primed browser cache get the latest changes to application-specific
- * resources upon deployment of new versions of the application, it is recommended that a version
- * string is used in the URL mapping pattern that selects this handler. Such patterns can be easily
- * parameterized using Spring EL. See the reference manual for further examples of this approach.
+ * <p>This request handler may also be configured with a
+ * {@link #setResourceResolvers(List) resourcesResolver} and
+ * {@link #setResourceTransformers(List) resourceTransformer} chains to support
+ * arbitrary resolution and transformation of resources being served. By default a
+ * {@link PathResourceResolver} simply finds resources based on the configured
+ * "locations". An application can configure additional resolvers and
+ * transformers such as the {@link VersionResourceResolver} which can resolve
+ * and prepare URLs for resources with a version in the URL.
*
- * <p>For various front-end needs &mdash; such as ensuring that users with a primed browser cache
- * get the latest changes, or serving variations of resources (e.g., minified versions) &mdash;
- * {@link org.springframework.web.servlet.resource.ResourceResolver}s can be configured.
- *
- * <p>This handler can be configured through use of a
- * {@link org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry}
- * or the {@code <mvc:resources/>} XML configuration element.
+ * <p>This handler also properly evaluates the {@code Last-Modified} header (if
+ * present) so that a {@code 304} status code will be returned as appropriate,
+ * avoiding unnecessary overhead for resources that are already cached by the
+ * client.
*
* @author Keith Donald
* @author Jeremy Grelle
* @author Juergen Hoeller
* @author Arjen Poutsma
* @author Brian Clozel
+ * @author Rossen Stoyanchev
* @since 3.0.4
*/
public class ResourceHttpRequestHandler extends WebContentGenerator
implements HttpRequestHandler, InitializingBean, CorsConfigurationSource {
- private static final Log logger = LogFactory.getLog(ResourceHttpRequestHandler.class);
+ // Servlet 3.1 setContentLengthLong(long) available?
+ private static final boolean contentLengthLongAvailable =
+ ClassUtils.hasMethod(ServletResponse.class, "setContentLengthLong", long.class);
- private static final boolean jafPresent = ClassUtils.isPresent(
- "javax.activation.FileTypeMap", ResourceHttpRequestHandler.class.getClassLoader());
+ private static final Log logger = LogFactory.getLog(ResourceHttpRequestHandler.class);
private final List<Resource> locations = new ArrayList<Resource>(4);
@@ -107,17 +105,24 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
private final List<ResourceTransformer> resourceTransformers = new ArrayList<ResourceTransformer>(4);
+ private ResourceHttpMessageConverter resourceHttpMessageConverter;
+
+ private ResourceRegionHttpMessageConverter resourceRegionHttpMessageConverter;
+
+ private ContentNegotiationManager contentNegotiationManager;
+
+ private final ContentNegotiationManagerFactoryBean cnmFactoryBean = new ContentNegotiationManagerFactoryBean();
+
private CorsConfiguration corsConfiguration;
public ResourceHttpRequestHandler() {
- super(METHOD_GET, METHOD_HEAD);
- this.resourceResolvers.add(new PathResourceResolver());
+ super(HttpMethod.GET.name(), HttpMethod.HEAD.name());
}
/**
- * Set a {@code List} of {@code Resource} paths to use as sources
+ * Set the {@code List} of {@code Resource} paths to use as sources
* for serving static resources.
*/
public void setLocations(List<Resource> locations) {
@@ -126,6 +131,10 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
this.locations.addAll(locations);
}
+ /**
+ * Return the {@code List} of {@code Resource} paths to use as sources
+ * for serving static resources.
+ */
public List<Resource> getLocations() {
return this.locations;
}
@@ -167,15 +176,87 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
return this.resourceTransformers;
}
+ /**
+ * Configure the {@link ResourceHttpMessageConverter} to use.
+ * <p>By default a {@link ResourceHttpMessageConverter} will be configured.
+ * @since 4.3
+ */
+ public void setResourceHttpMessageConverter(ResourceHttpMessageConverter resourceHttpMessageConverter) {
+ this.resourceHttpMessageConverter = resourceHttpMessageConverter;
+ }
+
+ /**
+ * Return the list of configured resource converters.
+ * @since 4.3
+ */
+ public ResourceHttpMessageConverter getResourceHttpMessageConverter() {
+ return this.resourceHttpMessageConverter;
+ }
+
+ /**
+ * Configure the {@link ResourceRegionHttpMessageConverter} to use.
+ * <p>By default a {@link ResourceRegionHttpMessageConverter} will be configured.
+ * @since 4.3
+ */
+ public void setResourceRegionHttpMessageConverter(ResourceRegionHttpMessageConverter resourceRegionHttpMessageConverter) {
+ this.resourceRegionHttpMessageConverter = resourceRegionHttpMessageConverter;
+ }
+
+ /**
+ * Return the list of configured resource region converters.
+ * @since 4.3
+ */
+ public ResourceRegionHttpMessageConverter getResourceRegionHttpMessageConverter() {
+ return this.resourceRegionHttpMessageConverter;
+ }
+
+ /**
+ * Configure a {@code ContentNegotiationManager} to determine the media types
+ * for resources being served. If the manager contains a path
+ * extension strategy it will be used to look up the file extension
+ * of resources being served via
+ * {@link PathExtensionContentNegotiationStrategy#getMediaTypeForResource
+ * getMediaTypeForResource}. If that fails the check is then expanded
+ * to use any configured content negotiation strategy against the request.
+ * <p>By default a {@link ContentNegotiationManagerFactoryBean} with default
+ * settings is used to create the manager. See the Javadoc of
+ * {@code ContentNegotiationManagerFactoryBean} for details
+ * @param contentNegotiationManager the manager to use
+ * @since 4.3
+ */
+ public void setContentNegotiationManager(ContentNegotiationManager contentNegotiationManager) {
+ this.contentNegotiationManager = contentNegotiationManager;
+ }
+
+ /**
+ * Return the specified content negotiation manager.
+ * @since 4.3
+ */
+ public ContentNegotiationManager getContentNegotiationManager() {
+ return this.contentNegotiationManager;
+ }
+
+ /**
+ * Specify the CORS configuration for resources served by this handler.
+ * <p>By default this is not set in which allows cross-origin requests.
+ */
public void setCorsConfiguration(CorsConfiguration corsConfiguration) {
this.corsConfiguration = corsConfiguration;
}
+ /**
+ * Return the specified CORS configuration.
+ */
@Override
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
return this.corsConfiguration;
}
+ @Override
+ protected void initServletContext(ServletContext servletContext) {
+ this.cnmFactoryBean.setServletContext(servletContext);
+ }
+
@Override
public void afterPropertiesSet() throws Exception {
@@ -183,7 +264,20 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
logger.warn("Locations list is empty. No resources will be served unless a " +
"custom ResourceResolver is configured as an alternative to PathResourceResolver.");
}
+ if (this.resourceResolvers.isEmpty()) {
+ this.resourceResolvers.add(new PathResourceResolver());
+ }
initAllowedLocations();
+ if (this.contentNegotiationManager == null) {
+ this.cnmFactoryBean.afterPropertiesSet();
+ this.contentNegotiationManager = this.cnmFactoryBean.getObject();
+ }
+ if (this.resourceHttpMessageConverter == null) {
+ this.resourceHttpMessageConverter = new ResourceHttpMessageConverter();
+ }
+ if(this.resourceRegionHttpMessageConverter == null) {
+ this.resourceRegionHttpMessageConverter = new ResourceRegionHttpMessageConverter();
+ }
}
/**
@@ -206,7 +300,6 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
}
}
-
/**
* Processes a resource request.
* <p>Checks for the existence of the requested resource in the configured list of locations.
@@ -223,10 +316,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
- // Supported methods and required session
- checkRequest(request);
-
- // Check whether a matching resource exists
+ // For very general mappings (e.g. "/") we need to check 404 first
Resource resource = getResource(request);
if (resource == null) {
logger.trace("No matching resource found - returning 404");
@@ -234,6 +324,14 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
return;
}
+ if (HttpMethod.OPTIONS.matches(request.getMethod())) {
+ response.setHeader("Allow", getAllowHeader());
+ return;
+ }
+
+ // Supported methods and required session
+ checkRequest(request);
+
// Header phase
if (new ServletWebRequest(request, response).checkNotModified(resource.lastModified())) {
logger.trace("Resource not modified - returning 304");
@@ -244,7 +342,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
prepareResponse(response);
// Check the media type for the resource
- MediaType mediaType = getMediaType(resource);
+ MediaType mediaType = getMediaType(request, resource);
if (mediaType != null) {
if (logger.isTraceEnabled()) {
logger.trace("Determined media type '" + mediaType + "' for " + resource);
@@ -263,12 +361,30 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
return;
}
+ ServletServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
if (request.getHeader(HttpHeaders.RANGE) == null) {
setHeaders(response, resource, mediaType);
- writeContent(response, resource);
+ this.resourceHttpMessageConverter.write(resource, mediaType, outputMessage);
}
else {
- writePartialContent(request, response, resource, mediaType);
+ response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
+ ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(request);
+ try {
+ List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
+ response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
+ if(httpRanges.size() == 1) {
+ ResourceRegion resourceRegion = httpRanges.get(0).toResourceRegion(resource);
+ this.resourceRegionHttpMessageConverter.write(resourceRegion, mediaType, outputMessage);
+ }
+ else {
+ this.resourceRegionHttpMessageConverter.write(
+ HttpRange.toResourceRegions(httpRanges, resource), mediaType, outputMessage);
+ }
+ }
+ catch (IllegalArgumentException ex) {
+ response.setHeader("Content-Range", "bytes */" + resource.contentLength());
+ response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
+ }
}
}
@@ -384,26 +500,59 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
}
/**
- * Determine an appropriate media type for the given resource.
+ * Determine the media type for the given request and the resource matched
+ * to it. This implementation first tries to determine the MediaType based
+ * strictly on the file extension of the Resource via
+ * {@link PathExtensionContentNegotiationStrategy#getMediaTypeForResource}
+ * and then expands to check against the request via
+ * {@link ContentNegotiationManager#resolveMediaTypes}.
+ * @param request the current request
* @param resource the resource to check
* @return the corresponding media type, or {@code null} if none found
*/
- protected MediaType getMediaType(Resource resource) {
- MediaType mediaType = null;
- String mimeType = getServletContext().getMimeType(resource.getFilename());
- if (StringUtils.hasText(mimeType)) {
- mediaType = MediaType.parseMediaType(mimeType);
- }
- if (jafPresent && (mediaType == null || MediaType.APPLICATION_OCTET_STREAM.equals(mediaType))) {
- MediaType jafMediaType = ActivationMediaTypeFactory.getMediaType(resource.getFilename());
- if (jafMediaType != null && !MediaType.APPLICATION_OCTET_STREAM.equals(jafMediaType)) {
- mediaType = jafMediaType;
+ @SuppressWarnings("deprecation")
+ protected MediaType getMediaType(HttpServletRequest request, Resource resource) {
+ // For backwards compatibility
+ MediaType mediaType = getMediaType(resource);
+ if (mediaType != null) {
+ return mediaType;
+ }
+
+ Class<PathExtensionContentNegotiationStrategy> clazz = PathExtensionContentNegotiationStrategy.class;
+ PathExtensionContentNegotiationStrategy strategy = this.contentNegotiationManager.getStrategy(clazz);
+ if (strategy != null) {
+ mediaType = strategy.getMediaTypeForResource(resource);
+ }
+
+ if (mediaType == null) {
+ ServletWebRequest webRequest = new ServletWebRequest(request);
+ try {
+ List<MediaType> mediaTypes = getContentNegotiationManager().resolveMediaTypes(webRequest);
+ if (!mediaTypes.isEmpty()) {
+ mediaType = mediaTypes.get(0);
+ }
+ }
+ catch (HttpMediaTypeNotAcceptableException ex) {
+ // Ignore
}
}
+
return mediaType;
}
/**
+ * Determine an appropriate media type for the given resource.
+ * @param resource the resource to check
+ * @return the corresponding media type, or {@code null} if none found
+ * @deprecated as of 4.3 this method is deprecated; please override
+ * {@link #getMediaType(HttpServletRequest, Resource)} instead.
+ */
+ @Deprecated
+ protected MediaType getMediaType(Resource resource) {
+ return null;
+ }
+
+ /**
* Set headers on the given servlet response.
* Called for GET requests as well as HEAD requests.
* @param response current servlet response
@@ -414,9 +563,17 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
protected void setHeaders(HttpServletResponse response, Resource resource, MediaType mediaType) throws IOException {
long length = resource.contentLength();
if (length > Integer.MAX_VALUE) {
- throw new IOException("Resource content too long (beyond Integer.MAX_VALUE): " + resource);
+ if (contentLengthLongAvailable) {
+ response.setContentLengthLong(length);
+ }
+ else {
+ response.setHeader(HttpHeaders.CONTENT_LENGTH, Long.toString(length));
+ }
}
- response.setContentLength((int) length);
+ else {
+ response.setContentLength((int) length);
+ }
+
if (mediaType != null) {
response.setContentType(mediaType.toString());
}
@@ -429,187 +586,10 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
}
- /**
- * Write the actual content out to the given servlet response,
- * streaming the resource's content.
- * @param response current servlet response
- * @param resource the identified resource (never {@code null})
- * @throws IOException in case of errors while writing the content
- */
- protected void writeContent(HttpServletResponse response, Resource resource) throws IOException {
- try {
- InputStream in = resource.getInputStream();
- try {
- StreamUtils.copy(in, response.getOutputStream());
- }
- catch (NullPointerException ex) {
- // ignore, see SPR-13620
- }
- finally {
- try {
- in.close();
- }
- catch (Throwable ex) {
- // ignore, see SPR-12999
- }
- }
- }
- catch (FileNotFoundException ex) {
- // ignore, see SPR-12999
- }
- }
-
- /**
- * Write parts of the resource as indicated by the request {@code Range} header.
- * @param request current servlet request
- * @param response current servlet response
- * @param resource the identified resource (never {@code null})
- * @param contentType the content type
- * @throws IOException in case of errors while writing the content
- */
- protected void writePartialContent(HttpServletRequest request, HttpServletResponse response,
- Resource resource, MediaType contentType) throws IOException {
-
- long length = resource.contentLength();
-
- List<HttpRange> ranges;
- try {
- HttpHeaders headers = new ServletServerHttpRequest(request).getHeaders();
- ranges = headers.getRange();
- }
- catch (IllegalArgumentException ex) {
- response.addHeader("Content-Range", "bytes */" + length);
- response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
- return;
- }
-
- response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
-
- if (ranges.size() == 1) {
- HttpRange range = ranges.get(0);
-
- long start = range.getRangeStart(length);
- long end = range.getRangeEnd(length);
- long rangeLength = end - start + 1;
-
- setHeaders(response, resource, contentType);
- response.addHeader("Content-Range", "bytes " + start + "-" + end + "/" + length);
- response.setContentLength((int) rangeLength);
-
- InputStream in = resource.getInputStream();
- try {
- copyRange(in, response.getOutputStream(), start, end);
- }
- finally {
- try {
- in.close();
- }
- catch (IOException ex) {
- // ignore
- }
- }
- }
- else {
- String boundaryString = MimeTypeUtils.generateMultipartBoundaryString();
- response.setContentType("multipart/byteranges; boundary=" + boundaryString);
-
- ServletOutputStream out = response.getOutputStream();
-
- for (HttpRange range : ranges) {
- long start = range.getRangeStart(length);
- long end = range.getRangeEnd(length);
-
- InputStream in = resource.getInputStream();
-
- // Writing MIME header.
- out.println();
- out.println("--" + boundaryString);
- if (contentType != null) {
- out.println("Content-Type: " + contentType);
- }
- out.println("Content-Range: bytes " + start + "-" + end + "/" + length);
- out.println();
-
- // Printing content
- copyRange(in, out, start, end);
- }
- out.println();
- out.print("--" + boundaryString + "--");
- }
- }
-
- private void copyRange(InputStream in, OutputStream out, long start, long end) throws IOException {
- long skipped = in.skip(start);
- if (skipped < start) {
- throw new IOException("Skipped only " + skipped + " bytes out of " + start + " required.");
- }
-
- long bytesToCopy = end - start + 1;
- byte buffer[] = new byte[StreamUtils.BUFFER_SIZE];
- while (bytesToCopy > 0) {
- int bytesRead = in.read(buffer);
- if (bytesRead <= bytesToCopy) {
- out.write(buffer, 0, bytesRead);
- bytesToCopy -= bytesRead;
- }
- else {
- out.write(buffer, 0, (int) bytesToCopy);
- bytesToCopy = 0;
- }
- if (bytesRead == -1) {
- break;
- }
- }
- }
-
@Override
public String toString() {
return "ResourceHttpRequestHandler [locations=" + getLocations() + ", resolvers=" + getResourceResolvers() + "]";
}
-
- /**
- * Inner class to avoid a hard-coded JAF dependency.
- */
- private static class ActivationMediaTypeFactory {
-
- private static final FileTypeMap fileTypeMap;
-
- static {
- fileTypeMap = loadFileTypeMapFromContextSupportModule();
- }
-
- private static FileTypeMap loadFileTypeMapFromContextSupportModule() {
- // See if we can find the extended mime.types from the context-support module...
- Resource mappingLocation = new ClassPathResource("org/springframework/mail/javamail/mime.types");
- if (mappingLocation.exists()) {
- InputStream inputStream = null;
- try {
- inputStream = mappingLocation.getInputStream();
- return new MimetypesFileTypeMap(inputStream);
- }
- catch (IOException ex) {
- // ignore
- }
- finally {
- if (inputStream != null) {
- try {
- inputStream.close();
- }
- catch (IOException ex) {
- // ignore
- }
- }
- }
- }
- return FileTypeMap.getDefaultFileTypeMap();
- }
-
- public static MediaType getMediaType(String filename) {
- String mediaType = fileTypeMap.getContentType(filename);
- return (StringUtils.hasText(mediaType) ? MediaType.parseMediaType(mediaType) : null);
- }
- }
-
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceTransformer.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceTransformer.java
index fe941d83..de17faf1 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceTransformer.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceTransformer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,10 +35,10 @@ public interface ResourceTransformer {
* @param request the current request
* @param resource the resource to transform
* @param transformerChain the chain of remaining transformers to delegate to
- * @return the transformed resource, never {@code null}
+ * @return the transformed resource (never {@code null})
* @throws IOException if the transformation fails
*/
Resource transform(HttpServletRequest request, Resource resource, ResourceTransformerChain transformerChain)
throws IOException;
-} \ No newline at end of file
+}
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 471ae154..901d0c5e 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
@@ -27,6 +27,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.filter.OncePerRequestFilter;
+import org.springframework.web.util.UrlPathHelper;
/**
* A filter that wraps the {@link HttpServletResponse} and overrides its
@@ -96,13 +97,14 @@ public class ResourceUrlEncodingFilter extends OncePerRequestFilter {
private void initLookupPath(ResourceUrlProvider urlProvider) {
if (this.indexLookupPath == null) {
- String requestUri = urlProvider.getPathHelper().getRequestUri(this.request);
- String lookupPath = urlProvider.getPathHelper().getLookupPathForRequest(this.request);
+ UrlPathHelper pathHelper = urlProvider.getUrlPathHelper();
+ String requestUri = pathHelper.getRequestUri(this.request);
+ String lookupPath = pathHelper.getLookupPathForRequest(this.request);
this.indexLookupPath = requestUri.lastIndexOf(lookupPath);
this.prefixLookupPath = requestUri.substring(0, this.indexLookupPath);
if ("/".equals(lookupPath) && !"/".equals(requestUri)) {
- String contextPath = urlProvider.getPathHelper().getContextPath(this.request);
+ String contextPath = pathHelper.getContextPath(this.request);
if (requestUri.equals(contextPath)) {
this.indexLookupPath = requestUri.length();
this.prefixLookupPath = requestUri;
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceUrlProvider.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceUrlProvider.java
index 9ee0c5a8..f39e02ff 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceUrlProvider.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceUrlProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -51,7 +51,7 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
protected final Log logger = LogFactory.getLog(getClass());
- private UrlPathHelper pathHelper = new UrlPathHelper();
+ private UrlPathHelper urlPathHelper = new UrlPathHelper();
private PathMatcher pathMatcher = new AntPathMatcher();
@@ -65,15 +65,24 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
* {@link #getForRequestUrl(javax.servlet.http.HttpServletRequest, String)}
* in order to derive the lookup path for a target request URL path.
*/
- public void setUrlPathHelper(UrlPathHelper pathHelper) {
- this.pathHelper = pathHelper;
+ public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
+ this.urlPathHelper = urlPathHelper;
}
/**
* Return the configured {@code UrlPathHelper}.
+ * @since 4.2.8
*/
+ public UrlPathHelper getUrlPathHelper() {
+ return this.urlPathHelper;
+ }
+
+ /**
+ * @deprecated as of Spring 4.2.8, in favor of {@link #getUrlPathHelper}
+ */
+ @Deprecated
public UrlPathHelper getPathHelper() {
- return this.pathHelper;
+ return this.urlPathHelper;
}
/**
@@ -135,6 +144,7 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
}
}
+
protected void detectResourceHandlers(ApplicationContext appContext) {
logger.debug("Looking for resource handler mappings");
@@ -158,7 +168,6 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
}
}
-
/**
* A variation on {@link #getForLookupPath(String)} that accepts a full request
* URL path (i.e. including context and servlet path) and returns the full request
@@ -181,8 +190,9 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
}
private int getLookupPathIndex(HttpServletRequest request) {
- String requestUri = getPathHelper().getRequestUri(request);
- String lookupPath = getPathHelper().getLookupPathForRequest(request);
+ UrlPathHelper pathHelper = getUrlPathHelper();
+ String requestUri = pathHelper.getRequestUri(request);
+ String lookupPath = pathHelper.getLookupPathForRequest(request);
return requestUri.indexOf(lookupPath);
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/WebJarsResourceResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/WebJarsResourceResolver.java
index 8d08bbd7..a76214c3 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/WebJarsResourceResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/WebJarsResourceResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,11 +45,29 @@ import org.springframework.core.io.Resource;
*/
public class WebJarsResourceResolver extends AbstractResourceResolver {
- private final static String WEBJARS_LOCATION = "META-INF/resources/webjars";
+ private final static String WEBJARS_LOCATION = "META-INF/resources/webjars/";
private final static int WEBJARS_LOCATION_LENGTH = WEBJARS_LOCATION.length();
- private final WebJarAssetLocator webJarAssetLocator = new WebJarAssetLocator();
+
+ private final WebJarAssetLocator webJarAssetLocator;
+
+
+ /**
+ * Create a {@code WebJarsResourceResolver} with a default {@code WebJarAssetLocator} instance.
+ */
+ public WebJarsResourceResolver() {
+ this(new WebJarAssetLocator());
+ }
+
+ /**
+ * Create a {@code WebJarsResourceResolver} with a custom {@code WebJarAssetLocator} instance,
+ * e.g. with a custom index.
+ * @since 4.3
+ */
+ public WebJarsResourceResolver(WebJarAssetLocator webJarAssetLocator) {
+ this.webJarAssetLocator = webJarAssetLocator;
+ }
@Override
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractAnnotationConfigDispatcherServletInitializer.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractAnnotationConfigDispatcherServletInitializer.java
index fc897a10..20b2b9e4 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractAnnotationConfigDispatcherServletInitializer.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractAnnotationConfigDispatcherServletInitializer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,6 +32,9 @@ import org.springframework.web.context.support.AnnotationConfigWebApplicationCon
* Further template and customization methods are provided by
* {@link AbstractDispatcherServletInitializer}.
*
+ * <p>This is the preferred approach for applications that use Java-based
+ * Spring configuration.
+ *
* @author Arjen Poutsma
* @author Chris Beams
* @since 3.2
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContext.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContext.java
index 9fae8679..df008b77 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContext.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContext.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,17 +53,18 @@ import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.WebUtils;
/**
- * Context holder for request-specific state, like current web application context, current locale, current theme,
- * and potential binding errors. Provides easy access to localized messages and Errors instances.
+ * Context holder for request-specific state, like current web application context, current locale,
+ * current theme, and potential binding errors. Provides easy access to localized messages and
+ * Errors instances.
*
- * <p>Suitable for exposition to views, and usage within JSP's "useBean" tag, JSP scriptlets, JSTL EL, Velocity
- * templates, etc. Necessary for views that do not have access to the servlet request, like Velocity templates.
+ * <p>Suitable for exposition to views, and usage within JSP's "useBean" tag, JSP scriptlets, JSTL EL,
+ * etc. Necessary for views that do not have access to the servlet request, like FreeMarker templates.
*
* <p>Can be instantiated manually, or automatically exposed to views as model attribute via AbstractView's
* "requestContextAttribute" property.
*
- * <p>Will also work outside of DispatcherServlet requests, accessing the root WebApplicationContext and using
- * an appropriate fallback for the locale (the HttpServletRequest's primary locale).
+ * <p>Will also work outside of DispatcherServlet requests, accessing the root WebApplicationContext
+ * and using an appropriate fallback for the locale (the HttpServletRequest's primary locale).
*
* @author Juergen Hoeller
* @author Rossen Stoyanchev
@@ -467,7 +468,7 @@ public class RequestContext {
/**
* (De)activate default HTML escaping for messages and errors, for the scope of this RequestContext.
* <p>The default is the application-wide setting (the "defaultHtmlEscape" context-param in web.xml).
- * @see org.springframework.web.util.WebUtils#isDefaultHtmlEscape
+ * @see org.springframework.web.util.WebUtils#getDefaultHtmlEscape
*/
public void setDefaultHtmlEscape(boolean defaultHtmlEscape) {
this.defaultHtmlEscape = defaultHtmlEscape;
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContextUtils.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContextUtils.java
index ac6e0769..8c854917 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContextUtils.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContextUtils.java
@@ -198,7 +198,7 @@ public abstract class RequestContextUtils {
* <p>Consider using {@link org.springframework.context.i18n.LocaleContextHolder#getTimeZone()}
* which will normally be populated with the same TimeZone: That method only
* differs in terms of its fallback to the system time zone if the LocaleResolver
- * hasn't provided provided a specific time zone (instead of this method's {@code null}).
+ * hasn't provided a specific time zone (instead of this method's {@code null}).
* @param request current HTTP request
* @return the current time zone for the given request, either from the
* TimeZoneAwareLocaleResolver or {@code null} if none associated
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/ServletUriComponentsBuilder.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/ServletUriComponentsBuilder.java
index 07abc64b..367aed85 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/ServletUriComponentsBuilder.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/ServletUriComponentsBuilder.java
@@ -16,6 +16,7 @@
package org.springframework.web.servlet.support;
+import java.util.Enumeration;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.HttpRequest;
@@ -27,8 +28,8 @@ import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
+import org.springframework.web.util.UriUtils;
import org.springframework.web.util.UrlPathHelper;
-import org.springframework.web.util.WebUtils;
/**
* A UriComponentsBuilder that extracts information from the HttpServletRequest.
@@ -43,7 +44,6 @@ public class ServletUriComponentsBuilder extends UriComponentsBuilder {
/**
* Default constructor. Protected to prevent direct instantiation.
- *
* @see #fromContextPath(HttpServletRequest)
* @see #fromServletMapping(HttpServletRequest)
* @see #fromRequest(HttpServletRequest)
@@ -133,8 +133,15 @@ public class ServletUriComponentsBuilder extends UriComponentsBuilder {
}
private static String prependForwardedPrefix(HttpServletRequest request, String path) {
- String prefix = request.getHeader("X-Forwarded-Prefix");
- if (StringUtils.hasText(prefix)) {
+ String prefix = null;
+ Enumeration<String> names = request.getHeaderNames();
+ while (names.hasMoreElements()) {
+ String name = names.nextElement();
+ if ("X-Forwarded-Prefix".equalsIgnoreCase(name)) {
+ prefix = request.getHeader(name);
+ }
+ }
+ if (prefix != null) {
path = prefix + path;
}
return path;
@@ -211,8 +218,7 @@ public class ServletUriComponentsBuilder extends UriComponentsBuilder {
public String removePathExtension() {
String extension = null;
if (this.originalPath != null) {
- String filename = WebUtils.extractFullFilenameFromUrlPath(this.originalPath);
- extension = StringUtils.getFilenameExtension(filename);
+ extension = UriUtils.extractFileExtension(this.originalPath);
if (!StringUtils.isEmpty(extension)) {
int end = this.originalPath.length() - (extension.length() + 1);
replacePath(this.originalPath.substring(0, end));
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/WebContentGenerator.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/WebContentGenerator.java
index 20cf89d2..6cf54cf2 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/WebContentGenerator.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/WebContentGenerator.java
@@ -16,8 +16,10 @@
package org.springframework.web.servlet.support;
+import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashSet;
+import java.util.Collection;
+import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@@ -26,6 +28,9 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.CacheControl;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.HttpRequestMethodNotSupportedException;
@@ -53,6 +58,7 @@ import org.springframework.web.context.support.WebApplicationObjectSupport;
* @author Rod Johnson
* @author Juergen Hoeller
* @author Brian Clozel
+ * @author Rossen Stoyanchev
* @see #setCacheSeconds
* @see #setCacheControl
* @see #setRequireSession
@@ -74,16 +80,27 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
protected static final String HEADER_CACHE_CONTROL = "Cache-Control";
+ /** Checking for Servlet 3.0+ HttpServletResponse.getHeaders(String) */
+ private static final boolean servlet3Present =
+ ClassUtils.hasMethod(HttpServletResponse.class, "getHeaders", String.class);
+
/** Set of supported HTTP methods */
private Set<String> supportedMethods;
+ private String allowHeader;
+
private boolean requireSession = false;
private CacheControl cacheControl;
private int cacheSeconds = -1;
+ private String[] varyByRequestHeaders;
+
+
+ // deprecated fields
+
/** Use HTTP 1.0 expires header? */
private boolean useExpiresHeader = false;
@@ -112,11 +129,12 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
*/
public WebContentGenerator(boolean restrictDefaultSupportedMethods) {
if (restrictDefaultSupportedMethods) {
- this.supportedMethods = new HashSet<String>(4);
+ this.supportedMethods = new LinkedHashSet<String>(4);
this.supportedMethods.add(METHOD_GET);
this.supportedMethods.add(METHOD_HEAD);
this.supportedMethods.add(METHOD_POST);
}
+ initAllowHeader();
}
/**
@@ -124,7 +142,7 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
* @param supportedMethods the supported HTTP methods for this content generator
*/
public WebContentGenerator(String... supportedMethods) {
- this.supportedMethods = new HashSet<String>(Arrays.asList(supportedMethods));
+ setSupportedMethods(supportedMethods);
}
@@ -140,6 +158,7 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
else {
this.supportedMethods = null;
}
+ initAllowHeader();
}
/**
@@ -149,6 +168,40 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
return StringUtils.toStringArray(this.supportedMethods);
}
+ private void initAllowHeader() {
+ Collection<String> allowedMethods;
+ if (this.supportedMethods == null) {
+ allowedMethods = new ArrayList<String>(HttpMethod.values().length - 1);
+ for (HttpMethod method : HttpMethod.values()) {
+ if (!HttpMethod.TRACE.equals(method)) {
+ allowedMethods.add(method.name());
+ }
+ }
+ }
+ else if (this.supportedMethods.contains(HttpMethod.OPTIONS.name())) {
+ allowedMethods = this.supportedMethods;
+ }
+ else {
+ allowedMethods = new ArrayList<String>(this.supportedMethods);
+ allowedMethods.add(HttpMethod.OPTIONS.name());
+
+ }
+ this.allowHeader = StringUtils.collectionToCommaDelimitedString(allowedMethods);
+ }
+
+ /**
+ * Return the "Allow" header value to use in response to an HTTP OPTIONS
+ * request based on the configured {@link #setSupportedMethods supported
+ * methods} also automatically adding "OPTIONS" to the list even if not
+ * present as a supported method. This means sub-classes don't have to
+ * explicitly list "OPTIONS" as a supported method as long as HTTP OPTIONS
+ * requests are handled before making a call to
+ * {@link #checkRequest(HttpServletRequest)}.
+ */
+ protected String getAllowHeader() {
+ return this.allowHeader;
+ }
+
/**
* Set whether a session should be required to handle requests.
*/
@@ -205,6 +258,29 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
}
/**
+ * Configure one or more request header names (e.g. "Accept-Language") to
+ * add to the "Vary" response header to inform clients that the response is
+ * subject to content negotiation and variances based on the value of the
+ * given request headers. The configured request header names are added only
+ * if not already present in the response "Vary" header.
+ * <p><strong>Note:</strong> This property is only supported on Servlet 3.0+
+ * which allows checking existing response header values.
+ * @param varyByRequestHeaders one or more request header names
+ * @since 4.3
+ */
+ public final void setVaryByRequestHeaders(String... varyByRequestHeaders) {
+ this.varyByRequestHeaders = varyByRequestHeaders;
+ }
+
+ /**
+ * Return the configured request header names for the "Vary" response header.
+ * @since 4.3
+ */
+ public final String[] getVaryByRequestHeaders() {
+ return this.varyByRequestHeaders;
+ }
+
+ /**
* Set whether to use the HTTP 1.0 expires header. Default is "false",
* as of 4.2.
* <p>Note: Cache headers will only get applied if caching is enabled
@@ -322,6 +398,11 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
else {
applyCacheSeconds(response, this.cacheSeconds);
}
+ if (servlet3Present && this.varyByRequestHeaders != null) {
+ for (String value : getVaryRequestHeadersToAdd(response)) {
+ response.addHeader("Vary", value);
+ }
+ }
}
/**
@@ -513,4 +594,26 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
}
}
+
+ private Collection<String> getVaryRequestHeadersToAdd(HttpServletResponse response) {
+ if (!response.containsHeader(HttpHeaders.VARY)) {
+ return Arrays.asList(getVaryByRequestHeaders());
+ }
+ Collection<String> result = new ArrayList<String>(getVaryByRequestHeaders().length);
+ Collections.addAll(result, getVaryByRequestHeaders());
+ for (String header : response.getHeaders(HttpHeaders.VARY)) {
+ for (String existing : StringUtils.tokenizeToStringArray(header, ",")) {
+ if ("*".equals(existing)) {
+ return Collections.emptyList();
+ }
+ for (String value : getVaryByRequestHeaders()) {
+ if (value.equalsIgnoreCase(existing)) {
+ result.remove(value);
+ }
+ }
+ }
+ }
+ return result;
+ }
+
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/CheckboxTag.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/CheckboxTag.java
index ebde41ea..f0e619e9 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/CheckboxTag.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/CheckboxTag.java
@@ -38,7 +38,7 @@ import org.springframework.web.bind.WebDataBinder;
* the bound {@link Collection}.
* <h3>Approach Three</h3>
* For any other bound value type, the '{@code input(checkbox)}' is marked as 'checked'
- * if the the configured {@link #setValue(Object) value} is equal to the bound value.
+ * if the configured {@link #setValue(Object) value} is equal to the bound value.
*
* @author Rob Harrop
* @author Juergen Hoeller
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/FormTag.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/FormTag.java
index 5aa9d232..d0b76ff9 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/FormTag.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/FormTag.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -147,7 +147,9 @@ public class FormTag extends AbstractHtmlElementTag {
* Set the name of the form attribute in the model.
* <p>May be a runtime expression.
* @see #setModelAttribute
+ * @deprecated as of Spring 4.3, in favor of {@link #setModelAttribute}
*/
+ @Deprecated
public void setCommandName(String commandName) {
this.modelAttribute = commandName;
}
@@ -155,7 +157,9 @@ public class FormTag extends AbstractHtmlElementTag {
/**
* Get the name of the form attribute in the model.
* @see #getModelAttribute
+ * @deprecated as of Spring 4.3, in favor of {@link #getModelAttribute}
*/
+ @Deprecated
protected String getCommandName() {
return this.modelAttribute;
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/DefaultRequestToViewNameTranslator.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/DefaultRequestToViewNameTranslator.java
index dd8b1d39..1e42bb3b 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/DefaultRequestToViewNameTranslator.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/DefaultRequestToViewNameTranslator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,28 +24,28 @@ import org.springframework.web.servlet.RequestToViewNameTranslator;
import org.springframework.web.util.UrlPathHelper;
/**
- * {@link org.springframework.web.servlet.RequestToViewNameTranslator}
- * that simply transforms the URI of the incoming request into a view name.
+ * {@link RequestToViewNameTranslator} that simply transforms the URI of
+ * the incoming request into a view name.
*
- * <p>Can be explicitly defined as the "viewNameTranslator" bean in a
+ * <p>Can be explicitly defined as the {@code viewNameTranslator} bean in a
* {@link org.springframework.web.servlet.DispatcherServlet} context.
* Otherwise, a plain default instance will be used.
*
* <p>The default transformation simply strips leading and trailing slashes
* as well as the file extension of the URI, and returns the result as the
- * view name with the configured {@link #setPrefix "prefix"} and a
- * {@link #setSuffix "suffix"} added as appropriate.
+ * view name with the configured {@link #setPrefix prefix} and a
+ * {@link #setSuffix suffix} added as appropriate.
*
* <p>The stripping of the leading slash and file extension can be disabled
- * using the {@link #setStripLeadingSlash "stripLeadingSlash"} and
- * {@link #setStripExtension "stripExtension"} properties, respectively.
+ * using the {@link #setStripLeadingSlash stripLeadingSlash} and
+ * {@link #setStripExtension stripExtension} properties, respectively.
*
* <p>Find below some examples of request to view name translation.
- *
- * <pre class="code">http://localhost:8080/gamecast/display.html -> display
- * http://localhost:8080/gamecast/displayShoppingCart.html -> displayShoppingCart
- * http://localhost:8080/gamecast/admin/index.html -> admin/index
- * </pre>
+ * <ul>
+ * <li>{@code http://localhost:8080/gamecast/display.html} &raquo; {@code display}</li>
+ * <li>{@code http://localhost:8080/gamecast/displayShoppingCart.html} &raquo; {@code displayShoppingCart}</li>
+ * <li>{@code http://localhost:8080/gamecast/admin/index.html} &raquo; {@code admin/index}</li>
+ * </ul>
*
* @author Rob Harrop
* @author Juergen Hoeller
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/InternalResourceViewResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/InternalResourceViewResolver.java
index 679e8a95..13a70850 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/InternalResourceViewResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/InternalResourceViewResolver.java
@@ -66,6 +66,19 @@ public class InternalResourceViewResolver extends UrlBasedViewResolver {
setViewClass(viewClass);
}
+ /**
+ * A convenience constructor that allows for specifying {@link #setPrefix prefix}
+ * and {@link #setSuffix suffix} as constructor arguments.
+ * @param prefix the prefix that gets prepended to view names when building a URL
+ * @param suffix the suffix that gets appended to view names when building a URL
+ * @since 4.3
+ */
+ public InternalResourceViewResolver(String prefix, String suffix) {
+ this();
+ setPrefix(prefix);
+ setSuffix(suffix);
+ }
+
/**
* This resolver requires {@link InternalResourceView}.
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/RedirectView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/RedirectView.java
index 0a188696..928e5ef9 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/RedirectView.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/RedirectView.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -104,6 +104,8 @@ public class RedirectView extends AbstractUrlBasedView implements SmartView {
private boolean propagateQueryParams = false;
+ private String[] hosts;
+
/**
* Constructor for use as a bean.
@@ -253,6 +255,28 @@ public class RedirectView extends AbstractUrlBasedView implements SmartView {
}
/**
+ * Configure one or more hosts associated with the application.
+ * All other hosts will be considered external hosts.
+ * <p>In effect, this property provides a way turn off encoding via
+ * {@link HttpServletResponse#encodeRedirectURL} for URLs that have a
+ * host and that host is not listed as a known host.
+ * <p>If not set (the default) all URLs are encoded through the response.
+ * @param hosts one or more application hosts
+ * @since 4.3
+ */
+ public void setHosts(String... hosts) {
+ this.hosts = hosts;
+ }
+
+ /**
+ * Return the configured application hosts.
+ * @since 4.3
+ */
+ public String[] getHosts() {
+ return this.hosts;
+ }
+
+ /**
* Returns "true" indicating this view performs a redirect.
*/
@Override
@@ -583,30 +607,56 @@ public class RedirectView extends AbstractUrlBasedView implements SmartView {
protected void sendRedirect(HttpServletRequest request, HttpServletResponse response,
String targetUrl, boolean http10Compatible) throws IOException {
- String encodedRedirectURL = response.encodeRedirectURL(targetUrl);
+ String encodedURL = (isRemoteHost(targetUrl) ? targetUrl : response.encodeRedirectURL(targetUrl));
if (http10Compatible) {
HttpStatus attributeStatusCode = (HttpStatus) request.getAttribute(View.RESPONSE_STATUS_ATTRIBUTE);
if (this.statusCode != null) {
response.setStatus(this.statusCode.value());
- response.setHeader("Location", encodedRedirectURL);
+ response.setHeader("Location", encodedURL);
}
else if (attributeStatusCode != null) {
response.setStatus(attributeStatusCode.value());
- response.setHeader("Location", encodedRedirectURL);
+ response.setHeader("Location", encodedURL);
}
else {
// Send status code 302 by default.
- response.sendRedirect(encodedRedirectURL);
+ response.sendRedirect(encodedURL);
}
}
else {
HttpStatus statusCode = getHttp11StatusCode(request, response, targetUrl);
response.setStatus(statusCode.value());
- response.setHeader("Location", encodedRedirectURL);
+ response.setHeader("Location", encodedURL);
}
}
/**
+ * Whether the given targetUrl has a host that is a "foreign" system in which
+ * case {@link HttpServletResponse#encodeRedirectURL} will not be applied.
+ * This method returns {@code true} if the {@link #setHosts(String[])}
+ * property is configured and the target URL has a host that does not match.
+ * @param targetUrl the target redirect URL
+ * @return {@code true} the target URL has a remote host, {@code false} if it
+ * the URL does not have a host or the "host" property is not configured.
+ * @since 4.3
+ */
+ protected boolean isRemoteHost(String targetUrl) {
+ if (ObjectUtils.isEmpty(getHosts())) {
+ return false;
+ }
+ String targetHost = UriComponentsBuilder.fromUriString(targetUrl).build().getHost();
+ if (StringUtils.isEmpty(targetHost)) {
+ return false;
+ }
+ for (String host : getHosts()) {
+ if (targetHost.equals(host)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
* Determines the status code to use for HTTP 1.1 compatible requests.
* <p>The default implementation returns the {@link #setStatusCode(HttpStatus) statusCode}
* property if set, or the value of the {@link #RESPONSE_STATUS_ATTRIBUTE} attribute.
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/UrlBasedViewResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/UrlBasedViewResolver.java
index 8bd441ea..9b09f8a9 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/UrlBasedViewResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/UrlBasedViewResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
+import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.BeanUtils;
import org.springframework.core.Ordered;
@@ -112,6 +113,8 @@ public class UrlBasedViewResolver extends AbstractCachingViewResolver implements
private boolean redirectHttp10Compatible = true;
+ private String[] redirectHosts;
+
private String requestContextAttribute;
/** Map of static attributes, keyed by attribute name (String) */
@@ -254,6 +257,28 @@ public class UrlBasedViewResolver extends AbstractCachingViewResolver implements
}
/**
+ * Configure one or more hosts associated with the application.
+ * All other hosts will be considered external hosts.
+ * <p>In effect, this property provides a way turn off encoding on redirect
+ * via {@link HttpServletResponse#encodeRedirectURL} for URLs that have a
+ * host and that host is not listed as a known host.
+ * <p>If not set (the default) all URLs are encoded through the response.
+ * @param redirectHosts one or more application hosts
+ * @since 4.3
+ */
+ public void setRedirectHosts(String... redirectHosts) {
+ this.redirectHosts = redirectHosts;
+ }
+
+ /**
+ * Return the configured application hosts for redirect purposes.
+ * @since 4.3
+ */
+ public String[] getRedirectHosts() {
+ return this.redirectHosts;
+ }
+
+ /**
* Set the name of the RequestContext attribute for all views.
* @param requestContextAttribute name of the RequestContext attribute
* @see AbstractView#setRequestContextAttribute
@@ -435,6 +460,7 @@ public class UrlBasedViewResolver extends AbstractCachingViewResolver implements
if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
+ view.setHosts(getRedirectHosts());
return applyLifecycleMethods(viewName, view);
}
// Check for special "forward:" prefix.
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerView.java
index f5e260eb..00e678f1 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerView.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerView.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,7 +20,6 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
-import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import javax.servlet.GenericServlet;
@@ -42,6 +41,7 @@ import freemarker.ext.servlet.HttpRequestParametersHashModel;
import freemarker.ext.servlet.HttpSessionHashModel;
import freemarker.ext.servlet.ServletContextHashModel;
import freemarker.template.Configuration;
+import freemarker.template.DefaultObjectWrapperBuilder;
import freemarker.template.ObjectWrapper;
import freemarker.template.SimpleHash;
import freemarker.template.Template;
@@ -186,10 +186,10 @@ public class FreeMarkerView extends AbstractTemplateView {
* {@link ObjectWrapper#DEFAULT_WRAPPER default wrapper} if none specified.
* @see freemarker.template.Configuration#getObjectWrapper()
*/
- @SuppressWarnings("deprecation")
protected ObjectWrapper getObjectWrapper() {
ObjectWrapper ow = getConfiguration().getObjectWrapper();
- return (ow != null ? ow : ObjectWrapper.DEFAULT_WRAPPER);
+ return (ow != null ? ow :
+ new DefaultObjectWrapperBuilder(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS).build());
}
/**
@@ -405,7 +405,7 @@ public class FreeMarkerView extends AbstractTemplateView {
@Override
public Enumeration<String> getInitParameterNames() {
- return Collections.enumeration(new HashSet<String>());
+ return Collections.enumeration(Collections.<String>emptySet());
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerViewResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerViewResolver.java
index c774ccf0..3768eb8a 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerViewResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerViewResolver.java
@@ -40,11 +40,29 @@ import org.springframework.web.servlet.view.AbstractTemplateViewResolver;
*/
public class FreeMarkerViewResolver extends AbstractTemplateViewResolver {
+ /**
+ * Sets the default {@link #setViewClass view class} to {@link #requiredViewClass}:
+ * by default {@link FreeMarkerView}.
+ */
public FreeMarkerViewResolver() {
setViewClass(requiredViewClass());
}
/**
+ * A convenience constructor that allows for specifying {@link #setPrefix prefix}
+ * and {@link #setSuffix suffix} as constructor arguments.
+ * @param prefix the prefix that gets prepended to view names when building a URL
+ * @param suffix the suffix that gets appended to view names when building a URL
+ * @since 4.3
+ */
+ public FreeMarkerViewResolver(String prefix, String suffix) {
+ this();
+ setPrefix(prefix);
+ setSuffix(suffix);
+ }
+
+
+ /**
* Requires {@link FreeMarkerView}.
*/
@Override
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupView.java
index 290d96c7..38eda981 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupView.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupView.java
@@ -54,7 +54,7 @@ public class GroovyMarkupView extends AbstractTemplateView {
/**
* Set the MarkupTemplateEngine to use in this view.
- * <p>If not set, the engine is auto-detected by looking up up a single
+ * <p>If not set, the engine is auto-detected by looking up a single
* {@link GroovyMarkupConfig} bean in the web application context and using
* it to obtain the configured {@code MarkupTemplateEngine} instance.
* @see GroovyMarkupConfig
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupViewResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupViewResolver.java
index 79c5f87a..197abdf2 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupViewResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupViewResolver.java
@@ -38,10 +38,28 @@ import org.springframework.web.servlet.view.AbstractTemplateViewResolver;
*/
public class GroovyMarkupViewResolver extends AbstractTemplateViewResolver {
+ /**
+ * Sets the default {@link #setViewClass view class} to {@link #requiredViewClass}:
+ * by default {@link GroovyMarkupView}.
+ */
public GroovyMarkupViewResolver() {
setViewClass(requiredViewClass());
}
+ /**
+ * A convenience constructor that allows for specifying {@link #setPrefix prefix}
+ * and {@link #setSuffix suffix} as constructor arguments.
+ * @param prefix the prefix that gets prepended to view names when building a URL
+ * @param suffix the suffix that gets appended to view names when building a URL
+ * @since 4.3
+ */
+ public GroovyMarkupViewResolver(String prefix, String suffix) {
+ this();
+ setPrefix(prefix);
+ setSuffix(suffix);
+ }
+
+
@Override
protected Class<?> requiredViewClass() {
return GroovyMarkupView.class;
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/jasperreports/JasperReportsMultiFormatView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/jasperreports/JasperReportsMultiFormatView.java
index 7e9af07d..7821494d 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/jasperreports/JasperReportsMultiFormatView.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/jasperreports/JasperReportsMultiFormatView.java
@@ -170,7 +170,7 @@ public class JasperReportsMultiFormatView extends AbstractJasperReportsView {
String format = (String) model.get(this.formatKey);
if (format == null) {
- throw new IllegalArgumentException("No format format found in model");
+ throw new IllegalArgumentException("No format found in model");
}
if (logger.isDebugEnabled()) {
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/AbstractJackson2View.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/AbstractJackson2View.java
index 34b75fa2..cf67ecc5 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/AbstractJackson2View.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/AbstractJackson2View.java
@@ -38,7 +38,7 @@ import org.springframework.web.servlet.view.AbstractView;
* Abstract base class for Jackson based and content type independent
* {@link AbstractView} implementations.
*
- * <p>Compatible with Jackson 2.1 and higher.
+ * <p>Compatible with Jackson 2.6 and higher, as of Spring 4.3.
*
* @author Jeremy Grelle
* @author Arjen Poutsma
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJackson2JsonView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJackson2JsonView.java
index 2039e8c0..12a5baa0 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJackson2JsonView.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJackson2JsonView.java
@@ -49,7 +49,7 @@ import org.springframework.web.servlet.View;
*
* <p>The default constructor uses the default configuration provided by {@link Jackson2ObjectMapperBuilder}.
*
- * <p>Compatible with Jackson 2.1 and higher.
+ * <p>Compatible with Jackson 2.6 and higher, as of Spring 4.3.
*
* @author Jeremy Grelle
* @author Arjen Poutsma
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateView.java
index 908b273e..ac8298a8 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateView.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateView.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,13 +18,9 @@ package org.springframework.web.servlet.view.script;
import java.io.IOException;
import java.io.InputStreamReader;
-import java.net.URL;
-import java.net.URLClassLoader;
import java.nio.charset.Charset;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
import javax.script.Invocable;
import javax.script.ScriptEngine;
@@ -40,7 +36,6 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextException;
import org.springframework.core.NamedThreadLocal;
-import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.scripting.support.StandardScriptEvalException;
@@ -55,7 +50,7 @@ import org.springframework.web.servlet.view.AbstractUrlBasedView;
* An {@link AbstractUrlBasedView} subclass designed to run any template library
* based on a JSR-223 script engine.
*
- * <p>If not set, each property is auto-detected by looking up up a single
+ * <p>If not set, each property is auto-detected by looking up a single
* {@link ScriptTemplateConfig} bean in the web application context and using
* it to obtain the configured properties.
*
@@ -96,7 +91,7 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
private Charset charset;
- private String resourceLoaderPath;
+ private String[] resourceLoaderPaths;
private ResourceLoader resourceLoader;
@@ -184,7 +179,16 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
* See {@link ScriptTemplateConfigurer#setResourceLoaderPath(String)} documentation.
*/
public void setResourceLoaderPath(String resourceLoaderPath) {
- this.resourceLoaderPath = resourceLoaderPath;
+ String[] paths = StringUtils.commaDelimitedListToStringArray(resourceLoaderPath);
+ this.resourceLoaderPaths = new String[paths.length + 1];
+ this.resourceLoaderPaths[0] = "";
+ for (int i = 0; i < paths.length; i++) {
+ String path = paths[i];
+ if (!path.endsWith("/") && !path.endsWith(":")) {
+ path = path + "/";
+ }
+ this.resourceLoaderPaths[i + 1] = path;
+ }
}
@@ -214,12 +218,12 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
if (this.charset == null) {
this.charset = (viewConfig.getCharset() != null ? viewConfig.getCharset() : DEFAULT_CHARSET);
}
- if (this.resourceLoaderPath == null) {
- this.resourceLoaderPath = (viewConfig.getResourceLoaderPath() != null ?
- viewConfig.getResourceLoaderPath() : DEFAULT_RESOURCE_LOADER_PATH);
+ if (this.resourceLoaderPaths == null) {
+ String resourceLoaderPath = viewConfig.getResourceLoaderPath();
+ setResourceLoaderPath(resourceLoaderPath == null ? DEFAULT_RESOURCE_LOADER_PATH : resourceLoaderPath);
}
if (this.resourceLoader == null) {
- this.resourceLoader = new DefaultResourceLoader(createClassLoader());
+ this.resourceLoader = getApplicationContext();
}
if (this.sharedEngine == null && viewConfig.isSharedEngine() != null) {
this.sharedEngine = viewConfig.isSharedEngine();
@@ -282,10 +286,7 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
if (!ObjectUtils.isEmpty(this.scripts)) {
try {
for (String script : this.scripts) {
- Resource resource = this.resourceLoader.getResource(script);
- if (!resource.exists()) {
- throw new IllegalStateException("Script resource [" + script + "] not found");
- }
+ Resource resource = getResource(script);
engine.eval(new InputStreamReader(resource.getInputStream()));
}
}
@@ -295,26 +296,14 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
}
}
- protected ClassLoader createClassLoader() {
- String[] paths = StringUtils.commaDelimitedListToStringArray(this.resourceLoaderPath);
- List<URL> urls = new ArrayList<URL>();
- try {
- for (String path : paths) {
- Resource[] resources = getApplicationContext().getResources(path);
- if (resources.length > 0) {
- for (Resource resource : resources) {
- if (resource.exists()) {
- urls.add(resource.getURL());
- }
- }
- }
+ protected Resource getResource(String location) {
+ for (String path : this.resourceLoaderPaths) {
+ Resource resource = this.resourceLoader.getResource(path + location);
+ if (resource.exists()) {
+ return resource;
}
}
- catch (IOException ex) {
- throw new IllegalStateException("Cannot create class loader: " + ex.getMessage());
- }
- ClassLoader classLoader = getApplicationContext().getClassLoader();
- return (urls.size() > 0 ? new URLClassLoader(urls.toArray(new URL[urls.size()]), classLoader) : classLoader);
+ throw new IllegalStateException("Resource [" + location + "] not found");
}
protected ScriptTemplateConfig autodetectViewConfig() throws BeansException {
@@ -329,7 +318,6 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
}
}
-
@Override
protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) {
super.prepareResponse(request, response);
@@ -365,7 +353,7 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
}
protected String getTemplate(String path) throws IOException {
- Resource resource = this.resourceLoader.getResource(path);
+ Resource resource = getResource(path);
InputStreamReader reader = new InputStreamReader(resource.getInputStream(), this.charset);
return FileCopyUtils.copyToString(reader);
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateViewResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateViewResolver.java
index 54f61470..168ce304 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateViewResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateViewResolver.java
@@ -35,10 +35,28 @@ import org.springframework.web.servlet.view.UrlBasedViewResolver;
*/
public class ScriptTemplateViewResolver extends UrlBasedViewResolver {
+ /**
+ * Sets the default {@link #setViewClass view class} to {@link #requiredViewClass}:
+ * by default {@link ScriptTemplateView}.
+ */
public ScriptTemplateViewResolver() {
setViewClass(requiredViewClass());
}
+ /**
+ * A convenience constructor that allows for specifying {@link #setPrefix prefix}
+ * and {@link #setSuffix suffix} as constructor arguments.
+ * @param prefix the prefix that gets prepended to view names when building a URL
+ * @param suffix the suffix that gets appended to view names when building a URL
+ * @since 4.3
+ */
+ public ScriptTemplateViewResolver(String prefix, String suffix) {
+ this();
+ setPrefix(prefix);
+ setSuffix(suffix);
+ }
+
+
@Override
protected Class<?> requiredViewClass() {
return ScriptTemplateView.class;
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/AbstractSpringPreparerFactory.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/AbstractSpringPreparerFactory.java
index 93103388..f58dbed0 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/AbstractSpringPreparerFactory.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/AbstractSpringPreparerFactory.java
@@ -25,7 +25,7 @@ import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
/**
- * Abstract implementation of the Tiles3 {@link org.apache.tiles.preparer.PreparerFactory}
+ * Abstract implementation of the Tiles {@link org.apache.tiles.preparer.PreparerFactory}
* interface, obtaining the current Spring WebApplicationContext and delegating to
* {@link #getPreparer(String, org.springframework.web.context.WebApplicationContext)}.
*
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/SimpleSpringPreparerFactory.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/SimpleSpringPreparerFactory.java
index 457ace34..6efe19a8 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/SimpleSpringPreparerFactory.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/SimpleSpringPreparerFactory.java
@@ -27,7 +27,7 @@ import org.apache.tiles.preparer.factory.NoSuchPreparerException;
import org.springframework.web.context.WebApplicationContext;
/**
- * Tiles3 {@link org.apache.tiles.preparer.PreparerFactory} implementation
+ * Tiles {@link org.apache.tiles.preparer.PreparerFactory} implementation
* that expects preparer class names and builds preparer instances for those,
* creating them through the Spring ApplicationContext in order to apply
* Spring container callbacks and configured Spring BeanPostProcessors.
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/SpringBeanPreparerFactory.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/SpringBeanPreparerFactory.java
index 0e252dc2..e04afdd2 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/SpringBeanPreparerFactory.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/SpringBeanPreparerFactory.java
@@ -22,7 +22,7 @@ import org.apache.tiles.preparer.ViewPreparer;
import org.springframework.web.context.WebApplicationContext;
/**
- * Tiles3 {@link org.apache.tiles.preparer.PreparerFactory} implementation
+ * Tiles {@link org.apache.tiles.preparer.PreparerFactory} implementation
* that expects preparer bean names and obtains preparer beans from the
* Spring ApplicationContext. The full bean creation process will be in
* the control of the Spring application context in this case, allowing
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/package-info.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/package-info.java
index 845ac016..8934e376 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/package-info.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/package-info.java
@@ -1,6 +1,6 @@
/**
* Support classes for the integration of
- * <a href="http://tiles.apache.org">Tiles3</a>
+ * <a href="http://tiles.apache.org">Tiles 3</a>
* (the standalone version of Tiles) as Spring web view technology.
* Contains a View implementation for Tiles definitions.
*/
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityConfig.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityConfig.java
index 9bffdadf..934883a3 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityConfig.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityConfig.java
@@ -26,7 +26,9 @@ import org.apache.velocity.app.VelocityEngine;
* @author Rod Johnson
* @see VelocityConfigurer
* @see VelocityView
+ * @deprecated as of Spring 4.3, in favor of FreeMarker
*/
+@Deprecated
public interface VelocityConfig {
/**
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityConfigurer.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityConfigurer.java
index 1dbd8a20..5f10a43a 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityConfigurer.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityConfigurer.java
@@ -25,7 +25,6 @@ import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ResourceLoaderAware;
-import org.springframework.ui.velocity.VelocityEngineFactory;
import org.springframework.web.context.ServletContextAware;
/**
@@ -69,8 +68,10 @@ import org.springframework.web.context.ServletContextAware;
* @see #setResourceLoaderPath
* @see #setVelocityEngine
* @see VelocityView
+ * @deprecated as of Spring 4.3, in favor of FreeMarker
*/
-public class VelocityConfigurer extends VelocityEngineFactory
+@Deprecated
+public class VelocityConfigurer extends org.springframework.ui.velocity.VelocityEngineFactory
implements VelocityConfig, InitializingBean, ResourceLoaderAware, ServletContextAware {
/** the name of the resource loader for Spring's bind macros */
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityLayoutView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityLayoutView.java
index 65493af1..b5e3ae8a 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityLayoutView.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityLayoutView.java
@@ -50,7 +50,9 @@ import org.springframework.core.NestedIOException;
* @see #setLayoutUrl
* @see #setLayoutKey
* @see #setScreenContentKey
+ * @deprecated as of Spring 4.3, in favor of FreeMarker
*/
+@Deprecated
public class VelocityLayoutView extends VelocityToolboxView {
/**
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityLayoutViewResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityLayoutViewResolver.java
index 02124e0d..630dcc3e 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityLayoutViewResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityLayoutViewResolver.java
@@ -31,7 +31,9 @@ import org.springframework.web.servlet.view.AbstractUrlBasedView;
* @see #setLayoutUrl
* @see #setLayoutKey
* @see #setScreenContentKey
+ * @deprecated as of Spring 4.3, in favor of FreeMarker
*/
+@Deprecated
public class VelocityLayoutViewResolver extends VelocityViewResolver {
private String layoutUrl;
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityToolboxView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityToolboxView.java
index ad54ffda..8513f9d5 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityToolboxView.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityToolboxView.java
@@ -62,7 +62,9 @@ import org.springframework.util.ReflectionUtils;
* @see #initTool
* @see org.apache.velocity.tools.view.context.ViewContext
* @see org.apache.velocity.tools.view.context.ChainedContext
+ * @deprecated as of Spring 4.3, in favor of FreeMarker
*/
+@Deprecated
public class VelocityToolboxView extends VelocityView {
private String toolboxConfigLocation;
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityView.java
index 23751a5d..936ee0d8 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityView.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityView.java
@@ -83,7 +83,9 @@ import org.springframework.web.util.NestedServletException;
* @see #setVelocityEngine
* @see VelocityConfig
* @see VelocityConfigurer
+ * @deprecated as of Spring 4.3, in favor of FreeMarker
*/
+@Deprecated
public class VelocityView extends AbstractTemplateView {
private Map<String, Class<?>> toolAttributes;
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityViewResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityViewResolver.java
index e812ec92..95191a4a 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityViewResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityViewResolver.java
@@ -40,7 +40,9 @@ import org.springframework.web.servlet.view.AbstractUrlBasedView;
* @see #setDateToolAttribute
* @see #setNumberToolAttribute
* @see VelocityView
+ * @deprecated as of Spring 4.3, in favor of FreeMarker
*/
+@Deprecated
public class VelocityViewResolver extends AbstractTemplateViewResolver {
private String dateToolAttribute;
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/xml/MappingJackson2XmlView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/xml/MappingJackson2XmlView.java
index dfcf59e5..ef40ba89 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/xml/MappingJackson2XmlView.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/xml/MappingJackson2XmlView.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,7 +36,7 @@ import org.springframework.web.servlet.view.json.AbstractJackson2View;
*
* <p>The default constructor uses the default configuration provided by {@link Jackson2ObjectMapperBuilder}.
*
- * <p>Compatible with Jackson 2.1 and higher.
+ * <p>Compatible with Jackson 2.6 and higher, as of Spring 4.3.
*
* @author Sebastien Deleuze
* @since 4.1
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/xslt/XsltView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/xslt/XsltView.java
index 35587b78..8b1e1f82 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/xslt/XsltView.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/xslt/XsltView.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -73,7 +73,7 @@ import org.springframework.web.util.WebUtils;
*/
public class XsltView extends AbstractUrlBasedView {
- private Class<?> transformerFactoryClass;
+ private Class<? extends TransformerFactory> transformerFactoryClass;
private String sourceKey;
@@ -97,8 +97,7 @@ public class XsltView extends AbstractUrlBasedView {
* <p>The default constructor of the specified class will be called
* to build the TransformerFactory for this view.
*/
- public void setTransformerFactoryClass(Class<?> transformerFactoryClass) {
- Assert.isAssignable(TransformerFactory.class, transformerFactoryClass);
+ public void setTransformerFactoryClass(Class<? extends TransformerFactory> transformerFactoryClass) {
this.transformerFactoryClass = transformerFactoryClass;
}
@@ -195,10 +194,10 @@ public class XsltView extends AbstractUrlBasedView {
* @see #setTransformerFactoryClass
* @see #getTransformerFactory()
*/
- protected TransformerFactory newTransformerFactory(Class<?> transformerFactoryClass) {
+ protected TransformerFactory newTransformerFactory(Class<? extends TransformerFactory> transformerFactoryClass) {
if (transformerFactoryClass != null) {
try {
- return (TransformerFactory) transformerFactoryClass.newInstance();
+ return transformerFactoryClass.newInstance();
}
catch (Exception ex) {
throw new TransformerFactoryConfigurationError(ex, "Could not instantiate TransformerFactory");
diff --git a/spring-webmvc/src/main/resources/META-INF/spring-form.tld b/spring-webmvc/src/main/resources/META-INF/spring-form.tld
index 872f240b..a85779c5 100644
--- a/spring-webmvc/src/main/resources/META-INF/spring-form.tld
+++ b/spring-webmvc/src/main/resources/META-INF/spring-form.tld
@@ -5,7 +5,7 @@
version="2.0">
<description>Spring Framework JSP Form Tag Library</description>
- <tlib-version>4.0</tlib-version>
+ <tlib-version>4.3</tlib-version>
<short-name>form</short-name>
<uri>http://www.springframework.org/tags/form</uri>
@@ -131,8 +131,7 @@
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
- <description>Name of the model attribute under which the form object is exposed.
- Defaults to 'command'.</description>
+ <description>DEPRECATED: Use "modelAttribute" instead.</description>
<name>commandName</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
diff --git a/spring-webmvc/src/main/resources/META-INF/spring.schemas b/spring-webmvc/src/main/resources/META-INF/spring.schemas
index 41d70bcd..53fa926e 100644
--- a/spring-webmvc/src/main/resources/META-INF/spring.schemas
+++ b/spring-webmvc/src/main/resources/META-INF/spring.schemas
@@ -4,4 +4,5 @@ http\://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd=org/springframewor
http\://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd=org/springframework/web/servlet/config/spring-mvc-4.0.xsd
http\://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd=org/springframework/web/servlet/config/spring-mvc-4.1.xsd
http\://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd=org/springframework/web/servlet/config/spring-mvc-4.2.xsd
-http\://www.springframework.org/schema/mvc/spring-mvc.xsd=org/springframework/web/servlet/config/spring-mvc-4.2.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd=org/springframework/web/servlet/config/spring-mvc-4.3.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc.xsd=org/springframework/web/servlet/config/spring-mvc-4.3.xsd
diff --git a/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-4.3.xsd b/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-4.3.xsd
new file mode 100644
index 00000000..50b90323
--- /dev/null
+++ b/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-4.3.xsd
@@ -0,0 +1,1374 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<xsd:schema xmlns="http://www.springframework.org/schema/mvc"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xmlns:tool="http://www.springframework.org/schema/tool"
+ targetNamespace="http://www.springframework.org/schema/mvc"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified">
+
+ <xsd:import namespace="http://www.springframework.org/schema/beans" schemaLocation="http://www.springframework.org/schema/beans/spring-beans-4.3.xsd"/>
+ <xsd:import namespace="http://www.springframework.org/schema/tool" schemaLocation="http://www.springframework.org/schema/tool/spring-tool-4.3.xsd"/>
+
+ <xsd:element name="annotation-driven">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"><![CDATA[
+ Configures the annotation-driven Spring MVC Controller programming model.
+ Note that this tag works in Web MVC only, not in Portlet MVC!
+
+ See org.springframework.web.servlet.config.annotation.EnableWebMvc javadoc for details
+ on code-based alternatives to enabling annotation-driven Spring MVC support.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:all minOccurs="0">
+ <xsd:element name="path-matching" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Configures the path matching part of the Spring MVC Controller programming model.
+ Like annotation-driven, code-based alternatives are also documented in EnableWebMvc javadoc.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:attribute name="suffix-pattern" type="xsd:boolean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Whether to use suffix pattern match (".*") when matching patterns to requests. If enabled
+ a method mapped to "/users" also matches to "/users.*".
+ The default value is true.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="trailing-slash" type="xsd:boolean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Whether to match to URLs irrespective of the presence of a trailing slash.
+ If enabled a method mapped to "/users" also matches to "/users/".
+ The default value is true.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="registered-suffixes-only" type="xsd:boolean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Whether suffix pattern matching should work only against path extensions
+ explicitly registered when you configure content negotiation.
+ This is generally recommended to reduce ambiguity and to
+ avoid issues such as when a "." appears in the path for other reasons.
+ The default value is false.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="path-helper" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The bean name of the UrlPathHelper to use for resolution of lookup paths.
+ Use this to override the default UrlPathHelper with a custom subclass, or to share common UrlPathHelper settings across
+ multiple HandlerMappings and MethodNameResolvers.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="path-matcher" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The bean name of the PathMatcher implementation to use for matching URL paths against registered URL patterns.
+ Default is AntPathMatcher.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="message-converters" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Configures one or more HttpMessageConverter types to use for converting @RequestBody method parameters and
+ @ResponseBody method return values. Using this configuration element is optional.
+ HttpMessageConverter registrations provided here will take precedence over HttpMessageConverter types registered
+ by default. Also see the register-defaults attribute if you want to turn off default registrations entirely.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element ref="beans:bean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ An HttpMessageConverter bean definition.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element ref="beans:ref">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A reference to an HttpMessageConverter bean.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:sequence>
+ <xsd:attribute name="register-defaults" type="xsd:boolean" default="true">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Whether or not default HttpMessageConverter registrations should be added in addition to the ones provided
+ within this element.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="argument-resolvers" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Configures HandlerMethodArgumentResolver types to support custom controller method argument types.
+ Using this option does not override the built-in support for resolving handler method arguments.
+ To customize the built-in support for argument resolution configure RequestMappingHandlerAdapter directly.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:choice minOccurs="1" maxOccurs="unbounded">
+ <xsd:element ref="beans:bean" minOccurs="0" maxOccurs="unbounded">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The HandlerMethodArgumentResolver (or WebArgumentResolver for backwards compatibility) bean definition.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element ref="beans:ref" minOccurs="0" maxOccurs="unbounded">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A reference to a HandlerMethodArgumentResolver bean definition.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="java:org.springframework.web.method.support.HandlerMethodArgumentResolver"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="return-value-handlers" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Configures HandlerMethodReturnValueHandler types to support custom controller method return value handling.
+ Using this option does not override the built-in support for handling return values.
+ To customize the built-in support for handling return values configure RequestMappingHandlerAdapter directly.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:choice minOccurs="1" maxOccurs="unbounded">
+ <xsd:element ref="beans:bean" minOccurs="0" maxOccurs="unbounded">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The HandlerMethodReturnValueHandler bean definition.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element ref="beans:ref" minOccurs="0" maxOccurs="unbounded">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A reference to a HandlerMethodReturnValueHandler bean definition.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="java:org.springframework.web.method.support.HandlerMethodReturnValueHandler"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="async-support" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Configure options for asynchronous request processing.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:all minOccurs="0">
+ <xsd:element name="callable-interceptors" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The ordered set of interceptors that intercept the lifecycle of concurrently executed
+ requests, which start after a controller returns a java.util.concurrent.Callable.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element ref="beans:bean" minOccurs="1" maxOccurs="unbounded">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Registers a CallableProcessingInterceptor.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="deferred-result-interceptors" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The ordered set of interceptors that intercept the lifecycle of concurrently executed
+ requests, which start after a controller returns a DeferredResult.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element ref="beans:bean" minOccurs="1" maxOccurs="unbounded">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Registers a DeferredResultProcessingInterceptor.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:all>
+ <xsd:attribute name="task-executor" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.core.task.AsyncTaskExecutor"><![CDATA[
+ The bean name of a default AsyncTaskExecutor to use when a controller method returns a {@link Callable}.
+ Controller methods can override this default on a per-request basis by returning an AsyncTask.
+ By default, a SimpleAsyncTaskExecutor is used which does not re-use threads and is not recommended for production.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="java:org.springframework.core.task.AsyncTaskExecutor"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="default-timeout" type="xsd:long">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Specify the amount of time, in milliseconds, before asynchronous request handling times out.
+ In Servlet 3, the timeout begins after the main request processing thread has exited and ends when the request
+ is dispatched again for further processing of the concurrently produced result. If this value is not set,
+ the default timeout of the underlying implementation is used, e.g. 10 seconds on Tomcat with Servlet 3.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:all>
+ <xsd:attribute name="conversion-service" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.core.convert.ConversionService"><![CDATA[
+ The bean name of the ConversionService that is to be used for type conversion during field binding.
+ This attribute is not required, and only needs to be specified if custom converters need to be configured.
+ If not specified, a default FormattingConversionService is registered with converters to/from common value types.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="java:org.springframework.core.convert.ConversionService"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="validator" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.validation.Validator"><![CDATA[
+ The bean name of the Validator that is to be used to validate Controller model objects.
+ This attribute is not required, and only needs to be specified if a custom Validator needs to be configured.
+ If not specified, JSR-303 validation will be installed if a JSR-303 provider is present on the classpath.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="java:org.springframework.validation.Validator"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="content-negotiation-manager" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.web.accept.ContentNegotiationManager"><![CDATA[
+ The bean name of a ContentNegotiationManager that is to be used to determine requested media types.
+ If not specified, a default ContentNegotiationManager is configured that checks the request path extension
+ first and the "Accept" header second where path extensions such as ".json", ".xml", ".atom", and ".rss" are
+ recognized if Jackson, JAXB2, or the Rome libraries are available. As a fallback option, the path extension
+ is also used to perform a lookup through the ServletContext and the Java Activation Framework (if available).
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="java:org.springframework.web.accept.ContentNegotiationManager"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="message-codes-resolver" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The bean name of a MessageCodesResolver to use to build message codes from data binding and validation error codes.
+ This attribute is not required.
+ If not specified the DefaultMessageCodesResolver is used.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="java:org.springframework.validation.MessageCodesResolver"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="enable-matrix-variables" type="xsd:boolean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Matrix variables can appear in any path segment, each matrix variable separated with a ";" (semicolon).
+ For example "/cars;color=red;year=2012". By default, they're removed from the URL. If this property
+ is set to true, matrix variables are not removed from the URL, and the request mapping pattern
+ must use URI variable in path segments where matrix variables are expected. For example "/{cars}".
+ Matrix variables can then be injected into a controller method with @MatrixVariable.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="ignore-default-model-on-redirect" type="xsd:boolean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ By default, the content of the "default" model is used both during rendering and redirect scenarios.
+ Alternatively a controller method can declare a RedirectAttributes argument and use it to provide attributes
+ for a redirect. Setting this flag to true ensures the "default" model is never used in a redirect scenario
+ even if a RedirectAttributes argument is not declared. Setting it to false means the "default" model
+ may be used in a redirect if the controller method doesn't declare a RedirectAttributes argument.
+ The default setting is false but new applications should consider setting it to true.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:complexType name="content-version-strategy">
+ <xsd:annotation>
+ <xsd:documentation source="org.springframework.web.servlet.resource.ContentVersionStrategy"><![CDATA[
+ A VersionStrategy that calculates an Hex MD5 hashes from the content of the resource and appends it
+ to the file name, e.g. "styles/main-e36d2e05253c6c7085a91522ce43a0b4.css".
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="patterns" type="xsd:string" use="required"/>
+ </xsd:complexType>
+
+ <xsd:complexType name="fixed-version-strategy">
+ <xsd:annotation>
+ <xsd:documentation source="org.springframework.web.servlet.resource.FixedVersionStrategy"><![CDATA[
+ A VersionStrategy that relies on a fixed version applied as a request path prefix,
+ e.g. reduced SHA, version name, release date, etc.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="version" type="xsd:string" use="required"/>
+ <xsd:attribute name="patterns" type="xsd:string" use="required"/>
+ </xsd:complexType>
+
+ <xsd:complexType name="resource-version-strategy">
+ <xsd:annotation>
+ <xsd:documentation source="org.springframework.web.servlet.resource.VersionStrategy"><![CDATA[
+ A strategy for extracting and embedding a resource version in its URL path.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice minOccurs="1" maxOccurs="1">
+ <xsd:element ref="beans:bean">
+ <xsd:annotation>
+ <xsd:documentation source="org.springframework.web.servlet.resource.VersionStrategy"><![CDATA[
+ A VersionStrategy bean definition.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element ref="beans:ref">
+ <xsd:annotation>
+ <xsd:documentation source="org.springframework.web.servlet.resource.VersionStrategy"><![CDATA[
+ A reference to a VersionStrategy bean.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:choice>
+ <xsd:attribute name="patterns" type="xsd:string" use="required"/>
+ </xsd:complexType>
+
+ <xsd:complexType name="version-resolver">
+ <xsd:annotation>
+ <xsd:documentation source="org.springframework.web.servlet.resource.VersionResourceResolver"><![CDATA[
+ Resolves request paths containing a version string that can be used as part of an HTTP caching strategy
+ in which a resource is cached with a far future date (e.g. 1 year) and cached until the version,
+ and therefore the URL, is changed.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element type="content-version-strategy" name="content-version-strategy"/>
+ <xsd:element type="fixed-version-strategy" name="fixed-version-strategy"/>
+ <xsd:element type="resource-version-strategy" name="version-strategy"/>
+ </xsd:choice>
+ </xsd:complexType>
+
+ <xsd:complexType name="resource-resolvers">
+ <xsd:annotation>
+ <xsd:documentation source="org.springframework.web.servlet.resource.ResourceResolver"><![CDATA[
+ A list of ResourceResolver beans definition and references.
+ A ResourceResolver provides mechanisms for resolving an incoming request to an actual Resource and for
+ obtaining the public URL path that clients should use when requesting the resource.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element type="version-resolver" name="version-resolver"/>
+ <xsd:element ref="beans:bean">
+ <xsd:annotation>
+ <xsd:documentation source="org.springframework.web.servlet.resource.ResourceResolver"><![CDATA[
+ A ResourceResolver bean definition.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element ref="beans:ref">
+ <xsd:annotation>
+ <xsd:documentation source="org.springframework.web.servlet.resource.ResourceResolver"><![CDATA[
+ A reference to a ResourceResolver bean.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="resource-transformers">
+ <xsd:annotation>
+ <xsd:documentation source="org.springframework.web.servlet.resource.ResourceTransformer"><![CDATA[
+ A list of ResourceTransformer beans definition and references.
+ A ResourceTransformer provides mechanisms for transforming the content of a resource.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element ref="beans:bean">
+ <xsd:annotation>
+ <xsd:documentation source="org.springframework.web.servlet.resource.ResourceTransformer"><![CDATA[
+ A ResourceTransformer bean definition.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element ref="beans:ref">
+ <xsd:annotation>
+ <xsd:documentation source="org.springframework.web.servlet.resource.ResourceTransformer"><![CDATA[
+ A reference to a ResourceTransformer bean.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="resource-chain">
+ <xsd:annotation>
+ <xsd:documentation source="org.springframework.web.servlet.config.annotation.ResourceChainRegistration"><![CDATA[
+ Assists with the registration of resource resolvers and transformers.
+ Unless set to "false", the auto-registration adds default Resolvers (a PathResourceResolver) and Transformers
+ (CssLinkResourceTransformer, if a VersionResourceResolver has been manually registered).
+ The resource-cache attribute sets whether to cache the result of resource resolution/transformation;
+ setting this to "true" is recommended for production (and "false" for development).
+ A custom Cache can be configured if a CacheManager is provided as a bean reference
+ in the "cache-manager" attribute, and the cache name provided in the "cache-name" attribute.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="resolvers" type="resource-resolvers" minOccurs="0" maxOccurs="1"/>
+ <xsd:element name="transformers" type="resource-transformers" minOccurs="0" maxOccurs="1"/>
+ </xsd:sequence>
+ <xsd:attribute name="resource-cache" type="xsd:boolean" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Whether the resource chain should cache resource resolution.
+ Note that the resource content itself won't be cached, but rather Resource instances.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="auto-registration" type="xsd:boolean" default="true" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Whether to register automatically ResourceResolvers and ResourceTransformers.
+ Setting this property to "false" means that it gives developers full control over the registration process.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="cache-manager" type="xsd:string" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The name of the Cache Manager to cache resource resolution. By default, a ConcurrentCacheMap will be used.
+ Since Resources aren't serializable and can be dependent on the application host, one should not use a
+ distributed cache but rather an in-memory cache.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="cache-name" type="xsd:string" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The cache name to use in the configured cache manager.
+ Will use "spring-resource-chain-cache" by default.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+
+ <xsd:complexType name="cache-control">
+ <xsd:annotation>
+ <xsd:documentation source="org.springframework.web.cache.CacheControl"><![CDATA[
+ Generates "Cache-Control" HTTP response headers.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="must-revalidate" type="xsd:boolean" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Adds a "must-revalidate" directive in the Cache-Control header.
+ This indicates that caches should revalidate the cached response when it's become stale.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="no-cache" type="xsd:boolean" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Adds a "no-cache" directive in the Cache-Control header.
+ This indicates that caches should always revalidate cached response with the server.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="no-store" type="xsd:boolean" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Adds a "no-store" directive in the Cache-Control header.
+ This indicates that caches should never cache the response.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="no-transform" type="xsd:boolean" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Adds a "no-transform" directive in the Cache-Control header.
+ This indicates that caches should never transform (i.e. compress, optimize) the response content.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="cache-public" type="xsd:boolean" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Adds a "public" directive in the Cache-Control header.
+ This indicates that any cache MAY store the response.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="cache-private" type="xsd:boolean" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Adds a "private" directive in the Cache-Control header.
+ This indicates that the response is intended for a single user and may not be stored by shared caches.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="proxy-revalidate" type="xsd:boolean" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Adds a "proxy-revalidate" directive in the Cache-Control header.
+ This directive has the same meaning as the "must-revalidate" directive, except it only applies to shared caches.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="max-age" type="xsd:int" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Adds a "max-age" directive in the Cache-Control header.
+ This indicates that the response should be cached for the given number of seconds.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="s-maxage" type="xsd:int" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Adds a "s-maxage" directive in the Cache-Control header.
+ This directive has the same meaning as the "max-age" directive, except it only applies to shared caches.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="stale-while-revalidate" type="xsd:int" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Adds a "stale-while-revalidate" directive in the Cache-Control header.
+ This indicates that caches may serve the response after it becomes stale up to the given number of seconds.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="stale-if-error" type="xsd:int" use="optional">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Adds a "s-maxage" directive in the Cache-Control header.
+ This directive has the same meaning as the "max-age" directive, except it only applies to shared caches.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+
+ <xsd:element name="resources">
+ <xsd:annotation>
+ <xsd:documentation
+ source="java:org.springframework.web.servlet.resource.ResourceHttpRequestHandler"><![CDATA[
+ Configures a handler for serving static resources such as images, js, and, css files with cache headers
+ optimized for efficient loading in a web browser. Allows resources to be served out of any path that is
+ reachable via Spring's Resource handling.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="cache-control" type="cache-control" minOccurs="0" maxOccurs="1"/>
+ <xsd:element name="resource-chain" type="resource-chain" minOccurs="0" maxOccurs="1"/>
+ </xsd:sequence>
+ <xsd:attribute name="mapping" use="required" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The URL mapping pattern within the current Servlet context to use for serving resources from this handler,
+ such as "/resources/**"
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="location" use="required" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The resource location from which to serve static content, specified at a Spring Resource pattern.
+ Each location must point to a valid directory. Multiple locations may be specified as a comma-separated list,
+ and the locations will be checked for a given resource in the order specified. For example, a value of
+ "/, classpath:/META-INF/public-web-resources/" will allow resources to be served both from the web app
+ root and from any JAR on the classpath that contains a /META-INF/public-web-resources/ directory,
+ with resources in the web app root taking precedence.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="cache-period" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation>
+ <![CDATA[
+ Specifies the cache period for the resources served by this resource handler, in seconds.
+ The default is to not send any cache headers but rather to rely on last-modified timestamps only.
+ Set this to 0 in order to send cache headers that prevent caching, or to a positive number of
+ seconds in order to send cache headers with the given max-age value.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="order" type="xsd:token">
+ <xsd:annotation>
+ <xsd:documentation>
+ <![CDATA[
+ Specifies the order of the HandlerMapping for the resource handler.
+ The default order is Ordered.LOWEST_PRECEDENCE - 1.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="default-servlet-handler">
+ <xsd:annotation>
+ <xsd:documentation
+ source="java:org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler"><![CDATA[
+ Configures a handler for serving static resources by forwarding to the Servlet container's default Servlet.
+ Use of this handler allows using a "/" mapping with the DispatcherServlet while still utilizing the Servlet
+ container to serve static resources.
+ This handler will forward all requests to the default Servlet. Therefore it is important that it remains last
+ in the order of all other URL HandlerMappings. That will be the case if you use the "annotation-driven" element
+ or alternatively if you are setting up your customized HandlerMapping instance be sure to set its "order"
+ property to a value lower than that of the DefaultServletHttpRequestHandler, which is Integer.MAX_VALUE.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:attribute name="default-servlet-name" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The name of the default Servlet to forward to for static resource requests. The handler will try to
+ autodetect the container's default Servlet at startup time using a list of known names. If the default
+ Servlet cannot be detected because of using an unknown container or because it has been manually configured,
+ the servlet name must be set explicitly.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="interceptors">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The ordered set of interceptors that intercept HTTP Servlet Requests handled by Controllers.
+ Interceptors allow requests to be pre/post processed before/after handling.
+ Each interceptor must implement the org.springframework.web.servlet.HandlerInterceptor or
+ org.springframework.web.context.request.WebRequestInterceptor interface.
+ The interceptors in this set are automatically detected by every registered HandlerMapping.
+ The URI paths each interceptor applies to are configurable.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:choice>
+ <xsd:element ref="beans:bean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Registers an interceptor that intercepts every request regardless of its URI path..
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element ref="beans:ref">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Registers an interceptor that intercepts every request regardless of its URI path..
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:choice>
+ <xsd:element name="interceptor">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.web.servlet.handler.MappedInterceptor"><![CDATA[
+ Registers an interceptor that interceptors requests sent to one or more URI paths.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="mapping" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:attribute name="path" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A path into the application intercepted by this interceptor.
+ Exact path mapping URIs (such as "/myPath") are supported as well as Ant-stype path patterns (such as /myPath/**).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="exclude-mapping" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:attribute name="path" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A path into the application that should not be intercepted by this interceptor.
+ Exact path mapping URIs (such as "/admin") are supported as well as Ant-stype path patterns (such as /admin/**).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:choice>
+ <xsd:element ref="beans:bean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The interceptor's bean definition.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element ref="beans:ref">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A reference to an interceptor bean.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ <xsd:attribute name="path-matcher" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.util.PathMatcher"><![CDATA[
+ The bean name of a PathMatcher implementation to use with nested interceptors. This is an optional,
+ advanced property required only if using custom PathMatcher implementations that support mapping
+ metadata other than the Ant path patterns supported by default.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="java:org.springframework.util.PathMatcher"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="view-controller">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.web.servlet.mvc.ParameterizableViewController"><![CDATA[
+ Map a simple (logic-less) view controller to a specific URL path (or pattern)
+ in order to render a response with a pre-configured status code and view.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:attribute name="path" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The URL path (or pattern) the controller is mapped to.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="view-name" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Set the view name to return. Optional.
+
+ If not specified, the view controller will return null as the
+ view name in which case the configured RequestToViewNameTranslator
+ will select the view name. The DefaultRequestToViewNameTranslator
+ for example translates "/foo/bar" to "foo/bar".
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="status-code" type="xsd:int">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Set the status code to set on the response. Optional.
+ If not set the response status will be 200 (OK).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="redirect-view-controller">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.web.servlet.mvc.ParameterizableViewController"><![CDATA[
+ Map a simple (logic-less) view controller to the given URL path (or pattern)
+ in order to redirect to another URL.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:attribute name="path" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The URL path (or pattern) the controller is mapped to.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="redirect-url" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ By default, the redirect URL is expected to be relative to the current ServletContext,
+ i.e. as relative to the web application root.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="status-code" type="xsd:int">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Set the specific redirect 3xx status code to use.
+
+ If not set, org.springframework.web.servlet.view.RedirectView
+ will select MOVED_TEMPORARILY (302) by default.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="context-relative" type="xsd:boolean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Whether to interpret a given redirect URL that starts with a slash ("/")
+ as relative to the current ServletContext, i.e. as relative to the web
+ application root. The default is "true".
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="keep-query-params" type="xsd:boolean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Whether to propagate the query parameters of the current request through
+ to the target redirect URL. The default is "false".
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="status-controller">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.web.servlet.mvc.ParameterizableViewController"><![CDATA[
+ Map a simple (logic-less) controller to the given URL path (or pattern) in order to
+ sets the response status to the given code without rendering a body.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:attribute name="path" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The URL path (or pattern) the controller is mapped to.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="status-code" type="xsd:int" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The status code to set on the response.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:complexType name="contentNegotiationType">
+ <xsd:all>
+ <xsd:element name="default-views" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element ref="beans:bean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A bean definition for an org.springframework.web.servlet.View class.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element ref="beans:ref">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A reference to a bean for an org.springframework.web.servlet.View class.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:all>
+ <xsd:attribute name="use-not-acceptable" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Indicate whether a 406 Not Acceptable status code should be returned if no suitable
+ view can be found.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+
+ <xsd:complexType name="urlViewResolverType">
+ <xsd:attribute name="prefix" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The prefix that gets prepended to view names when building a URL.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="suffix" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The suffix that gets appended to view names when building a URL.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="cache-views" type="xsd:boolean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Enable or disable thew caching of resolved views.
+ Default is "true": caching is enabled.
+ Disable this only for debugging and development.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="view-class" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The view class that should be used to create views.
+ Configure this if you want to provide a custom View implementation, typically a ub-class of the expected View type.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="java:java.lang.Class"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="view-names" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Set the view names (or name patterns) that can be handled by this view resolver.
+ View names can contain simple wildcards such that 'my*', '*Report' and '*Repo*'
+ will all match the view name 'myReport'.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+
+ <xsd:element name="view-resolvers">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Configure a chain of ViewResolver instances to resolve view names returned from
+ controllers into actual view instances to use for rendering.
+
+ All registered resolvers are wrapped in a single (composite) ViewResolver
+ with its order property set to 0 so that other external resolvers may be ordered
+ before or after it.
+
+ When content negotiation is enabled the order property is set to highest priority
+ instead with the ContentNegotiatingViewResolver encapsulating all other registered
+ view resolver instances. That way the resolvers registered through the MVC namespace
+ form self-encapsulated resolver chain.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:choice minOccurs="1" maxOccurs="unbounded">
+ <xsd:element name="content-negotiation" type="contentNegotiationType">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Registers a ContentNegotiatingViewResolver with the list of all other registered
+ ViewResolver instances used to set its "viewResolvers" property. See the javadoc
+ of ContentNegotiatingViewResolver for more details.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="jsp" type="urlViewResolverType">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Register an InternalResourceViewResolver bean for JSP rendering.
+ By default, "/WEB-INF/" is registered as a view name prefix and ".jsp" as a suffix.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="tiles" type="urlViewResolverType">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Register a TilesViewResolver based on Tiles 3.x.
+ To configure Tiles you must also add a top-level <mvc:tiles-configurer> element
+ or declare a TilesConfigurer bean.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="freemarker" type="urlViewResolverType">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Register a FreeMarkerViewResolver.
+ By default, ".ftl" is configured as a view name suffix.
+ To configure FreeMarker you must also add a top-level <mvc:freemarker-configurer> element
+ or declare a FreeMarkerConfigurer bean.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="groovy" type="urlViewResolverType">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Register a GroovyMarkupViewResolver.
+ By default, ".tpl" is configured as a view name suffix.
+ To configure the Groovy markup template engine you must also add a top-level <mvc:groovy-configurer> element
+ or declare a GroovyMarkupConfigurer bean.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="script-template" type="urlViewResolverType">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Register a ScriptTemplateViewResolver.
+ To configure the Script engine you must also add a top-level <mvc:script-template-configurer> element
+ or declare a ScriptTemplateConfigurer bean.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="bean-name" maxOccurs="1">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Register a BeanNameViewResolver bean.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element ref="beans:bean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Register a ViewResolver as a direct bean declaration.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element ref="beans:ref">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Register a ViewResolver through references to an existing bean declaration.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:choice>
+ <xsd:attribute name="order" type="xsd:int">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ ViewResolver's registered through this element are encapsulated in an
+ instance of org.springframework.web.servlet.view.ViewResolverComposite and
+ follow the order of registration. This attribute determines the order of the
+ ViewResolverComposite itself relative to any additional ViewResolver's
+ (not registered through this element) present in the Spring configuration
+ By default this property is not set, which means the resolver is ordered at
+ Ordered.LOWEST_PRECEDENCE unless content negotiation is enabled in which case
+ the order (if not set explicitly) is changed to Ordered.HIGHEST_PRECEDENCE.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="tiles-configurer">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Configure Tiles 3.x by registering a TilesConfigurer bean.
+ This is a shortcut alternative to declaring a TilesConfigurer bean directly.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="definitions" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:attribute name="location" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The location of a file containing Tiles definitions (or a Spring resource pattern).
+ If no Tiles definitions are registerd, then "/WEB-INF/tiles.xml" is expected to exists.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="check-refresh" type="xsd:boolean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Whether to check Tiles definition files for a refresh at runtime.
+ Default is "false".
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="validate-definitions" type="xsd:boolean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Whether to validate the Tiles XML definitions. Default is "true".
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="definitions-factory" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The Tiles DefinitionsFactory class to use. Default is Tiles' default.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="preparer-factory" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The Tiles PreparerFactory class to use. Default is Tiles' default.
+ Consider "org.springframework.web.servlet.view.tiles3.SimpleSpringPreparerFactory" or
+ "org.springframework.web.servlet.view.tiles3.SpringBeanPreparerFactory" (see javadoc).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="freemarker-configurer">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Configure FreeMarker for view resolution by registering a FreeMarkerConfigurer bean.
+ This is a shortcut alternative to declaring a FreeMarkerConfigurer bean directly.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="template-loader-path" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:attribute name="location" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The location of a FreeMarker template loader path (or a Spring resource pattern).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="groovy-configurer">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Configure the Groovy markup template engine for view resolution by registering a GroovyMarkupConfigurer bean.
+ This is a shortcut alternative to declaring a GroovyMarkupConfigurer bean directly.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:attribute name="auto-indent" type="xsd:boolean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Whether you want the template engine to render indents automatically.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="cache-templates" type="xsd:boolean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ If enabled templates are compiled once for each source (URL or File).
+ It is recommended to keep this flag to true unless you are in development mode
+ and want automatic reloading of templates.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="resource-loader-path" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The Groovy markup template engine resource loader path via a Spring resource location.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="script-template-configurer">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Configure the script engine for view resolution by registering a ScriptTemplateConfigurer
+ bean. This is a shortcut alternative to declaring a ScriptTemplateConfigurer bean directly.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="script" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:attribute name="location" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The location of the script to be loaded by the script engine (library or user provided).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="engine-name" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The script engine name to use by the view.
+ The script engine must implement Invocable.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="render-object" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The object where belong the render function.
+ For example, in order to call Mustache.render(), renderObject
+ should be set to Mustache and renderFunction to render.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="render-function" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Set the render function name.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="content-type" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Set the content type to use for the response (text/html by default).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="charset" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Set the charset used to read script and template files (UTF-8 by default).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="resource-loader-path" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The script engine resource loader path via a Spring resource location.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="shared-engine" type="xsd:boolean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ When set to false, use thread-local ScriptEngine instances instead of one single shared
+ instance. This flag should be set to false for those using non thread-safe script engines
+ with templating libraries not designed for concurrency, like Handlebars or React
+ running on Nashorn for example. In this case, Java 8u60 or greater is required due to
+ this bug: https://bugs.openjdk.java.net/browse/JDK-8076099.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="cors">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Configure cross origin requests processing.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="mapping" minOccurs="1" maxOccurs="unbounded">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Enable cross origin requests processing on the specified path pattern.
+ By default, all origins, GET HEAD POST methods, all headers and credentials
+ are allowed and max age is set to 30 minutes.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:attribute name="path" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A path into the application that should handle CORS requests.
+ Exact path mapping URIs (such as "/admin") are supported as well as Ant-stype path patterns (such as /admin/**).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="allowed-origins" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Comma-separated list of origins to allow, e.g. "http://domain1.com, http://domain2.com".
+ The special value "*" allows all domains (default).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="allowed-methods" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Comma-separated list of HTTP methods to allow, e.g. "GET, POST".
+ The special value "*" allows all method.
+ By default GET, HEAD and POST methods are allowed.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="allowed-headers" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Comma-separated list of headers that a pre-flight request can list as allowed for use during an actual request.
+ The special value of "*" allows actual requests to send any header (default).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="exposed-headers" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Comma-separated list of response headers other than simple headers (i.e.
+ Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, Pragma) that an
+ actual response might have and can be exposed.
+ Empty by default.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="allow-credentials" type="xsd:boolean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Whether user credentials are supported (true by default).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="max-age" type="xsd:long">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ How long, in seconds, the response from a pre-flight request can be cached by clients.
+ 1800 seconds (30 minutes) by default.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+
+</xsd:schema>
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/DispatcherServletTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/DispatcherServletTests.java
index d5cb10f7..3c5f9632 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/DispatcherServletTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/DispatcherServletTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -862,6 +862,7 @@ public class DispatcherServletTests {
MockHttpServletRequest request = new MockHttpServletRequest(getServletContext(), "OPTIONS", "/foo");
MockHttpServletResponse response = spy(new MockHttpServletResponse());
DispatcherServlet servlet = new DispatcherServlet();
+ servlet.setDispatchOptionsRequest(false);
servlet.service(request, response);
verify(response, never()).getHeader(anyString()); // SPR-10341
assertThat(response.getHeader("Allow"), equalTo("GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH"));
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParserTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParserTests.java
index 322acd67..0b48995a 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParserTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParserTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +25,7 @@ import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.core.MethodParameter;
import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.ResourceHttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
@@ -62,17 +63,18 @@ public class AnnotationDrivenBeanDefinitionParserTests {
@Before
public void setup() {
- appContext = new GenericWebApplicationContext();
+ this.appContext = new GenericWebApplicationContext();
}
@Test
public void testMessageCodesResolver() {
loadBeanDefinitions("mvc-config-message-codes-resolver.xml");
- RequestMappingHandlerAdapter adapter = appContext.getBean(RequestMappingHandlerAdapter.class);
+ RequestMappingHandlerAdapter adapter = this.appContext.getBean(RequestMappingHandlerAdapter.class);
assertNotNull(adapter);
Object initializer = adapter.getWebBindingInitializer();
assertNotNull(initializer);
- MessageCodesResolver resolver = ((ConfigurableWebBindingInitializer) initializer).getMessageCodesResolver();
+ MessageCodesResolver resolver =
+ ((ConfigurableWebBindingInitializer) initializer).getMessageCodesResolver();
assertNotNull(resolver);
assertEquals(TestMessageCodesResolver.class, resolver.getClass());
assertEquals(false, new DirectFieldAccessor(adapter).getPropertyValue("ignoreDefaultModelOnRedirect"));
@@ -81,7 +83,7 @@ public class AnnotationDrivenBeanDefinitionParserTests {
@Test
public void testPathMatchingConfiguration() {
loadBeanDefinitions("mvc-config-path-matching.xml");
- RequestMappingHandlerMapping hm = appContext.getBean(RequestMappingHandlerMapping.class);
+ RequestMappingHandlerMapping hm = this.appContext.getBean(RequestMappingHandlerMapping.class);
assertNotNull(hm);
assertTrue(hm.useSuffixPatternMatch());
assertFalse(hm.useTrailingSlashMatch());
@@ -96,28 +98,32 @@ public class AnnotationDrivenBeanDefinitionParserTests {
@Test
public void testMessageConverters() {
loadBeanDefinitions("mvc-config-message-converters.xml");
- verifyMessageConverters(appContext.getBean(RequestMappingHandlerAdapter.class), true);
- verifyMessageConverters(appContext.getBean(ExceptionHandlerExceptionResolver.class), true);
- verifyRequestResponseBodyAdvice(appContext.getBean(RequestMappingHandlerAdapter.class));
- verifyResponseBodyAdvice(appContext.getBean(ExceptionHandlerExceptionResolver.class));
+ verifyMessageConverters(this.appContext.getBean(RequestMappingHandlerAdapter.class), true);
+ verifyMessageConverters(this.appContext.getBean(ExceptionHandlerExceptionResolver.class), true);
+ verifyRequestResponseBodyAdvice(this.appContext.getBean(RequestMappingHandlerAdapter.class));
+ verifyResponseBodyAdvice(this.appContext.getBean(ExceptionHandlerExceptionResolver.class));
}
@Test
public void testMessageConvertersWithoutDefaultRegistrations() {
loadBeanDefinitions("mvc-config-message-converters-defaults-off.xml");
- verifyMessageConverters(appContext.getBean(RequestMappingHandlerAdapter.class), false);
- verifyMessageConverters(appContext.getBean(ExceptionHandlerExceptionResolver.class), false);
+ verifyMessageConverters(this.appContext.getBean(RequestMappingHandlerAdapter.class), false);
+ verifyMessageConverters(this.appContext.getBean(ExceptionHandlerExceptionResolver.class), false);
}
- @SuppressWarnings("unchecked")
@Test
public void testArgumentResolvers() {
loadBeanDefinitions("mvc-config-argument-resolvers.xml");
- RequestMappingHandlerAdapter adapter = appContext.getBean(RequestMappingHandlerAdapter.class);
- assertNotNull(adapter);
- Object value = new DirectFieldAccessor(adapter).getPropertyValue("customArgumentResolvers");
+ testArgumentResolvers(this.appContext.getBean(RequestMappingHandlerAdapter.class));
+ testArgumentResolvers(this.appContext.getBean(ExceptionHandlerExceptionResolver.class));
+ }
+
+ private void testArgumentResolvers(Object bean) {
+ assertNotNull(bean);
+ Object value = new DirectFieldAccessor(bean).getPropertyValue("customArgumentResolvers");
assertNotNull(value);
assertTrue(value instanceof List);
+ @SuppressWarnings("unchecked")
List<HandlerMethodArgumentResolver> resolvers = (List<HandlerMethodArgumentResolver>) value;
assertEquals(3, resolvers.size());
assertTrue(resolvers.get(0) instanceof ServletWebArgumentResolverAdapter);
@@ -126,15 +132,19 @@ public class AnnotationDrivenBeanDefinitionParserTests {
assertNotSame(resolvers.get(1), resolvers.get(2));
}
- @SuppressWarnings("unchecked")
@Test
public void testReturnValueHandlers() {
loadBeanDefinitions("mvc-config-return-value-handlers.xml");
- RequestMappingHandlerAdapter adapter = appContext.getBean(RequestMappingHandlerAdapter.class);
- assertNotNull(adapter);
- Object value = new DirectFieldAccessor(adapter).getPropertyValue("customReturnValueHandlers");
+ testReturnValueHandlers(this.appContext.getBean(RequestMappingHandlerAdapter.class));
+ testReturnValueHandlers(this.appContext.getBean(ExceptionHandlerExceptionResolver.class));
+ }
+
+ private void testReturnValueHandlers(Object bean) {
+ assertNotNull(bean);
+ Object value = new DirectFieldAccessor(bean).getPropertyValue("customReturnValueHandlers");
assertNotNull(value);
assertTrue(value instanceof List);
+ @SuppressWarnings("unchecked")
List<HandlerMethodReturnValueHandler> handlers = (List<HandlerMethodReturnValueHandler>) value;
assertEquals(2, handlers.size());
assertEquals(TestHandlerMethodReturnValueHandler.class, handlers.get(0).getClass());
@@ -145,16 +155,16 @@ public class AnnotationDrivenBeanDefinitionParserTests {
@Test
public void beanNameUrlHandlerMapping() {
loadBeanDefinitions("mvc-config.xml");
- BeanNameUrlHandlerMapping mapping = appContext.getBean(BeanNameUrlHandlerMapping.class);
+ BeanNameUrlHandlerMapping mapping = this.appContext.getBean(BeanNameUrlHandlerMapping.class);
assertNotNull(mapping);
assertEquals(2, mapping.getOrder());
}
private void loadBeanDefinitions(String fileName) {
- XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(appContext);
- ClassPathResource resource = new ClassPathResource(fileName, AnnotationDrivenBeanDefinitionParserTests.class);
+ XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this.appContext);
+ Resource resource = new ClassPathResource(fileName, AnnotationDrivenBeanDefinitionParserTests.class);
reader.loadBeanDefinitions(resource);
- appContext.refresh();
+ this.appContext.refresh();
}
@SuppressWarnings("unchecked")
@@ -165,9 +175,10 @@ public class AnnotationDrivenBeanDefinitionParserTests {
assertTrue(value instanceof List);
List<HttpMessageConverter<?>> converters = (List<HttpMessageConverter<?>>) value;
if (hasDefaultRegistrations) {
- assertTrue("Default converters are registered in addition to custom ones", converters.size() > 2);
- } else {
- assertTrue("Default converters should not be registered", converters.size() == 2);
+ assertTrue("Default and custom converter expected", converters.size() > 2);
+ }
+ else {
+ assertTrue("Only custom converters expected", converters.size() == 2);
}
assertTrue(converters.get(0) instanceof StringHttpMessageConverter);
assertTrue(converters.get(1) instanceof ResourceHttpMessageConverter);
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java
index 8e3c347d..859bf4eb 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java
@@ -31,6 +31,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
import javax.servlet.RequestDispatcher;
import javax.validation.constraints.NotNull;
@@ -145,6 +146,7 @@ import org.springframework.web.servlet.view.velocity.VelocityViewResolver;
import org.springframework.web.util.UrlPathHelper;
import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.*;
/**
@@ -343,17 +345,21 @@ public class MvcNamespaceTests {
@Test
public void testResources() throws Exception {
- loadBeanDefinitions("mvc-config-resources.xml", 10);
+ loadBeanDefinitions("mvc-config-resources.xml", 20);
HttpRequestHandlerAdapter adapter = appContext.getBean(HttpRequestHandlerAdapter.class);
assertNotNull(adapter);
+ RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
+ ContentNegotiationManager manager = mapping.getContentNegotiationManager();
+
ResourceHttpRequestHandler handler = appContext.getBean(ResourceHttpRequestHandler.class);
assertNotNull(handler);
+ assertSame(manager, handler.getContentNegotiationManager());
- SimpleUrlHandlerMapping mapping = appContext.getBean(SimpleUrlHandlerMapping.class);
- assertNotNull(mapping);
- assertEquals(Ordered.LOWEST_PRECEDENCE - 1, mapping.getOrder());
+ SimpleUrlHandlerMapping resourceMapping = appContext.getBean(SimpleUrlHandlerMapping.class);
+ assertNotNull(resourceMapping);
+ assertEquals(Ordered.LOWEST_PRECEDENCE - 1, resourceMapping.getOrder());
BeanNameUrlHandlerMapping beanNameMapping = appContext.getBean(BeanNameUrlHandlerMapping.class);
assertNotNull(beanNameMapping);
@@ -362,15 +368,19 @@ public class MvcNamespaceTests {
ResourceUrlProvider urlProvider = appContext.getBean(ResourceUrlProvider.class);
assertNotNull(urlProvider);
- MappedInterceptor mappedInterceptor = appContext.getBean(MappedInterceptor.class);
- assertNotNull(urlProvider);
- assertEquals(ResourceUrlProviderExposingInterceptor.class, mappedInterceptor.getInterceptor().getClass());
+ Map<String, MappedInterceptor> beans = appContext.getBeansOfType(MappedInterceptor.class);
+ List<Class<?>> interceptors = beans.values().stream()
+ .map(mappedInterceptor -> mappedInterceptor.getInterceptor().getClass())
+ .collect(Collectors.toList());
+ assertThat(interceptors, containsInAnyOrder(ConversionServiceExposingInterceptor.class,
+ ResourceUrlProviderExposingInterceptor.class));
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("/resources/foo.css");
request.setMethod("GET");
- HandlerExecutionChain chain = mapping.getHandler(request);
+ HandlerExecutionChain chain = resourceMapping.getHandler(request);
+ assertNotNull(chain);
assertTrue(chain.getHandler() instanceof ResourceHttpRequestHandler);
MockHttpServletResponse response = new MockHttpServletResponse();
@@ -842,6 +852,7 @@ public class MvcNamespaceTests {
ContentNegotiationManager manager = (ContentNegotiationManager) accessor.getPropertyValue(beanName);
assertNotNull(manager);
assertSame(manager, this.appContext.getBean(ContentNegotiationManager.class));
+ assertSame(manager, this.appContext.getBean("mvcContentNegotiationManager"));
}
@Test
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/InterceptorRegistryTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/InterceptorRegistryTests.java
index e927d94e..4e49bc9c 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/InterceptorRegistryTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/InterceptorRegistryTests.java
@@ -90,7 +90,7 @@ public class InterceptorRegistryTests {
this.registry.addInterceptor(this.interceptor1).addPathPatterns("/path1/**").excludePathPatterns("/path1/secret");
this.registry.addInterceptor(this.interceptor2).addPathPatterns("/path2");
- assertEquals(Arrays.asList(this.interceptor1), getInterceptorsForPath("/path1"));
+ assertEquals(Arrays.asList(this.interceptor1), getInterceptorsForPath("/path1/test"));
assertEquals(Arrays.asList(this.interceptor2), getInterceptorsForPath("/path2"));
assertEquals(Collections.emptyList(), getInterceptorsForPath("/path1/secret"));
}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistryTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistryTests.java
index 1530dc77..66965ed5 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistryTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistryTests.java
@@ -180,6 +180,7 @@ public class ResourceHandlerRegistryTests {
public void resourceChainWithOverrides() throws Exception {
CachingResourceResolver cachingResolver = Mockito.mock(CachingResourceResolver.class);
VersionResourceResolver versionResolver = Mockito.mock(VersionResourceResolver.class);
+ WebJarsResourceResolver webjarsResolver = Mockito.mock(WebJarsResourceResolver.class);
PathResourceResolver pathResourceResolver = new PathResourceResolver();
CachingResourceTransformer cachingTransformer = Mockito.mock(CachingResourceTransformer.class);
AppCacheManifestTransformer appCacheTransformer = Mockito.mock(AppCacheManifestTransformer.class);
@@ -189,6 +190,7 @@ public class ResourceHandlerRegistryTests {
.resourceChain(false)
.addResolver(cachingResolver)
.addResolver(versionResolver)
+ .addResolver(webjarsResolver)
.addResolver(pathResourceResolver)
.addTransformer(cachingTransformer)
.addTransformer(appCacheTransformer)
@@ -196,10 +198,11 @@ public class ResourceHandlerRegistryTests {
ResourceHttpRequestHandler handler = getHandler("/resources/**");
List<ResourceResolver> resolvers = handler.getResourceResolvers();
- assertThat(resolvers.toString(), resolvers, Matchers.hasSize(3));
+ assertThat(resolvers.toString(), resolvers, Matchers.hasSize(4));
assertThat(resolvers.get(0), Matchers.sameInstance(cachingResolver));
assertThat(resolvers.get(1), Matchers.sameInstance(versionResolver));
- assertThat(resolvers.get(2), Matchers.sameInstance(pathResourceResolver));
+ assertThat(resolvers.get(2), Matchers.sameInstance(webjarsResolver));
+ assertThat(resolvers.get(3), Matchers.sameInstance(pathResourceResolver));
List<ResourceTransformer> transformers = handler.getResourceTransformers();
assertThat(transformers, Matchers.hasSize(3));
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ViewResolutionIntegrationTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ViewResolutionIntegrationTests.java
index 5bb2493f..2bfd1fd3 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ViewResolutionIntegrationTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ViewResolutionIntegrationTests.java
@@ -257,9 +257,7 @@ public class ViewResolutionIntegrationTests {
@Bean
public FreeMarkerViewResolver freeMarkerViewResolver() {
- FreeMarkerViewResolver viewResolver = new FreeMarkerViewResolver();
- viewResolver.setSuffix(".ftl");
- return viewResolver;
+ return new FreeMarkerViewResolver("", ".ftl");
}
@Bean
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ViewResolverRegistryTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ViewResolverRegistryTests.java
index fdbf8874..33eca3c0 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ViewResolverRegistryTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ViewResolverRegistryTests.java
@@ -99,9 +99,7 @@ public class ViewResolverRegistryTests {
@Test
public void customViewResolver() {
- InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
- viewResolver.setPrefix("/");
- viewResolver.setSuffix(".jsp");
+ InternalResourceViewResolver viewResolver = new InternalResourceViewResolver("/", ".jsp");
this.registry.viewResolver(viewResolver);
assertSame(viewResolver, this.registry.getViewResolvers().get(0));
}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java
index 9a6bc3cd..8116a9fd 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,12 +17,12 @@
package org.springframework.web.servlet.config.annotation;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
-import com.fasterxml.jackson.databind.DeserializationFeature;
-import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
+
import org.junit.Before;
import org.junit.Test;
@@ -69,9 +69,12 @@ import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor;
import org.springframework.web.servlet.handler.HandlerExceptionResolverComposite;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
+import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
+import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
import org.springframework.web.servlet.resource.ResourceUrlProviderExposingInterceptor;
import org.springframework.web.servlet.view.ContentNegotiatingViewResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
@@ -79,7 +82,15 @@ import org.springframework.web.servlet.view.ViewResolverComposite;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
import org.springframework.web.util.UrlPathHelper;
-import static org.junit.Assert.*;
+import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
+import static com.fasterxml.jackson.databind.MapperFeature.DEFAULT_VIEW_INCLUSION;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.springframework.http.MediaType.APPLICATION_ATOM_XML;
+import static org.springframework.http.MediaType.APPLICATION_JSON;
+import static org.springframework.http.MediaType.APPLICATION_XML;
/**
* A test fixture with a sub-class of {@link WebMvcConfigurationSupport} that also
@@ -119,6 +130,7 @@ public class WebMvcConfigurationSupportExtensionTests {
assertEquals(TestPathHelper.class, rmHandlerMapping.getUrlPathHelper().getClass());
assertEquals(TestPathMatcher.class, rmHandlerMapping.getPathMatcher().getClass());
HandlerExecutionChain chain = rmHandlerMapping.getHandler(new MockHttpServletRequest("GET", "/"));
+ assertNotNull(chain);
assertNotNull(chain.getInterceptors());
assertEquals(3, chain.getInterceptors().length);
assertEquals(LocaleChangeInterceptor.class, chain.getInterceptors()[0].getClass());
@@ -132,10 +144,13 @@ public class WebMvcConfigurationSupportExtensionTests {
assertEquals(TestPathHelper.class, handlerMapping.getUrlPathHelper().getClass());
assertEquals(TestPathMatcher.class, handlerMapping.getPathMatcher().getClass());
chain = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/path"));
+ assertNotNull(chain);
assertNotNull(chain.getHandler());
chain = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/bad"));
+ assertNotNull(chain);
assertNotNull(chain.getHandler());
chain = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/old"));
+ assertNotNull(chain);
assertNotNull(chain.getHandler());
handlerMapping = (AbstractHandlerMapping) this.config.resourceHandlerMapping();
@@ -145,6 +160,7 @@ public class WebMvcConfigurationSupportExtensionTests {
assertEquals(TestPathHelper.class, handlerMapping.getUrlPathHelper().getClass());
assertEquals(TestPathMatcher.class, handlerMapping.getPathMatcher().getClass());
chain = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/resources/foo.gif"));
+ assertNotNull(chain);
assertNotNull(chain.getHandler());
assertEquals(Arrays.toString(chain.getInterceptors()), 2, chain.getInterceptors().length);
// PathExposingHandlerInterceptor at chain.getInterceptors()[0]
@@ -155,6 +171,7 @@ public class WebMvcConfigurationSupportExtensionTests {
assertNotNull(handlerMapping);
assertEquals(Integer.MAX_VALUE, handlerMapping.getOrder());
chain = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/anyPath"));
+ assertNotNull(chain);
assertNotNull(chain.getHandler());
}
@@ -168,13 +185,14 @@ public class WebMvcConfigurationSupportExtensionTests {
assertEquals("converted", actual);
// Message converters
- assertEquals(2, adapter.getMessageConverters().size());
- assertEquals(StringHttpMessageConverter.class, adapter.getMessageConverters().get(0).getClass());
- assertEquals(MappingJackson2HttpMessageConverter.class, adapter.getMessageConverters().get(1).getClass());
- ObjectMapper objectMapper = ((MappingJackson2HttpMessageConverter)adapter.getMessageConverters().get(1)).getObjectMapper();
- assertFalse(objectMapper.getDeserializationConfig().isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION));
- assertFalse(objectMapper.getSerializationConfig().isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION));
- assertFalse(objectMapper.getDeserializationConfig().isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES));
+ List<HttpMessageConverter<?>> converters = adapter.getMessageConverters();
+ assertEquals(2, converters.size());
+ assertEquals(StringHttpMessageConverter.class, converters.get(0).getClass());
+ assertEquals(MappingJackson2HttpMessageConverter.class, converters.get(1).getClass());
+ ObjectMapper objectMapper = ((MappingJackson2HttpMessageConverter) converters.get(1)).getObjectMapper();
+ assertFalse(objectMapper.getDeserializationConfig().isEnabled(DEFAULT_VIEW_INCLUSION));
+ assertFalse(objectMapper.getSerializationConfig().isEnabled(DEFAULT_VIEW_INCLUSION));
+ assertFalse(objectMapper.getDeserializationConfig().isEnabled(FAIL_ON_UNKNOWN_PROPERTIES));
DirectFieldAccessor fieldAccessor = new DirectFieldAccessor(adapter);
@@ -222,27 +240,42 @@ public class WebMvcConfigurationSupportExtensionTests {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo.json");
NativeWebRequest webRequest = new ServletWebRequest(request);
- ContentNegotiationManager manager = this.config.requestMappingHandlerMapping().getContentNegotiationManager();
- assertEquals(Arrays.asList(MediaType.APPLICATION_JSON), manager.resolveMediaTypes(webRequest));
+ RequestMappingHandlerMapping mapping = this.config.requestMappingHandlerMapping();
+ ContentNegotiationManager manager = mapping.getContentNegotiationManager();
+ assertEquals(Collections.singletonList(APPLICATION_JSON), manager.resolveMediaTypes(webRequest));
request.setRequestURI("/foo.xml");
- assertEquals(Arrays.asList(MediaType.APPLICATION_XML), manager.resolveMediaTypes(webRequest));
+ assertEquals(Collections.singletonList(APPLICATION_XML), manager.resolveMediaTypes(webRequest));
request.setRequestURI("/foo.rss");
- assertEquals(Arrays.asList(MediaType.valueOf("application/rss+xml")), manager.resolveMediaTypes(webRequest));
+ assertEquals(Collections.singletonList(MediaType.valueOf("application/rss+xml")),
+ manager.resolveMediaTypes(webRequest));
request.setRequestURI("/foo.atom");
- assertEquals(Arrays.asList(MediaType.APPLICATION_ATOM_XML), manager.resolveMediaTypes(webRequest));
+ assertEquals(Collections.singletonList(APPLICATION_ATOM_XML), manager.resolveMediaTypes(webRequest));
request.setRequestURI("/foo");
request.setParameter("f", "json");
- assertEquals(Arrays.asList(MediaType.APPLICATION_JSON), manager.resolveMediaTypes(webRequest));
+ assertEquals(Collections.singletonList(APPLICATION_JSON), manager.resolveMediaTypes(webRequest));
+
+ request.setRequestURI("/resources/foo.gif");
+ SimpleUrlHandlerMapping handlerMapping = (SimpleUrlHandlerMapping) this.config.resourceHandlerMapping();
+ handlerMapping.setApplicationContext(this.context);
+ HandlerExecutionChain chain = handlerMapping.getHandler(request);
+ assertNotNull(chain);
+ ResourceHttpRequestHandler handler = (ResourceHttpRequestHandler) chain.getHandler();
+ assertNotNull(handler);
+ assertSame(manager, handler.getContentNegotiationManager());
}
@Test
public void exceptionResolvers() throws Exception {
- HandlerExceptionResolver exceptionResolver = this.config.handlerExceptionResolver();
- assertEquals(1, ((HandlerExceptionResolverComposite) exceptionResolver).getExceptionResolvers().size());
+ List<HandlerExceptionResolver> resolvers = ((HandlerExceptionResolverComposite)
+ this.config.handlerExceptionResolver()).getExceptionResolvers();
+
+ assertEquals(2, resolvers.size());
+ assertEquals(ResponseStatusExceptionResolver.class, resolvers.get(0).getClass());
+ assertEquals(SimpleMappingExceptionResolver.class, resolvers.get(1).getClass());
}
@SuppressWarnings("unchecked")
@@ -359,6 +392,11 @@ public class WebMvcConfigurationSupportExtensionTests {
}
@Override
+ public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
+ exceptionResolvers.add(0, new ResponseStatusExceptionResolver());
+ }
+
+ @Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setPathMatcher(new TestPathMatcher());
configurer.setUrlPathHelper(new TestPathHelper());
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java
index f716b3fc..60de6e83 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,11 +18,11 @@ package org.springframework.web.servlet.config.annotation;
import java.util.List;
import java.util.Locale;
-
import javax.servlet.http.HttpServletRequest;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import org.joda.time.DateTime;
-
import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
@@ -34,6 +34,7 @@ import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.support.StaticMessageSource;
+import org.springframework.core.MethodParameter;
import org.springframework.core.Ordered;
import org.springframework.core.convert.ConversionService;
import org.springframework.format.annotation.DateTimeFormat;
@@ -56,8 +57,13 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
+import org.springframework.web.bind.support.WebDataBinderFactory;
+import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.method.support.CompositeUriComponentsContributor;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
+import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.ViewResolver;
@@ -79,12 +85,13 @@ import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.ViewResolverComposite;
import org.springframework.web.util.UrlPathHelper;
-import com.fasterxml.jackson.databind.DeserializationFeature;
-import com.fasterxml.jackson.databind.MapperFeature;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.dataformat.xml.XmlMapper;
-
-import static org.junit.Assert.*;
+import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
+import static com.fasterxml.jackson.databind.MapperFeature.DEFAULT_VIEW_INCLUSION;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
/**
* Integration tests for {@link WebMvcConfigurationSupport} (imported via
@@ -135,6 +142,7 @@ public class WebMvcConfigurationSupportTests {
HttpServletRequest request = new MockHttpServletRequest("GET", "/testController");
HandlerExecutionChain chain = handlerMapping.getHandler(request);
+ assertNotNull(chain);
assertNotNull(chain.getInterceptors());
assertEquals(3, chain.getInterceptors().length);
assertEquals(ConversionServiceExposingInterceptor.class, chain.getInterceptors()[1].getClass());
@@ -168,19 +176,20 @@ public class WebMvcConfigurationSupportTests {
RequestMappingHandlerAdapter adapter = context.getBean(RequestMappingHandlerAdapter.class);
List<HttpMessageConverter<?>> converters = adapter.getMessageConverters();
assertEquals(9, converters.size());
- for(HttpMessageConverter<?> converter : converters) {
- if (converter instanceof AbstractJackson2HttpMessageConverter) {
- ObjectMapper objectMapper = ((AbstractJackson2HttpMessageConverter)converter).getObjectMapper();
- assertFalse(objectMapper.getDeserializationConfig().isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION));
- assertFalse(objectMapper.getSerializationConfig().isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION));
- assertFalse(objectMapper.getDeserializationConfig().isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES));
- if (converter instanceof MappingJackson2XmlHttpMessageConverter) {
- assertEquals(XmlMapper.class, objectMapper.getClass());
- }
- }
- }
-
- ConfigurableWebBindingInitializer initializer = (ConfigurableWebBindingInitializer) adapter.getWebBindingInitializer();
+ converters.stream()
+ .filter(converter -> converter instanceof AbstractJackson2HttpMessageConverter)
+ .forEach(converter -> {
+ ObjectMapper mapper = ((AbstractJackson2HttpMessageConverter) converter).getObjectMapper();
+ assertFalse(mapper.getDeserializationConfig().isEnabled(DEFAULT_VIEW_INCLUSION));
+ assertFalse(mapper.getSerializationConfig().isEnabled(DEFAULT_VIEW_INCLUSION));
+ assertFalse(mapper.getDeserializationConfig().isEnabled(FAIL_ON_UNKNOWN_PROPERTIES));
+ if (converter instanceof MappingJackson2XmlHttpMessageConverter) {
+ assertEquals(XmlMapper.class, mapper.getClass());
+ }
+ });
+
+ ConfigurableWebBindingInitializer initializer =
+ (ConfigurableWebBindingInitializer) adapter.getWebBindingInitializer();
assertNotNull(initializer);
ConversionService conversionService = initializer.getConversionService();
@@ -245,6 +254,32 @@ public class WebMvcConfigurationSupportTests {
}
@Test
+ public void customArgumentResolvers() {
+ ApplicationContext context = initContext(CustomArgumentResolverConfig.class);
+ RequestMappingHandlerAdapter adapter = context.getBean(RequestMappingHandlerAdapter.class);
+ HandlerExceptionResolverComposite composite = context.getBean(HandlerExceptionResolverComposite.class);
+
+ assertNotNull(adapter);
+ assertEquals(1, adapter.getCustomArgumentResolvers().size());
+ assertEquals(TestArgumentResolver.class, adapter.getCustomArgumentResolvers().get(0).getClass());
+ assertEquals(1, adapter.getCustomReturnValueHandlers().size());
+ assertEquals(TestReturnValueHandler.class, adapter.getCustomReturnValueHandlers().get(0).getClass());
+
+ assertNotNull(composite);
+ assertEquals(3, composite.getExceptionResolvers().size());
+ assertEquals(ExceptionHandlerExceptionResolver.class, composite.getExceptionResolvers().get(0).getClass());
+
+ ExceptionHandlerExceptionResolver resolver =
+ (ExceptionHandlerExceptionResolver) composite.getExceptionResolvers().get(0);
+
+ assertEquals(1, resolver.getCustomArgumentResolvers().size());
+ assertEquals(TestArgumentResolver.class, resolver.getCustomArgumentResolvers().get(0).getClass());
+ assertEquals(1, resolver.getCustomReturnValueHandlers().size());
+ assertEquals(TestReturnValueHandler.class, resolver.getCustomReturnValueHandlers().get(0).getClass());
+ }
+
+
+ @Test
public void mvcViewResolver() {
ApplicationContext context = initContext(WebConfig.class);
ViewResolverComposite resolver = context.getBean("mvcViewResolver", ViewResolverComposite.class);
@@ -298,8 +333,8 @@ public class WebMvcConfigurationSupportTests {
@EnableWebMvc
- @Configuration
- public static class WebConfig {
+ @Configuration @SuppressWarnings("unused")
+ static class WebConfig {
@Bean(name="/testController")
public TestController testController() {
@@ -315,8 +350,8 @@ public class WebMvcConfigurationSupportTests {
}
- @Configuration
- public static class ViewResolverConfig {
+ @Configuration @SuppressWarnings("unused")
+ static class ViewResolverConfig {
@Bean
public ViewResolver beanNameViewResolver() {
@@ -327,7 +362,7 @@ public class WebMvcConfigurationSupportTests {
@EnableWebMvc
@Configuration
- public static class CustomViewResolverOrderConfig extends WebMvcConfigurerAdapter {
+ static class CustomViewResolverOrderConfig extends WebMvcConfigurerAdapter {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
@@ -336,9 +371,24 @@ public class WebMvcConfigurationSupportTests {
}
}
+ @EnableWebMvc
+ @Configuration
+ static class CustomArgumentResolverConfig extends WebMvcConfigurerAdapter {
+
+ @Override
+ public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
+ resolvers.add(new TestArgumentResolver());
+ }
+
+ @Override
+ public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
+ handlers.add(new TestReturnValueHandler());
+ }
+ }
- @Controller
- public static class TestController {
+
+ @Controller @SuppressWarnings("unused")
+ private static class TestController {
@RequestMapping("/")
public void handle() {
@@ -354,7 +404,7 @@ public class WebMvcConfigurationSupportTests {
@Controller
@Scope("prototype")
- public static class ScopedController {
+ private static class ScopedController {
@RequestMapping("/scoped")
public void handle() {
@@ -364,7 +414,7 @@ public class WebMvcConfigurationSupportTests {
@Controller
@Scope(value="prototype", proxyMode=ScopedProxyMode.TARGET_CLASS)
- public static class ScopedProxyController {
+ static class ScopedProxyController {
@RequestMapping("/scopedProxy")
public void handle() {
@@ -374,7 +424,34 @@ public class WebMvcConfigurationSupportTests {
@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "exception.user.exists")
@SuppressWarnings("serial")
- public static class UserAlreadyExistsException extends RuntimeException {
+ private static class UserAlreadyExistsException extends RuntimeException {
+ }
+
+ private static class TestArgumentResolver implements HandlerMethodArgumentResolver {
+
+ @Override
+ public boolean supportsParameter(MethodParameter parameter) {
+ return false;
+ }
+
+ @Override
+ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer container,
+ NativeWebRequest request, WebDataBinderFactory factory) {
+ return null;
+ }
+ }
+
+ private static class TestReturnValueHandler implements HandlerMethodReturnValueHandler {
+
+ @Override
+ public boolean supportsReturnType(MethodParameter returnType) {
+ return false;
+ }
+
+ @Override
+ public void handleReturnValue(Object value, MethodParameter parameter,
+ ModelAndViewContainer container, NativeWebRequest request) {
+ }
}
}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/HandlerMappingIntrospectorTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/HandlerMappingIntrospectorTests.java
new file mode 100644
index 00000000..d3bf4853
--- /dev/null
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/HandlerMappingIntrospectorTests.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.servlet.handler;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import javax.servlet.http.HttpServletRequest;
+
+import org.junit.Test;
+
+import org.springframework.beans.MutablePropertyValues;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpHeaders;
+import org.springframework.mock.web.test.MockHttpServletRequest;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
+import org.springframework.web.context.support.StaticWebApplicationContext;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.servlet.HandlerExecutionChain;
+import org.springframework.web.servlet.HandlerMapping;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+
+import static org.junit.Assert.*;
+import static org.springframework.web.servlet.HandlerMapping.*;
+
+/**
+ * Unit tests for {@link HandlerMappingIntrospector}.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3.1
+ */
+public class HandlerMappingIntrospectorTests {
+
+ @Test
+ public void detectHandlerMappings() throws Exception {
+ StaticWebApplicationContext cxt = new StaticWebApplicationContext();
+ cxt.registerSingleton("hmA", SimpleUrlHandlerMapping.class);
+ cxt.registerSingleton("hmB", SimpleUrlHandlerMapping.class);
+ cxt.registerSingleton("hmC", SimpleUrlHandlerMapping.class);
+ cxt.refresh();
+
+ List<?> expected = Arrays.asList(cxt.getBean("hmA"), cxt.getBean("hmB"), cxt.getBean("hmC"));
+ List<HandlerMapping> actual = new HandlerMappingIntrospector(cxt).getHandlerMappings();
+
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void detectHandlerMappingsOrdered() throws Exception {
+ StaticWebApplicationContext cxt = new StaticWebApplicationContext();
+ MutablePropertyValues pvs = new MutablePropertyValues(Collections.singletonMap("order", "3"));
+ cxt.registerSingleton("hmA", SimpleUrlHandlerMapping.class, pvs);
+ pvs = new MutablePropertyValues(Collections.singletonMap("order", "2"));
+ cxt.registerSingleton("hmB", SimpleUrlHandlerMapping.class, pvs);
+ pvs = new MutablePropertyValues(Collections.singletonMap("order", "1"));
+ cxt.registerSingleton("hmC", SimpleUrlHandlerMapping.class, pvs);
+ cxt.refresh();
+
+ List<?> expected = Arrays.asList(cxt.getBean("hmC"), cxt.getBean("hmB"), cxt.getBean("hmA"));
+ List<HandlerMapping> actual = new HandlerMappingIntrospector(cxt).getHandlerMappings();
+
+ assertEquals(expected, actual);
+ }
+
+ @Test @SuppressWarnings("deprecation")
+ public void defaultHandlerMappings() throws Exception {
+ StaticWebApplicationContext cxt = new StaticWebApplicationContext();
+ cxt.refresh();
+
+ List<HandlerMapping> actual = new HandlerMappingIntrospector(cxt).getHandlerMappings();
+ assertEquals(2, actual.size());
+ assertEquals(BeanNameUrlHandlerMapping.class, actual.get(0).getClass());
+ assertEquals(org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping.class,
+ actual.get(1).getClass());
+ }
+
+ @Test
+ public void getMatchable() throws Exception {
+ MutablePropertyValues pvs = new MutablePropertyValues(
+ Collections.singletonMap("urlMap", Collections.singletonMap("/path", new Object())));
+
+ StaticWebApplicationContext cxt = new StaticWebApplicationContext();
+ cxt.registerSingleton("hm", SimpleUrlHandlerMapping.class, pvs);
+ cxt.refresh();
+
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", "/path");
+ MatchableHandlerMapping hm = new HandlerMappingIntrospector(cxt).getMatchableHandlerMapping(request);
+
+ assertEquals(cxt.getBean("hm"), hm);
+ assertNull("Attributes changes not ignored", request.getAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void getMatchableWhereHandlerMappingDoesNotImplementMatchableInterface() throws Exception {
+ StaticWebApplicationContext cxt = new StaticWebApplicationContext();
+ cxt.registerSingleton("hm1", TestHandlerMapping.class);
+ cxt.refresh();
+
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ new HandlerMappingIntrospector(cxt).getMatchableHandlerMapping(request);
+ }
+
+ @Test
+ public void getCorsConfigurationPreFlight() throws Exception {
+ AnnotationConfigWebApplicationContext cxt = new AnnotationConfigWebApplicationContext();
+ cxt.register(TestConfig.class);
+ cxt.refresh();
+
+ // PRE-FLIGHT
+
+ MockHttpServletRequest request = new MockHttpServletRequest("OPTIONS", "/path");
+ request.addHeader("Origin", "http://localhost:9000");
+ request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "POST");
+ CorsConfiguration corsConfig = new HandlerMappingIntrospector(cxt).getCorsConfiguration(request);
+
+ assertNotNull(corsConfig);
+ assertEquals(Collections.singletonList("http://localhost:9000"), corsConfig.getAllowedOrigins());
+ assertEquals(Collections.singletonList("POST"), corsConfig.getAllowedMethods());
+ }
+
+ @Test
+ public void getCorsConfigurationActual() throws Exception {
+ AnnotationConfigWebApplicationContext cxt = new AnnotationConfigWebApplicationContext();
+ cxt.register(TestConfig.class);
+ cxt.refresh();
+
+ MockHttpServletRequest request = new MockHttpServletRequest("POST", "/path");
+ request.addHeader("Origin", "http://localhost:9000");
+ CorsConfiguration corsConfig = new HandlerMappingIntrospector(cxt).getCorsConfiguration(request);
+
+ assertNotNull(corsConfig);
+ assertEquals(Collections.singletonList("http://localhost:9000"), corsConfig.getAllowedOrigins());
+ assertEquals(Collections.singletonList("POST"), corsConfig.getAllowedMethods());
+ }
+
+
+ private static class TestHandlerMapping implements HandlerMapping {
+
+ @Override
+ public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
+ return new HandlerExecutionChain(new Object());
+ }
+ }
+
+
+ @Configuration @SuppressWarnings({"WeakerAccess", "unused"})
+ static class TestConfig {
+
+ @Bean
+ public RequestMappingHandlerMapping handlerMapping() {
+ return new RequestMappingHandlerMapping();
+ }
+
+ @Bean
+ public TestController testController() {
+ return new TestController();
+ }
+ }
+
+
+ @CrossOrigin("http://localhost:9000")
+ @Controller
+ private static class TestController {
+
+ @PostMapping("/path")
+ public void handle() {
+ }
+ }
+
+}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/MappedInterceptorTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/MappedInterceptorTests.java
index 6c25c27c..49c71b56 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/MappedInterceptorTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/MappedInterceptorTests.java
@@ -15,14 +15,8 @@
*/
package org.springframework.web.servlet.handler;
-import static org.junit.Assert.*;
-import static org.mockito.BDDMockito.any;
-import static org.mockito.BDDMockito.*;
-import static org.mockito.Mockito.mock;
-
import java.util.Comparator;
import java.util.Map;
-
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -35,6 +29,9 @@ import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
+import static org.junit.Assert.*;
+import static org.mockito.BDDMockito.*;
+
/**
* Test fixture for {@link MappedInterceptor} tests.
*
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/PathMatchingUrlHandlerMappingTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/PathMatchingUrlHandlerMappingTests.java
index 323f6aa4..c2ce3e35 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/PathMatchingUrlHandlerMappingTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/PathMatchingUrlHandlerMappingTests.java
@@ -126,7 +126,7 @@ public class PathMatchingUrlHandlerMappingTests {
hec = getHandler(req);
assertTrue("Handler is correct bean", hec != null && hec.getHandler() == bean);
- // should match because because exact pattern is there
+ // should match because exact pattern is there
req = new MockHttpServletRequest("GET", "/administrator/another/bla.xml");
hec = getHandler(req);
assertTrue("Handler is correct bean", hec != null && hec.getHandler() == bean);
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/AcceptHeaderLocaleResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/AcceptHeaderLocaleResolverTests.java
new file mode 100644
index 00000000..d9bb1a4c
--- /dev/null
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/AcceptHeaderLocaleResolverTests.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.web.servlet.i18n;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Locale;
+import javax.servlet.http.HttpServletRequest;
+
+import org.junit.Test;
+
+import org.springframework.mock.web.test.MockHttpServletRequest;
+
+import static java.util.Locale.CANADA;
+import static java.util.Locale.JAPANESE;
+import static java.util.Locale.UK;
+import static java.util.Locale.US;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Unit tests for {@link AcceptHeaderLocaleResolver}.
+ * @author Rossen Stoyanchev
+ */
+public class AcceptHeaderLocaleResolverTests {
+
+ private AcceptHeaderLocaleResolver resolver = new AcceptHeaderLocaleResolver();
+
+
+ @Test
+ public void resolve() throws Exception {
+ assertEquals(CANADA, this.resolver.resolveLocale(request(CANADA)));
+ assertEquals(US, this.resolver.resolveLocale(request(US, CANADA)));
+ }
+
+ @Test
+ public void resolvePreferredSupported() throws Exception {
+ this.resolver.setSupportedLocales(Collections.singletonList(CANADA));
+ assertEquals(CANADA, this.resolver.resolveLocale(request(US, CANADA)));
+ }
+
+ @Test
+ public void resolvePreferredNotSupported() throws Exception {
+ this.resolver.setSupportedLocales(Collections.singletonList(CANADA));
+ assertEquals(US, this.resolver.resolveLocale(request(US, UK)));
+ }
+
+ @Test
+ public void defaultLocale() throws Exception {
+ this.resolver.setDefaultLocale(JAPANESE);
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ assertEquals(JAPANESE, this.resolver.resolveLocale(request));
+
+ request.addHeader("Accept-Language", US.toString());
+ request.setPreferredLocales(Collections.singletonList(US));
+ assertEquals(US, this.resolver.resolveLocale(request));
+ }
+
+
+ private HttpServletRequest request(Locale... locales) {
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.setPreferredLocales(Arrays.asList(locales));
+ return request;
+ }
+
+}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/CookieLocaleResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/CookieLocaleResolverTests.java
index 96b72c9c..270f6ab0 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/CookieLocaleResolverTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/CookieLocaleResolverTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -165,6 +165,58 @@ public class CookieLocaleResolverTests {
}
@Test
+ public void testSetAndResolveLocaleWithCountry() {
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ MockHttpServletResponse response = new MockHttpServletResponse();
+
+ CookieLocaleResolver resolver = new CookieLocaleResolver();
+ resolver.setLocale(request, response, new Locale("de", "AT"));
+
+ Cookie cookie = response.getCookie(CookieLocaleResolver.DEFAULT_COOKIE_NAME);
+ assertNotNull(cookie);
+ assertEquals(CookieLocaleResolver.DEFAULT_COOKIE_NAME, cookie.getName());
+ assertEquals(null, cookie.getDomain());
+ assertEquals(CookieLocaleResolver.DEFAULT_COOKIE_PATH, cookie.getPath());
+ assertFalse(cookie.getSecure());
+ assertEquals("de_AT", cookie.getValue());
+
+ request = new MockHttpServletRequest();
+ request.setCookies(cookie);
+
+ resolver = new CookieLocaleResolver();
+ Locale loc = resolver.resolveLocale(request);
+ assertEquals("de", loc.getLanguage());
+ assertEquals("AT", loc.getCountry());
+ }
+
+ @Test
+ public void testSetAndResolveLocaleWithCountryAsLanguageTag() {
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ MockHttpServletResponse response = new MockHttpServletResponse();
+
+ CookieLocaleResolver resolver = new CookieLocaleResolver();
+ resolver.setLanguageTagCompliant(true);
+ resolver.setLocale(request, response, new Locale("de", "AT"));
+
+ Cookie cookie = response.getCookie(CookieLocaleResolver.DEFAULT_COOKIE_NAME);
+ assertNotNull(cookie);
+ assertEquals(CookieLocaleResolver.DEFAULT_COOKIE_NAME, cookie.getName());
+ assertEquals(null, cookie.getDomain());
+ assertEquals(CookieLocaleResolver.DEFAULT_COOKIE_PATH, cookie.getPath());
+ assertFalse(cookie.getSecure());
+ assertEquals("de-AT", cookie.getValue());
+
+ request = new MockHttpServletRequest();
+ request.setCookies(cookie);
+
+ resolver = new CookieLocaleResolver();
+ resolver.setLanguageTagCompliant(true);
+ Locale loc = resolver.resolveLocale(request);
+ assertEquals("de", loc.getLanguage());
+ assertEquals("AT", loc.getCountry());
+ }
+
+ @Test
public void testCustomCookie() {
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/ParameterizableViewControllerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/ParameterizableViewControllerTests.java
index f7d416e5..721f8a67 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/ParameterizableViewControllerTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/ParameterizableViewControllerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@ package org.springframework.web.servlet.mvc;
import org.junit.Before;
import org.junit.Test;
+import org.springframework.http.HttpMethod;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse;
import org.springframework.ui.ModelMap;
@@ -69,4 +70,14 @@ public class ParameterizableViewControllerTests {
assertEquals("value", mav.getModel().get("name"));
}
+ @Test
+ public void handleRequestHttpOptions() throws Exception {
+ this.request.setMethod(HttpMethod.OPTIONS.name());
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ ModelAndView mav = this.controller.handleRequest(this.request, response);
+
+ assertNull(mav);
+ assertEquals("GET,HEAD,OPTIONS", response.getHeader("Allow"));
+ }
+
}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolverTests.java
index d615b3a2..f5bc1271 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolverTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolverTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Locale;
+import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.TypeMismatchException;
@@ -49,6 +50,11 @@ public class ResponseStatusExceptionResolverTests {
private final MockHttpServletResponse response = new MockHttpServletResponse();
+ @Before
+ public void setup() {
+ exceptionResolver.setWarnLogCategory(exceptionResolver.getClass().getName());
+ }
+
@Test
public void statusCode() {
StatusCodeException ex = new StatusCodeException();
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java
index 8c50836c..fc4bf0bc 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1356,7 +1356,7 @@ public class ServletAnnotationControllerTests {
request.addHeader("Accept", "application/json, text/javascript, */*");
MockHttpServletResponse response = new MockHttpServletResponse();
servlet.service(request, response);
- assertEquals("Invalid response status code", "application/json", response.getHeader("Content-Type"));
+ assertEquals("Invalid response status code", "application/json;charset=ISO-8859-1", response.getHeader("Content-Type"));
}
@Test
@@ -1770,7 +1770,7 @@ public class ServletAnnotationControllerTests {
servlet.service(request, response);
assertEquals(200, response.getStatus());
- assertEquals("application/json", response.getHeader("Content-Type"));
+ assertEquals("application/json;charset=ISO-8859-1", response.getHeader("Content-Type"));
assertEquals("homeJson", response.getContentAsString());
}
@@ -1966,6 +1966,17 @@ public class ServletAnnotationControllerTests {
assertEquals("1-2", response.getContentAsString());
}
+ @Test
+ public void httpOptions() throws ServletException, IOException {
+ initServlet(ResponseEntityController.class);
+
+ MockHttpServletRequest request = new MockHttpServletRequest("OPTIONS", "/foo");
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ servlet.service(request, response);
+ assertEquals(404, response.getStatus());
+ }
+
+
public static class ListEditorRegistrar implements PropertyEditorRegistrar {
@Override
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/UriTemplateServletAnnotationControllerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/UriTemplateServletAnnotationControllerTests.java
index ca2df2ca..79d50c93 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/UriTemplateServletAnnotationControllerTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/UriTemplateServletAnnotationControllerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author 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,7 @@ public class UriTemplateServletAnnotationControllerTests {
private DispatcherServlet servlet;
+
@Test
public void simple() throws Exception {
initServlet(SimpleUriTemplateController.class);
@@ -318,8 +319,7 @@ public class UriTemplateServletAnnotationControllerTests {
assertEquals("test-42", response.getContentAsString());
}
- // SPR-6640
- @Test
+ @Test // SPR-6640
public void menuTree() throws Exception {
initServlet(MenuTreeController.class);
@@ -329,8 +329,7 @@ public class UriTemplateServletAnnotationControllerTests {
assertEquals("M5", response.getContentAsString());
}
- // SPR-6876
- @Test
+ @Test // SPR-6876
public void variableNames() throws Exception {
initServlet(VariableNamesController.class);
@@ -345,8 +344,7 @@ public class UriTemplateServletAnnotationControllerTests {
assertEquals("bar-bar", response.getContentAsString());
}
- // SPR-8543
- @Test
+ @Test // SPR-8543
public void variableNamesWithUrlExtension() throws Exception {
initServlet(VariableNamesController.class);
@@ -356,8 +354,7 @@ public class UriTemplateServletAnnotationControllerTests {
assertEquals("foo-foo", response.getContentAsString());
}
- // SPR-9333
- @Test
+ @Test // SPR-9333
@SuppressWarnings("serial")
public void suppressDefaultSuffixPattern() throws Exception {
servlet = new DispatcherServlet() {
@@ -381,8 +378,7 @@ public class UriTemplateServletAnnotationControllerTests {
assertEquals("foo-jsmith@mail.com", response.getContentAsString());
}
- // SPR-6906
- @Test
+ @Test // SPR-6906
@SuppressWarnings("serial")
public void controllerClassName() throws Exception {
servlet = new DispatcherServlet() {
@@ -416,8 +412,7 @@ public class UriTemplateServletAnnotationControllerTests {
assertEquals("plain-bar", response.getContentAsString());
}
- // SPR-6978
- @Test
+ @Test // SPR-6978
public void doIt() throws Exception {
initServlet(Spr6978Controller.class);
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/condition/ProducesRequestConditionTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/condition/ProducesRequestConditionTests.java
index 57d43e7e..f4d41d8f 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/condition/ProducesRequestConditionTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/condition/ProducesRequestConditionTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,6 +27,8 @@ import org.springframework.web.servlet.mvc.condition.ProducesRequestCondition.Pr
import static org.junit.Assert.*;
/**
+ * Unit tests for {@link ProducesRequestCondition}.
+ *
* @author Arjen Poutsma
* @author Rossen Stoyanchev
*/
@@ -53,6 +55,15 @@ public class ProducesRequestConditionTests {
}
@Test
+ public void matchNegatedWithoutAcceptHeader() {
+ ProducesRequestCondition condition = new ProducesRequestCondition("!text/plain");
+ MockHttpServletRequest request = new MockHttpServletRequest();
+
+ assertNotNull(condition.getMatchingCondition(request));
+ assertEquals(Collections.emptySet(), condition.getProducibleMediaTypes());
+ }
+
+ @Test
public void getProducibleMediaTypes() {
ProducesRequestCondition condition = new ProducesRequestCondition("!application/xml");
assertEquals(Collections.emptySet(), condition.getProducibleMediaTypes());
@@ -316,6 +327,7 @@ public class ProducesRequestConditionTests {
assertNull(result);
}
+
private void assertConditions(ProducesRequestCondition condition, String... expected) {
Collection<ProduceMediaTypeExpression> expressions = condition.getContent();
assertEquals("Invalid number of conditions", expressions.size(), expected.length);
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/condition/RequestMethodsRequestConditionTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/condition/RequestMethodsRequestConditionTests.java
index e0430306..70558fe7 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/condition/RequestMethodsRequestConditionTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/condition/RequestMethodsRequestConditionTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author 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,92 +16,132 @@
package org.springframework.web.servlet.mvc.condition;
+import java.util.Collections;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.http.HttpServletRequest;
+
import org.junit.Test;
+import org.springframework.http.HttpHeaders;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.web.bind.annotation.RequestMethod;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.springframework.web.bind.annotation.RequestMethod.DELETE;
+import static org.springframework.web.bind.annotation.RequestMethod.GET;
+import static org.springframework.web.bind.annotation.RequestMethod.HEAD;
+import static org.springframework.web.bind.annotation.RequestMethod.OPTIONS;
+import static org.springframework.web.bind.annotation.RequestMethod.POST;
+import static org.springframework.web.bind.annotation.RequestMethod.PUT;
/**
* @author Arjen Poutsma
+ * @author Rossen Stoyanchev
*/
public class RequestMethodsRequestConditionTests {
@Test
- public void methodMatch() {
- RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition(RequestMethod.GET);
-
- MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
-
- assertNotNull(condition.getMatchingCondition(request));
+ public void getMatchingCondition() {
+ testMatch(new RequestMethodsRequestCondition(GET), GET);
+ testMatch(new RequestMethodsRequestCondition(GET, POST), GET);
+ testNoMatch(new RequestMethodsRequestCondition(GET), POST);
}
@Test
- public void methodNoMatch() {
- RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition(RequestMethod.GET);
-
- MockHttpServletRequest request = new MockHttpServletRequest("POST", "/foo");
-
- assertNull(condition.getMatchingCondition(request));
+ public void getMatchingConditionWithHttpHead() {
+ testMatch(new RequestMethodsRequestCondition(HEAD), HEAD);
+ testMatch(new RequestMethodsRequestCondition(GET), GET);
+ testNoMatch(new RequestMethodsRequestCondition(POST), HEAD);
}
@Test
- public void multipleMethodsMatch() {
- RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition(RequestMethod.GET, RequestMethod.POST);
-
- MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
-
- assertNotNull(condition.getMatchingCondition(request));
+ public void getMatchingConditionWithEmptyConditions() {
+ RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition();
+ for (RequestMethod method : RequestMethod.values()) {
+ if (!OPTIONS.equals(method)) {
+ HttpServletRequest request = new MockHttpServletRequest(method.name(), "");
+ assertNotNull(condition.getMatchingCondition(request));
+ }
+ }
+ testNoMatch(condition, OPTIONS);
}
@Test
- public void noMethodsMatchAll() {
- RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition();
-
- assertNotNull(condition.getMatchingCondition(new MockHttpServletRequest("GET", "")));
- assertNotNull(condition.getMatchingCondition(new MockHttpServletRequest("POST", "")));
- assertNotNull(condition.getMatchingCondition(new MockHttpServletRequest("HEAD", "")));
+ public void getMatchingConditionWithCustomMethod() {
+ HttpServletRequest request = new MockHttpServletRequest("PROPFIND", "");
+ assertNotNull(new RequestMethodsRequestCondition().getMatchingCondition(request));
+ assertNull(new RequestMethodsRequestCondition(GET, POST).getMatchingCondition(request));
}
@Test
- public void unknownMethodType() throws Exception {
- RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition(RequestMethod.GET, RequestMethod.POST);
+ public void getMatchingConditionWithCorsPreFlight() throws Exception {
+ MockHttpServletRequest request = new MockHttpServletRequest("OPTIONS", "");
+ request.addHeader("Origin", "http://example.com");
+ request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "PUT");
+
+ assertNotNull(new RequestMethodsRequestCondition().getMatchingCondition(request));
+ assertNotNull(new RequestMethodsRequestCondition(PUT).getMatchingCondition(request));
+ assertNull(new RequestMethodsRequestCondition(DELETE).getMatchingCondition(request));
+ }
- MockHttpServletRequest request = new MockHttpServletRequest("PROPFIND", "/foo");
+ @Test // SPR-14410
+ public void getMatchingConditionWithHttpOptionsInErrorDispatch() throws Exception {
+ MockHttpServletRequest request = new MockHttpServletRequest("OPTIONS", "/path");
+ request.setDispatcherType(DispatcherType.ERROR);
- assertNull(condition.getMatchingCondition(request));
+ RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition();
+ RequestMethodsRequestCondition result = condition.getMatchingCondition(request);
+
+ assertNotNull(result);
+ assertSame(condition, result);
}
@Test
public void compareTo() {
- RequestMethodsRequestCondition condition1 = new RequestMethodsRequestCondition(RequestMethod.GET, RequestMethod.HEAD);
- RequestMethodsRequestCondition condition2 = new RequestMethodsRequestCondition(RequestMethod.POST);
- RequestMethodsRequestCondition condition3 = new RequestMethodsRequestCondition();
+ RequestMethodsRequestCondition c1 = new RequestMethodsRequestCondition(GET, HEAD);
+ RequestMethodsRequestCondition c2 = new RequestMethodsRequestCondition(POST);
+ RequestMethodsRequestCondition c3 = new RequestMethodsRequestCondition();
MockHttpServletRequest request = new MockHttpServletRequest();
- int result = condition1.compareTo(condition2, request);
+ int result = c1.compareTo(c2, request);
assertTrue("Invalid comparison result: " + result, result < 0);
- result = condition2.compareTo(condition1, request);
+ result = c2.compareTo(c1, request);
assertTrue("Invalid comparison result: " + result, result > 0);
- result = condition2.compareTo(condition3, request);
+ result = c2.compareTo(c3, request);
assertTrue("Invalid comparison result: " + result, result < 0);
- result = condition1.compareTo(condition1, request);
+ result = c1.compareTo(c1, request);
assertEquals("Invalid comparison result ", 0, result);
}
@Test
public void combine() {
- RequestMethodsRequestCondition condition1 = new RequestMethodsRequestCondition(RequestMethod.GET);
- RequestMethodsRequestCondition condition2 = new RequestMethodsRequestCondition(RequestMethod.POST);
+ RequestMethodsRequestCondition condition1 = new RequestMethodsRequestCondition(GET);
+ RequestMethodsRequestCondition condition2 = new RequestMethodsRequestCondition(POST);
RequestMethodsRequestCondition result = condition1.combine(condition2);
assertEquals(2, result.getContent().size());
}
+ private void testMatch(RequestMethodsRequestCondition condition, RequestMethod method) {
+ MockHttpServletRequest request = new MockHttpServletRequest(method.name(), "");
+ RequestMethodsRequestCondition actual = condition.getMatchingCondition(request);
+ assertNotNull(actual);
+ assertEquals(Collections.singleton(method), actual.getContent());
+ }
+
+ private void testNoMatch(RequestMethodsRequestCondition condition, RequestMethod method) {
+ MockHttpServletRequest request = new MockHttpServletRequest(method.name(), "");
+ assertNull(condition.getMatchingCondition(request));
+ }
+
}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMappingTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMappingTests.java
index cdbae443..e1792520 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMappingTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMappingTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +33,7 @@ import org.junit.Before;
import org.junit.Test;
import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.stereotype.Controller;
@@ -44,8 +45,11 @@ import org.springframework.web.bind.UnsatisfiedServletRequestParameterException;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.support.StaticWebApplicationContext;
import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.method.support.InvocableHandlerMethod;
+import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.HandlerMapping;
@@ -59,6 +63,7 @@ import org.springframework.web.servlet.mvc.condition.ProducesRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.util.UrlPathHelper;
+
/**
* Test fixture with {@link RequestMappingInfoHandlerMapping}.
*
@@ -77,6 +82,7 @@ public class RequestMappingInfoHandlerMappingTests {
private HandlerMethod emptyMethod;
+
@Before
public void setUp() throws Exception {
TestController testController = new TestController();
@@ -91,96 +97,95 @@ public class RequestMappingInfoHandlerMappingTests {
this.handlerMapping.setRemoveSemicolonContent(false);
}
+
@Test
public void getMappingPathPatterns() throws Exception {
- RequestMappingInfo info = new RequestMappingInfo(
- new PatternsRequestCondition("/foo/*", "/foo", "/bar/*", "/bar"), null, null, null, null, null, null);
- Set<String> paths = this.handlerMapping.getMappingPathPatterns(info);
- HashSet<String> expected = new HashSet<String>(Arrays.asList("/foo/*", "/foo", "/bar/*", "/bar"));
+ String[] patterns = {"/foo/*", "/foo", "/bar/*", "/bar"};
+ RequestMappingInfo info = RequestMappingInfo.paths(patterns).build();
+ Set<String> actual = this.handlerMapping.getMappingPathPatterns(info);
- assertEquals(expected, paths);
+ assertEquals(new HashSet<>(Arrays.asList(patterns)), actual);
}
@Test
- public void directMatch() throws Exception {
+ public void getHandlerDirectMatch() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
- HandlerMethod hm = (HandlerMethod) this.handlerMapping.getHandler(request).getHandler();
- assertEquals(this.fooMethod.getMethod(), hm.getMethod());
+ HandlerMethod handlerMethod = getHandler(request);
+
+ assertEquals(this.fooMethod.getMethod(), handlerMethod.getMethod());
}
@Test
- public void globMatch() throws Exception {
+ public void getHandlerGlobMatch() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bar");
- HandlerMethod hm = (HandlerMethod) this.handlerMapping.getHandler(request).getHandler();
- assertEquals(this.barMethod.getMethod(), hm.getMethod());
+ HandlerMethod handlerMethod = getHandler(request);
+ assertEquals(this.barMethod.getMethod(), handlerMethod.getMethod());
}
@Test
- public void emptyPathMatch() throws Exception {
+ public void getHandlerEmptyPathMatch() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
- HandlerMethod hm = (HandlerMethod) this.handlerMapping.getHandler(request).getHandler();
- assertEquals(this.emptyMethod.getMethod(), hm.getMethod());
+ HandlerMethod handlerMethod = getHandler(request);
+
+ assertEquals(this.emptyMethod.getMethod(), handlerMethod.getMethod());
request = new MockHttpServletRequest("GET", "/");
- hm = (HandlerMethod) this.handlerMapping.getHandler(request).getHandler();
- assertEquals(this.emptyMethod.getMethod(), hm.getMethod());
+ handlerMethod = getHandler(request);
+
+ assertEquals(this.emptyMethod.getMethod(), handlerMethod.getMethod());
}
@Test
- public void bestMatch() throws Exception {
+ public void getHandlerBestMatch() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
request.setParameter("p", "anything");
- HandlerMethod hm = (HandlerMethod) this.handlerMapping.getHandler(request).getHandler();
- assertEquals(this.fooParamMethod.getMethod(), hm.getMethod());
+ HandlerMethod handlerMethod = getHandler(request);
+
+ assertEquals(this.fooParamMethod.getMethod(), handlerMethod.getMethod());
}
@Test
- public void requestMethodNotAllowed() throws Exception {
+ public void getHandlerRequestMethodNotAllowed() throws Exception {
try {
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/bar");
this.handlerMapping.getHandler(request);
fail("HttpRequestMethodNotSupportedException expected");
}
catch (HttpRequestMethodNotSupportedException ex) {
- assertArrayEquals("Invalid supported methods", new String[]{"GET", "HEAD"}, ex.getSupportedMethods());
+ assertArrayEquals("Invalid supported methods", new String[]{"GET", "HEAD"},
+ ex.getSupportedMethods());
}
}
// SPR-9603
@Test(expected=HttpMediaTypeNotAcceptableException.class)
- public void requestMethodMatchFalsePositive() throws Exception {
+ public void getHandlerRequestMethodMatchFalsePositive() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/users");
request.addHeader("Accept", "application/xml");
-
this.handlerMapping.registerHandler(new UserController());
this.handlerMapping.getHandler(request);
}
- @Test
- public void mediaTypeNotSupported() throws Exception {
- testMediaTypeNotSupported("/person/1");
+ // SPR-8462
- // SPR-8462
- testMediaTypeNotSupported("/person/1/");
- testMediaTypeNotSupported("/person/1.json");
+ @Test
+ public void getHandlerMediaTypeNotSupported() throws Exception {
+ testHttpMediaTypeNotSupportedException("/person/1");
+ testHttpMediaTypeNotSupportedException("/person/1/");
+ testHttpMediaTypeNotSupportedException("/person/1.json");
}
- private void testMediaTypeNotSupported(String url) throws Exception {
- try {
- MockHttpServletRequest request = new MockHttpServletRequest("PUT", url);
- request.setContentType("application/json");
- this.handlerMapping.getHandler(request);
- fail("HttpMediaTypeNotSupportedException expected");
- }
- catch (HttpMediaTypeNotSupportedException ex) {
- assertEquals("Invalid supported consumable media types",
- Arrays.asList(new MediaType("application", "xml")), ex.getSupportedMediaTypes());
- }
+ @Test
+ public void getHandlerHttpOptions() throws Exception {
+ testHttpOptions("/foo", "GET,HEAD");
+ testHttpOptions("/person/1", "PUT");
+ testHttpOptions("/persons", "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS");
+ testHttpOptions("/something", "PUT,POST");
}
@Test
- public void testMediaTypeNotValue() throws Exception {
+ public void getHandlerTestInvalidContentType() throws Exception {
try {
MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/person/1");
request.setContentType("bogus");
@@ -192,32 +197,19 @@ public class RequestMappingInfoHandlerMappingTests {
}
}
- @Test
- public void mediaTypeNotAccepted() throws Exception {
- testMediaTypeNotAccepted("/persons");
-
- // SPR-8462
- testMediaTypeNotAccepted("/persons/");
- testMediaTypeNotAccepted("/persons.json");
- }
+ // SPR-8462
- private void testMediaTypeNotAccepted(String url) throws Exception {
- try {
- MockHttpServletRequest request = new MockHttpServletRequest("GET", url);
- request.addHeader("Accept", "application/json");
- this.handlerMapping.getHandler(request);
- fail("HttpMediaTypeNotAcceptableException expected");
- }
- catch (HttpMediaTypeNotAcceptableException ex) {
- assertEquals("Invalid supported producible media types",
- Arrays.asList(new MediaType("application", "xml")), ex.getSupportedMediaTypes());
- }
+ @Test
+ public void getHandlerMediaTypeNotAccepted() throws Exception {
+ testHttpMediaTypeNotAcceptableException("/persons");
+ testHttpMediaTypeNotAcceptableException("/persons/");
+ testHttpMediaTypeNotAcceptableException("/persons.json");
}
// SPR-12854
@Test
- public void testUnsatisfiedServletRequestParameterException() throws Exception {
+ public void getHandlerUnsatisfiedServletRequestParameterException() throws Exception {
try {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/params");
this.handlerMapping.getHandler(request);
@@ -232,17 +224,51 @@ public class RequestMappingInfoHandlerMappingTests {
}
@Test
- public void uriTemplateVariables() {
- PatternsRequestCondition patterns = new PatternsRequestCondition("/{path1}/{path2}");
- RequestMappingInfo key = new RequestMappingInfo(patterns, null, null, null, null, null, null);
+ public void getHandlerProducibleMediaTypesAttribute() throws Exception {
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", "/content");
+ request.addHeader("Accept", "application/xml");
+ this.handlerMapping.getHandler(request);
+
+ String name = HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE;
+ assertEquals(Collections.singleton(MediaType.APPLICATION_XML), request.getAttribute(name));
+
+ request = new MockHttpServletRequest("GET", "/content");
+ request.addHeader("Accept", "application/json");
+ this.handlerMapping.getHandler(request);
+
+ assertNull("Negated expression shouldn't be listed as producible type", request.getAttribute(name));
+ }
+
+ @Test
+ public void getHandlerMappedInterceptors() throws Exception {
+ String path = "/foo";
+ HandlerInterceptor interceptor = new HandlerInterceptorAdapter() {};
+ MappedInterceptor mappedInterceptor = new MappedInterceptor(new String[] {path}, interceptor);
+
+ TestRequestMappingInfoHandlerMapping mapping = new TestRequestMappingInfoHandlerMapping();
+ mapping.registerHandler(new TestController());
+ mapping.setInterceptors(new Object[] { mappedInterceptor });
+ mapping.setApplicationContext(new StaticWebApplicationContext());
+
+ HandlerExecutionChain chain = mapping.getHandler(new MockHttpServletRequest("GET", path));
+ assertNotNull(chain);
+ assertNotNull(chain.getInterceptors());
+ assertSame(interceptor, chain.getInterceptors()[0]);
+
+ chain = mapping.getHandler(new MockHttpServletRequest("GET", "/invalid"));
+ assertNull(chain);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void handleMatchUriTemplateVariables() {
+ RequestMappingInfo key = RequestMappingInfo.paths("/{path1}/{path2}").build();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/1/2");
String lookupPath = new UrlPathHelper().getLookupPathForRequest(request);
this.handlerMapping.handleMatch(key, lookupPath, request);
- @SuppressWarnings("unchecked")
- Map<String, String> uriVariables =
- (Map<String, String>) request.getAttribute(
- HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
+ String name = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
+ Map<String, String> uriVariables = (Map<String, String>) request.getAttribute(name);
assertNotNull(uriVariables);
assertEquals("1", uriVariables.get("path1"));
@@ -251,10 +277,10 @@ public class RequestMappingInfoHandlerMappingTests {
// SPR-9098
+ @SuppressWarnings("unchecked")
@Test
- public void uriTemplateVariablesDecode() {
- PatternsRequestCondition patterns = new PatternsRequestCondition("/{group}/{identifier}");
- RequestMappingInfo key = new RequestMappingInfo(patterns, null, null, null, null, null, null);
+ public void handleMatchUriTemplateVariablesDecode() {
+ RequestMappingInfo key = RequestMappingInfo.paths("/{group}/{identifier}").build();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/group/a%2Fb");
UrlPathHelper pathHelper = new UrlPathHelper();
@@ -264,9 +290,8 @@ public class RequestMappingInfoHandlerMappingTests {
this.handlerMapping.setUrlPathHelper(pathHelper);
this.handlerMapping.handleMatch(key, lookupPath, request);
- @SuppressWarnings("unchecked")
- Map<String, String> uriVariables =
- (Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
+ String name = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
+ Map<String, String> uriVariables = (Map<String, String>) request.getAttribute(name);
assertNotNull(uriVariables);
assertEquals("group", uriVariables.get("group"));
@@ -274,20 +299,17 @@ public class RequestMappingInfoHandlerMappingTests {
}
@Test
- public void bestMatchingPatternAttribute() {
- PatternsRequestCondition patterns = new PatternsRequestCondition("/{path1}/2", "/**");
- RequestMappingInfo key = new RequestMappingInfo(patterns, null, null, null, null, null, null);
+ public void handleMatchBestMatchingPatternAttribute() {
+ RequestMappingInfo key = RequestMappingInfo.paths("/{path1}/2", "/**").build();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/1/2");
-
this.handlerMapping.handleMatch(key, "/1/2", request);
assertEquals("/{path1}/2", request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE));
}
@Test
- public void bestMatchingPatternAttributeNoPatternsDefined() {
- PatternsRequestCondition patterns = new PatternsRequestCondition();
- RequestMappingInfo key = new RequestMappingInfo(patterns, null, null, null, null, null, null);
+ public void handleMatchBestMatchingPatternAttributeNoPatternsDefined() {
+ RequestMappingInfo key = RequestMappingInfo.paths().build();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/1/2");
this.handlerMapping.handleMatch(key, "/1/2", request);
@@ -296,51 +318,13 @@ public class RequestMappingInfoHandlerMappingTests {
}
@Test
- public void producibleMediaTypesAttribute() throws Exception {
- MockHttpServletRequest request = new MockHttpServletRequest("GET", "/content");
- request.addHeader("Accept", "application/xml");
- this.handlerMapping.getHandler(request);
-
- assertEquals(Collections.singleton(MediaType.APPLICATION_XML),
- request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE));
-
- request = new MockHttpServletRequest("GET", "/content");
- request.addHeader("Accept", "application/json");
- this.handlerMapping.getHandler(request);
-
- assertNull("Negated expression should not be listed as a producible type",
- request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE));
- }
-
- @Test
- public void mappedInterceptors() throws Exception {
- String path = "/foo";
- HandlerInterceptor interceptor = new HandlerInterceptorAdapter() {};
- MappedInterceptor mappedInterceptor = new MappedInterceptor(new String[] {path}, interceptor);
-
- TestRequestMappingInfoHandlerMapping hm = new TestRequestMappingInfoHandlerMapping();
- hm.registerHandler(new TestController());
- hm.setInterceptors(new Object[] { mappedInterceptor });
- hm.setApplicationContext(new StaticWebApplicationContext());
-
- HandlerExecutionChain chain = hm.getHandler(new MockHttpServletRequest("GET", path));
- assertNotNull(chain);
- assertNotNull(chain.getInterceptors());
- assertSame(interceptor, chain.getInterceptors()[0]);
-
- chain = hm.getHandler(new MockHttpServletRequest("GET", "/invalid"));
- assertNull(chain);
- }
-
- @Test
- public void matrixVariables() {
-
+ public void handleMatchMatrixVariables() {
MockHttpServletRequest request;
MultiValueMap<String, String> matrixVariables;
Map<String, String> uriVariables;
request = new MockHttpServletRequest();
- testHandleMatch(request, "/{cars}", "/cars;colors=red,blue,green;year=2012");
+ handleMatch(request, "/{cars}", "/cars;colors=red,blue,green;year=2012");
matrixVariables = getMatrixVariables(request, "cars");
uriVariables = getUriTemplateVariables(request);
@@ -351,7 +335,7 @@ public class RequestMappingInfoHandlerMappingTests {
assertEquals("cars", uriVariables.get("cars"));
request = new MockHttpServletRequest();
- testHandleMatch(request, "/{cars:[^;]+}{params}", "/cars;colors=red,blue,green;year=2012");
+ handleMatch(request, "/{cars:[^;]+}{params}", "/cars;colors=red,blue,green;year=2012");
matrixVariables = getMatrixVariables(request, "params");
uriVariables = getUriTemplateVariables(request);
@@ -363,7 +347,7 @@ public class RequestMappingInfoHandlerMappingTests {
assertEquals(";colors=red,blue,green;year=2012", uriVariables.get("params"));
request = new MockHttpServletRequest();
- testHandleMatch(request, "/{cars:[^;]+}{params}", "/cars");
+ handleMatch(request, "/{cars:[^;]+}{params}", "/cars");
matrixVariables = getMatrixVariables(request, "params");
uriVariables = getUriTemplateVariables(request);
@@ -374,7 +358,7 @@ public class RequestMappingInfoHandlerMappingTests {
}
@Test
- public void matrixVariablesDecoding() {
+ public void handleMatchMatrixVariablesDecoding() {
MockHttpServletRequest request;
@@ -385,20 +369,66 @@ public class RequestMappingInfoHandlerMappingTests {
this.handlerMapping.setUrlPathHelper(urlPathHelper );
request = new MockHttpServletRequest();
- testHandleMatch(request, "/path{filter}", "/path;mvar=a%2fb");
+ handleMatch(request, "/path{filter}", "/path;mvar=a%2fb");
MultiValueMap<String, String> matrixVariables = getMatrixVariables(request, "filter");
Map<String, String> uriVariables = getUriTemplateVariables(request);
assertNotNull(matrixVariables);
- assertEquals(Arrays.asList("a/b"), matrixVariables.get("mvar"));
+ assertEquals(Collections.singletonList("a/b"), matrixVariables.get("mvar"));
assertEquals(";mvar=a/b", uriVariables.get("filter"));
}
- private void testHandleMatch(MockHttpServletRequest request, String pattern, String lookupPath) {
- PatternsRequestCondition patterns = new PatternsRequestCondition(pattern);
- RequestMappingInfo info = new RequestMappingInfo(patterns, null, null, null, null, null, null);
+ private HandlerMethod getHandler(MockHttpServletRequest request) throws Exception {
+ HandlerExecutionChain chain = this.handlerMapping.getHandler(request);
+ assertNotNull(chain);
+ return (HandlerMethod) chain.getHandler();
+ }
+
+ private void testHttpMediaTypeNotSupportedException(String url) throws Exception {
+ try {
+ MockHttpServletRequest request = new MockHttpServletRequest("PUT", url);
+ request.setContentType("application/json");
+ this.handlerMapping.getHandler(request);
+ fail("HttpMediaTypeNotSupportedException expected");
+ }
+ catch (HttpMediaTypeNotSupportedException ex) {
+ assertEquals("Invalid supported consumable media types",
+ Collections.singletonList(new MediaType("application", "xml")),
+ ex.getSupportedMediaTypes());
+ }
+ }
+
+ private void testHttpOptions(String requestURI, String allowHeader) throws Exception {
+ MockHttpServletRequest request = new MockHttpServletRequest("OPTIONS", requestURI);
+ HandlerMethod handlerMethod = getHandler(request);
+
+ ServletWebRequest webRequest = new ServletWebRequest(request);
+ ModelAndViewContainer mavContainer = new ModelAndViewContainer();
+ Object result = new InvocableHandlerMethod(handlerMethod).invokeForRequest(webRequest, mavContainer);
+
+ assertNotNull(result);
+ assertEquals(HttpHeaders.class, result.getClass());
+ assertEquals(allowHeader, ((HttpHeaders) result).getFirst("Allow"));
+ }
+
+ private void testHttpMediaTypeNotAcceptableException(String url) throws Exception {
+ try {
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", url);
+ request.addHeader("Accept", "application/json");
+ this.handlerMapping.getHandler(request);
+ fail("HttpMediaTypeNotAcceptableException expected");
+ }
+ catch (HttpMediaTypeNotAcceptableException ex) {
+ assertEquals("Invalid supported producible media types",
+ Collections.singletonList(new MediaType("application", "xml")),
+ ex.getSupportedMediaTypes());
+ }
+ }
+
+ private void handleMatch(MockHttpServletRequest request, String pattern, String lookupPath) {
+ RequestMappingInfo info = RequestMappingInfo.paths(pattern).build();
this.handlerMapping.handleMatch(info, lookupPath, request);
}
@@ -463,6 +493,13 @@ public class RequestMappingInfoHandlerMappingTests {
public String nonXmlContent() {
return "";
}
+
+ @RequestMapping(value = "/something", method = RequestMethod.OPTIONS)
+ public HttpHeaders fooOptions() {
+ HttpHeaders headers = new HttpHeaders();
+ headers.add("Allow", "PUT,POST");
+ return headers;
+ }
}
@SuppressWarnings("unused")
@@ -491,15 +528,15 @@ public class RequestMappingInfoHandlerMappingTests {
@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
- RequestMapping annotation = AnnotationUtils.findAnnotation(method, RequestMapping.class);
- if (annotation != null) {
+ RequestMapping annot = AnnotationUtils.findAnnotation(method, RequestMapping.class);
+ if (annot != null) {
return new RequestMappingInfo(
- new PatternsRequestCondition(annotation.value(), getUrlPathHelper(), getPathMatcher(), true, true),
- new RequestMethodsRequestCondition(annotation.method()),
- new ParamsRequestCondition(annotation.params()),
- new HeadersRequestCondition(annotation.headers()),
- new ConsumesRequestCondition(annotation.consumes(), annotation.headers()),
- new ProducesRequestCondition(annotation.produces(), annotation.headers()), null);
+ new PatternsRequestCondition(annot.value(), getUrlPathHelper(), getPathMatcher(), true, true),
+ new RequestMethodsRequestCondition(annot.method()),
+ new ParamsRequestCondition(annot.params()),
+ new HeadersRequestCondition(annot.headers()),
+ new ConsumesRequestCondition(annot.consumes(), annot.headers()),
+ new ProducesRequestCondition(annot.produces(), annot.headers()), null);
}
else {
return null;
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoTests.java
index 29661840..c36f8c53 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,21 +19,23 @@ package org.springframework.web.servlet.mvc.method;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import javax.servlet.http.HttpServletRequest;
import org.junit.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.servlet.mvc.condition.ConsumesRequestCondition;
-import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition;
-import org.springframework.web.servlet.mvc.condition.ParamsRequestCondition;
-import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
-import org.springframework.web.servlet.mvc.condition.ProducesRequestCondition;
-import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
-import static java.util.Arrays.*;
-import static org.junit.Assert.*;
+import static java.util.Arrays.asList;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.springframework.web.bind.annotation.RequestMethod.GET;
+import static org.springframework.web.bind.annotation.RequestMethod.HEAD;
+import static org.springframework.web.servlet.mvc.method.RequestMappingInfo.paths;
/**
* Test fixture for {@link RequestMappingInfo} tests.
@@ -43,9 +45,10 @@ import static org.junit.Assert.*;
*/
public class RequestMappingInfoTests {
+
@Test
public void createEmpty() {
- RequestMappingInfo info = new RequestMappingInfo(null, null, null, null, null, null, null);
+ RequestMappingInfo info = paths().build();
assertEquals(0, info.getPatternsCondition().getPatterns().size());
assertEquals(0, info.getMethodsCondition().getMethods().size());
@@ -58,19 +61,15 @@ public class RequestMappingInfoTests {
@Test
public void matchPatternsCondition() {
- MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
+ HttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
- RequestMappingInfo info = new RequestMappingInfo(
- new PatternsRequestCondition("/foo*", "/bar"), null, null, null, null, null, null);
- RequestMappingInfo expected = new RequestMappingInfo(
- new PatternsRequestCondition("/foo*"), null, null, null, null, null, null);
+ RequestMappingInfo info = paths("/foo*", "/bar").build();
+ RequestMappingInfo expected = paths("/foo*").build();
assertEquals(expected, info.getMatchingCondition(request));
- info = new RequestMappingInfo(
- new PatternsRequestCondition("/**", "/foo*", "/foo"), null, null, null, null, null, null);
- expected = new RequestMappingInfo(
- new PatternsRequestCondition("/foo", "/foo*", "/**"), null, null, null, null, null, null);
+ info = paths("/**", "/foo*", "/foo").build();
+ expected = paths("/foo", "/foo*", "/**").build();
assertEquals(expected, info.getMatchingCondition(request));
}
@@ -80,17 +79,12 @@ public class RequestMappingInfoTests {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
request.setParameter("foo", "bar");
- RequestMappingInfo info =
- new RequestMappingInfo(
- new PatternsRequestCondition("/foo"), null,
- new ParamsRequestCondition("foo=bar"), null, null, null, null);
+ RequestMappingInfo info = paths("/foo").params("foo=bar").build();
RequestMappingInfo match = info.getMatchingCondition(request);
assertNotNull(match);
- info = new RequestMappingInfo(
- new PatternsRequestCondition("/foo"), null,
- new ParamsRequestCondition("foo!=bar"), null, null, null, null);
+ info = paths("/foo").params("foo!=bar").build();
match = info.getMatchingCondition(request);
assertNull(match);
@@ -101,17 +95,12 @@ public class RequestMappingInfoTests {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
request.addHeader("foo", "bar");
- RequestMappingInfo info =
- new RequestMappingInfo(
- new PatternsRequestCondition("/foo"), null, null,
- new HeadersRequestCondition("foo=bar"), null, null, null);
+ RequestMappingInfo info = paths("/foo").headers("foo=bar").build();
RequestMappingInfo match = info.getMatchingCondition(request);
assertNotNull(match);
- info = new RequestMappingInfo(
- new PatternsRequestCondition("/foo"), null, null,
- new HeadersRequestCondition("foo!=bar"), null, null, null);
+ info = paths("/foo").headers("foo!=bar").build();
match = info.getMatchingCondition(request);
assertNull(match);
@@ -122,17 +111,12 @@ public class RequestMappingInfoTests {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
request.setContentType("text/plain");
- RequestMappingInfo info =
- new RequestMappingInfo(
- new PatternsRequestCondition("/foo"), null, null, null,
- new ConsumesRequestCondition("text/plain"), null, null);
+ RequestMappingInfo info = paths("/foo").consumes("text/plain").build();
RequestMappingInfo match = info.getMatchingCondition(request);
assertNotNull(match);
- info = new RequestMappingInfo(
- new PatternsRequestCondition("/foo"), null, null, null,
- new ConsumesRequestCondition("application/xml"), null, null);
+ info = paths("/foo").consumes("application/xml").build();
match = info.getMatchingCondition(request);
assertNull(match);
@@ -143,17 +127,12 @@ public class RequestMappingInfoTests {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
request.addHeader("Accept", "text/plain");
- RequestMappingInfo info =
- new RequestMappingInfo(
- new PatternsRequestCondition("/foo"), null, null, null, null,
- new ProducesRequestCondition("text/plain"), null);
+ RequestMappingInfo info = paths("/foo").produces("text/plain").build();
RequestMappingInfo match = info.getMatchingCondition(request);
assertNotNull(match);
- info = new RequestMappingInfo(
- new PatternsRequestCondition("/foo"), null, null, null, null,
- new ProducesRequestCondition("application/xml"), null);
+ info = paths("/foo").produces("application/xml").build();
match = info.getMatchingCondition(request);
assertNull(match);
@@ -164,153 +143,123 @@ public class RequestMappingInfoTests {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
request.setParameter("foo", "bar");
- RequestMappingInfo info =
- new RequestMappingInfo(
- new PatternsRequestCondition("/foo"), null, null, null, null, null,
- new ParamsRequestCondition("foo=bar"));
+ RequestMappingInfo info = paths("/foo").params("foo=bar").build();
RequestMappingInfo match = info.getMatchingCondition(request);
assertNotNull(match);
- info = new RequestMappingInfo(
- new PatternsRequestCondition("/foo"), null,
- new ParamsRequestCondition("foo!=bar"), null, null, null,
- new ParamsRequestCondition("foo!=bar"));
+ info = paths("/foo").params("foo!=bar").params("foo!=bar").build();
match = info.getMatchingCondition(request);
assertNull(match);
}
@Test
- public void compareTwoHttpMethodsOneParam() {
- RequestMappingInfo none = new RequestMappingInfo(null, null, null, null, null, null, null);
- RequestMappingInfo oneMethod =
- new RequestMappingInfo(null,
- new RequestMethodsRequestCondition(RequestMethod.GET), null, null, null, null, null);
- RequestMappingInfo oneMethodOneParam =
- new RequestMappingInfo(null,
- new RequestMethodsRequestCondition(RequestMethod.GET),
- new ParamsRequestCondition("foo"), null, null, null, null);
-
- Comparator<RequestMappingInfo> comparator = new Comparator<RequestMappingInfo>() {
- @Override
- public int compare(RequestMappingInfo info, RequestMappingInfo otherInfo) {
- return info.compareTo(otherInfo, new MockHttpServletRequest());
- }
- };
-
- List<RequestMappingInfo> list = asList(none, oneMethod, oneMethodOneParam);
+ public void compareToWithImpicitVsExplicitHttpMethodDeclaration() {
+ RequestMappingInfo noMethods = paths().build();
+ RequestMappingInfo oneMethod = paths().methods(GET).build();
+ RequestMappingInfo oneMethodOneParam = paths().methods(GET).params("foo").build();
+
+ Comparator<RequestMappingInfo> comparator =
+ (info, otherInfo) -> info.compareTo(otherInfo, new MockHttpServletRequest());
+
+ List<RequestMappingInfo> list = asList(noMethods, oneMethod, oneMethodOneParam);
Collections.shuffle(list);
Collections.sort(list, comparator);
assertEquals(oneMethodOneParam, list.get(0));
assertEquals(oneMethod, list.get(1));
- assertEquals(none, list.get(2));
+ assertEquals(noMethods, list.get(2));
+ }
+
+ @Test // SPR-14383
+ public void compareToWithHttpHeadMapping() {
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.setMethod("HEAD");
+ request.addHeader("Accept", "application/json");
+
+ RequestMappingInfo noMethods = paths().build();
+ RequestMappingInfo getMethod = paths().methods(GET).produces("application/json").build();
+ RequestMappingInfo headMethod = paths().methods(HEAD).build();
+
+ Comparator<RequestMappingInfo> comparator = (info, otherInfo) -> info.compareTo(otherInfo, request);
+
+ List<RequestMappingInfo> list = asList(noMethods, getMethod, headMethod);
+ Collections.shuffle(list);
+ Collections.sort(list, comparator);
+
+ assertEquals(headMethod, list.get(0));
+ assertEquals(getMethod, list.get(1));
+ assertEquals(noMethods, list.get(2));
}
@Test
public void equals() {
- RequestMappingInfo info1 = new RequestMappingInfo(
- new PatternsRequestCondition("/foo"),
- new RequestMethodsRequestCondition(RequestMethod.GET),
- new ParamsRequestCondition("foo=bar"),
- new HeadersRequestCondition("foo=bar"),
- new ConsumesRequestCondition("text/plain"),
- new ProducesRequestCondition("text/plain"),
- new ParamsRequestCondition("customFoo=customBar"));
-
- RequestMappingInfo info2 = new RequestMappingInfo(
- new PatternsRequestCondition("/foo"),
- new RequestMethodsRequestCondition(RequestMethod.GET),
- new ParamsRequestCondition("foo=bar"),
- new HeadersRequestCondition("foo=bar"),
- new ConsumesRequestCondition("text/plain"),
- new ProducesRequestCondition("text/plain"),
- new ParamsRequestCondition("customFoo=customBar"));
+ RequestMappingInfo info1 = paths("/foo").methods(GET)
+ .params("foo=bar", "customFoo=customBar").headers("foo=bar")
+ .consumes("text/plain").produces("text/plain")
+ .build();
+
+ RequestMappingInfo info2 = paths("/foo").methods(GET)
+ .params("foo=bar", "customFoo=customBar").headers("foo=bar")
+ .consumes("text/plain").produces("text/plain")
+ .build();
assertEquals(info1, info2);
assertEquals(info1.hashCode(), info2.hashCode());
- info2 = new RequestMappingInfo(
- new PatternsRequestCondition("/foo", "/NOOOOOO"),
- new RequestMethodsRequestCondition(RequestMethod.GET),
- new ParamsRequestCondition("foo=bar"),
- new HeadersRequestCondition("foo=bar"),
- new ConsumesRequestCondition("text/plain"),
- new ProducesRequestCondition("text/plain"),
- new ParamsRequestCondition("customFoo=customBar"));
+ info2 = paths("/foo", "/NOOOOOO").methods(GET)
+ .params("foo=bar", "customFoo=customBar").headers("foo=bar")
+ .consumes("text/plain").produces("text/plain")
+ .build();
assertFalse(info1.equals(info2));
assertNotEquals(info1.hashCode(), info2.hashCode());
- info2 = new RequestMappingInfo(
- new PatternsRequestCondition("/foo"),
- new RequestMethodsRequestCondition(RequestMethod.GET, RequestMethod.POST),
- new ParamsRequestCondition("foo=bar"),
- new HeadersRequestCondition("foo=bar"),
- new ConsumesRequestCondition("text/plain"),
- new ProducesRequestCondition("text/plain"),
- new ParamsRequestCondition("customFoo=customBar"));
+ info2 = paths("/foo").methods(GET, RequestMethod.POST)
+ .params("foo=bar", "customFoo=customBar").headers("foo=bar")
+ .consumes("text/plain").produces("text/plain")
+ .build();
assertFalse(info1.equals(info2));
assertNotEquals(info1.hashCode(), info2.hashCode());
- info2 = new RequestMappingInfo(
- new PatternsRequestCondition("/foo"),
- new RequestMethodsRequestCondition(RequestMethod.GET),
- new ParamsRequestCondition("/NOOOOOO"),
- new HeadersRequestCondition("foo=bar"),
- new ConsumesRequestCondition("text/plain"),
- new ProducesRequestCondition("text/plain"),
- new ParamsRequestCondition("customFoo=customBar"));
+ info2 = paths("/foo").methods(GET)
+ .params("/NOOOOOO", "customFoo=customBar").headers("foo=bar")
+ .consumes("text/plain").produces("text/plain")
+ .build();
assertFalse(info1.equals(info2));
assertNotEquals(info1.hashCode(), info2.hashCode());
- info2 = new RequestMappingInfo(
- new PatternsRequestCondition("/foo"),
- new RequestMethodsRequestCondition(RequestMethod.GET),
- new ParamsRequestCondition("foo=bar"),
- new HeadersRequestCondition("/NOOOOOO"),
- new ConsumesRequestCondition("text/plain"),
- new ProducesRequestCondition("text/plain"),
- new ParamsRequestCondition("customFoo=customBar"));
+ info2 = paths("/foo").methods(GET)
+ .params("foo=bar", "customFoo=customBar").headers("/NOOOOOO")
+ .consumes("text/plain").produces("text/plain")
+ .build();
assertFalse(info1.equals(info2));
assertNotEquals(info1.hashCode(), info2.hashCode());
- info2 = new RequestMappingInfo(
- new PatternsRequestCondition("/foo"),
- new RequestMethodsRequestCondition(RequestMethod.GET),
- new ParamsRequestCondition("foo=bar"),
- new HeadersRequestCondition("foo=bar"),
- new ConsumesRequestCondition("text/NOOOOOO"),
- new ProducesRequestCondition("text/plain"),
- new ParamsRequestCondition("customFoo=customBar"));
+ info2 = paths("/foo").methods(GET)
+ .params("foo=bar", "customFoo=customBar").headers("foo=bar")
+ .consumes("text/NOOOOOO").produces("text/plain")
+ .build();
assertFalse(info1.equals(info2));
assertNotEquals(info1.hashCode(), info2.hashCode());
- info2 = new RequestMappingInfo(
- new PatternsRequestCondition("/foo"),
- new RequestMethodsRequestCondition(RequestMethod.GET),
- new ParamsRequestCondition("foo=bar"),
- new HeadersRequestCondition("foo=bar"),
- new ConsumesRequestCondition("text/plain"),
- new ProducesRequestCondition("text/NOOOOOO"),
- new ParamsRequestCondition("customFoo=customBar"));
+ info2 = paths("/foo").methods(GET)
+ .params("foo=bar", "customFoo=customBar").headers("foo=bar")
+ .consumes("text/plain").produces("text/NOOOOOO")
+ .build();
assertFalse(info1.equals(info2));
assertNotEquals(info1.hashCode(), info2.hashCode());
- info2 = new RequestMappingInfo(
- new PatternsRequestCondition("/foo"),
- new RequestMethodsRequestCondition(RequestMethod.GET),
- new ParamsRequestCondition("foo=bar"),
- new HeadersRequestCondition("foo=bar"),
- new ConsumesRequestCondition("text/plain"),
- new ProducesRequestCondition("text/plain"),
- new ParamsRequestCondition("customFoo=NOOOOOO"));
+ info2 = paths("/foo").methods(GET)
+ .params("foo=bar", "customFoo=NOOOOOO").headers("foo=bar")
+ .consumes("text/plain").produces("text/plain")
+ .build();
assertFalse(info1.equals(info2));
assertNotEquals(info1.hashCode(), info2.hashCode());
@@ -322,17 +271,13 @@ public class RequestMappingInfoTests {
request.addHeader(HttpHeaders.ORIGIN, "http://domain.com");
request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "POST");
- RequestMappingInfo info = new RequestMappingInfo(
- new PatternsRequestCondition("/foo"), new RequestMethodsRequestCondition(RequestMethod.POST), null,
- null, null, null, null);
+ RequestMappingInfo info = paths("/foo").methods(RequestMethod.POST).build();
RequestMappingInfo match = info.getMatchingCondition(request);
assertNotNull(match);
- info = new RequestMappingInfo(
- new PatternsRequestCondition("/foo"), new RequestMethodsRequestCondition(RequestMethod.OPTIONS), null,
- null, null, null, null);
+ info = paths("/foo").methods(RequestMethod.OPTIONS).build();
match = info.getMatchingCondition(request);
- assertNotNull(match);
+ assertNull("Pre-flight should match the ACCESS_CONTROL_REQUEST_METHOD", match);
}
}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/AbstractRequestAttributesArgumentResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/AbstractRequestAttributesArgumentResolverTests.java
new file mode 100644
index 00000000..cc2dc56e
--- /dev/null
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/AbstractRequestAttributesArgumentResolverTests.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.servlet.mvc.method.annotation;
+
+import java.lang.reflect.Method;
+import java.util.Optional;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.springframework.core.DefaultParameterNameDiscoverer;
+import org.springframework.core.GenericTypeResolver;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.annotation.SynthesizingMethodParameter;
+import org.springframework.core.convert.support.DefaultConversionService;
+import org.springframework.mock.web.test.MockHttpServletRequest;
+import org.springframework.mock.web.test.MockHttpServletResponse;
+import org.springframework.web.bind.ServletRequestBindingException;
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.bind.annotation.RequestAttribute;
+import org.springframework.web.bind.annotation.SessionAttribute;
+import org.springframework.web.bind.support.WebDataBinderFactory;
+import org.springframework.web.bind.support.WebRequestDataBinder;
+import org.springframework.web.context.request.ServletWebRequest;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+import org.springframework.web.method.support.ModelAndViewContainer;
+
+import static org.junit.Assert.*;
+import static org.mockito.BDDMockito.*;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Base class for {@code @RequestAttribute} and {@code @SessionAttribute} method
+ * method argument resolution tests.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3
+ */
+public abstract class AbstractRequestAttributesArgumentResolverTests {
+
+ private ServletWebRequest webRequest;
+
+ private HandlerMethodArgumentResolver resolver;
+
+ private Method handleMethod;
+
+
+ @Before
+ public void setUp() throws Exception {
+ HttpServletRequest request = new MockHttpServletRequest();
+ HttpServletResponse response = new MockHttpServletResponse();
+ this.webRequest = new ServletWebRequest(request, response);
+ this.resolver = createResolver();
+ this.handleMethod = AbstractRequestAttributesArgumentResolverTests.class
+ .getDeclaredMethod(getHandleMethodName(), Foo.class, Foo.class, Foo.class, Optional.class);
+ }
+
+
+ protected abstract HandlerMethodArgumentResolver createResolver();
+
+ protected abstract String getHandleMethodName();
+
+ protected abstract int getScope();
+
+
+ @Test
+ public void supportsParameter() throws Exception {
+ assertTrue(this.resolver.supportsParameter(new MethodParameter(this.handleMethod, 0)));
+ assertFalse(this.resolver.supportsParameter(new MethodParameter(this.handleMethod, -1)));
+ }
+
+ @Test
+ public void resolve() throws Exception {
+ MethodParameter param = initMethodParameter(0);
+ try {
+ testResolveArgument(param);
+ fail("Should be required by default");
+ }
+ catch (ServletRequestBindingException ex) {
+ assertTrue(ex.getMessage().startsWith("Missing "));
+ }
+
+ Foo foo = new Foo();
+ this.webRequest.setAttribute("foo", foo, getScope());
+ assertSame(foo, testResolveArgument(param));
+ }
+
+ @Test
+ public void resolveWithName() throws Exception {
+ MethodParameter param = initMethodParameter(1);
+ Foo foo = new Foo();
+ this.webRequest.setAttribute("specialFoo", foo, getScope());
+ assertSame(foo, testResolveArgument(param));
+ }
+
+ @Test
+ public void resolveNotRequired() throws Exception {
+ MethodParameter param = initMethodParameter(2);
+ assertNull(testResolveArgument(param));
+
+ Foo foo = new Foo();
+ this.webRequest.setAttribute("foo", foo, getScope());
+ assertSame(foo, testResolveArgument(param));
+ }
+
+ @Test
+ public void resolveOptional() throws Exception {
+ WebDataBinder dataBinder = new WebRequestDataBinder(null);
+ dataBinder.setConversionService(new DefaultConversionService());
+ WebDataBinderFactory factory = mock(WebDataBinderFactory.class);
+ given(factory.createBinder(this.webRequest, null, "foo")).willReturn(dataBinder);
+
+ MethodParameter param = initMethodParameter(3);
+ Object actual = testResolveArgument(param, factory);
+ assertNotNull(actual);
+ assertEquals(Optional.class, actual.getClass());
+ assertFalse(((Optional) actual).isPresent());
+
+ Foo foo = new Foo();
+ this.webRequest.setAttribute("foo", foo, getScope());
+
+ actual = testResolveArgument(param, factory);
+ assertNotNull(actual);
+ assertEquals(Optional.class, actual.getClass());
+ assertTrue(((Optional) actual).isPresent());
+ assertSame(foo, ((Optional) actual).get());
+ }
+
+ private Object testResolveArgument(MethodParameter param) throws Exception {
+ return testResolveArgument(param, null);
+ }
+
+ private Object testResolveArgument(MethodParameter param, WebDataBinderFactory factory) throws Exception {
+ ModelAndViewContainer mavContainer = new ModelAndViewContainer();
+ return this.resolver.resolveArgument(param, mavContainer, this.webRequest, factory);
+ }
+
+ private MethodParameter initMethodParameter(int parameterIndex) {
+ MethodParameter param = new SynthesizingMethodParameter(this.handleMethod, parameterIndex);
+ param.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
+ GenericTypeResolver.resolveParameterType(param, this.resolver.getClass());
+ return param;
+ }
+
+
+ @SuppressWarnings("unused")
+ private void handleWithRequestAttribute(
+ @RequestAttribute Foo foo,
+ @RequestAttribute("specialFoo") Foo namedFoo,
+ @RequestAttribute(name="foo", required = false) Foo notRequiredFoo,
+ @RequestAttribute(name="foo") Optional<Foo> optionalFoo) {
+ }
+
+ @SuppressWarnings("unused")
+ private void handleWithSessionAttribute(
+ @SessionAttribute Foo foo,
+ @SessionAttribute("specialFoo") Foo namedFoo,
+ @SessionAttribute(name="foo", required = false) Foo notRequiredFoo,
+ @SessionAttribute(name="foo") Optional<Foo> optionalFoo) {
+ }
+
+
+ private static class Foo {
+ }
+
+}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/CrossOriginTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/CrossOriginTests.java
index 482adf87..e1ee78a5 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/CrossOriginTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/CrossOriginTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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 java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.Arrays;
+import java.util.Properties;
import org.junit.Before;
import org.junit.Rule;
@@ -29,8 +30,10 @@ import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.DirectFieldAccessor;
+import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.http.HttpHeaders;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.stereotype.Controller;
@@ -72,9 +75,15 @@ public class CrossOriginTests {
@Before
public void setUp() {
+ StaticWebApplicationContext wac = new StaticWebApplicationContext();
+ Properties props = new Properties();
+ props.setProperty("myOrigin", "http://example.com");
+ wac.getEnvironment().getPropertySources().addFirst(new PropertiesPropertySource("ps", props));
+ wac.registerSingleton("ppc", PropertySourcesPlaceholderConfigurer.class);
+ wac.refresh();
+
this.handlerMapping.setRemoveSemicolonContent(false);
- this.handlerMapping.setApplicationContext(new StaticWebApplicationContext());
- this.handlerMapping.afterPropertiesSet();
+ wac.getAutowireCapableBeanFactory().initializeBean(this.handlerMapping, "hm");
this.request.setMethod("GET");
this.request.addHeader(HttpHeaders.ORIGIN, "http://domain.com/");
@@ -112,10 +121,10 @@ public class CrossOriginTests {
HandlerExecutionChain chain = this.handlerMapping.getHandler(request);
CorsConfiguration config = getCorsConfiguration(chain, false);
assertNotNull(config);
- assertArrayEquals(new String[]{"GET"}, config.getAllowedMethods().toArray());
- assertArrayEquals(new String[]{"*"}, config.getAllowedOrigins().toArray());
+ assertArrayEquals(new String[] {"GET"}, config.getAllowedMethods().toArray());
+ assertArrayEquals(new String[] {"*"}, config.getAllowedOrigins().toArray());
assertTrue(config.getAllowCredentials());
- assertArrayEquals(new String[]{"*"}, config.getAllowedHeaders().toArray());
+ assertArrayEquals(new String[] {"*"}, config.getAllowedHeaders().toArray());
assertTrue(CollectionUtils.isEmpty(config.getExposedHeaders()));
assertEquals(new Long(1800), config.getMaxAge());
}
@@ -127,10 +136,10 @@ public class CrossOriginTests {
HandlerExecutionChain chain = this.handlerMapping.getHandler(request);
CorsConfiguration config = getCorsConfiguration(chain, false);
assertNotNull(config);
- assertArrayEquals(new String[]{"DELETE"}, config.getAllowedMethods().toArray());
- assertArrayEquals(new String[]{"http://site1.com", "http://site2.com"}, config.getAllowedOrigins().toArray());
- assertArrayEquals(new String[]{"header1", "header2"}, config.getAllowedHeaders().toArray());
- assertArrayEquals(new String[]{"header3", "header4"}, config.getExposedHeaders().toArray());
+ assertArrayEquals(new String[] {"DELETE"}, config.getAllowedMethods().toArray());
+ assertArrayEquals(new String[] {"http://site1.com", "http://site2.com"}, config.getAllowedOrigins().toArray());
+ assertArrayEquals(new String[] {"header1", "header2"}, config.getAllowedHeaders().toArray());
+ assertArrayEquals(new String[] {"header3", "header4"}, config.getExposedHeaders().toArray());
assertEquals(new Long(123), config.getMaxAge());
assertFalse(config.getAllowCredentials());
}
@@ -147,6 +156,17 @@ public class CrossOriginTests {
}
@Test
+ public void customOriginDefinedViaPlaceholder() throws Exception {
+ this.handlerMapping.registerHandler(new MethodLevelController());
+ this.request.setRequestURI("/someOrigin");
+ HandlerExecutionChain chain = this.handlerMapping.getHandler(request);
+ CorsConfiguration config = getCorsConfiguration(chain, false);
+ assertNotNull(config);
+ assertEquals(Arrays.asList("http://example.com"), config.getAllowedOrigins());
+ assertTrue(config.getAllowCredentials());
+ }
+
+ @Test
public void bogusAllowCredentialsValue() throws Exception {
exception.expect(IllegalStateException.class);
exception.expectMessage(containsString("@CrossOrigin's allowCredentials"));
@@ -162,24 +182,24 @@ public class CrossOriginTests {
HandlerExecutionChain chain = this.handlerMapping.getHandler(request);
CorsConfiguration config = getCorsConfiguration(chain, false);
assertNotNull(config);
- assertArrayEquals(new String[]{"GET"}, config.getAllowedMethods().toArray());
- assertArrayEquals(new String[]{"*"}, config.getAllowedOrigins().toArray());
+ assertArrayEquals(new String[] {"GET"}, config.getAllowedMethods().toArray());
+ assertArrayEquals(new String[] {"*"}, config.getAllowedOrigins().toArray());
assertFalse(config.getAllowCredentials());
this.request.setRequestURI("/bar");
chain = this.handlerMapping.getHandler(request);
config = getCorsConfiguration(chain, false);
assertNotNull(config);
- assertArrayEquals(new String[]{"GET"}, config.getAllowedMethods().toArray());
- assertArrayEquals(new String[]{"*"}, config.getAllowedOrigins().toArray());
+ assertArrayEquals(new String[] {"GET"}, config.getAllowedMethods().toArray());
+ assertArrayEquals(new String[] {"*"}, config.getAllowedOrigins().toArray());
assertFalse(config.getAllowCredentials());
this.request.setRequestURI("/baz");
chain = this.handlerMapping.getHandler(request);
config = getCorsConfiguration(chain, false);
assertNotNull(config);
- assertArrayEquals(new String[]{"GET"}, config.getAllowedMethods().toArray());
- assertArrayEquals(new String[]{"*"}, config.getAllowedOrigins().toArray());
+ assertArrayEquals(new String[] {"GET"}, config.getAllowedMethods().toArray());
+ assertArrayEquals(new String[] {"*"}, config.getAllowedOrigins().toArray());
assertTrue(config.getAllowCredentials());
}
@@ -191,8 +211,8 @@ public class CrossOriginTests {
HandlerExecutionChain chain = this.handlerMapping.getHandler(request);
CorsConfiguration config = getCorsConfiguration(chain, false);
assertNotNull(config);
- assertArrayEquals(new String[]{"GET"}, config.getAllowedMethods().toArray());
- assertArrayEquals(new String[]{"http://foo.com"}, config.getAllowedOrigins().toArray());
+ assertArrayEquals(new String[] {"GET"}, config.getAllowedMethods().toArray());
+ assertArrayEquals(new String[] {"http://foo.com"}, config.getAllowedOrigins().toArray());
assertTrue(config.getAllowCredentials());
}
@@ -204,8 +224,8 @@ public class CrossOriginTests {
HandlerExecutionChain chain = this.handlerMapping.getHandler(request);
CorsConfiguration config = getCorsConfiguration(chain, false);
assertNotNull(config);
- assertArrayEquals(new String[]{"GET"}, config.getAllowedMethods().toArray());
- assertArrayEquals(new String[]{"http://foo.com"}, config.getAllowedOrigins().toArray());
+ assertArrayEquals(new String[] {"GET"}, config.getAllowedMethods().toArray());
+ assertArrayEquals(new String[] {"http://foo.com"}, config.getAllowedOrigins().toArray());
assertTrue(config.getAllowCredentials());
}
@@ -218,10 +238,10 @@ public class CrossOriginTests {
HandlerExecutionChain chain = this.handlerMapping.getHandler(request);
CorsConfiguration config = getCorsConfiguration(chain, true);
assertNotNull(config);
- assertArrayEquals(new String[]{"GET"}, config.getAllowedMethods().toArray());
- assertArrayEquals(new String[]{"*"}, config.getAllowedOrigins().toArray());
+ assertArrayEquals(new String[] {"GET"}, config.getAllowedMethods().toArray());
+ assertArrayEquals(new String[] {"*"}, config.getAllowedOrigins().toArray());
assertTrue(config.getAllowCredentials());
- assertArrayEquals(new String[]{"*"}, config.getAllowedHeaders().toArray());
+ assertArrayEquals(new String[] {"*"}, config.getAllowedHeaders().toArray());
assertTrue(CollectionUtils.isEmpty(config.getExposedHeaders()));
assertEquals(new Long(1800), config.getMaxAge());
}
@@ -236,9 +256,9 @@ public class CrossOriginTests {
HandlerExecutionChain chain = this.handlerMapping.getHandler(request);
CorsConfiguration config = getCorsConfiguration(chain, true);
assertNotNull(config);
- assertArrayEquals(new String[]{"*"}, config.getAllowedMethods().toArray());
- assertArrayEquals(new String[]{"*"}, config.getAllowedOrigins().toArray());
- assertArrayEquals(new String[]{"*"}, config.getAllowedHeaders().toArray());
+ assertArrayEquals(new String[] {"*"}, config.getAllowedMethods().toArray());
+ assertArrayEquals(new String[] {"*"}, config.getAllowedOrigins().toArray());
+ assertArrayEquals(new String[] {"*"}, config.getAllowedHeaders().toArray());
assertTrue(config.getAllowCredentials());
assertTrue(CollectionUtils.isEmpty(config.getExposedHeaders()));
assertNull(config.getMaxAge());
@@ -253,9 +273,9 @@ public class CrossOriginTests {
HandlerExecutionChain chain = this.handlerMapping.getHandler(request);
CorsConfiguration config = getCorsConfiguration(chain, true);
assertNotNull(config);
- assertArrayEquals(new String[]{"*"}, config.getAllowedMethods().toArray());
- assertArrayEquals(new String[]{"*"}, config.getAllowedOrigins().toArray());
- assertArrayEquals(new String[]{"*"}, config.getAllowedHeaders().toArray());
+ assertArrayEquals(new String[] {"*"}, config.getAllowedMethods().toArray());
+ assertArrayEquals(new String[] {"*"}, config.getAllowedOrigins().toArray());
+ assertArrayEquals(new String[] {"*"}, config.getAllowedHeaders().toArray());
assertTrue(config.getAllowCredentials());
assertTrue(CollectionUtils.isEmpty(config.getExposedHeaders()));
assertNull(config.getMaxAge());
@@ -343,8 +363,14 @@ public class CrossOriginTests {
@RequestMapping("/customOrigin")
public void customOriginDefinedViaValueAttribute() {
}
+
+ @CrossOrigin("${myOrigin}")
+ @RequestMapping("/someOrigin")
+ public void customOriginDefinedViaPlaceholder() {
+ }
}
+
@Controller
private static class MethodLevelControllerWithBogusAllowCredentialsValue {
@@ -354,6 +380,7 @@ public class CrossOriginTests {
}
}
+
@Controller
@CrossOrigin(allowCredentials = "false")
private static class ClassLevelController {
@@ -374,14 +401,18 @@ public class CrossOriginTests {
}
+
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@CrossOrigin
private @interface ComposedCrossOrigin {
+
String[] origins() default {};
+
String allowCredentials() default "";
}
+
@Controller
@ComposedCrossOrigin(origins = "http://foo.com", allowCredentials = "true")
private static class ClassLevelMappingWithComposedAnnotation {
@@ -401,6 +432,7 @@ public class CrossOriginTests {
}
}
+
private static class TestRequestMappingInfoHandlerMapping extends RequestMappingHandlerMapping {
public void registerHandler(Object handler) {
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/DeferredResultReturnValueHandlerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/DeferredResultReturnValueHandlerTests.java
new file mode 100644
index 00000000..9e902ac6
--- /dev/null
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/DeferredResultReturnValueHandlerTests.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.web.servlet.mvc.method.annotation;
+
+import java.lang.reflect.Method;
+import java.util.concurrent.CompletableFuture;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.mock.web.test.MockHttpServletRequest;
+import org.springframework.mock.web.test.MockHttpServletResponse;
+import org.springframework.util.concurrent.ListenableFuture;
+import org.springframework.util.concurrent.SettableListenableFuture;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.context.request.ServletWebRequest;
+import org.springframework.web.context.request.async.AsyncWebRequest;
+import org.springframework.web.context.request.async.DeferredResult;
+import org.springframework.web.context.request.async.StandardServletAsyncWebRequest;
+import org.springframework.web.context.request.async.WebAsyncUtils;
+import org.springframework.web.method.support.ModelAndViewContainer;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Unit tests for {@link DeferredResultMethodReturnValueHandler}.
+ * @author Rossen Stoyanchev
+ */
+public class DeferredResultReturnValueHandlerTests {
+
+ private DeferredResultMethodReturnValueHandler handler;
+
+ private MockHttpServletRequest request;
+
+ private NativeWebRequest webRequest;
+
+
+ @Before
+ public void setUp() throws Exception {
+ this.handler = new DeferredResultMethodReturnValueHandler();
+ this.request = new MockHttpServletRequest();
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ this.webRequest = new ServletWebRequest(this.request, response);
+
+ AsyncWebRequest asyncWebRequest = new StandardServletAsyncWebRequest(this.request, response);
+ WebAsyncUtils.getAsyncManager(this.webRequest).setAsyncWebRequest(asyncWebRequest);
+ this.request.setAsyncSupported(true);
+ }
+
+
+ @Test
+ public void supportsReturnType() throws Exception {
+ assertTrue(this.handler.supportsReturnType(returnType("handleDeferredResult")));
+ assertTrue(this.handler.supportsReturnType(returnType("handleListenableFuture")));
+ assertTrue(this.handler.supportsReturnType(returnType("handleCompletableFuture")));
+ assertFalse(this.handler.supportsReturnType(returnType("handleString")));
+ }
+
+ @Test
+ public void deferredResult() throws Exception {
+ MethodParameter returnType = returnType("handleDeferredResult");
+ DeferredResult<String> deferredResult = new DeferredResult<>();
+ handleReturnValue(deferredResult, returnType);
+
+ assertTrue(this.request.isAsyncStarted());
+ assertFalse(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
+
+ deferredResult.setResult("foo");
+ assertTrue(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
+ assertEquals("foo", WebAsyncUtils.getAsyncManager(this.webRequest).getConcurrentResult());
+ }
+
+ @Test
+ public void deferredResultWitError() throws Exception {
+ MethodParameter returnType = returnType("handleDeferredResult");
+ DeferredResult<String> deferredResult = new DeferredResult<>();
+ handleReturnValue(deferredResult, returnType);
+
+ assertTrue(this.request.isAsyncStarted());
+ assertFalse(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
+
+ IllegalStateException ex = new IllegalStateException();
+ deferredResult.setErrorResult(ex);
+ assertTrue(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
+ assertSame(ex, WebAsyncUtils.getAsyncManager(this.webRequest).getConcurrentResult());
+ }
+
+ @Test
+ public void listenableFuture() throws Exception {
+ MethodParameter returnType = returnType("handleListenableFuture");
+ SettableListenableFuture<String> future = new SettableListenableFuture<>();
+ handleReturnValue(future, returnType);
+
+ assertTrue(this.request.isAsyncStarted());
+ assertFalse(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
+
+ future.set("foo");
+ assertTrue(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
+ assertEquals("foo", WebAsyncUtils.getAsyncManager(this.webRequest).getConcurrentResult());
+ }
+
+ @Test
+ public void listenableFutureWithError() throws Exception {
+ MethodParameter returnType = returnType("handleListenableFuture");
+ SettableListenableFuture<String> future = new SettableListenableFuture<>();
+ handleReturnValue(future, returnType);
+
+ assertTrue(this.request.isAsyncStarted());
+ assertFalse(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
+
+ IllegalStateException ex = new IllegalStateException();
+ future.setException(ex);
+ assertTrue(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
+ assertSame(ex, WebAsyncUtils.getAsyncManager(this.webRequest).getConcurrentResult());
+ }
+
+ @Test
+ public void completableFuture() throws Exception {
+ MethodParameter returnType = returnType("handleCompletableFuture");
+ SettableListenableFuture<String> future = new SettableListenableFuture<>();
+ handleReturnValue(future, returnType);
+
+ assertTrue(this.request.isAsyncStarted());
+ assertFalse(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
+
+ future.set("foo");
+ assertTrue(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
+ assertEquals("foo", WebAsyncUtils.getAsyncManager(this.webRequest).getConcurrentResult());
+ }
+
+ @Test
+ public void completableFutureWithError() throws Exception {
+ MethodParameter returnType = returnType("handleCompletableFuture");
+ CompletableFuture<String> future = new CompletableFuture<>();
+ handleReturnValue(future, returnType);
+
+ assertTrue(this.request.isAsyncStarted());
+ assertFalse(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
+
+ IllegalStateException ex = new IllegalStateException();
+ future.completeExceptionally(ex);
+ assertTrue(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
+ assertSame(ex, WebAsyncUtils.getAsyncManager(this.webRequest).getConcurrentResult());
+ }
+
+
+ private void handleReturnValue(Object returnValue, MethodParameter returnType) throws Exception {
+ ModelAndViewContainer mavContainer = new ModelAndViewContainer();
+ this.handler.handleReturnValue(returnValue, returnType, mavContainer, this.webRequest);
+ }
+
+ private MethodParameter returnType(String methodName) throws NoSuchMethodException {
+ Method method = TestController.class.getDeclaredMethod(methodName);
+ return new MethodParameter(method, -1);
+ }
+
+
+ @SuppressWarnings("unused")
+ private static class TestController {
+
+ private String handleString() {
+ return null;
+ }
+
+ private DeferredResult<String> handleDeferredResult() {
+ return null;
+ }
+
+ private ListenableFuture<String> handleListenableFuture() {
+ return null;
+ }
+
+ private CompletableFuture<String> handleCompletableFuture() {
+ return null;
+ }
+
+
+ }
+
+}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolverTests.java
index d64e8220..f633f73b 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolverTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolverTests.java
@@ -25,6 +25,7 @@ import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
+import org.springframework.beans.FatalBeanException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -34,14 +35,15 @@ import org.springframework.mock.web.test.MockHttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.ClassUtils;
-import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.ModelMethodProcessor;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.util.NestedServletException;
import static org.junit.Assert.*;
@@ -53,6 +55,7 @@ import static org.junit.Assert.*;
* @author Kazuki Shimizu
* @since 3.1
*/
+@SuppressWarnings("unused")
public class ExceptionHandlerExceptionResolverTests {
private static int RESOLVER_COUNT;
@@ -77,11 +80,13 @@ public class ExceptionHandlerExceptionResolverTests {
@Before
public void setup() throws Exception {
this.resolver = new ExceptionHandlerExceptionResolver();
+ this.resolver.setWarnLogCategory(this.resolver.getClass().getName());
this.request = new MockHttpServletRequest("GET", "/");
this.response = new MockHttpServletResponse();
}
+ @SuppressWarnings("ConstantConditions")
@Test
public void nullHandler() {
Object handler = null;
@@ -233,6 +238,38 @@ public class ExceptionHandlerExceptionResolverTests {
}
@Test
+ public void resolveExceptionWithAssertionError() throws Exception {
+ AnnotationConfigApplicationContext cxt = new AnnotationConfigApplicationContext(MyConfig.class);
+ this.resolver.setApplicationContext(cxt);
+ this.resolver.afterPropertiesSet();
+
+ AssertionError err = new AssertionError("argh");
+ HandlerMethod handlerMethod = new HandlerMethod(new ResponseBodyController(), "handle");
+ ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod,
+ new NestedServletException("Handler dispatch failed", err));
+
+ assertNotNull("Exception was not handled", mav);
+ assertTrue(mav.isEmpty());
+ assertEquals(err.toString(), this.response.getContentAsString());
+ }
+
+ @Test
+ public void resolveExceptionWithAssertionErrorAsRootCause() throws Exception {
+ AnnotationConfigApplicationContext cxt = new AnnotationConfigApplicationContext(MyConfig.class);
+ this.resolver.setApplicationContext(cxt);
+ this.resolver.afterPropertiesSet();
+
+ AssertionError err = new AssertionError("argh");
+ FatalBeanException ex = new FatalBeanException("wrapped", err);
+ HandlerMethod handlerMethod = new HandlerMethod(new ResponseBodyController(), "handle");
+ ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod, ex);
+
+ assertNotNull("Exception was not handled", mav);
+ assertTrue(mav.isEmpty());
+ assertEquals(err.toString(), this.response.getContentAsString());
+ }
+
+ @Test
public void resolveExceptionControllerAdviceHandler() throws Exception {
AnnotationConfigApplicationContext cxt = new AnnotationConfigApplicationContext(MyControllerAdviceConfig.class);
this.resolver.setApplicationContext(cxt);
@@ -328,30 +365,32 @@ public class ExceptionHandlerExceptionResolverTests {
}
- @ControllerAdvice
+ @RestControllerAdvice
@Order(1)
static class TestExceptionResolver {
@ExceptionHandler
- @ResponseBody
public String handleException(IllegalStateException ex) {
return "TestExceptionResolver: " + ClassUtils.getShortName(ex.getClass());
}
@ExceptionHandler(ArrayIndexOutOfBoundsException.class)
- @ResponseBody
public String handleWithHandlerMethod(HandlerMethod handlerMethod) {
return "HandlerMethod: " + handlerMethod.getMethod().getName();
}
+
+ @ExceptionHandler(AssertionError.class)
+ public String handleAssertionError(Error err) {
+ return err.toString();
+ }
}
- @ControllerAdvice
+ @RestControllerAdvice
@Order(2)
static class AnotherTestExceptionResolver {
@ExceptionHandler({IllegalStateException.class, IllegalAccessException.class})
- @ResponseBody
public String handleException(Exception ex) {
return "AnotherTestExceptionResolver: " + ClassUtils.getShortName(ex.getClass());
}
@@ -361,46 +400,45 @@ public class ExceptionHandlerExceptionResolverTests {
@Configuration
static class MyConfig {
- @Bean public TestExceptionResolver testExceptionResolver() {
+ @Bean
+ public TestExceptionResolver testExceptionResolver() {
return new TestExceptionResolver();
}
- @Bean public AnotherTestExceptionResolver anotherTestExceptionResolver() {
+ @Bean
+ public AnotherTestExceptionResolver anotherTestExceptionResolver() {
return new AnotherTestExceptionResolver();
}
}
- @ControllerAdvice("java.lang")
+ @RestControllerAdvice("java.lang")
@Order(1)
static class NotCalledTestExceptionResolver {
@ExceptionHandler
- @ResponseBody
public String handleException(IllegalStateException ex) {
return "NotCalledTestExceptionResolver: " + ClassUtils.getShortName(ex.getClass());
}
}
- @ControllerAdvice("org.springframework.web.servlet.mvc.method.annotation")
+ @RestControllerAdvice("org.springframework.web.servlet.mvc.method.annotation")
@Order(2)
static class BasePackageTestExceptionResolver {
@ExceptionHandler
- @ResponseBody
public String handleException(IllegalStateException ex) {
return "BasePackageTestExceptionResolver: " + ClassUtils.getShortName(ex.getClass());
}
}
- @ControllerAdvice
+ @RestControllerAdvice
@Order(3)
static class DefaultTestExceptionResolver {
@ExceptionHandler
- @ResponseBody
public String handleException(IllegalStateException ex) {
return "DefaultTestExceptionResolver: " + ClassUtils.getShortName(ex.getClass());
}
@@ -410,15 +448,18 @@ public class ExceptionHandlerExceptionResolverTests {
@Configuration
static class MyControllerAdviceConfig {
- @Bean public NotCalledTestExceptionResolver notCalledTestExceptionResolver() {
+ @Bean
+ public NotCalledTestExceptionResolver notCalledTestExceptionResolver() {
return new NotCalledTestExceptionResolver();
}
- @Bean public BasePackageTestExceptionResolver basePackageTestExceptionResolver() {
+ @Bean
+ public BasePackageTestExceptionResolver basePackageTestExceptionResolver() {
return new BasePackageTestExceptionResolver();
}
- @Bean public DefaultTestExceptionResolver defaultTestExceptionResolver() {
+ @Bean
+ public DefaultTestExceptionResolver defaultTestExceptionResolver() {
return new DefaultTestExceptionResolver();
}
}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorMockTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorMockTests.java
index 0e748b03..6acf1028 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorMockTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorMockTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,12 +16,18 @@
package org.springframework.web.servlet.mvc.method.annotation;
+import static org.junit.Assert.*;
+import static org.mockito.BDDMockito.*;
+import static org.springframework.web.servlet.HandlerMapping.*;
+
import java.lang.reflect.Method;
import java.net.URI;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
+import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
@@ -30,6 +36,8 @@ import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.core.MethodParameter;
+import org.springframework.core.io.ByteArrayResource;
+import org.springframework.core.io.Resource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
@@ -48,10 +56,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;
-import static org.junit.Assert.*;
-import static org.mockito.BDDMockito.*;
-import static org.springframework.web.servlet.HandlerMapping.*;
-
/**
* Test fixture for {@link HttpEntityMethodProcessor} delegating to a mock
* {@link HttpMessageConverter}.
@@ -60,6 +64,7 @@ import static org.springframework.web.servlet.HandlerMapping.*;
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
+ * @author Brian Clozel
*/
public class HttpEntityMethodProcessorMockTests {
@@ -67,7 +72,9 @@ public class HttpEntityMethodProcessorMockTests {
private HttpEntityMethodProcessor processor;
- private HttpMessageConverter<String> messageConverter;
+ private HttpMessageConverter<String> stringHttpMessageConverter;
+
+ private HttpMessageConverter<Resource> resourceMessageConverter;
private MethodParameter paramHttpEntity;
private MethodParameter paramRequestEntity;
@@ -75,6 +82,7 @@ public class HttpEntityMethodProcessorMockTests {
private MethodParameter paramInt;
private MethodParameter returnTypeResponseEntity;
private MethodParameter returnTypeResponseEntityProduces;
+ private MethodParameter returnTypeResponseEntityResource;
private MethodParameter returnTypeHttpEntity;
private MethodParameter returnTypeHttpEntitySubclass;
private MethodParameter returnTypeInt;
@@ -94,13 +102,20 @@ public class HttpEntityMethodProcessorMockTests {
dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
- messageConverter = mock(HttpMessageConverter.class);
- given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
+ stringHttpMessageConverter = mock(HttpMessageConverter.class);
+ given(stringHttpMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
+ resourceMessageConverter = mock(HttpMessageConverter.class);
+ given(resourceMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.ALL));
+ List<HttpMessageConverter<?>> converters = new ArrayList<>();
+ converters.add(stringHttpMessageConverter);
+ converters.add(resourceMessageConverter);
+ processor = new HttpEntityMethodProcessor(converters);
+ reset(stringHttpMessageConverter);
+ reset(resourceMessageConverter);
- processor = new HttpEntityMethodProcessor(Collections.<HttpMessageConverter<?>>singletonList(messageConverter));
- reset(messageConverter);
+ Method handle1 = getClass().getMethod("handle1", HttpEntity.class, ResponseEntity.class,
+ Integer.TYPE, RequestEntity.class);
- Method handle1 = getClass().getMethod("handle1", HttpEntity.class, ResponseEntity.class, Integer.TYPE, RequestEntity.class);
paramHttpEntity = new MethodParameter(handle1, 0);
paramRequestEntity = new MethodParameter(handle1, 3);
paramResponseEntity = new MethodParameter(handle1, 1);
@@ -110,6 +125,7 @@ public class HttpEntityMethodProcessorMockTests {
returnTypeHttpEntity = new MethodParameter(getClass().getMethod("handle2", HttpEntity.class), -1);
returnTypeHttpEntitySubclass = new MethodParameter(getClass().getMethod("handle2x", HttpEntity.class), -1);
returnTypeInt = new MethodParameter(getClass().getMethod("handle3"), -1);
+ returnTypeResponseEntityResource = new MethodParameter(getClass().getMethod("handle5"), -1);
mavContainer = new ModelAndViewContainer();
servletRequest = new MockHttpServletRequest("GET", "/foo");
@@ -144,8 +160,8 @@ public class HttpEntityMethodProcessorMockTests {
servletRequest.addHeader("Content-Type", contentType.toString());
servletRequest.setContent(body.getBytes(Charset.forName("UTF-8")));
- given(messageConverter.canRead(String.class, contentType)).willReturn(true);
- given(messageConverter.read(eq(String.class), isA(HttpInputMessage.class))).willReturn(body);
+ given(stringHttpMessageConverter.canRead(String.class, contentType)).willReturn(true);
+ given(stringHttpMessageConverter.read(eq(String.class), isA(HttpInputMessage.class))).willReturn(body);
Object result = processor.resolveArgument(paramHttpEntity, mavContainer, webRequest, null);
@@ -166,8 +182,8 @@ public class HttpEntityMethodProcessorMockTests {
servletRequest.setRequestURI("/path");
servletRequest.setContent(body.getBytes(Charset.forName("UTF-8")));
- given(messageConverter.canRead(String.class, contentType)).willReturn(true);
- given(messageConverter.read(eq(String.class), isA(HttpInputMessage.class))).willReturn(body);
+ given(stringHttpMessageConverter.canRead(String.class, contentType)).willReturn(true);
+ given(stringHttpMessageConverter.read(eq(String.class), isA(HttpInputMessage.class))).willReturn(body);
Object result = processor.resolveArgument(paramRequestEntity, mavContainer, webRequest, null);
@@ -175,6 +191,7 @@ public class HttpEntityMethodProcessorMockTests {
assertFalse("The requestHandled flag shouldn't change", mavContainer.isRequestHandled());
RequestEntity<?> requestEntity = (RequestEntity<?>) result;
assertEquals("Invalid method", HttpMethod.GET, requestEntity.getMethod());
+ // using default port (which is 80), so do not need to append the port (-1 means ignore)
assertEquals("Invalid url", new URI("http", null, "www.example.com", -1, "/path", null, null), requestEntity.getUrl());
assertEquals("Invalid argument", body, requestEntity.getBody());
}
@@ -185,8 +202,8 @@ public class HttpEntityMethodProcessorMockTests {
servletRequest.setMethod("POST");
servletRequest.addHeader("Content-Type", contentType.toString());
- given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(contentType));
- given(messageConverter.canRead(String.class, contentType)).willReturn(false);
+ given(stringHttpMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(contentType));
+ given(stringHttpMessageConverter.canRead(String.class, contentType)).willReturn(false);
processor.resolveArgument(paramHttpEntity, mavContainer, webRequest, null);
@@ -204,35 +221,33 @@ public class HttpEntityMethodProcessorMockTests {
@Test
public void handleReturnValue() throws Exception {
String body = "Foo";
- ResponseEntity<String> returnValue = new ResponseEntity<String>(body, HttpStatus.OK);
+ ResponseEntity<String> returnValue = new ResponseEntity<>(body, HttpStatus.OK);
MediaType accepted = MediaType.TEXT_PLAIN;
servletRequest.addHeader("Accept", accepted.toString());
- given(messageConverter.canWrite(String.class, null)).willReturn(true);
- given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
- given(messageConverter.canWrite(String.class, accepted)).willReturn(true);
+ initStringMessageConversion(accepted);
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
assertTrue(mavContainer.isRequestHandled());
- verify(messageConverter).write(eq(body), eq(accepted), isA(HttpOutputMessage.class));
+ verify(stringHttpMessageConverter).write(eq(body), eq(accepted), isA(HttpOutputMessage.class));
}
@Test
public void handleReturnValueProduces() throws Exception {
String body = "Foo";
- ResponseEntity<String> returnValue = new ResponseEntity<String>(body, HttpStatus.OK);
+ ResponseEntity<String> returnValue = new ResponseEntity<>(body, HttpStatus.OK);
servletRequest.addHeader("Accept", "text/*");
servletRequest.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, Collections.singleton(MediaType.TEXT_HTML));
- given(messageConverter.canWrite(String.class, MediaType.TEXT_HTML)).willReturn(true);
+ given(stringHttpMessageConverter.canWrite(String.class, MediaType.TEXT_HTML)).willReturn(true);
processor.handleReturnValue(returnValue, returnTypeResponseEntityProduces, mavContainer, webRequest);
assertTrue(mavContainer.isRequestHandled());
- verify(messageConverter).write(eq(body), eq(MediaType.TEXT_HTML), isA(HttpOutputMessage.class));
+ verify(stringHttpMessageConverter).write(eq(body), eq(MediaType.TEXT_HTML), isA(HttpOutputMessage.class));
}
@SuppressWarnings("unchecked")
@@ -248,28 +263,28 @@ public class HttpEntityMethodProcessorMockTests {
given(advice.beforeBodyWrite(any(), any(), any(), any(), any(), any())).willReturn("Foo");
HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(
- Collections.singletonList(messageConverter), null, Collections.singletonList(advice));
+ Collections.singletonList(stringHttpMessageConverter), null, Collections.singletonList(advice));
- reset(messageConverter);
- given(messageConverter.canWrite(String.class, MediaType.TEXT_HTML)).willReturn(true);
+ reset(stringHttpMessageConverter);
+ given(stringHttpMessageConverter.canWrite(String.class, MediaType.TEXT_HTML)).willReturn(true);
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
assertTrue(mavContainer.isRequestHandled());
- verify(messageConverter).write(eq("Foo"), eq(MediaType.TEXT_HTML), isA(HttpOutputMessage.class));
+ verify(stringHttpMessageConverter).write(eq("Foo"), eq(MediaType.TEXT_HTML), isA(HttpOutputMessage.class));
}
@Test(expected = HttpMediaTypeNotAcceptableException.class)
public void handleReturnValueNotAcceptable() throws Exception {
String body = "Foo";
- ResponseEntity<String> returnValue = new ResponseEntity<String>(body, HttpStatus.OK);
+ ResponseEntity<String> returnValue = new ResponseEntity<>(body, HttpStatus.OK);
MediaType accepted = MediaType.APPLICATION_ATOM_XML;
servletRequest.addHeader("Accept", accepted.toString());
- given(messageConverter.canWrite(String.class, null)).willReturn(true);
- given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
- given(messageConverter.canWrite(String.class, accepted)).willReturn(false);
+ given(stringHttpMessageConverter.canWrite(String.class, null)).willReturn(true);
+ given(stringHttpMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
+ given(stringHttpMessageConverter.canWrite(String.class, accepted)).willReturn(false);
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
@@ -279,14 +294,14 @@ public class HttpEntityMethodProcessorMockTests {
@Test(expected = HttpMediaTypeNotAcceptableException.class)
public void handleReturnValueNotAcceptableProduces() throws Exception {
String body = "Foo";
- ResponseEntity<String> returnValue = new ResponseEntity<String>(body, HttpStatus.OK);
+ ResponseEntity<String> returnValue = new ResponseEntity<>(body, HttpStatus.OK);
MediaType accepted = MediaType.TEXT_PLAIN;
servletRequest.addHeader("Accept", accepted.toString());
- given(messageConverter.canWrite(String.class, null)).willReturn(true);
- given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
- given(messageConverter.canWrite(String.class, accepted)).willReturn(false);
+ given(stringHttpMessageConverter.canWrite(String.class, null)).willReturn(true);
+ given(stringHttpMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
+ given(stringHttpMessageConverter.canWrite(String.class, accepted)).willReturn(false);
processor.handleReturnValue(returnValue, returnTypeResponseEntityProduces, mavContainer, webRequest);
@@ -297,7 +312,7 @@ public class HttpEntityMethodProcessorMockTests {
@Test(expected=HttpMediaTypeNotAcceptableException.class)
public void handleReturnValueNotAcceptableParseError() throws Exception {
- ResponseEntity<String> returnValue = new ResponseEntity<String>("Body", HttpStatus.ACCEPTED);
+ ResponseEntity<String> returnValue = new ResponseEntity<>("Body", HttpStatus.ACCEPTED);
servletRequest.addHeader("Accept", "01");
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
@@ -305,10 +320,10 @@ public class HttpEntityMethodProcessorMockTests {
}
@Test
- public void responseHeaderNoBody() throws Exception {
+ public void handleReturnValueResponseHeaderNoBody() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.set("headerName", "headerValue");
- ResponseEntity<String> returnValue = new ResponseEntity<String>(headers, HttpStatus.ACCEPTED);
+ ResponseEntity<String> returnValue = new ResponseEntity<>(headers, HttpStatus.ACCEPTED);
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
@@ -317,36 +332,30 @@ public class HttpEntityMethodProcessorMockTests {
}
@Test
- public void responseHeaderAndBody() throws Exception {
+ public void handleReturnValueResponseHeaderAndBody() throws Exception {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("header", "headerValue");
- ResponseEntity<String> returnValue = new ResponseEntity<String>("body", responseHeaders, HttpStatus.ACCEPTED);
-
- given(messageConverter.canWrite(String.class, null)).willReturn(true);
- given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
- given(messageConverter.canWrite(String.class, MediaType.TEXT_PLAIN)).willReturn(true);
+ ResponseEntity<String> returnValue = new ResponseEntity<>("body", responseHeaders, HttpStatus.ACCEPTED);
+ initStringMessageConversion(MediaType.TEXT_PLAIN);
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
ArgumentCaptor<HttpOutputMessage> outputMessage = ArgumentCaptor.forClass(HttpOutputMessage.class);
- verify(messageConverter).write(eq("body"), eq(MediaType.TEXT_PLAIN), outputMessage.capture());
+ verify(stringHttpMessageConverter).write(eq("body"), eq(MediaType.TEXT_PLAIN), outputMessage.capture());
assertTrue(mavContainer.isRequestHandled());
assertEquals("headerValue", outputMessage.getValue().getHeaders().get("header").get(0));
}
@Test
- public void handleReturnTypeLastModified() throws Exception {
+ public void handleReturnValueLastModified() throws Exception {
long currentTime = new Date().getTime();
long oneMinuteAgo = currentTime - (1000 * 60);
servletRequest.addHeader(HttpHeaders.IF_MODIFIED_SINCE, dateFormat.format(currentTime));
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setDate(HttpHeaders.LAST_MODIFIED, oneMinuteAgo);
- ResponseEntity<String> returnValue = new ResponseEntity<String>("body", responseHeaders, HttpStatus.OK);
-
- given(messageConverter.canWrite(String.class, null)).willReturn(true);
- given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
- given(messageConverter.canWrite(String.class, MediaType.TEXT_PLAIN)).willReturn(true);
+ ResponseEntity<String> returnValue = new ResponseEntity<>("body", responseHeaders, HttpStatus.OK);
+ initStringMessageConversion(MediaType.TEXT_PLAIN);
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
assertResponseNotModified();
@@ -355,17 +364,14 @@ public class HttpEntityMethodProcessorMockTests {
}
@Test
- public void handleReturnTypeEtag() throws Exception {
+ public void handleReturnValueEtag() throws Exception {
String etagValue = "\"deadb33f8badf00d\"";
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, etagValue);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set(HttpHeaders.ETAG, etagValue);
- ResponseEntity<String> returnValue = new ResponseEntity<String>("body", responseHeaders, HttpStatus.OK);
-
- given(messageConverter.canWrite(String.class, null)).willReturn(true);
- given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
- given(messageConverter.canWrite(String.class, MediaType.TEXT_PLAIN)).willReturn(true);
+ ResponseEntity<String> returnValue = new ResponseEntity<>("body", responseHeaders, HttpStatus.OK);
+ initStringMessageConversion(MediaType.TEXT_PLAIN);
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
assertResponseNotModified();
@@ -374,7 +380,7 @@ public class HttpEntityMethodProcessorMockTests {
}
@Test
- public void handleReturnTypeETagAndLastModified() throws Exception {
+ public void handleReturnValueETagAndLastModified() throws Exception {
long currentTime = new Date().getTime();
long oneMinuteAgo = currentTime - (1000 * 60);
String etagValue = "\"deadb33f8badf00d\"";
@@ -383,12 +389,9 @@ public class HttpEntityMethodProcessorMockTests {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setDate(HttpHeaders.LAST_MODIFIED, oneMinuteAgo);
responseHeaders.set(HttpHeaders.ETAG, etagValue);
- ResponseEntity<String> returnValue = new ResponseEntity<String>("body", responseHeaders, HttpStatus.OK);
-
- given(messageConverter.canWrite(String.class, null)).willReturn(true);
- given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
- given(messageConverter.canWrite(String.class, MediaType.TEXT_PLAIN)).willReturn(true);
+ ResponseEntity<String> returnValue = new ResponseEntity<>("body", responseHeaders, HttpStatus.OK);
+ initStringMessageConversion(MediaType.TEXT_PLAIN);
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
assertResponseNotModified();
@@ -399,19 +402,16 @@ public class HttpEntityMethodProcessorMockTests {
}
@Test
- public void handleReturnTypeNotModified() throws Exception {
+ public void handleReturnValueNotModified() throws Exception {
long currentTime = new Date().getTime();
long oneMinuteAgo = currentTime - (1000 * 60);
String etagValue = "\"deadb33f8badf00d\"";
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setDate(HttpHeaders.LAST_MODIFIED, oneMinuteAgo);
responseHeaders.set(HttpHeaders.ETAG, etagValue);
- ResponseEntity<String> returnValue = new ResponseEntity<String>("body", responseHeaders, HttpStatus.NOT_MODIFIED);
-
- given(messageConverter.canWrite(String.class, null)).willReturn(true);
- given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
- given(messageConverter.canWrite(String.class, MediaType.TEXT_PLAIN)).willReturn(true);
+ ResponseEntity<String> returnValue = new ResponseEntity<>("body", responseHeaders, HttpStatus.NOT_MODIFIED);
+ initStringMessageConversion(MediaType.TEXT_PLAIN);
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
assertResponseNotModified();
@@ -421,14 +421,8 @@ public class HttpEntityMethodProcessorMockTests {
assertEquals(etagValue, servletResponse.getHeader(HttpHeaders.ETAG));
}
- private void assertResponseNotModified() {
- assertTrue(mavContainer.isRequestHandled());
- assertEquals(HttpStatus.NOT_MODIFIED.value(), servletResponse.getStatus());
- assertEquals(0, servletResponse.getContentAsByteArray().length);
- }
-
@Test
- public void handleReturnTypeChangedETagAndLastModified() throws Exception {
+ public void handleReturnValueChangedETagAndLastModified() throws Exception {
long currentTime = new Date().getTime();
long oneMinuteAgo = currentTime - (1000 * 60);
String etagValue = "\"deadb33f8badf00d\"";
@@ -438,12 +432,9 @@ public class HttpEntityMethodProcessorMockTests {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setDate(HttpHeaders.LAST_MODIFIED, oneMinuteAgo);
responseHeaders.set(HttpHeaders.ETAG, changedEtagValue);
- ResponseEntity<String> returnValue = new ResponseEntity<String>("body", responseHeaders, HttpStatus.OK);
-
- given(messageConverter.canWrite(String.class, null)).willReturn(true);
- given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
- given(messageConverter.canWrite(String.class, MediaType.TEXT_PLAIN)).willReturn(true);
+ ResponseEntity<String> returnValue = new ResponseEntity<>("body", responseHeaders, HttpStatus.OK);
+ initStringMessageConversion(MediaType.TEXT_PLAIN);
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
assertTrue(mavContainer.isRequestHandled());
@@ -457,19 +448,16 @@ public class HttpEntityMethodProcessorMockTests {
// SPR-13496
@Test
- public void handleReturnTypePostRequestWithIfNotModified() throws Exception {
+ public void handleReturnValuePostRequestWithIfNotModified() throws Exception {
String wildcardValue = "*";
String etagValue = "\"some-etag\"";
servletRequest.setMethod("POST");
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, wildcardValue);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set(HttpHeaders.ETAG, etagValue);
- ResponseEntity<String> returnValue = new ResponseEntity<String>("body", responseHeaders, HttpStatus.OK);
-
- given(messageConverter.canWrite(String.class, null)).willReturn(true);
- given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
- given(messageConverter.canWrite(String.class, MediaType.TEXT_PLAIN)).willReturn(true);
+ ResponseEntity<String> returnValue = new ResponseEntity<>("body", responseHeaders, HttpStatus.OK);
+ initStringMessageConversion(MediaType.TEXT_PLAIN);
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
assertResponseOkWithBody("body");
@@ -479,18 +467,15 @@ public class HttpEntityMethodProcessorMockTests {
// SPR-13626
@Test
- public void handleReturnTypeGetIfNoneMatchWildcard() throws Exception {
+ public void handleReturnValueGetIfNoneMatchWildcard() throws Exception {
String wildcardValue = "*";
String etagValue = "\"some-etag\"";
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, wildcardValue);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set(HttpHeaders.ETAG, etagValue);
- ResponseEntity<String> returnValue = new ResponseEntity<String>("body", responseHeaders, HttpStatus.OK);
-
- given(messageConverter.canWrite(String.class, null)).willReturn(true);
- given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
- given(messageConverter.canWrite(String.class, MediaType.TEXT_PLAIN)).willReturn(true);
+ ResponseEntity<String> returnValue = new ResponseEntity<>("body", responseHeaders, HttpStatus.OK);
+ initStringMessageConversion(MediaType.TEXT_PLAIN);
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
assertResponseOkWithBody("body");
@@ -500,18 +485,15 @@ public class HttpEntityMethodProcessorMockTests {
// SPR-13626
@Test
- public void handleReturnTypeIfNoneMatchIfMatch() throws Exception {
+ public void handleReturnValueIfNoneMatchIfMatch() throws Exception {
String etagValue = "\"some-etag\"";
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, etagValue);
servletRequest.addHeader(HttpHeaders.IF_MATCH, "ifmatch");
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set(HttpHeaders.ETAG, etagValue);
- ResponseEntity<String> returnValue = new ResponseEntity<String>("body", responseHeaders, HttpStatus.OK);
-
- given(messageConverter.canWrite(String.class, null)).willReturn(true);
- given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
- given(messageConverter.canWrite(String.class, MediaType.TEXT_PLAIN)).willReturn(true);
+ ResponseEntity<String> returnValue = new ResponseEntity<>("body", responseHeaders, HttpStatus.OK);
+ initStringMessageConversion(MediaType.TEXT_PLAIN);
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
assertResponseOkWithBody("body");
@@ -521,18 +503,15 @@ public class HttpEntityMethodProcessorMockTests {
// SPR-13626
@Test
- public void handleReturnTypeIfNoneMatchIfUnmodifiedSince() throws Exception {
+ public void handleReturnValueIfNoneMatchIfUnmodifiedSince() throws Exception {
String etagValue = "\"some-etag\"";
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, etagValue);
servletRequest.addHeader(HttpHeaders.IF_UNMODIFIED_SINCE, dateFormat.format(new Date().getTime()));
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set(HttpHeaders.ETAG, etagValue);
- ResponseEntity<String> returnValue = new ResponseEntity<String>("body", responseHeaders, HttpStatus.OK);
-
- given(messageConverter.canWrite(String.class, null)).willReturn(true);
- given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
- given(messageConverter.canWrite(String.class, MediaType.TEXT_PLAIN)).willReturn(true);
+ ResponseEntity<String> returnValue = new ResponseEntity<>("body", responseHeaders, HttpStatus.OK);
+ initStringMessageConversion(MediaType.TEXT_PLAIN);
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
assertResponseOkWithBody("body");
@@ -540,11 +519,39 @@ public class HttpEntityMethodProcessorMockTests {
assertEquals(etagValue, servletResponse.getHeader(HttpHeaders.ETAG));
}
+ @Test
+ public void handleReturnTypeResource() throws Exception {
+ ResponseEntity<Resource> returnValue = ResponseEntity
+ .ok(new ByteArrayResource("Content".getBytes(Charset.forName("UTF-8"))));
+
+ given(resourceMessageConverter.canWrite(ByteArrayResource.class, null)).willReturn(true);
+ given(resourceMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.ALL));
+ given(resourceMessageConverter.canWrite(ByteArrayResource.class, MediaType.APPLICATION_OCTET_STREAM)).willReturn(true);
+
+ processor.handleReturnValue(returnValue, returnTypeResponseEntityResource, mavContainer, webRequest);
+
+ then(resourceMessageConverter).should(times(1)).write(any(ByteArrayResource.class),
+ eq(MediaType.APPLICATION_OCTET_STREAM), any(HttpOutputMessage.class));
+ assertEquals(200, servletResponse.getStatus());
+ }
+
+ private void initStringMessageConversion(MediaType accepted) {
+ given(stringHttpMessageConverter.canWrite(String.class, null)).willReturn(true);
+ given(stringHttpMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
+ given(stringHttpMessageConverter.canWrite(String.class, accepted)).willReturn(true);
+ }
+
+ private void assertResponseNotModified() {
+ assertTrue(mavContainer.isRequestHandled());
+ assertEquals(HttpStatus.NOT_MODIFIED.value(), servletResponse.getStatus());
+ assertEquals(0, servletResponse.getContentAsByteArray().length);
+ }
+
private void assertResponseOkWithBody(String body) throws Exception {
assertTrue(mavContainer.isRequestHandled());
assertEquals(HttpStatus.OK.value(), servletResponse.getStatus());
ArgumentCaptor<HttpOutputMessage> outputMessage = ArgumentCaptor.forClass(HttpOutputMessage.class);
- verify(messageConverter).write(eq("body"), eq(MediaType.TEXT_PLAIN), outputMessage.capture());
+ verify(stringHttpMessageConverter).write(eq(body), eq(MediaType.TEXT_PLAIN), outputMessage.capture());
}
@SuppressWarnings("unused")
@@ -576,7 +583,10 @@ public class HttpEntityMethodProcessorMockTests {
}
@SuppressWarnings("unused")
+ public ResponseEntity<Resource> handle5() {return null;}
+
+ @SuppressWarnings("unused")
public static class CustomHttpEntity extends HttpEntity<Object> {
}
-}
+} \ No newline at end of file
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorTests.java
index 812592de..e6f621ed 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,8 +19,6 @@ package org.springframework.web.servlet.mvc.method.annotation;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
@@ -31,7 +29,10 @@ import org.junit.Test;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpEntity;
import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse;
@@ -45,7 +46,10 @@ import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.ModelAndViewContainer;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
/**
* Test fixture with {@link HttpEntityMethodProcessor} delegating to
@@ -75,7 +79,7 @@ public class HttpEntityMethodProcessorTests {
@Before
public void setUp() throws Exception {
- Method method = getClass().getMethod("handle", HttpEntity.class, HttpEntity.class);
+ Method method = getClass().getDeclaredMethod("handle", HttpEntity.class, HttpEntity.class);
paramList = new MethodParameter(method, 0);
paramSimpleBean = new MethodParameter(method, 1);
@@ -85,16 +89,16 @@ public class HttpEntityMethodProcessorTests {
servletResponse = new MockHttpServletResponse();
servletRequest.setMethod("POST");
webRequest = new ServletWebRequest(servletRequest, servletResponse);
-
}
+
@Test
public void resolveArgument() throws Exception {
String content = "{\"name\" : \"Jad\"}";
this.servletRequest.setContent(content.getBytes("UTF-8"));
this.servletRequest.setContentType("application/json");
- List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
+ List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(new MappingJackson2HttpMessageConverter());
HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(converters);
@@ -113,7 +117,8 @@ public class HttpEntityMethodProcessorTests {
this.servletRequest.setContent(new byte[0]);
this.servletRequest.setContentType("application/json");
- List<HttpMessageConverter<?>> converters = Collections.singletonList(new MappingJackson2HttpMessageConverter());
+ List<HttpMessageConverter<?>> converters = new ArrayList<>();
+ converters.add(new MappingJackson2HttpMessageConverter());
HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(converters);
HttpEntity<?> result = (HttpEntity<?>) processor.resolveArgument(this.paramSimpleBean,
@@ -129,7 +134,7 @@ public class HttpEntityMethodProcessorTests {
this.servletRequest.setContent(content.getBytes("UTF-8"));
this.servletRequest.setContentType("application/json");
- List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
+ List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(new MappingJackson2HttpMessageConverter());
HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(converters);
@@ -152,7 +157,7 @@ public class HttpEntityMethodProcessorTests {
this.servletRequest.setContent(content.getBytes("UTF-8"));
this.servletRequest.setContentType(MediaType.APPLICATION_JSON_VALUE);
- List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
+ List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(new MappingJackson2HttpMessageConverter());
HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(converters);
@@ -170,7 +175,7 @@ public class HttpEntityMethodProcessorTests {
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
MethodParameter methodReturnType = handlerMethod.getReturnType();
- List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
+ List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(new MappingJackson2HttpMessageConverter());
HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(converters);
@@ -182,9 +187,33 @@ public class HttpEntityMethodProcessorTests {
assertTrue(content.contains("\"type\":\"bar\""));
}
+ // SPR-13423
+
+ @Test
+ public void handleReturnValueCharSequence() throws Exception {
+ List<HttpMessageConverter<?>>converters = new ArrayList<>();
+ converters.add(new ByteArrayHttpMessageConverter());
+ converters.add(new StringHttpMessageConverter());
+
+ Method method = getClass().getDeclaredMethod("handle");
+ MethodParameter returnType = new MethodParameter(method, -1);
+ ResponseEntity<StringBuilder> returnValue = ResponseEntity.ok(new StringBuilder("Foo"));
+
+ HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(converters);
+ processor.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
+
+ assertEquals("text/plain;charset=ISO-8859-1", servletResponse.getHeader("Content-Type"));
+ assertEquals("Foo", servletResponse.getContentAsString());
+ }
+
+
@SuppressWarnings("unused")
- public void handle(HttpEntity<List<SimpleBean>> arg1, HttpEntity<SimpleBean> arg2) {
+ private void handle(HttpEntity<List<SimpleBean>> arg1, HttpEntity<SimpleBean> arg2) {
+ }
+
+ private ResponseEntity<CharSequence> handle() {
+ return null;
}
@SuppressWarnings("unused")
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MatrixVariablesMapMethodArgumentResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MatrixVariablesMapMethodArgumentResolverTests.java
index f4fe3ac3..fdf6c652 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MatrixVariablesMapMethodArgumentResolverTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MatrixVariablesMapMethodArgumentResolverTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -102,7 +102,7 @@ public class MatrixVariablesMapMethodArgumentResolverTests {
Map<String, String> map = (Map<String, String>) this.resolver.resolveArgument(
this.paramMap, this.mavContainer, this.webRequest, null);
- assertEquals(Arrays.asList("red", "green", "blue"), map.get("colors"));
+ assertEquals("red", map.get("colors"));
@SuppressWarnings("unchecked")
MultiValueMap<String, String> multivalueMap = (MultiValueMap<String, String>) this.resolver.resolveArgument(
@@ -131,7 +131,7 @@ public class MatrixVariablesMapMethodArgumentResolverTests {
Map<String, String> mapAll = (Map<String, String>) this.resolver.resolveArgument(
this.paramMap, this.mavContainer, this.webRequest, null);
- assertEquals(Arrays.asList("red", "purple", "yellow", "orange"), mapAll.get("colors"));
+ assertEquals("red", mapAll.get("colors"));
}
@Test
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilderTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilderTests.java
index 49f204ef..daa1108a 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilderTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilderTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2015 the original author or authors.
+ * Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -221,6 +221,14 @@ public class MvcUriComponentsBuilderTests {
assertThat(uriComponents.toUriString(), is("http://localhost/input"));
}
+ @Test // SPR-14405
+ public void testFromMappingNameWithOptionalParam() throws Exception {
+ UriComponents uriComponents = fromMethodName(ControllerWithMethods.class,
+ "methodWithOptionalParam", new Object[] {null}).build();
+
+ assertThat(uriComponents.toUriString(), is("http://localhost/something/optional-param"));
+ }
+
@Test
public void testFromMethodCall() {
UriComponents uriComponents = fromMethodCall(on(ControllerWithMethods.class).myMethod(null)).build();
@@ -369,25 +377,25 @@ public class MvcUriComponentsBuilderTests {
}
- class PersonControllerImpl implements PersonController {
+ private class PersonControllerImpl implements PersonController {
}
@RequestMapping("/people/{id}/addresses")
- static class PersonsAddressesController {
+ private static class PersonsAddressesController {
@RequestMapping("/{country}")
- public HttpEntity<Void> getAddressesForCountry(@PathVariable String country) {
+ HttpEntity<Void> getAddressesForCountry(@PathVariable String country) {
return null;
}
}
@RequestMapping({ "/persons", "/people" })
- class InvalidController {
+ private class InvalidController {
}
- class UnmappedController {
+ private class UnmappedController {
@RequestMapping
public void unmappedMethod() {
@@ -424,15 +432,20 @@ public class MvcUriComponentsBuilderTests {
@RequestParam List<Integer> items, @RequestParam Integer limit) {
return null;
}
+
+ @RequestMapping("/optional-param")
+ HttpEntity<Void> methodWithOptionalParam(@RequestParam(defaultValue = "") String q) {
+ return null;
+ }
}
- @RequestMapping("/extended")
+ @RequestMapping("/extended") @SuppressWarnings("WeakerAccess")
static class ExtendedController extends ControllerWithMethods {
}
@RequestMapping("/user/{userId}/contacts")
- static class UserContactController {
+ private static class UserContactController {
@RequestMapping("/create")
public String showCreate(@PathVariable Integer userId) {
@@ -445,7 +458,7 @@ public class MvcUriComponentsBuilderTests {
abstract T get(ID id);
}
- static class PersonCrudController extends AbstractCrudController<Person, Long> {
+ private static class PersonCrudController extends AbstractCrudController<Person, Long> {
@RequestMapping(path = "/{id}", method = RequestMethod.GET)
public Person get(@PathVariable Long id) {
@@ -454,7 +467,7 @@ public class MvcUriComponentsBuilderTests {
}
@Controller
- static class MetaAnnotationController {
+ private static class MetaAnnotationController {
@RequestMapping
public void handle() {
@@ -472,7 +485,7 @@ public class MvcUriComponentsBuilderTests {
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
- @interface PostJson {
+ private @interface PostJson {
String[] path() default {};
}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestAttributeMethodArgumentResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestAttributeMethodArgumentResolverTests.java
new file mode 100644
index 00000000..5ef464d8
--- /dev/null
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestAttributeMethodArgumentResolverTests.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.servlet.mvc.method.annotation;
+
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+
+/**
+ * Unit tests for {@link RequestAttributeMethodArgumentResolver}.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3
+ */
+public class RequestAttributeMethodArgumentResolverTests extends AbstractRequestAttributesArgumentResolverTests {
+
+ @Override
+ protected HandlerMethodArgumentResolver createResolver() {
+ return new RequestAttributeMethodArgumentResolver();
+ }
+
+ @Override
+ protected String getHandleMethodName() {
+ return "handleWithRequestAttribute";
+ }
+
+ @Override
+ protected int getScope() {
+ return RequestAttributes.SCOPE_REQUEST;
+ }
+
+}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterIntegrationTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterIntegrationTests.java
index 5ba4bb54..7b67044d 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterIntegrationTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterIntegrationTests.java
@@ -62,12 +62,14 @@ import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestAttribute;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.SessionAttribute;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.bind.support.SessionStatus;
@@ -146,14 +148,16 @@ public class RequestMappingHandlerAdapterIntegrationTests {
@Test
public void handle() throws Exception {
- Class<?>[] parameterTypes = new Class<?>[] { int.class, String.class, String.class, String.class, Map.class,
+ Class<?>[] parameterTypes = new Class<?>[] {int.class, String.class, String.class, String.class, Map.class,
Date.class, Map.class, String.class, String.class, TestBean.class, Errors.class, TestBean.class,
- Color.class, HttpServletRequest.class, HttpServletResponse.class, User.class, OtherUser.class,
- Model.class, UriComponentsBuilder.class };
+ Color.class, HttpServletRequest.class, HttpServletResponse.class, TestBean.class, TestBean.class,
+ User.class, OtherUser.class, Model.class, UriComponentsBuilder.class};
String datePattern = "yyyy.MM.dd";
String formattedDate = "2011.03.16";
Date date = new GregorianCalendar(2011, Calendar.MARCH, 16).getTime();
+ TestBean sessionAttribute = new TestBean();
+ TestBean requestAttribute = new TestBean();
request.addHeader("Content-Type", "text/plain; charset=utf-8");
request.addHeader("header", "headerValue");
@@ -171,6 +175,8 @@ public class RequestMappingHandlerAdapterIntegrationTests {
Map<String, String> uriTemplateVars = new HashMap<String, String>();
uriTemplateVars.put("pathvar", "pathvarValue");
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVars);
+ request.getSession().setAttribute("sessionAttribute", sessionAttribute);
+ request.setAttribute("requestAttribute", requestAttribute);
HandlerMethod handlerMethod = handlerMethod("handle", parameterTypes);
ModelAndView mav = handlerAdapter.handle(request, response, handlerMethod);
@@ -182,12 +188,12 @@ public class RequestMappingHandlerAdapterIntegrationTests {
assertEquals("headerValue", model.get("header"));
assertEquals(date, model.get("dateParam"));
- Map<?,?> map = (Map<?,?>) model.get("headerMap");
+ Map<?, ?> map = (Map<?, ?>) model.get("headerMap");
assertEquals("headerValue", map.get("header"));
assertEquals("anotherHeaderValue", map.get("anotherHeader"));
assertEquals("systemHeaderValue", model.get("systemHeader"));
- map = (Map<?,?>) model.get("paramMap");
+ map = (Map<?, ?>) model.get("paramMap");
assertEquals(formattedDate, map.get("dateParam"));
assertEquals("paramByConventionValue", map.get("paramByConvention"));
@@ -215,12 +221,15 @@ public class RequestMappingHandlerAdapterIntegrationTests {
assertEquals(User.class, model.get("user").getClass());
assertEquals(OtherUser.class, model.get("otherUser").getClass());
+ assertSame(sessionAttribute, model.get("sessionAttribute"));
+ assertSame(requestAttribute, model.get("requestAttribute"));
+
assertEquals(new URI("http://localhost/contextPath/main/path"), model.get("url"));
}
@Test
public void handleRequestBody() throws Exception {
- Class<?>[] parameterTypes = new Class<?>[] { byte[].class };
+ Class<?>[] parameterTypes = new Class<?>[] {byte[].class};
request.setMethod("POST");
request.addHeader("Content-Type", "text/plain; charset=utf-8");
@@ -237,7 +246,7 @@ public class RequestMappingHandlerAdapterIntegrationTests {
@Test
public void handleAndValidateRequestBody() throws Exception {
- Class<?>[] parameterTypes = new Class<?>[] { TestBean.class, Errors.class };
+ Class<?>[] parameterTypes = new Class<?>[] {TestBean.class, Errors.class};
request.addHeader("Content-Type", "text/plain; charset=utf-8");
request.setContent("Hello Server".getBytes("UTF-8"));
@@ -253,7 +262,7 @@ public class RequestMappingHandlerAdapterIntegrationTests {
@Test
public void handleHttpEntity() throws Exception {
- Class<?>[] parameterTypes = new Class<?>[] { HttpEntity.class };
+ Class<?>[] parameterTypes = new Class<?>[] {HttpEntity.class};
request.addHeader("Content-Type", "text/plain; charset=utf-8");
request.setContent("Hello Server".getBytes("UTF-8"));
@@ -273,7 +282,7 @@ public class RequestMappingHandlerAdapterIntegrationTests {
// SPR-13867
@Test
public void handleHttpEntityWithCacheControl() throws Exception {
- Class<?>[] parameterTypes = new Class<?>[] { HttpEntity.class };
+ Class<?>[] parameterTypes = new Class<?>[] {HttpEntity.class};
request.addHeader("Content-Type", "text/plain; charset=utf-8");
request.setContent("Hello Server".getBytes("UTF-8"));
@@ -348,31 +357,35 @@ public class RequestMappingHandlerAdapterIntegrationTests {
}
public String handle(
- @CookieValue("cookie") int cookie,
- @PathVariable("pathvar") String pathvar,
- @RequestHeader("header") String header,
- @RequestHeader(defaultValue="#{systemProperties.systemHeader}") String systemHeader,
- @RequestHeader Map<String, Object> headerMap,
- @RequestParam("dateParam") Date dateParam,
- @RequestParam Map<String, Object> paramMap,
- String paramByConvention,
- @Value("#{request.contextPath}") String value,
- @ModelAttribute("modelAttr") @Valid TestBean modelAttr,
- Errors errors,
- TestBean modelAttrByConvention,
- Color customArg,
- HttpServletRequest request,
- HttpServletResponse response,
- User user,
- @ModelAttribute OtherUser otherUser,
- Model model,
- UriComponentsBuilder builder) throws Exception {
+ @CookieValue("cookie") int cookie,
+ @PathVariable("pathvar") String pathvar,
+ @RequestHeader("header") String header,
+ @RequestHeader(defaultValue = "#{systemProperties.systemHeader}") String systemHeader,
+ @RequestHeader Map<String, Object> headerMap,
+ @RequestParam("dateParam") Date dateParam,
+ @RequestParam Map<String, Object> paramMap,
+ String paramByConvention,
+ @Value("#{request.contextPath}") String value,
+ @ModelAttribute("modelAttr") @Valid TestBean modelAttr,
+ Errors errors,
+ TestBean modelAttrByConvention,
+ Color customArg,
+ HttpServletRequest request,
+ HttpServletResponse response,
+ @SessionAttribute TestBean sessionAttribute,
+ @RequestAttribute TestBean requestAttribute,
+ User user,
+ @ModelAttribute OtherUser otherUser,
+ Model model,
+ UriComponentsBuilder builder) throws Exception {
model.addAttribute("cookie", cookie).addAttribute("pathvar", pathvar).addAttribute("header", header)
.addAttribute("systemHeader", systemHeader).addAttribute("headerMap", headerMap)
.addAttribute("dateParam", dateParam).addAttribute("paramMap", paramMap)
.addAttribute("paramByConvention", paramByConvention).addAttribute("value", value)
.addAttribute("customArg", customArg).addAttribute(user)
+ .addAttribute("sessionAttribute", sessionAttribute)
+ .addAttribute("requestAttribute", requestAttribute)
.addAttribute("url", builder.path("/path").build().toUri());
assertNotNull(request);
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMappingTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMappingTests.java
index a784f1f1..13946db1 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMappingTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMappingTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,6 +34,11 @@ import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.accept.PathExtensionContentNegotiationStrategy;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PatchMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.support.StaticWebApplicationContext;
@@ -132,12 +137,64 @@ public class RequestMappingHandlerMappingTests {
@Test
public void resolveRequestMappingViaComposedAnnotation() throws Exception {
+ RequestMappingInfo info = assertComposedAnnotationMapping("postJson", "/postJson", RequestMethod.POST);
+
+ assertEquals(MediaType.APPLICATION_JSON_VALUE,
+ info.getConsumesCondition().getConsumableMediaTypes().iterator().next().toString());
+ assertEquals(MediaType.APPLICATION_JSON_VALUE,
+ info.getProducesCondition().getProducibleMediaTypes().iterator().next().toString());
+ }
+
+ @Test
+ public void getMapping() throws Exception {
+ assertComposedAnnotationMapping(RequestMethod.GET);
+ }
+
+ @Test
+ public void postMapping() throws Exception {
+ assertComposedAnnotationMapping(RequestMethod.POST);
+ }
+
+ @Test
+ public void putMapping() throws Exception {
+ assertComposedAnnotationMapping(RequestMethod.PUT);
+ }
+
+ @Test
+ public void deleteMapping() throws Exception {
+ assertComposedAnnotationMapping(RequestMethod.DELETE);
+ }
+
+ @Test
+ public void patchMapping() throws Exception {
+ assertComposedAnnotationMapping(RequestMethod.PATCH);
+ }
+
+ private RequestMappingInfo assertComposedAnnotationMapping(RequestMethod requestMethod) throws Exception {
+ String methodName = requestMethod.name().toLowerCase();
+ String path = "/" + methodName;
+
+ return assertComposedAnnotationMapping(methodName, path, requestMethod);
+ }
+
+ private RequestMappingInfo assertComposedAnnotationMapping(String methodName, String path,
+ RequestMethod requestMethod) throws Exception {
+
Class<?> clazz = ComposedAnnotationController.class;
- Method method = clazz.getMethod("handleInput");
+ Method method = clazz.getMethod(methodName);
RequestMappingInfo info = this.handlerMapping.getMappingForMethod(method, clazz);
assertNotNull(info);
- assertEquals(Collections.singleton("/input"), info.getPatternsCondition().getPatterns());
+
+ Set<String> paths = info.getPatternsCondition().getPatterns();
+ assertEquals(1, paths.size());
+ assertEquals(path, paths.iterator().next());
+
+ Set<RequestMethod> methods = info.getMethodsCondition().getMethods();
+ assertEquals(1, methods.size());
+ assertEquals(requestMethod, methods.iterator().next());
+
+ return info;
}
@@ -148,9 +205,30 @@ public class RequestMappingHandlerMappingTests {
public void handle() {
}
- @PostJson("/input")
- public void handleInput() {
+ @PostJson("/postJson")
+ public void postJson() {
+ }
+
+ @GetMapping("/get")
+ public void get() {
}
+
+ @PostMapping("/post")
+ public void post() {
+ }
+
+ @PutMapping("/put")
+ public void put() {
+ }
+
+ @DeleteMapping("/delete")
+ public void delete() {
+ }
+
+ @PatchMapping("/patch")
+ public void patch() {
+ }
+
}
@RequestMapping(method = RequestMethod.POST,
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartMethodArgumentResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartMethodArgumentResolverTests.java
index d1944b17..3382790b 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartMethodArgumentResolverTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartMethodArgumentResolverTests.java
@@ -40,6 +40,7 @@ import org.springframework.mock.web.test.MockHttpServletResponse;
import org.springframework.mock.web.test.MockMultipartFile;
import org.springframework.mock.web.test.MockMultipartHttpServletRequest;
import org.springframework.mock.web.test.MockPart;
+import org.springframework.util.ReflectionUtils;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.WebDataBinder;
@@ -52,7 +53,6 @@ import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
-import org.springframework.web.multipart.support.RequestPartServletServerHttpRequest;
import static org.junit.Assert.*;
import static org.mockito.BDDMockito.*;
@@ -85,7 +85,9 @@ public class RequestPartMethodArgumentResolverTests {
private MethodParameter paramPartArray;
private MethodParameter paramRequestParamAnnot;
private MethodParameter optionalMultipartFile;
+ private MethodParameter optionalMultipartFileList;
private MethodParameter optionalPart;
+ private MethodParameter optionalPartList;
private MethodParameter optionalRequestPart;
private NativeWebRequest webRequest;
@@ -96,10 +98,7 @@ public class RequestPartMethodArgumentResolverTests {
@SuppressWarnings("unchecked")
@Before
public void setUp() throws Exception {
- Method method = getClass().getMethod("handle", SimpleBean.class, SimpleBean.class,
- SimpleBean.class, MultipartFile.class, List.class, MultipartFile[].class,
- Integer.TYPE, MultipartFile.class, Part.class, List.class, Part[].class,
- MultipartFile.class, Optional.class, Optional.class, Optional.class);
+ Method method = ReflectionUtils.findMethod(getClass(), "handle", (Class<?>[]) null);
paramRequestPart = new SynthesizingMethodParameter(method, 0);
paramRequestPart.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer());
@@ -118,9 +117,13 @@ public class RequestPartMethodArgumentResolverTests {
paramRequestParamAnnot = new SynthesizingMethodParameter(method, 11);
optionalMultipartFile = new SynthesizingMethodParameter(method, 12);
optionalMultipartFile.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer());
- optionalPart = new SynthesizingMethodParameter(method, 13);
+ optionalMultipartFileList = new SynthesizingMethodParameter(method, 13);
+ optionalMultipartFileList.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer());
+ optionalPart = new SynthesizingMethodParameter(method, 14);
optionalPart.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer());
- optionalRequestPart = new SynthesizingMethodParameter(method, 14);
+ optionalPartList = new SynthesizingMethodParameter(method, 15);
+ optionalPartList.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer());
+ optionalRequestPart = new SynthesizingMethodParameter(method, 16);
messageConverter = mock(HttpMessageConverter.class);
given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
@@ -135,22 +138,30 @@ public class RequestPartMethodArgumentResolverTests {
multipartRequest = new MockMultipartHttpServletRequest();
multipartRequest.addFile(multipartFile1);
multipartRequest.addFile(multipartFile2);
+ multipartRequest.addFile(new MockMultipartFile("otherPart", "", "text/plain", content));
webRequest = new ServletWebRequest(multipartRequest, new MockHttpServletResponse());
}
@Test
public void supportsParameter() {
- assertTrue("RequestPart parameter not supported", resolver.supportsParameter(paramRequestPart));
- assertTrue("MultipartFile parameter not supported", resolver.supportsParameter(paramMultipartFileNotAnnot));
- assertTrue("Part parameter not supported", resolver.supportsParameter(paramPart));
- assertTrue("List<Part> parameter not supported", resolver.supportsParameter(paramPartList));
- assertTrue("Part[] parameter not supported", resolver.supportsParameter(paramPartArray));
- assertTrue("MultipartFile parameter not supported", resolver.supportsParameter(paramMultipartFile));
- assertTrue("List<MultipartFile> parameter not supported", resolver.supportsParameter(paramMultipartFileList));
- assertTrue("MultipartFile[] parameter not supported", resolver.supportsParameter(paramMultipartFileArray));
- assertFalse("non-RequestPart parameter should not be supported", resolver.supportsParameter(paramInt));
- assertFalse("@RequestParam args should not be supported", resolver.supportsParameter(paramRequestParamAnnot));
+ assertTrue(resolver.supportsParameter(paramRequestPart));
+ assertTrue(resolver.supportsParameter(paramNamedRequestPart));
+ assertTrue(resolver.supportsParameter(paramValidRequestPart));
+ assertTrue(resolver.supportsParameter(paramMultipartFile));
+ assertTrue(resolver.supportsParameter(paramMultipartFileList));
+ assertTrue(resolver.supportsParameter(paramMultipartFileArray));
+ assertFalse(resolver.supportsParameter(paramInt));
+ assertTrue(resolver.supportsParameter(paramMultipartFileNotAnnot));
+ assertTrue(resolver.supportsParameter(paramPart));
+ assertTrue(resolver.supportsParameter(paramPartList));
+ assertTrue(resolver.supportsParameter(paramPartArray));
+ assertFalse(resolver.supportsParameter(paramRequestParamAnnot));
+ assertTrue(resolver.supportsParameter(optionalMultipartFile));
+ assertTrue(resolver.supportsParameter(optionalMultipartFileList));
+ assertTrue(resolver.supportsParameter(optionalPart));
+ assertTrue(resolver.supportsParameter(optionalPartList));
+ assertTrue(resolver.supportsParameter(optionalRequestPart));
}
@Test
@@ -182,6 +193,7 @@ public class RequestPartMethodArgumentResolverTests {
MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
MultipartFile expected = new MockMultipartFile("multipartFileNotAnnot", "Hello World".getBytes());
request.addFile(expected);
+ request.addFile(new MockMultipartFile("otherPart", "", "text/plain", "Hello World".getBytes()));
webRequest = new ServletWebRequest(request);
Object result = resolver.resolveArgument(paramMultipartFileNotAnnot, null, webRequest, null);
@@ -192,11 +204,12 @@ public class RequestPartMethodArgumentResolverTests {
@Test
public void resolvePartArgument() throws Exception {
- MockPart expected = new MockPart("part", "Hello World".getBytes());
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("POST");
request.setContentType("multipart/form-data");
+ MockPart expected = new MockPart("part", "Hello World".getBytes());
request.addPart(expected);
+ request.addPart(new MockPart("otherPart", "Hello World".getBytes()));
webRequest = new ServletWebRequest(request);
Object result = resolver.resolveArgument(paramPart, null, webRequest, null);
@@ -206,13 +219,14 @@ public class RequestPartMethodArgumentResolverTests {
@Test
public void resolvePartListArgument() throws Exception {
- MockPart part1 = new MockPart("requestPart1", "Hello World 1".getBytes());
- MockPart part2 = new MockPart("requestPart2", "Hello World 2".getBytes());
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("POST");
request.setContentType("multipart/form-data");
+ MockPart part1 = new MockPart("requestPart", "Hello World 1".getBytes());
+ MockPart part2 = new MockPart("requestPart", "Hello World 2".getBytes());
request.addPart(part1);
request.addPart(part2);
+ request.addPart(new MockPart("otherPart", "Hello World".getBytes()));
webRequest = new ServletWebRequest(request);
Object result = resolver.resolveArgument(paramPartList, null, webRequest, null);
@@ -222,13 +236,14 @@ public class RequestPartMethodArgumentResolverTests {
@Test
public void resolvePartArrayArgument() throws Exception {
- MockPart part1 = new MockPart("requestPart1", "Hello World 1".getBytes());
- MockPart part2 = new MockPart("requestPart2", "Hello World 2".getBytes());
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("POST");
request.setContentType("multipart/form-data");
+ MockPart part1 = new MockPart("requestPart", "Hello World 1".getBytes());
+ MockPart part2 = new MockPart("requestPart", "Hello World 2".getBytes());
request.addPart(part1);
request.addPart(part2);
+ request.addPart(new MockPart("otherPart", "Hello World".getBytes()));
webRequest = new ServletWebRequest(request);
Object result = resolver.resolveArgument(paramPartArray, null, webRequest, null);
@@ -306,6 +321,7 @@ public class RequestPartMethodArgumentResolverTests {
MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
MultipartFile expected = new MockMultipartFile("optionalMultipartFile", "Hello World".getBytes());
request.addFile(expected);
+ request.addFile(new MockMultipartFile("otherPart", "", "text/plain", "Hello World".getBytes()));
webRequest = new ServletWebRequest(request);
Object actualValue = resolver.resolveArgument(optionalMultipartFile, null, webRequest, null);
@@ -330,12 +346,64 @@ public class RequestPartMethodArgumentResolverTests {
}
@Test
+ public void resolveOptionalMultipartFileArgumentWithoutMultipartRequest() throws Exception {
+ webRequest = new ServletWebRequest(new MockHttpServletRequest());
+
+ Object actualValue = resolver.resolveArgument(optionalMultipartFile, null, webRequest, null);
+ assertEquals("Invalid argument value", Optional.empty(), actualValue);
+
+ actualValue = resolver.resolveArgument(optionalMultipartFile, null, webRequest, null);
+ assertEquals("Invalid argument value", Optional.empty(), actualValue);
+ }
+
+ @Test
+ public void resolveOptionalMultipartFileList() throws Exception {
+ MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
+ MultipartFile expected = new MockMultipartFile("requestPart", "Hello World".getBytes());
+ request.addFile(expected);
+ request.addFile(new MockMultipartFile("otherPart", "", "text/plain", "Hello World".getBytes()));
+ webRequest = new ServletWebRequest(request);
+
+ Object actualValue = resolver.resolveArgument(optionalMultipartFileList, null, webRequest, null);
+ assertTrue(actualValue instanceof Optional);
+ assertEquals("Invalid result", Collections.singletonList(expected), ((Optional) actualValue).get());
+
+ actualValue = resolver.resolveArgument(optionalMultipartFileList, null, webRequest, null);
+ assertTrue(actualValue instanceof Optional);
+ assertEquals("Invalid result", Collections.singletonList(expected), ((Optional) actualValue).get());
+ }
+
+ @Test
+ public void resolveOptionalMultipartFileListNotPresent() throws Exception {
+ MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
+ webRequest = new ServletWebRequest(request);
+
+ Object actualValue = resolver.resolveArgument(optionalMultipartFileList, null, webRequest, null);
+ assertEquals("Invalid argument value", Optional.empty(), actualValue);
+
+ actualValue = resolver.resolveArgument(optionalMultipartFileList, null, webRequest, null);
+ assertEquals("Invalid argument value", Optional.empty(), actualValue);
+ }
+
+ @Test
+ public void resolveOptionalMultipartFileListWithoutMultipartRequest() throws Exception {
+ webRequest = new ServletWebRequest(new MockHttpServletRequest());
+
+ Object actualValue = resolver.resolveArgument(optionalMultipartFileList, null, webRequest, null);
+ assertEquals("Invalid argument value", Optional.empty(), actualValue);
+
+ actualValue = resolver.resolveArgument(optionalMultipartFileList, null, webRequest, null);
+ assertEquals("Invalid argument value", Optional.empty(), actualValue);
+ }
+
+ @Test
public void resolveOptionalPartArgument() throws Exception {
MockPart expected = new MockPart("optionalPart", "Hello World".getBytes());
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("POST");
request.setContentType("multipart/form-data");
request.addPart(expected);
+ request.addPart(new MockPart("otherPart", "Hello World".getBytes()));
webRequest = new ServletWebRequest(request);
Object actualValue = resolver.resolveArgument(optionalPart, null, webRequest, null);
@@ -362,6 +430,61 @@ public class RequestPartMethodArgumentResolverTests {
}
@Test
+ public void resolveOptionalPartArgumentWithoutMultipartRequest() throws Exception {
+ webRequest = new ServletWebRequest(new MockHttpServletRequest());
+
+ Object actualValue = resolver.resolveArgument(optionalPart, null, webRequest, null);
+ assertEquals("Invalid argument value", Optional.empty(), actualValue);
+
+ actualValue = resolver.resolveArgument(optionalPart, null, webRequest, null);
+ assertEquals("Invalid argument value", Optional.empty(), actualValue);
+ }
+
+ @Test
+ public void resolveOptionalPartList() throws Exception {
+ MockPart expected = new MockPart("requestPart", "Hello World".getBytes());
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.setMethod("POST");
+ request.setContentType("multipart/form-data");
+ request.addPart(expected);
+ request.addPart(new MockPart("otherPart", "Hello World".getBytes()));
+ webRequest = new ServletWebRequest(request);
+
+ Object actualValue = resolver.resolveArgument(optionalPartList, null, webRequest, null);
+ assertTrue(actualValue instanceof Optional);
+ assertEquals("Invalid result", Collections.singletonList(expected), ((Optional) actualValue).get());
+
+ actualValue = resolver.resolveArgument(optionalPartList, null, webRequest, null);
+ assertTrue(actualValue instanceof Optional);
+ assertEquals("Invalid result", Collections.singletonList(expected), ((Optional) actualValue).get());
+ }
+
+ @Test
+ public void resolveOptionalPartListNotPresent() throws Exception {
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.setMethod("POST");
+ request.setContentType("multipart/form-data");
+ webRequest = new ServletWebRequest(request);
+
+ Object actualValue = resolver.resolveArgument(optionalPartList, null, webRequest, null);
+ assertEquals("Invalid argument value", Optional.empty(), actualValue);
+
+ actualValue = resolver.resolveArgument(optionalPartList, null, webRequest, null);
+ assertEquals("Invalid argument value", Optional.empty(), actualValue);
+ }
+
+ @Test
+ public void resolveOptionalPartListWithoutMultipartRequest() throws Exception {
+ webRequest = new ServletWebRequest(new MockHttpServletRequest());
+
+ Object actualValue = resolver.resolveArgument(optionalPartList, null, webRequest, null);
+ assertEquals("Invalid argument value", Optional.empty(), actualValue);
+
+ actualValue = resolver.resolveArgument(optionalPartList, null, webRequest, null);
+ assertEquals("Invalid argument value", Optional.empty(), actualValue);
+ }
+
+ @Test
public void resolveOptionalRequestPart() throws Exception {
SimpleBean simpleBean = new SimpleBean("foo");
given(messageConverter.canRead(SimpleBean.class, MediaType.TEXT_PLAIN)).willReturn(true);
@@ -380,18 +503,27 @@ public class RequestPartMethodArgumentResolverTests {
@Test
public void resolveOptionalRequestPartNotPresent() throws Exception {
- given(messageConverter.canRead(SimpleBean.class, MediaType.TEXT_PLAIN)).willReturn(true);
- given(messageConverter.read(eq(SimpleBean.class), isA(RequestPartServletServerHttpRequest.class))).willReturn(null);
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.setMethod("POST");
+ request.setContentType("multipart/form-data");
+ webRequest = new ServletWebRequest(request);
- ModelAndViewContainer mavContainer = new ModelAndViewContainer();
+ Object actualValue = resolver.resolveArgument(optionalRequestPart, null, webRequest, null);
+ assertEquals("Invalid argument value", Optional.empty(), actualValue);
- Object actualValue = resolver.resolveArgument(optionalRequestPart, mavContainer, webRequest, new ValidatingBinderFactory());
+ actualValue = resolver.resolveArgument(optionalRequestPart, null, webRequest, null);
assertEquals("Invalid argument value", Optional.empty(), actualValue);
- assertFalse("The requestHandled flag shouldn't change", mavContainer.isRequestHandled());
+ }
- actualValue = resolver.resolveArgument(optionalRequestPart, mavContainer, webRequest, new ValidatingBinderFactory());
+ @Test
+ public void resolveOptionalRequestPartWithoutMultipartRequest() throws Exception {
+ webRequest = new ServletWebRequest(new MockHttpServletRequest());
+
+ Object actualValue = resolver.resolveArgument(optionalRequestPart, null, webRequest, null);
+ assertEquals("Invalid argument value", Optional.empty(), actualValue);
+
+ actualValue = resolver.resolveArgument(optionalRequestPart, null, webRequest, null);
assertEquals("Invalid argument value", Optional.empty(), actualValue);
- assertFalse("The requestHandled flag shouldn't change", mavContainer.isRequestHandled());
}
@@ -451,7 +583,9 @@ public class RequestPartMethodArgumentResolverTests {
@RequestPart("requestPart") Part[] partArray,
@RequestParam MultipartFile requestParamAnnot,
Optional<MultipartFile> optionalMultipartFile,
+ @RequestPart("requestPart") Optional<List<MultipartFile>> optionalMultipartFileList,
Optional<Part> optionalPart,
+ @RequestPart("requestPart") Optional<List<Part>> optionalPartList,
@RequestPart("requestPart") Optional<SimpleBean> optionalRequestPart) {
}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorMockTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorMockTests.java
index 8a4cef7a..645e66dd 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorMockTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorMockTests.java
@@ -32,6 +32,8 @@ import org.junit.Before;
import org.junit.Test;
import org.springframework.core.MethodParameter;
+import org.springframework.core.io.ByteArrayResource;
+import org.springframework.core.io.Resource;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
@@ -65,7 +67,9 @@ public class RequestResponseBodyMethodProcessorMockTests {
private RequestResponseBodyMethodProcessor processor;
- private HttpMessageConverter<String> messageConverter;
+ private HttpMessageConverter<String> stringMessageConverter;
+
+ private HttpMessageConverter<Resource> resourceMessageConverter;
private MethodParameter paramRequestBodyString;
private MethodParameter paramInt;
@@ -74,6 +78,7 @@ public class RequestResponseBodyMethodProcessorMockTests {
private MethodParameter returnTypeString;
private MethodParameter returnTypeInt;
private MethodParameter returnTypeStringProduces;
+ private MethodParameter returnTypeResource;
private ModelAndViewContainer mavContainer;
@@ -81,14 +86,19 @@ public class RequestResponseBodyMethodProcessorMockTests {
private MockHttpServletRequest servletRequest;
+ private MockHttpServletResponse servletResponse;
+
@SuppressWarnings("unchecked")
@Before
public void setUp() throws Exception {
- messageConverter = mock(HttpMessageConverter.class);
- given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
+ stringMessageConverter = mock(HttpMessageConverter.class);
+ given(stringMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
+
+ resourceMessageConverter = mock(HttpMessageConverter.class);
+ given(resourceMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.ALL));
- processor = new RequestResponseBodyMethodProcessor(Collections.<HttpMessageConverter<?>>singletonList(messageConverter));
+ processor = new RequestResponseBodyMethodProcessor(Arrays.asList(stringMessageConverter, resourceMessageConverter));
Method methodHandle1 = getClass().getMethod("handle1", String.class, Integer.TYPE);
paramRequestBodyString = new MethodParameter(methodHandle1, 0);
@@ -96,6 +106,7 @@ public class RequestResponseBodyMethodProcessorMockTests {
returnTypeString = new MethodParameter(methodHandle1, -1);
returnTypeInt = new MethodParameter(getClass().getMethod("handle2"), -1);
returnTypeStringProduces = new MethodParameter(getClass().getMethod("handle3"), -1);
+ returnTypeResource = new MethodParameter(getClass().getMethod("handle6"), -1);
paramValidBean = new MethodParameter(getClass().getMethod("handle4", SimpleBean.class), 0);
paramStringNotRequired = new MethodParameter(getClass().getMethod("handle5", String.class), 0);
@@ -103,7 +114,8 @@ public class RequestResponseBodyMethodProcessorMockTests {
servletRequest = new MockHttpServletRequest();
servletRequest.setMethod("POST");
- webRequest = new ServletWebRequest(servletRequest, new MockHttpServletResponse());
+ servletResponse = new MockHttpServletResponse();
+ webRequest = new ServletWebRequest(servletRequest, servletResponse);
}
@Test
@@ -126,8 +138,8 @@ public class RequestResponseBodyMethodProcessorMockTests {
String body = "Foo";
servletRequest.setContent(body.getBytes(Charset.forName("UTF-8")));
- given(messageConverter.canRead(String.class, contentType)).willReturn(true);
- given(messageConverter.read(eq(String.class), isA(HttpInputMessage.class))).willReturn(body);
+ given(stringMessageConverter.canRead(String.class, contentType)).willReturn(true);
+ given(stringMessageConverter.read(eq(String.class), isA(HttpInputMessage.class))).willReturn(body);
Object result = processor.resolveArgument(paramRequestBodyString, mavContainer, webRequest, new ValidatingBinderFactory());
@@ -174,7 +186,7 @@ public class RequestResponseBodyMethodProcessorMockTests {
servletRequest.addHeader("Content-Type", contentType.toString());
servletRequest.setContent("payload".getBytes(Charset.forName("UTF-8")));
- given(messageConverter.canRead(String.class, contentType)).willReturn(false);
+ given(stringMessageConverter.canRead(String.class, contentType)).willReturn(false);
processor.resolveArgument(paramRequestBodyString, mavContainer, webRequest, null);
}
@@ -182,7 +194,7 @@ public class RequestResponseBodyMethodProcessorMockTests {
@Test(expected = HttpMediaTypeNotSupportedException.class)
public void resolveArgumentNoContentType() throws Exception {
servletRequest.setContent("payload".getBytes(Charset.forName("UTF-8")));
- given(messageConverter.canRead(String.class, MediaType.APPLICATION_OCTET_STREAM)).willReturn(false);
+ given(stringMessageConverter.canRead(String.class, MediaType.APPLICATION_OCTET_STREAM)).willReturn(false);
processor.resolveArgument(paramRequestBodyString, mavContainer, webRequest, null);
}
@@ -199,8 +211,8 @@ public class RequestResponseBodyMethodProcessorMockTests {
public void resolveArgumentRequiredNoContent() throws Exception {
servletRequest.setContentType(MediaType.TEXT_PLAIN_VALUE);
servletRequest.setContent(new byte[0]);
- given(messageConverter.canRead(String.class, MediaType.TEXT_PLAIN)).willReturn(true);
- given(messageConverter.read(eq(String.class), isA(HttpInputMessage.class))).willReturn(null);
+ given(stringMessageConverter.canRead(String.class, MediaType.TEXT_PLAIN)).willReturn(true);
+ given(stringMessageConverter.read(eq(String.class), isA(HttpInputMessage.class))).willReturn(null);
assertNull(processor.resolveArgument(paramRequestBodyString, mavContainer, webRequest, new ValidatingBinderFactory()));
}
@@ -208,7 +220,7 @@ public class RequestResponseBodyMethodProcessorMockTests {
public void resolveArgumentNotRequiredNoContent() throws Exception {
servletRequest.setContentType("text/plain");
servletRequest.setContent(new byte[0]);
- given(messageConverter.canRead(String.class, MediaType.TEXT_PLAIN)).willReturn(true);
+ given(stringMessageConverter.canRead(String.class, MediaType.TEXT_PLAIN)).willReturn(true);
assertNull(processor.resolveArgument(paramStringNotRequired, mavContainer, webRequest, new ValidatingBinderFactory()));
}
@@ -216,8 +228,8 @@ public class RequestResponseBodyMethodProcessorMockTests {
@Test
public void resolveArgumentNotRequiredNoContentNoContentType() throws Exception {
servletRequest.setContent(new byte[0]);
- given(messageConverter.canRead(String.class, MediaType.TEXT_PLAIN)).willReturn(true);
- given(messageConverter.canRead(String.class, MediaType.APPLICATION_OCTET_STREAM)).willReturn(false);
+ given(stringMessageConverter.canRead(String.class, MediaType.TEXT_PLAIN)).willReturn(true);
+ given(stringMessageConverter.canRead(String.class, MediaType.APPLICATION_OCTET_STREAM)).willReturn(false);
assertNull(processor.resolveArgument(paramStringNotRequired, mavContainer, webRequest, new ValidatingBinderFactory()));
}
@@ -225,7 +237,7 @@ public class RequestResponseBodyMethodProcessorMockTests {
public void resolveArgumentNotGetRequests() throws Exception {
servletRequest.setMethod("GET");
servletRequest.setContent(new byte[0]);
- given(messageConverter.canRead(String.class, MediaType.APPLICATION_OCTET_STREAM)).willReturn(false);
+ given(stringMessageConverter.canRead(String.class, MediaType.APPLICATION_OCTET_STREAM)).willReturn(false);
assertNull(processor.resolveArgument(paramStringNotRequired, mavContainer, webRequest, new ValidatingBinderFactory()));
}
@@ -235,14 +247,14 @@ public class RequestResponseBodyMethodProcessorMockTests {
servletRequest.addHeader("Accept", accepted.toString());
String body = "Foo";
- given(messageConverter.canWrite(String.class, null)).willReturn(true);
- given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
- given(messageConverter.canWrite(String.class, accepted)).willReturn(true);
+ given(stringMessageConverter.canWrite(String.class, null)).willReturn(true);
+ given(stringMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
+ given(stringMessageConverter.canWrite(String.class, accepted)).willReturn(true);
processor.handleReturnValue(body, returnTypeString, mavContainer, webRequest);
assertTrue("The requestHandled flag wasn't set", mavContainer.isRequestHandled());
- verify(messageConverter).write(eq(body), eq(accepted), isA(HttpOutputMessage.class));
+ verify(stringMessageConverter).write(eq(body), eq(accepted), isA(HttpOutputMessage.class));
}
@Test
@@ -252,12 +264,12 @@ public class RequestResponseBodyMethodProcessorMockTests {
servletRequest.addHeader("Accept", "text/*");
servletRequest.setAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, Collections.singleton(MediaType.TEXT_HTML));
- given(messageConverter.canWrite(String.class, MediaType.TEXT_HTML)).willReturn(true);
+ given(stringMessageConverter.canWrite(String.class, MediaType.TEXT_HTML)).willReturn(true);
processor.handleReturnValue(body, returnTypeStringProduces, mavContainer, webRequest);
assertTrue(mavContainer.isRequestHandled());
- verify(messageConverter).write(eq(body), eq(MediaType.TEXT_HTML), isA(HttpOutputMessage.class));
+ verify(stringMessageConverter).write(eq(body), eq(MediaType.TEXT_HTML), isA(HttpOutputMessage.class));
}
@@ -266,9 +278,9 @@ public class RequestResponseBodyMethodProcessorMockTests {
MediaType accepted = MediaType.APPLICATION_ATOM_XML;
servletRequest.addHeader("Accept", accepted.toString());
- given(messageConverter.canWrite(String.class, null)).willReturn(true);
- given(messageConverter.getSupportedMediaTypes()).willReturn(Arrays.asList(MediaType.TEXT_PLAIN));
- given(messageConverter.canWrite(String.class, accepted)).willReturn(false);
+ given(stringMessageConverter.canWrite(String.class, null)).willReturn(true);
+ given(stringMessageConverter.getSupportedMediaTypes()).willReturn(Arrays.asList(MediaType.TEXT_PLAIN));
+ given(stringMessageConverter.canWrite(String.class, accepted)).willReturn(false);
processor.handleReturnValue("Foo", returnTypeString, mavContainer, webRequest);
}
@@ -278,13 +290,28 @@ public class RequestResponseBodyMethodProcessorMockTests {
MediaType accepted = MediaType.TEXT_PLAIN;
servletRequest.addHeader("Accept", accepted.toString());
- given(messageConverter.canWrite(String.class, null)).willReturn(true);
- given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
- given(messageConverter.canWrite(String.class, accepted)).willReturn(false);
+ given(stringMessageConverter.canWrite(String.class, null)).willReturn(true);
+ given(stringMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
+ given(stringMessageConverter.canWrite(String.class, accepted)).willReturn(false);
processor.handleReturnValue("Foo", returnTypeStringProduces, mavContainer, webRequest);
}
+ @Test
+ public void handleReturnTypeResource() throws Exception {
+ Resource returnValue = new ByteArrayResource("Content".getBytes(Charset.forName("UTF-8")));
+
+ given(resourceMessageConverter.canWrite(ByteArrayResource.class, null)).willReturn(true);
+ given(resourceMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.ALL));
+ given(resourceMessageConverter.canWrite(ByteArrayResource.class, MediaType.APPLICATION_OCTET_STREAM)).willReturn(true);
+
+ processor.handleReturnValue(returnValue, returnTypeResource, mavContainer, webRequest);
+
+ then(resourceMessageConverter).should(times(1)).write(any(ByteArrayResource.class),
+ eq(MediaType.APPLICATION_OCTET_STREAM), any(HttpOutputMessage.class));
+ assertEquals(200, servletResponse.getStatus());
+ }
+
// SPR-9841
@Test
@@ -295,14 +322,14 @@ public class RequestResponseBodyMethodProcessorMockTests {
servletRequest.addHeader("Accept", accepted);
- given(messageConverter.canWrite(String.class, null)).willReturn(true);
- given(messageConverter.getSupportedMediaTypes()).willReturn(supported);
- given(messageConverter.canWrite(String.class, accepted)).willReturn(true);
+ given(stringMessageConverter.canWrite(String.class, null)).willReturn(true);
+ given(stringMessageConverter.getSupportedMediaTypes()).willReturn(supported);
+ given(stringMessageConverter.canWrite(String.class, accepted)).willReturn(true);
processor.handleReturnValue(body, returnTypeStringProduces, mavContainer, webRequest);
assertTrue(mavContainer.isRequestHandled());
- verify(messageConverter).write(eq(body), eq(accepted), isA(HttpOutputMessage.class));
+ verify(stringMessageConverter).write(eq(body), eq(accepted), isA(HttpOutputMessage.class));
}
@@ -331,6 +358,10 @@ public class RequestResponseBodyMethodProcessorMockTests {
public void handle5(@RequestBody(required=false) String s) {
}
+ @SuppressWarnings("unused")
+ @ResponseBody
+ public Resource handle6() {return null;}
+
private final class ValidatingBinderFactory implements WebDataBinderFactory {
@Override
public WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName) throws Exception {
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java
index 32ddb8bd..2d7d789a 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -77,9 +77,8 @@ import static org.junit.Assert.assertTrue;
/**
* Test fixture for a {@link RequestResponseBodyMethodProcessor} with
- * actual delegation to {@link HttpMessageConverter} instances.
- *
- * <p>Also see {@link RequestResponseBodyMethodProcessorMockTests}.
+ * actual delegation to {@link HttpMessageConverter} instances. Also see
+ * {@link RequestResponseBodyMethodProcessorMockTests}.
*
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
@@ -93,22 +92,22 @@ public class RequestResponseBodyMethodProcessorTests {
private MethodParameter paramString;
private MethodParameter returnTypeString;
- private ModelAndViewContainer mavContainer;
+ private ModelAndViewContainer container;
- private NativeWebRequest webRequest;
+ private NativeWebRequest request;
private MockHttpServletRequest servletRequest;
private MockHttpServletResponse servletResponse;
- private ValidatingBinderFactory binderFactory;
+ private ValidatingBinderFactory factory;
@Before
public void setUp() throws Exception {
- Method method = getClass().getDeclaredMethod("handle", List.class,
- SimpleBean.class, MultiValueMap.class, String.class);
+ Method method = getClass().getDeclaredMethod("handle", List.class, SimpleBean.class,
+ MultiValueMap.class, String.class);
paramGenericList = new MethodParameter(method, 0);
paramSimpleBean = new MethodParameter(method, 1);
@@ -116,14 +115,14 @@ public class RequestResponseBodyMethodProcessorTests {
paramString = new MethodParameter(method, 3);
returnTypeString = new MethodParameter(method, -1);
- mavContainer = new ModelAndViewContainer();
+ container = new ModelAndViewContainer();
servletRequest = new MockHttpServletRequest();
servletRequest.setMethod("POST");
servletResponse = new MockHttpServletResponse();
- webRequest = new ServletWebRequest(servletRequest, servletResponse);
+ request = new ServletWebRequest(servletRequest, servletResponse);
- this.binderFactory = new ValidatingBinderFactory();
+ this.factory = new ValidatingBinderFactory();
}
@Test
@@ -132,13 +131,13 @@ public class RequestResponseBodyMethodProcessorTests {
this.servletRequest.setContent(content.getBytes("UTF-8"));
this.servletRequest.setContentType(MediaType.APPLICATION_JSON_VALUE);
- List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
+ List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(new MappingJackson2HttpMessageConverter());
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
@SuppressWarnings("unchecked")
List<SimpleBean> result = (List<SimpleBean>) processor.resolveArgument(
- paramGenericList, mavContainer, webRequest, binderFactory);
+ paramGenericList, container, request, factory);
assertNotNull(result);
assertEquals("Jad", result.get(0).getName());
@@ -152,13 +151,13 @@ public class RequestResponseBodyMethodProcessorTests {
this.servletRequest.setContent(content.getBytes("UTF-8"));
this.servletRequest.setContentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE);
- List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
+ List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(new AllEncompassingFormHttpMessageConverter());
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
@SuppressWarnings("unchecked")
MultiValueMap<String, String> result = (MultiValueMap<String, String>) processor.resolveArgument(
- paramMultiValueMap, mavContainer, webRequest, binderFactory);
+ paramMultiValueMap, container, request, factory);
assertNotNull(result);
assertEquals("apple", result.getFirst("fruit"));
@@ -171,12 +170,12 @@ public class RequestResponseBodyMethodProcessorTests {
this.servletRequest.setContent(content.getBytes("UTF-8"));
this.servletRequest.setContentType("application/json");
- List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
+ List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(new MappingJackson2HttpMessageConverter());
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
SimpleBean result = (SimpleBean) processor.resolveArgument(
- paramSimpleBean, mavContainer, webRequest, binderFactory);
+ paramSimpleBean, container, request, factory);
assertNotNull(result);
assertEquals("Jad", result.getName());
@@ -188,12 +187,12 @@ public class RequestResponseBodyMethodProcessorTests {
this.servletRequest.setContent(content.getBytes("UTF-8"));
this.servletRequest.setContentType("application/json");
- List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
+ List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(new StringHttpMessageConverter());
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
String result = (String) processor.resolveArgument(
- paramString, mavContainer, webRequest, binderFactory);
+ paramString, container, request, factory);
assertNotNull(result);
assertEquals("foobarbaz", result);
@@ -203,10 +202,10 @@ public class RequestResponseBodyMethodProcessorTests {
public void resolveArgumentRequiredNoContent() throws Exception {
this.servletRequest.setContent(new byte[0]);
this.servletRequest.setContentType("text/plain");
- List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
+ List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(new StringHttpMessageConverter());
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
- processor.resolveArgument(paramString, mavContainer, webRequest, binderFactory);
+ processor.resolveArgument(paramString, container, request, factory);
}
@Test // SPR-12778
@@ -216,7 +215,7 @@ public class RequestResponseBodyMethodProcessorTests {
List<HttpMessageConverter<?>> converters = Collections.singletonList(new StringHttpMessageConverter());
List<Object> advice = Collections.singletonList(new EmptyRequestBodyAdvice());
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters, advice);
- String arg = (String) processor.resolveArgument(paramString, mavContainer, webRequest, binderFactory);
+ String arg = (String) processor.resolveArgument(paramString, container, request, factory);
assertNotNull(arg);
assertEquals("default value for empty body", arg);
}
@@ -231,16 +230,39 @@ public class RequestResponseBodyMethodProcessorTests {
this.servletRequest.setContent(content.getBytes("UTF-8"));
this.servletRequest.setContentType(MediaType.APPLICATION_JSON_VALUE);
- List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
+ List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(new MappingJackson2HttpMessageConverter());
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
- SimpleBean result = (SimpleBean) processor.resolveArgument(methodParam, mavContainer, webRequest, binderFactory);
+ SimpleBean result = (SimpleBean) processor.resolveArgument(methodParam, container, request, factory);
assertNotNull(result);
assertEquals("Jad", result.getName());
}
+ @Test // SPR-14470
+ public void resolveParameterizedWithTypeVariableArgument() throws Exception {
+ Method method = MyParameterizedControllerWithList.class.getMethod("handleDto", List.class);
+ HandlerMethod handlerMethod = new HandlerMethod(new MySimpleParameterizedControllerWithList(), method);
+ MethodParameter methodParam = handlerMethod.getMethodParameters()[0];
+
+ String content = "[{\"name\" : \"Jad\"}, {\"name\" : \"Robert\"}]";
+ this.servletRequest.setContent(content.getBytes("UTF-8"));
+ this.servletRequest.setContentType(MediaType.APPLICATION_JSON_VALUE);
+
+ List<HttpMessageConverter<?>> converters = new ArrayList<>();
+ converters.add(new MappingJackson2HttpMessageConverter());
+ RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
+
+ @SuppressWarnings("unchecked")
+ List<SimpleBean> result = (List<SimpleBean>)
+ processor.resolveArgument(methodParam, container, request, factory);
+
+ assertNotNull(result);
+ assertEquals("Jad", result.get(0).getName());
+ assertEquals("Robert", result.get(1).getName());
+ }
+
@Test // SPR-11225
public void resolveArgumentTypeVariableWithNonGenericConverter() throws Exception {
Method method = MyParameterizedController.class.getMethod("handleDto", Identifiable.class);
@@ -251,13 +273,13 @@ public class RequestResponseBodyMethodProcessorTests {
this.servletRequest.setContent(content.getBytes("UTF-8"));
this.servletRequest.setContentType(MediaType.APPLICATION_JSON_VALUE);
- List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
+ List<HttpMessageConverter<?>> converters = new ArrayList<>();
HttpMessageConverter target = new MappingJackson2HttpMessageConverter();
HttpMessageConverter proxy = ProxyFactory.getProxy(HttpMessageConverter.class, new SingletonTargetSource(target));
converters.add(proxy);
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
- SimpleBean result = (SimpleBean) processor.resolveArgument(methodParam, mavContainer, webRequest, binderFactory);
+ SimpleBean result = (SimpleBean) processor.resolveArgument(methodParam, container, request, factory);
assertNotNull(result);
assertEquals("Jad", result.getName());
@@ -267,24 +289,42 @@ public class RequestResponseBodyMethodProcessorTests {
public void handleReturnValueSortByQuality() throws Exception {
this.servletRequest.addHeader("Accept", "text/plain; q=0.5, application/json");
- List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
+ List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(new MappingJackson2HttpMessageConverter());
converters.add(new StringHttpMessageConverter());
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
- processor.writeWithMessageConverters("Foo", returnTypeString, webRequest);
+ processor.writeWithMessageConverters("Foo", returnTypeString, request);
assertEquals("application/json;charset=UTF-8", servletResponse.getHeader("Content-Type"));
}
@Test
public void handleReturnValueString() throws Exception {
- List<HttpMessageConverter<?>>converters = new ArrayList<HttpMessageConverter<?>>();
+ List<HttpMessageConverter<?>>converters = new ArrayList<>();
converters.add(new ByteArrayHttpMessageConverter());
converters.add(new StringHttpMessageConverter());
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
- processor.handleReturnValue("Foo", returnTypeString, mavContainer, webRequest);
+ processor.handleReturnValue("Foo", returnTypeString, container, request);
+
+ assertEquals("text/plain;charset=ISO-8859-1", servletResponse.getHeader("Content-Type"));
+ assertEquals("Foo", servletResponse.getContentAsString());
+ }
+
+ // SPR-13423
+
+ @Test
+ public void handleReturnValueCharSequence() throws Exception {
+ List<HttpMessageConverter<?>>converters = new ArrayList<>();
+ converters.add(new ByteArrayHttpMessageConverter());
+ converters.add(new StringHttpMessageConverter());
+
+ Method method = ResponseBodyController.class.getMethod("handleWithCharSequence");
+ MethodParameter returnType = new MethodParameter(method, -1);
+
+ RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
+ processor.handleReturnValue(new StringBuilder("Foo"), returnType, container, request);
assertEquals("text/plain;charset=ISO-8859-1", servletResponse.getHeader("Content-Type"));
assertEquals("Foo", servletResponse.getContentAsString());
@@ -294,12 +334,12 @@ public class RequestResponseBodyMethodProcessorTests {
public void handleReturnValueStringAcceptCharset() throws Exception {
this.servletRequest.addHeader("Accept", "text/plain;charset=UTF-8");
- List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
+ List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(new ByteArrayHttpMessageConverter());
converters.add(new StringHttpMessageConverter());
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
- processor.writeWithMessageConverters("Foo", returnTypeString, webRequest);
+ processor.writeWithMessageConverters("Foo", returnTypeString, request);
assertEquals("text/plain;charset=UTF-8", servletResponse.getHeader("Content-Type"));
}
@@ -313,11 +353,12 @@ public class RequestResponseBodyMethodProcessorTests {
Method method = getClass().getDeclaredMethod("getImage");
MethodParameter returnType = new MethodParameter(method, -1);
- List<HttpMessageConverter<?>> converters = Collections.singletonList(new ResourceHttpMessageConverter());
+ List<HttpMessageConverter<?>> converters = new ArrayList<>();
+ converters.add(new ResourceHttpMessageConverter());
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
ClassPathResource resource = new ClassPathResource("logo.jpg", getClass());
- processor.writeWithMessageConverters(resource, returnType, this.webRequest);
+ processor.writeWithMessageConverters(resource, returnType, this.request);
assertEquals("image/jpeg", this.servletResponse.getHeader("Content-Type"));
}
@@ -329,7 +370,7 @@ public class RequestResponseBodyMethodProcessorTests {
Method method = getClass().getDeclaredMethod("handleAndReturnOutputStream");
MethodParameter returnType = new MethodParameter(method, -1);
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(new ArrayList<>());
- processor.writeWithMessageConverters(new ByteArrayOutputStream(), returnType, this.webRequest);
+ processor.writeWithMessageConverters(new ByteArrayOutputStream(), returnType, this.request);
}
@Test
@@ -368,7 +409,7 @@ public class RequestResponseBodyMethodProcessorTests {
Method method = ResponseBodyController.class.getMethod("handle");
MethodParameter returnType = new MethodParameter(method, -1);
- List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
+ List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(new StringHttpMessageConverter());
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
@@ -381,7 +422,7 @@ public class RequestResponseBodyMethodProcessorTests {
Method method = TestRestController.class.getMethod("handle");
MethodParameter returnType = new MethodParameter(method, -1);
- List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
+ List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(new StringHttpMessageConverter());
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
@@ -395,14 +436,14 @@ public class RequestResponseBodyMethodProcessorTests {
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
MethodParameter methodReturnType = handlerMethod.getReturnType();
- List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
+ List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(new MappingJackson2HttpMessageConverter());
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(
converters, null, Collections.singletonList(new JsonViewResponseBodyAdvice()));
Object returnValue = new JacksonController().handleResponseBody();
- processor.handleReturnValue(returnValue, methodReturnType, this.mavContainer, this.webRequest);
+ processor.handleReturnValue(returnValue, methodReturnType, this.container, this.request);
String content = this.servletResponse.getContentAsString();
assertFalse(content.contains("\"withView1\":\"with\""));
@@ -416,14 +457,14 @@ public class RequestResponseBodyMethodProcessorTests {
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
MethodParameter methodReturnType = handlerMethod.getReturnType();
- List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
+ List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(new MappingJackson2HttpMessageConverter());
HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(
converters, null, Collections.singletonList(new JsonViewResponseBodyAdvice()));
Object returnValue = new JacksonController().handleResponseEntity();
- processor.handleReturnValue(returnValue, methodReturnType, this.mavContainer, this.webRequest);
+ processor.handleReturnValue(returnValue, methodReturnType, this.container, this.request);
String content = this.servletResponse.getContentAsString();
assertFalse(content.contains("\"withView1\":\"with\""));
@@ -437,14 +478,14 @@ public class RequestResponseBodyMethodProcessorTests {
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
MethodParameter methodReturnType = handlerMethod.getReturnType();
- List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
+ List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(new MappingJackson2XmlHttpMessageConverter());
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(
converters, null, Collections.singletonList(new JsonViewResponseBodyAdvice()));
Object returnValue = new JacksonController().handleResponseBody();
- processor.handleReturnValue(returnValue, methodReturnType, this.mavContainer, this.webRequest);
+ processor.handleReturnValue(returnValue, methodReturnType, this.container, this.request);
String content = this.servletResponse.getContentAsString();
assertFalse(content.contains("<withView1>with</withView1>"));
@@ -458,14 +499,14 @@ public class RequestResponseBodyMethodProcessorTests {
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
MethodParameter methodReturnType = handlerMethod.getReturnType();
- List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
+ List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(new MappingJackson2XmlHttpMessageConverter());
HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(
converters, null, Collections.singletonList(new JsonViewResponseBodyAdvice()));
Object returnValue = new JacksonController().handleResponseEntity();
- processor.handleReturnValue(returnValue, methodReturnType, this.mavContainer, this.webRequest);
+ processor.handleReturnValue(returnValue, methodReturnType, this.container, this.request);
String content = this.servletResponse.getContentAsString();
assertFalse(content.contains("<withView1>with</withView1>"));
@@ -483,7 +524,7 @@ public class RequestResponseBodyMethodProcessorTests {
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
MethodParameter methodParameter = handlerMethod.getMethodParameters()[0];
- List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
+ List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(new MappingJackson2HttpMessageConverter());
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(
@@ -491,7 +532,7 @@ public class RequestResponseBodyMethodProcessorTests {
@SuppressWarnings("unchecked")
JacksonViewBean result = (JacksonViewBean)processor.resolveArgument(methodParameter,
- this.mavContainer, this.webRequest, this.binderFactory);
+ this.container, this.request, this.factory);
assertNotNull(result);
assertEquals("with", result.getWithView1());
@@ -509,15 +550,15 @@ public class RequestResponseBodyMethodProcessorTests {
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
MethodParameter methodParameter = handlerMethod.getMethodParameters()[0];
- List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
+ List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(new MappingJackson2HttpMessageConverter());
HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(
converters, null, Collections.singletonList(new JsonViewRequestBodyAdvice()));
@SuppressWarnings("unchecked")
- HttpEntity<JacksonViewBean> result = (HttpEntity<JacksonViewBean>)processor.resolveArgument(methodParameter,
- this.mavContainer, this.webRequest, this.binderFactory);
+ HttpEntity<JacksonViewBean> result = (HttpEntity<JacksonViewBean>)processor.resolveArgument(
+ methodParameter, this.container, this.request, this.factory);
assertNotNull(result);
assertNotNull(result.getBody());
@@ -536,7 +577,7 @@ public class RequestResponseBodyMethodProcessorTests {
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
MethodParameter methodParameter = handlerMethod.getMethodParameters()[0];
- List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
+ List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(new MappingJackson2XmlHttpMessageConverter());
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(
@@ -544,7 +585,7 @@ public class RequestResponseBodyMethodProcessorTests {
@SuppressWarnings("unchecked")
JacksonViewBean result = (JacksonViewBean)processor.resolveArgument(methodParameter,
- this.mavContainer, this.webRequest, this.binderFactory);
+ this.container, this.request, this.factory);
assertNotNull(result);
assertEquals("with", result.getWithView1());
@@ -562,7 +603,7 @@ public class RequestResponseBodyMethodProcessorTests {
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
MethodParameter methodParameter = handlerMethod.getMethodParameters()[0];
- List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
+ List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(new MappingJackson2XmlHttpMessageConverter());
HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(
@@ -570,7 +611,7 @@ public class RequestResponseBodyMethodProcessorTests {
@SuppressWarnings("unchecked")
HttpEntity<JacksonViewBean> result = (HttpEntity<JacksonViewBean>)processor.resolveArgument(methodParameter,
- this.mavContainer, this.webRequest, this.binderFactory);
+ this.container, this.request, this.factory);
assertNotNull(result);
assertNotNull(result.getBody());
@@ -585,12 +626,12 @@ public class RequestResponseBodyMethodProcessorTests {
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
MethodParameter methodReturnType = handlerMethod.getReturnType();
- List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
+ List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(new MappingJackson2HttpMessageConverter());
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
Object returnValue = new JacksonController().handleTypeInfoList();
- processor.handleReturnValue(returnValue, methodReturnType, this.mavContainer, this.webRequest);
+ processor.handleReturnValue(returnValue, methodReturnType, this.container, this.request);
String content = this.servletResponse.getContentAsString();
assertTrue(content.contains("\"type\":\"foo\""));
@@ -603,12 +644,12 @@ public class RequestResponseBodyMethodProcessorTests {
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
MethodParameter methodReturnType = handlerMethod.getReturnType();
- List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
+ List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(new MappingJackson2HttpMessageConverter());
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
Object returnValue = new JacksonController().handleSubType();
- processor.handleReturnValue(returnValue, methodReturnType, this.mavContainer, this.webRequest);
+ processor.handleReturnValue(returnValue, methodReturnType, this.container, this.request);
String content = this.servletResponse.getContentAsString();
assertTrue(content.contains("\"id\":123"));
@@ -621,12 +662,12 @@ public class RequestResponseBodyMethodProcessorTests {
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
MethodParameter methodReturnType = handlerMethod.getReturnType();
- List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
+ List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(new MappingJackson2HttpMessageConverter());
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
Object returnValue = new JacksonController().handleSubTypeList();
- processor.handleReturnValue(returnValue, methodReturnType, this.mavContainer, this.webRequest);
+ processor.handleReturnValue(returnValue, methodReturnType, this.container, this.request);
String content = this.servletResponse.getContentAsString();
assertTrue(content.contains("\"id\":123"));
@@ -635,11 +676,27 @@ public class RequestResponseBodyMethodProcessorTests {
assertTrue(content.contains("\"name\":\"bar\""));
}
+ @Test // SPR-13631
+ public void defaultCharset() throws Exception {
+ Method method = JacksonController.class.getMethod("defaultCharset");
+ HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
+ MethodParameter methodReturnType = handlerMethod.getReturnType();
+
+ List<HttpMessageConverter<?>> converters = new ArrayList<>();
+ converters.add(new MappingJackson2HttpMessageConverter());
+ RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
+
+ Object returnValue = new JacksonController().defaultCharset();
+ processor.handleReturnValue(returnValue, methodReturnType, this.container, this.request);
+
+ assertEquals("UTF-8", this.servletResponse.getCharacterEncoding());
+ }
+
private void assertContentDisposition(RequestResponseBodyMethodProcessor processor,
boolean expectContentDisposition, String requestURI, String comment) throws Exception {
this.servletRequest.setRequestURI(requestURI);
- processor.handleReturnValue("body", this.returnTypeString, this.mavContainer, this.webRequest);
+ processor.handleReturnValue("body", this.returnTypeString, this.container, this.request);
String header = servletResponse.getHeader("Content-Disposition");
if (expectContentDisposition) {
@@ -652,7 +709,7 @@ public class RequestResponseBodyMethodProcessorTests {
this.servletRequest = new MockHttpServletRequest();
this.servletResponse = new MockHttpServletResponse();
- this.webRequest = new ServletWebRequest(servletRequest, servletResponse);
+ this.request = new ServletWebRequest(servletRequest, servletResponse);
}
@@ -691,6 +748,17 @@ public class RequestResponseBodyMethodProcessorTests {
void setId(Long id);
}
+ @SuppressWarnings("unused")
+ private static abstract class MyParameterizedControllerWithList<DTO extends Identifiable> {
+
+ public void handleDto(@RequestBody List<DTO> dto) {
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private static class MySimpleParameterizedControllerWithList extends MyParameterizedControllerWithList<SimpleBean> {
+ }
+
@SuppressWarnings({ "serial" })
private static class SimpleBean implements Identifiable {
@@ -722,7 +790,7 @@ public class RequestResponseBodyMethodProcessorTests {
private final class ValidatingBinderFactory implements WebDataBinderFactory {
@Override
- public WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName) throws Exception {
+ public WebDataBinder createBinder(NativeWebRequest request, Object target, String objectName) {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
WebDataBinder dataBinder = new WebDataBinder(target, objectName);
@@ -739,6 +807,11 @@ public class RequestResponseBodyMethodProcessorTests {
public String handle() {
return "hello";
}
+
+ @RequestMapping
+ public CharSequence handleWithCharSequence() {
+ return null;
+ }
}
@@ -854,7 +927,7 @@ public class RequestResponseBodyMethodProcessorTests {
bean.setWithoutView("without");
ModelAndView mav = new ModelAndView(new MappingJackson2JsonView());
mav.addObject("bean", bean);
- return new ResponseEntity<JacksonViewBean>(bean, HttpStatus.OK);
+ return new ResponseEntity<>(bean, HttpStatus.OK);
}
@RequestMapping
@@ -898,6 +971,13 @@ public class RequestResponseBodyMethodProcessorTests {
bar.setName("bar");
return Arrays.asList(foo, bar);
}
+
+ @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE)
+ @ResponseBody
+ public String defaultCharset() {
+ return "foo";
+ }
+
}
private static class EmptyRequestBodyAdvice implements RequestBodyAdvice {
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandlerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandlerTests.java
index c38dd69d..bf367815 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandlerTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandlerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@ import static org.springframework.web.servlet.mvc.method.annotation.SseEmitter.*
import java.lang.reflect.Method;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
@@ -51,14 +52,12 @@ public class ResponseBodyEmitterReturnValueHandlerTests {
private ResponseBodyEmitterReturnValueHandler handler;
- private ModelAndViewContainer mavContainer;
-
- private NativeWebRequest webRequest;
-
private MockHttpServletRequest request;
private MockHttpServletResponse response;
+ private NativeWebRequest webRequest;
+
@Before
public void setUp() throws Exception {
@@ -67,8 +66,6 @@ public class ResponseBodyEmitterReturnValueHandlerTests {
new StringHttpMessageConverter(), new MappingJackson2HttpMessageConverter());
this.handler = new ResponseBodyEmitterReturnValueHandler(converters);
- this.mavContainer = new ModelAndViewContainer();
-
this.request = new MockHttpServletRequest();
this.response = new MockHttpServletResponse();
this.webRequest = new ServletWebRequest(this.request, this.response);
@@ -80,18 +77,19 @@ public class ResponseBodyEmitterReturnValueHandlerTests {
@Test
public void supportsReturnType() throws Exception {
- assertTrue(this.handler.supportsReturnType(returnType(TestController.class, "handle")));
- assertTrue(this.handler.supportsReturnType(returnType(TestController.class, "handleSse")));
- assertTrue(this.handler.supportsReturnType(returnType(TestController.class, "handleResponseEntity")));
- assertFalse(this.handler.supportsReturnType(returnType(TestController.class, "handleResponseEntityString")));
- assertFalse(this.handler.supportsReturnType(returnType(TestController.class, "handleResponseEntityParameterized")));
+ assertTrue(this.handler.supportsReturnType(returnType("handle")));
+ assertTrue(this.handler.supportsReturnType(returnType("handleSse")));
+ assertTrue(this.handler.supportsReturnType(returnType("handleResponseEntity")));
+ assertFalse(this.handler.supportsReturnType(returnType("handleResponseEntityString")));
+ assertFalse(this.handler.supportsReturnType(returnType("handleResponseEntityParameterized")));
+ assertFalse(this.handler.supportsReturnType(returnType("handleRawResponseEntity")));
}
@Test
public void responseBodyEmitter() throws Exception {
- MethodParameter returnType = returnType(TestController.class, "handle");
+ MethodParameter returnType = returnType("handle");
ResponseBodyEmitter emitter = new ResponseBodyEmitter();
- this.handler.handleReturnValue(emitter, returnType, this.mavContainer, this.webRequest);
+ handleReturnValue(emitter, returnType);
assertTrue(this.request.isAsyncStarted());
assertEquals("", this.response.getContentAsString());
@@ -133,8 +131,8 @@ public class ResponseBodyEmitterReturnValueHandlerTests {
emitter.onTimeout(mock(Runnable.class));
emitter.onCompletion(mock(Runnable.class));
- MethodParameter returnType = returnType(TestController.class, "handle");
- this.handler.handleReturnValue(emitter, returnType, this.mavContainer, this.webRequest);
+ MethodParameter returnType = returnType("handle");
+ handleReturnValue(emitter, returnType);
verify(asyncWebRequest).setTimeout(19000L);
verify(asyncWebRequest).addTimeoutHandler(any(Runnable.class));
@@ -144,13 +142,13 @@ public class ResponseBodyEmitterReturnValueHandlerTests {
@Test
public void sseEmitter() throws Exception {
- MethodParameter returnType = returnType(TestController.class, "handleSse");
+ MethodParameter returnType = returnType("handleSse");
SseEmitter emitter = new SseEmitter();
- this.handler.handleReturnValue(emitter, returnType, this.mavContainer, this.webRequest);
+ handleReturnValue(emitter, returnType);
assertTrue(this.request.isAsyncStarted());
assertEquals(200, this.response.getStatus());
- assertEquals("text/event-stream", this.response.getContentType());
+ assertEquals("text/event-stream;charset=UTF-8", this.response.getContentType());
SimpleBean bean1 = new SimpleBean();
bean1.setId(1L);
@@ -174,29 +172,34 @@ public class ResponseBodyEmitterReturnValueHandlerTests {
@Test
public void responseEntitySse() throws Exception {
- MethodParameter returnType = returnType(TestController.class, "handleResponseEntitySse");
- ResponseEntity<SseEmitter> emitter = ResponseEntity.ok().header("foo", "bar").body(new SseEmitter());
- this.handler.handleReturnValue(emitter, returnType, this.mavContainer, this.webRequest);
+ MethodParameter returnType = returnType("handleResponseEntitySse");
+ ResponseEntity<SseEmitter> entity = ResponseEntity.ok().header("foo", "bar").body(new SseEmitter());
+ handleReturnValue(entity, returnType);
assertTrue(this.request.isAsyncStarted());
assertEquals(200, this.response.getStatus());
- assertEquals("text/event-stream", this.response.getContentType());
+ assertEquals("text/event-stream;charset=UTF-8", this.response.getContentType());
assertEquals("bar", this.response.getHeader("foo"));
}
@Test
public void responseEntitySseNoContent() throws Exception {
- MethodParameter returnType = returnType(TestController.class, "handleResponseEntitySse");
- ResponseEntity<?> emitter = ResponseEntity.noContent().build();
- this.handler.handleReturnValue(emitter, returnType, this.mavContainer, this.webRequest);
+ MethodParameter returnType = returnType("handleResponseEntitySse");
+ ResponseEntity<?> entity = ResponseEntity.noContent().header("foo", "bar").build();
+ handleReturnValue(entity, returnType);
assertFalse(this.request.isAsyncStarted());
assertEquals(204, this.response.getStatus());
+ assertEquals(Collections.singletonList("bar"), this.response.getHeaders("foo"));
}
+ private void handleReturnValue(Object returnValue, MethodParameter returnType) throws Exception {
+ ModelAndViewContainer mavContainer = new ModelAndViewContainer();
+ this.handler.handleReturnValue(returnValue, returnType, mavContainer, this.webRequest);
+ }
- private MethodParameter returnType(Class<?> clazz, String methodName) throws NoSuchMethodException {
- Method method = clazz.getDeclaredMethod(methodName);
+ private MethodParameter returnType(String methodName) throws NoSuchMethodException {
+ Method method = TestController.class.getDeclaredMethod(methodName);
return new MethodParameter(method, -1);
}
@@ -229,6 +232,10 @@ public class ResponseBodyEmitterReturnValueHandlerTests {
return null;
}
+ private ResponseEntity handleRawResponseEntity() {
+ return null;
+ }
+
}
private static class SimpleBean {
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java
index 66354638..48163090 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,7 +19,6 @@ package org.springframework.web.servlet.mvc.method.annotation;
import java.beans.PropertyEditorSupport;
import java.io.IOException;
import java.io.Serializable;
-import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -106,7 +105,6 @@ import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
-import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.accept.ContentNegotiationManagerFactoryBean;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.CookieValue;
@@ -141,30 +139,23 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.web.servlet.support.RequestContextUtils;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.*;
/**
* The origin of this test class is {@link ServletAnnotationControllerHandlerMethodTests}.
*
* Tests in this class run against the {@link HandlerMethod} infrastructure:
* <ul>
- * <li>RequestMappingHandlerMapping
- * <li>RequestMappingHandlerAdapter
- * <li>ExceptionHandlerExceptionResolver
+ * <li>RequestMappingHandlerMapping
+ * <li>RequestMappingHandlerAdapter
+ * <li>ExceptionHandlerExceptionResolver
* </ul>
*
* <p>Rather than against the existing infrastructure:
* <ul>
- * <li>DefaultAnnotationHandlerMapping
- * <li>AnnotationMethodHandlerAdapter
- * <li>AnnotationMethodHandlerExceptionResolver
+ * <li>DefaultAnnotationHandlerMapping
+ * <li>AnnotationMethodHandlerAdapter
+ * <li>AnnotationMethodHandlerExceptionResolver
* </ul>
*
* @author Rossen Stoyanchev
@@ -184,6 +175,18 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
}
@Test
+ public void errorThrownFromHandlerMethod() throws Exception {
+ initServletWithControllers(ControllerWithErrorThrown.class);
+
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
+ request.setContextPath("/foo");
+ request.setServletPath("");
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ getServlet().service(request, response);
+ assertEquals("test", response.getContentAsString());
+ }
+
+ @Test
public void customAnnotationController() throws Exception {
initServletWithControllers(CustomAnnotationController.class);
@@ -969,7 +972,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
public void httpEntity() throws ServletException, IOException {
initServletWithControllers(ResponseEntityController.class);
- MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/foo");
+ MockHttpServletRequest request = new MockHttpServletRequest("POST", "/foo");
String requestBody = "Hello World";
request.setContent(requestBody.getBytes("UTF-8"));
request.addHeader("Content-Type", "text/plain; charset=utf-8");
@@ -981,7 +984,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
assertEquals(requestBody, response.getContentAsString());
assertEquals("MyValue", response.getHeader("MyResponseHeader"));
- request = new MockHttpServletRequest("PUT", "/bar");
+ request = new MockHttpServletRequest("GET", "/bar");
response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals("MyValue", response.getHeader("MyResponseHeader"));
@@ -1013,7 +1016,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
request.addHeader("Accept", "application/json, text/javascript, */*");
MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response);
- assertEquals("Invalid content-type", "application/json", response.getHeader("Content-Type"));
+ assertEquals("Invalid content-type", "application/json;charset=ISO-8859-1", response.getHeader("Content-Type"));
}
@Test
@@ -1533,7 +1536,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
getServlet().service(request, response);
assertEquals(200, response.getStatus());
- assertEquals("application/json", response.getHeader("Content-Type"));
+ assertEquals("application/json;charset=ISO-8859-1", response.getHeader("Content-Type"));
assertEquals("homeJson", response.getContentAsString());
}
@@ -1654,7 +1657,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
getServlet().service(request, response);
assertEquals(200, response.getStatus());
- assertEquals("text/html", response.getContentType());
+ assertEquals("text/html;charset=ISO-8859-1", response.getContentType());
assertEquals("inline;filename=f.txt", response.getHeader("Content-Disposition"));
assertArrayEquals(content, response.getContentAsByteArray());
}
@@ -1680,7 +1683,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
getServlet().service(request, response);
assertEquals(200, response.getStatus());
- assertEquals("text/html", response.getContentType());
+ assertEquals("text/html;charset=ISO-8859-1", response.getContentType());
assertNull(response.getHeader("Content-Disposition"));
assertArrayEquals(content, response.getContentAsByteArray());
}
@@ -1706,7 +1709,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
getServlet().service(request, response);
assertEquals(200, response.getStatus());
- assertEquals("text/html", response.getContentType());
+ assertEquals("text/html;charset=ISO-8859-1", response.getContentType());
assertNull(response.getHeader("Content-Disposition"));
assertArrayEquals(content, response.getContentAsByteArray());
}
@@ -1732,11 +1735,73 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
getServlet().service(request, response);
assertEquals(200, response.getStatus());
- assertEquals("text/css", response.getContentType());
+ assertEquals("text/css;charset=ISO-8859-1", response.getContentType());
assertNull(response.getHeader("Content-Disposition"));
assertArrayEquals(content, response.getContentAsByteArray());
}
+ @Test
+ public void modelAndViewWithStatus() throws Exception {
+ initServletWithControllers(ModelAndViewController.class);
+
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", "/path");
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ getServlet().service(request, response);
+
+ assertEquals(422, response.getStatus());
+ assertEquals("view", response.getForwardedUrl());
+ }
+
+ @Test
+ public void httpHead() throws ServletException, IOException {
+ initServletWithControllers(ResponseEntityController.class);
+
+ MockHttpServletRequest request = new MockHttpServletRequest("HEAD", "/baz");
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ getServlet().service(request, response);
+
+ assertEquals(200, response.getStatus());
+ assertEquals("MyValue", response.getHeader("MyResponseHeader"));
+ assertEquals(4, response.getContentLength());
+ assertTrue(response.getContentAsByteArray().length == 0);
+
+ // Now repeat with GET
+ request = new MockHttpServletRequest("GET", "/baz");
+ response = new MockHttpServletResponse();
+ getServlet().service(request, response);
+
+ assertEquals(200, response.getStatus());
+ assertEquals("MyValue", response.getHeader("MyResponseHeader"));
+ assertEquals(4, response.getContentLength());
+ assertEquals("body", response.getContentAsString());
+ }
+
+ @Test
+ public void httpHeadExplicit() throws ServletException, IOException {
+ initServletWithControllers(ResponseEntityController.class);
+
+ MockHttpServletRequest request = new MockHttpServletRequest("HEAD", "/stores");
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ getServlet().service(request, response);
+
+ assertEquals(200, response.getStatus());
+ assertEquals("v1", response.getHeader("h1"));
+ }
+
+ @Test
+ public void httpOptions() throws ServletException, IOException {
+ initServletWithControllers(ResponseEntityController.class);
+
+ MockHttpServletRequest request = new MockHttpServletRequest("OPTIONS", "/baz");
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ getServlet().service(request, response);
+
+ assertEquals(200, response.getStatus());
+ assertEquals("GET,HEAD", response.getHeader("Allow"));
+ assertTrue(response.getContentAsByteArray().length == 0);
+ }
+
+
/*
* Controllers
*/
@@ -1761,6 +1826,25 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
}
@Controller
+ private static class ControllerWithErrorThrown {
+
+ @RequestMapping("")
+ public void myPath2(HttpServletResponse response) throws IOException {
+ throw new AssertionError("test");
+ }
+
+ @RequestMapping("/bar")
+ public void myPath3(HttpServletResponse response) throws IOException {
+ response.getWriter().write("testX");
+ }
+
+ @ExceptionHandler
+ public void myPath2(Error err, HttpServletResponse response) throws IOException {
+ response.getWriter().write(err.getMessage());
+ }
+ }
+
+ @Controller
static class MyAdaptedController {
@RequestMapping("/myPath1.do")
@@ -3007,25 +3091,37 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
@Controller
public static class ResponseEntityController {
- @RequestMapping("/foo")
- public ResponseEntity<String> foo(HttpEntity<byte[]> requestEntity) throws UnsupportedEncodingException {
+ @RequestMapping(path = "/foo", method = RequestMethod.POST)
+ public ResponseEntity<String> foo(HttpEntity<byte[]> requestEntity) throws Exception {
assertNotNull(requestEntity);
assertEquals("MyValue", requestEntity.getHeaders().getFirst("MyRequestHeader"));
- String requestBody = new String(requestEntity.getBody(), "UTF-8");
- assertEquals("Hello World", requestBody);
- HttpHeaders responseHeaders = new HttpHeaders();
- responseHeaders.set("MyResponseHeader", "MyValue");
- return new ResponseEntity<String>(requestBody, responseHeaders, HttpStatus.CREATED);
+ String body = new String(requestEntity.getBody(), "UTF-8");
+ assertEquals("Hello World", body);
+
+ URI location = new URI("/foo");
+ return ResponseEntity.created(location).header("MyResponseHeader", "MyValue").body(body);
}
- @RequestMapping("/bar")
- public ResponseEntity<String> bar() {
- HttpHeaders responseHeaders = new HttpHeaders();
- responseHeaders.set("MyResponseHeader", "MyValue");
- return new ResponseEntity<String>(responseHeaders, HttpStatus.NOT_FOUND);
+ @RequestMapping(path = "/bar", method = RequestMethod.GET)
+ public ResponseEntity<Void> bar() {
+ return ResponseEntity.notFound().header("MyResponseHeader", "MyValue").build();
}
+ @RequestMapping(path = "/baz", method = RequestMethod.GET)
+ public ResponseEntity<String> baz() {
+ return ResponseEntity.ok().header("MyResponseHeader", "MyValue").body("body");
+ }
+
+ @RequestMapping(path = "/stores", method = RequestMethod.HEAD)
+ public ResponseEntity<Void> headResource() {
+ return ResponseEntity.ok().header("h1", "v1").build();
+ }
+
+ @RequestMapping(path = "/stores", method = RequestMethod.GET)
+ public ResponseEntity<String> getResource() {
+ return ResponseEntity.ok().body("body");
+ }
}
@Controller
@@ -3220,6 +3316,14 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
}
}
+ @Controller
+ public static class ModelAndViewController {
+
+ @RequestMapping("/path")
+ public ModelAndView methodWithHttpStatus(MyEntity object) {
+ return new ModelAndView("view", new ModelMap(), HttpStatus.UNPROCESSABLE_ENTITY);
+ }
+ }
// Test cases deleted from the original ServletAnnotationControllerTests:
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethodTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethodTests.java
index a1ba9246..b2e886a9 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethodTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethodTests.java
@@ -94,6 +94,15 @@ public class ServletInvocableHandlerMethodTests {
}
@Test
+ public void invokeAndHandle_VoidWithTypeLevelResponseStatus() throws Exception {
+ ServletInvocableHandlerMethod handlerMethod = getHandlerMethod(new ResponseStatusHandler(), "handle");
+ handlerMethod.invokeAndHandle(this.webRequest, this.mavContainer);
+
+ assertTrue(this.mavContainer.isRequestHandled());
+ assertEquals(HttpStatus.BAD_REQUEST.value(), this.response.getStatus());
+ }
+
+ @Test
public void invokeAndHandle_VoidWithHttpServletResponseArgument() throws Exception {
this.argumentResolvers.addResolver(new ServletResponseMethodArgumentResolver());
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/SessionAttributeMethodArgumentResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/SessionAttributeMethodArgumentResolverTests.java
new file mode 100644
index 00000000..20cd57e0
--- /dev/null
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/SessionAttributeMethodArgumentResolverTests.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.servlet.mvc.method.annotation;
+
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+
+/**
+ * Unit tests for {@link SessionAttributeMethodArgumentResolver}.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3
+ */
+public class SessionAttributeMethodArgumentResolverTests extends AbstractRequestAttributesArgumentResolverTests {
+
+ @Override
+ protected HandlerMethodArgumentResolver createResolver() {
+ return new SessionAttributeMethodArgumentResolver();
+ }
+
+ @Override
+ protected String getHandleMethodName() {
+ return "handleWithSessionAttribute";
+ }
+
+ @Override
+ protected int getScope() {
+ return RequestAttributes.SCOPE_SESSION;
+ }
+
+}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/StreamingResponseBodyReturnValueHandlerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/StreamingResponseBodyReturnValueHandlerTests.java
index 7be780de..30cf9995 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/StreamingResponseBodyReturnValueHandlerTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/StreamingResponseBodyReturnValueHandlerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,12 +15,9 @@
*/
package org.springframework.web.servlet.mvc.method.annotation;
-import static org.junit.Assert.*;
-
-import java.io.IOException;
-import java.io.OutputStream;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
+import java.util.Collections;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@@ -39,6 +36,10 @@ import org.springframework.web.context.request.async.StandardServletAsyncWebRequ
import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.method.support.ModelAndViewContainer;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
/**
* Unit tests for
@@ -74,6 +75,7 @@ public class StreamingResponseBodyReturnValueHandlerTests {
this.request.setAsyncSupported(true);
}
+
@Test
public void supportsReturnType() throws Exception {
assertTrue(this.handler.supportsReturnType(returnType(TestController.class, "handle")));
@@ -88,13 +90,9 @@ public class StreamingResponseBodyReturnValueHandlerTests {
CountDownLatch latch = new CountDownLatch(1);
MethodParameter returnType = returnType(TestController.class, "handle");
- StreamingResponseBody streamingBody = new StreamingResponseBody() {
-
- @Override
- public void writeTo(OutputStream outputStream) throws IOException {
- outputStream.write("foo".getBytes(Charset.forName("UTF-8")));
- latch.countDown();
- }
+ StreamingResponseBody streamingBody = outputStream -> {
+ outputStream.write("foo".getBytes(Charset.forName("UTF-8")));
+ latch.countDown();
};
this.handler.handleReturnValue(streamingBody, returnType, this.mavContainer, this.webRequest);
@@ -111,13 +109,9 @@ public class StreamingResponseBodyReturnValueHandlerTests {
MethodParameter returnType = returnType(TestController.class, "handleResponseEntity");
ResponseEntity<StreamingResponseBody> emitter = ResponseEntity.ok().header("foo", "bar")
- .body(new StreamingResponseBody() {
-
- @Override
- public void writeTo(OutputStream outputStream) throws IOException {
- outputStream.write("foo".getBytes(Charset.forName("UTF-8")));
- latch.countDown();
- }
+ .body(outputStream -> {
+ outputStream.write("foo".getBytes(Charset.forName("UTF-8")));
+ latch.countDown();
});
this.handler.handleReturnValue(emitter, returnType, this.mavContainer, this.webRequest);
@@ -140,6 +134,14 @@ public class StreamingResponseBodyReturnValueHandlerTests {
assertEquals(204, this.response.getStatus());
}
+ @Test
+ public void responseEntityWithHeadersAndNoContent() throws Exception {
+ ResponseEntity<?> emitter = ResponseEntity.noContent().header("foo", "bar").build();
+ MethodParameter returnType = returnType(TestController.class, "handleResponseEntity");
+ this.handler.handleReturnValue(emitter, returnType, this.mavContainer, this.webRequest);
+
+ assertEquals(Collections.singletonList("bar"), this.response.getHeaders("foo"));
+ }
private MethodParameter returnType(Class<?> clazz, String methodName) throws NoSuchMethodException {
Method method = clazz.getDeclaredMethod(methodName);
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolverTests.java
index 21bf3834..40ee1ecb 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolverTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolverTests.java
@@ -61,6 +61,7 @@ public class DefaultHandlerExceptionResolverTests {
@Before
public void setUp() {
exceptionResolver = new DefaultHandlerExceptionResolver();
+ exceptionResolver.setWarnLogCategory(exceptionResolver.getClass().getName());
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
request.setMethod("GET");
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java
index 453bdbdc..c78f7573 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java
@@ -16,20 +16,14 @@
package org.springframework.web.servlet.resource;
-import static org.junit.Assert.*;
-import static org.mockito.BDDMockito.given;
-import static org.mockito.Mockito.*;
-
-import java.io.FileNotFoundException;
import java.io.IOException;
-import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
-
import javax.servlet.http.HttpServletResponse;
import org.hamcrest.Matchers;
@@ -39,19 +33,27 @@ import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse;
import org.springframework.mock.web.test.MockServletContext;
import org.springframework.util.StringUtils;
import org.springframework.web.HttpRequestMethodNotSupportedException;
+import org.springframework.web.accept.ContentNegotiationManager;
+import org.springframework.web.accept.ContentNegotiationManagerFactoryBean;
import org.springframework.web.servlet.HandlerMapping;
+import static org.junit.Assert.*;
+
/**
* Unit tests for ResourceHttpRequestHandler.
*
* @author Keith Donald
* @author Jeremy Grelle
* @author Rossen Stoyanchev
+ * @author Brian Clozel
*/
public class ResourceHttpRequestHandlerTests {
@@ -94,10 +96,39 @@ public class ResourceHttpRequestHandlerTests {
assertEquals("max-age=3600", this.response.getHeader("Cache-Control"));
assertTrue(this.response.containsHeader("Last-Modified"));
assertEquals(this.response.getHeader("Last-Modified"), resourceLastModifiedDate("test/foo.css"));
+ assertEquals("bytes", this.response.getHeader("Accept-Ranges"));
+ assertEquals(1, this.response.getHeaders("Accept-Ranges").size());
assertEquals("h1 { color:red; }", this.response.getContentAsString());
}
@Test
+ public void getResourceHttpHeader() throws Exception {
+ this.request.setMethod("HEAD");
+ this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
+ this.handler.handleRequest(this.request, this.response);
+
+ assertEquals(200, this.response.getStatus());
+ assertEquals("text/css", this.response.getContentType());
+ assertEquals(17, this.response.getContentLength());
+ assertEquals("max-age=3600", this.response.getHeader("Cache-Control"));
+ assertTrue(this.response.containsHeader("Last-Modified"));
+ assertEquals(this.response.getHeader("Last-Modified"), resourceLastModifiedDate("test/foo.css"));
+ assertEquals("bytes", this.response.getHeader("Accept-Ranges"));
+ assertEquals(1, this.response.getHeaders("Accept-Ranges").size());
+ assertEquals(0, this.response.getContentAsByteArray().length);
+ }
+
+ @Test
+ public void getResourceHttpOptions() throws Exception {
+ this.request.setMethod("OPTIONS");
+ this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
+ this.handler.handleRequest(this.request, this.response);
+
+ assertEquals(200, this.response.getStatus());
+ assertEquals("GET,HEAD,OPTIONS", this.response.getHeader("Allow"));
+ }
+
+ @Test
public void getResourceNoCache() throws Exception {
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
this.handler.setCacheSeconds(0);
@@ -106,6 +137,8 @@ public class ResourceHttpRequestHandlerTests {
assertEquals("no-store", this.response.getHeader("Cache-Control"));
assertTrue(this.response.containsHeader("Last-Modified"));
assertEquals(this.response.getHeader("Last-Modified"), resourceLastModifiedDate("test/foo.css"));
+ assertEquals("bytes", this.response.getHeader("Accept-Ranges"));
+ assertEquals(1, this.response.getHeaders("Accept-Ranges").size());
}
@Test
@@ -119,6 +152,8 @@ public class ResourceHttpRequestHandlerTests {
this.handler.handleRequest(this.request, this.response);
assertEquals("\"versionString\"", this.response.getHeader("ETag"));
+ assertEquals("bytes", this.response.getHeader("Accept-Ranges"));
+ assertEquals(1, this.response.getHeaders("Accept-Ranges").size());
}
@Test
@@ -135,6 +170,8 @@ public class ResourceHttpRequestHandlerTests {
assertTrue(dateHeaderAsLong("Expires") >= System.currentTimeMillis() - 1000 + (3600 * 1000));
assertTrue(this.response.containsHeader("Last-Modified"));
assertEquals(this.response.getHeader("Last-Modified"), resourceLastModifiedDate("test/foo.css"));
+ assertEquals("bytes", this.response.getHeader("Accept-Ranges"));
+ assertEquals(1, this.response.getHeaders("Accept-Ranges").size());
}
@Test
@@ -153,6 +190,8 @@ public class ResourceHttpRequestHandlerTests {
assertTrue(dateHeaderAsLong("Expires") <= System.currentTimeMillis());
assertTrue(this.response.containsHeader("Last-Modified"));
assertEquals(dateHeaderAsLong("Last-Modified") / 1000, resourceLastModified("test/foo.css") / 1000);
+ assertEquals("bytes", this.response.getHeader("Accept-Ranges"));
+ assertEquals(1, this.response.getHeaders("Accept-Ranges").size());
}
@Test
@@ -164,6 +203,8 @@ public class ResourceHttpRequestHandlerTests {
assertEquals("max-age=3600", this.response.getHeader("Cache-Control"));
assertTrue(this.response.containsHeader("Last-Modified"));
assertEquals(this.response.getHeader("Last-Modified"), resourceLastModifiedDate("test/foo.html"));
+ assertEquals("bytes", this.response.getHeader("Accept-Ranges"));
+ assertEquals(1, this.response.getHeaders("Accept-Ranges").size());
}
@Test
@@ -176,6 +217,8 @@ public class ResourceHttpRequestHandlerTests {
assertEquals("max-age=3600", this.response.getHeader("Cache-Control"));
assertTrue(this.response.containsHeader("Last-Modified"));
assertEquals(this.response.getHeader("Last-Modified"), resourceLastModifiedDate("testalternatepath/baz.css"));
+ assertEquals("bytes", this.response.getHeader("Accept-Ranges"));
+ assertEquals(1, this.response.getHeaders("Accept-Ranges").size());
assertEquals("h1 { color:red; }", this.response.getContentAsString());
}
@@ -197,18 +240,96 @@ public class ResourceHttpRequestHandlerTests {
assertEquals("function foo() { console.log(\"hello world\"); }", this.response.getContentAsString());
}
+ @Test // SPR-13658
+ public void getResourceWithRegisteredMediaType() throws Exception {
+ ContentNegotiationManagerFactoryBean factory = new ContentNegotiationManagerFactoryBean();
+ factory.addMediaType("css", new MediaType("foo", "bar"));
+ factory.afterPropertiesSet();
+ ContentNegotiationManager manager = factory.getObject();
+
+ List<Resource> paths = Collections.singletonList(new ClassPathResource("test/", getClass()));
+ this.handler = new ResourceHttpRequestHandler();
+ this.handler.setLocations(paths);
+ this.handler.setContentNegotiationManager(manager);
+ this.handler.afterPropertiesSet();
+
+ this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
+ this.handler.handleRequest(this.request, this.response);
+
+ assertEquals("foo/bar", this.response.getContentType());
+ assertEquals("h1 { color:red; }", this.response.getContentAsString());
+ }
+
+ @Test // SPR-13658
+ public void getResourceWithRegisteredMediaTypeDefaultStrategy() throws Exception {
+ ContentNegotiationManagerFactoryBean factory = new ContentNegotiationManagerFactoryBean();
+ factory.setFavorPathExtension(false);
+ factory.setDefaultContentType(new MediaType("foo", "bar"));
+ factory.afterPropertiesSet();
+ ContentNegotiationManager manager = factory.getObject();
+
+ List<Resource> paths = Collections.singletonList(new ClassPathResource("test/", getClass()));
+ this.handler = new ResourceHttpRequestHandler();
+ this.handler.setLocations(paths);
+ this.handler.setContentNegotiationManager(manager);
+ this.handler.afterPropertiesSet();
+
+ this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
+ this.handler.handleRequest(this.request, this.response);
+
+ assertEquals("foo/bar", this.response.getContentType());
+ assertEquals("h1 { color:red; }", this.response.getContentAsString());
+ }
+
+ @Test // SPR-14368
+ public void getResourceWithMediaTypeResolvedThroughServletContext() throws Exception {
+ MockServletContext servletContext = new MockServletContext() {
+
+ @Override
+ public String getMimeType(String filePath) {
+ return "foo/bar";
+ }
+
+ @Override
+ public String getVirtualServerName() {
+ return null;
+ }
+ };
+
+ List<Resource> paths = Collections.singletonList(new ClassPathResource("test/", getClass()));
+ this.handler = new ResourceHttpRequestHandler();
+ this.handler.setServletContext(servletContext);
+ this.handler.setLocations(paths);
+ this.handler.afterPropertiesSet();
+
+ this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
+ this.handler.handleRequest(this.request, this.response);
+
+ assertEquals("foo/bar", this.response.getContentType());
+ assertEquals("h1 { color:red; }", this.response.getContentAsString());
+ }
+
@Test
public void invalidPath() throws Exception {
+ for (HttpMethod method : HttpMethod.values()) {
+ this.request = new MockHttpServletRequest("GET", "");
+ this.response = new MockHttpServletResponse();
+ testInvalidPath(method);
+ }
+ }
+
+ private void testInvalidPath(HttpMethod httpMethod) throws Exception {
+ this.request.setMethod(httpMethod.name());
Resource location = new ClassPathResource("test/", getClass());
- this.handler.setLocations(Arrays.asList(location));
+ this.handler.setLocations(Collections.singletonList(location));
testInvalidPath(location, "../testsecret/secret.txt");
testInvalidPath(location, "test/../../testsecret/secret.txt");
testInvalidPath(location, ":/../../testsecret/secret.txt");
location = new UrlResource(getClass().getResource("./test/"));
- this.handler.setLocations(Arrays.asList(location));
+ this.handler.setLocations(Collections.singletonList(location));
Resource secretResource = new UrlResource(getClass().getResource("testsecret/secret.txt"));
String secretPath = secretResource.getURL().getPath();
@@ -230,7 +351,7 @@ public class ResourceHttpRequestHandlerTests {
if (!location.createRelative(requestPath).exists() && !requestPath.contains(":")) {
fail(requestPath + " doesn't actually exist as a relative path");
}
- assertEquals(404, this.response.getStatus());
+ assertEquals(HttpStatus.NOT_FOUND.value(), this.response.getStatus());
}
@Test
@@ -290,7 +411,7 @@ public class ResourceHttpRequestHandlerTests {
pathResolver.setAllowedLocations(location1);
ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
- handler.setResourceResolvers(Arrays.asList(pathResolver));
+ handler.setResourceResolvers(Collections.singletonList(pathResolver));
handler.setLocations(Arrays.asList(location1, location2));
handler.afterPropertiesSet();
@@ -352,9 +473,18 @@ public class ResourceHttpRequestHandlerTests {
@Test
public void resourceNotFound() throws Exception {
+ for (HttpMethod method : HttpMethod.values()) {
+ this.request = new MockHttpServletRequest("GET", "");
+ this.response = new MockHttpServletResponse();
+ resourceNotFound(method);
+ }
+ }
+
+ private void resourceNotFound(HttpMethod httpMethod) throws Exception {
+ this.request.setMethod(httpMethod.name());
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "not-there.css");
this.handler.handleRequest(this.request, this.response);
- assertEquals(404, this.response.getStatus());
+ assertEquals(HttpStatus.NOT_FOUND.value(), this.response.getStatus());
}
@Test
@@ -368,6 +498,8 @@ public class ResourceHttpRequestHandlerTests {
assertEquals(2, this.response.getContentLength());
assertEquals("bytes 0-1/10", this.response.getHeader("Content-Range"));
assertEquals("So", this.response.getContentAsString());
+ assertEquals("bytes", this.response.getHeader("Accept-Ranges"));
+ assertEquals(1, this.response.getHeaders("Accept-Ranges").size());
}
@Test
@@ -381,6 +513,8 @@ public class ResourceHttpRequestHandlerTests {
assertEquals(1, this.response.getContentLength());
assertEquals("bytes 9-9/10", this.response.getHeader("Content-Range"));
assertEquals(".", this.response.getContentAsString());
+ assertEquals("bytes", this.response.getHeader("Accept-Ranges"));
+ assertEquals(1, this.response.getHeaders("Accept-Ranges").size());
}
@Test
@@ -394,6 +528,8 @@ public class ResourceHttpRequestHandlerTests {
assertEquals(1, this.response.getContentLength());
assertEquals("bytes 9-9/10", this.response.getHeader("Content-Range"));
assertEquals(".", this.response.getContentAsString());
+ assertEquals("bytes", this.response.getHeader("Accept-Ranges"));
+ assertEquals(1, this.response.getHeaders("Accept-Ranges").size());
}
@Test
@@ -407,6 +543,8 @@ public class ResourceHttpRequestHandlerTests {
assertEquals(1, this.response.getContentLength());
assertEquals("bytes 9-9/10", this.response.getHeader("Content-Range"));
assertEquals(".", this.response.getContentAsString());
+ assertEquals("bytes", this.response.getHeader("Accept-Ranges"));
+ assertEquals(1, this.response.getHeaders("Accept-Ranges").size());
}
@Test
@@ -420,6 +558,8 @@ public class ResourceHttpRequestHandlerTests {
assertEquals(10, this.response.getContentLength());
assertEquals("bytes 0-9/10", this.response.getHeader("Content-Range"));
assertEquals("Some text.", this.response.getContentAsString());
+ assertEquals("bytes", this.response.getHeader("Accept-Ranges"));
+ assertEquals(1, this.response.getHeaders("Accept-Ranges").size());
}
@Test
@@ -430,6 +570,8 @@ public class ResourceHttpRequestHandlerTests {
assertEquals(416, this.response.getStatus());
assertEquals("bytes */10", this.response.getHeader("Content-Range"));
+ assertEquals("bytes", this.response.getHeader("Accept-Ranges"));
+ assertEquals(1, this.response.getHeaders("Accept-Ranges").size());
}
@Test
@@ -462,47 +604,6 @@ public class ResourceHttpRequestHandlerTests {
assertEquals("t.", ranges[11]);
}
- // SPR-12999
- @Test
- public void writeContentNotGettingInputStream() throws Exception {
- Resource resource = mock(Resource.class);
- given(resource.getInputStream()).willThrow(FileNotFoundException.class);
-
- this.handler.writeContent(this.response, resource);
-
- assertEquals(200, this.response.getStatus());
- assertEquals(0, this.response.getContentLength());
- }
-
- // SPR-12999
- @Test
- public void writeContentNotClosingInputStream() throws Exception {
- Resource resource = mock(Resource.class);
- InputStream inputStream = mock(InputStream.class);
- given(resource.getInputStream()).willReturn(inputStream);
- given(inputStream.read(any())).willReturn(-1);
- doThrow(new NullPointerException()).when(inputStream).close();
-
- this.handler.writeContent(this.response, resource);
-
- assertEquals(200, this.response.getStatus());
- assertEquals(0, this.response.getContentLength());
- }
-
- // SPR-13620
- @Test
- public void writeContentInputStreamThrowingNullPointerException() throws Exception {
- Resource resource = mock(Resource.class);
- InputStream in = mock(InputStream.class);
- given(resource.getInputStream()).willReturn(in);
- given(in.read(any())).willThrow(NullPointerException.class);
-
- this.handler.writeContent(this.response, resource);
-
- assertEquals(200, this.response.getStatus());
- assertEquals(0, this.response.getContentLength());
- }
-
// SPR-14005
@Test
public void doOverwriteExistingCacheControlHeaders() throws Exception {
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceUrlProviderTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceUrlProviderTests.java
index bd0a6ac9..fbb222c0 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceUrlProviderTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceUrlProviderTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,45 +45,39 @@ import static org.junit.Assert.*;
*/
public class ResourceUrlProviderTests {
- private List<Resource> locations;
+ private final List<Resource> locations = new ArrayList<>();
- private ResourceUrlProvider translator;
+ private final ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
- private ResourceHttpRequestHandler handler;
+ private final Map<String, ResourceHttpRequestHandler> handlerMap = new HashMap<>();
- private Map<String, ResourceHttpRequestHandler> handlerMap;
+ private final ResourceUrlProvider urlProvider = new ResourceUrlProvider();
@Before
- public void setUp() {
- this.locations = new ArrayList<Resource>();
+ public void setUp() throws Exception {
this.locations.add(new ClassPathResource("test/", getClass()));
this.locations.add(new ClassPathResource("testalternatepath/", getClass()));
-
- this.handler = new ResourceHttpRequestHandler();
this.handler.setLocations(locations);
-
- this.handlerMap = new HashMap<String, ResourceHttpRequestHandler>();
+ this.handler.afterPropertiesSet();
this.handlerMap.put("/resources/**", this.handler);
+ this.urlProvider.setHandlerMap(this.handlerMap);
}
+
@Test
public void getStaticResourceUrl() {
- initTranslator();
-
- String url = this.translator.getForLookupPath("/resources/foo.css");
+ String url = this.urlProvider.getForLookupPath("/resources/foo.css");
assertEquals("/resources/foo.css", url);
}
- // SPR-13374
- @Test
+ @Test // SPR-13374
public void getStaticResourceUrlRequestWithRequestParams() {
- initTranslator();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setContextPath("/");
request.setRequestURI("/");
- String url = this.translator.getForRequestUrl(request, "/resources/foo.css?foo=bar&url=http://example.org");
+ String url = this.urlProvider.getForRequestUrl(request, "/resources/foo.css?foo=bar&url=http://example.org");
assertEquals("/resources/foo.css?foo=bar&url=http://example.org", url);
}
@@ -94,23 +88,16 @@ public class ResourceUrlProviderTests {
VersionResourceResolver versionResolver = new VersionResourceResolver();
versionResolver.setStrategyMap(versionStrategyMap);
- List<ResourceResolver> resolvers = new ArrayList<ResourceResolver>();
+ List<ResourceResolver> resolvers = new ArrayList<>();
resolvers.add(versionResolver);
resolvers.add(new PathResourceResolver());
this.handler.setResourceResolvers(resolvers);
- initTranslator();
- String url = this.translator.getForLookupPath("/resources/foo.css");
+ String url = this.urlProvider.getForLookupPath("/resources/foo.css");
assertEquals("/resources/foo-e36d2e05253c6c7085a91522ce43a0b4.css", url);
}
- private void initTranslator() {
- this.translator = new ResourceUrlProvider();
- this.translator.setHandlerMap(this.handlerMap);
- }
-
- // SPR-12647
- @Test
+ @Test // SPR-12647
public void bestPatternMatch() throws Exception {
ResourceHttpRequestHandler otherHandler = new ResourceHttpRequestHandler();
otherHandler.setLocations(this.locations);
@@ -119,36 +106,38 @@ public class ResourceUrlProviderTests {
VersionResourceResolver versionResolver = new VersionResourceResolver();
versionResolver.setStrategyMap(versionStrategyMap);
- List<ResourceResolver> resolvers = new ArrayList<ResourceResolver>();
+ List<ResourceResolver> resolvers = new ArrayList<>();
resolvers.add(versionResolver);
resolvers.add(new PathResourceResolver());
otherHandler.setResourceResolvers(resolvers);
this.handlerMap.put("/resources/*.css", otherHandler);
- initTranslator();
+ this.urlProvider.setHandlerMap(this.handlerMap);
- String url = this.translator.getForLookupPath("/resources/foo.css");
+ String url = this.urlProvider.getForLookupPath("/resources/foo.css");
assertEquals("/resources/foo-e36d2e05253c6c7085a91522ce43a0b4.css", url);
}
- // SPR-12592
- @Test
+ @Test // SPR-12592
public void initializeOnce() throws Exception {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.setServletContext(new MockServletContext());
context.register(HandlerMappingConfiguration.class);
context.refresh();
- ResourceUrlProvider translator = context.getBean(ResourceUrlProvider.class);
- assertThat(translator.getHandlerMap(), Matchers.hasKey("/resources/**"));
- assertFalse(translator.isAutodetect());
+
+ ResourceUrlProvider urlProviderBean = context.getBean(ResourceUrlProvider.class);
+ assertThat(urlProviderBean.getHandlerMap(), Matchers.hasKey("/resources/**"));
+ assertFalse(urlProviderBean.isAutodetect());
}
- @Configuration
+
+ @Configuration @SuppressWarnings("unused")
public static class HandlerMappingConfiguration {
+
@Bean
public SimpleUrlHandlerMapping simpleUrlHandlerMapping() {
ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
- HashMap<String, ResourceHttpRequestHandler> handlerMap = new HashMap<String, ResourceHttpRequestHandler>();
+ HashMap<String, ResourceHttpRequestHandler> handlerMap = new HashMap<>();
handlerMap.put("/resources/**", handler);
SimpleUrlHandlerMapping hm = new SimpleUrlHandlerMapping();
hm.setUrlMap(handlerMap);
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/VersionResourceResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/VersionResourceResolverTests.java
index d4238726..a38f416f 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/VersionResourceResolverTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/VersionResourceResolverTests.java
@@ -176,10 +176,10 @@ public class VersionResourceResolverTests {
this.resolver.addFixedVersionStrategy("fixedversion", "/js/**", "/css/**", "/fixedversion/css/**");
assertThat(this.resolver.getStrategyMap().size(), is(4));
- assertThat(this.resolver.getStrategyForPath("/js/something.js"), Matchers.instanceOf(FixedVersionStrategy.class));
- assertThat(this.resolver.getStrategyForPath("/fixedversion/js/something.js"), Matchers.instanceOf(FixedVersionStrategy.class));
- assertThat(this.resolver.getStrategyForPath("/css/something.css"), Matchers.instanceOf(FixedVersionStrategy.class));
- assertThat(this.resolver.getStrategyForPath("/fixedversion/css/something.css"), Matchers.instanceOf(FixedVersionStrategy.class));
+ assertThat(this.resolver.getStrategyForPath("js/something.js"), Matchers.instanceOf(FixedVersionStrategy.class));
+ assertThat(this.resolver.getStrategyForPath("fixedversion/js/something.js"), Matchers.instanceOf(FixedVersionStrategy.class));
+ assertThat(this.resolver.getStrategyForPath("css/something.css"), Matchers.instanceOf(FixedVersionStrategy.class));
+ assertThat(this.resolver.getStrategyForPath("fixedversion/css/something.css"), Matchers.instanceOf(FixedVersionStrategy.class));
}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/WebJarsResourceResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/WebJarsResourceResolverTests.java
index 71d932de..0241bc9e 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/WebJarsResourceResolverTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/WebJarsResourceResolverTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,10 +29,6 @@ import org.springframework.mock.web.test.MockHttpServletRequest;
import static org.junit.Assert.*;
import static org.mockito.BDDMockito.*;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
/**
* Unit tests for
@@ -54,7 +50,7 @@ public class WebJarsResourceResolverTests {
@Before
public void setup() {
// for this to work, an actual WebJar must be on the test classpath
- this.locations = Collections.singletonList(new ClassPathResource("/META-INF/resources/webjars/"));
+ this.locations = Collections.singletonList(new ClassPathResource("/META-INF/resources/webjars"));
this.resolver = new WebJarsResourceResolver();
this.chain = mock(ResourceResolverChain.class);
}
@@ -75,20 +71,20 @@ public class WebJarsResourceResolverTests {
@Test
public void resolveUrlExistingNotInJarFile() {
this.locations = Collections.singletonList(new ClassPathResource("/META-INF/resources/webjars/", getClass()));
- String file = "/foo/foo.txt";
+ String file = "foo/foo.txt";
given(this.chain.resolveUrlPath(file, this.locations)).willReturn(null);
String actual = this.resolver.resolveUrlPath(file, this.locations, this.chain);
assertNull(actual);
verify(this.chain, times(1)).resolveUrlPath(file, this.locations);
- verify(this.chain, never()).resolveUrlPath("/foo/2.3/foo.txt", this.locations);
+ verify(this.chain, never()).resolveUrlPath("foo/2.3/foo.txt", this.locations);
}
@Test
public void resolveUrlWebJarResource() {
- String file = "/underscorejs/underscore.js";
- String expected = "/underscorejs/1.8.3/underscore.js";
+ String file = "underscorejs/underscore.js";
+ String expected = "underscorejs/1.8.3/underscore.js";
given(this.chain.resolveUrlPath(file, this.locations)).willReturn(null);
given(this.chain.resolveUrlPath(expected, this.locations)).willReturn(expected);
@@ -101,7 +97,7 @@ public class WebJarsResourceResolverTests {
@Test
public void resolveUrlWebJarResourceNotFound() {
- String file = "/something/something.js";
+ String file = "something/something.js";
given(this.chain.resolveUrlPath(file, this.locations)).willReturn(null);
String actual = this.resolver.resolveUrlPath(file, this.locations, this.chain);
@@ -115,7 +111,7 @@ public class WebJarsResourceResolverTests {
public void resolveResourceExisting() {
Resource expected = mock(Resource.class);
this.locations = Collections.singletonList(new ClassPathResource("/META-INF/resources/webjars/", getClass()));
- String file = "/foo/2.3/foo.txt";
+ String file = "foo/2.3/foo.txt";
given(this.chain.resolveResource(this.request, file, this.locations)).willReturn(expected);
Resource actual = this.resolver.resolveResource(this.request, file, this.locations, this.chain);
@@ -126,7 +122,7 @@ public class WebJarsResourceResolverTests {
@Test
public void resolveResourceNotFound() {
- String file = "/something/something.js";
+ String file = "something/something.js";
given(this.chain.resolveUrlPath(file, this.locations)).willReturn(null);
Resource actual = this.resolver.resolveResource(this.request, file, this.locations, this.chain);
@@ -139,8 +135,8 @@ public class WebJarsResourceResolverTests {
@Test
public void resolveResourceWebJar() {
Resource expected = mock(Resource.class);
- String file = "/underscorejs/underscore.js";
- String expectedPath = "/underscorejs/1.8.3/underscore.js";
+ String file = "underscorejs/underscore.js";
+ String expectedPath = "underscorejs/1.8.3/underscore.js";
this.locations = Collections.singletonList(new ClassPathResource("/META-INF/resources/webjars/", getClass()));
given(this.chain.resolveResource(this.request, expectedPath, this.locations)).willReturn(expected);
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/FlashMapManagerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/FlashMapManagerTests.java
index 9a5e3cb7..333a093b 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/FlashMapManagerTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/FlashMapManagerTests.java
@@ -181,7 +181,7 @@ public class FlashMapManagerTests {
@Test
public void retrieveAndUpdateRemoveExpired() throws InterruptedException {
List<FlashMap> flashMaps = new ArrayList<FlashMap>();
- for (int i=0; i < 5; i++) {
+ for (int i = 0; i < 5; i++) {
FlashMap expiredFlashMap = new FlashMap();
expiredFlashMap.startExpirationPeriod(-1);
flashMaps.add(expiredFlashMap);
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/WebContentGeneratorTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/WebContentGeneratorTests.java
new file mode 100644
index 00000000..28563fe2
--- /dev/null
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/WebContentGeneratorTests.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.web.servlet.support;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+
+import org.springframework.mock.web.test.MockHttpServletResponse;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+/**
+ * Unit tests for {@link WebContentGenerator}.
+ * @author Rossen Stoyanchev
+ */
+public class WebContentGeneratorTests {
+
+ @Test
+ public void getAllowHeaderWithConstructorTrue() throws Exception {
+ WebContentGenerator generator = new TestWebContentGenerator(true);
+ assertEquals("GET,HEAD,POST,OPTIONS", generator.getAllowHeader());
+ }
+
+ @Test
+ public void getAllowHeaderWithConstructorFalse() throws Exception {
+ WebContentGenerator generator = new TestWebContentGenerator(false);
+ assertEquals("GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS", generator.getAllowHeader());
+ }
+
+ @Test
+ public void getAllowHeaderWithSupportedMethodsConstructor() throws Exception {
+ WebContentGenerator generator = new TestWebContentGenerator("POST");
+ assertEquals("POST,OPTIONS", generator.getAllowHeader());
+ }
+
+ @Test
+ public void getAllowHeaderWithSupportedMethodsSetter() throws Exception {
+ WebContentGenerator generator = new TestWebContentGenerator();
+ generator.setSupportedMethods("POST");
+ assertEquals("POST,OPTIONS", generator.getAllowHeader());
+ }
+
+ @Test
+ public void getAllowHeaderWithSupportedMethodsSetterEmpty() throws Exception {
+ WebContentGenerator generator = new TestWebContentGenerator();
+ generator.setSupportedMethods();
+ assertEquals("Effectively \"no restriction\" on supported methods",
+ "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS", generator.getAllowHeader());
+ }
+
+ @Test
+ public void varyHeaderNone() throws Exception {
+ WebContentGenerator generator = new TestWebContentGenerator();
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ generator.prepareResponse(response);
+
+ assertNull(response.getHeader("Vary"));
+ }
+
+ @Test
+ public void varyHeader() throws Exception {
+ String[] configuredValues = {"Accept-Language", "User-Agent"};
+ String[] responseValues = {};
+ String[] expected = {"Accept-Language", "User-Agent"};
+ testVaryHeader(configuredValues, responseValues, expected);
+ }
+
+ @Test
+ public void varyHeaderWithExistingWildcard() throws Exception {
+ String[] configuredValues = {"Accept-Language"};
+ String[] responseValues = {"*"};
+ String[] expected = {"*"};
+ testVaryHeader(configuredValues, responseValues, expected);
+ }
+
+ @Test
+ public void varyHeaderWithExistingCommaValues() throws Exception {
+ String[] configuredValues = {"Accept-Language", "User-Agent"};
+ String[] responseValues = {"Accept-Encoding", "Accept-Language"};
+ String[] expected = {"Accept-Encoding", "Accept-Language", "User-Agent"};
+ testVaryHeader(configuredValues, responseValues, expected);
+ }
+
+ @Test
+ public void varyHeaderWithExistingCommaSeparatedValues() throws Exception {
+ String[] configuredValues = {"Accept-Language", "User-Agent"};
+ String[] responseValues = {"Accept-Encoding, Accept-Language"};
+ String[] expected = {"Accept-Encoding, Accept-Language", "User-Agent"};
+ testVaryHeader(configuredValues, responseValues, expected);
+ }
+
+ private void testVaryHeader(String[] configuredValues, String[] responseValues, String[] expected) {
+ WebContentGenerator generator = new TestWebContentGenerator();
+ generator.setVaryByRequestHeaders(configuredValues);
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ for (String value : responseValues) {
+ response.addHeader("Vary", value);
+ }
+ generator.prepareResponse(response);
+ assertEquals(Arrays.asList(expected), response.getHeaderValues("Vary"));
+ }
+
+
+ private static class TestWebContentGenerator extends WebContentGenerator {
+
+ public TestWebContentGenerator() {
+ }
+
+ public TestWebContentGenerator(boolean restrictDefaultSupportedMethods) {
+ super(restrictDefaultSupportedMethods);
+ }
+
+ public TestWebContentGenerator(String... supportedMethods) {
+ super(supportedMethods);
+ }
+ }
+}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/OptionTagTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/OptionTagTests.java
index f3419532..d68d8cf7 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/OptionTagTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/OptionTagTests.java
@@ -465,7 +465,8 @@ public class OptionTagTests extends AbstractHtmlElementTagTests {
tag.setValue("foo");
tag.doStartTag();
fail("Must throw an IllegalStateException when not nested within a <select/> tag.");
- } catch (IllegalStateException ex) {
+ }
+ catch (IllegalStateException ex) {
// expected
}
}
@@ -542,7 +543,8 @@ public class OptionTagTests extends AbstractHtmlElementTagTests {
public String toId() {
if (this.variant != null) {
return this.rules + "-" + this.variant;
- } else {
+ }
+ else {
return rules;
}
}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/PasswordInputTagTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/PasswordInputTagTests.java
index b5e5a989..f85bfdaf 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/PasswordInputTagTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/PasswordInputTagTests.java
@@ -100,7 +100,8 @@ public class PasswordInputTagTests extends InputTagTests {
protected void assertValueAttribute(String output, String expectedValue) {
if (this.getPasswordTag().isShowPassword()) {
super.assertValueAttribute(output, expectedValue);
- } else {
+ }
+ else {
super.assertValueAttribute(output, "");
}
}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/RedirectViewTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/RedirectViewTests.java
index 0120d8b2..fb66f41f 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/RedirectViewTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/RedirectViewTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,12 +17,13 @@
package org.springframework.web.servlet.view;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
+import org.junit.Before;
import org.junit.Test;
import org.springframework.http.HttpStatus;
@@ -35,15 +36,18 @@ import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.support.StaticWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.FlashMap;
-import org.springframework.web.servlet.FlashMapManager;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.support.RequestDataValueProcessor;
import org.springframework.web.servlet.support.RequestDataValueProcessorWrapper;
import org.springframework.web.servlet.support.SessionFlashMapManager;
import org.springframework.web.util.WebUtils;
-import static org.junit.Assert.*;
-import static org.mockito.BDDMockito.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.BDDMockito.mock;
+import static org.mockito.BDDMockito.verify;
/**
* Tests for redirect view, and query string construction.
@@ -53,10 +57,28 @@ import static org.mockito.BDDMockito.*;
* @author Juergen Hoeller
* @author Sam Brannen
* @author Arjen Poutsma
+ * @author Rossen Stoyanchev
* @since 27.05.2003
*/
public class RedirectViewTests {
+ private MockHttpServletRequest request;
+
+ private MockHttpServletResponse response;
+
+
+ @Before
+ public void setUp() throws Exception {
+ this.request = new MockHttpServletRequest();
+ this.request.setContextPath("/context");
+ this.request.setCharacterEncoding(WebUtils.DEFAULT_CHARACTER_ENCODING);
+ this.request.setAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
+ this.request.setAttribute(DispatcherServlet.FLASH_MAP_MANAGER_ATTRIBUTE, new SessionFlashMapManager());
+ this.response = new MockHttpServletResponse();
+
+ }
+
+
@Test(expected = IllegalArgumentException.class)
public void noUrlSet() throws Exception {
RedirectView rv = new RedirectView();
@@ -68,31 +90,18 @@ public class RedirectViewTests {
RedirectView rv = new RedirectView();
rv.setUrl("http://url.somewhere.com");
rv.setHttp10Compatible(false);
- MockHttpServletRequest request = createRequest();
- MockHttpServletResponse response = new MockHttpServletResponse();
- request.setAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
- request.setAttribute(DispatcherServlet.FLASH_MAP_MANAGER_ATTRIBUTE, new SessionFlashMapManager());
- rv.render(new HashMap<String, Object>(), request, response);
+ rv.render(new HashMap<>(), request, response);
assertEquals(303, response.getStatus());
assertEquals("http://url.somewhere.com", response.getHeader("Location"));
}
- private MockHttpServletRequest createRequest() {
- MockHttpServletRequest request = new MockHttpServletRequest();
- request.setAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
- request.setAttribute(DispatcherServlet.FLASH_MAP_MANAGER_ATTRIBUTE, new SessionFlashMapManager());
- return request;
- }
-
@Test
public void explicitStatusCodeHttp11() throws Exception {
RedirectView rv = new RedirectView();
rv.setUrl("http://url.somewhere.com");
rv.setHttp10Compatible(false);
rv.setStatusCode(HttpStatus.MOVED_PERMANENTLY);
- MockHttpServletRequest request = createRequest();
- MockHttpServletResponse response = new MockHttpServletResponse();
- rv.render(new HashMap<String, Object>(), request, response);
+ rv.render(new HashMap<>(), request, response);
assertEquals(301, response.getStatus());
assertEquals("http://url.somewhere.com", response.getHeader("Location"));
}
@@ -102,9 +111,7 @@ public class RedirectViewTests {
RedirectView rv = new RedirectView();
rv.setUrl("http://url.somewhere.com");
rv.setStatusCode(HttpStatus.MOVED_PERMANENTLY);
- MockHttpServletRequest request = createRequest();
- MockHttpServletResponse response = new MockHttpServletResponse();
- rv.render(new HashMap<String, Object>(), request, response);
+ rv.render(new HashMap<>(), request, response);
assertEquals(301, response.getStatus());
assertEquals("http://url.somewhere.com", response.getHeader("Location"));
}
@@ -113,10 +120,8 @@ public class RedirectViewTests {
public void attributeStatusCodeHttp10() throws Exception {
RedirectView rv = new RedirectView();
rv.setUrl("http://url.somewhere.com");
- MockHttpServletRequest request = createRequest();
request.setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, HttpStatus.CREATED);
- MockHttpServletResponse response = new MockHttpServletResponse();
- rv.render(new HashMap<String, Object>(), request, response);
+ rv.render(new HashMap<>(), request, response);
assertEquals(201, response.getStatus());
assertEquals("http://url.somewhere.com", response.getHeader("Location"));
}
@@ -126,21 +131,18 @@ public class RedirectViewTests {
RedirectView rv = new RedirectView();
rv.setUrl("http://url.somewhere.com");
rv.setHttp10Compatible(false);
- MockHttpServletRequest request = createRequest();
request.setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, HttpStatus.CREATED);
- MockHttpServletResponse response = new MockHttpServletResponse();
- rv.render(new HashMap<String, Object>(), request, response);
+ rv.render(new HashMap<>(), request, response);
assertEquals(201, response.getStatus());
assertEquals("http://url.somewhere.com", response.getHeader("Location"));
}
+ @SuppressWarnings("AssertEqualsBetweenInconvertibleTypes")
@Test
public void flashMap() throws Exception {
RedirectView rv = new RedirectView();
rv.setUrl("http://url.somewhere.com/path");
rv.setHttp10Compatible(false);
- MockHttpServletRequest request = createRequest();
- HttpServletResponse response = new MockHttpServletResponse();
FlashMap flashMap = new FlashMap();
flashMap.put("successMessage", "yay!");
request.setAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE, flashMap);
@@ -167,9 +169,7 @@ public class RedirectViewTests {
rv.setApplicationContext(wac); // Init RedirectView with WebAppCxt
rv.setUrl("/path");
- MockHttpServletRequest request = createRequest();
request.setAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE, wac);
- HttpServletResponse response = new MockHttpServletResponse();
given(mockProcessor.processUrl(request, "/path")).willReturn("/path?key=123");
@@ -195,9 +195,6 @@ public class RedirectViewTests {
RedirectView rv = new RedirectView();
rv.setUrl("/path");
- MockHttpServletRequest request = createRequest();
- HttpServletResponse response = new MockHttpServletResponse();
-
given(mockProcessor.processUrl(request, "/path")).willReturn("/path?key=123");
rv.render(new ModelMap(), request, response);
@@ -209,16 +206,34 @@ public class RedirectViewTests {
}
}
+ // SPR-13693
+
+ @Test
+ public void remoteHost() throws Exception {
+ RedirectView rv = new RedirectView();
+
+ assertFalse(rv.isRemoteHost("http://url.somewhere.com"));
+ assertFalse(rv.isRemoteHost("/path"));
+ assertFalse(rv.isRemoteHost("http://url.somewhereelse.com"));
+
+ rv.setHosts(new String[] {"url.somewhere.com"});
+
+ assertFalse(rv.isRemoteHost("http://url.somewhere.com"));
+ assertFalse(rv.isRemoteHost("/path"));
+ assertTrue(rv.isRemoteHost("http://url.somewhereelse.com"));
+
+ }
+
@Test
public void emptyMap() throws Exception {
String url = "/myUrl";
- doTest(new HashMap<String, Object>(), url, false, url);
+ doTest(new HashMap<>(), url, false, url);
}
@Test
public void emptyMapWithContextRelative() throws Exception {
String url = "/myUrl";
- doTest(new HashMap<String, Object>(), url, true, url);
+ doTest(new HashMap<>(), url, true, "/context" + url);
}
@Test
@@ -226,7 +241,7 @@ public class RedirectViewTests {
String url = "http://url.somewhere.com";
String key = "foo";
String val = "bar";
- Map<String, String> model = new HashMap<String, String>();
+ Map<String, String> model = new HashMap<>();
model.put(key, val);
String expectedUrlForEncoding = url + "?" + key + "=" + val;
doTest(model, url, false, expectedUrlForEncoding);
@@ -235,12 +250,13 @@ public class RedirectViewTests {
@Test
public void singleParamWithoutExposingModelAttributes() throws Exception {
String url = "http://url.somewhere.com";
- String key = "foo";
- String val = "bar";
- Map<String, String> model = new HashMap<String, String>();
- model.put(key, val);
- String expectedUrlForEncoding = url; // + "?" + key + "=" + val;
- doTest(model, url, false, false, expectedUrlForEncoding);
+ Map<String, String> model = Collections.singletonMap("foo", "bar");
+
+ TestRedirectView rv = new TestRedirectView(url, false, model);
+ rv.setExposeModelAttributes(false);
+ rv.render(model, request, response);
+
+ assertEquals(url, this.response.getRedirectedUrl());
}
@Test
@@ -248,7 +264,7 @@ public class RedirectViewTests {
String url = "http://url.somewhere.com/test.htm#myAnchor";
String key = "foo";
String val = "bar";
- Map<String, String> model = new HashMap<String, String>();
+ Map<String, String> model = new HashMap<>();
model.put(key, val);
String expectedUrlForEncoding = "http://url.somewhere.com/test.htm" + "?" + key + "=" + val + "#myAnchor";
doTest(model, url, false, expectedUrlForEncoding);
@@ -257,7 +273,7 @@ public class RedirectViewTests {
@Test
public void contextRelativeQueryParam() throws Exception {
String url = "/test.html?id=1";
- doTest(new HashMap<String, Object>(), url, true, url);
+ doTest(new HashMap<>(), url, true, "/context" + url);
}
@Test
@@ -267,16 +283,16 @@ public class RedirectViewTests {
String val = "bar";
String key2 = "thisIsKey2";
String val2 = "andThisIsVal2";
- Map<String, String> model = new HashMap<String, String>();
+ Map<String, String> model = new HashMap<>();
model.put(key, val);
model.put(key2, val2);
try {
- String expectedUrlForEncoding = "http://url.somewhere.com?" + key + "=" + val + "&" + key2 + "=" + val2;
+ String expectedUrlForEncoding = url + "?" + key + "=" + val + "&" + key2 + "=" + val2;
doTest(model, url, false, expectedUrlForEncoding);
}
catch (AssertionError err) {
// OK, so it's the other order... probably on Sun JDK 1.6 or IBM JDK 1.5
- String expectedUrlForEncoding = "http://url.somewhere.com?" + key2 + "=" + val2 + "&" + key + "=" + val;
+ String expectedUrlForEncoding = url + "?" + key2 + "=" + val2 + "&" + key + "=" + val;
doTest(model, url, false, expectedUrlForEncoding);
}
}
@@ -286,15 +302,15 @@ public class RedirectViewTests {
String url = "http://url.somewhere.com";
String key = "foo";
String[] val = new String[] {"bar", "baz"};
- Map<String, String[]> model = new HashMap<String, String[]>();
+ Map<String, String[]> model = new HashMap<>();
model.put(key, val);
try {
- String expectedUrlForEncoding = "http://url.somewhere.com?" + key + "=" + val[0] + "&" + key + "=" + val[1];
+ String expectedUrlForEncoding = url + "?" + key + "=" + val[0] + "&" + key + "=" + val[1];
doTest(model, url, false, expectedUrlForEncoding);
}
catch (AssertionError err) {
// OK, so it's the other order... probably on Sun JDK 1.6 or IBM JDK 1.5
- String expectedUrlForEncoding = "http://url.somewhere.com?" + key + "=" + val[1] + "&" + key + "=" + val[0];
+ String expectedUrlForEncoding = url + "?" + key + "=" + val[1] + "&" + key + "=" + val[0];
doTest(model, url, false, expectedUrlForEncoding);
}
}
@@ -303,18 +319,18 @@ public class RedirectViewTests {
public void collectionParam() throws Exception {
String url = "http://url.somewhere.com";
String key = "foo";
- List<String> val = new ArrayList<String>();
+ List<String> val = new ArrayList<>();
val.add("bar");
val.add("baz");
- Map<String, List<String>> model = new HashMap<String, List<String>>();
+ Map<String, List<String>> model = new HashMap<>();
model.put(key, val);
try {
- String expectedUrlForEncoding = "http://url.somewhere.com?" + key + "=" + val.get(0) + "&" + key + "=" + val.get(1);
+ String expectedUrlForEncoding = url + "?" + key + "=" + val.get(0) + "&" + key + "=" + val.get(1);
doTest(model, url, false, expectedUrlForEncoding);
}
catch (AssertionError err) {
// OK, so it's the other order... probably on Sun JDK 1.6 or IBM JDK 1.5
- String expectedUrlForEncoding = "http://url.somewhere.com?" + key + "=" + val.get(1) + "&" + key + "=" + val.get(0);
+ String expectedUrlForEncoding = url + "?" + key + "=" + val.get(1) + "&" + key + "=" + val.get(0);
doTest(model, url, false, expectedUrlForEncoding);
}
}
@@ -325,14 +341,14 @@ public class RedirectViewTests {
String key = "foo";
String val = "bar";
String key2 = "int2";
- Object val2 = new Long(611);
+ Object val2 = 611;
String key3 = "tb";
Object val3 = new TestBean();
- Map<String, Object> model = new HashMap<String, Object>();
+ Map<String, Object> model = new LinkedHashMap<>();
model.put(key, val);
model.put(key2, val2);
model.put(key3, val3);
- String expectedUrlForEncoding = "http://url.somewhere.com?" + key + "=" + val + "&" + key2 + "=" + val2;
+ String expectedUrlForEncoding = url + "?" + key + "=" + val + "&" + key2 + "=" + val2;
doTest(model, url, false, expectedUrlForEncoding);
}
@@ -341,64 +357,43 @@ public class RedirectViewTests {
RedirectView rv = new RedirectView();
rv.setPropagateQueryParams(true);
rv.setUrl("http://url.somewhere.com?foo=bar#bazz");
- MockHttpServletRequest request = createRequest();
- MockHttpServletResponse response = new MockHttpServletResponse();
request.setQueryString("a=b&c=d");
- rv.render(new HashMap<String, Object>(), request, response);
+ rv.render(new HashMap<>(), request, response);
assertEquals(302, response.getStatus());
assertEquals("http://url.somewhere.com?foo=bar&a=b&c=d#bazz", response.getHeader("Location"));
}
- private void doTest(Map<String, ?> map, String url, boolean contextRelative, String expectedUrlForEncoding)
+ private void doTest(Map<String, ?> map, String url, boolean contextRelative, String expectedUrl)
throws Exception {
- doTest(map, url, contextRelative, true, expectedUrlForEncoding);
- }
- private void doTest(final Map<String, ?> map, final String url, final boolean contextRelative,
- final boolean exposeModelAttributes, String expectedUrlForEncoding) throws Exception {
-
- class TestRedirectView extends RedirectView {
+ TestRedirectView rv = new TestRedirectView(url, contextRelative, map);
+ rv.render(map, request, response);
- public boolean queryPropertiesCalled = false;
+ assertTrue("queryProperties() should have been called.", rv.queryPropertiesCalled);
+ assertEquals(expectedUrl, this.response.getRedirectedUrl());
+ }
- /**
- * Test whether this callback method is called with correct args
- */
- @Override
- protected Map<String, Object> queryProperties(Map<String, Object> model) {
- // They may not be the same model instance, but they're still equal
- assertTrue("Map and model must be equal.", map.equals(model));
- this.queryPropertiesCalled = true;
- return super.queryProperties(model);
- }
- }
- TestRedirectView rv = new TestRedirectView();
- rv.setUrl(url);
- rv.setContextRelative(contextRelative);
- rv.setExposeModelAttributes(exposeModelAttributes);
+ private static class TestRedirectView extends RedirectView {
- HttpServletRequest request = mock(HttpServletRequest.class, "request");
- if (exposeModelAttributes) {
- given(request.getCharacterEncoding()).willReturn(WebUtils.DEFAULT_CHARACTER_ENCODING);
- }
- if (contextRelative) {
- expectedUrlForEncoding = "/context" + expectedUrlForEncoding;
- given(request.getContextPath()).willReturn("/context");
- }
+ private Map<String, ?> expectedModel;
- given(request.getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE)).willReturn(new FlashMap());
+ private boolean queryPropertiesCalled = false;
- FlashMapManager flashMapManager = new SessionFlashMapManager();
- given(request.getAttribute(DispatcherServlet.FLASH_MAP_MANAGER_ATTRIBUTE)).willReturn(flashMapManager);
- HttpServletResponse response = mock(HttpServletResponse.class, "response");
- given(response.encodeRedirectURL(expectedUrlForEncoding)).willReturn(expectedUrlForEncoding);
- response.sendRedirect(expectedUrlForEncoding);
+ public TestRedirectView(String url, boolean contextRelative, Map<String, ?> expectedModel) {
+ super(url, contextRelative);
+ this.expectedModel = expectedModel;
+ }
- rv.render(map, request, response);
- if (exposeModelAttributes) {
- assertTrue("queryProperties() should have been called.", rv.queryPropertiesCalled);
+ /**
+ * Test whether this callback method is called with correct args
+ */
+ @Override
+ protected Map<String, Object> queryProperties(Map<String, Object> model) {
+ assertTrue("Map and model must be equal.", this.expectedModel.equals(model));
+ this.queryPropertiesCalled = true;
+ return super.queryProperties(model);
}
}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/freemarker/FreeMarkerViewTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/freemarker/FreeMarkerViewTests.java
index fe3c4fb3..44d3faff 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/freemarker/FreeMarkerViewTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/freemarker/FreeMarkerViewTests.java
@@ -156,9 +156,7 @@ public class FreeMarkerViewTests {
wac.getBeanFactory().registerSingleton("configurer", configurer);
wac.refresh();
- FreeMarkerViewResolver vr = new FreeMarkerViewResolver();
- vr.setPrefix("prefix_");
- vr.setSuffix("_suffix");
+ FreeMarkerViewResolver vr = new FreeMarkerViewResolver("prefix_", "_suffix");
vr.setApplicationContext(wac);
View view = vr.resolveViewName("test", Locale.CANADA);
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/jasperreports/JasperReportsMultiFormatViewTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/jasperreports/JasperReportsMultiFormatViewTests.java
index 225a8ec9..869c3e50 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/jasperreports/JasperReportsMultiFormatViewTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/jasperreports/JasperReportsMultiFormatViewTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,16 +19,11 @@ package org.springframework.web.servlet.view.jasperreports;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
-
import javax.servlet.http.HttpServletResponse;
import net.sf.jasperreports.engine.JasperPrint;
-
import org.junit.Test;
-import org.springframework.tests.Assume;
-import org.springframework.tests.TestGroup;
-
import static org.junit.Assert.*;
import static org.junit.Assume.*;
@@ -46,7 +41,6 @@ public class JasperReportsMultiFormatViewTests extends AbstractJasperReportsView
@Test
public void simpleHtmlRender() throws Exception {
- Assume.group(TestGroup.CUSTOM_COMPILATION);
assumeTrue(canCompileReport);
AbstractJasperReportsView view = getView(UNCOMPILED_REPORT);
@@ -62,7 +56,6 @@ public class JasperReportsMultiFormatViewTests extends AbstractJasperReportsView
@Test
@Override
public void overrideContentDisposition() throws Exception {
- Assume.group(TestGroup.CUSTOM_COMPILATION);
assumeTrue(canCompileReport);
AbstractJasperReportsView view = getView(UNCOMPILED_REPORT);
@@ -85,7 +78,6 @@ public class JasperReportsMultiFormatViewTests extends AbstractJasperReportsView
@Test
public void exporterParametersAreCarriedAcross() throws Exception {
- Assume.group(TestGroup.CUSTOM_COMPILATION);
assumeTrue(canCompileReport);
JasperReportsMultiFormatView view = (JasperReportsMultiFormatView) getView(UNCOMPILED_REPORT);
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/json/MappingJackson2JsonViewTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/json/MappingJackson2JsonViewTests.java
index e36d211b..4e062b2a 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/json/MappingJackson2JsonViewTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/json/MappingJackson2JsonViewTests.java
@@ -17,14 +17,27 @@
package org.springframework.web.servlet.view.json;
import java.io.IOException;
-import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.ContextFactory;
+import org.mozilla.javascript.ScriptableObject;
+
+import org.springframework.beans.DirectFieldAccessor;
+import org.springframework.http.MediaType;
+import org.springframework.mock.web.test.MockHttpServletRequest;
+import org.springframework.mock.web.test.MockHttpServletResponse;
+import org.springframework.ui.ModelMap;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.servlet.View;
+
import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.core.JsonGenerator;
@@ -40,22 +53,8 @@ import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.SerializerFactory;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.CoreMatchers.not;
-import org.junit.Before;
-import org.junit.Test;
-import org.mozilla.javascript.Context;
-import org.mozilla.javascript.ContextFactory;
-import org.mozilla.javascript.ScriptableObject;
-
-import org.springframework.beans.DirectFieldAccessor;
-import org.springframework.http.MediaType;
-import org.springframework.mock.web.test.MockHttpServletRequest;
-import org.springframework.mock.web.test.MockHttpServletResponse;
-import org.springframework.ui.ModelMap;
-import org.springframework.validation.BindingResult;
-import org.springframework.web.servlet.View;
+import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/script/ScriptTemplateViewTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/script/ScriptTemplateViewTests.java
index e08605b9..af4e8251 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/script/ScriptTemplateViewTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/script/ScriptTemplateViewTests.java
@@ -16,10 +16,8 @@
package org.springframework.web.servlet.view.script;
-import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -29,7 +27,6 @@ import java.util.concurrent.Future;
import javax.script.Invocable;
import javax.script.ScriptEngine;
-import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
@@ -47,7 +44,6 @@ import org.springframework.web.servlet.DispatcherServlet;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.BDDMockito.*;
-import static org.mockito.Mockito.mock;
/**
* Unit tests for {@link ScriptTemplateView}.
@@ -62,8 +58,6 @@ public class ScriptTemplateViewTests {
private StaticWebApplicationContext wac;
- private static final String RESOURCE_LOADER_PATH = "classpath:org/springframework/web/servlet/view/script/";
-
@Before
public void setup() {
@@ -71,7 +65,6 @@ public class ScriptTemplateViewTests {
this.wac = new StaticWebApplicationContext();
this.wac.getBeanFactory().registerSingleton("scriptTemplateConfigurer", this.configurer);
this.view = new ScriptTemplateView();
- this.view.setUrl(RESOURCE_LOADER_PATH + "empty.txt");
}
@Test
@@ -147,11 +140,11 @@ public class ScriptTemplateViewTests {
this.view.setApplicationContext(this.wac);
ExecutorService executor = Executors.newFixedThreadPool(4);
List<Future<Boolean>> results = new ArrayList<>();
- for(int i = 0; i < iterations; i++) {
+ for (int i = 0; i < iterations; i++) {
results.add(executor.submit(() -> view.getEngine() != null));
}
assertEquals(iterations, results.size());
- for(int i = 0; i < iterations; i++) {
+ for (int i = 0; i < iterations; i++) {
assertTrue(results.get(i).get());
}
executor.shutdown();
@@ -211,25 +204,35 @@ public class ScriptTemplateViewTests {
fail();
}
- @Test
- public void parentLoader() {
- this.view.setEngine(mock(InvocableScriptEngine.class));
+ @Test // SPR-14210
+ public void resourceLoaderPath() throws Exception {
+ MockServletContext servletContext = new MockServletContext();
+ this.wac.setServletContext(servletContext);
+ this.wac.refresh();
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.setAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.wac);
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ Map<String, Object> model = new HashMap<String, Object>();
+ InvocableScriptEngine engine = mock(InvocableScriptEngine.class);
+ when(engine.invokeFunction(any(), any(), any(), any())).thenReturn("foo");
+ this.view.setEngine(engine);
this.view.setRenderFunction("render");
- this.view.setResourceLoaderPath(RESOURCE_LOADER_PATH);
this.view.setApplicationContext(this.wac);
- ClassLoader classLoader = this.view.createClassLoader();
- assertNotNull(classLoader);
- URLClassLoader urlClassLoader = (URLClassLoader) classLoader;
- assertThat(Arrays.asList(urlClassLoader.getURLs()), Matchers.hasSize(1));
- assertThat(Arrays.asList(urlClassLoader.getURLs()).get(0).toString(),
- Matchers.endsWith("org/springframework/web/servlet/view/script/"));
- this.view.setResourceLoaderPath(RESOURCE_LOADER_PATH + ",classpath:org/springframework/web/servlet/view/");
- classLoader = this.view.createClassLoader();
- assertNotNull(classLoader);
- urlClassLoader = (URLClassLoader) classLoader;
- assertThat(Arrays.asList(urlClassLoader.getURLs()), Matchers.hasSize(2));
- assertThat(Arrays.asList(urlClassLoader.getURLs()).get(0).toString(), Matchers.endsWith("org/springframework/web/servlet/view/script/"));
- assertThat(Arrays.asList(urlClassLoader.getURLs()).get(1).toString(), Matchers.endsWith("org/springframework/web/servlet/view/"));
+ this.view.setUrl("org/springframework/web/servlet/view/script/empty.txt");
+ this.view.render(model, request, response);
+ assertEquals("foo", response.getContentAsString());
+
+ response = new MockHttpServletResponse();
+ this.view.setResourceLoaderPath("classpath:org/springframework/web/servlet/view/script/");
+ this.view.setUrl("empty.txt");
+ this.view.render(model, request, response);
+ assertEquals("foo", response.getContentAsString());
+
+ response = new MockHttpServletResponse();
+ this.view.setResourceLoaderPath("classpath:org/springframework/web/servlet/view/script");
+ this.view.setUrl("empty.txt");
+ this.view.render(model, request, response);
+ assertEquals("foo", response.getContentAsString());
}
@Test // SPR-13379
@@ -243,7 +246,8 @@ public class ScriptTemplateViewTests {
Map<String, Object> model = new HashMap<String, Object>();
this.view.setEngine(mock(InvocableScriptEngine.class));
this.view.setRenderFunction("render");
- this.view.setResourceLoaderPath(RESOURCE_LOADER_PATH);
+ this.view.setResourceLoaderPath("classpath:org/springframework/web/servlet/view/script/");
+ this.view.setUrl("empty.txt");
this.view.setApplicationContext(this.wac);
this.view.render(model, request, response);
diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-resources.xml b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-resources.xml
index 82dd141b..f4a216a3 100644
--- a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-resources.xml
+++ b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-resources.xml
@@ -5,6 +5,7 @@
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
+ <mvc:annotation-driven />
<mvc:resources mapping="/resources/**" location="/, classpath:/META-INF/" />
</beans>
diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-controllers-minimal.xml b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-controllers-minimal.xml
index 13092194..80006361 100644
--- a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-controllers-minimal.xml
+++ b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-controllers-minimal.xml
@@ -6,11 +6,10 @@
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
+ <mvc:view-controller path="/path" view-name="home"/>
- <mvc:view-controller path="/path" view-name="home" />
+ <mvc:redirect-view-controller path="/old" redirect-url="/new"/>
- <mvc:redirect-view-controller path="/old" redirect-url="/new" />
-
- <mvc:status-controller path="/bad" status-code="404" />
+ <mvc:status-controller path="/bad" status-code="404"/>
</beans>
diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-controllers.xml b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-controllers.xml
index 2f9c5851..dd9c36ef 100644
--- a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-controllers.xml
+++ b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-controllers.xml
@@ -1,12 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:mvc="http://www.springframework.org/schema/mvc"
+ xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
-
<mvc:annotation-driven/>
<mvc:view-controller path="/foo"/>
@@ -14,9 +13,9 @@
<mvc:view-controller path="/" view-name="root"/>
<mvc:redirect-view-controller path="/old" redirect-url="/new"
- context-relative="false" status-code="308" keep-query-params="true" />
+ context-relative="false" status-code="308" keep-query-params="true"/>
- <mvc:status-controller path="/bad" status-code="404" />
+ <mvc:status-controller path="/bad" status-code="404"/>
<mvc:interceptors>
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/>
diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-resolution-content-negotiation.xml b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-resolution-content-negotiation.xml
index 5dba4fb3..2f07f51c 100644
--- a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-resolution-content-negotiation.xml
+++ b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-resolution-content-negotiation.xml
@@ -1,45 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:mvc="http://www.springframework.org/schema/mvc"
- xsi:schemaLocation="
- http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
- http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:mvc="http://www.springframework.org/schema/mvc"
+ xsi:schemaLocation="
+ http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
+ http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd">
<!--
View resolution finds the ContentNegotiationManager created by or
registered with <mvc:annotation-driven>. Or in its absence simply declare it.
-->
- <bean id="mvcContentNegotiationManager"
- class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean"/>
+ <bean id="mvcContentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean"/>
<mvc:view-resolvers>
<mvc:content-negotiation use-not-acceptable="true">
<mvc:default-views>
- <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView" />
+ <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
</mvc:default-views>
</mvc:content-negotiation>
- <mvc:bean-name />
- <mvc:jsp />
- <mvc:tiles />
- <mvc:freemarker />
- <mvc:velocity />
- <mvc:groovy />
- <mvc:script-template />
+ <mvc:bean-name/>
+ <mvc:jsp/>
+ <mvc:tiles/>
+ <mvc:freemarker/>
+ <mvc:velocity/>
+ <mvc:groovy/>
+ <mvc:script-template/>
</mvc:view-resolvers>
<mvc:tiles-configurer check-refresh="true">
- <mvc:definitions location="/org/springframework/web/servlet/resource/tiles/tiles1.xml" />
+ <mvc:definitions location="/org/springframework/web/servlet/resource/tiles/tiles1.xml"/>
</mvc:tiles-configurer>
<mvc:freemarker-configurer>
- <mvc:template-loader-path location="/org/springframework/web/servlet/view" />
+ <mvc:template-loader-path location="/org/springframework/web/servlet/view"/>
</mvc:freemarker-configurer>
- <mvc:velocity-configurer resource-loader-path="/org/springframework/web/servlet/view" />
+ <mvc:velocity-configurer resource-loader-path="/org/springframework/web/servlet/view"/>
- <mvc:groovy-configurer />
+ <mvc:groovy-configurer/>
<mvc:script-template-configurer engine-name="nashorn" render-function="render"/>
diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-resolution-custom-order.xml b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-resolution-custom-order.xml
index 713d85a7..aa038322 100644
--- a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-resolution-custom-order.xml
+++ b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-resolution-custom-order.xml
@@ -1,13 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:mvc="http://www.springframework.org/schema/mvc"
- xsi:schemaLocation="
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:mvc="http://www.springframework.org/schema/mvc"
+ xsi:schemaLocation="
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<mvc:view-resolvers order="123">
- <mvc:bean-name />
+ <mvc:bean-name/>
</mvc:view-resolvers>
</beans>
diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-resolution.xml b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-resolution.xml
index 3422243a..af2043a4 100644
--- a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-resolution.xml
+++ b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-resolution.xml
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:mvc="http://www.springframework.org/schema/mvc"
- xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
- http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:mvc="http://www.springframework.org/schema/mvc"
+ xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
+ http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd">
<mvc:view-resolvers>
<mvc:bean-name/>
@@ -20,8 +20,8 @@
<bean id="customResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"/>
<mvc:tiles-configurer check-refresh="true" validate-definitions="true"
- definitions-factory="org.apache.tiles.definition.UnresolvingLocaleDefinitionsFactory"
- preparer-factory="org.springframework.web.servlet.view.tiles3.SpringBeanPreparerFactory">
+ definitions-factory="org.apache.tiles.definition.UnresolvingLocaleDefinitionsFactory"
+ preparer-factory="org.springframework.web.servlet.view.tiles3.SpringBeanPreparerFactory">
<mvc:definitions location="/org/springframework/web/servlet/resource/tiles/tiles1.xml"/>
<mvc:definitions location="/org/springframework/web/servlet/resource/tiles/tiles2.xml"/>
</mvc:tiles-configurer>
@@ -35,9 +35,8 @@
<mvc:groovy-configurer resource-loader-path="/test" cache-templates="false" auto-indent="true"/>
- <mvc:script-template-configurer engine-name="nashorn" render-function="render"
- content-type="text/plain" charset="ISO-8859-1"
- resource-loader-path="classpath:" shared-engine="false">
+ <mvc:script-template-configurer engine-name="nashorn" render-function="render" content-type="text/plain"
+ charset="ISO-8859-1" resource-loader-path="classpath:" shared-engine="false">
<mvc:script location="org/springframework/web/servlet/view/script/nashorn/render.js"/>
</mvc:script-template-configurer>
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketHttpHeaders.java b/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketHttpHeaders.java
index 8efb5fab..4cda2747 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketHttpHeaders.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketHttpHeaders.java
@@ -172,7 +172,7 @@ public class WebSocketHttpHeaders extends HttpHeaders {
return Collections.emptyList();
}
else if (values.size() == 1) {
- return getFirstValueAsList(SEC_WEBSOCKET_PROTOCOL);
+ return getValuesAsList(SEC_WEBSOCKET_PROTOCOL);
}
else {
return values;
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/jetty/JettyWebSocketSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/jetty/JettyWebSocketSession.java
index cfb0aec6..2825a482 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/jetty/JettyWebSocketSession.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/jetty/JettyWebSocketSession.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,7 +24,9 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import org.eclipse.jetty.websocket.api.RemoteEndpoint;
import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
import org.springframework.http.HttpHeaders;
@@ -185,22 +187,31 @@ public class JettyWebSocketSession extends AbstractWebSocketSession<Session> {
@Override
protected void sendTextMessage(TextMessage message) throws IOException {
- getNativeSession().getRemote().sendString(message.getPayload());
+ getRemoteEndpoint().sendString(message.getPayload());
}
@Override
protected void sendBinaryMessage(BinaryMessage message) throws IOException {
- getNativeSession().getRemote().sendBytes(message.getPayload());
+ getRemoteEndpoint().sendBytes(message.getPayload());
}
@Override
protected void sendPingMessage(PingMessage message) throws IOException {
- getNativeSession().getRemote().sendPing(message.getPayload());
+ getRemoteEndpoint().sendPing(message.getPayload());
}
@Override
protected void sendPongMessage(PongMessage message) throws IOException {
- getNativeSession().getRemote().sendPong(message.getPayload());
+ getRemoteEndpoint().sendPong(message.getPayload());
+ }
+
+ private RemoteEndpoint getRemoteEndpoint() throws IOException {
+ try {
+ return getNativeSession().getRemote();
+ }
+ catch (WebSocketException ex) {
+ throw new IOException("Unable to obtain RemoteEndpoint in session=" + getId(), ex);
+ }
}
@Override
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/client/standard/StandardWebSocketClient.java b/spring-websocket/src/main/java/org/springframework/web/socket/client/standard/StandardWebSocketClient.java
index 2dbd8157..faa32b69 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/client/standard/StandardWebSocketClient.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/client/standard/StandardWebSocketClient.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,7 +26,6 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.Callable;
-
import javax.websocket.ClientEndpointConfig;
import javax.websocket.ClientEndpointConfig.Configurator;
import javax.websocket.ContainerProvider;
@@ -51,7 +50,6 @@ import org.springframework.web.socket.adapter.standard.StandardWebSocketSession;
import org.springframework.web.socket.adapter.standard.WebSocketToStandardExtensionAdapter;
import org.springframework.web.socket.client.AbstractWebSocketClient;
-
/**
* A WebSocketClient based on standard Java WebSocket API.
*
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParser.java b/spring-websocket/src/main/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParser.java
index 12778c9d..9caa5a8f 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParser.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -109,6 +109,9 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser {
private static final boolean jackson2Present = ClassUtils.isPresent(
"com.fasterxml.jackson.databind.ObjectMapper", MessageBrokerBeanDefinitionParser.class.getClassLoader());
+ private static final boolean javaxValidationPresent =
+ ClassUtils.isPresent("javax.validation.Validator", MessageBrokerBeanDefinitionParser.class.getClassLoader());
+
@Override
public BeanDefinition parse(Element element, ParserContext context) {
@@ -516,6 +519,11 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser {
beanDef.getPropertyValues().add("pathMatcher", new RuntimeBeanReference(pathMatcherRef));
}
+ RuntimeBeanReference validatorRef = getValidator(messageBrokerElement, source, context);
+ if (validatorRef != null) {
+ beanDef.getPropertyValues().add("validator", validatorRef);
+ }
+
Element resolversElement = DomUtils.getChildElementByTagName(messageBrokerElement, "argument-resolvers");
if (resolversElement != null) {
values.add("customArgumentResolvers", extractBeanSubElements(resolversElement, context));
@@ -529,6 +537,24 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser {
registerBeanDef(beanDef, context, source);
}
+ private RuntimeBeanReference getValidator(Element messageBrokerElement, Object source, ParserContext parserContext) {
+ if (messageBrokerElement.hasAttribute("validator")) {
+ return new RuntimeBeanReference(messageBrokerElement.getAttribute("validator"));
+ }
+ else if (javaxValidationPresent) {
+ RootBeanDefinition validatorDef = new RootBeanDefinition(
+ "org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean");
+ validatorDef.setSource(source);
+ validatorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+ String validatorName = parserContext.getReaderContext().registerWithGeneratedName(validatorDef);
+ parserContext.registerComponent(new BeanComponentDefinition(validatorDef, validatorName));
+ return new RuntimeBeanReference(validatorName);
+ }
+ else {
+ return null;
+ }
+ }
+
private ManagedList<Object> extractBeanSubElements(Element parentElement, ParserContext parserContext) {
ManagedList<Object> list = new ManagedList<Object>();
list.setSource(parserContext.extractSource(parentElement));
@@ -547,6 +573,10 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser {
if (brokerElem.hasAttribute("user-destination-prefix")) {
beanDef.getPropertyValues().add("userDestinationPrefix", brokerElem.getAttribute("user-destination-prefix"));
}
+ if (brokerElem.hasAttribute("path-matcher")) {
+ String pathMatcherRef = brokerElem.getAttribute("path-matcher");
+ beanDef.getPropertyValues().add("pathMatcher", new RuntimeBeanReference(pathMatcherRef));
+ }
return new RuntimeBeanReference(registerBeanDef(beanDef, context, source));
}
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/EnableWebSocketMessageBroker.java b/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/EnableWebSocketMessageBroker.java
index 716489ee..e9d4d85e 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/EnableWebSocketMessageBroker.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/EnableWebSocketMessageBroker.java
@@ -45,12 +45,12 @@ import org.springframework.context.annotation.Import;
* &#064;EnableWebSocketMessageBroker
* public class MyConfiguration extends AbstractWebSocketMessageBrokerConfigurer {
*
- * &#064;Override
+ * &#064;Override
* public void registerStompEndpoints(StompEndpointRegistry registry) {
* registry.addEndpoint("/portfolio").withSockJS();
* }
*
- * &#064;Bean
+ * &#064;Bean
* public void configureMessageBroker(MessageBrokerRegistry registry) {
* registry.enableStompBrokerRelay("/queue/", "/topic/");
* registry.setApplicationDestinationPrefixes("/app/");
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebMvcStompEndpointRegistry.java b/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebMvcStompEndpointRegistry.java
index bbe55538..048cd3a0 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebMvcStompEndpointRegistry.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebMvcStompEndpointRegistry.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -67,8 +67,8 @@ public class WebMvcStompEndpointRegistry implements StompEndpointRegistry {
org.springframework.messaging.simp.user.UserSessionRegistry userSessionRegistry,
TaskScheduler defaultSockJsTaskScheduler) {
- Assert.notNull(webSocketHandler, "'webSocketHandler' is required ");
- Assert.notNull(transportRegistration, "'transportRegistration' is required");
+ Assert.notNull(webSocketHandler, "WebSocketHandler is required ");
+ Assert.notNull(transportRegistration, "WebSocketTransportRegistration is required");
this.webSocketHandler = webSocketHandler;
this.subProtocolWebSocketHandler = unwrapSubProtocolWebSocketHandler(webSocketHandler);
@@ -87,19 +87,17 @@ public class WebMvcStompEndpointRegistry implements StompEndpointRegistry {
this.stompHandler.setMessageSizeLimit(transportRegistration.getMessageSizeLimit());
}
-
this.sockJsScheduler = defaultSockJsTaskScheduler;
}
private static SubProtocolWebSocketHandler unwrapSubProtocolWebSocketHandler(WebSocketHandler handler) {
WebSocketHandler actual = WebSocketHandlerDecorator.unwrap(handler);
- Assert.isInstanceOf(SubProtocolWebSocketHandler.class, actual, "No SubProtocolWebSocketHandler in " + handler);
+ if (!(actual instanceof SubProtocolWebSocketHandler)) {
+ throw new IllegalArgumentException("No SubProtocolWebSocketHandler in " + handler);
+ };
return (SubProtocolWebSocketHandler) actual;
}
- protected void setApplicationContext(ApplicationContext applicationContext) {
- this.stompHandler.setApplicationEventPublisher(applicationContext);
- }
@Override
public StompWebSocketEndpointRegistration addEndpoint(String... paths) {
@@ -144,6 +142,11 @@ public class WebMvcStompEndpointRegistry implements StompEndpointRegistry {
return this;
}
+ protected void setApplicationContext(ApplicationContext applicationContext) {
+ this.stompHandler.setApplicationEventPublisher(applicationContext);
+ }
+
+
/**
* Return a handler mapping with the mapped ViewControllers; or {@code null}
* in case of no registrations.
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebMvcStompWebSocketEndpointRegistration.java b/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebMvcStompWebSocketEndpointRegistration.java
index ecac1596..56582796 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebMvcStompWebSocketEndpointRegistration.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebMvcStompWebSocketEndpointRegistration.java
@@ -36,7 +36,7 @@ import org.springframework.web.socket.sockjs.transport.handler.WebSocketTranspor
import java.util.ArrayList;
import java.util.List;
/**
- * An abstract base class class for configuring STOMP over WebSocket/SockJS endpoints.
+ * An abstract base class for configuring STOMP over WebSocket/SockJS endpoints.
*
* @author Rossen Stoyanchev
* @since 4.0
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/handler/ConcurrentWebSocketSessionDecorator.java b/spring-websocket/src/main/java/org/springframework/web/socket/handler/ConcurrentWebSocketSessionDecorator.java
index 16de9aa4..15dd486a 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/handler/ConcurrentWebSocketSessionDecorator.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/handler/ConcurrentWebSocketSessionDecorator.java
@@ -31,13 +31,14 @@ import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;
/**
- * Wraps a {@link org.springframework.web.socket.WebSocketSession} and guarantees
- * only one thread can send messages at a time.
+ * Wrap a {@link org.springframework.web.socket.WebSocketSession WebSocketSession}
+ * to guarantee only one thread can send messages at a time.
*
- * <p>If a send is slow, subsequent attempts to send more messages from a different
- * thread will fail to acquire the flush lock and the messages will be buffered
- * instead: At that time, the specified buffer-size limit and send-time limit will
- * be checked and the session closed if the limits are exceeded.
+ * <p>If a send is slow, subsequent attempts to send more messages from other
+ * threads will not be able to acquire the flush lock and messages will be
+ * buffered instead -- at that time, the specified buffer-size limit and
+ * send-time limit will be checked and the session closed if the limits are
+ * exceeded.
*
* @author Rossen Stoyanchev
* @since 4.0.3
@@ -47,21 +48,20 @@ public class ConcurrentWebSocketSessionDecorator extends WebSocketSessionDecorat
private static final Log logger = LogFactory.getLog(ConcurrentWebSocketSessionDecorator.class);
- private final Queue<WebSocketMessage<?>> buffer = new LinkedBlockingQueue<WebSocketMessage<?>>();
-
- private final AtomicInteger bufferSize = new AtomicInteger();
+ private final int sendTimeLimit;
private final int bufferSizeLimit;
- private volatile long sendStartTime;
+ private final Queue<WebSocketMessage<?>> buffer = new LinkedBlockingQueue<WebSocketMessage<?>>();
- private final int sendTimeLimit;
+ private final AtomicInteger bufferSize = new AtomicInteger();
+ private volatile long sendStartTime;
private volatile boolean limitExceeded;
- private volatile boolean shutdownInProgress;
+ private volatile boolean closeInProgress;
private final Lock flushLock = new ReentrantLock();
@@ -93,7 +93,7 @@ public class ConcurrentWebSocketSessionDecorator extends WebSocketSessionDecorat
public void sendMessage(WebSocketMessage<?> message) throws IOException {
- if (isDisabled()) {
+ if (shouldNotSend()) {
return;
}
@@ -103,32 +103,33 @@ public class ConcurrentWebSocketSessionDecorator extends WebSocketSessionDecorat
do {
if (!tryFlushMessageBuffer()) {
if (logger.isTraceEnabled()) {
- logger.trace("Another send already in progress, session id '" +
- getId() + "'" + ", in-progress send time " + getTimeSinceSendStarted() +
- " (ms)" + ", buffer size " + this.bufferSize + " bytes");
+ String text = String.format("Another send already in progress: " +
+ "session id '%s':, \"in-progress\" send time %d (ms), buffer size %d bytes",
+ getId(), getTimeSinceSendStarted(), this.bufferSize.get());
+ logger.trace(text);
}
checkSessionLimits();
break;
}
}
- while (!this.buffer.isEmpty() && !isDisabled());
+ while (!this.buffer.isEmpty() && !shouldNotSend());
}
- private boolean isDisabled() {
- return (this.limitExceeded || this.shutdownInProgress);
+ private boolean shouldNotSend() {
+ return (this.limitExceeded || this.closeInProgress);
}
private boolean tryFlushMessageBuffer() throws IOException {
if (this.flushLock.tryLock()) {
try {
while (true) {
- WebSocketMessage<?> messageToSend = this.buffer.poll();
- if (messageToSend == null || isDisabled()) {
+ WebSocketMessage<?> message = this.buffer.poll();
+ if (message == null || shouldNotSend()) {
break;
}
- this.bufferSize.addAndGet(messageToSend.getPayloadLength() * -1);
+ this.bufferSize.addAndGet(message.getPayloadLength() * -1);
this.sendStartTime = System.currentTimeMillis();
- getDelegate().sendMessage(messageToSend);
+ getDelegate().sendMessage(message);
this.sendStartTime = 0;
}
}
@@ -142,18 +143,17 @@ public class ConcurrentWebSocketSessionDecorator extends WebSocketSessionDecorat
}
private void checkSessionLimits() throws IOException {
- if (!isDisabled() && this.closeLock.tryLock()) {
+ if (!shouldNotSend() && this.closeLock.tryLock()) {
try {
if (getTimeSinceSendStarted() > this.sendTimeLimit) {
- String errorMessage = "Message send time " + getTimeSinceSendStarted() +
- " (ms) exceeded the allowed limit " + this.sendTimeLimit;
- sessionLimitReached(errorMessage, CloseStatus.SESSION_NOT_RELIABLE);
+ String format = "Message send time %d (ms) for session '%s' exceeded the allowed limit %d";
+ String reason = String.format(format, getTimeSinceSendStarted(), getId(), this.sendTimeLimit);
+ setLimitExceeded(reason);
}
else if (this.bufferSize.get() > this.bufferSizeLimit) {
- String errorMessage = "The send buffer size " + this.bufferSize.get() + " bytes for " +
- "session '" + getId() + " exceeded the allowed limit " + this.bufferSizeLimit;
- sessionLimitReached(errorMessage,
- (getTimeSinceSendStarted() >= 10000 ? CloseStatus.SESSION_NOT_RELIABLE : null));
+ String format = "The send buffer size %d bytes for session '%s' exceeded the allowed limit %d";
+ String reason = String.format(format, this.bufferSize.get(), getId(), this.bufferSizeLimit);
+ setLimitExceeded(reason);
}
}
finally {
@@ -162,16 +162,16 @@ public class ConcurrentWebSocketSessionDecorator extends WebSocketSessionDecorat
}
}
- private void sessionLimitReached(String reason, CloseStatus status) {
+ private void setLimitExceeded(String reason) {
this.limitExceeded = true;
- throw new SessionLimitExceededException(reason, status);
+ throw new SessionLimitExceededException(reason, CloseStatus.SESSION_NOT_RELIABLE);
}
@Override
public void close(CloseStatus status) throws IOException {
this.closeLock.lock();
try {
- if (this.shutdownInProgress) {
+ if (this.closeInProgress) {
return;
}
if (!CloseStatus.SESSION_NOT_RELIABLE.equals(status)) {
@@ -188,7 +188,7 @@ public class ConcurrentWebSocketSessionDecorator extends WebSocketSessionDecorat
status = CloseStatus.SESSION_NOT_RELIABLE;
}
}
- this.shutdownInProgress = true;
+ this.closeInProgress = true;
super.close(status);
}
finally {
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/messaging/DefaultSimpUserRegistry.java b/spring-websocket/src/main/java/org/springframework/web/socket/messaging/DefaultSimpUserRegistry.java
index 4cd575bd..9ea63ea2 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/messaging/DefaultSimpUserRegistry.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/messaging/DefaultSimpUserRegistry.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,41 +37,30 @@ import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.util.Assert;
/**
- * Default, mutable, thread-safe implementation of {@link SimpUserRegistry} that
- * listens ApplicationContext events of type {@link AbstractSubProtocolEvent} to
- * keep track of user presence and subscription information.
+ * A default implementation of {@link SimpUserRegistry} that relies on
+ * {@link AbstractSubProtocolEvent} application context events to keep track of
+ * connected users and their subscriptions.
*
* @author Rossen Stoyanchev
* @since 4.2
*/
public class DefaultSimpUserRegistry implements SimpUserRegistry, SmartApplicationListener {
- private final Map<String, DefaultSimpUser> users = new ConcurrentHashMap<String, DefaultSimpUser>();
+ /* Primary lookup that holds all users and their sessions */
+ private final Map<String, LocalSimpUser> users = new ConcurrentHashMap<String, LocalSimpUser>();
- private final Map<String, DefaultSimpSession> sessions = new ConcurrentHashMap<String, DefaultSimpSession>();
+ /* Secondary lookup across all sessions by id */
+ private final Map<String, LocalSimpSession> sessions = new ConcurrentHashMap<String, LocalSimpSession>();
+ private final Object sessionLock = new Object();
- @Override
- public SimpUser getUser(String userName) {
- return this.users.get(userName);
- }
@Override
- public Set<SimpUser> getUsers() {
- return new HashSet<SimpUser>(this.users.values());
+ public int getOrder() {
+ return Ordered.LOWEST_PRECEDENCE;
}
- public Set<SimpSubscription> findSubscriptions(SimpSubscriptionMatcher matcher) {
- Set<SimpSubscription> result = new HashSet<SimpSubscription>();
- for (DefaultSimpSession session : this.sessions.values()) {
- for (SimpSubscription subscription : session.subscriptions.values()) {
- if (matcher.match(subscription)) {
- result.add(subscription);
- }
- }
- }
- return result;
- }
+ // SmartApplicationListener methods
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
@@ -79,11 +68,6 @@ public class DefaultSimpUserRegistry implements SimpUserRegistry, SmartApplicati
}
@Override
- public boolean supportsSourceType(Class<?> sourceType) {
- return true;
- }
-
- @Override
public void onApplicationEvent(ApplicationEvent event) {
AbstractSubProtocolEvent subProtocolEvent = (AbstractSubProtocolEvent) event;
@@ -92,7 +76,7 @@ public class DefaultSimpUserRegistry implements SimpUserRegistry, SmartApplicati
String sessionId = accessor.getSessionId();
if (event instanceof SessionSubscribeEvent) {
- DefaultSimpSession session = this.sessions.get(sessionId);
+ LocalSimpSession session = this.sessions.get(sessionId);
if (session != null) {
String id = accessor.getSubscriptionId();
String destination = accessor.getDestination();
@@ -108,23 +92,22 @@ public class DefaultSimpUserRegistry implements SimpUserRegistry, SmartApplicati
if (user instanceof DestinationUserNameProvider) {
name = ((DestinationUserNameProvider) user).getDestinationUserName();
}
- synchronized (this) {
- DefaultSimpUser simpUser = this.users.get(name);
+ synchronized (this.sessionLock) {
+ LocalSimpUser simpUser = this.users.get(name);
if (simpUser == null) {
- simpUser = new DefaultSimpUser(name, sessionId);
+ simpUser = new LocalSimpUser(name);
this.users.put(name, simpUser);
}
- else {
- simpUser.addSession(sessionId);
- }
- this.sessions.put(sessionId, (DefaultSimpSession) simpUser.getSession(sessionId));
+ LocalSimpSession session = new LocalSimpSession(sessionId, simpUser);
+ simpUser.addSession(session);
+ this.sessions.put(sessionId, session);
}
}
else if (event instanceof SessionDisconnectEvent) {
- synchronized (this) {
- DefaultSimpSession session = this.sessions.remove(sessionId);
+ synchronized (this.sessionLock) {
+ LocalSimpSession session = this.sessions.remove(sessionId);
if (session != null) {
- DefaultSimpUser user = session.getUser();
+ LocalSimpUser user = session.getUser();
user.removeSession(sessionId);
if (!user.hasSessions()) {
this.users.remove(user.getName());
@@ -133,7 +116,7 @@ public class DefaultSimpUserRegistry implements SimpUserRegistry, SmartApplicati
}
}
else if (event instanceof SessionUnsubscribeEvent) {
- DefaultSimpSession session = this.sessions.get(sessionId);
+ LocalSimpSession session = this.sessions.get(sessionId);
if (session != null) {
String subscriptionId = accessor.getSubscriptionId();
session.removeSubscription(subscriptionId);
@@ -142,28 +125,52 @@ public class DefaultSimpUserRegistry implements SimpUserRegistry, SmartApplicati
}
@Override
- public int getOrder() {
- return Ordered.LOWEST_PRECEDENCE;
+ public boolean supportsSourceType(Class<?> sourceType) {
+ return true;
+ }
+
+ // SimpUserRegistry methods
+
+ @Override
+ public SimpUser getUser(String userName) {
+ return this.users.get(userName);
+ }
+
+ @Override
+ public Set<SimpUser> getUsers() {
+ return new HashSet<SimpUser>(this.users.values());
}
+ public Set<SimpSubscription> findSubscriptions(SimpSubscriptionMatcher matcher) {
+ Set<SimpSubscription> result = new HashSet<SimpSubscription>();
+ for (LocalSimpSession session : this.sessions.values()) {
+ for (SimpSubscription subscription : session.subscriptions.values()) {
+ if (matcher.match(subscription)) {
+ result.add(subscription);
+ }
+ }
+ }
+ return result;
+ }
+
+
@Override
public String toString() {
return "users=" + this.users;
}
- private static class DefaultSimpUser implements SimpUser {
+
+ private static class LocalSimpUser implements SimpUser {
private final String name;
- private final Map<String, SimpSession> sessions =
+ private final Map<String, SimpSession> userSessions =
new ConcurrentHashMap<String, SimpSession>(1);
- public DefaultSimpUser(String userName, String sessionId) {
+ public LocalSimpUser(String userName) {
Assert.notNull(userName);
- Assert.notNull(sessionId);
this.name = userName;
- this.sessions.put(sessionId, new DefaultSimpSession(sessionId, this));
}
@Override
@@ -173,26 +180,25 @@ public class DefaultSimpUserRegistry implements SimpUserRegistry, SmartApplicati
@Override
public boolean hasSessions() {
- return !this.sessions.isEmpty();
+ return !this.userSessions.isEmpty();
}
@Override
public SimpSession getSession(String sessionId) {
- return (sessionId != null ? this.sessions.get(sessionId) : null);
+ return (sessionId != null ? this.userSessions.get(sessionId) : null);
}
@Override
public Set<SimpSession> getSessions() {
- return new HashSet<SimpSession>(this.sessions.values());
+ return new HashSet<SimpSession>(this.userSessions.values());
}
- void addSession(String sessionId) {
- DefaultSimpSession session = new DefaultSimpSession(sessionId, this);
- this.sessions.put(sessionId, session);
+ void addSession(SimpSession session) {
+ this.userSessions.put(session.getId(), session);
}
void removeSession(String sessionId) {
- this.sessions.remove(sessionId);
+ this.userSessions.remove(sessionId);
}
@Override
@@ -213,20 +219,20 @@ public class DefaultSimpUserRegistry implements SimpUserRegistry, SmartApplicati
@Override
public String toString() {
- return "name=" + this.name + ", sessions=" + this.sessions;
+ return "name=" + this.name + ", sessions=" + this.userSessions;
}
}
- private static class DefaultSimpSession implements SimpSession {
+ private static class LocalSimpSession implements SimpSession {
private final String id;
- private final DefaultSimpUser user;
+ private final LocalSimpUser user;
private final Map<String, SimpSubscription> subscriptions = new ConcurrentHashMap<String, SimpSubscription>(4);
- public DefaultSimpSession(String id, DefaultSimpUser user) {
+ public LocalSimpSession(String id, LocalSimpUser user) {
Assert.notNull(id);
Assert.notNull(user);
this.id = id;
@@ -239,7 +245,7 @@ public class DefaultSimpUserRegistry implements SimpUserRegistry, SmartApplicati
}
@Override
- public DefaultSimpUser getUser() {
+ public LocalSimpUser getUser() {
return this.user;
}
@@ -249,7 +255,7 @@ public class DefaultSimpUserRegistry implements SimpUserRegistry, SmartApplicati
}
void addSubscription(String id, String destination) {
- this.subscriptions.put(id, new DefaultSimpSubscription(id, destination, this));
+ this.subscriptions.put(id, new LocalSimpSubscription(id, destination, this));
}
void removeSubscription(String id) {
@@ -278,16 +284,16 @@ public class DefaultSimpUserRegistry implements SimpUserRegistry, SmartApplicati
}
}
- private static class DefaultSimpSubscription implements SimpSubscription {
+ private static class LocalSimpSubscription implements SimpSubscription {
private final String id;
- private final DefaultSimpSession session;
+ private final LocalSimpSession session;
private final String destination;
- public DefaultSimpSubscription(String id, String destination, DefaultSimpSession session) {
+ public LocalSimpSubscription(String id, String destination, LocalSimpSession session) {
Assert.notNull(id);
Assert.hasText(destination);
Assert.notNull(session);
@@ -302,7 +308,7 @@ public class DefaultSimpUserRegistry implements SimpUserRegistry, SmartApplicati
}
@Override
- public DefaultSimpSession getSession() {
+ public LocalSimpSession getSession() {
return this.session;
}
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/messaging/SessionConnectEvent.java b/spring-websocket/src/main/java/org/springframework/web/socket/messaging/SessionConnectEvent.java
index 41006fea..a372a0e2 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/messaging/SessionConnectEvent.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/messaging/SessionConnectEvent.java
@@ -25,7 +25,7 @@ import org.springframework.messaging.Message;
* (e.g. STOMP) as the WebSocket sub-protocol issues a connect request.
*
* <p>Note that this is not the same as the WebSocket session getting established
- * but rather the client's first attempt to connect within the the sub-protocol,
+ * but rather the client's first attempt to connect within the sub-protocol,
* for example sending the STOMP CONNECT frame.
*
* @author Rossen Stoyanchev
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/messaging/WebSocketAnnotationMethodMessageHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/messaging/WebSocketAnnotationMethodMessageHandler.java
index 42994516..9f1c4a69 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/messaging/WebSocketAnnotationMethodMessageHandler.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/messaging/WebSocketAnnotationMethodMessageHandler.java
@@ -28,6 +28,7 @@ import org.springframework.messaging.simp.SimpMessageSendingOperations;
import org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler;
import org.springframework.web.method.ControllerAdviceBean;
+
/**
* A sub-class of {@link SimpAnnotationMethodMessageHandler} to provide support
* for {@link org.springframework.web.bind.annotation.ControllerAdvice
@@ -38,8 +39,9 @@ import org.springframework.web.method.ControllerAdviceBean;
*/
public class WebSocketAnnotationMethodMessageHandler extends SimpAnnotationMethodMessageHandler {
- public WebSocketAnnotationMethodMessageHandler(SubscribableChannel clientInChannel, MessageChannel clientOutChannel,
- SimpMessageSendingOperations brokerTemplate) {
+
+ public WebSocketAnnotationMethodMessageHandler(SubscribableChannel clientInChannel,
+ MessageChannel clientOutChannel, SimpMessageSendingOperations brokerTemplate) {
super(clientInChannel, clientOutChannel, brokerTemplate);
}
@@ -58,9 +60,9 @@ public class WebSocketAnnotationMethodMessageHandler extends SimpAnnotationMetho
if (logger.isDebugEnabled()) {
logger.debug("Looking for @MessageExceptionHandler mappings: " + getApplicationContext());
}
- List<ControllerAdviceBean> controllerAdvice = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
- AnnotationAwareOrderComparator.sort(controllerAdvice);
- initMessagingAdviceCache(MessagingControllerAdviceBean.createFromList(controllerAdvice));
+ List<ControllerAdviceBean> beans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
+ AnnotationAwareOrderComparator.sort(beans);
+ initMessagingAdviceCache(MessagingControllerAdviceBean.createFromList(beans));
}
private void initMessagingAdviceCache(List<MessagingAdviceBean> beans) {
@@ -68,8 +70,8 @@ public class WebSocketAnnotationMethodMessageHandler extends SimpAnnotationMetho
return;
}
for (MessagingAdviceBean bean : beans) {
- Class<?> beanType = bean.getBeanType();
- AnnotationExceptionHandlerMethodResolver resolver = new AnnotationExceptionHandlerMethodResolver(beanType);
+ Class<?> type = bean.getBeanType();
+ AnnotationExceptionHandlerMethodResolver resolver = new AnnotationExceptionHandlerMethodResolver(type);
if (resolver.hasExceptionMappings()) {
registerExceptionHandlerAdvice(bean, resolver);
logger.info("Detected @MessageExceptionHandler methods in " + bean);
@@ -89,12 +91,12 @@ public class WebSocketAnnotationMethodMessageHandler extends SimpAnnotationMetho
this.adviceBean = adviceBean;
}
- public static List<MessagingAdviceBean> createFromList(List<ControllerAdviceBean> controllerAdvice) {
- List<MessagingAdviceBean> messagingAdvice = new ArrayList<MessagingAdviceBean>(controllerAdvice.size());
- for (ControllerAdviceBean bean : controllerAdvice) {
- messagingAdvice.add(new MessagingControllerAdviceBean(bean));
+ public static List<MessagingAdviceBean> createFromList(List<ControllerAdviceBean> beans) {
+ List<MessagingAdviceBean> result = new ArrayList<MessagingAdviceBean>(beans.size());
+ for (ControllerAdviceBean bean : beans) {
+ result.add(new MessagingControllerAdviceBean(bean));
}
- return messagingAdvice;
+ return result;
}
@Override
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/AbstractStandardUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/AbstractStandardUpgradeStrategy.java
index 7ca2f7ac..2dd7ff41 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/AbstractStandardUpgradeStrategy.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/AbstractStandardUpgradeStrategy.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -105,8 +105,20 @@ public abstract class AbstractStandardUpgradeStrategy implements RequestUpgradeS
WebSocketHandler wsHandler, Map<String, Object> attrs) throws HandshakeFailureException {
HttpHeaders headers = request.getHeaders();
- InetSocketAddress localAddr = request.getLocalAddress();
- InetSocketAddress remoteAddr = request.getRemoteAddress();
+ InetSocketAddress localAddr = null;
+ try {
+ localAddr = request.getLocalAddress();
+ }
+ catch (Exception ex) {
+ // Ignore
+ }
+ InetSocketAddress remoteAddr = null;
+ try {
+ remoteAddr = request.getRemoteAddress();
+ }
+ catch (Exception ex) {
+ // Ignore
+ }
StandardWebSocketSession session = new StandardWebSocketSession(headers, attrs, localAddr, remoteAddr, user);
StandardWebSocketHandlerAdapter endpoint = new StandardWebSocketHandlerAdapter(wsHandler, session);
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/AbstractTyrusRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/AbstractTyrusRequestUpgradeStrategy.java
index 0e6a31df..35886efc 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/AbstractTyrusRequestUpgradeStrategy.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/AbstractTyrusRequestUpgradeStrategy.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -193,7 +193,7 @@ public abstract class AbstractTyrusRequestUpgradeStrategy extends AbstractStanda
private static final Method registerMethod;
- private static final Method unRegisterMethod;
+ private static final Method unregisterMethod;
static {
try {
@@ -204,7 +204,7 @@ public abstract class AbstractTyrusRequestUpgradeStrategy extends AbstractStanda
throw new IllegalStateException("Expected TyrusEndpointWrapper constructor with 9 or 10 arguments");
}
registerMethod = TyrusWebSocketEngine.class.getDeclaredMethod("register", TyrusEndpointWrapper.class);
- unRegisterMethod = TyrusWebSocketEngine.class.getDeclaredMethod("unregister", TyrusEndpointWrapper.class);
+ unregisterMethod = TyrusWebSocketEngine.class.getDeclaredMethod("unregister", TyrusEndpointWrapper.class);
ReflectionUtils.makeAccessible(registerMethod);
}
catch (Exception ex) {
@@ -259,7 +259,7 @@ public abstract class AbstractTyrusRequestUpgradeStrategy extends AbstractStanda
@Override
public void unregister(TyrusWebSocketEngine engine, Object endpoint) {
try {
- unRegisterMethod.invoke(engine, endpoint);
+ unregisterMethod.invoke(engine, endpoint);
}
catch (Exception ex) {
throw new HandshakeFailureException("Failed to unregister " + endpoint, ex);
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/ServletServerContainerFactoryBean.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/ServletServerContainerFactoryBean.java
index 6460c96d..7fe86146 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/ServletServerContainerFactoryBean.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/ServletServerContainerFactoryBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -40,6 +40,7 @@ import org.springframework.web.context.ServletContextAware;
* to customize the properties of the (one and only) {@code ServerContainer} instance.
*
* @author Rossen Stoyanchev
+ * @author Sam Brannen
* @since 4.0
*/
public class ServletServerContainerFactoryBean
@@ -53,6 +54,8 @@ public class ServletServerContainerFactoryBean
private Integer maxBinaryMessageBufferSize;
+ private ServletContext servletContext;
+
private ServerContainer serverContainer;
@@ -90,14 +93,18 @@ public class ServletServerContainerFactoryBean
@Override
public void setServletContext(ServletContext servletContext) {
- this.serverContainer = (ServerContainer) servletContext.getAttribute("javax.websocket.server.ServerContainer");
+ this.servletContext = servletContext;
}
@Override
public void afterPropertiesSet() {
- Assert.state(this.serverContainer != null,
+ Assert.state(this.servletContext != null,
"A ServletContext is required to access the javax.websocket.server.ServerContainer instance");
+ this.serverContainer = (ServerContainer) this.servletContext.getAttribute(
+ "javax.websocket.server.ServerContainer");
+ Assert.state(this.serverContainer != null,
+ "Attribute 'javax.websocket.server.ServerContainer' not found in ServletContext");
if (this.asyncSendTimeout != null) {
this.serverContainer.setAsyncSendTimeout(this.asyncSendTimeout);
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/UndertowRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/UndertowRequestUpgradeStrategy.java
index 6d5edef2..327df2f2 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/UndertowRequestUpgradeStrategy.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/UndertowRequestUpgradeStrategy.java
@@ -16,13 +16,15 @@
package org.springframework.web.socket.server.standard;
+import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
-import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.websocket.Decoder;
@@ -59,209 +61,262 @@ import org.springframework.util.ReflectionUtils;
import org.springframework.web.socket.server.HandshakeFailureException;
/**
- * A WebSocket {@code RequestUpgradeStrategy} for use with WildFly and its
- * underlying Undertow web server. Also compatible with embedded Undertow usage.
+ * A WebSocket {@code RequestUpgradeStrategy} for WildFly and its underlying
+ * Undertow web server. Also compatible with embedded Undertow usage.
*
- * <p>Compatible with Undertow 1.0 to 1.3 - as included in WildFly 8.x, 9 and 10.
+ * <p>Designed for Undertow 1.3.5+ as of Spring Framework 4.3, with a fallback
+ * strategy for Undertow 1.0 to 1.3 - as included in WildFly 8.x, 9 and 10.
*
* @author Rossen Stoyanchev
- * @author Brian Clozel
- * @author Juergen Hoeller
* @since 4.0.1
*/
public class UndertowRequestUpgradeStrategy extends AbstractStandardUpgradeStrategy {
- private static final Constructor<ServletWebSocketHttpExchange> exchangeConstructor;
+ private static final boolean HAS_DO_UPGRADE = ClassUtils.hasMethod(ServerWebSocketContainer.class, "doUpgrade",
+ HttpServletRequest.class, HttpServletResponse.class, ServerEndpointConfig.class, Map.class);
- private static final boolean exchangeConstructorWithPeerConnections;
+ private static final FallbackStrategy FALLBACK_STRATEGY = (HAS_DO_UPGRADE ? null : new FallbackStrategy());
- private static final Constructor<ConfiguredServerEndpoint> endpointConstructor;
-
- private static final boolean endpointConstructorWithEndpointFactory;
-
- private static final Method getBufferPoolMethod;
-
- private static final Method createChannelMethod;
-
- static {
- try {
- Class<ServletWebSocketHttpExchange> exchangeType = ServletWebSocketHttpExchange.class;
- Class<?>[] exchangeParamTypes =
- new Class<?>[] {HttpServletRequest.class, HttpServletResponse.class, Set.class};
- Constructor<ServletWebSocketHttpExchange> exchangeCtor =
- ClassUtils.getConstructorIfAvailable(exchangeType, exchangeParamTypes);
- if (exchangeCtor != null) {
- // Undertow 1.1+
- exchangeConstructor = exchangeCtor;
- exchangeConstructorWithPeerConnections = true;
- }
- else {
- // Undertow 1.0
- exchangeParamTypes = new Class<?>[] {HttpServletRequest.class, HttpServletResponse.class};
- exchangeConstructor = exchangeType.getConstructor(exchangeParamTypes);
- exchangeConstructorWithPeerConnections = false;
- }
-
- Class<ConfiguredServerEndpoint> endpointType = ConfiguredServerEndpoint.class;
- Class<?>[] endpointParamTypes = new Class<?>[] {ServerEndpointConfig.class, InstanceFactory.class,
- PathTemplate.class, EncodingFactory.class, AnnotatedEndpointFactory.class};
- Constructor<ConfiguredServerEndpoint> endpointCtor =
- ClassUtils.getConstructorIfAvailable(endpointType, endpointParamTypes);
- if (endpointCtor != null) {
- // Undertow 1.1+
- endpointConstructor = endpointCtor;
- endpointConstructorWithEndpointFactory = true;
- }
- else {
- // Undertow 1.0
- endpointParamTypes = new Class<?>[] {ServerEndpointConfig.class, InstanceFactory.class,
- PathTemplate.class, EncodingFactory.class};
- endpointConstructor = endpointType.getConstructor(endpointParamTypes);
- endpointConstructorWithEndpointFactory = false;
- }
-
- // Adapting between different Pool API types in Undertow 1.0-1.2 vs 1.3
- getBufferPoolMethod = WebSocketHttpExchange.class.getMethod("getBufferPool");
- createChannelMethod = ReflectionUtils.findMethod(Handshake.class, "createChannel", (Class<?>[]) null);
- }
- catch (Throwable ex) {
- throw new IllegalStateException("Incompatible Undertow API version", ex);
- }
- }
-
- private static final String[] supportedVersions = new String[] {
+ private static final String[] VERSIONS = new String[] {
WebSocketVersion.V13.toHttpHeaderValue(),
WebSocketVersion.V08.toHttpHeaderValue(),
WebSocketVersion.V07.toHttpHeaderValue()
};
- private final Set<WebSocketChannel> peerConnections;
+ @Override
+ public String[] getSupportedVersions() {
+ return VERSIONS;
+ }
+ @Override
+ protected void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response,
+ String selectedProtocol, List<Extension> selectedExtensions, Endpoint endpoint)
+ throws HandshakeFailureException {
+
+ if (HAS_DO_UPGRADE) {
+ HttpServletRequest servletRequest = getHttpServletRequest(request);
+ HttpServletResponse servletResponse = getHttpServletResponse(response);
+
+ StringBuffer requestUrl = servletRequest.getRequestURL();
+ String path = servletRequest.getRequestURI(); // shouldn't matter
+ Map<String, String> pathParams = Collections.<String, String>emptyMap();
- public UndertowRequestUpgradeStrategy() {
- if (exchangeConstructorWithPeerConnections) {
- this.peerConnections = Collections.newSetFromMap(new ConcurrentHashMap<WebSocketChannel, Boolean>());
+ ServerEndpointRegistration endpointConfig = new ServerEndpointRegistration(path, endpoint);
+ endpointConfig.setSubprotocols(Collections.singletonList(selectedProtocol));
+ endpointConfig.setExtensions(selectedExtensions);
+
+ try {
+ getContainer(servletRequest).doUpgrade(servletRequest, servletResponse, endpointConfig, pathParams);
+ }
+ catch (ServletException ex) {
+ throw new HandshakeFailureException(
+ "Servlet request failed to upgrade to WebSocket: " + requestUrl, ex);
+ }
+ catch (IOException ex) {
+ throw new HandshakeFailureException(
+ "Response update failed during upgrade to WebSocket: " + requestUrl, ex);
+ }
}
else {
- this.peerConnections = null;
+ FALLBACK_STRATEGY.upgradeInternal(request, response, selectedProtocol, selectedExtensions, endpoint);
}
}
-
- @Override
- public String[] getSupportedVersions() {
- return supportedVersions;
+ public ServerWebSocketContainer getContainer(HttpServletRequest request) {
+ return (ServerWebSocketContainer) super.getContainer(request);
}
- @Override
- protected void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response,
- String selectedProtocol, List<Extension> selectedExtensions, final Endpoint endpoint)
- throws HandshakeFailureException {
- HttpServletRequest servletRequest = getHttpServletRequest(request);
- HttpServletResponse servletResponse = getHttpServletResponse(response);
+ /**
+ * Strategy for use with Undertow 1.0 to 1.3 before there was a public API
+ * to perform a WebSocket upgrade.
+ */
+ private static class FallbackStrategy extends AbstractStandardUpgradeStrategy {
- final ServletWebSocketHttpExchange exchange = createHttpExchange(servletRequest, servletResponse);
- exchange.putAttachment(HandshakeUtil.PATH_PARAMS, Collections.<String, String>emptyMap());
+ private static final Constructor<ServletWebSocketHttpExchange> exchangeConstructor;
- ServerWebSocketContainer wsContainer = (ServerWebSocketContainer) getContainer(servletRequest);
- final EndpointSessionHandler endpointSessionHandler = new EndpointSessionHandler(wsContainer);
+ private static final boolean exchangeConstructorWithPeerConnections;
- final ConfiguredServerEndpoint configuredServerEndpoint = createConfiguredServerEndpoint(
- selectedProtocol, selectedExtensions, endpoint, servletRequest);
+ private static final Constructor<ConfiguredServerEndpoint> endpointConstructor;
- final Handshake handshake = getHandshakeToUse(exchange, configuredServerEndpoint);
+ private static final boolean endpointConstructorWithEndpointFactory;
- exchange.upgradeChannel(new HttpUpgradeListener() {
- @Override
- public void handleUpgrade(StreamConnection connection, HttpServerExchange serverExchange) {
- Object bufferPool = ReflectionUtils.invokeMethod(getBufferPoolMethod, exchange);
- WebSocketChannel channel = (WebSocketChannel) ReflectionUtils.invokeMethod(
- createChannelMethod, handshake, exchange, connection, bufferPool);
- if (peerConnections != null) {
- peerConnections.add(channel);
+ private static final Method getBufferPoolMethod;
+
+ private static final Method createChannelMethod;
+
+ static {
+ try {
+ Class<ServletWebSocketHttpExchange> exchangeType = ServletWebSocketHttpExchange.class;
+ Class<?>[] exchangeParamTypes =
+ new Class<?>[] {HttpServletRequest.class, HttpServletResponse.class, Set.class};
+ Constructor<ServletWebSocketHttpExchange> exchangeCtor =
+ ClassUtils.getConstructorIfAvailable(exchangeType, exchangeParamTypes);
+ if (exchangeCtor != null) {
+ // Undertow 1.1+
+ exchangeConstructor = exchangeCtor;
+ exchangeConstructorWithPeerConnections = true;
+ }
+ else {
+ // Undertow 1.0
+ exchangeParamTypes = new Class<?>[] {HttpServletRequest.class, HttpServletResponse.class};
+ exchangeConstructor = exchangeType.getConstructor(exchangeParamTypes);
+ exchangeConstructorWithPeerConnections = false;
}
- endpointSessionHandler.onConnect(exchange, channel);
- }
- });
- handshake.handshake(exchange);
- }
+ Class<ConfiguredServerEndpoint> endpointType = ConfiguredServerEndpoint.class;
+ Class<?>[] endpointParamTypes = new Class<?>[] {ServerEndpointConfig.class, InstanceFactory.class,
+ PathTemplate.class, EncodingFactory.class, AnnotatedEndpointFactory.class};
+ Constructor<ConfiguredServerEndpoint> endpointCtor =
+ ClassUtils.getConstructorIfAvailable(endpointType, endpointParamTypes);
+ if (endpointCtor != null) {
+ // Undertow 1.1+
+ endpointConstructor = endpointCtor;
+ endpointConstructorWithEndpointFactory = true;
+ }
+ else {
+ // Undertow 1.0
+ endpointParamTypes = new Class<?>[] {ServerEndpointConfig.class, InstanceFactory.class,
+ PathTemplate.class, EncodingFactory.class};
+ endpointConstructor = endpointType.getConstructor(endpointParamTypes);
+ endpointConstructorWithEndpointFactory = false;
+ }
- private ServletWebSocketHttpExchange createHttpExchange(HttpServletRequest request, HttpServletResponse response) {
- try {
- return (this.peerConnections != null ?
- exchangeConstructor.newInstance(request, response, this.peerConnections) :
- exchangeConstructor.newInstance(request, response));
+ // Adapting between different Pool API types in Undertow 1.0-1.2 vs 1.3
+ getBufferPoolMethod = WebSocketHttpExchange.class.getMethod("getBufferPool");
+ createChannelMethod = ReflectionUtils.findMethod(Handshake.class, "createChannel", (Class<?>[]) null);
+ }
+ catch (Throwable ex) {
+ throw new IllegalStateException("Incompatible Undertow API version", ex);
+ }
}
- catch (Exception ex) {
- throw new HandshakeFailureException("Failed to instantiate ServletWebSocketHttpExchange", ex);
+
+ private final Set<WebSocketChannel> peerConnections;
+
+ public FallbackStrategy() {
+ if (exchangeConstructorWithPeerConnections) {
+ this.peerConnections = Collections.newSetFromMap(new ConcurrentHashMap<WebSocketChannel, Boolean>());
+ }
+ else {
+ this.peerConnections = null;
+ }
}
- }
- private Handshake getHandshakeToUse(ServletWebSocketHttpExchange exchange, ConfiguredServerEndpoint endpoint) {
- Handshake handshake = new JsrHybi13Handshake(endpoint);
- if (handshake.matches(exchange)) {
- return handshake;
+ @Override
+ public String[] getSupportedVersions() {
+ return VERSIONS;
}
- handshake = new JsrHybi08Handshake(endpoint);
- if (handshake.matches(exchange)) {
- return handshake;
+
+ @Override
+ protected void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response,
+ String selectedProtocol, List<Extension> selectedExtensions, final Endpoint endpoint)
+ throws HandshakeFailureException {
+
+ HttpServletRequest servletRequest = getHttpServletRequest(request);
+ HttpServletResponse servletResponse = getHttpServletResponse(response);
+
+ final ServletWebSocketHttpExchange exchange = createHttpExchange(servletRequest, servletResponse);
+ exchange.putAttachment(HandshakeUtil.PATH_PARAMS, Collections.<String, String>emptyMap());
+
+ ServerWebSocketContainer wsContainer = (ServerWebSocketContainer) getContainer(servletRequest);
+ final EndpointSessionHandler endpointSessionHandler = new EndpointSessionHandler(wsContainer);
+
+ final ConfiguredServerEndpoint configuredServerEndpoint = createConfiguredServerEndpoint(
+ selectedProtocol, selectedExtensions, endpoint, servletRequest);
+
+ final Handshake handshake = getHandshakeToUse(exchange, configuredServerEndpoint);
+
+ exchange.upgradeChannel(new HttpUpgradeListener() {
+ @Override
+ public void handleUpgrade(StreamConnection connection, HttpServerExchange serverExchange) {
+ Object bufferPool = ReflectionUtils.invokeMethod(getBufferPoolMethod, exchange);
+ WebSocketChannel channel = (WebSocketChannel) ReflectionUtils.invokeMethod(
+ createChannelMethod, handshake, exchange, connection, bufferPool);
+ if (peerConnections != null) {
+ peerConnections.add(channel);
+ }
+ endpointSessionHandler.onConnect(exchange, channel);
+ }
+ });
+
+ handshake.handshake(exchange);
}
- handshake = new JsrHybi07Handshake(endpoint);
- if (handshake.matches(exchange)) {
- return handshake;
+
+ private ServletWebSocketHttpExchange createHttpExchange(HttpServletRequest request, HttpServletResponse response) {
+ try {
+ return (this.peerConnections != null ?
+ exchangeConstructor.newInstance(request, response, this.peerConnections) :
+ exchangeConstructor.newInstance(request, response));
+ }
+ catch (Exception ex) {
+ throw new HandshakeFailureException("Failed to instantiate ServletWebSocketHttpExchange", ex);
+ }
}
- // Should never occur
- throw new HandshakeFailureException("No matching Undertow Handshake found: " + exchange.getRequestHeaders());
- }
- private ConfiguredServerEndpoint createConfiguredServerEndpoint(String selectedProtocol,
- List<Extension> selectedExtensions, Endpoint endpoint, HttpServletRequest servletRequest) {
-
- String path = servletRequest.getRequestURI(); // shouldn't matter
- ServerEndpointRegistration endpointRegistration = new ServerEndpointRegistration(path, endpoint);
- endpointRegistration.setSubprotocols(Arrays.asList(selectedProtocol));
- endpointRegistration.setExtensions(selectedExtensions);
-
- EncodingFactory encodingFactory = new EncodingFactory(
- Collections.<Class<?>, List<InstanceFactory<? extends Encoder>>>emptyMap(),
- Collections.<Class<?>, List<InstanceFactory<? extends Decoder>>>emptyMap(),
- Collections.<Class<?>, List<InstanceFactory<? extends Encoder>>>emptyMap(),
- Collections.<Class<?>, List<InstanceFactory<? extends Decoder>>>emptyMap());
- try {
- return (endpointConstructorWithEndpointFactory ?
- endpointConstructor.newInstance(endpointRegistration,
- new EndpointInstanceFactory(endpoint), null, encodingFactory, null) :
- endpointConstructor.newInstance(endpointRegistration,
- new EndpointInstanceFactory(endpoint), null, encodingFactory));
+ private Handshake getHandshakeToUse(ServletWebSocketHttpExchange exchange, ConfiguredServerEndpoint endpoint) {
+ Handshake handshake = new JsrHybi13Handshake(endpoint);
+ if (handshake.matches(exchange)) {
+ return handshake;
+ }
+ handshake = new JsrHybi08Handshake(endpoint);
+ if (handshake.matches(exchange)) {
+ return handshake;
+ }
+ handshake = new JsrHybi07Handshake(endpoint);
+ if (handshake.matches(exchange)) {
+ return handshake;
+ }
+ // Should never occur
+ throw new HandshakeFailureException("No matching Undertow Handshake found: " + exchange.getRequestHeaders());
}
- catch (Exception ex) {
- throw new HandshakeFailureException("Failed to instantiate ConfiguredServerEndpoint", ex);
+
+ private ConfiguredServerEndpoint createConfiguredServerEndpoint(String selectedProtocol,
+ List<Extension> selectedExtensions, Endpoint endpoint, HttpServletRequest servletRequest) {
+
+ String path = servletRequest.getRequestURI(); // shouldn't matter
+ ServerEndpointRegistration endpointRegistration = new ServerEndpointRegistration(path, endpoint);
+ endpointRegistration.setSubprotocols(Collections.singletonList(selectedProtocol));
+ endpointRegistration.setExtensions(selectedExtensions);
+
+ EncodingFactory encodingFactory = new EncodingFactory(
+ Collections.<Class<?>, List<InstanceFactory<? extends Encoder>>>emptyMap(),
+ Collections.<Class<?>, List<InstanceFactory<? extends Decoder>>>emptyMap(),
+ Collections.<Class<?>, List<InstanceFactory<? extends Encoder>>>emptyMap(),
+ Collections.<Class<?>, List<InstanceFactory<? extends Decoder>>>emptyMap());
+ try {
+ return (endpointConstructorWithEndpointFactory ?
+ endpointConstructor.newInstance(endpointRegistration,
+ new EndpointInstanceFactory(endpoint), null, encodingFactory, null) :
+ endpointConstructor.newInstance(endpointRegistration,
+ new EndpointInstanceFactory(endpoint), null, encodingFactory));
+ }
+ catch (Exception ex) {
+ throw new HandshakeFailureException("Failed to instantiate ConfiguredServerEndpoint", ex);
+ }
}
- }
- private static class EndpointInstanceFactory implements InstanceFactory<Endpoint> {
+ private static class EndpointInstanceFactory implements InstanceFactory<Endpoint> {
- private final Endpoint endpoint;
+ private final Endpoint endpoint;
- public EndpointInstanceFactory(Endpoint endpoint) {
- this.endpoint = endpoint;
- }
+ public EndpointInstanceFactory(Endpoint endpoint) {
+ this.endpoint = endpoint;
+ }
- @Override
- public InstanceHandle<Endpoint> createInstance() throws InstantiationException {
- return new InstanceHandle<Endpoint>() {
- @Override
- public Endpoint getInstance() {
- return endpoint;
- }
- @Override
- public void release() {
- }
- };
+ @Override
+ public InstanceHandle<Endpoint> createInstance() throws InstantiationException {
+ return new InstanceHandle<Endpoint>() {
+ @Override
+ public Endpoint getInstance() {
+ return endpoint;
+ }
+ @Override
+ public void release() {
+ }
+ };
+ }
}
}
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/WebLogicRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/WebLogicRequestUpgradeStrategy.java
index c16d747a..58b29407 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/WebLogicRequestUpgradeStrategy.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/WebLogicRequestUpgradeStrategy.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -60,7 +60,6 @@ public class WebLogicRequestUpgradeStrategy extends AbstractTyrusRequestUpgradeS
private static final WebLogicServletWriterHelper servletWriterHelper = new WebLogicServletWriterHelper();
private static final Connection.CloseListener noOpCloseListener = new Connection.CloseListener() {
-
@Override
public void close(CloseReason reason) {
}
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/support/AbstractHandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/AbstractHandshakeHandler.java
index d76ed66d..ab51f293 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/server/support/AbstractHandshakeHandler.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/AbstractHandshakeHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -72,25 +72,23 @@ public abstract class AbstractHandshakeHandler implements HandshakeHandler, Life
private static final Charset UTF8_CHARSET = Charset.forName("UTF-8");
- private static final ClassLoader classLoader = AbstractHandshakeHandler.class.getClassLoader();
-
private static final boolean jettyWsPresent = ClassUtils.isPresent(
- "org.eclipse.jetty.websocket.server.WebSocketServerFactory", classLoader);
+ "org.eclipse.jetty.websocket.server.WebSocketServerFactory", AbstractHandshakeHandler.class.getClassLoader());
private static final boolean tomcatWsPresent = ClassUtils.isPresent(
- "org.apache.tomcat.websocket.server.WsHttpUpgradeHandler", classLoader);
+ "org.apache.tomcat.websocket.server.WsHttpUpgradeHandler", AbstractHandshakeHandler.class.getClassLoader());
private static final boolean undertowWsPresent = ClassUtils.isPresent(
- "io.undertow.websockets.jsr.ServerWebSocketContainer", classLoader);
+ "io.undertow.websockets.jsr.ServerWebSocketContainer", AbstractHandshakeHandler.class.getClassLoader());
private static final boolean glassfishWsPresent = ClassUtils.isPresent(
- "org.glassfish.tyrus.servlet.TyrusHttpUpgradeHandler", classLoader);
+ "org.glassfish.tyrus.servlet.TyrusHttpUpgradeHandler", AbstractHandshakeHandler.class.getClassLoader());
private static final boolean weblogicWsPresent = ClassUtils.isPresent(
- "weblogic.websocket.tyrus.TyrusServletWriter", classLoader);
+ "weblogic.websocket.tyrus.TyrusServletWriter", AbstractHandshakeHandler.class.getClassLoader());
private static final boolean websphereWsPresent = ClassUtils.isPresent(
- "com.ibm.websphere.wsoc.WsWsocServerContainer", classLoader);
+ "com.ibm.websphere.wsoc.WsWsocServerContainer", AbstractHandshakeHandler.class.getClassLoader());
protected final Log logger = LogFactory.getLog(getClass());
@@ -146,7 +144,7 @@ public abstract class AbstractHandshakeHandler implements HandshakeHandler, Life
}
try {
- Class<?> clazz = ClassUtils.forName(className, classLoader);
+ Class<?> clazz = ClassUtils.forName(className, AbstractHandshakeHandler.class.getClassLoader());
return (RequestUpgradeStrategy) clazz.newInstance();
}
catch (Throwable ex) {
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/client/AbstractXhrTransport.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/client/AbstractXhrTransport.java
index 7b7f51d2..2d0fac29 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/client/AbstractXhrTransport.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/client/AbstractXhrTransport.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author 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,7 @@ public abstract class AbstractXhrTransport implements XhrTransport {
PRELUDE = new String(bytes, SockJsFrame.CHARSET);
}
+
protected Log logger = LogFactory.getLog(getClass());
private boolean xhrStreamingDisabled;
@@ -137,6 +138,7 @@ public abstract class AbstractXhrTransport implements XhrTransport {
URI receiveUrl, HttpHeaders handshakeHeaders, XhrClientSockJsSession session,
SettableListenableFuture<WebSocketSession> connectFuture);
+
// InfoReceiver methods
@Override
@@ -165,6 +167,7 @@ public abstract class AbstractXhrTransport implements XhrTransport {
protected abstract ResponseEntity<String> executeInfoRequestInternal(URI infoUrl, HttpHeaders headers);
+
// XhrTransport methods
@Override
@@ -184,8 +187,8 @@ public abstract class AbstractXhrTransport implements XhrTransport {
}
}
- protected abstract ResponseEntity<String> executeSendRequestInternal(URI url,
- HttpHeaders headers, TextMessage message);
+ protected abstract ResponseEntity<String> executeSendRequestInternal(
+ URI url, HttpHeaders headers, TextMessage message);
@Override
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/client/UndertowXhrTransport.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/client/UndertowXhrTransport.java
index 863059a4..4f70016a 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/client/UndertowXhrTransport.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/client/UndertowXhrTransport.java
@@ -1,11 +1,11 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -326,14 +326,12 @@ public class UndertowXhrTransport extends AbstractXhrTransport {
result.getResponse().putAttachment(RESPONSE_BODY, string);
latch.countDown();
}
-
@Override
protected void error(IOException ex) {
onFailure(latch, ex);
}
}.setup(result.getResponseChannel());
}
-
@Override
public void failed(IOException ex) {
onFailure(latch, ex);
@@ -473,7 +471,7 @@ public class UndertowXhrTransport extends AbstractXhrTransport {
public void onFailure(Throwable failure) {
IoUtils.safeClose(this.connection);
- if (connectFuture.setException(failure)) {
+ if (this.connectFuture.setException(failure)) {
return;
}
if (this.session.isDisconnected()) {
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/frame/Jackson2SockJsMessageCodec.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/frame/Jackson2SockJsMessageCodec.java
index e879b26f..8ed46882 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/frame/Jackson2SockJsMessageCodec.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/frame/Jackson2SockJsMessageCodec.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@ import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.util.Assert;
/**
- * A Jackson 2.x codec for encoding and decoding SockJS messages.
+ * A Jackson 2.6+ codec for encoding and decoding SockJS messages.
*
* <p>It customizes Jackson's default properties with the following ones:
* <ul>
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/AbstractHttpSockJsSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/AbstractHttpSockJsSession.java
index df0e2771..451dd6bd 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/AbstractHttpSockJsSession.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/AbstractHttpSockJsSession.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -196,8 +196,18 @@ public abstract class AbstractHttpSockJsSession extends AbstractSockJsSession {
this.uri = request.getURI();
this.handshakeHeaders = request.getHeaders();
this.principal = request.getPrincipal();
- this.localAddress = request.getLocalAddress();
- this.remoteAddress = request.getRemoteAddress();
+ try {
+ this.localAddress = request.getLocalAddress();
+ }
+ catch (Exception ex) {
+ // Ignore
+ }
+ try {
+ this.remoteAddress = request.getRemoteAddress();
+ }
+ catch (Exception ex) {
+ // Ignore
+ }
synchronized (this.responseLock) {
try {
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/PollingSockJsSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/PollingSockJsSession.java
index e3cb4488..d8066c2e 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/PollingSockJsSession.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/PollingSockJsSession.java
@@ -35,7 +35,6 @@ import org.springframework.web.socket.sockjs.transport.SockJsServiceConfig;
*/
public class PollingSockJsSession extends AbstractHttpSockJsSession {
-
public PollingSockJsSession(String sessionId, SockJsServiceConfig config,
WebSocketHandler wsHandler, Map<String, Object> attributes) {
diff --git a/spring-websocket/src/main/resources/META-INF/spring.schemas b/spring-websocket/src/main/resources/META-INF/spring.schemas
index 338e12b0..1b557438 100644
--- a/spring-websocket/src/main/resources/META-INF/spring.schemas
+++ b/spring-websocket/src/main/resources/META-INF/spring.schemas
@@ -1,4 +1,5 @@
http\://www.springframework.org/schema/websocket/spring-websocket-4.0.xsd=org/springframework/web/socket/config/spring-websocket-4.0.xsd
http\://www.springframework.org/schema/websocket/spring-websocket-4.1.xsd=org/springframework/web/socket/config/spring-websocket-4.1.xsd
http\://www.springframework.org/schema/websocket/spring-websocket-4.2.xsd=org/springframework/web/socket/config/spring-websocket-4.2.xsd
-http\://www.springframework.org/schema/websocket/spring-websocket.xsd=org/springframework/web/socket/config/spring-websocket-4.2.xsd
+http\://www.springframework.org/schema/websocket/spring-websocket-4.3.xsd=org/springframework/web/socket/config/spring-websocket-4.3.xsd
+http\://www.springframework.org/schema/websocket/spring-websocket.xsd=org/springframework/web/socket/config/spring-websocket-4.3.xsd
diff --git a/spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket-4.0.xsd b/spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket-4.0.xsd
index 36fa8af6..861fd865 100644
--- a/spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket-4.0.xsd
+++ b/spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket-4.0.xsd
@@ -349,7 +349,7 @@
This is essentially the "Unbounded queues" strategy as explained in java.util.concurrent.ThreadPoolExecutor.
When this strategy is used, the max pool size is effectively ignored.
By default this is set to twice the value of Runtime.availableProcessors().
- In an an application where tasks do not block frequently,
+ In an application where tasks do not block frequently,
the number should be closer to or equal to the number of available CPUs/cores.
]]></xsd:documentation>
</xsd:annotation>
diff --git a/spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket-4.1.xsd b/spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket-4.1.xsd
index a5cabbd6..943a7db4 100644
--- a/spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket-4.1.xsd
+++ b/spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket-4.1.xsd
@@ -367,7 +367,7 @@
This is essentially the "Unbounded queues" strategy as explained in java.util.concurrent.ThreadPoolExecutor.
When this strategy is used, the max pool size is effectively ignored.
By default this is set to twice the value of Runtime.availableProcessors().
- In an an application where tasks do not block frequently,
+ In an application where tasks do not block frequently,
the number should be closer to or equal to the number of available CPUs/cores.
]]></xsd:documentation>
</xsd:annotation>
diff --git a/spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket-4.2.xsd b/spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket-4.2.xsd
index f6a8718a..f60e11cb 100644
--- a/spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket-4.2.xsd
+++ b/spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket-4.2.xsd
@@ -407,7 +407,7 @@
This is essentially the "Unbounded queues" strategy as explained in java.util.concurrent.ThreadPoolExecutor.
When this strategy is used, the max pool size is effectively ignored.
By default this is set to twice the value of Runtime.availableProcessors().
- In an an application where tasks do not block frequently,
+ In an application where tasks do not block frequently,
the number should be closer to or equal to the number of available CPUs/cores.
]]></xsd:documentation>
</xsd:annotation>
diff --git a/spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket-4.3.xsd b/spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket-4.3.xsd
new file mode 100644
index 00000000..10348c4a
--- /dev/null
+++ b/spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket-4.3.xsd
@@ -0,0 +1,931 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<xsd:schema xmlns="http://www.springframework.org/schema/websocket"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xmlns:tool="http://www.springframework.org/schema/tool"
+ targetNamespace="http://www.springframework.org/schema/websocket"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified">
+
+ <xsd:import namespace="http://www.springframework.org/schema/beans" schemaLocation="http://www.springframework.org/schema/beans/spring-beans-4.3.xsd"/>
+ <xsd:import namespace="http://www.springframework.org/schema/tool" schemaLocation="http://www.springframework.org/schema/tool/spring-tool-4.3.xsd"/>
+
+ <xsd:complexType name="mapping">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ An entry in the registered HandlerMapping that matches a path with a handler.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="path" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A path that maps a particular request to a handler.
+ Exact path mapping URIs (such as "/myPath") are supported as well as Ant-type path patterns (such as /myPath/**).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="handler" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.web.socket.WebSocketHandler"><![CDATA[
+ The bean name of a WebSocketHandler to use for requests that match the path configuration.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+
+ <xsd:complexType name="handshake-handler">
+ <xsd:attribute name="ref" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.web.socket.server.HandshakeHandler"><![CDATA[
+ The bean name of a HandshakeHandler to use for processing WebSocket handshake requests.
+ If none specified, a DefaultHandshakeHandler will be configured by default.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+
+ <xsd:complexType name="handshake-interceptors">
+ <xsd:annotation>
+ <xsd:documentation source="org.springframework.web.socket.server.HandshakeInterceptor"><![CDATA[
+ A list of HandshakeInterceptor beans definition and references.
+ A HandshakeInterceptor can be used to inspect the handshake request and response as well as to pass attributes to the target WebSocketHandler.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element ref="beans:bean">
+ <xsd:annotation>
+ <xsd:documentation source="org.springframework.web.socket.server.HandshakeInterceptor"><![CDATA[
+ A HandshakeInterceptor bean definition.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element ref="beans:ref">
+ <xsd:annotation>
+ <xsd:documentation source="org.springframework.web.socket.server.HandshakeInterceptor"><![CDATA[
+ A reference to a HandshakeInterceptor bean.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="sockjs-service">
+ <xsd:annotation>
+ <xsd:documentation source="org.springframework.web.socket.sockjs.transport.handler.DefaultSockJsService"><![CDATA[
+ Configures a DefaultSockJsService for processing HTTP requests from SockJS clients.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="transport-handlers" minOccurs="0" maxOccurs="1">
+ <xsd:annotation>
+ <xsd:documentation source="org.springframework.web.socket.sockjs.transport.TransportHandler"><![CDATA[
+ List of TransportHandler beans to be configured for the current handlers element.
+ One can choose not to register the default TransportHandlers and/or override those using
+ custom TransportHandlers.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element ref="beans:bean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A TransportHandler bean definition.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element ref="beans:ref">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A reference to a TransportHandler bean.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:sequence>
+ <xsd:attribute name="register-defaults" type="xsd:boolean" default="true">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Whether or not default TransportHandlers registrations should be added in addition to the ones provided within this element.
+ Default registrations include XhrPollingTransportHandler, XhrReceivingTransportHandler,
+ JsonpPollingTransportHandler, JsonpReceivingTransportHandler, XhrStreamingTransportHandler,
+ EventSourceTransportHandler, HtmlFileTransportHandler, and WebSocketTransportHandler.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.web.socket.sockjs.support.AbstractSockJsService"><![CDATA[
+ A unique name for the service, mainly for logging purposes.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="client-library-url" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.web.socket.sockjs.support.AbstractSockJsService"><![CDATA[
+ Transports with no native cross-domain communication (e.g. "eventsource",
+ "htmlfile") must get a simple page from the "foreign" domain in an invisible
+ iframe so that code in the iframe can run from a domain local to the SockJS
+ server. Since the iframe needs to load the SockJS javascript client library,
+ this property allows specifying where to load it from.
+
+ By default this is set to point to
+ "https://d1fxtkz8shb9d2.cloudfront.net/sockjs-0.3.4.min.js". However it can
+ also be set to point to a URL served by the application.
+
+ Note that it's possible to specify a relative URL in which case the URL
+ must be relative to the iframe URL. For example assuming a SockJS endpoint
+ mapped to "/sockjs", and resulting iframe URL "/sockjs/iframe.html", then the
+ The relative URL must start with "../../" to traverse up to the location
+ above the SockJS mapping. In case of a prefix-based Servlet mapping one more
+ traversal may be needed.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="stream-bytes-limit" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.web.socket.sockjs.support.AbstractSockJsService"><![CDATA[
+ Minimum number of bytes that can be send over a single HTTP streaming request before it will be closed.
+ Defaults to 128K (i.e. 128 1024).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="session-cookie-needed" type="xsd:boolean">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.web.socket.sockjs.support.AbstractSockJsService"><![CDATA[
+ The "cookie_needed" value in the response from the SockJs "/info" endpoint.
+ This property indicates whether the use of a JSESSIONID cookie is required for the application to function correctly,
+ e.g. for load balancing or in Java Servlet containers for the use of an HTTP session.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="heartbeat-time" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.web.socket.sockjs.support.AbstractSockJsService"><![CDATA[
+ The amount of time in milliseconds when the server has not sent any messages and after which the server
+ should send a heartbeat frame to the client in order to keep the connection from breaking.
+ The default value is 25,000 (25 seconds).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="disconnect-delay" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.web.socket.sockjs.support.AbstractSockJsService"><![CDATA[
+ The amount of time in milliseconds before a client is considered disconnected after not having
+ a receiving connection, i.e. an active connection over which the server can send data to the client.
+ The default value is 5000.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="message-cache-size" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.web.socket.sockjs.support.AbstractSockJsService"><![CDATA[
+ The number of server-to-client messages that a session can cache while waiting for
+ the next HTTP polling request from the client.
+ The default size is 100.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="websocket-enabled" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.web.socket.sockjs.support.AbstractSockJsService"><![CDATA[
+ Some load balancers don't support websockets. Set this option to "false" to disable the WebSocket transport on the server side.
+ The default value is "true".
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="scheduler" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.web.socket.sockjs.support.AbstractSockJsService"><![CDATA[
+ The bean name of a TaskScheduler; a new ThreadPoolTaskScheduler instance will be created if no value is provided.
+ This scheduler instance will be used for scheduling heart-beat messages.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="message-codec" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.web.socket.sockjs.support.AbstractSockJsService"><![CDATA[
+ The bean name of a SockJsMessageCodec to use for encoding and decoding SockJS messages.
+ By default Jackson2SockJsMessageCodec is used requiring the Jackson library to be present on the classpath.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="suppress-cors" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.web.socket.sockjs.support.AbstractSockJsService"><![CDATA[
+ This option can be used to disable automatic addition of CORS headers for SockJS requests.
+ The default value is "false".
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+
+ <xsd:complexType name="stomp-broker-relay">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler"><![CDATA[
+ Configures a MessageHandler that handles messages by forwarding them to a STOMP broker.
+ This MessageHandler also opens a default "system" TCP connection to the message
+ broker that is used for sending messages that originate from the server application (as
+ opposed to from a client).
+ The "login", "password", "heartbeat-send-interval" and "heartbeat-receive-interval" attributes
+ are provided to configure this "system" connection.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="prefix" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler"><![CDATA[
+ Comma-separated list of destination prefixes supported by the broker being configured.
+ Destinations that do not match the given prefix(es) are ignored.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="relay-host" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler"><![CDATA[
+ The STOMP message broker host.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="relay-port" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler"><![CDATA[
+ The STOMP message broker port.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="client-login" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler"><![CDATA[
+ The login to use when creating connections to the STOMP broker on behalf of connected clients.
+ By default this is set to "guest".
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="client-passcode" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler"><![CDATA[
+ The passcode to use when creating connections to the STOMP broker on behalf of connected clients.
+ By default this is set to "guest".
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="system-login" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler"><![CDATA[
+ The login for the shared "system" connection used to send messages to
+ the STOMP broker from within the application, i.e. messages not associated
+ with a specific client session (e.g. REST/HTTP request handling method).
+ By default this is set to "guest".
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="system-passcode" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler"><![CDATA[
+ The passcode for the shared "system" connection used to send messages to
+ the STOMP broker from within the application, i.e. messages not associated
+ with a specific client session (e.g. REST/HTTP request handling method).
+ By default this is set to "guest".
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="heartbeat-send-interval" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler"><![CDATA[
+ The interval, in milliseconds, at which the "system" connection will send heartbeats to the STOMP broker.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="heartbeat-receive-interval" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler"><![CDATA[
+ The interval, in milliseconds, at which the "system" connection expects to receive heartbeats from the STOMP broker.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="auto-startup" type="xsd:boolean">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler"><![CDATA[
+ Whether or not the StompBrokerRelay should be automatically started as part of its SmartLifecycle,
+ i.e. at the time of an application context refresh.
+ Default value is "true".
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="virtual-host" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler"><![CDATA[
+ The value of the "host" header to use in STOMP CONNECT frames sent to the STOMP broker.
+ This may be useful for example in a cloud environment where the actual host to which
+ the TCP connection is established is different from the host providing the cloud-based STOMP service.
+ By default this property is not set.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="user-destination-broadcast" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Set a destination to broadcast messages to that remain unresolved because
+ the user is not connected. In a multi-application server scenario this
+ gives other application servers a chance to try.
+ By default this is not set.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="user-registry-broadcast" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Set a destination to broadcast the content of the local user registry to
+ and to listen for such broadcasts from other servers. In a multi-application
+ server scenarios this allows each server's user registry to be aware of
+ users connected to other servers.
+ By default this is not set.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+
+ <xsd:complexType name="simple-broker">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.messaging.simp.broker.SimpleBrokerMessageHandler"><![CDATA[
+ Configures a SimpleBrokerMessageHandler that handles messages as a simple message broker implementation.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="prefix" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.messaging.simp.stomp.SimpleBrokerMessageHandler"><![CDATA[
+ Comma-separated list of destination prefixes supported by the broker being configured.
+ Destinations that do not match the given prefix(es) are ignored.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="heartbeat" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.messaging.simp.stomp.SimpleBrokerMessageHandler"><![CDATA[
+ Configure the value for the heartbeat settings. The first number represents how often the server will
+ write or send a heartbeat. The second is how often the client should write. 0 means no heartbeats.
+ By default this is set to "0, 0" unless the scheduler attribute is also set in which case the
+ default becomes "10000,10000" (in milliseconds).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="scheduler" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.messaging.simp.stomp.SimpleBrokerMessageHandler"><![CDATA[
+ The name of a task TaskScheduler to use for heartbeat support. Setting this property also
+ automatically sets the heartbeat attribute to "10000, 10000".
+ By default this attribute is not set.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+
+ <xsd:complexType name="channel">
+ <xsd:sequence>
+ <xsd:element name="executor" type="channel-executor" minOccurs="0" maxOccurs="1"/>
+ <xsd:element name="interceptors" type="channel-interceptors" minOccurs="0" maxOccurs="1"/>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="channel-executor">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"><![CDATA[
+ Configuration for the ThreadPoolTaskExecutor that sends messages for the message channel.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="core-pool-size" type="xsd:string" use="optional">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"><![CDATA[
+ Set the core pool size of the ThreadPoolExecutor.
+ NOTE: the core pool size is effectively the max pool size when an unbounded queue-capacity is configured (the default).
+ This is essentially the "Unbounded queues" strategy as explained in java.util.concurrent.ThreadPoolExecutor.
+ When this strategy is used, the max pool size is effectively ignored.
+ By default this is set to twice the value of Runtime.availableProcessors().
+ In an application where tasks do not block frequently,
+ the number should be closer to or equal to the number of available CPUs/cores.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="max-pool-size" type="xsd:string" use="optional">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"><![CDATA[
+ Set the max pool size of the ThreadPoolExecutor.
+ NOTE: when an unbounded queue-capacity is configured (the default), the max pool size is effectively ignored.
+ See the "Unbounded queues" strategy in java.util.concurrent.ThreadPoolExecutor for more details.
+ By default this is set to Integer.MAX_VALUE.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="keep-alive-seconds" type="xsd:string" use="optional">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"><![CDATA[
+ Set the time limit for which threads may remain idle before being terminated.
+ If there are more than the core number of threads currently in the pool, after waiting this amount of time without
+ processing a task, excess threads will be terminated. This overrides any value set in the constructor.
+ By default this is set to 60.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="queue-capacity" type="xsd:string" use="optional">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"><![CDATA[
+ Set the queue capacity for the ThreadPoolExecutor.
+ NOTE: when an unbounded queue-capacity is configured (the default) the core pool size is effectively the max pool size.
+ This is essentially the "Unbounded queues" strategy as explained in java.util.concurrent.ThreadPoolExecutor.
+ When this strategy is used, the max pool size is effectively ignored.
+ By default this is set to Integer.MAX_VALUE.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+
+ <xsd:complexType name="channel-interceptors">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.messaging.support.ChannelInterceptor"><![CDATA[
+ List of ChannelInterceptor beans to be used with this channel.
+ Empty by default.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element ref="beans:bean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A ChannelInterceptor bean definition.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element ref="beans:ref">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A reference to a ChannelInterceptor bean.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <!-- Elements definitions -->
+
+ <xsd:element name="handlers">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Configures WebSocket support by registering a SimpleUrlHandlerMapping and mapping
+ paths to registered WebSocketHandlers.
+
+ If a sockjs service is configured within this element, then a
+ SockJsHttpRequestHandler will handle
+ requests mapped to the given path.
+
+ Otherwise a WebSocketHttpRequestHandler
+ will be registered for that purpose.
+
+ See EnableWebSocket Javadoc for
+ information on code-based alternatives to enabling WebSocket support.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="mapping" type="mapping" minOccurs="1" maxOccurs="unbounded"/>
+ <xsd:element name="handshake-handler" type="handshake-handler" minOccurs="0" maxOccurs="1"/>
+ <xsd:element name="handshake-interceptors" type="handshake-interceptors" minOccurs="0" maxOccurs="1"/>
+ <xsd:element name="sockjs" type="sockjs-service" minOccurs="0" maxOccurs="1"/>
+ </xsd:sequence>
+ <xsd:attribute name="order" type="xsd:token">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Order value for this SimpleUrlHandlerMapping.
+ Default value is 1.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="allowed-origins" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Configure allowed {@code Origin} header values. Multiple origins may be specified
+ as a comma-separated list.
+
+ This check is mostly designed for browser clients. There is noting preventing other
+ types of client to modify the Origin header value.
+
+ When SockJS is enabled and allowed origins are restricted, transport types that do not
+ use {@code Origin} headers for cross origin requests (jsonp-polling, iframe-xhr-polling,
+ iframe-eventsource and iframe-htmlfile) are disabled. As a consequence, IE6/IE7 won't be
+ supported anymore and IE8/IE9 will only be supported without cookies.
+
+ By default, all origins are allowed.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="message-broker">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Configures broker-backed messaging over WebSocket using a higher-level messaging sub-protocol.
+ Registers a SimpleUrlHandlerMapping and maps paths to registered Controllers.
+
+ A StompSubProtocolHandler is registered to handle various versions of the STOMP protocol.
+
+ See EnableWebSocketMessageBroker javadoc for information on code-based alternatives to enabling broker-backed messaging.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="transport" minOccurs="0" maxOccurs="1">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Configure options related to the processing of messages received from and sent to WebSocket clients.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="decorator-factories" maxOccurs="1" minOccurs="0">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation source="org.springframework.web.socket.handler.WebSocketHandlerDecoratorFactory"><![CDATA[
+ Configure one or more factories to decorate the handler used to process WebSocket
+ messages. This may be useful for some advanced use cases, for example to allow
+ Spring Security to forcibly close the WebSocket session when the corresponding
+ HTTP session expires.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:choice minOccurs="1" maxOccurs="unbounded">
+ <xsd:element ref="beans:bean">
+ <xsd:annotation>
+ <xsd:documentation source="org.springframework.web.socket.handler.WebSocketHandlerDecoratorFactory"><![CDATA[
+ A WebSocketHandlerDecoratorFactory bean definition.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element ref="beans:ref">
+ <xsd:annotation>
+ <xsd:documentation source="org.springframework.web.socket.handler.WebSocketHandlerDecoratorFactory"><![CDATA[
+ A reference to a WebSocketHandlerDecoratorFactory bean.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="message-size" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Configure the maximum size for an incoming sub-protocol message.
+ For example a STOMP message may be received as multiple WebSocket messages
+ or multiple HTTP POST requests when SockJS fallback options are in use.
+
+ In theory a WebSocket message can be almost unlimited in size.
+ In practice WebSocket servers impose limits on incoming message size.
+ STOMP clients for example tend to split large messages around 16K
+ boundaries. Therefore a server must be able to buffer partial content
+ and decode when enough data is received. Use this property to configure
+ the max size of the buffer to use.
+
+ The default value is 64K (i.e. 64 * 1024).
+
+ NOTE that the current version 1.2 of the STOMP spec
+ does not specifically discuss how to send STOMP messages over WebSocket.
+ Version 2 of the spec will but in the mean time existing client libraries
+ have already established a practice that servers must handle.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="send-timeout" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Configure a time limit (in milliseconds) for the maximum amount of a time
+ allowed when sending messages to a WebSocket session or writing to an
+ HTTP response when SockJS fallback option are in use.
+
+ In general WebSocket servers expect that messages to a single WebSocket
+ session are sent from a single thread at a time. This is automatically
+ guaranteed when using {@code @EnableWebSocketMessageBroker} configuration.
+ If message sending is slow, or at least slower than rate of messages sending,
+ subsequent messages are buffered until either the {@code sendTimeLimit}
+ or the {@code sendBufferSizeLimit} are reached at which point the session
+ state is cleared and an attempt is made to close the session.
+
+ NOTE that the session time limit is checked only
+ on attempts to send additional messages. So if only a single message is
+ sent and it hangs, the session will not time out until another message is
+ sent or the underlying physical socket times out. So this is not a
+ replacement for WebSocket server or HTTP connection timeout but is rather
+ intended to control the extent of buffering of unsent messages.
+
+ NOTE that closing the session may not succeed in
+ actually closing the physical socket and may also hang. This is true
+ especially when using blocking IO such as the BIO connector in Tomcat
+ that is used by default on Tomcat 7. Therefore it is recommended to ensure
+ the server is using non-blocking IO such as Tomcat's NIO connector that
+ is used by default on Tomcat 8. If you must use blocking IO consider
+ customizing OS-level TCP settings, for example
+ {@code /proc/sys/net/ipv4/tcp_retries2} on Linux.
+
+ The default value is 10 seconds (i.e. 10 * 10000).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="send-buffer-size" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Configure the maximum amount of data to buffer when sending messages
+ to a WebSocket session, or an HTTP response when SockJS fallback
+ option are in use.
+
+ In general WebSocket servers expect that messages to a single WebSocket
+ session are sent from a single thread at a time. This is automatically
+ guaranteed when using {@code @EnableWebSocketMessageBroker} configuration.
+ If message sending is slow, or at least slower than rate of messages sending,
+ subsequent messages are buffered until either the {@code sendTimeLimit}
+ or the {@code sendBufferSizeLimit} are reached at which point the session
+ state is cleared and an attempt is made to close the session.
+
+ NOTE that closing the session may not succeed in
+ actually closing the physical socket and may also hang. This is true
+ especially when using blocking IO such as the BIO connector in Tomcat
+ configured by default on Tomcat 7. Therefore it is recommended to ensure
+ the server is using non-blocking IO such as Tomcat's NIO connector used
+ by default on Tomcat 8. If you must use blocking IO consider customizing
+ OS-level TCP settings, for example {@code /proc/sys/net/ipv4/tcp_retries2}
+ on Linux.
+
+ The default value is 512K (i.e. 512 * 1024).
+
+ @param sendBufferSizeLimit the maximum number of bytes to buffer when
+ sending messages; if the value is less than or equal to 0 then buffering
+ is effectively disabled.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="stomp-endpoint" minOccurs="1" maxOccurs="unbounded">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Registers STOMP over WebSocket endpoints.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="handshake-handler" type="handshake-handler" minOccurs="0" maxOccurs="1"/>
+ <xsd:element name="handshake-interceptors" type="handshake-interceptors" minOccurs="0" maxOccurs="1"/>
+ <xsd:element name="sockjs" type="sockjs-service" minOccurs="0" maxOccurs="1"/>
+ </xsd:sequence>
+ <xsd:attribute name="path" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A path that maps a particular message destination to a handler method.
+ Exact path mapping URIs (such as "/myPath") are supported as well as Ant-stype path patterns (such as /myPath/**).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="allowed-origins" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Configure allowed {@code Origin} header values. Multiple origins may be specified
+ as a comma-separated list.
+
+ This check is mostly designed for browser clients. There is noting preventing other
+ types of client to modify the Origin header value.
+
+ When SockJS is enabled and allowed origins are restricted, transport types that do not
+ use {@code Origin} headers for cross origin requests (jsonp-polling, iframe-xhr-polling,
+ iframe-eventsource and iframe-htmlfile) are disabled. As a consequence, IE6/IE7 won't be
+ supported anymore and IE8/IE9 will only be supported without cookies.
+
+ By default, all origins are allowed.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="stomp-error-handler" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Configures a StompSubProtocolErrorHandler to customize or handle STOMP ERROR to clients.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:attribute name="ref" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation source="java:org.springframework.web.socket.messaging.StompSubProtocolErrorHandler"><![CDATA[
+ The bean name of a StompSubProtocolErrorHandler.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:choice>
+ <xsd:element name="simple-broker" type="simple-broker"/>
+ <xsd:element name="stomp-broker-relay" type="stomp-broker-relay"/>
+ </xsd:choice>
+ <xsd:element name="argument-resolvers" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Configures HandlerMethodArgumentResolver types to support custom controller method argument types.
+ Using this option does not override the built-in support for resolving handler method arguments.
+ To customize the built-in support for argument resolution configure WebSocketAnnotationMethodMessageHandler directly.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:choice minOccurs="1" maxOccurs="unbounded">
+ <xsd:element ref="beans:bean" minOccurs="0" maxOccurs="unbounded">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The HandlerMethodArgumentResolver (or WebArgumentResolver for backwards compatibility) bean definition.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element ref="beans:ref" minOccurs="0" maxOccurs="unbounded">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A reference to a HandlerMethodArgumentResolver bean definition.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="java:org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="return-value-handlers" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Configures HandlerMethodReturnValueHandler types to support custom controller method return value handling.
+ Using this option does not override the built-in support for handling return values.
+ To customize the built-in support for handling return values configure WebSocketAnnotationMethodMessageHandler directly.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:choice minOccurs="1" maxOccurs="unbounded">
+ <xsd:element ref="beans:bean" minOccurs="0" maxOccurs="unbounded">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The HandlerMethodReturnValueHandler bean definition.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element ref="beans:ref" minOccurs="0" maxOccurs="unbounded">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A reference to a HandlerMethodReturnValueHandler bean definition.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="java:org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="message-converters" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Configure the message converters to use when extracting the payload of messages in annotated methods
+ and when sending messages (e.g. through the "broker" SimpMessagingTemplate.
+ MessageConverter registrations provided here will take precedence over MessageConverter types registered by default.
+ Also see the register-defaults attribute if you want to turn off default registrations entirely.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element ref="beans:bean">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A MessageConverter bean definition.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element ref="beans:ref">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A reference to an HttpMessageConverter bean.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:sequence>
+ <xsd:attribute name="register-defaults" type="xsd:boolean" default="true">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Whether or not default MessageConverter registrations should be added in addition to the ones provided within this element.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="client-inbound-channel" type="channel" minOccurs="0" maxOccurs="1">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The channel for receiving messages from clients (e.g. WebSocket clients).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="client-outbound-channel" type="channel" minOccurs="0" maxOccurs="1">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The channel for sending messages to clients (e.g. WebSocket clients).
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="broker-channel" type="channel" minOccurs="0" maxOccurs="1">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The channel for sending messages with translated user destinations.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="application-destination-prefix" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Comma-separated list of prefixes to match to the destinations of handled messages.
+ Messages whose destination does not start with one of the configured prefixes are ignored.
+
+ Prefix is removed from the destination part and then messages are delegated to
+ @SubscribeMapping and @MessageMapping}annotated methods.
+
+ Prefixes without a trailing slash will have one appended automatically.
+ By default the list of prefixes is empty in which case all destinations match.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="user-destination-prefix" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The prefix used to identify user destinations.
+ Any destinations that do not start with the given prefix are not be resolved.
+ The default value is "/user/".
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="path-matcher" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ A reference to the PathMatcher to use to match the destinations of incoming
+ messages to @MessageMapping and @SubscribeMapping methods.
+
+ By default AntPathMatcher is configured.
+ However applications may provide an AntPathMatcher instance
+ customized to use "." (commonly used in messaging) instead of "/" as path
+ separator or provide a completely different PathMatcher implementation.
+
+ Note that the configured PathMatcher is only used for matching the
+ portion of the destination after the configured prefix. For example given
+ application destination prefix "/app" and destination "/app/price.stock.**",
+ the message might be mapped to a controller with "price" and "stock.**"
+ as its type and method-level mappings respectively.
+
+ When the simple broker is enabled, the PathMatcher configured here is
+ also used to match message destinations when brokering messages.
+ ]]></xsd:documentation>
+ <xsd:appinfo>
+ <tool:annotation kind="ref">
+ <tool:expected-type type="java:org.springframework.util.PathMatcher"/>
+ </tool:annotation>
+ </xsd:appinfo>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="order" type="xsd:token">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ Order value for this SimpleUrlHandlerMapping.
+ Default value is 1.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="path-helper" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The bean name of the UrlPathHelper to use for the HandlerMapping used to map handshake requests.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="validator" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation><![CDATA[
+ The bean name of the Validator instance used for validating @Payload arguments.
+ ]]></xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+</xsd:schema>
diff --git a/spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket.gif b/spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket.gif
new file mode 100644
index 00000000..9e4c800e
--- /dev/null
+++ b/spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket.gif
Binary files differ
diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/AbstractWebSocketIntegrationTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/AbstractWebSocketIntegrationTests.java
index 7067ef25..2309f74f 100644
--- a/spring-websocket/src/test/java/org/springframework/web/socket/AbstractWebSocketIntegrationTests.java
+++ b/spring-websocket/src/test/java/org/springframework/web/socket/AbstractWebSocketIntegrationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,6 @@ import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -48,9 +47,7 @@ import org.springframework.web.socket.server.support.DefaultHandshakeHandler;
*/
public abstract class AbstractWebSocketIntegrationTests {
- protected Log logger = LogFactory.getLog(getClass());
-
- private static Map<Class<?>, Class<?>> upgradeStrategyConfigTypes = new HashMap<Class<?>, Class<?>>();
+ private static Map<Class<?>, Class<?>> upgradeStrategyConfigTypes = new HashMap<>();
static {
upgradeStrategyConfigTypes.put(JettyWebSocketTestServer.class, JettyUpgradeStrategyConfig.class);
@@ -58,6 +55,7 @@ public abstract class AbstractWebSocketIntegrationTests {
upgradeStrategyConfigTypes.put(UndertowTestServer.class, UndertowUpgradeStrategyConfig.class);
}
+
@Rule
public final TestName testName = new TestName();
@@ -67,12 +65,13 @@ public abstract class AbstractWebSocketIntegrationTests {
@Parameter(1)
public WebSocketClient webSocketClient;
+ protected final Log logger = LogFactory.getLog(getClass());
+
protected AnnotationConfigWebApplicationContext wac;
@Before
public void setup() throws Exception {
-
logger.debug("Setting up '" + this.testName.getMethodName() + "', client=" +
this.webSocketClient.getClass().getSimpleName() + ", server=" +
this.server.getClass().getSimpleName());
@@ -155,6 +154,7 @@ public abstract class AbstractWebSocketIntegrationTests {
}
}
+
@Configuration
static class TomcatUpgradeStrategyConfig extends AbstractRequestUpgradeStrategyConfig {
@@ -164,6 +164,7 @@ public abstract class AbstractWebSocketIntegrationTests {
}
}
+
@Configuration
static class UndertowUpgradeStrategyConfig extends AbstractRequestUpgradeStrategyConfig {
diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/ContextLoaderTestUtils.java b/spring-websocket/src/test/java/org/springframework/web/socket/ContextLoaderTestUtils.java
index ba56ae97..8853d824 100644
--- a/spring-websocket/src/test/java/org/springframework/web/socket/ContextLoaderTestUtils.java
+++ b/spring-websocket/src/test/java/org/springframework/web/socket/ContextLoaderTestUtils.java
@@ -38,7 +38,8 @@ public class ContextLoaderTestUtils {
public static void setCurrentWebApplicationContext(ClassLoader classLoader, WebApplicationContext applicationContext) {
if (applicationContext != null) {
currentContextPerThread.put(classLoader, applicationContext);
- } else {
+ }
+ else {
currentContextPerThread.remove(classLoader);
}
}
diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/WebSocketIntegrationTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/WebSocketHandshakeTests.java
index 1d86998d..813b7b5f 100644
--- a/spring-websocket/src/test/java/org/springframework/web/socket/WebSocketIntegrationTests.java
+++ b/spring-websocket/src/test/java/org/springframework/web/socket/WebSocketHandshakeTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -46,9 +46,10 @@ import static org.junit.Assert.*;
* Client and server-side WebSocket integration tests.
*
* @author Rossen Stoyanchev
+ * @author Juergen Hoeller
*/
@RunWith(Parameterized.class)
-public class WebSocketIntegrationTests extends AbstractWebSocketIntegrationTests {
+public class WebSocketHandshakeTests extends AbstractWebSocketIntegrationTests {
@Parameters(name = "server [{0}], client [{1}]")
public static Iterable<Object[]> arguments() {
@@ -62,7 +63,7 @@ public class WebSocketIntegrationTests extends AbstractWebSocketIntegrationTest
@Override
protected Class<?>[] getAnnotatedConfigClasses() {
- return new Class<?>[] { TestConfig.class };
+ return new Class<?>[] {TestConfig.class};
}
@Test
@@ -75,11 +76,8 @@ public class WebSocketIntegrationTests extends AbstractWebSocketIntegrationTest
session.close();
}
- // SPR-12727
-
- @Test
+ @Test // SPR-12727
public void unsolicitedPongWithEmptyPayload() throws Exception {
-
String url = getWsBaseUrl() + "/ws";
WebSocketSession session = this.webSocketClient.doHandshake(new AbstractWebSocketHandler() {}, url).get();
@@ -126,7 +124,6 @@ public class WebSocketIntegrationTests extends AbstractWebSocketIntegrationTest
private Throwable transportError;
-
public void setWaitMessageCount(int waitMessageCount) {
this.waitMessageCount = waitMessageCount;
}
diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/adapter/jetty/JettyWebSocketSessionTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/adapter/jetty/JettyWebSocketSessionTests.java
index 594f0828..a8377521 100644
--- a/spring-websocket/src/test/java/org/springframework/web/socket/adapter/jetty/JettyWebSocketSessionTests.java
+++ b/spring-websocket/src/test/java/org/springframework/web/socket/adapter/jetty/JettyWebSocketSessionTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2015 the original author 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,8 +22,9 @@ import java.util.Map;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.UpgradeRequest;
import org.eclipse.jetty.websocket.api.UpgradeResponse;
-import org.junit.Before;
+
import org.junit.Test;
+
import org.mockito.Mockito;
import org.springframework.web.socket.handler.TestPrincipal;
@@ -38,16 +39,11 @@ import static org.mockito.BDDMockito.*;
*/
public class JettyWebSocketSessionTests {
- private Map<String,Object> attributes;
-
-
- @Before
- public void setup() {
- this.attributes = new HashMap<>();
- }
+ private final Map<String, Object> attributes = new HashMap<>();
@Test
+ @SuppressWarnings("resource")
public void getPrincipalWithConstructorArg() {
TestPrincipal user = new TestPrincipal("joe");
JettyWebSocketSession session = new JettyWebSocketSession(attributes, user);
@@ -56,8 +52,8 @@ public class JettyWebSocketSessionTests {
}
@Test
+ @SuppressWarnings("resource")
public void getPrincipalFromNativeSession() {
-
TestPrincipal user = new TestPrincipal("joe");
UpgradeRequest request = Mockito.mock(UpgradeRequest.class);
@@ -80,8 +76,8 @@ public class JettyWebSocketSessionTests {
}
@Test
+ @SuppressWarnings("resource")
public void getPrincipalNotAvailable() {
-
UpgradeRequest request = Mockito.mock(UpgradeRequest.class);
given(request.getUserPrincipal()).willReturn(null);
@@ -102,8 +98,8 @@ public class JettyWebSocketSessionTests {
}
@Test
+ @SuppressWarnings("resource")
public void getAcceptedProtocol() {
-
String protocol = "foo";
UpgradeRequest request = Mockito.mock(UpgradeRequest.class);
diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/adapter/standard/StandardWebSocketSessionTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/adapter/standard/StandardWebSocketSessionTests.java
index edd17640..8c832aad 100644
--- a/spring-websocket/src/test/java/org/springframework/web/socket/adapter/standard/StandardWebSocketSessionTests.java
+++ b/spring-websocket/src/test/java/org/springframework/web/socket/adapter/standard/StandardWebSocketSessionTests.java
@@ -1,4 +1,4 @@
-/* Copyright 2002-2014 the original author or authors.
+/* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,8 +19,8 @@ import java.util.HashMap;
import java.util.Map;
import javax.websocket.Session;
-import org.junit.Before;
import org.junit.Test;
+
import org.mockito.Mockito;
import org.springframework.http.HttpHeaders;
@@ -36,19 +36,13 @@ import static org.mockito.BDDMockito.*;
*/
public class StandardWebSocketSessionTests {
- private HttpHeaders headers;
-
- private Map<String,Object> attributes;
+ private final HttpHeaders headers = new HttpHeaders();
-
- @Before
- public void setup() {
- this.headers = new HttpHeaders();
- this.attributes = new HashMap<>();
- }
+ private final Map<String, Object> attributes = new HashMap<>();
@Test
+ @SuppressWarnings("resource")
public void getPrincipalWithConstructorArg() {
TestPrincipal user = new TestPrincipal("joe");
StandardWebSocketSession session = new StandardWebSocketSession(this.headers, this.attributes, null, null, user);
@@ -57,8 +51,8 @@ public class StandardWebSocketSessionTests {
}
@Test
+ @SuppressWarnings("resource")
public void getPrincipalWithNativeSession() {
-
TestPrincipal user = new TestPrincipal("joe");
Session nativeSession = Mockito.mock(Session.class);
@@ -71,8 +65,8 @@ public class StandardWebSocketSessionTests {
}
@Test
+ @SuppressWarnings("resource")
public void getPrincipalNone() {
-
Session nativeSession = Mockito.mock(Session.class);
given(nativeSession.getUserPrincipal()).willReturn(null);
@@ -86,8 +80,8 @@ public class StandardWebSocketSessionTests {
}
@Test
+ @SuppressWarnings("resource")
public void getAcceptedProtocol() {
-
String protocol = "foo";
Session nativeSession = Mockito.mock(Session.class);
diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParserTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParserTests.java
index aa52922b..824cba62 100644
--- a/spring-websocket/src/test/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParserTests.java
+++ b/spring-websocket/src/test/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParserTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -60,6 +60,8 @@ import org.springframework.mock.web.test.MockServletContext;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.util.MimeTypeUtils;
+import org.springframework.validation.Errors;
+import org.springframework.validation.Validator;
import org.springframework.web.HttpRequestHandler;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.servlet.HandlerMapping;
@@ -356,6 +358,14 @@ public class MessageBrokerBeanDefinitionParserTests {
public void customChannels() {
loadBeanDefinitions("websocket-config-broker-customchannels.xml");
+ SimpAnnotationMethodMessageHandler annotationMethodMessageHandler =
+ this.appContext.getBean(SimpAnnotationMethodMessageHandler.class);
+
+ Validator validator = annotationMethodMessageHandler.getValidator();
+ assertNotNull(validator);
+ assertSame(this.appContext.getBean("myValidator"), validator);
+ assertThat(validator, Matchers.instanceOf(TestValidator.class));
+
List<Class<? extends MessageHandler>> subscriberTypes =
Arrays.<Class<? extends MessageHandler>>asList(SimpAnnotationMethodMessageHandler.class,
UserDestinationMessageHandler.class, SimpleBrokerMessageHandler.class);
@@ -520,3 +530,13 @@ class TestWebSocketHandlerDecorator extends WebSocketHandlerDecorator {
class TestStompErrorHandler extends StompSubProtocolErrorHandler {
}
+
+class TestValidator implements Validator {
+ @Override
+ public boolean supports(Class<?> clazz) {
+ return false;
+ }
+
+ @Override
+ public void validate(Object target, Errors errors) { }
+}
diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/config/annotation/WebSocketConfigurationTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/config/annotation/WebSocketConfigurationTests.java
index 1a66397b..f4aa0460 100644
--- a/spring-websocket/src/test/java/org/springframework/web/socket/config/annotation/WebSocketConfigurationTests.java
+++ b/spring-websocket/src/test/java/org/springframework/web/socket/config/annotation/WebSocketConfigurationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -61,7 +61,7 @@ public class WebSocketConfigurationTests extends AbstractWebSocketIntegrationTes
@Override
protected Class<?>[] getAnnotatedConfigClasses() {
- return new Class<?>[] { TestConfig.class };
+ return new Class<?>[] {TestConfig.class};
}
@Test
diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/handler/ConcurrentWebSocketSessionDecoratorTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/handler/ConcurrentWebSocketSessionDecoratorTests.java
index c4cdb02a..5af30742 100644
--- a/spring-websocket/src/test/java/org/springframework/web/socket/handler/ConcurrentWebSocketSessionDecoratorTests.java
+++ b/spring-websocket/src/test/java/org/springframework/web/socket/handler/ConcurrentWebSocketSessionDecoratorTests.java
@@ -40,6 +40,7 @@ import static org.junit.Assert.*;
@SuppressWarnings("resource")
public class ConcurrentWebSocketSessionDecoratorTests {
+
@Test
public void send() throws IOException {
@@ -70,16 +71,13 @@ public class ConcurrentWebSocketSessionDecoratorTests {
final ConcurrentWebSocketSessionDecorator concurrentSession =
new ConcurrentWebSocketSessionDecorator(blockingSession, 10 * 1000, 1024);
- Executors.newSingleThreadExecutor().submit(new Runnable() {
- @Override
- public void run() {
- TextMessage textMessage = new TextMessage("slow message");
- try {
- concurrentSession.sendMessage(textMessage);
- }
- catch (IOException e) {
- e.printStackTrace();
- }
+ Executors.newSingleThreadExecutor().submit((Runnable) () -> {
+ TextMessage message = new TextMessage("slow message");
+ try {
+ concurrentSession.sendMessage(message);
+ }
+ catch (IOException e) {
+ e.printStackTrace();
}
});
@@ -90,7 +88,7 @@ public class ConcurrentWebSocketSessionDecoratorTests {
assertTrue(concurrentSession.getTimeSinceSendStarted() > 0);
TextMessage payload = new TextMessage("payload");
- for (int i=0; i < 5; i++) {
+ for (int i = 0; i < 5; i++) {
concurrentSession.sendMessage(payload);
}
@@ -103,6 +101,7 @@ public class ConcurrentWebSocketSessionDecoratorTests {
public void sendTimeLimitExceeded() throws IOException, InterruptedException {
BlockingSession blockingSession = new BlockingSession();
+ blockingSession.setId("123");
blockingSession.setOpen(true);
CountDownLatch sentMessageLatch = blockingSession.getSentMessageLatch();
@@ -112,16 +111,13 @@ public class ConcurrentWebSocketSessionDecoratorTests {
final ConcurrentWebSocketSessionDecorator concurrentSession =
new ConcurrentWebSocketSessionDecorator(blockingSession, sendTimeLimit, bufferSizeLimit);
- Executors.newSingleThreadExecutor().submit(new Runnable() {
- @Override
- public void run() {
- TextMessage textMessage = new TextMessage("slow message");
- try {
- concurrentSession.sendMessage(textMessage);
- }
- catch (IOException e) {
- e.printStackTrace();
- }
+ Executors.newSingleThreadExecutor().submit((Runnable) () -> {
+ TextMessage message = new TextMessage("slow message");
+ try {
+ concurrentSession.sendMessage(message);
+ }
+ catch (IOException e) {
+ e.printStackTrace();
}
});
@@ -136,6 +132,9 @@ public class ConcurrentWebSocketSessionDecoratorTests {
fail("Expected exception");
}
catch (SessionLimitExceededException ex) {
+ String actual = ex.getMessage();
+ String regex = "Message send time [\\d]+ \\(ms\\) for session '123' exceeded the allowed limit 100";
+ assertTrue("Unexpected message: " + actual, actual.matches(regex));
assertEquals(CloseStatus.SESSION_NOT_RELIABLE, ex.getStatus());
}
}
@@ -144,6 +143,7 @@ public class ConcurrentWebSocketSessionDecoratorTests {
public void sendBufferSizeExceeded() throws IOException, InterruptedException {
BlockingSession blockingSession = new BlockingSession();
+ blockingSession.setId("123");
blockingSession.setOpen(true);
CountDownLatch sentMessageLatch = blockingSession.getSentMessageLatch();
@@ -153,23 +153,20 @@ public class ConcurrentWebSocketSessionDecoratorTests {
final ConcurrentWebSocketSessionDecorator concurrentSession =
new ConcurrentWebSocketSessionDecorator(blockingSession, sendTimeLimit, bufferSizeLimit);
- Executors.newSingleThreadExecutor().submit(new Runnable() {
- @Override
- public void run() {
- TextMessage textMessage = new TextMessage("slow message");
- try {
- concurrentSession.sendMessage(textMessage);
- }
- catch (IOException e) {
- e.printStackTrace();
- }
+ Executors.newSingleThreadExecutor().submit((Runnable) () -> {
+ TextMessage message = new TextMessage("slow message");
+ try {
+ concurrentSession.sendMessage(message);
+ }
+ catch (IOException e) {
+ e.printStackTrace();
}
});
assertTrue(sentMessageLatch.await(5, TimeUnit.SECONDS));
StringBuilder sb = new StringBuilder();
- for (int i=0 ; i < 1023; i++) {
+ for (int i = 0 ; i < 1023; i++) {
sb.append("a");
}
@@ -184,6 +181,10 @@ public class ConcurrentWebSocketSessionDecoratorTests {
fail("Expected exception");
}
catch (SessionLimitExceededException ex) {
+ String actual = ex.getMessage();
+ String regex = "The send buffer size [\\d]+ bytes for session '123' exceeded the allowed limit 1024";
+ assertTrue("Unexpected message: " + actual, actual.matches(regex));
+ assertEquals(CloseStatus.SESSION_NOT_RELIABLE, ex.getStatus());
}
}
@@ -215,16 +216,13 @@ public class ConcurrentWebSocketSessionDecoratorTests {
final ConcurrentWebSocketSessionDecorator concurrentSession =
new ConcurrentWebSocketSessionDecorator(blockingSession, sendTimeLimit, bufferSizeLimit);
- Executors.newSingleThreadExecutor().submit(new Runnable() {
- @Override
- public void run() {
- TextMessage message = new TextMessage("slow message");
- try {
- concurrentSession.sendMessage(message);
- }
- catch (IOException e) {
- e.printStackTrace();
- }
+ Executors.newSingleThreadExecutor().submit((Runnable) () -> {
+ TextMessage message = new TextMessage("slow message");
+ try {
+ concurrentSession.sendMessage(message);
+ }
+ catch (IOException e) {
+ e.printStackTrace();
}
});
diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/client/RestTemplateXhrTransportTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/client/RestTemplateXhrTransportTests.java
index ca8f2c83..1337fcb2 100644
--- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/client/RestTemplateXhrTransportTests.java
+++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/client/RestTemplateXhrTransportTests.java
@@ -91,7 +91,7 @@ public class RestTemplateXhrTransportTests {
@Test
public void connectReceiveAndCloseWithPrelude() throws Exception {
StringBuilder sb = new StringBuilder(2048);
- for (int i=0; i < 2048; i++) {
+ for (int i = 0; i < 2048; i++) {
sb.append('h');
}
String body = sb.toString() + "\n" + "o\n" + "a[\"foo\"]\n" + "c[3000,\"Go away!\"]";
diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/handler/HttpSendingTransportHandlerTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/handler/HttpSendingTransportHandlerTests.java
index 8b941778..41911429 100644
--- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/handler/HttpSendingTransportHandlerTests.java
+++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/handler/HttpSendingTransportHandlerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,7 +20,6 @@ import java.sql.Date;
import org.junit.Before;
import org.junit.Test;
-
import org.springframework.scheduling.TaskScheduler;
import org.springframework.web.socket.AbstractHttpRequestTests;
import org.springframework.web.socket.WebSocketHandler;
@@ -115,6 +114,7 @@ public class HttpSendingTransportHandlerTests extends AbstractHttpRequestTests
setRequest("POST", "/");
if (callbackValue != null) {
+ // need to encode the query parameter
this.servletRequest.setQueryString("c=" + UriUtils.encodeQueryParam(callbackValue, "UTF-8"));
this.servletRequest.addParameter("c", callbackValue);
}
diff --git a/spring-websocket/src/test/resources/org/springframework/web/socket/config/websocket-config-broker-customchannels.xml b/spring-websocket/src/test/resources/org/springframework/web/socket/config/websocket-config-broker-customchannels.xml
index e8908da1..fdf47049 100644
--- a/spring-websocket/src/test/resources/org/springframework/web/socket/config/websocket-config-broker-customchannels.xml
+++ b/spring-websocket/src/test/resources/org/springframework/web/socket/config/websocket-config-broker-customchannels.xml
@@ -4,7 +4,7 @@
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd">
- <websocket:message-broker application-destination-prefix="/app" user-destination-prefix="/personal">
+ <websocket:message-broker application-destination-prefix="/app" user-destination-prefix="/personal" validator="myValidator">
<websocket:stomp-endpoint path="/foo,/bar">
<websocket:handshake-handler ref="myHandler"/>
</websocket:stomp-endpoint>
@@ -31,4 +31,6 @@
<bean id="myInterceptor" class="org.springframework.web.socket.config.TestChannelInterceptor"/>
+ <bean id="myValidator" class="org.springframework.web.socket.config.TestValidator"/>
+
</beans>
diff --git a/src/asciidoc/appendix.adoc b/src/asciidoc/appendix.adoc
index 98091bec..f5bc66b0 100644
--- a/src/asciidoc/appendix.adoc
+++ b/src/asciidoc/appendix.adoc
@@ -3163,9 +3163,9 @@ the correct schema so that the tags in the `cache` namespace are available to yo
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- __xmlns:jdbc="http://www.springframework.org/schema/cache"__ xsi:schemaLocation="
+ __xmlns:cache="http://www.springframework.org/schema/cache"__ xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
- __http://www.springframework.org/schema/cache http://www.springframework.org/schema/jdbc/spring-cache.xsd"__> <!-- bean definitions here -->
+ __http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd"__> <!-- bean definitions here -->
</beans>
----
diff --git a/src/asciidoc/appx-spring-form-tld.adoc b/src/asciidoc/appx-spring-form-tld.adoc
index fd5ac0bd..9c996924 100644
--- a/src/asciidoc/appx-spring-form-tld.adoc
+++ b/src/asciidoc/appx-spring-form-tld.adoc
@@ -528,11 +528,6 @@ Renders an HTML 'form' tag and exposes a binding path to inner tags for binding.
| true
| HTML Required Attribute
-| commandName
-| false
-| true
-| Name of the model attribute under which the form object is exposed. Defaults to 'command'.
-
| cssClass
| false
| true
diff --git a/src/asciidoc/core-aop.adoc b/src/asciidoc/core-aop.adoc
index 57dffd37..8c8022c1 100644
--- a/src/asciidoc/core-aop.adoc
+++ b/src/asciidoc/core-aop.adoc
@@ -398,10 +398,10 @@ releases to support more of the AspectJ pointcut designators.
Because Spring AOP limits matching to only method execution join points, the discussion
of the pointcut designators above gives a narrower definition than you will find in the
AspectJ programming guide. In addition, AspectJ itself has type-based semantics and at
-an execution join point both '++this++' and '++target++' refer to the same object - the
+an execution join point both `this` and `target` refer to the same object - the
object executing the method. Spring AOP is a proxy-based system and differentiates
-between the proxy object itself (bound to '++this++') and the target object behind the
-proxy (bound to '++target++').
+between the proxy object itself (bound to `this`) and the target object behind the
+proxy (bound to `target`).
[NOTE]
====
@@ -418,9 +418,9 @@ different characteristics, so be sure to make yourself familiar with weaving fir
before making a decision.
====
-Spring AOP also supports an additional PCD named '++bean++'. This PCD allows you to limit
+Spring AOP also supports an additional PCD named `bean`. This PCD allows you to limit
the matching of join points to a particular named Spring bean, or to a set of named
-Spring beans (when using wildcards). The '++bean++' PCD has the following form:
+Spring beans (when using wildcards). The `bean` PCD has the following form:
[source,java,indent=0]
[subs="verbatim,quotes"]
@@ -428,19 +428,19 @@ Spring beans (when using wildcards). The '++bean++' PCD has the following form:
bean(idOrNameOfBean)
----
-The '++idOrNameOfBean++' token can be the name of any Spring bean: limited wildcard
-support using the '++*++' character is provided, so if you establish some naming
-conventions for your Spring beans you can quite easily write a '++bean++' PCD expression
-to pick them out. As is the case with other pointcut designators, the '++bean++' PCD can
+The `idOrNameOfBean` token can be the name of any Spring bean: limited wildcard
+support using the `*` character is provided, so if you establish some naming
+conventions for your Spring beans you can quite easily write a `bean` PCD expression
+to pick them out. As is the case with other pointcut designators, the `bean` PCD can
be &&'ed, ||'ed, and ! (negated) too.
[NOTE]
====
-Please note that the '++bean++' PCD is __only__ supported in Spring AOP - and __not__ in
+Please note that the `bean` PCD is __only__ supported in Spring AOP - and __not__ in
native AspectJ weaving. It is a Spring-specific extension to the standard PCDs that
AspectJ defines and therefore not available for aspects declared in the `@Aspect` model.
-The '++bean++' PCD operates at the __instance__ level (building on the Spring bean name
+The `bean` PCD operates at the __instance__ level (building on the Spring bean name
concept) rather than at the type level only (which is what weaving-based AOP is limited
to). Instance-based pointcut designators are a special capability of Spring's
proxy-based AOP framework and its close integration with the Spring bean factory, where
@@ -769,7 +769,7 @@ how to make the annotation object(s) available in the advice body.
====
* any join point (method execution only in Spring AOP) on a Spring bean named
- '++tradeService++':
+ `tradeService`:
[source,java,indent=0]
[subs="verbatim,quotes"]
@@ -778,7 +778,7 @@ how to make the annotation object(s) available in the advice body.
----
* any join point (method execution only in Spring AOP) on Spring beans having names that
- match the wildcard expression '++*Service++':
+ match the wildcard expression `*Service`:
[source,java,indent=0]
[subs="verbatim,quotes"]
@@ -2391,7 +2391,7 @@ In the XML style I can declare the first two pointcuts:
----
The downside of the XML approach is that you cannot define the
-'++accountPropertyAccess++' pointcut by combining these definitions.
+`accountPropertyAccess` pointcut by combining these definitions.
The @AspectJ style supports additional instantiation models, and richer pointcut
composition. It has the advantage of keeping the aspect as a modular unit. It also has
@@ -2467,7 +2467,7 @@ at runtime, which applies the __strongest__ proxy settings that any of the
This also applies to the `<tx:annotation-driven/>` and `<aop:aspectj-autoproxy/>`
elements.
-To be clear: using '++proxy-target-class="true"++' on `<tx:annotation-driven/>`,
+To be clear: using `proxy-target-class="true"` on `<tx:annotation-driven/>`,
`<aop:aspectj-autoproxy/>` or `<aop:config/>` elements will force the use of CGLIB
proxies __for all three of them__.
====
@@ -2720,7 +2720,7 @@ Spring will now look for a bean definition named "account" and use that as the
definition to configure new `Account` instances.
You can also use autowiring to avoid having to specify a dedicated bean definition at
-all. To have Spring apply autowiring use the '++autowire++' property of the
+all. To have Spring apply autowiring use the `autowire` property of the
`@Configurable` annotation: specify either `@Configurable(autowire=Autowire.BY_TYPE)` or
`@Configurable(autowire=Autowire.BY_NAME` for autowiring by type or by name
respectively. As an alternative, as of Spring 2.5 it is preferable to specify explicit,
@@ -2740,7 +2740,7 @@ the annotation. In essence the aspect says "after returning from the initializat
new object of a type annotated with `@Configurable`, configure the newly created object
using Spring in accordance with the properties of the annotation". In this context,
__initialization__ refers to newly instantiated objects (e.g., objects instantiated with
-the '++new++' operator) as well as to `Serializable` objects that are undergoing
+the `new` operator) as well as to `Serializable` objects that are undergoing
deserialization (e.g., via
http://docs.oracle.com/javase/6/docs/api/java/io/Serializable.html[readResolve()]).
@@ -2929,7 +2929,7 @@ fully-qualified class names:
When using AspectJ aspects with Spring applications, it is natural to both want and
expect to be able to configure such aspects using Spring. The AspectJ runtime itself is
responsible for aspect creation, and the means of configuring the AspectJ created
-aspects via Spring depends on the AspectJ instantiation model (the '++per-xxx++' clause)
+aspects via Spring depends on the AspectJ instantiation model (the `per-xxx` clause)
used by the aspect.
The majority of AspectJ aspects are __singleton__ aspects. Configuration of these
@@ -3067,10 +3067,10 @@ profiler, using the @AspectJ-style of aspect declaration.
}
----
-We will also need to create an '++META-INF/aop.xml++' file, to inform the AspectJ weaver
+We will also need to create an `META-INF/aop.xml` file, to inform the AspectJ weaver
that we want to weave our `ProfilingAspect` into our classes. This file convention,
namely the presence of a file (or files) on the Java classpath called
-'++META-INF/aop.xml++' is standard AspectJ.
+`META-INF/aop.xml` is standard AspectJ.
[source,xml,indent=0]
[subs="verbatim,quotes"]
@@ -3094,7 +3094,7 @@ namely the presence of a file (or files) on the Java classpath called
Now to the Spring-specific portion of the configuration. We need to configure a
`LoadTimeWeaver` (all explained later, just take it on trust for now). This load-time
weaver is the essential component responsible for weaving the aspect configuration in
-one or more '++META-INF/aop.xml++' files into the classes in your application. The good
+one or more `META-INF/aop.xml` files into the classes in your application. The good
thing is that it does not require a lot of configuration, as can be seen below (there
are some more options that you can specify, but these are detailed later).
@@ -3120,7 +3120,7 @@ are some more options that you can specify, but these are detailed later).
</beans>
----
-Now that all the required artifacts are in place - the aspect, the '++META-INF/aop.xml++'
+Now that all the required artifacts are in place - the aspect, the `META-INF/aop.xml`
file, and the Spring configuration -, let us create a simple driver class with a
`main(..)` method to demonstrate the LTW in action.
@@ -3157,7 +3157,7 @@ to switch on the LTW. This is the command line we will use to run the above `Mai
java -javaagent:C:/projects/foo/lib/global/spring-instrument.jar foo.Main
----
-The '++-javaagent++' is a flag for specifying and enabling
+The `-javaagent` is a flag for specifying and enabling
http://docs.oracle.com/javase/6/docs/api/java/lang/instrument/package-summary.html[agents
to instrument programs running on the JVM]. The Spring Framework ships with such an
agent, the `InstrumentationSavingAgent`, which is packaged in the
@@ -3235,13 +3235,13 @@ Furthermore, the compiled aspect classes need to be available on the classpath.
[[aop-aj-ltw-aop_dot_xml]]
==== 'META-INF/aop.xml'
-The AspectJ LTW infrastructure is configured using one or more '++META-INF/aop.xml++'
+The AspectJ LTW infrastructure is configured using one or more `META-INF/aop.xml`
files, that are on the Java classpath (either directly, or more typically in jar files).
The structure and contents of this file is detailed in the main AspectJ reference
documentation, and the interested reader is
http://www.eclipse.org/aspectj/doc/released/devguide/ltw-configuration.html[referred to
-that resource]. (I appreciate that this section is brief, but the '++aop.xml++' file is
+that resource]. (I appreciate that this section is brief, but the `aop.xml` file is
100% AspectJ - there is no Spring-specific information or semantics that apply to it,
and so there is no extra value that I can contribute either as a result), so rather than
rehash the quite satisfactory section that the AspectJ developers wrote, I am just
@@ -3301,7 +3301,7 @@ which typically is done using the `@EnableLoadTimeWeaving` annotation.
Alternatively, if you prefer XML based configuration, use the
`<context:load-time-weaver/>` element. Note that the element is defined in the
-'++context++' namespace.
+`context` namespace.
[source,xml,indent=0]
[subs="verbatim,quotes"]
@@ -3380,7 +3380,7 @@ To specify a specific `LoadTimeWeaver` with Java configuration implement the
----
If you are using XML based configuration you can specify the fully-qualified classname
-as the value of the '++weaver-class++' attribute on the `<context:load-time-weaver/>`
+as the value of the `weaver-class` attribute on the `<context:load-time-weaver/>`
element:
[source,xml,indent=0]
@@ -3403,7 +3403,7 @@ element:
----
The `LoadTimeWeaver` that is defined and registered by the configuration can be later
-retrieved from the Spring container using the well-known name '++loadTimeWeaver++'.
+retrieved from the Spring container using the well-known name `loadTimeWeaver`.
Remember that the `LoadTimeWeaver` exists just as a mechanism for Spring's LTW
infrastructure to add one or more `ClassFileTransformers`. The actual
`ClassFileTransformer` that does the LTW is the `ClassPreProcessorAgentAdapter` (from
@@ -3412,10 +3412,10 @@ the `org.aspectj.weaver.loadtime` package) class. See the class-level javadocs o
the weaving is actually effected is beyond the scope of this section.
There is one final attribute of the configuration left to discuss: the
-'++aspectjWeaving++' attribute (or '++aspectj-weaving++' if you are using XML). This is a
+`aspectjWeaving` attribute (or `aspectj-weaving` if you are using XML). This is a
simple attribute that controls whether LTW is enabled or not; it is as simple as that.
It accepts one of three possible values, summarized below, with the default value being
-'++autodetect++' if the attribute is not present.
+`autodetect` if the attribute is not present.
[[aop-aj-ltw-ltw-tag-attrs]]
.AspectJ weaving attribute values
@@ -3432,7 +3432,7 @@ It accepts one of three possible values, summarized below, with the default valu
| `AUTODETECT`
| `autodetect`
-| If the Spring LTW infrastructure can find at least one '++META-INF/aop.xml++' file,
+| If the Spring LTW infrastructure can find at least one `META-INF/aop.xml` file,
then AspectJ weaving is on, else it is off. This is the default value.
|===
diff --git a/src/asciidoc/core-beans.adoc b/src/asciidoc/core-beans.adoc
index 89d85349..8af1139e 100644
--- a/src/asciidoc/core-beans.adoc
+++ b/src/asciidoc/core-beans.adoc
@@ -2513,6 +2513,19 @@ created from the same `loginAction` bean definition will not see these changes i
they are particular to an individual request. When the request completes processing, the
bean that is scoped to the request is discarded.
+When using annotation-driven components or Java Config, the `@RequestScope` annotation
+can be used to assign a component to the `request` scope.
+
+[source,java,indent=0]
+[subs="verbatim,quotes"]
+----
+ **@RequestScope**
+ @Component
+ public class LoginAction {
+ // ...
+ }
+----
+
[[beans-factory-scopes-session]]
==== Session scope
@@ -2535,6 +2548,19 @@ changes in state, because they are particular to an individual HTTP `Session`. W
HTTP `Session` is eventually discarded, the bean that is scoped to that particular HTTP
`Session` is also discarded.
+When using annotation-driven components or Java Config, the `@SessionScope` annotation
+can be used to assign a component to the `session` scope.
+
+[source,java,indent=0]
+[subs="verbatim,quotes"]
+----
+ **@SessionScope**
+ @Component
+ public class UserPreferences {
+ // ...
+ }
+----
+
[[beans-factory-scopes-global-session]]
==== Global session scope
@@ -2578,6 +2604,19 @@ differs in two important ways: It is a singleton per `ServletContext`, not per S
'ApplicationContext' (for which there may be several in any given web application),
and it is actually exposed and therefore visible as a `ServletContext` attribute.
+When using annotation-driven components or Java Config, the `@ApplicationScope`
+annotation can be used to assign a component to the `application` scope.
+
+[source,java,indent=0]
+[subs="verbatim,quotes"]
+----
+ **@ApplicationScope**
+ @Component
+ public class AppPreferences {
+ // ...
+ }
+----
+
[[beans-factory-scopes-other-injection]]
==== Scoped beans as dependencies
@@ -3446,7 +3485,7 @@ dependency type:
| `BootstrapContextAware`
| Resource adapter `BootstrapContext` the container runs in. Typically available only in
- JCA aware ++ApplicationContext++s
+ JCA aware ``ApplicationContext``s
| <<cci>>
| `LoadTimeWeaverAware`
@@ -3598,21 +3637,21 @@ after the Spring container finishes instantiating, configuring, and initializing
you can plug in one or more `BeanPostProcessor` implementations.
You can configure multiple `BeanPostProcessor` instances, and you can control the order
-in which these ++BeanPostProcessor++s execute by setting the `order` property. You can
+in which these ``BeanPostProcessor``s execute by setting the `order` property. You can
set this property only if the `BeanPostProcessor` implements the `Ordered` interface; if
you write your own `BeanPostProcessor` you should consider implementing the `Ordered`
interface too. For further details, consult the javadocs of the `BeanPostProcessor` and
`Ordered` interfaces. See also the note below on
<<beans-factory-programmatically-registering-beanpostprocessors, programmatic
-registration of `BeanPostProcessors`>>.
+registration of ``BeanPostProcessor``s>>.
[NOTE]
====
-++BeanPostProcessor++s operate on bean (or object) __instances__; that is to say, the
-Spring IoC container instantiates a bean instance and __then__ ++BeanPostProcessor++s do
+``BeanPostProcessor``s operate on bean (or object) __instances__; that is to say, the
+Spring IoC container instantiates a bean instance and __then__ ``BeanPostProcessor``s do
their work.
-++BeanPostProcessor++s are scoped __per-container__. This is only relevant if you are
+``BeanPostProcessor``s are scoped __per-container__. This is only relevant if you are
using container hierarchies. If you define a `BeanPostProcessor` in one container, it
will __only__ post-process the beans in that container. In other words, beans that are
defined in one container are not post-processed by a `BeanPostProcessor` defined in
@@ -3640,12 +3679,12 @@ configuration metadata which implement the `BeanPostProcessor` interface. The
later upon bean creation. Bean post-processors can be deployed in the container just
like any other beans.
-Note that when declaring a ++BeanPostProcessor++ using an `@Bean` factory method on a
+Note that when declaring a `BeanPostProcessor` using an `@Bean` factory method on a
configuration class, the return type of the factory method should be the implementation
class itself or at least the `org.springframework.beans.factory.config.BeanPostProcessor`
interface, clearly indicating the post-processor nature of that bean. Otherwise, the
`ApplicationContext` won't be able to autodetect it by type before fully creating it.
-Since a ++BeanPostProcessor++ needs to be instantiated early in order to apply to the
+Since a `BeanPostProcessor` needs to be instantiated early in order to apply to the
initialization of other beans in the context, this early type detection is critical.
@@ -3658,9 +3697,9 @@ While the recommended approach for `BeanPostProcessor` registration is through
register them __programmatically__ against a `ConfigurableBeanFactory` using the
`addBeanPostProcessor` method. This can be useful when needing to evaluate conditional
logic before registration, or even for copying bean post processors across contexts in a
-hierarchy. Note however that `BeanPostProcessors` added programmatically __do not
+hierarchy. Note however that ``BeanPostProcessor``s added programmatically __do not
respect the `Ordered` interface__. Here it is the __order of registration__ that
-dictates the order of execution. Note also that `BeanPostProcessors` registered
+dictates the order of execution. Note also that ``BeanPostProcessor``s registered
programmatically are always processed before those registered through auto-detection,
regardless of any explicit ordering.
====
@@ -3669,11 +3708,11 @@ regardless of any explicit ordering.
[NOTE]
====
Classes that implement the `BeanPostProcessor` interface are __special__ and are treated
-differently by the container. All `BeanPostProcessors` __and beans that they reference
+differently by the container. All ``BeanPostProcessor``s __and beans that they reference
directly__ are instantiated on startup, as part of the special startup phase of the
-`ApplicationContext`. Next, all `BeanPostProcessors` are registered in a sorted fashion
+`ApplicationContext`. Next, all ``BeanPostProcessor``s are registered in a sorted fashion
and applied to all further beans in the container. Because AOP auto-proxying is
-implemented as a `BeanPostProcessor` itself, neither `BeanPostProcessors` nor the beans
+implemented as a `BeanPostProcessor` itself, neither ``BeanPostProcessor``s nor the beans
they reference directly are eligible for auto-proxying, and thus do not have aspects
woven into them.
@@ -3690,7 +3729,7 @@ directly correspond to the declared name of a bean and no name attribute is used
Spring will access other beans for matching them by type.
====
-The following examples show how to write, register, and use `BeanPostProcessors` in an
+The following examples show how to write, register, and use ``BeanPostProcessor``s in an
`ApplicationContext`.
@@ -3811,10 +3850,10 @@ this interface are similar to those of the `BeanPostProcessor`, with one major
difference: `BeanFactoryPostProcessor` operates on the __bean configuration metadata__;
that is, the Spring IoC container allows a `BeanFactoryPostProcessor` to read the
configuration metadata and potentially change it __before__ the container instantiates
-any beans other than `BeanFactoryPostProcessors`.
+any beans other than ``BeanFactoryPostProcessor``s.
-You can configure multiple `BeanFactoryPostProcessors`, and you can control the order in
-which these `BeanFactoryPostProcessors` execute by setting the `order` property.
+You can configure multiple ``BeanFactoryPostProcessor``s, and you can control the order in
+which these ``BeanFactoryPostProcessor``s execute by setting the `order` property.
However, you can only set this property if the `BeanFactoryPostProcessor` implements the
`Ordered` interface. If you write your own `BeanFactoryPostProcessor`, you should
consider implementing the `Ordered` interface too. Consult the javadocs of the
@@ -3830,10 +3869,10 @@ to work with bean instances within a `BeanFactoryPostProcessor` (e.g., using
standard container lifecycle. This may cause negative side effects such as bypassing
bean post processing.
-Also, `BeanFactoryPostProcessors` are scoped __per-container__. This is only relevant if
+Also, ``BeanFactoryPostProcessor``s are scoped __per-container__. This is only relevant if
you are using container hierarchies. If you define a `BeanFactoryPostProcessor` in one
container, it will __only__ be applied to the bean definitions in that container. Bean
-definitions in one container will not be post-processed by `BeanFactoryPostProcessors`
+definitions in one container will not be post-processed by ``BeanFactoryPostProcessor``s
in another container, even if both containers are part of the same hierarchy.
====
@@ -3853,8 +3892,8 @@ you would any other bean.
[NOTE]
====
-As with ++BeanPostProcessor++s , you typically do not want to configure
-++BeanFactoryPostProcessor++s for lazy initialization. If no other bean references a
+As with ``BeanPostProcessor``s , you typically do not want to configure
+``BeanFactoryPostProcessor``s for lazy initialization. If no other bean references a
`Bean(Factory)PostProcessor`, that post-processor will not get instantiated at all.
Thus, marking it for lazy initialization will be ignored, and the
`Bean(Factory)PostProcessor` will be instantiated eagerly even if you set the
@@ -4172,7 +4211,7 @@ example:
This annotation simply indicates that the affected bean property must be populated at
configuration time, through an explicit property value in a bean definition or through
autowiring. The container throws an exception if the affected bean property has not been
-populated; this allows for eager and explicit failure, avoiding ++NullPointerException++s
+populated; this allows for eager and explicit failure, avoiding ``NullPointerException``s
or the like later on. It is still recommended that you put assertions into the bean
class itself, for example, into an init method. Doing so enforces those required
references and values even when you use the class outside of a container.
@@ -4182,18 +4221,24 @@ references and values even when you use the class outside of a container.
[[beans-autowired-annotation]]
=== @Autowired
-As expected, you can apply the `@Autowired` annotation to "traditional" setter methods:
+[NOTE]
+====
+JSR 330's `@Inject` annotation can be used in place of Spring's `@Autowired` annotation
+in the examples below. See <<beans-standard-annotations,here>> for more details.
+====
+
+You can apply the `@Autowired` annotation to constructors:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- public class SimpleMovieLister {
+ public class MovieRecommender {
- private MovieFinder movieFinder;
+ private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
- public void setMovieFinder(MovieFinder movieFinder) {
- this.movieFinder = movieFinder;
+ public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
+ this.customerPreferenceDao = customerPreferenceDao;
}
// ...
@@ -4203,10 +4248,31 @@ As expected, you can apply the `@Autowired` annotation to "traditional" setter m
[NOTE]
====
-JSR 330's `@Inject` annotation can be used in place of Spring's `@Autowired` annotation
-in the examples below. See <<beans-standard-annotations,here>> for more details.
+As of Spring Framework 4.3, the `@Autowired` constructor is no longer necessary if the
+target bean only defines one constructor. If several constructors are available, at
+least one must be annotated to teach the container which one it has to use.
====
+As expected, you can also apply the `@Autowired` annotation to "traditional" setter
+methods:
+
+[source,java,indent=0]
+[subs="verbatim,quotes"]
+----
+ public class SimpleMovieLister {
+
+ private MovieFinder movieFinder;
+
+ @Autowired
+ public void setMovieFinder(MovieFinder movieFinder) {
+ this.movieFinder = movieFinder;
+ }
+
+ // ...
+
+ }
+----
+
You can also apply the annotation to methods with arbitrary names and/or multiple
arguments:
@@ -4568,12 +4634,28 @@ If you intend to express annotation-driven injection by name, do not primarily u
`@Autowired`, even if is technically capable of referring to a bean name through
`@Qualifier` values. Instead, use the JSR-250 `@Resource` annotation, which is
semantically defined to identify a specific target component by its unique name, with
-the declared type being irrelevant for the matching process.
-
-As a specific consequence of this semantic difference, beans that are themselves defined
-as a collection or map type cannot be injected through `@Autowired`, because type
-matching is not properly applicable to them. Use `@Resource` for such beans, referring
-to the specific collection or map bean by unique name.
+the declared type being irrelevant for the matching process. `@Autowired` has rather
+different semantics: After selecting candidate beans by type, the specified String
+qualifier value will be considered within those type-selected candidates only, e.g.
+matching an "account" qualifier against beans marked with the same qualifier label.
+
+For beans that are themselves defined as a collection/map or array type, `@Resource`
+is a fine solution, referring to the specific collection or array bean by unique name.
+That said, as of 4.3, collection/map and array types can be matched through Spring's
+`@Autowired` type matching algorithm as well, as long as the element type information
+is preserved in `@Bean` return type signatures or collection inheritance hierarchies.
+In this case, qualifier values can be used to select among same-typed collections,
+as outlined in the previous paragraph.
+
+As of 4.3, `@Autowired` also considers self references for injection, i.e. references
+back to the bean that is currently injected. Note that self injection is a fallback;
+regular dependencies on other components always have precedence. In that sense, self
+references do not participate in regular candidate selection and are therefore in
+particular never primary; on the contrary, they always end up as lowest precedence.
+In practice, use self references as a last resort only, e.g. for calling other methods
+on the same instance through the bean's transactional proxy: Consider factoring out
+the affected methods to a separate delegate bean in such a scenario. Alternatively,
+use `@Resource` which may obtain a proxy back to the current bean by its unique name.
`@Autowired` applies to fields, constructors, and multi-argument methods, allowing for
narrowing through qualifier annotations at the parameter level. By contrast, `@Resource`
@@ -5110,19 +5192,26 @@ the `@RestController` annotation from Spring MVC is __composed__ of `@Controller
In addition, composed annotations may optionally redeclare attributes from
meta-annotations to allow user customization. This can be particularly useful when you
-want to only expose a subset of the meta-annotation's attributes. For example, the
-following is a custom `@Scope` annotation that hardcodes the scope name to `session` but
-still allows customization of the `proxyMode`.
+want to only expose a subset of the meta-annotation's attributes. For example, Spring's
+`@SessionScope` annotation hardcodes the scope name to `session` but still allows
+customization of the `proxyMode`.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @Target(ElementType.TYPE)
+ @Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
- **@Scope("session")**
+ @Documented
+ @Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {
- ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
+ /**
+ * Alias for {@link Scope#proxyMode}.
+ * <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
+ */
+ @AliasFor(annotation = Scope.class)
+ ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
+
}
----
@@ -5133,7 +5222,7 @@ still allows customization of the `proxyMode`.
----
@Service
**@SessionScope**
- public class SessionScopedUserService implements UserService {
+ public class SessionScopedService {
// ...
}
----
@@ -5144,8 +5233,8 @@ Or with an overridden value for the `proxyMode` as follows:
[subs="verbatim,quotes"]
----
@Service
- **@SessionScope(proxyMode = ScopedProxyMode.TARGET_CLASS)**
- public class SessionScopedService {
+ **@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)**
+ public class SessionScopedUserService implements UserService {
// ...
}
----
@@ -5157,7 +5246,7 @@ For further details, consult the <<annotation-programming-model,Spring Annotatio
=== Automatically detecting classes and registering bean definitions
Spring can automatically detect stereotyped classes and register corresponding
-++BeanDefinition++s with the `ApplicationContext`. For example, the following two classes
+``BeanDefinition``s with the `ApplicationContext`. For example, the following two classes
are eligible for such autodetection:
[source,java,indent=0]
@@ -5405,13 +5494,12 @@ support for autowiring of `@Bean` methods:
}
@Bean
- @Scope(BeanDefinition.SCOPE_SINGLETON)
private TestBean privateInstance() {
return new TestBean("privateInstance", i++);
}
@Bean
- @Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
+ @RequestScope
public TestBean requestScopedInstance() {
return new TestBean("requestScopedInstance", 3);
}
@@ -5557,6 +5645,9 @@ within the annotation:
}
----
+For details on web-specific scopes, see <<beans-factory-scopes-other>>.
+
+
[NOTE]
====
To provide a custom strategy for scope resolution rather than relying on the
@@ -6389,7 +6480,7 @@ link) to our `@Bean` using Java, it would look like the following:
----
// an HTTP Session-scoped bean exposed as a proxy
@Bean
- **@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)**
+ **@SessionScope**
public UserPreferences userPreferences() {
return new UserPreferences();
}
@@ -6429,7 +6520,7 @@ resulting bean. This functionality can be overridden, however, with the `name` a
==== Bean aliasing
As discussed in <<beans-beanname>>, it is sometimes desirable to give a single bean
-multiple names, otherwise known as__bean aliasing__. The `name` attribute of the `@Bean`
+multiple names, otherwise known as __bean aliasing__. The `name` attribute of the `@Bean`
annotation accepts a String array for this purpose.
[source,java,indent=0]
@@ -6485,7 +6576,7 @@ inter-bean dependencies. See <<beans-java-basic-concepts>> for a general introdu
[[beans-java-injecting-dependencies]]
==== Injecting inter-bean dependencies
-When ++@Bean++s have dependencies on one another, expressing that dependency is as simple
+When ``@Bean``s have dependencies on one another, expressing that dependency is as simple
as having one bean method call another:
[source,java,indent=0]
@@ -6613,7 +6704,7 @@ where the magic comes in: All `@Configuration` classes are subclassed at startup
with `CGLIB`. In the subclass, the child method checks the container first for any
cached (scoped) beans before it calls the parent method and creates a new instance. Note
that as of Spring 3.2, it is no longer necessary to add CGLIB to your classpath because
-CGLIB classes have been repackaged under org.springframework and included directly
+CGLIB classes have been repackaged under `org.springframework.cglib` and included directly
within the spring-core JAR.
[NOTE]
@@ -6781,8 +6872,12 @@ work on the configuration class itself since it is being created as a bean insta
@Configuration
public class RepositoryConfig {
+ private final DataSource dataSource;
+
@Autowired
- private DataSource dataSource;
+ public RepositoryConfig(DataSource dataSource) {
+ this.dataSource = dataSource;
+ }
@Bean
public AccountRepository accountRepository() {
@@ -6810,6 +6905,14 @@ work on the configuration class itself since it is being created as a bean insta
}
----
+[TIP]
+====
+Constructor injection in `@Configuration` classes is only supported as of Spring
+Framework 4.3. Note also that there is no need to specify `@Autowired` if the target
+bean defines only one constructor; in the example above, `@Autowired` is not necessary
+on the `RepositoryConfig` constructor.
+====
+
.[[beans-java-injecting-imported-beans-fq]]Fully-qualifying imported beans for ease of navigation
--
In the scenario above, using `@Autowired` works well and provides the desired
@@ -8189,11 +8292,42 @@ method signature to return the event that should be published, something like:
}
----
+NOTE: This feature is not supported for <<context-functionality-events-async,asynchronous
+listeners>>.
+
This new method will publish a new `ListUpdateEvent` for every `BlackListEvent` handled
by the method above. If you need to publish several events, just return a `Collection` of
events instead.
-Finally if you need the listener to be invoked before another one, just add the `@Order`
+[[context-functionality-events-async]]
+==== Asynchronous Listeners
+
+If you want a particular listener to process events asynchronously, simply reuse the
+<<scheduling-annotation-support-async,regular `@Async` support>>:
+
+[source,java,indent=0]
+[subs="verbatim,quotes"]
+----
+ @EventListener
+ @Async
+ public void processBlackListEvent(BlackListEvent event) {
+ // BlackListEvent is processed in a separate thread
+ }
+----
+
+Be aware of the following limitations when using asynchronous events:
+
+. If the event listener throws an `Exception` it will not be propagated to the caller,
+ check `AsyncUncaughtExceptionHandler` for more details.
+. Such event listener cannot send replies. If you need to send another event as the
+ result of the processing, inject `ApplicationEventPublisher` to send the event
+ manually.
+
+
+[[context-functionality-events-order]]
+==== Ordering Listeners
+
+If you need the listener to be invoked before another one, just add the `@Order`
annotation to the method declaration:
[source,java,indent=0]
@@ -8266,7 +8400,7 @@ For optimal usage and understanding of application contexts, users should genera
familiarize themselves with Spring's `Resource` abstraction, as described in the chapter
<<resources>>.
-An application context is a `ResourceLoader`, which can be used to load ++Resource++s. A
+An application context is a `ResourceLoader`, which can be used to load ``Resource``s. A
`Resource` is essentially a more feature rich version of the JDK class `java.net.URL`,
in fact, the implementations of the `Resource` wrap an instance of `java.net.URL` where
appropriate. A `Resource` can obtain low-level resources from almost any location in a
@@ -8352,7 +8486,7 @@ class for the configuration details involved in RAR deployment.
__For a simple deployment of a Spring ApplicationContext as a Java EE RAR file:__ package
all application classes into a RAR file, which is a standard JAR file with a different
file extension. Add all required library JARs into the root of the RAR archive. Add a
-"META-INF/ra.xml" deployment descriptor (as shown in ++SpringContextResourceAdapter++'s
+"META-INF/ra.xml" deployment descriptor (as shown in ``SpringContextResourceAdapter``s
JavaDoc) and the corresponding Spring XML bean definition file(s) (typically
"META-INF/applicationContext.xml"), and drop the resulting RAR file into your
application server's deployment directory.
@@ -8470,7 +8604,7 @@ implementation, you must write code like this:
In both cases, the explicit registration step is inconvenient, which is one reason why
the various `ApplicationContext` implementations are preferred above plain `BeanFactory`
implementations in the vast majority of Spring-backed applications, especially when
-using `BeanFactoryPostProcessors` and `BeanPostProcessors`. These mechanisms implement
+using ``BeanFactoryPostProcessor``s and ``BeanPostProcessor``s. These mechanisms implement
important functionality such as property placeholder replacement and AOP.
@@ -8506,4 +8640,3 @@ utility class
locator that is described in this
https://spring.io/blog/2007/06/11/using-a-shared-parent-application-context-in-a-multi-war-spring-application/[Spring
team blog entry].
-
diff --git a/src/asciidoc/core-expressions.adoc b/src/asciidoc/core-expressions.adoc
index ec15a381..86e34a50 100644
--- a/src/asciidoc/core-expressions.adoc
+++ b/src/asciidoc/core-expressions.adoc
@@ -247,7 +247,7 @@ expression using the methods `setVariable()` and `registerFunction()`. The use o
variables and functions are described in the language reference sections
<<expressions-ref-variables,Variables>> and <<expressions-ref-functions,Functions>>. The
`StandardEvaluationContext` is also where you can register custom
-++ConstructorResolver++s, ++MethodResolver++s, and ++PropertyAccessor++s to extend how SpEL
+``ConstructorResolver``s, ``MethodResolver``s, and ``PropertyAccessor``s to extend how SpEL
evaluates expressions. Please refer to the JavaDoc of these classes for more details.
@@ -432,7 +432,7 @@ More and more types of expression will be compilable in the future.
[[expressions-beandef]]
== Expression support for defining bean definitions
SpEL expressions can be used with XML or annotation-based configuration metadata for
-defining ++BeanDefinition++s. In both cases the syntax to define the expression is of the
+defining ``BeanDefinition``s. In both cases the syntax to define the expression is of the
form `#{ <expression string> }`.
@@ -785,7 +785,7 @@ expression based `matches` operator.
----
// evaluates to false
boolean falseValue = parser.parseExpression(
- "'xyz' instanceof T(int)").getValue(Boolean.class);
+ "'xyz' instanceof T(Integer.class)").getValue(Boolean.class);
// evaluates to true
boolean trueValue = parser.parseExpression(
@@ -796,6 +796,13 @@ expression based `matches` operator.
"'5.0067' matches '\^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
----
+[NOTE]
+====
+Be careful with primitive types as they are immediately boxed up to the wrapper type,
+so `1 instanceof T(int)` evaluates to `false` while `1 instanceof T(Integer.class)`
+evaluates to `true`, as expected.
+====
+
Each symbolic operator can also be specified as a purely alphabetic equivalent. This
avoids problems where the symbols used have special meaning for the document type in
which the expression is embedded (eg. an XML document). The textual equivalents are
@@ -1056,6 +1063,18 @@ lookup beans from an expression using the (@) symbol.
Object bean = parser.parseExpression("@foo").getValue(context);
----
+To access a factory bean itself, the bean name should instead be prefixed with a (&) symbol.
+
+[source,java,indent=0]
+[subs="verbatim,quotes"]
+----
+ ExpressionParser parser = new SpelExpressionParser();
+ StandardEvaluationContext context = new StandardEvaluationContext();
+ context.setBeanResolver(new MyBeanResolver());
+
+ // This will end up calling resolve(context,"&foo") on MyBeanResolver during evaluation
+ Object bean = parser.parseExpression("&foo").getValue(context);
+----
[[expressions-operator-ternary]]
diff --git a/src/asciidoc/core-resources.adoc b/src/asciidoc/core-resources.adoc
index 140be8b6..11cfca24 100644
--- a/src/asciidoc/core-resources.adoc
+++ b/src/asciidoc/core-resources.adoc
@@ -253,7 +253,7 @@ Similarly, one can force a `UrlResource` to be used by specifying any of the sta
Resource template = ctx.getResource("http://myhost.com/resource/path/myTemplate.txt");
----
-The following table summarizes the strategy for converting ++String++s to ++Resource++s:
+The following table summarizes the strategy for converting ``String``s to ``Resource``s:
[[resources-resource-strings]]
.Resource strings
diff --git a/src/asciidoc/core-validation.adoc b/src/asciidoc/core-validation.adoc
index bb5916ef..0374d8cf 100644
--- a/src/asciidoc/core-validation.adoc
+++ b/src/asciidoc/core-validation.adoc
@@ -349,7 +349,7 @@ the properties of instantiated `Companies` and `Employees`:
Spring uses the concept of `PropertyEditors` to effect the conversion between an
`Object` and a `String`. If you think about it, it sometimes might be handy to be able
to represent properties in a different way than the object itself. For example, a `Date`
-can be represented in a human readable way (as the `String` ' `2007-14-09`'), while
+can be represented in a human readable way (as the `String` `'2007-14-09'`), while
we're still able to convert the human readable form back to the original date (or even
better: convert any date entered in a human readable form, back to `Date` objects). This
behavior can be achieved by __registering custom editors__, of type
@@ -1317,7 +1317,7 @@ See <<mvc-config-conversion>> in the Spring MVC chapter.
[[format-configuring-formatting-globaldatetimeformat]]
== Configuring a global date & time format
By default, date and time fields that are not annotated with `@DateTimeFormat` are
-converted from strings using the the `DateFormat.SHORT` style. If you prefer, you can
+converted from strings using the `DateFormat.SHORT` style. If you prefer, you can
change this by defining your own global format.
You will need to ensure that Spring does not register default formatters, and instead
diff --git a/src/asciidoc/data-access.adoc b/src/asciidoc/data-access.adoc
index 0cef7939..d26eba96 100644
--- a/src/asciidoc/data-access.adoc
+++ b/src/asciidoc/data-access.adoc
@@ -801,7 +801,7 @@ the call stack, and make a determination whether to mark the transaction for rol
In its default configuration, the Spring Framework's transaction infrastructure code
__only__ marks a transaction for rollback in the case of runtime, unchecked exceptions;
that is, when the thrown exception is an instance or subclass of `RuntimeException`. (
-++Error++s will also - by default - result in a rollback). Checked exceptions that are
+``Error``s will also - by default - result in a rollback). Checked exceptions that are
thrown from a transactional method do __not__ result in rollback in the default
configuration.
@@ -2330,7 +2330,7 @@ creation and release of resources, which helps you avoid common errors such as
forgetting to close the connection. It performs the basic tasks of the core JDBC
workflow such as statement creation and execution, leaving application code to provide
SQL and extract results. The `JdbcTemplate` class executes SQL queries, update
-statements and stored procedure calls, performs iteration over ++ResultSet++s and
+statements and stored procedure calls, performs iteration over ``ResultSet``s and
extraction of returned parameter values. It also catches JDBC exceptions and translates
them to the generic, more informative, exception hierarchy defined in the
`org.springframework.dao` package.
@@ -3435,7 +3435,7 @@ The batch update methods for this call returns an array of int arrays containing
entry for each batch with an array of the number of affected rows for each update. The top
level array's length indicates the number of batches executed and the second level array's
length indicates the number of updates in that batch. The number of updates in each batch
-should be the the batch size provided for all batches except for the last one that might
+should be the batch size provided for all batches except for the last one that might
be less, depending on the total number of update objects provided. The update count for
each update statement is the one reported by the JDBC driver. If the count is not
available, the JDBC driver returns a -2 value.
@@ -4543,8 +4543,8 @@ declaration of an `SqlOutParameter`.
You use the `SqlTypeValue` to pass in the value of a Java object like `TestItem` into a
stored procedure. The `SqlTypeValue` interface has a single method named
`createTypeValue` that you must implement. The active connection is passed in, and you
-can use it to create database-specific objects such as ++StructDescriptor++s, as shown in
-the following example, or ++ArrayDescriptor++s.
+can use it to create database-specific objects such as ``StructDescriptor``s, as shown in
+the following example, or ``ArrayDescriptor``s.
[source,java,indent=0]
[subs="verbatim,quotes"]
@@ -4873,6 +4873,25 @@ followed by a set of `CREATE` statements.
The `ignore-failures` option can be set to `NONE` (the default), `DROPS` (ignore failed
drops), or `ALL` (ignore all failures).
+Each statement should be separated by `;` or a new line if the `;` character is not
+present at all in the script. You can control that globally or script by script, for
+example:
+
+[source,xml,indent=0]
+[subs="verbatim,quotes"]
+----
+ <jdbc:initialize-database data-source="dataSource" **separator="@@"**>
+ <jdbc:script location="classpath:com/foo/sql/db-schema.sql" **separator=";"**/>
+ <jdbc:script location="classpath:com/foo/sql/db-test-data-1.sql"/>
+ <jdbc:script location="classpath:com/foo/sql/db-test-data-2.sql"/>
+ </jdbc:initialize-database>
+----
+
+In this example, the two `test-data` scripts use `@@` as statement separator and only
+the `db-schema.sql` uses `;`. This configuration specifies that the default separator
+is `@@` and override that default for the `db-schema` script.
+
+
If you need more control than you get from the XML namespace, you can simply use the
`DataSourceInitializer` directly and define it as a component in your application.
@@ -7086,7 +7105,7 @@ set using the `targetClass` property. Optionally, you can set the binding name u
----
A `JibxMarshaller` is configured for a single class. If you want to marshal multiple
-classes, you have to configure multiple ++JibxMarshaller++s with different `targetClass`
+classes, you have to configure multiple ``JibxMarshaller``s with different `targetClass`
property values.
diff --git a/src/asciidoc/index-docinfo.xml b/src/asciidoc/index-docinfo.xml
index 78f1c77c..57d964cf 100644
--- a/src/asciidoc/index-docinfo.xml
+++ b/src/asciidoc/index-docinfo.xml
@@ -1,7 +1,7 @@
<productname>Spring Framework</productname>
<releaseinfo>{revnumber}</releaseinfo>
<copyright>
- <year>2004-2015</year>
+ <year>2004-2016</year>
</copyright>
<legalnotice>
<para>Copies of this document may be made for your own use and for distribution to
diff --git a/src/asciidoc/integration.adoc b/src/asciidoc/integration.adoc
index 43903b45..19dd7b1f 100644
--- a/src/asciidoc/integration.adoc
+++ b/src/asciidoc/integration.adoc
@@ -1292,7 +1292,7 @@ Concrete implementations for the main media (mime) types are provided in the fra
and are registered by default with the `RestTemplate` on the client-side and with
`AnnotationMethodHandlerAdapter` on the server-side.
-The implementations of ++HttpMessageConverter++s are described in the following sections.
+The implementations of ``HttpMessageConverter``s are described in the following sections.
For all converters a default media type is used but can be overridden by setting the
`supportedMediaTypes` bean property
@@ -2796,6 +2796,9 @@ as follow to automatically send a response:
}
----
+TIP: If you have several `@JmsListener`-annotated methods, you can also place the `@SendTo`
+annotation at class-level to share a default reply destination.
+
If you need to set additional headers in a transport-independent manner, you could return a
`Message` instead, something like:
@@ -3505,6 +3508,9 @@ be marked with the `ManagedAttribute` annotation. When marking properties you ca
either the annotation of the getter or the setter to create a write-only or read-only
attribute respectively.
+NOTE: A `ManagedResource` annotated bean must be public as well as the methods exposing
+an operation or an attribute.
+
The example below shows the annotated version of the `JmxTestBean` class that you saw
earlier:
@@ -3888,7 +3894,7 @@ lists of method names.
=== Controlling the ObjectNames for your beans
Behind the scenes, the `MBeanExporter` delegates to an implementation of the
-`ObjectNamingStrategy` to obtain ++ObjectName++s for each of the beans it is registering.
+`ObjectNamingStrategy` to obtain ``ObjectName``s for each of the beans it is registering.
The default implementation, `KeyNamingStrategy`, will, by default, use the key of the
`beans` `Map` as the `ObjectName`. In addition, the `KeyNamingStrategy` can map the key
of the `beans` `Map` to an entry in a `Properties` file (or files) to resolve the
@@ -3903,7 +3909,7 @@ uses source level metadata to obtain the `ObjectName`.
==== Reading ObjectNames from Properties
You can configure your own `KeyNamingStrategy` instance and configure it to read
-++ObjectName++s from a `Properties` instance rather than use bean key. The
+``ObjectName``s from a `Properties` instance rather than use bean key. The
`KeyNamingStrategy` will attempt to locate an entry in the `Properties` with a key
corresponding to the bean key. If no entry is found or if the `Properties` instance is
`null` then the bean key itself is used.
@@ -6556,6 +6562,8 @@ Context, then those would typically have been provided through dependency inject
[NOTE]
====
+As of Spring Framework 4.3, `@Scheduled` methods are supported on beans of any scope.
+
Make sure that you are not initializing multiple instances of the same `@Scheduled`
annotation class at runtime, unless you do want to schedule callbacks to each such
instance. Related to this, make sure that you do not use `@Configurable` on bean
@@ -8168,6 +8176,7 @@ materialized by the `org.springframework.cache.Cache` and
There are <<cache-store-configuration,a few implementations>> of that abstraction
available out of the box: JDK `java.util.concurrent.ConcurrentMap` based caches,
http://ehcache.org/[EhCache], Gemfire cache,
+https://github.com/ben-manes/caffeine/wiki[Caffeine],
https://code.google.com/p/guava-libraries/wiki/CachesExplained[Guava caches] and
JSR-107 compliant caches. See <<cache-plug>> for more information on plugging in
other cache stores/providers.
@@ -8394,6 +8403,33 @@ result in an exception as a custom `CacheManager` will be ignored by the
`CacheResolver` implementation. This is probably not what you expect.
====
+[[cache-annotations-cacheable-synchronized]]
+===== Synchronized caching
+In a multi-threaded environment, certain operations might be concurrently invoked for
+the same argument (typically on startup). By default, the cache abstraction does not
+lock anything and the same value may be computed several times, defeating the purpose
+of caching.
+
+For those particular cases, the `sync` attribute can be used to instruct the underlying
+cache provider to _lock_ the cache entry while the value is being computed. As a result,
+only one thread will be busy computing the value while the others are blocked until the
+entry is updated in the cache.
+
+[source,java,indent=0]
+[subs="verbatim,quotes"]
+----
+ @Cacheable(cacheNames="foos", **sync="true"**)
+ public Foo executeExpensiveOperation(String id) {...}
+----
+
+[NOTE]
+====
+This is an optional feature and your favorite cache library may not support it. All
+`CacheManager` implementations provided by the core framework support it. Check the
+documentation of your cache provider for more details.
+====
+
+
[[cache-annotations-cacheable-condition]]
===== Conditional caching
Sometimes, a method might not be suitable for caching all the time (for example, it
@@ -8561,10 +8597,8 @@ and thus requires a result.
There are cases when multiple annotations of the same type, such as `@CacheEvict` or
`@CachePut` need to be specified, for example because the condition or the key
-expression is different between different caches. Unfortunately Java does not support
-such declarations however there is a workaround - using an __enclosing__ annotation, in
-this case, `@Caching`. `@Caching` allows multiple nested `@Cacheable`, `@CachePut` and
-`@CacheEvict` to be used on the same method:
+expression is different between different caches. `@Caching` allows multiple nested
+`@Cacheable`, `@CachePut` and `@CacheEvict` to be used on the same method:
[source,java,indent=0]
[subs="verbatim,quotes"]
@@ -9006,7 +9040,7 @@ we did in the example above by defining the target cache through the `cache:defi
=== Configuring the cache storage
Out of the box, the cache abstraction provides several storage integration. To use
them, one needs to simply declare an appropriate `CacheManager` - an entity that
-controls and manages ++Cache++s and can be used to retrieve these for storage.
+controls and manages ``Cache``s and can be used to retrieve these for storage.
[[cache-store-configuration-jdk]]
@@ -9062,6 +9096,42 @@ This setup bootstraps the ehcache library inside Spring IoC (through the `ehcach
is then wired into the dedicated `CacheManager` implementation. Note the entire
ehcache-specific configuration is read from `ehcache.xml`.
+[[cache-store-configuration-caffeine]]
+==== Caffeine Cache
+
+Caffeine is a Java 8 rewrite of Guava's cache and its implementation is located under
+`org.springframework.cache.caffeine` package and provides access to several features
+of Caffeine.
+
+Configuring a `CacheManager` that creates the cache on demand is straightforward:
+
+[source,xml,indent=0]
+[subs="verbatim,quotes"]
+----
+ <bean id="cacheManager"
+ class="org.springframework.cache.caffeine.CaffeineCacheManager"/>
+----
+
+It is also possible to provide the caches to use explicitly. In that case, only those
+will be made available by the manager:
+
+[source,xml,indent=0]
+[subs="verbatim,quotes"]
+----
+ <bean id="cacheManager" class="org.springframework.cache.caffeine.CaffeineCacheManager">
+ <property name="caches">
+ <set>
+ <value>default</value>
+ <value>books</value>
+ </set>
+ </property>
+ </bean>
+----
+
+The Caffeine `CacheManager` also supports customs `Caffeine` and `CacheLoader`. See
+the https://github.com/ben-manes/caffeine/wiki[Caffeine documentation] for more
+information about those.
+
[[cache-store-configuration-guava]]
==== Guava Cache
@@ -9149,7 +9219,7 @@ performs no caching - that is, forces the cached methods to be executed every ti
</bean>
----
-The `CompositeCacheManager` above chains multiple ++CacheManager++s and additionally,
+The `CompositeCacheManager` above chains multiple ``CacheManager``s and additionally,
through the `fallbackToNoOpCache` flag, adds a __no op__ cache that for all the
definitions not handled by the configured cache managers. That is, every cache
definition not found in either `jdkCache` or `gemfireCache` (configured above) will be
diff --git a/src/asciidoc/overview.adoc b/src/asciidoc/overview.adoc
index 183fec5e..a59898ae 100644
--- a/src/asciidoc/overview.adoc
+++ b/src/asciidoc/overview.adoc
@@ -238,7 +238,7 @@ of the `spring-webmvc` module.
The `spring-test` module supports the <<unit-testing,unit testing>> and
<<integration-testing,integration testing>> of Spring components with JUnit or TestNG. It
provides consistent <<testcontext-ctx-management,loading>> of Spring
-++ApplicationContext++s and <<testcontext-ctx-management-caching,caching>> of those
+``ApplicationContext``s and <<testcontext-ctx-management-caching,caching>> of those
contexts. It also provides <<mock-objects,mock objects>> that you can use to test your
code in isolation.
@@ -260,7 +260,7 @@ container-managed transactions. All your custom business logic can be implemente
simple POJOs and managed by Spring's IoC container. Additional services include support
for sending email and validation that is independent of the web layer, which lets you
choose where to execute validation rules. Spring's ORM support is integrated with JPA,
-Hibernate and and JDO; for example, when using Hibernate, you can continue to use
+Hibernate and JDO; for example, when using Hibernate, you can continue to use
your existing mapping files and standard Hibernate `SessionFactory` configuration. Form
controllers seamlessly integrate the web-layer with the domain model, removing the need
for `ActionForms` or other classes that transform HTTP parameters to values for your
diff --git a/src/asciidoc/testing.adoc b/src/asciidoc/testing.adoc
index 3ef4ba77..aa9e7dc8 100644
--- a/src/asciidoc/testing.adoc
+++ b/src/asciidoc/testing.adoc
@@ -64,7 +64,7 @@ depends on environment-specific properties.
==== JNDI
The `org.springframework.mock.jndi` package contains an implementation of the JNDI SPI,
which you can use to set up a simple JNDI environment for test suites or stand-alone
-applications. If, for example, JDBC ++DataSource++s get bound to the same JNDI names in
+applications. If, for example, JDBC ``DataSource``s get bound to the same JNDI names in
test code as within a Java EE container, you can reuse both application code and
configuration in testing scenarios without modification.
@@ -75,11 +75,11 @@ The `org.springframework.mock.web` package contains a comprehensive set of Servl
mock objects, which are useful for testing web contexts, controllers, and filters. These
mock objects are targeted at usage with Spring's Web MVC framework and are generally more
convenient to use than dynamic mock objects such as http://www.easymock.org[EasyMock] or
-alternative Servlet API mock objects such as http://www.mockobjects.com[MockObjects]. As of
+alternative Servlet API mock objects such as http://www.mockobjects.com[MockObjects]. Since
Spring Framework 4.0, the set of mocks in the `org.springframework.mock.web` package is
based on the Servlet 3.0 API.
-For thorough integration testing of your Spring MVC and REST ++Controller++s in
+For thorough integration testing of your Spring MVC and REST ``Controller``s in
conjunction with your `WebApplicationContext` configuration for Spring MVC, see the
<<spring-mvc-test-framework,_Spring MVC Test Framework_>>.
@@ -132,10 +132,10 @@ dealing with Spring MVC `ModelAndView` objects.
.Unit testing Spring MVC Controllers
[TIP]
====
-To unit test your Spring MVC ++Controller++s as POJOs, use `ModelAndViewAssert` combined
+To unit test your Spring MVC ``Controller``s as POJOs, use `ModelAndViewAssert` combined
with `MockHttpServletRequest`, `MockHttpSession`, and so on from Spring's
<<mock-objects-servlet, Servlet API mocks>>. For thorough integration testing of your
-Spring MVC and REST ++Controller++s in conjunction with your `WebApplicationContext`
+Spring MVC and REST ``Controller``s in conjunction with your `WebApplicationContext`
configuration for Spring MVC, use the <<spring-mvc-test-framework,_Spring MVC Test
Framework_>> instead.
====
@@ -193,7 +193,7 @@ configuration details.
[[testing-ctx-management]]
==== Context management and caching
The Spring TestContext Framework provides consistent loading of Spring
-++ApplicationContext++s and ++WebApplicationContext++s as well as caching of those
+``ApplicationContext``s and ``WebApplicationContext``s as well as caching of those
contexts. Support for the caching of loaded contexts is important, because startup time
can become an issue -- not because of the overhead of Spring itself, but because the
objects instantiated by the Spring container take time to instantiate. For example, a
@@ -201,10 +201,11 @@ project with 50 to 100 Hibernate mapping files might take 10 to 20 seconds to lo
mapping files, and incurring that cost before running every test in every test fixture
leads to slower overall test runs that reduce developer productivity.
-Test classes typically declare either an array of __resource locations__ for XML
+Test classes typically declare either an array of __resource locations__ for XML or Groovy
configuration metadata -- often in the classpath -- or an array of __annotated classes__
that is used to configure the application. These locations or classes are the same as or
-similar to those specified in `web.xml` or other deployment configuration files.
+similar to those specified in `web.xml` or other configuration files for production
+deployments.
By default, once loaded, the configured `ApplicationContext` is reused for each test.
Thus the setup cost is incurred only once per test suite, and subsequent test execution
@@ -226,7 +227,7 @@ configure instances of your test classes via Dependency Injection. This provides
convenient mechanism for setting up test fixtures using preconfigured beans from your
application context. A strong benefit here is that you can reuse application contexts
across various testing scenarios (e.g., for configuring Spring-managed object graphs,
-transactional proxies, ++DataSource++s, etc.), thus avoiding the need to duplicate
+transactional proxies, ``DataSource``s, etc.), thus avoiding the need to duplicate
complex test fixture setup for individual test cases.
As an example, consider the scenario where we have a class, `HibernateTitleRepository`,
@@ -330,23 +331,22 @@ you can use in your unit and integration tests in conjunction with the TestConte
framework. Refer to the corresponding javadocs for further information, including
default attribute values, attribute aliases, and so on.
-* `@ContextConfiguration`
-
-+
-
-Defines class-level metadata that is used to determine how to load and configure an
-`ApplicationContext` for integration tests. Specifically, `@ContextConfiguration`
-declares the application context resource `locations` or the annotated `classes`
-that will be used to load the context.
+===== @BootstrapWith
+`@BootstrapWith` is a class-level annotation that is used to configure how the _Spring
+TestContext Framework_ is bootstrapped. Specifically, `@BootstrapWith` is used to specify
+a custom `TestContextBootstrapper`. Consult the <<testcontext-bootstrapping,Bootstrapping
+the TestContext framework>> section for further details.
-+
-
-Resource locations are typically XML configuration files located in the classpath;
-whereas, annotated classes are typically `@Configuration` classes. However, resource
-locations can also refer to files in the file system, and annotated classes can be
-component classes, etc.
+===== @ContextConfiguration
+`@ContextConfiguration` defines class-level metadata that is used to determine how to
+load and configure an `ApplicationContext` for integration tests. Specifically,
+`@ContextConfiguration` declares the application context resource `locations` or the
+annotated `classes` that will be used to load the context.
-+
+Resource locations are typically XML configuration files or Groovy scripts located in
+the classpath; whereas, annotated classes are typically `@Configuration` classes. However,
+resource locations can also refer to files and scripts in the file system, and annotated
+classes can be component classes, etc.
[source,java,indent=0]
[subs="verbatim,quotes"]
@@ -357,8 +357,6 @@ component classes, etc.
}
----
-+
-
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@@ -368,13 +366,9 @@ component classes, etc.
}
----
-+
-
As an alternative or in addition to declaring resource locations or annotated classes,
`@ContextConfiguration` may be used to declare `ApplicationContextInitializer` classes.
-+
-
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@@ -384,15 +378,11 @@ As an alternative or in addition to declaring resource locations or annotated cl
}
----
-+
-
`@ContextConfiguration` may optionally be used to declare the `ContextLoader` strategy
as well. Note, however, that you typically do not need to explicitly configure the
loader since the default loader supports either resource `locations` or annotated
`classes` as well as `initializers`.
-+
-
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@@ -402,8 +392,6 @@ loader since the default loader supports either resource `locations` or annotate
}
----
-+
-
[NOTE]
====
`@ContextConfiguration` provides support for __inheriting__ resource locations or
@@ -411,24 +399,18 @@ configuration classes as well as context initializers declared by superclasses b
default.
====
-+
-
See <<testcontext-ctx-management>> and the `@ContextConfiguration` javadocs for
further details.
-* `@WebAppConfiguration`
-
-+
-
-A class-level annotation that is used to declare that the `ApplicationContext` loaded
-for an integration test should be a `WebApplicationContext`. The mere presence of
-`@WebAppConfiguration` on a test class ensures that a `WebApplicationContext` will be
-loaded for the test, using the default value of `"file:src/main/webapp"` for the path to
-the root of the web application (i.e., the __resource base path__). The resource base
-path is used behind the scenes to create a `MockServletContext` which serves as the
-`ServletContext` for the test's `WebApplicationContext`.
-
-+
+===== @WebAppConfiguration
+`@WebAppConfiguration` is a class-level annotation that is used to declare that the
+`ApplicationContext` loaded for an integration test should be a `WebApplicationContext`.
+The mere presence of `@WebAppConfiguration` on a test class ensures that a
+`WebApplicationContext` will be loaded for the test, using the default value of
+`"file:src/main/webapp"` for the path to the root of the web application (i.e., the
+__resource base path__). The resource base path is used behind the scenes to create a
+`MockServletContext` which serves as the `ServletContext` for the test's
+`WebApplicationContext`.
[source,java,indent=0]
[subs="verbatim,quotes"]
@@ -440,14 +422,10 @@ path is used behind the scenes to create a `MockServletContext` which serves as
}
----
-+
-
To override the default, specify a different base resource path via the __implicit__
`value` attribute. Both `classpath:` and `file:` resource prefixes are supported. If no
resource prefix is supplied the path is assumed to be a file system resource.
-+
-
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@@ -458,26 +436,17 @@ resource prefix is supplied the path is assumed to be a file system resource.
}
----
-+
-
Note that `@WebAppConfiguration` must be used in conjunction with
`@ContextConfiguration`, either within a single test class or within a test class
hierarchy. See the `@WebAppConfiguration` javadocs for further details.
-+
-
-* `@ContextHierarchy`
-
-+
-
-A class-level annotation that is used to define a hierarchy of ++ApplicationContext++s
-for integration tests. `@ContextHierarchy` should be declared with a list of one or more
-`@ContextConfiguration` instances, each of which defines a level in the context
-hierarchy. The following examples demonstrate the use of `@ContextHierarchy` within a
-single test class; however, `@ContextHierarchy` can also be used within a test class
-hierarchy.
-
-+
+===== @ContextHierarchy
+`@ContextHierarchy` is a class-level annotation that is used to define a hierarchy of
+``ApplicationContext``s for integration tests. `@ContextHierarchy` should be declared
+with a list of one or more `@ContextConfiguration` instances, each of which defines a
+level in the context hierarchy. The following examples demonstrate the use of
+`@ContextHierarchy` within a single test class; however, `@ContextHierarchy` can also be
+used within a test class hierarchy.
[source,java,indent=0]
[subs="verbatim,quotes"]
@@ -491,8 +460,6 @@ hierarchy.
}
----
-+
-
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@@ -506,8 +473,6 @@ hierarchy.
}
----
-+
-
If you need to merge or override the configuration for a given level of the context
hierarchy within a test class hierarchy, you must explicitly name that level by
supplying the same value to the `name` attribute in `@ContextConfiguration` at each
@@ -515,14 +480,10 @@ corresponding level in the class hierarchy. See
<<testcontext-ctx-management-ctx-hierarchies>> and the `@ContextHierarchy` javadocs
for further examples.
-* `@ActiveProfiles`
-
-+
-
-A class-level annotation that is used to declare which __bean definition profiles__
-should be active when loading an `ApplicationContext` for test classes.
-
-+
+===== @ActiveProfiles
+`@ActiveProfiles` is a class-level annotation that is used to declare which __bean
+definition profiles__ should be active when loading an `ApplicationContext` for an
+integration test.
[source,java,indent=0]
[subs="verbatim,quotes"]
@@ -534,8 +495,6 @@ should be active when loading an `ApplicationContext` for test classes.
}
----
-+
-
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@@ -546,8 +505,6 @@ should be active when loading an `ApplicationContext` for test classes.
}
----
-+
-
[NOTE]
====
`@ActiveProfiles` provides support for __inheriting__ active bean definition profiles
@@ -557,20 +514,13 @@ definition profiles programmatically by implementing a custom
and registering it via the `resolver` attribute of `@ActiveProfiles`.
====
-+
-
See <<testcontext-ctx-management-env-profiles>> and the `@ActiveProfiles` javadocs
for examples and further details.
-* `@TestPropertySource`
-
-+
-
-A class-level annotation that is used to configure the locations of properties files and
-inlined properties to be added to the set of `PropertySources` in the `Environment` for
-an `ApplicationContext` loaded for an integration test.
-
-+
+===== @TestPropertySource
+`@TestPropertySource` is a class-level annotation that is used to configure the locations
+of properties files and inlined properties to be added to the set of `PropertySources` in
+the `Environment` for an `ApplicationContext` loaded for an integration test.
Test property sources have higher precedence than those loaded from the operating
system's environment or Java system properties as well as property sources added by the
@@ -579,12 +529,8 @@ sources can be used to selectively override properties defined in system and app
property sources. Furthermore, inlined properties have higher precedence than properties
loaded from resource locations.
-+
-
The following example demonstrates how to declare a properties file from the classpath.
-+
-
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@@ -595,12 +541,8 @@ The following example demonstrates how to declare a properties file from the cla
}
----
-+
-
The following example demonstrates how to declare _inlined_ properties.
-+
-
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@@ -611,32 +553,23 @@ The following example demonstrates how to declare _inlined_ properties.
}
----
-* `@DirtiesContext`
-
-+
-
-Indicates that the underlying Spring `ApplicationContext` has been __dirtied__ during
-the execution of a test (i.e., modified or corrupted in some manner -- for example, by
-changing the state of a singleton bean) and should be closed. When an application
-context is marked __dirty__, it is removed from the testing framework's cache and
-closed. As a consequence, the underlying Spring container will be rebuilt for any
+===== @DirtiesContext
+`@DirtiesContext` indicates that the underlying Spring `ApplicationContext` has been
+__dirtied__ during the execution of a test (i.e., modified or corrupted in some manner --
+for example, by changing the state of a singleton bean) and should be closed. When an
+application context is marked __dirty__, it is removed from the testing framework's cache
+and closed. As a consequence, the underlying Spring container will be rebuilt for any
subsequent test that requires a context with the same configuration metadata.
-+
-
`@DirtiesContext` can be used as both a class-level and method-level annotation within
the same class or class hierarchy. In such scenarios, the `ApplicationContext` is marked
as __dirty__ before or after any such annotated method as well as before or after the
current test class, depending on the configured `methodMode` and `classMode`.
-+
-
The following examples explain when the context would be dirtied for various
configuration scenarios:
-+
-
-** Before the current test class, when declared on a class with class mode set to
+* Before the current test class, when declared on a class with class mode set to
`BEFORE_CLASS`.
+
@@ -652,7 +585,7 @@ configuration scenarios:
+
-** After the current test class, when declared on a class with class mode set to
+* After the current test class, when declared on a class with class mode set to
`AFTER_CLASS` (i.e., the default class mode).
+
@@ -668,7 +601,7 @@ configuration scenarios:
+
-** Before each test method in the current test class, when declared on a class with class
+* Before each test method in the current test class, when declared on a class with class
mode set to `BEFORE_EACH_TEST_METHOD.`
+
@@ -684,7 +617,7 @@ mode set to `BEFORE_EACH_TEST_METHOD.`
+
-** After each test method in the current test class, when declared on a class with class
+* After each test method in the current test class, when declared on a class with class
mode set to `AFTER_EACH_TEST_METHOD.`
+
@@ -700,7 +633,7 @@ mode set to `AFTER_EACH_TEST_METHOD.`
+
-** Before the current test, when declared on a method with the method mode set to
+* Before the current test, when declared on a method with the method mode set to
`BEFORE_METHOD`.
+
@@ -717,7 +650,7 @@ mode set to `AFTER_EACH_TEST_METHOD.`
+
-** After the current test, when declared on a method with the method mode set to
+* After the current test, when declared on a method with the method mode set to
`AFTER_METHOD` (i.e., the default method mode).
+
@@ -732,20 +665,16 @@ mode set to `AFTER_EACH_TEST_METHOD.`
}
----
-+
-
If `@DirtiesContext` is used in a test whose context is configured as part of a context
hierarchy via `@ContextHierarchy`, the `hierarchyMode` flag can be used to control how
the context cache is cleared. By default an __exhaustive__ algorithm will be used that
clears the context cache including not only the current level but also all other context
hierarchies that share an ancestor context common to the current test; all
-++ApplicationContext++s that reside in a sub-hierarchy of the common ancestor context
+``ApplicationContext``s that reside in a sub-hierarchy of the common ancestor context
will be removed from the context cache and closed. If the __exhaustive__ algorithm is
overkill for a particular use case, the simpler __current level__ algorithm can be
specified instead, as seen below.
-+
-
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@@ -767,20 +696,14 @@ specified instead, as seen below.
}
----
-+
-
For further details regarding the `EXHAUSTIVE` and `CURRENT_LEVEL` algorithms see the
`DirtiesContext.HierarchyMode` javadocs.
-* `@TestExecutionListeners`
-
-+
-
-Defines class-level metadata for configuring which ++TestExecutionListener++s should be
-registered with the `TestContextManager`. Typically, `@TestExecutionListeners` is used
-in conjunction with `@ContextConfiguration`.
-
-+
+===== @TestExecutionListeners
+`@TestExecutionListeners` defines class-level metadata for configuring the
+`TestExecutionListener` implementations that should be registered with the
+`TestContextManager`. Typically, `@TestExecutionListeners` is used in conjunction with
+`@ContextConfiguration`.
[source,java,indent=0]
[subs="verbatim,quotes"]
@@ -792,23 +715,15 @@ in conjunction with `@ContextConfiguration`.
}
----
-+
-
-`@TestExecutionListeners` supports __inherited__ listeners by default. See the javadocs
+`@TestExecutionListeners` supports _inherited_ listeners by default. See the javadocs
for an example and further details.
-+
-
-* `@Commit`
-
-+
-
-Indicates that the transaction for a transactional test method should be __committed__
-after the test method has completed. `@Commit` can be used as a direct replacement for
-`@Rollback(false)` in order to more explicitly convey the intent of the code. Analogous to
-`@Rollback`, `@Commit` may also be declared as a class-level or method-level annotation.
-
-+
+===== @Commit
+`@Commit` indicates that the transaction for a transactional test method should be
+__committed__ after the test method has completed. `@Commit` can be used as a direct
+replacement for `@Rollback(false)` in order to more explicitly convey the intent of the
+code. Analogous to `@Rollback`, `@Commit` may also be declared as a class-level or
+method-level annotation.
[source,java,indent=0]
[subs="verbatim,quotes"]
@@ -820,25 +735,18 @@ after the test method has completed. `@Commit` can be used as a direct replaceme
}
----
-* `@Rollback`
-
-+
-
-Indicates whether the transaction for a transactional test method should be __rolled
-back__ after the test method has completed. If `true`, the transaction is rolled back;
-otherwise, the transaction is committed (see also `@Commit`). Rollback semantics for
-integration tests in the Spring TestContext Framework default to `true` even if
+===== @Rollback
+`@Rollback` indicates whether the transaction for a transactional test method should be
+__rolled back__ after the test method has completed. If `true`, the transaction is rolled
+back; otherwise, the transaction is committed (see also `@Commit`). Rollback semantics
+for integration tests in the Spring TestContext Framework default to `true` even if
`@Rollback` is not explicitly declared.
-+
-
When declared as a class-level annotation, `@Rollback` defines the default rollback
semantics for all test methods within the test class hierarchy. When declared as a
method-level annotation, `@Rollback` defines rollback semantics for the specific test
method, potentially overriding class-level `@Rollback` or `@Commit` semantics.
-+
-
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@@ -849,52 +757,41 @@ method, potentially overriding class-level `@Rollback` or `@Commit` semantics.
}
----
-* `@BeforeTransaction`
-
-+
-
-Indicates that the annotated `public void` method should be executed __before__ a
-transaction is started for test methods configured to run within a transaction via the
-`@Transactional` annotation.
-
-+
+===== @BeforeTransaction
+`@BeforeTransaction` indicates that the annotated `void` method should be executed
+__before__ a transaction is started for test methods configured to run within a
+transaction via Spring's `@Transactional` annotation. As of Spring Framework 4.3,
+`@BeforeTransaction` methods are not required to be `public` and may be declared on Java
+8 based interface default methods.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
**@BeforeTransaction**
- public void beforeTransaction() {
+ void beforeTransaction() {
// logic to be executed before a transaction is started
}
----
-* `@AfterTransaction`
-
-+
-
-Indicates that the annotated `public void` method should be executed __after__ a
-transaction has ended for test methods configured to run within a transaction via the
-`@Transactional` annotation.
-
-+
+===== @AfterTransaction
+`@AfterTransaction` indicates that the annotated `void` method should be executed
+__after__ a transaction is ended for test methods configured to run within a transaction
+via Spring's `@Transactional` annotation. As of Spring Framework 4.3, `@AfterTransaction`
+methods are not required to be `public` and may be declared on Java 8 based interface
+default methods.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
**@AfterTransaction**
- public void afterTransaction() {
+ void afterTransaction() {
// logic to be executed after a transaction has ended
}
----
-* `@Sql`
-
-+
-
-Used to annotate a test class or test method to configure SQL scripts to be executed
-against a given database during integration tests.
-
-+
+===== @Sql
+`@Sql` is used to annotate a test class or test method to configure SQL scripts to be
+executed against a given database during integration tests.
[source,java,indent=0]
[subs="verbatim,quotes"]
@@ -906,18 +803,11 @@ against a given database during integration tests.
}
----
-+
-
See <<testcontext-executing-sql-declaratively>> for further details.
-* `@SqlConfig`
-
-+
-
-Defines metadata that is used to determine how to parse and execute SQL scripts
-configured via the `@Sql` annotation.
-
-+
+===== @SqlConfig
+`@SqlConfig` defines metadata that is used to determine how to parse and execute SQL
+scripts configured via the `@Sql` annotation.
[source,java,indent=0]
[subs="verbatim,quotes"]
@@ -932,16 +822,12 @@ configured via the `@Sql` annotation.
}
----
-* `@SqlGroup`
-
-+
-
-A container annotation that aggregates several `@Sql` annotations. Can be used natively,
-declaring several nested `@Sql` annotations. Can also be used in conjunction with Java
-8's support for repeatable annotations, where `@Sql` can simply be declared several times
-on the same class or method, implicitly generating this container annotation.
-
-+
+===== @SqlGroup
+`@SqlGroup` is a container annotation that aggregates several `@Sql` annotations.
+`@SqlGroup` can be used natively, declaring several nested `@Sql` annotations, or it can
+be used in conjunction with Java 8's support for repeatable annotations, where `@Sql` can
+simply be declared several times on the same class or method, implicitly generating this
+container annotation.
[source,java,indent=0]
[subs="verbatim,quotes"]
@@ -982,7 +868,7 @@ however, these lifecycle annotations have limited usage within an actual test cl
If a method within a test class is annotated with `@PostConstruct`, that method will be
executed before any __before__ methods of the underlying test framework (e.g., methods
-annotated with JUnit's `@Before`), and that will apply for every test method in the test
+annotated with JUnit 4's `@Before`), and that will apply for every test method in the test
class. On the other hand, if a method within a test class is annotated with
`@PreDestroy`, that method will __never__ be executed. Within a test class it is
therefore recommended to use test lifecycle callbacks from the underlying test framework
@@ -991,32 +877,25 @@ instead of `@PostConstruct` and `@PreDestroy`.
[[integration-testing-annotations-junit]]
-==== Spring JUnit Testing Annotations
+==== Spring JUnit 4 Testing Annotations
The following annotations are __only__ supported when used in conjunction with the
-<<testcontext-junit4-runner,SpringJUnit4ClassRunner>>,
-<<testcontext-junit4-rules,Spring's JUnit rules>>, or
-<<testcontext-support-classes-junit4,Spring's JUnit support classes>>.
+<<testcontext-junit4-runner,SpringRunner>>, <<testcontext-junit4-rules,Spring's JUnit
+rules>>, or <<testcontext-support-classes-junit4,Spring's JUnit 4 support classes>>.
-* `@IfProfileValue`
-
-+
-
-Indicates that the annotated test is enabled for a specific testing environment. If the
-configured `ProfileValueSource` returns a matching `value` for the provided `name`, the
-test is enabled. Otherwise, the test will be disabled and effectively _ignored_.
-
-+
+===== @IfProfileValue
+`@IfProfileValue` indicates that the annotated test is enabled for a specific testing
+environment. If the configured `ProfileValueSource` returns a matching `value` for the
+provided `name`, the test is enabled. Otherwise, the test will be disabled and
+effectively _ignored_.
`@IfProfileValue` can be applied at the class level, the method level, or both.
Class-level usage of `@IfProfileValue` takes precedence over method-level usage for any
methods within that class or its subclasses. Specifically, a test is enabled if it is
enabled both at the class level _and_ at the method level; the absence of
`@IfProfileValue` means the test is implicitly enabled. This is analogous to the
-semantics of JUnit's `@Ignore` annotation, except that the presence of `@Ignore` always
+semantics of JUnit 4's `@Ignore` annotation, except that the presence of `@Ignore` always
disables a test.
-+
-
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@@ -1027,14 +906,10 @@ disables a test.
}
----
-+
-
Alternatively, you can configure `@IfProfileValue` with a list of `values` (with __OR__
-semantics) to achieve TestNG-like support for __test groups__ in a JUnit environment.
+semantics) to achieve TestNG-like support for __test groups__ in a JUnit 4 environment.
Consider the following example:
-+
-
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@@ -1045,18 +920,11 @@ Consider the following example:
}
----
-+
-
-* `@ProfileValueSourceConfiguration`
-
-+
-
-Class-level annotation that specifies what type of `ProfileValueSource` to use when
-retrieving __profile values__ configured through the `@IfProfileValue` annotation. If
-`@ProfileValueSourceConfiguration` is not declared for a test,
-`SystemProfileValueSource` is used by default.
-
-+
+===== @ProfileValueSourceConfiguration
+`@ProfileValueSourceConfiguration` is a class-level annotation that specifies what type
+of `ProfileValueSource` to use when retrieving __profile values__ configured through the
+`@IfProfileValue` annotation. If `@ProfileValueSourceConfiguration` is not declared for a
+test, `SystemProfileValueSource` is used by default.
[source,java,indent=0]
[subs="verbatim,quotes"]
@@ -1067,21 +935,14 @@ retrieving __profile values__ configured through the `@IfProfileValue` annotatio
}
----
-* `@Timed`
-
-+
-
-Indicates that the annotated test method must finish execution in a specified time
-period (in milliseconds). If the text execution time exceeds the specified time period,
-the test fails.
-
-+
+===== @Timed
+`@Timed` indicates that the annotated test method must finish execution in a specified
+time period (in milliseconds). If the text execution time exceeds the specified time
+period, the test fails.
The time period includes execution of the test method itself, any repetitions of the
test (see `@Repeat`), as well as any __set up__ or __tear down__ of the test fixture.
-+
-
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@@ -1091,29 +952,20 @@ test (see `@Repeat`), as well as any __set up__ or __tear down__ of the test fix
}
----
-+
-
-Spring's `@Timed` annotation has different semantics than JUnit's `@Test(timeout=...)`
-support. Specifically, due to the manner in which JUnit handles test execution timeouts
+Spring's `@Timed` annotation has different semantics than JUnit 4's `@Test(timeout=...)`
+support. Specifically, due to the manner in which JUnit 4 handles test execution timeouts
(that is, by executing the test method in a separate `Thread`), `@Test(timeout=...)`
preemptively fails the test if the test takes too long. Spring's `@Timed`, on the other
hand, does not preemptively fail the test but rather waits for the test to complete
before failing.
-* `@Repeat`
-
-+
-
-Indicates that the annotated test method must be executed repeatedly. The number of
-times that the test method is to be executed is specified in the annotation.
-
-+
+===== @Repeat
+`@Repeat` indicates that the annotated test method must be executed repeatedly. The
+number of times that the test method is to be executed is specified in the annotation.
The scope of execution to be repeated includes execution of the test method itself as
well as any __set up__ or __tear down__ of the test fixture.
-+
-
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@@ -1127,13 +979,14 @@ well as any __set up__ or __tear down__ of the test fixture.
[[integration-testing-annotations-meta]]
==== Meta-Annotation Support for Testing
-As of Spring Framework 4.0, it is possible to use test-related annotations as
-<<beans-meta-annotations,meta-annotations>> in order to create custom _composed annotations_
-and reduce configuration duplication across a test suite.
+It is possible to use most test-related annotations as
+<<beans-meta-annotations,meta-annotations>> in order to create custom _composed
+annotations_ and reduce configuration duplication across a test suite.
Each of the following may be used as meta-annotations in conjunction with the
<<testcontext-framework,TestContext framework>>.
+* `@BootstrapWith`
* `@ContextConfiguration`
* `@ContextHierarchy`
* `@ActiveProfiles`
@@ -1144,6 +997,7 @@ Each of the following may be used as meta-annotations in conjunction with the
* `@Transactional`
* `@BeforeTransaction`
* `@AfterTransaction`
+* `@Commit`
* `@Rollback`
* `@Sql`
* `@SqlConfig`
@@ -1154,18 +1008,18 @@ Each of the following may be used as meta-annotations in conjunction with the
* `@ProfileValueSourceConfiguration`
For example, if we discover that we are repeating the following configuration
-across our JUnit-based test suite...
+across our JUnit 4 based test suite...
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public class OrderRepositoryTests { }
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
@@ -1192,11 +1046,11 @@ configuration of individual test classes as follows:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
@TransactionalDevTest
public class OrderRepositoryTests { }
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
@TransactionalDevTest
public class UserRepositoryTests { }
----
@@ -1214,14 +1068,14 @@ configuration__ with reasonable defaults that can be overridden through annotati
configuration.
In addition to generic testing infrastructure, the TestContext framework provides
-explicit support for JUnit and TestNG in the form of `abstract` support classes. For
-JUnit, Spring also provides a custom JUnit `Runner` and custom JUnit `Rules` that allow
+explicit support for JUnit 4 and TestNG in the form of `abstract` support classes. For
+JUnit 4, Spring also provides a custom JUnit `Runner` and custom JUnit `Rules` that allow
one to write so-called __POJO test classes__. POJO test classes are not required to
extend a particular class hierarchy.
The following section provides an overview of the internals of the TestContext
-framework. If you are only interested in using the framework and not necessarily
-interested in extending it with your own custom listeners or custom loaders, feel free
+framework. If you are only interested in _using_ the framework and not necessarily
+interested in _extending_ it with your own custom listeners or custom loaders, feel free
to go directly to the configuration (<<testcontext-ctx-management,context management>>,
<<testcontext-fixture-di,dependency injection>>, <<testcontext-tx,transaction
management>>), <<testcontext-support-classes,support classes>>, and
@@ -1230,89 +1084,106 @@ management>>), <<testcontext-support-classes,support classes>>, and
[[testcontext-key-abstractions]]
==== Key abstractions
-The core of the framework consists of the `TestContext` and `TestContextManager` classes
-and the `TestExecutionListener`, `ContextLoader`, and `SmartContextLoader` interfaces. A
-`TestContextManager` is created on a per-test basis (e.g., for the execution of a single
-test method in JUnit). The `TestContextManager` in turn manages a `TestContext` that
-holds the context of the current test. The `TestContextManager` also updates the state
-of the `TestContext` as the test progresses and delegates to ++TestExecutionListener++s,
-which instrument the actual test execution by providing dependency injection, managing
-transactions, and so on. A `ContextLoader` (or `SmartContextLoader`) is responsible for
-loading an `ApplicationContext` for a given test class. Consult the javadocs and the
-Spring test suite for further information and examples of various implementations.
-
-* `TestContext`: Encapsulates the context in which a test is executed, agnostic of the
- actual testing framework in use, and provides context management and caching support
- for the test instance for which it is responsible. The `TestContext` also delegates to
- a `ContextLoader` (or `SmartContextLoader`) to load an `ApplicationContext` if
- requested.
-* `TestContextManager`: The main entry point into the __Spring TestContext Framework__,
- which manages a single `TestContext` and signals events to all registered
- ++TestExecutionListener++s at well-defined test execution points:
-** prior to any __before class methods__ of a particular testing framework
-** test instance preparation
-** prior to any __before methods__ of a particular testing framework
-** after any __after methods__ of a particular testing framework
-** after any __after class methods__ of a particular testing framework
-* `TestExecutionListener`: Defines a __listener__ API for reacting to test execution
- events published by the `TestContextManager` with which the listener is registered. See
- <<testcontext-tel-config>>.
-* `ContextLoader`: Strategy interface introduced in Spring 2.5 for loading an
- `ApplicationContext` for an integration test managed by the Spring TestContext
- Framework.
-
-+
-
+The core of the framework consists of the `TestContextManager` class and the
+`TestContext`, `TestExecutionListener`, and `SmartContextLoader` interfaces. A
+`TestContextManager` is created per test class (e.g., for the execution of all test
+methods within a single test class in JUnit 4). The `TestContextManager` in turn manages a
+`TestContext` that holds the context of the current test. The `TestContextManager` also
+updates the state of the `TestContext` as the test progresses and delegates to
+`TestExecutionListener` implementations, which instrument the actual test execution by
+providing dependency injection, managing transactions, and so on. A `SmartContextLoader`
+is responsible for loading an `ApplicationContext` for a given test class. Consult the
+javadocs and the Spring test suite for further information and examples of various
+implementations.
+
+===== TestContext
+`TestContext` encapsulates the context in which a test is executed, agnostic of the
+actual testing framework in use, and provides context management and caching support for
+the test instance for which it is responsible. The `TestContext` also delegates to a
+`SmartContextLoader` to load an `ApplicationContext` if requested.
+
+===== TestContextManager
+`TestContextManager` is the main entry point into the __Spring TestContext Framework__,
+which manages a single `TestContext` and signals events to each registered
+`TestExecutionListener` at well-defined test execution points:
+
+* prior to any __before class__ or __before all__ methods of a particular testing framework
+* test instance post-processing
+* prior to any __before__ or __before each__ methods of a particular testing framework
+* after any __after__ or __after each__ methods of a particular testing framework
+* after any __after class__ or __after all__ methods of a particular testing framework
+
+===== TestExecutionListener
+`TestExecutionListener` defines the API for reacting to test execution events published
+by the `TestContextManager` with which the listener is registered. See
+<<testcontext-tel-config>>.
+
+===== Context Loaders
+`ContextLoader` is a strategy interface that was introduced in Spring 2.5 for loading an
+`ApplicationContext` for an integration test managed by the Spring TestContext Framework.
Implement `SmartContextLoader` instead of this interface in order to provide support for
annotated classes, active bean definition profiles, test property sources, context
-hierarchies, and ++WebApplicationContext++s.
-
-* `SmartContextLoader`: Extension of the `ContextLoader` interface introduced in Spring
- 3.1.
+hierarchies, and `WebApplicationContext` support.
-+
-
-The `SmartContextLoader` SPI supersedes the `ContextLoader` SPI that was introduced in
-Spring 2.5. Specifically, a `SmartContextLoader` can choose to process resource
-`locations`, annotated `classes`, or context `initializers`. Furthermore, a
+`SmartContextLoader` is an extension of the `ContextLoader` interface introduced in
+Spring 3.1. The `SmartContextLoader` SPI supersedes the `ContextLoader` SPI that was
+introduced in Spring 2.5. Specifically, a `SmartContextLoader` can choose to process
+resource `locations`, annotated `classes`, or context `initializers`. Furthermore, a
`SmartContextLoader` can set active bean definition profiles and test property sources in
the context that it loads.
-+
-
Spring provides the following implementations:
-+
-
-** `DelegatingSmartContextLoader`: one of two default loaders which delegates internally
+* `DelegatingSmartContextLoader`: one of two default loaders which delegates internally
to an `AnnotationConfigContextLoader`, a `GenericXmlContextLoader`, or a
`GenericGroovyXmlContextLoader` depending either on the configuration declared for the
test class or on the presence of default locations or default configuration classes.
Groovy support is only enabled if Groovy is on the classpath.
-** `WebDelegatingSmartContextLoader`: one of two default loaders which delegates
+* `WebDelegatingSmartContextLoader`: one of two default loaders which delegates
internally to an `AnnotationConfigWebContextLoader`, a `GenericXmlWebContextLoader`, or a
`GenericGroovyXmlWebContextLoader` depending either on the configuration declared for the
test class or on the presence of default locations or default configuration classes. A
web `ContextLoader` will only be used if `@WebAppConfiguration` is present on the test
class. Groovy support is only enabled if Groovy is on the classpath.
-** `AnnotationConfigContextLoader`: loads a standard `ApplicationContext` from
+* `AnnotationConfigContextLoader`: loads a standard `ApplicationContext` from
__annotated classes__.
-** `AnnotationConfigWebContextLoader`: loads a `WebApplicationContext` from __annotated
+* `AnnotationConfigWebContextLoader`: loads a `WebApplicationContext` from __annotated
classes__.
-** `GenericGroovyXmlContextLoader`: loads a standard `ApplicationContext` from __resource
+* `GenericGroovyXmlContextLoader`: loads a standard `ApplicationContext` from __resource
locations__ that are either Groovy scripts or XML configuration files.
-** `GenericGroovyXmlWebContextLoader`: loads a `WebApplicationContext` from __resource
+* `GenericGroovyXmlWebContextLoader`: loads a `WebApplicationContext` from __resource
locations__ that are either Groovy scripts or XML configuration files.
-** `GenericXmlContextLoader`: loads a standard `ApplicationContext` from XML __resource
+* `GenericXmlContextLoader`: loads a standard `ApplicationContext` from XML __resource
locations__.
-** `GenericXmlWebContextLoader`: loads a `WebApplicationContext` from XML __resource
+* `GenericXmlWebContextLoader`: loads a `WebApplicationContext` from XML __resource
locations__.
-** `GenericPropertiesContextLoader`: loads a standard `ApplicationContext` from Java
+* `GenericPropertiesContextLoader`: loads a standard `ApplicationContext` from Java
Properties files.
-The following sections explain how to configure the TestContext framework through
-annotations and provide working examples of how to write unit and integration tests with
-the framework.
+[[testcontext-bootstrapping]]
+==== Bootstrapping the TestContext framework
+
+The default configuration for the internals of the Spring TestContext Framework is
+sufficient for all common use cases. However, there are times when a development team or
+third party framework would like to change the default `ContextLoader`, implement a
+custom `TestContext` or `ContextCache`, augment the default sets of
+`ContextCustomizerFactory` and `TestExecutionListener` implementations, etc. For such low
+level control over how the TestContext framework operates, Spring provides a
+bootstrapping strategy.
+
+`TestContextBootstrapper` defines the SPI for _bootstrapping_ the TestContext framework.
+A `TestContextBootstrapper` is used by the `TestContextManager` to load the
+`TestExecutionListener` implementations for the current test and to build the
+`TestContext` that it manages. A custom bootstrapping strategy can be configured for a
+test class (or test class hierarchy) via `@BootstrapWith`, either directly or as a
+meta-annotation. If a bootstrapper is not explicitly configured via `@BootstrapWith`,
+either the `DefaultTestContextBootstrapper` or the `WebTestContextBootstrapper` will be
+used, depending on the presence of `@WebAppConfiguration`.
+
+Since the `TestContextBootstrapper` SPI is likely to change in the future in order to
+accommodate new requirements, implementers are strongly encouraged not to implement this
+interface directly but rather to extend `AbstractTestContextBootstrapper` or one of its
+concrete subclasses instead.
[[testcontext-tel-config]]
==== TestExecutionListener configuration
@@ -1322,9 +1193,12 @@ by default, exactly in this order.
* `ServletTestExecutionListener`: configures Servlet API mocks for a
`WebApplicationContext`
+* `DirtiesContextBeforeModesTestExecutionListener`: handles the `@DirtiesContext` annotation for
+ _before_ modes
* `DependencyInjectionTestExecutionListener`: provides dependency injection for the test
instance
-* `DirtiesContextTestExecutionListener`: handles the `@DirtiesContext` annotation
+* `DirtiesContextTestExecutionListener`: handles the `@DirtiesContext` annotation for
+ _after_ modes
* `TransactionalTestExecutionListener`: provides transactional test execution with
default rollback semantics
* `SqlScriptsTestExecutionListener`: executes SQL scripts configured via the `@Sql`
@@ -1333,7 +1207,7 @@ by default, exactly in this order.
[[testcontext-tel-config-registering-tels]]
===== Registering custom TestExecutionListeners
-Custom ++TestExecutionListener++s can be registered for a test class and its subclasses
+Custom ``TestExecutionListener``s can be registered for a test class and its subclasses
via the `@TestExecutionListeners` annotation. See
<<integration-testing-annotations,annotation support>> and the javadocs for
`@TestExecutionListeners` for details and examples.
@@ -1341,31 +1215,31 @@ via the `@TestExecutionListeners` annotation. See
[[testcontext-tel-config-automatic-discovery]]
===== Automatic discovery of default TestExecutionListeners
-Registering custom ++TestExecutionListener++s via `@TestExecutionListeners` is suitable
+Registering custom ``TestExecutionListener``s via `@TestExecutionListeners` is suitable
for custom listeners that are used in limited testing scenarios; however, it can become
-cumbersome if a custom listener needs to be used across a test suite. To address this
-issue, Spring Framework 4.1 supports automatic discovery of _default_
+cumbersome if a custom listener needs to be used across a test suite. Since Spring
+Framework 4.1, this issue is addressed via support for automatic discovery of _default_
`TestExecutionListener` implementations via the `SpringFactoriesLoader` mechanism.
Specifically, the `spring-test` module declares all core default
-++TestExecutionListener++s under the
+``TestExecutionListener``s under the
`org.springframework.test.context.TestExecutionListener` key in its
`META-INF/spring.factories` properties file. Third-party frameworks and developers can
-contribute their own ++TestExecutionListener++s to the list of default listeners in the
+contribute their own ``TestExecutionListener``s to the list of default listeners in the
same manner via their own `META-INF/spring.factories` properties file.
[[testcontext-tel-config-ordering]]
===== Ordering TestExecutionListeners
-When the TestContext framework discovers default ++TestExecutionListener++s via the
+When the TestContext framework discovers default ``TestExecutionListener``s via the
aforementioned `SpringFactoriesLoader` mechanism, the instantiated listeners are sorted
using Spring's `AnnotationAwareOrderComparator` which honors Spring's `Ordered` interface
and `@Order` annotation for ordering. `AbstractTestExecutionListener` and all default
-++TestExecutionListener++s provided by Spring implement `Ordered` with appropriate
+``TestExecutionListener``s provided by Spring implement `Ordered` with appropriate
values. Third-party frameworks and developers should therefore make sure that their
-_default_ ++TestExecutionListener++s are registered in the proper order by implementing
+_default_ ``TestExecutionListener``s are registered in the proper order by implementing
`Ordered` or declaring `@Order`. Consult the javadocs for the `getOrder()` methods of the
-core default ++TestExecutionListener++s for details on what values are assigned to each
+core default ``TestExecutionListener``s for details on what values are assigned to each
core listener.
[[testcontext-tel-config-merging]]
@@ -1383,6 +1257,7 @@ any custom listeners. The following listing demonstrates this style of configura
@TestExecutionListeners({
MyCustomTestExecutionListener.class,
ServletTestExecutionListener.class,
+ DirtiesContextBeforeModesTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class,
@@ -1396,8 +1271,9 @@ any custom listeners. The following listing demonstrates this style of configura
The challenge with this approach is that it requires that the developer know exactly
which listeners are registered by default. Moreover, the set of default listeners can
change from release to release -- for example, `SqlScriptsTestExecutionListener` was
-introduced in Spring Framework 4.1. Furthermore, third-party frameworks like Spring
-Security register their own default ++TestExecutionListener++s via the aforementioned
+introduced in Spring Framework 4.1, and `DirtiesContextBeforeModesTestExecutionListener`
+was introduced in Spring Framework 4.2. Furthermore, third-party frameworks like Spring
+Security register their own default ``TestExecutionListener``s via the aforementioned
<<testcontext-tel-config-automatic-discovery, automatic discovery mechanism>>.
To avoid having to be aware of and re-declare **all** _default_ listeners, the
@@ -1453,7 +1329,7 @@ on either a field or setter method. For example:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
@ContextConfiguration
public class MyTest {
@@ -1470,7 +1346,7 @@ the web application context into your test as follows:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
**@WebAppConfiguration**
@ContextConfiguration
public class MyWebAppTest {
@@ -1496,9 +1372,10 @@ addition to context resource `locations` and annotated `classes`, an application
can also be configured via application context `initializers`.
The following sections explain how to configure an `ApplicationContext` via XML
-configuration files, annotated classes (typically `@Configuration` classes), or context
-initializers using Spring's `@ContextConfiguration` annotation. Alternatively, you can
-implement and configure your own custom `SmartContextLoader` for advanced use cases.
+configuration files, Groovy scripts, annotated classes (typically `@Configuration`
+classes), or context initializers using Spring's `@ContextConfiguration` annotation.
+Alternatively, you can implement and configure your own custom `SmartContextLoader` for
+advanced use cases.
[[testcontext-ctx-management-xml]]
===== Context configuration with XML resources
@@ -1515,7 +1392,7 @@ prefixed with `classpath:`, `file:`, `http:`, etc.) will be used __as is__.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
// ApplicationContext will be loaded from "/app-config.xml" and
// "/test-config.xml" in the root of the classpath
**@ContextConfiguration(locations={"/app-config.xml", "/test-config.xml"})**
@@ -1533,7 +1410,7 @@ demonstrated in the following example.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
**@ContextConfiguration({"/app-config.xml", "/test-config.xml"})**
public class MyTest {
// class body...
@@ -1552,7 +1429,7 @@ a default location based on the name of the test class. If your class is named
----
package com.example;
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTest-context.xml"
**@ContextConfiguration**
@@ -1582,7 +1459,7 @@ TestContext Framework is enabled automatically if Groovy is on the classpath.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
// ApplicationContext will be loaded from "/AppConfig.groovy" and
// "/TestConfig.groovy" in the root of the classpath
**@ContextConfiguration({"/AppConfig.groovy", "/TestConfig.Groovy"})**
@@ -1603,7 +1480,7 @@ detect a default location based on the name of the test class. If your class is
----
package com.example;
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTestContext.groovy"
**@ContextConfiguration**
@@ -1625,7 +1502,7 @@ The following listing demonstrates how to combine both in an integration test.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
// ApplicationContext will be loaded from
// "/app-config.xml" and "/TestConfig.groovy"
@ContextConfiguration({ "/app-config.xml", "/TestConfig.groovy" })
@@ -1645,7 +1522,7 @@ To load an `ApplicationContext` for your tests using __annotated classes__ (see
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
// ApplicationContext will be loaded from AppConfig and TestConfig
**@ContextConfiguration(classes = {AppConfig.class, TestConfig.class})**
public class MyTest {
@@ -1682,7 +1559,7 @@ configuration class is arbitrary. In addition, a test class can contain more tha
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
// ApplicationContext will be loaded from the
// static nested Config class
**@ContextConfiguration**
@@ -1738,11 +1615,11 @@ classes to configure your tests, you will have to pick one as the __entry point_
that one will have to include or import the other. For example, in XML or Groovy scripts
you can include `@Configuration` classes via component scanning or define them as normal
Spring beans; whereas, in a `@Configuration` class you can use `@ImportResource` to
-import XML configuration files. Note that this behavior is semantically equivalent to how
-you configure your application in production: in production configuration you will define
-either a set of XML or Groovy resource locations or a set of `@Configuration` classes
-that your production `ApplicationContext` will be loaded from, but you still have the
-freedom to include or import the other type of configuration.
+import XML configuration files or Groovy scripts. Note that this behavior is semantically
+equivalent to how you configure your application in production: in production
+configuration you will define either a set of XML or Groovy resource locations or a set
+of `@Configuration` classes that your production `ApplicationContext` will be loaded
+from, but you still have the freedom to include or import the other type of configuration.
[[testcontext-ctx-management-initializers]]
===== Context configuration with context initializers
@@ -1755,13 +1632,13 @@ the concrete `ConfigurableApplicationContext` type supported by each declared
initializer must be compatible with the type of `ApplicationContext` created by the
`SmartContextLoader` in use (i.e., typically a `GenericApplicationContext`).
Furthermore, the order in which the initializers are invoked depends on whether they
-implement Spring's `Ordered` interface, are annotated with Spring's `@Order` or the
-standard `@Priority` annotation.
+implement Spring's `Ordered` interface or are annotated with Spring's `@Order` annotation
+or the standard `@Priority` annotation.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
// ApplicationContext will be loaded from TestConfig
// and initialized by TestAppCtxInitializer
**@ContextConfiguration(
@@ -1772,8 +1649,8 @@ standard `@Priority` annotation.
}
----
-It is also possible to omit the declaration of XML configuration files or annotated
-classes in `@ContextConfiguration` entirely and instead declare only
+It is also possible to omit the declaration of XML configuration files, Groovy scripts,
+or annotated classes in `@ContextConfiguration` entirely and instead declare only
`ApplicationContextInitializer` classes which are then responsible for registering beans
in the context -- for example, by programmatically loading bean definitions from XML
files or configuration classes.
@@ -1781,7 +1658,7 @@ files or configuration classes.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
// ApplicationContext will be initialized by EntireAppInitializer
// which presumably registers beans in the context
**@ContextConfiguration(initializers = EntireAppInitializer.class)**
@@ -1816,7 +1693,7 @@ therefore __override__ (i.e., replace) those defined in __"base-config.xml"__.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
// ApplicationContext will be loaded from "/base-config.xml"
// in the root of the classpath
**@ContextConfiguration("/base-config.xml")**
@@ -1840,7 +1717,7 @@ override (i.e., replace) those defined in `BaseConfig`.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
// ApplicationContext will be loaded from BaseConfig
**@ContextConfiguration(classes = BaseConfig.class)**
public class BaseTest {
@@ -1857,13 +1734,13 @@ override (i.e., replace) those defined in `BaseConfig`.
In the following example that uses context initializers, the `ApplicationContext` for
`ExtendedTest` will be initialized using `BaseInitializer` __and__
`ExtendedInitializer`. Note, however, that the order in which the initializers are
-invoked depends on whether they implement Spring's `Ordered` interface, are annotated
-with Spring's `@Order` or the standard `@Priority` annotation.
+invoked depends on whether they implement Spring's `Ordered` interface or are annotated
+with Spring's `@Order` annotation or the standard `@Priority` annotation.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
// ApplicationContext will be initialized by BaseInitializer
**@ContextConfiguration(initializers = BaseInitializer.class)**
public class BaseTest {
@@ -1948,7 +1825,7 @@ Let's take a look at some examples with XML configuration and `@Configuration` c
----
package com.bank.service;
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
// ApplicationContext will be loaded from "classpath:/app-config.xml"
@ContextConfiguration("/app-config.xml")
@ActiveProfiles("dev")
@@ -2067,7 +1944,7 @@ integration test but using `@Configuration` classes instead of XML.
----
package com.bank.service;
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
@ContextConfiguration(classes = {
TransferServiceConfig.class,
StandaloneDataConfig.class,
@@ -2115,7 +1992,7 @@ annotations) has been moved to an abstract superclass, `AbstractIntegrationTest`
----
package com.bank.service;
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
@ContextConfiguration(classes = {
TransferServiceConfig.class,
StandaloneDataConfig.class,
@@ -2406,7 +2283,7 @@ a `WebApplicationContext`.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
// defaults to "file:src/main/webapp"
@WebAppConfiguration
@@ -2433,7 +2310,7 @@ nested `@Configuration` classes).
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
// file system resource
@WebAppConfiguration("webapp")
@@ -2456,7 +2333,7 @@ whereas, `@ContextConfiguration` resource locations are classpath based.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
// classpath resource
@WebAppConfiguration("classpath:test-web-resources")
@@ -2542,6 +2419,7 @@ the context cache key:
* `locations` __(from @ContextConfiguration)__
* `classes` __(from @ContextConfiguration)__
* `contextInitializerClasses` __(from @ContextConfiguration)__
+* `contextCustomizers` __(from ContextCustomizerFactory)__
* `contextLoader` __(from @ContextConfiguration)__
* `parent` __(from @ContextHierarchy)__
* `activeProfiles` __(from @ActiveProfiles)__
@@ -2580,6 +2458,13 @@ framework will not be able to cache application contexts between test classes an
build process will run significantly slower as a result.
====
+Since Spring Framework 4.3, the size of the context cache is bounded with a default
+maximum size of 32. Whenever the maximum size is reached, a _least recently used_ (LRU)
+eviction policy is used to evict and close stale contexts. The maximum size can be
+configured from the command line or a build script by setting a JVM system property named
+`spring.test.context.cache.maxSize`. As an alternative, the same property can be set
+programmatically via the `SpringProperties` API.
+
Since having a large number of application contexts loaded within a given test suite can
cause the suite to take an unnecessarily long time to execute, it is often beneficial to
know exactly how many contexts have been loaded and cached. To view the statistics for
@@ -2592,8 +2477,8 @@ you can annotate your test class or test method with `@DirtiesContext` (see the
discussion of `@DirtiesContext` in <<integration-testing-annotations-spring>>). This
instructs Spring to remove the context from the cache and rebuild the application
context before executing the next test. Note that support for the `@DirtiesContext`
-annotation is provided by the `DirtiesContextTestExecutionListener` which is enabled by
-default.
+annotation is provided by the `DirtiesContextBeforeModesTestExecutionListener` and the
+`DirtiesContextTestExecutionListener` which are enabled by default.
[[testcontext-ctx-management-ctx-hierarchies]]
@@ -2601,7 +2486,7 @@ default.
When writing integration tests that rely on a loaded Spring `ApplicationContext`, it is
often sufficient to test against a single context; however, there are times when it is
-beneficial or even necessary to test against a hierarchy of ++ApplicationContext++s. For
+beneficial or even necessary to test against a hierarchy of ``ApplicationContext``s. For
example, if you are developing a Spring MVC web application you will typically have a
root `WebApplicationContext` loaded via Spring's `ContextLoaderListener` and a child
`WebApplicationContext` loaded via Spring's `DispatcherServlet`. This results in a
@@ -2611,7 +2496,7 @@ components. Another use case can be found in Spring Batch applications where you
have a parent context that provides configuration for shared batch infrastructure and a
child context for the configuration of a specific batch job.
-As of Spring Framework 3.2.2, it is possible to write integration tests that use context
+Since Spring Framework 3.2.2, it is possible to write integration tests that use context
hierarchies by declaring context configuration via the `@ContextHierarchy` annotation,
either on an individual test class or within a test class hierarchy. If a context
hierarchy is declared on multiple classes within a test class hierarchy it is also
@@ -2621,7 +2506,7 @@ configuration resource type (i.e., XML configuration files or annotated classes)
consistent; otherwise, it is perfectly acceptable to have different levels in a context
hierarchy configured using different resource types.
-The following JUnit-based examples demonstrate common configuration scenarios for
+The following JUnit 4 based examples demonstrate common configuration scenarios for
integration tests that require the use of context hierarchies.
.Single test class with context hierarchy
@@ -2637,7 +2522,7 @@ lowest context in the hierarchy).
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
@WebAppConfiguration
@ContextHierarchy({
@ContextConfiguration(classes = TestAppConfig.class),
@@ -2672,7 +2557,7 @@ for the concrete subclasses.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
@WebAppConfiguration
@ContextConfiguration("file:src/main/webapp/WEB-INF/applicationContext.xml")
public abstract class AbstractWebTests {}
@@ -2703,7 +2588,7 @@ and `{"/user-config.xml", "/order-config.xml"}`.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
@ContextHierarchy({
@ContextConfiguration(name = "parent", locations = "/app-config.xml"),
@ContextConfiguration(name = "child", locations = "/user-config.xml")
@@ -2729,7 +2614,7 @@ application context for `ExtendedTests` will be loaded only from
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
@ContextHierarchy({
@ContextConfiguration(name = "parent", locations = "/app-config.xml"),
@ContextConfiguration(name = "child", locations = "/user-config.xml")
@@ -2765,7 +2650,7 @@ application context that you configured with `@ContextConfiguration`. You may us
injection, field injection, or both, depending on which annotations you choose and
whether you place them on setter methods or fields. For consistency with the annotation
support introduced in Spring 2.5 and 3.0, you can use Spring's `@Autowired` annotation
-or the `@Inject` annotation from JSR 300.
+or the `@Inject` annotation from JSR 330.
[TIP]
====
@@ -2797,7 +2682,7 @@ is presented after all sample code listings.
[NOTE]
====
The dependency injection behavior in the following code listings is not specific to
-JUnit. The same DI techniques can be used in conjunction with any testing framework.
+JUnit 4. The same DI techniques can be used in conjunction with any testing framework.
The following examples make calls to static assertion methods such as `assertNotNull()`
but without prepending the call with `Assert`. In such cases, assume that the method was
@@ -2805,13 +2690,13 @@ properly imported through an `import static` declaration that is not shown in th
example.
====
-The first code listing shows a JUnit-based implementation of the test class that uses
+The first code listing shows a JUnit 4 based implementation of the test class that uses
`@Autowired` for field injection.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
// specifies the Spring configuration to load for this test fixture
**@ContextConfiguration("repository-config.xml")**
public class HibernateTitleRepositoryTests {
@@ -2834,7 +2719,7 @@ seen below.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
// specifies the Spring configuration to load for this test fixture
**@ContextConfiguration("repository-config.xml")**
public class HibernateTitleRepositoryTests {
@@ -2873,7 +2758,7 @@ this:
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
- <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
+ <bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<!-- configuration elided for brevity -->
</bean>
@@ -2915,9 +2800,8 @@ bean by name there (as shown above, assuming that "myDataSource" is the bean id)
==== Testing request and session scoped beans
<<beans-factory-scopes-other,Request and session scoped beans>> have been supported by
-Spring for several years now, but it's always been a bit non-trivial to test them. As of
-Spring 3.2 it's a breeze to test your request-scoped and session-scoped beans by
-following these steps.
+Spring since the early years, and since Spring 3.2 it's a breeze to test your
+request-scoped and session-scoped beans by following these steps.
* Ensure that a `WebApplicationContext` is loaded for your test by annotating your test
class with `@WebAppConfiguration`.
@@ -2945,8 +2829,8 @@ framework.
c:loginAction-ref="loginAction" />
<bean id="loginAction" class="com.example.LoginAction"
- c:username="#{request.getParameter(''user'')}"
- c:password="#{request.getParameter(''pswd'')}"
+ c:username="#{request.getParameter('user')}"
+ c:password="#{request.getParameter('pswd')}"
scope="request">
<aop:scoped-proxy />
</bean>
@@ -2967,7 +2851,7 @@ inputs for the username and password.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class RequestScopedBeanTests {
@@ -3007,7 +2891,7 @@ framework.
<bean id="userPreferences"
class="com.example.UserPreferences"
- c:theme="#{session.getAttribute(''theme'')}"
+ c:theme="#{session.getAttribute('theme')}"
scope="session">
<aop:scoped-proxy />
</bean>
@@ -3027,7 +2911,7 @@ configured theme.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class SessionScopedBeanTests {
@@ -3098,7 +2982,7 @@ See <<testing-examples-petclinic>> for an additional example.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
@ContextConfiguration(classes = TestConfig.class)
@Transactional
public class HibernateUserRepositoryTests {
@@ -3149,7 +3033,7 @@ via the `@Commit` and `@Rollback` annotations. See the corresponding entries in
[[testcontext-tx-programmatic-tx-mgt]]
===== Programmatic transaction management
-As of Spring Framework 4.1, it is possible to interact with test-managed transactions
+Since Spring Framework 4.1, it is possible to interact with test-managed transactions
_programmatically_ via the static methods in `TestTransaction`. For example,
`TestTransaction` may be used within _test_ methods, _before_ methods, and _after_
methods to start or end the current test-managed transaction or to configure the current
@@ -3196,17 +3080,17 @@ javadocs for `TestTransaction` for further details.
Occasionally you need to execute certain code before or after a transactional test method
but outside the transactional context -- for example, to verify the initial database state
prior to execution of your test or to verify expected transactional commit behavior after
-test execution (if the test was configured not to roll back the transaction).
+test execution (if the test was configured to commit the transaction).
`TransactionalTestExecutionListener` supports the `@BeforeTransaction` and
-`@AfterTransaction` annotations exactly for such scenarios. Simply annotate any `public
-void` method in your test class with one of these annotations, and the
-`TransactionalTestExecutionListener` ensures that your __before transaction method__ or
-__after transaction method__ is executed at the appropriate time.
+`@AfterTransaction` annotations exactly for such scenarios. Simply annotate any `void`
+method in a test class or any `void` default method in a test interface with one of these
+annotations, and the `TransactionalTestExecutionListener` ensures that your __before
+transaction method__ or __after transaction method__ is executed at the appropriate time.
[TIP]
====
-Any __before methods__ (such as methods annotated with JUnit's `@Before`) and any __after
-methods__ (such as methods annotated with JUnit's `@After`) are executed __within__ a
+Any __before methods__ (such as methods annotated with JUnit 4's `@Before`) and any __after
+methods__ (such as methods annotated with JUnit 4's `@After`) are executed __within__ a
transaction. In addition, methods annotated with `@BeforeTransaction` or
`@AfterTransaction` are naturally not executed for test methods that are not configured
to run within a transaction.
@@ -3227,7 +3111,7 @@ used to look up a transaction manager in the test's `ApplicationContext`.
[[testcontext-tx-annotation-demo]]
===== Demonstration of all transaction-related annotations
-The following JUnit-based example displays a fictitious integration testing scenario
+The following JUnit 4 based example displays a fictitious integration testing scenario
highlighting all transaction-related annotations. The example is **not** intended to
demonstrate best practices but rather to demonstrate how these annotations can be used.
Consult the <<integration-testing-annotations,annotation support>> section for further
@@ -3238,14 +3122,14 @@ declarative SQL script execution with default transaction rollback semantics.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
@ContextConfiguration
**@Transactional(transactionManager = "txMgr")**
**@Commit**
public class FictitiousTransactionalTest {
**@BeforeTransaction**
- public void verifyInitialDatabaseState() {
+ void verifyInitialDatabaseState() {
// logic to verify the initial state before a transaction is started
}
@@ -3267,7 +3151,7 @@ declarative SQL script execution with default transaction rollback semantics.
}
**@AfterTransaction**
- public void verifyFinalDatabaseState() {
+ void verifyFinalDatabaseState() {
// logic to verify the final state after transaction has rolled back
}
@@ -3278,13 +3162,14 @@ declarative SQL script execution with default transaction rollback semantics.
.Avoid false positives when testing ORM code
[NOTE]
====
-When you test application code that manipulates the state of the Hibernate or JPA session,
-make sure to __flush__ the underlying session within test methods that execute that code.
-Failing to flush the underlying session can produce __false positives__: your test may
-pass, but the same code throws an exception in a live, production environment. In the
-following Hibernate-based example test case, one method demonstrates a false positive,
-and the other method correctly exposes the results of flushing the session. Note that
-this applies to any ORM frameworks that maintain an in-memory __unit of work__.
+When you test application code that manipulates the state of a Hibernate session or JPA
+persistence context, make sure to __flush__ the underlying unit of work within test
+methods that execute that code. Failing to flush the underlying unit of work can produce
+__false positives__: your test may pass, but the same code throws an exception in a live,
+production environment. In the following Hibernate-based example test case, one method
+demonstrates a false positive, and the other method correctly exposes the results of
+flushing the session. Note that this applies to any ORM frameworks that maintain an
+in-memory __unit of work__.
[source,java,indent=0]
[subs="verbatim,quotes"]
@@ -3292,8 +3177,9 @@ this applies to any ORM frameworks that maintain an in-memory __unit of work__.
// ...
@Autowired
- private SessionFactory sessionFactory;
+ SessionFactory sessionFactory;
+ @Transactional
@Test // no expected exception!
public void falsePositive() {
updateEntityInHibernateSession();
@@ -3301,6 +3187,7 @@ this applies to any ORM frameworks that maintain an in-memory __unit of work__.
// Session is finally flushed (i.e., in production code)
}
+ @Transactional
@Test(expected = ...)
public void updateWithSessionFlush() {
updateEntityInHibernateSession();
@@ -3318,19 +3205,21 @@ Or for JPA:
----
// ...
- @Autowired
- private EntityManager entityManager;
+ @PersistenceContext
+ EntityManager entityManager;
+ @Transactional
@Test // no expected exception!
public void falsePositive() {
- updateEntityInJpaTransaction();
+ updateEntityInJpaPersistenceContext();
// False positive: an exception will be thrown once the JPA
// EntityManager is finally flushed (i.e., in production code)
}
+ @Transactional
@Test(expected = ...)
public void updateWithEntityManagerFlush() {
- updateEntityInJpaTransaction();
+ updateEntityInJpaPersistenceContext();
// Manual flush is required to avoid false positive in test
entityManager.flush();
}
@@ -3409,7 +3298,7 @@ for the various `executeSqlScript(..)` methods for further details.
[[testcontext-executing-sql-declaratively]]
-===== Executing SQL scripts declaratively with `@Sql`
+===== Executing SQL scripts declaratively with @Sql
In addition to the aforementioned mechanisms for executing SQL scripts
_programmatically_, SQL scripts can also be configured _declaratively_ in the Spring
@@ -3429,12 +3318,12 @@ which references a URL (e.g., a path prefixed with `classpath:`, `file:`, `http:
will be loaded using the specified resource protocol.
The following example demonstrates how to use `@Sql` at the class level and at the method
-level within a JUnit-based integration test class.
+level within a JUnit 4 based integration test class.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
@ContextConfiguration
@Sql("/test-schema.sql")
public class DatabaseTests {
@@ -3576,16 +3465,16 @@ executed in an isolated transaction. Although a thorough discussion of all suppo
options for transaction management with `@Sql` is beyond the scope of this reference
manual, the javadocs for `@SqlConfig` and `SqlScriptsTestExecutionListener` provide
detailed information, and the following example demonstrates a typical testing scenario
-using JUnit and transactional tests with `@Sql`. Note that there is no need to clean up
+using JUnit 4 and transactional tests with `@Sql`. Note that there is no need to clean up
the database after the `usersTest()` method is executed since any changes made to the
-database (either within the the test method or within the `/test-data.sql` script) will
+database (either within the test method or within the `/test-data.sql` script) will
be automatically rolled back by the `TransactionalTestExecutionListener` (see
<<testcontext-tx,transaction management>> for details).
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
@ContextConfiguration(classes = TestDatabaseConfig.class)
@Transactional
public class TransactionalSqlScriptsTests {
@@ -3621,17 +3510,18 @@ be automatically rolled back by the `TransactionalTestExecutionListener` (see
[[testcontext-junit4-runner]]
-===== Spring JUnit Runner
-
-The __Spring TestContext Framework__ offers full integration with JUnit 4.9+ through a
-custom runner (supported on JUnit 4.9 through 4.12). By annotating test classes with
-`@RunWith(SpringJUnit4ClassRunner.class)`, developers can implement standard JUnit-based
-unit and integration tests and simultaneously reap the benefits of the TestContext
-framework such as support for loading application contexts, dependency injection of test
-instances, transactional test method execution, and so on. If you would like to use the
-Spring TestContext Framework with an alternative runner such as JUnit's `Parameterized`
-or third-party runners such as the `MockitoJUnitRunner`, you may optionally use
-<<testcontext-junit4-rules,Spring's support for JUnit rules>> instead.
+===== Spring JUnit 4 Runner
+
+The __Spring TestContext Framework__ offers full integration with JUnit 4 through a
+custom runner (supported on JUnit 4.12 or higher). By annotating test classes with
+`@RunWith(SpringJUnit4ClassRunner.class)` or the shorter `@RunWith(SpringRunner.class)`
+variant, developers can implement standard JUnit 4 based unit and integration tests and
+simultaneously reap the benefits of the TestContext framework such as support for loading
+application contexts, dependency injection of test instances, transactional test method
+execution, and so on. If you would like to use the Spring TestContext Framework with an
+alternative runner such as JUnit 4's `Parameterized` or third-party runners such as the
+`MockitoJUnitRunner`, you may optionally use <<testcontext-junit4-rules,Spring's support
+for JUnit rules>> instead.
The following code listing displays the minimal requirements for configuring a test class
to run with the custom Spring `Runner`. `@TestExecutionListeners` is configured with an
@@ -3641,7 +3531,7 @@ empty list in order to disable the default listeners, which otherwise would requ
[source,java,indent=0]
[subs="verbatim,quotes"]
----
-@RunWith(SpringJUnit4ClassRunner.class)
+@RunWith(SpringRunner.class)
@TestExecutionListeners({})
public class SimpleTest {
@@ -3654,10 +3544,10 @@ public class SimpleTest {
[[testcontext-junit4-rules]]
-===== Spring JUnit Rules
+===== Spring JUnit 4 Rules
The `org.springframework.test.context.junit4.rules` package provides the following JUnit
-rules.
+4 rules (supported on JUnit 4.12 or higher).
* `SpringClassRule`
* `SpringMethodRule`
@@ -3666,10 +3556,10 @@ rules.
_Spring TestContext Framework_; whereas, `SpringMethodRule` is a JUnit `MethodRule` that
supports instance-level and method-level features of the _Spring TestContext Framework_.
-In contrast to the `SpringJUnit4ClassRunner`, Spring's rule-based JUnit support has the
-advantage that it is independent of any `org.junit.runner.Runner` implementation and can
-therefore be combined with existing alternative runners like JUnit's `Parameterized` or
-third-party runners such as the `MockitoJUnitRunner`.
+In contrast to the `SpringRunner`, Spring's rule-based JUnit support has the advantage
+that it is independent of any `org.junit.runner.Runner` implementation and can therefore
+be combined with existing alternative runners like JUnit 4's `Parameterized` or third-party
+runners such as the `MockitoJUnitRunner`.
In order to support the full functionality of the TestContext framework, a
`SpringClassRule` must be combined with a `SpringMethodRule`. The following example
@@ -3698,17 +3588,17 @@ public class IntegrationTest {
[[testcontext-support-classes-junit4]]
-===== JUnit support classes
+===== JUnit 4 support classes
The `org.springframework.test.context.junit4` package provides the following support
-classes for JUnit-based test cases.
+classes for JUnit 4 based test cases (supported on JUnit 4.12 or higher).
* `AbstractJUnit4SpringContextTests`
* `AbstractTransactionalJUnit4SpringContextTests`
`AbstractJUnit4SpringContextTests` is an abstract base test class that integrates the
__Spring TestContext Framework__ with explicit `ApplicationContext` testing support in
-a JUnit 4.9+ environment. When you extend `AbstractJUnit4SpringContextTests`, you can
+a JUnit 4 environment. When you extend `AbstractJUnit4SpringContextTests`, you can
access a `protected` `applicationContext` instance variable that can be used to perform
explicit bean lookups or to test the state of the context as a whole.
@@ -3732,7 +3622,7 @@ provides an `executeSqlScript(..)` method for executing SQL scripts against the
====
These classes are a convenience for extension. If you do not want your test classes to be
tied to a Spring-specific class hierarchy, you can configure your own custom test classes
-by using `@RunWith(SpringJUnit4ClassRunner.class)` or <<testcontext-junit4-rules,Spring's
+by using `@RunWith(SpringRunner.class)` or <<testcontext-junit4-rules,Spring's
JUnit rules>>.
====
@@ -3825,14 +3715,14 @@ of the Servlet API>> available in the `spring-test` module. This allows performi
requests and generating responses without the need for running in a Servlet container.
For the most part everything should work as it does at runtime with a few notable
exceptions as explained in <<spring-mvc-test-vs-end-to-end-integration-tests>>. Here is a
-JUnit-based example of using Spring MVC Test:
+JUnit 4 based example of using Spring MVC Test:
[source,java,indent=0]
----
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
@WebAppConfiguration
@ContextConfiguration("test-servlet-context.xml")
public class ExampleTests {
@@ -3892,7 +3782,7 @@ into the test to use to build a `MockMvc` instance:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
@WebAppConfiguration
@ContextConfiguration("my-servlet-context.xml")
public class MyWebTests {
@@ -3957,7 +3847,7 @@ expectations:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RunWith(SpringJUnit4ClassRunner.class)
+ @RunWith(SpringRunner.class)
@WebAppConfiguration
@ContextConfiguration("test-servlet-context.xml")
public class AccountTests {
@@ -4112,7 +4002,7 @@ This can be done as follows, where `print()` is a static import from
----
As long as request processing does not cause an unhandled exception, the `print()` method
-will print all the available result data to `System.out`. Spring Framework 4.2 introduces
+will print all the available result data to `System.out`. Spring Framework 4.2 introduced
a `log()` method and two additional variants of the `print()` method, one that accepts
an `OutputStream` and one that accepts a `Writer`. For example, invoking
`print(System.err)` will print the result data to `System.err`; while invoking
@@ -4155,7 +4045,7 @@ be verified using JsonPath expressions:
[subs="verbatim,quotes"]
----
mockMvc.perform(get("/people").accept(MediaType.APPLICATION_JSON))
- .andExpect(jsonPath("$.links[?(@.rel == ''self'')].href").value("http://localhost:8080/people"));
+ .andExpect(jsonPath("$.links[?(@.rel == 'self')].href").value("http://localhost:8080/people"));
----
When XML response content contains hypermedia links created with
@@ -4167,7 +4057,7 @@ be verified using XPath expressions:
----
Map<String, String> ns = Collections.singletonMap("ns", "http://www.w3.org/2005/Atom");
mockMvc.perform(get("/handle").accept(MediaType.APPLICATION_XML))
- .andExpect(xpath("/person/ns:link[@rel=''self'']/@href", ns).string("http://localhost:8080/people"));
+ .andExpect(xpath("/person/ns:link[@rel='self']/@href", ns).string("http://localhost:8080/people"));
----
[[spring-mvc-test-server-filters]]
@@ -5035,39 +4925,93 @@ http://www.gebish.org/manual/current/[The Book of Geb] user's manual.
[[spring-mvc-test-client]]
==== Client-Side REST Tests
-Client-side tests are for code using the `RestTemplate`. The goal is to define expected
-requests and provide "stub" responses:
+Client-side tests can be used to test code that internally uses the `RestTemplate`.
+The idea is to declare expected requests and to provide "stub" responses so that
+you can focus on testing the code in isolation, i.e. without running a server.
+Here is an example:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
RestTemplate restTemplate = new RestTemplate();
- MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate);
- mockServer.expect(requestTo("/greeting")).andRespond(withSuccess("Hello world", MediaType.TEXT_PLAIN));
+ MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build();
+ mockServer.expect(requestTo("/greeting")).andRespond(withSuccess());
- // use RestTemplate ...
+ // Test code that uses the above RestTemplate ...
mockServer.verify();
----
-In the above example, `MockRestServiceServer` -- the central class for client-side REST
-tests -- configures the `RestTemplate` with a custom `ClientHttpRequestFactory` that
+In the above example, `MockRestServiceServer`, the central class for client-side REST
+tests, configures the `RestTemplate` with a custom `ClientHttpRequestFactory` that
asserts actual requests against expectations and returns "stub" responses. In this case
-we expect a single request to "/greeting" and want to return a 200 response with
-"text/plain" content. We could define as many additional requests and stub responses as
-necessary.
+we expect a request to "/greeting" and want to return a 200 response with
+"text/plain" content. We could define as additional expected requests and stub responses as
+needed. When expected requests and stub responses are defined, the `RestTemplate` can be
+used in client-side code as usual. At the end of testing `mockServer.verify()` can be
+used to verify that all expectations have been satisfied.
+
+By default requests are expected in the order in which expectations were declared.
+You can set the `ignoreExpectOrder` option when building the server in which case
+all expectations are checked (in order) to find a match for a given request. That
+means requests are allowed to come in any order. Here is an example:
+
+[source,java,indent=0]
+[subs="verbatim,quotes"]
+----
+ server = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build();
+----
+
+Even with unordered requests by default each request is allowed to execute once only.
+The `expect` method provides an overloaded variant that accepts an `ExpectedCount`
+argument that specifies a count range, e.g. `once`, `manyTimes`, `max`, `min`,
+`between`, and so on. Here is an example:
+
+[source,java,indent=0]
+[subs="verbatim,quotes"]
+----
+ RestTemplate restTemplate = new RestTemplate();
+
+ MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build();
+ mockServer.expect(times(2), requestTo("/foo")).andRespond(withSuccess());
+ mockServer.expect(times(3), requestTo("/bar")).andRespond(withSuccess());
+
+ // ...
+
+ mockServer.verify();
+----
+
+Note that when `ignoreExpectOrder` is not set (the default), and therefore requests
+are expected in order of declaration, then that order only applies to the first of
+any expected request. For example if "/foo" is expected 2 times followed by "/bar"
+3 times, then there should be a request to "/foo" before there is a request to "/bar"
+but aside from that subsequent "/foo" and "/bar" requests can come at any time.
+
+As an alternative to all of the above the client-side test support also provides a
+`ClientHttpRequestFactory` implementation that can be configured into a `RestTemplate`
+to bind it to a `MockMvc` instance. That allows processing requests using actual
+server-side logic but without running a server. Here is an example:
+
+[source,java,indent=0]
+[subs="verbatim,quotes"]
+----
+ MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
+ this.restTemplate = new RestTemplate(new MockMvcClientHttpRequestFactory(mockMvc));
+
+ // Test code that uses the above RestTemplate ...
+
+ mockServer.verify();
+----
+
-Once expected requests and stub responses have been defined, the `RestTemplate` can be
-used in client-side code as usual. At the end of the tests `mockServer.verify()` can be
-used to verify that all expected requests were performed.
[[spring-mvc-test-client-static-imports]]
===== Static Imports
Just like with server-side tests, the fluent API for client-side tests requires a few
static imports. Those are easy to find by searching __"MockRest*"__. Eclipse users
should add `"MockRestRequestMatchers.{asterisk}"` and `"MockRestResponseCreators.{asterisk}"`
-as "favorite static members" in the Eclipse preferences under
+as "favorite static members" in the Eclipse preferences under
__Java -> Editor -> Content Assist -> Favorites__.
That allows using content assist after typing the first character of the
static method name. Other IDEs (e.g. IntelliJ) may not require any additional
@@ -5086,7 +5030,7 @@ tests] of client-side REST tests.
The PetClinic application, available on
https://github.com/spring-projects/spring-petclinic[GitHub], illustrates several features
-of the __Spring TestContext Framework__ in a JUnit environment. Most test functionality
+of the __Spring TestContext Framework__ in a JUnit 4 environment. Most test functionality
is included in the `AbstractClinicTests`, for which a partial listing is shown below:
[source,java,indent=0]
diff --git a/src/asciidoc/web-cors.adoc b/src/asciidoc/web-cors.adoc
index 5e8c351c..d32a095f 100644
--- a/src/asciidoc/web-cors.adoc
+++ b/src/asciidoc/web-cors.adoc
@@ -17,7 +17,7 @@ and less powerful hacks like IFRAME or JSONP.
As of Spring Framework 4.2, CORS is supported out of the box. CORS requests
(https://github.com/spring-projects/spring-framework/blob/master/spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java#L906[including preflight ones with an `OPTIONS` method])
-are automatically dispatched to the various registered ++HandlerMapping++s. They handle
+are automatically dispatched to the various registered ``HandlerMapping``s. They handle
CORS preflight requests and intercept CORS simple and actual requests thanks to a
{api-spring-framework}/web/cors/CorsProcessor.html[CorsProcessor]
implementation (https://github.com/spring-projects/spring-framework/blob/master/spring-web/src/main/java/org/springframework/web/cors/DefaultCorsProcessor.java[DefaultCorsProcessor]
@@ -203,4 +203,42 @@ It can be provided in various ways:
* Handlers can implement the {api-spring-framework}/web/cors/CorsConfigurationSource.html[`CorsConfigurationSource`]
interface (like https://github.com/spring-projects/spring-framework/blob/master/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java[`ResourceHttpRequestHandler`]
now does) in order to provide a {api-spring-framework}/web/cors/CorsConfiguration.html[CorsConfiguration]
- instance for each request. \ No newline at end of file
+ instance for each request.
+
+== Filter based CORS support
+
+In order to support CORS with filter-based security frameworks like
+http://projects.spring.io/spring-security/[Spring Security], or
+with other libraries that do not support natively CORS, Spring Framework also
+provides a http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/filter/CorsFilter.html[`CorsFilter`].
+Instead of using `@CrossOrigin` or `WebMvcConfigurer#addCorsMappings(CorsRegistry)`, you
+need to register a custom filter defined like bellow:
+
+[source,java,indent=0]
+----
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+import org.springframework.web.filter.CorsFilter;
+
+public class MyCorsFilter extends CorsFilter {
+
+ public MyCorsFilter() {
+ super(configurationSource());
+ }
+
+ private static UrlBasedCorsConfigurationSource configurationSource() {
+ CorsConfiguration config = new CorsConfiguration();
+ config.setAllowCredentials(true);
+ config.addAllowedOrigin("http://domain1.com");
+ config.addAllowedHeader("*");
+ config.addAllowedMethod("*");
+ UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+ source.registerCorsConfiguration("/**", config);
+ return source;
+ }
+}
+----
+
+You need to ensure that `CorsFilter` is ordered before the other filters, see
+https://spring.io/blog/2015/06/08/cors-support-in-spring-framework#filter-based-cors-support[this blog post]
+about how to configure Spring Boot accordingly.
diff --git a/src/asciidoc/web-mvc.adoc b/src/asciidoc/web-mvc.adoc
index e1853347..65f5bdf7 100644
--- a/src/asciidoc/web-mvc.adoc
+++ b/src/asciidoc/web-mvc.adoc
@@ -25,7 +25,7 @@ For an explanation of this principle, refer to __Expert Spring Web MVC and Web F
Seth Ladd and others; specifically see the section "A Look At Design," on page 117 of
the first edition. Alternatively, see
-* http://www.objectmentor.com/resources/articles/ocp.pdf[Bob Martin, The Open-Closed
+* https://www.cs.duke.edu/courses/fall07/cps108/papers/ocp.pdf[Bob Martin, The Open-Closed
Principle (PDF)]
You cannot add advice to final methods when you use Spring MVC. For example, you cannot
@@ -37,7 +37,7 @@ add advice to final methods.
In Spring Web MVC you can use any object as a command or form-backing object; you do not
need to implement a framework-specific interface or base class. Spring's data binding is
highly flexible: for example, it treats type mismatches as validation errors that can be
-evaluated by the application, not as system errors. Thus you need not duplicate your
+evaluated by the application, not as system errors. Thus you do not need to duplicate your
business objects' properties as simple, untyped strings in your form objects simply to
handle invalid submissions, or to convert the Strings properly. Instead, it is often
preferable to bind directly to your business objects.
@@ -160,11 +160,44 @@ pattern that Spring Web MVC shares with many other leading web frameworks).
image::images/mvc.png[width=400]
The `DispatcherServlet` is an actual `Servlet` (it inherits from the `HttpServlet` base
+class), and as such is declared in your web application. You need to map requests that
+you want the `DispatcherServlet` to handle, by using a URL mapping. Here is a standard
+Java EE Servlet configuration in a Servlet 3.0+ environment:
+
+[source,java,indent=0]
+[subs="verbatim,quotes"]
+----
+ public class MyWebApplicationInitializer implements WebApplicationInitializer {
+
+ @Override
+ public void onStartup(ServletContext container) {
+ ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet());
+ registration.setLoadOnStartup(1);
+ registration.addMapping("/example/*");
+ }
+
+ }
+----
+
+In the preceding example, all requests starting with `/example` will be handled by the
+`DispatcherServlet` instance named `example`.
+
+`WebApplicationInitializer` is an interface provided by Spring MVC that ensures your
+code-based configuration is detected and automatically used to initialize any Servlet 3
+container. An abstract base class implementation of this interface named
+`AbstractAnnotationConfigDispatcherServletInitializer` makes it even easier to register the
+`DispatcherServlet` by simply specifying its servlet mapping and listing configuration
+classes - it's even the recommended way to set up your Spring MVC application.
+See <<mvc-container-config,Code-based Servlet container initialization>> for more details.
+
+The `DispatcherServlet` is an actual `Servlet` (it inherits from the `HttpServlet` base
class), and as such is declared in the `web.xml` of your web application. You need to
map requests that you want the `DispatcherServlet` to handle, by using a URL mapping in
the same `web.xml` file. This is standard Java EE Servlet configuration; the following
example shows such a `DispatcherServlet` declaration and mapping:
+Below is the `web.xml` equivalent of the above code based example:
+
[source,xml,indent=0]
[subs="verbatim,quotes"]
----
@@ -183,41 +216,13 @@ example shows such a `DispatcherServlet` declaration and mapping:
</web-app>
----
-In the preceding example, all requests starting with `/example` will be handled by the
-`DispatcherServlet` instance named `example`. In a Servlet 3.0+ environment, you also
-have the option of configuring the Servlet container programmatically. Below is the code
-based equivalent of the above `web.xml` example:
-
-[source,java,indent=0]
-[subs="verbatim,quotes"]
-----
- public class MyWebApplicationInitializer implements WebApplicationInitializer {
-
- @Override
- public void onStartup(ServletContext container) {
- ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet());
- registration.setLoadOnStartup(1);
- registration.addMapping("/example/*");
- }
-
- }
-----
-
-`WebApplicationInitializer` is an interface provided by Spring MVC that ensures your
-code-based configuration is detected and automatically used to initialize any Servlet 3
-container. An abstract base class implementation of this interface named
-`AbstractDispatcherServletInitializer` makes it even easier to register the
-`DispatcherServlet` by simply specifying its servlet mapping.
-See <<mvc-container-config,Code-based Servlet container initialization>> for more details.
-
-The above is only the first step in setting up Spring Web MVC. You now need to configure
-the various beans used by the Spring Web MVC framework (over and above the
-`DispatcherServlet` itself).
As detailed in <<context-introduction>>, `ApplicationContext` instances in Spring can be
scoped. In the Web MVC framework, each `DispatcherServlet` has its own
`WebApplicationContext`, which inherits all the beans already defined in the root
-`WebApplicationContext`. These inherited beans can be overridden in the servlet-specific
+`WebApplicationContext`. The root `WebApplicationContext` should contain all the
+infrastructure beans that should be shared between your other contexts and Servlet
+instances. These inherited beans can be overridden in the servlet-specific
scope, and you can define new scope-specific beans local to a given Servlet instance.
.Typical context hierarchy in Spring Web MVC
@@ -295,7 +300,32 @@ a link to the `ServletContext`). The `WebApplicationContext` is bound in the
`ServletContext`, and by using static methods on the `RequestContextUtils` class you can
always look up the `WebApplicationContext` if you need access to it.
+Note that we can achieve the same with java-based configurations:
+[source,java,indent=0]
+[subs="verbatim,quotes"]
+----
+ public class GolfingWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
+
+ @Override
+ protected Class<?>[] getRootConfigClasses() {
+ // GolfingAppConfig defines beans that would be in root-context.xml
+ return new Class[] { GolfingAppConfig.class };
+ }
+
+ @Override
+ protected Class<?>[] getServletConfigClasses() {
+ // GolfingWebConfig defines beans that would be in golfing-servlet.xml
+ return new Class[] { GolfingWebConfig.class };
+ }
+
+ @Override
+ protected String[] getServletMappings() {
+ return new String[] { "/golfing/*" };
+ }
+
+ }
+----
[[mvc-servlet-special-bean-types]]
=== Special Bean Types In the WebApplicationContext
@@ -581,16 +611,16 @@ application that uses this annotation:
}
----
-In the example, the `@RequestMapping` is used in a number of places. The first usage is
-on the type (class) level, which indicates that all handling methods on this controller
+In the above example, `@RequestMapping` is used in a number of places. The first usage is
+on the type (class) level, which indicates that all handler methods in this controller
are relative to the `/appointments` path. The `get()` method has a further
-`@RequestMapping` refinement: it only accepts GET requests, meaning that an HTTP GET for
+`@RequestMapping` refinement: it only accepts `GET` requests, meaning that an HTTP `GET` for
`/appointments` invokes this method. The `add()` has a similar refinement, and the
-`getNewForm()` combines the definition of HTTP method and path into one, so that GET
+`getNewForm()` combines the definition of HTTP method and path into one, so that `GET`
requests for `appointments/new` are handled by that method.
The `getForDay()` method shows another usage of `@RequestMapping`: URI templates. (See
-<<mvc-ann-requestmapping-uri-templates,the next section >>).
+<<mvc-ann-requestmapping-uri-templates>>).
A `@RequestMapping` on the class level is not required. Without it, all paths are simply
absolute, and not relative. The following example from the __PetClinic__ sample
@@ -621,10 +651,66 @@ application shows a multi-action controller using `@RequestMapping`:
}
----
-The above example does not specify GET vs. PUT, POST, and so forth, because
-`@RequestMapping` maps all HTTP methods by default. Use `@RequestMapping(method=GET)`
-to narrow the mapping.
+The above example does not specify `GET` vs. `PUT`, `POST`, and so forth, because
+`@RequestMapping` maps all HTTP methods by default. Use `@RequestMapping(method=GET)` or
+`@GetMapping` to narrow the mapping.
+
+[[mvc-ann-requestmapping-composed]]
+==== Composed @RequestMapping Variants
+
+Spring Framework 4.3 introduces the following method-level _composed_ variants of the
+`@RequestMapping` annotation that help to simplify mappings for common HTTP methods and
+better express the semantics of the annotated handler method. For example, a
+`@GetMapping` can be read as a `GET` `@RequestMapping`.
+
+- `@GetMapping`
+- `@PostMapping`
+- `@PutMapping`
+- `@DeleteMapping`
+- `@PatchMapping`
+
+The following example shows a modified version of the `AppointmentsController` from the
+previous section that has been simplified with _composed_ `@RequestMapping` annotations.
+
+[source,java,indent=0]
+[subs="verbatim,quotes"]
+----
+ @Controller
+ **@RequestMapping("/appointments")**
+ public class AppointmentsController {
+
+ private final AppointmentBook appointmentBook;
+
+ @Autowired
+ public AppointmentsController(AppointmentBook appointmentBook) {
+ this.appointmentBook = appointmentBook;
+ }
+
+ **@GetMapping**
+ public Map<String, Appointment> get() {
+ return appointmentBook.getAppointmentsForToday();
+ }
+ **@GetMapping("/{day}")**
+ public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) {
+ return appointmentBook.getAppointmentsForDay(day);
+ }
+
+ **@GetMapping("/new")**
+ public AppointmentForm getNewForm() {
+ return new AppointmentForm();
+ }
+
+ **@PostMapping**
+ public String add(@Valid AppointmentForm appointment, BindingResult result) {
+ if (result.hasErrors()) {
+ return "appointments/new";
+ }
+ appointmentBook.addAppointment(appointment);
+ return "redirect:/appointments";
+ }
+ }
+----
[[mvc-ann-requestmapping-proxying]]
==== @Controller and AOP Proxying
@@ -699,7 +785,7 @@ to the value of a URI template variable:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RequestMapping(path="/owners/{ownerId}", method=RequestMethod.GET)
+ @GetMapping("/owners/{ownerId}")
public String findOwner(**@PathVariable** String ownerId, Model model) {
Owner owner = ownerService.findOwner(ownerId);
model.addAttribute("owner", owner);
@@ -721,20 +807,21 @@ template variable by name. You can specify it in the annotation:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RequestMapping(path="/owners/{ownerId}", method=RequestMethod.GET)
+ @GetMapping("/owners/{ownerId}")
public String findOwner(**@PathVariable("ownerId")** String theOwner, Model model) {
// implementation omitted
}
----
Or if the URI template variable name matches the method argument name you can omit that
-detail. As long as your code is not compiled without debugging information, Spring MVC
-will match the method argument name to the URI template variable name:
+detail. As long as your code is compiled with debugging information or the `-parameters`
+compiler flag on Java 8, Spring MVC will match the method argument name to the URI
+template variable name:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RequestMapping(path="/owners/{ownerId}", method=RequestMethod.GET)
+ @GetMapping("/owners/{ownerId}")
public String findOwner(**@PathVariable** String ownerId, Model model) {
// implementation omitted
}
@@ -746,7 +833,7 @@ A method can have any number of `@PathVariable` annotations:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RequestMapping(path="/owners/{ownerId}/pets/{petId}", method=RequestMethod.GET)
+ @GetMapping("/owners/{ownerId}/pets/{petId}")
public String findPet(**@PathVariable** String ownerId, **@PathVariable** String petId, Model model) {
Owner owner = ownerService.findOwner(ownerId);
Pet pet = owner.getPet(petId);
@@ -758,7 +845,7 @@ A method can have any number of `@PathVariable` annotations:
When a `@PathVariable` annotation is used on a `Map<String, String>` argument, the map
is populated with all URI template variables.
-A URI template can be assembled from type and path level __@RequestMapping__
+A URI template can be assembled from type and method level __@RequestMapping__
annotations. As a result the `findPet()` method can be invoked with a URL such as
`/owners/42/pets/21`.
@@ -777,7 +864,7 @@ annotations. As a result the `findPet()` method can be invoked with a URL such a
}
----
-A `@PathVariable` argument can be of __any simple type__ such as int, long, Date, etc.
+A `@PathVariable` argument can be of __any simple type__ such as `int`, `long`, `Date`, etc.
Spring automatically converts to the appropriate type or throws a
`TypeMismatchException` if it fails to do so. You can also register support for parsing
additional data types. See <<mvc-ann-typeconversion>> and <<mvc-ann-webdatabinder>>.
@@ -790,24 +877,24 @@ Sometimes you need more precision in defining URI template variables. Consider t
The `@RequestMapping` annotation supports the use of regular expressions in URI template
variables. The syntax is `{varName:regex}` where the first part defines the variable
-name and the second - the regular expression.For example:
+name and the second - the regular expression. For example:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@RequestMapping("/spring-web/{symbolicName:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{extension:\\.[a-z]+}")
- public void handle(@PathVariable String version, @PathVariable String extension) {
- // ...
- }
+ public void handle(@PathVariable String version, @PathVariable String extension) {
+ // ...
}
----
[[mvc-ann-requestmapping-patterns]]
==== Path Patterns
-In addition to URI templates, the `@RequestMapping` annotation also supports Ant-style
-path patterns (for example, `/myPath/{asterisk}.do`). A combination of URI template variables and
-Ant-style globs is also supported (e.g. `/owners/{asterisk}/pets/{petId}`).
+In addition to URI templates, the `@RequestMapping` annotation and all _composed_
+`@RequestMapping` variants also support Ant-style path patterns (for example,
+`/myPath/{asterisk}.do`). A combination of URI template variables and Ant-style globs is
+also supported (e.g. `/owners/{asterisk}/pets/{petId}`).
[[mvc-ann-requestmapping-pattern-comparison]]
@@ -837,7 +924,7 @@ can be customized (see <<mvc-config-path-matching>> in the section on configurin
[[mvc-ann-requestmapping-placeholders]]
==== Path Patterns with Placeholders
-Patterns in `@RequestMapping` annotations support ${...} placeholders against local
+Patterns in `@RequestMapping` annotations support `${...}` placeholders against local
properties and/or system properties and environment variables. This may be useful in
cases where the path a controller is mapped to may need to be customized through
configuration. For more information on placeholders, see the javadocs of the
@@ -944,7 +1031,7 @@ Below is an example of extracting the matrix variable "q":
----
// GET /pets/42;q=11;r=22
- @RequestMapping(path = "/pets/{petId}", method = RequestMethod.GET)
+ @GetMapping("/pets/{petId}")
public void findPet(@PathVariable String petId, @MatrixVariable int q) {
// petId == 42
@@ -961,7 +1048,7 @@ specific to identify where the variable is expected to be:
----
// GET /owners/42;q=11/pets/21;q=22
- @RequestMapping(path = "/owners/{ownerId}/pets/{petId}", method = RequestMethod.GET)
+ @GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
@MatrixVariable(name="q", pathVar="ownerId") int q1,
@MatrixVariable(name="q", pathVar="petId") int q2) {
@@ -979,7 +1066,7 @@ A matrix variable may be defined as optional and a default value specified:
----
// GET /pets/42
- @RequestMapping(path = "/pets/{petId}", method = RequestMethod.GET)
+ @GetMapping("/pets/{petId}")
public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) {
// q == 1
@@ -994,10 +1081,10 @@ All matrix variables may be obtained in a Map:
----
// GET /owners/42;q=11;r=12/pets/21;q=22;s=23
- @RequestMapping(path = "/owners/{ownerId}/pets/{petId}", method = RequestMethod.GET)
+ @GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
- @MatrixVariable Map<String, String> matrixVars,
- @MatrixVariable(pathVar="petId"") Map<String, String> petMatrixVars) {
+ @MatrixVariable MultiValueMap<String, String> matrixVars,
+ @MatrixVariable(pathVar="petId"") MultiValueMap<String, String> petMatrixVars) {
// matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
// petMatrixVars: ["q" : 11, "s" : 23]
@@ -1045,21 +1132,20 @@ to `false`.
[[mvc-ann-requestmapping-consumes]]
==== Consumable Media Types
You can narrow the primary mapping by specifying a list of consumable media types. The
-request will be matched only if the __Content-Type__ request header matches the specified
+request will be matched only if the `Content-Type` request header matches the specified
media type. For example:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @Controller
- @RequestMapping(path = "/pets", method = RequestMethod.POST, **consumes="application/json"**)
+ @PostMapping(path = "/pets", **consumes = "application/json"**)
public void addPet(@RequestBody Pet pet, Model model) {
// implementation omitted
}
----
-Consumable media type expressions can also be negated as in __!text/plain__ to match to
-all requests other than those with __Content-Type__ of __text/plain__. Also consider
+Consumable media type expressions can also be negated as in `!text/plain` to match to
+all requests other than those with `Content-Type` of `text/plain`. Also consider
using constants provided in `MediaType` such as `APPLICATION_JSON_VALUE` and
`APPLICATION_JSON_UTF8_VALUE`.
@@ -1075,7 +1161,7 @@ rather than extend type-level consumable types.
[[mvc-ann-requestmapping-produces]]
==== Producible Media Types
You can narrow the primary mapping by specifying a list of producible media types. The
-request will be matched only if the __Accept__ request header matches one of these
+request will be matched only if the `Accept` request header matches one of these
values. Furthermore, use of the __produces__ condition ensures the actual content type
used to generate the response respects the media types specified in the __produces__
condition. For example:
@@ -1083,8 +1169,7 @@ condition. For example:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @Controller
- @RequestMapping(path = "/pets/{petId}", method = RequestMethod.GET, **produces = MediaType.APPLICATION_JSON_UTF8_VALUE**)
+ @GetMapping(path = "/pets/{petId}", **produces = MediaType.APPLICATION_JSON_UTF8_VALUE**)
@ResponseBody
public Pet getPet(@PathVariable String petId, Model model) {
// implementation omitted
@@ -1100,8 +1185,8 @@ the `UTF-8` charset.
====
Just like with __consumes__, producible media type expressions can be negated as in
-__!text/plain__ to match to all requests other than those with an __Accept__ header
-value of __text/plain__. Also consider using constants provided in `MediaType` such
+`!text/plain` to match to all requests other than those with an `Accept` header
+value of `text/plain`. Also consider using constants provided in `MediaType` such
as `APPLICATION_JSON_VALUE` and `APPLICATION_JSON_UTF8_VALUE`.
[TIP]
@@ -1126,7 +1211,7 @@ example with a request parameter value condition:
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {
- @RequestMapping(path = "/pets/{petId}", method = RequestMethod.GET, **params="myParam=myValue"**)
+ @GetMapping(path = "/pets/{petId}", **params = "myParam=myValue"**)
public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
// implementation omitted
}
@@ -1144,7 +1229,7 @@ specific request header value:
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {
- @RequestMapping(path = "/pets", method = RequestMethod.GET, **headers="myHeader=myValue"**)
+ @GetMapping(path = "/pets", **headers = "myHeader=myValue"**)
public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
// implementation omitted
}
@@ -1162,13 +1247,35 @@ respectively instead. They are intended specifically for that purpose.
====
+[[mvc-ann-requestmapping-head-options]]
+==== HTTP HEAD and HTTP OPTIONS
+
+`@RequestMapping` methods mapped to "GET" are also implicitly mapped to "HEAD",
+i.e. there is no need to have "HEAD" explicitly declared. An HTTP HEAD request
+is processed as if it were an HTTP GET except instead of writing the body only
+the number of bytes are counted and the "Content-Length" header set.
+
+`@RequestMapping` methods have built-in support for HTTP OPTIONS. By default an
+HTTP OPTIONS request is handled by setting the "Allow" response header to the
+HTTP methods explicitly declared on all `@RequestMapping` methods with matching
+URL patterns. When no HTTP methods are explicitly declared the "Allow" header
+is set to "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS". Ideally always declare the
+HTTP method(s) that an `@RequestMapping` method is intended to handle, or alternatively
+use one of the dedicated _composed_ `@RequestMapping` variants (see
+<<mvc-ann-requestmapping-composed>>).
+
+Although not necessary an `@RequestMapping` method can be mapped to and handle
+either HTTP HEAD or HTTP OPTIONS, or both.
+
+
+
[[mvc-ann-methods]]
=== Defining @RequestMapping handler methods
-An `@RequestMapping` handler method can have a very flexible signatures. The supported
+`@RequestMapping` handler methods can have very flexible signatures. The supported
method arguments and return values are described in the following section. Most
-arguments can be used in arbitrary order with the only exception of `BindingResult`
+arguments can be used in arbitrary order with the only exception being `BindingResult`
arguments. This is described in the next section.
[NOTE]
@@ -1195,7 +1302,7 @@ The following are the supported method arguments:
[NOTE]
====
Session access may not be thread-safe, in particular in a Servlet environment. Consider
-setting the ++RequestMappingHandlerAdapter++'s "synchronizeOnSession" flag to "true" if
+setting the ``RequestMappingHandlerAdapter``'s "synchronizeOnSession" flag to "true" if
multiple requests are allowed to access a session concurrently.
====
@@ -1226,13 +1333,18 @@ multiple requests are allowed to access a session concurrently.
See <<mvc-ann-requestheader>>.
* `@RequestBody` annotated parameters for access to the HTTP request body. Parameter
values are converted to the declared method argument type using
- ++HttpMessageConverter++s. See <<mvc-ann-requestbody>>.
+ ``HttpMessageConverter``s. See <<mvc-ann-requestbody>>.
* `@RequestPart` annotated parameters for access to the content of a
"multipart/form-data" request part. See <<mvc-multipart-forms-non-browsers>> and
<<mvc-multipart>>.
+* `@SessionAttribute` annotated parameters for access to existing, permanent
+ session attributes (e.g. user authentication object) as opposed to model
+ attributes temporarily stored in the session as part of a controller workflow
+ via `@SessionAttributes`.
+* `@RequestAttribute` annotated parameters for access to request attributes.
* `HttpEntity<?>` parameters for access to the Servlet request HTTP headers and
contents. The request stream will be converted to the entity body using
- ++HttpMessageConverter++s. See <<mvc-ann-httpentity>>.
+ ``HttpMessageConverter``s. See <<mvc-ann-httpentity>>.
* `java.util.Map` / `org.springframework.ui.Model` / `org.springframework.ui.ModelMap`
for enriching the implicit model that is exposed to the web view.
* `org.springframework.web.servlet.mvc.support.RedirectAttributes` to specify the exact
@@ -1245,7 +1357,7 @@ multiple requests are allowed to access a session concurrently.
methods and/or the HandlerAdapter configuration. See the `webBindingInitializer`
property on `RequestMappingHandlerAdapter`. Such command objects along with their
validation results will be exposed as model attributes by default, using the command
- class class name - e.g. model attribute "orderAddress" for a command object of type
+ class name - e.g. model attribute "orderAddress" for a command object of type
"some.package.OrderAddress". The `ModelAttribute` annotation can be used on a method
argument to customize the model attribute name used.
* `org.springframework.validation.Errors` /
@@ -1267,7 +1379,7 @@ sample won't work:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RequestMapping(method = RequestMethod.POST)
+ @PostMapping
public String processSubmit(**@ModelAttribute("pet") Pet pet**, Model model, **BindingResult result**) { ... }
----
@@ -1277,7 +1389,7 @@ this working you have to reorder the parameters as follows:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RequestMapping(method = RequestMethod.POST)
+ @PostMapping
public String processSubmit(**@ModelAttribute("pet") Pet pet**, **BindingResult result**, Model model) { ... }
----
@@ -1315,10 +1427,10 @@ The following are the supported return types:
signature).
* If the method is annotated with `@ResponseBody`, the return type is written to the
response HTTP body. The return value will be converted to the declared method argument
- type using ++HttpMessageConverter++s. See <<mvc-ann-responsebody>>.
+ type using ``HttpMessageConverter``s. See <<mvc-ann-responsebody>>.
* An `HttpEntity<?>` or `ResponseEntity<?>` object to provide access to the Servlet
response HTTP headers and contents. The entity body will be converted to the response
- stream using ++HttpMessageConverter++s. See <<mvc-ann-httpentity>>.
+ stream using ``HttpMessageConverter``s. See <<mvc-ann-httpentity>>.
* An `HttpHeaders` object to return a response with no body.
* A `Callable<?>` can be returned when the application wants to produce the return value
asynchronously in a thread managed by Spring MVC.
@@ -1357,7 +1469,7 @@ The following code snippet shows the usage:
// ...
- @RequestMapping(method = RequestMethod.GET)
+ @GetMapping
public String setupForm(**@RequestParam("petId") int petId**, ModelMap model) {
Pet pet = this.clinic.loadPet(petId);
model.addAttribute("pet", pet);
@@ -1370,7 +1482,7 @@ The following code snippet shows the usage:
----
Parameters using this annotation are required by default, but you can specify that a
-parameter is optional by setting ++@RequestParam++'s `required` attribute to `false`
+parameter is optional by setting ``@RequestParam``'s `required` attribute to `false`
(e.g., `@RequestParam(path="id", required=false)`).
Type conversion is applied automatically if the target method parameter type is not
@@ -1389,7 +1501,7 @@ be bound to the value of the HTTP request body. For example:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RequestMapping(path = "/something", method = RequestMethod.PUT)
+ @PutMapping("/something")
public void handle(@RequestBody String body, Writer writer) throws IOException {
writer.write(body);
}
@@ -1461,14 +1573,14 @@ or the MVC Java config.
[[mvc-ann-responsebody]]
==== Mapping the response body with the @ResponseBody annotation
-The `@ResponseBody` annotation is similar to `@RequestBody`. This annotation can be put
+The `@ResponseBody` annotation is similar to `@RequestBody`. This annotation can be placed
on a method and indicates that the return type should be written straight to the HTTP
response body (and not placed in a Model, or interpreted as a view name). For example:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RequestMapping(path = "/something", method = RequestMethod.PUT)
+ @GetMapping("/something")
@ResponseBody
public String helloWorld() {
return "Hello World";
@@ -1487,7 +1599,7 @@ section and <<rest-message-conversion,Message Converters>>.
It's a very common use case to have Controllers implement a REST API, thus serving only
JSON, XML or custom MediaType content. For convenience, instead of annotating all your
-`@RequestMapping` methods with `@ResponseBody`, you can annotate your Controller Class
+`@RequestMapping` methods with `@ResponseBody`, you can annotate your controller Class
with `@RestController`.
{api-spring-framework}/web/bind/annotation/RestController.html[`@RestController`]
@@ -1495,8 +1607,9 @@ is a stereotype annotation that combines `@ResponseBody` and `@Controller`. More
that, it gives more meaning to your Controller and also may carry additional semantics
in future releases of the framework.
-As with regular ++@Controller++s, a `@RestController` may be assisted by a
-`@ControllerAdvice` Bean. See the <<mvc-ann-controller-advice>> section for more details.
+As with regular ``@Controller``s, a `@RestController` may be assisted by
+`@ControllerAdvice` or `@RestControllerAdvice` beans. See the <<mvc-ann-controller-advice>>
+section for more details.
[[mvc-ann-httpentity]]
==== Using HttpEntity
@@ -1578,7 +1691,7 @@ depending on your needs.
A controller can have any number of `@ModelAttribute` methods. All such methods are
invoked before `@RequestMapping` methods of the same controller.
-`@ModelAttribute` methods can also be defined in an ++@ControllerAdvice++-annotated class
+`@ModelAttribute` methods can also be defined in an ``@ControllerAdvice``-annotated class
and such methods apply to many controllers. See the <<mvc-ann-controller-advice>> section
for more details.
@@ -1595,8 +1708,8 @@ i.e., with or without an attribute name.
The `@ModelAttribute` annotation can be used on `@RequestMapping` methods as well. In
that case the return value of the `@RequestMapping` method is interpreted as a model
-attribute rather than as a view name. The view name is derived from view name
-conventions instead much like for methods returning void -- see <<mvc-coc-r2vnt>>.
+attribute rather than as a view name. The view name is then derived based on view name
+conventions instead, much like for methods returning `void` -- see <<mvc-coc-r2vnt>>.
[[mvc-ann-modelattrib-method-args]]
@@ -1615,7 +1728,7 @@ form field individually.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RequestMapping(path = "/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
+ @PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(**@ModelAttribute Pet pet**) { }
----
@@ -1629,7 +1742,7 @@ Given the above example where can the Pet instance come from? There are several
more detail below).
* It may be instantiated using its default constructor.
-An `@ModelAttribute` method is a common way to to retrieve an attribute from the
+An `@ModelAttribute` method is a common way to retrieve an attribute from the
database, which may optionally be stored between requests through the use of
`@SessionAttributes`. In some cases it may be convenient to retrieve the attribute by
using an URI template variable and a type converter. Here is an example:
@@ -1637,9 +1750,9 @@ using an URI template variable and a type converter. Here is an example:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RequestMapping(path = "/accounts/{account}", method = RequestMethod.PUT)
+ @PutMapping("/accounts/{account}")
public String save(@ModelAttribute("account") Account account) {
-
+ // ...
}
----
@@ -1662,7 +1775,7 @@ following the `@ModelAttribute` argument:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RequestMapping(path = "/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
+ @PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(**@ModelAttribute("pet") Pet pet**, BindingResult result) {
if (result.hasErrors()) {
@@ -1678,6 +1791,31 @@ With a `BindingResult` you can check if errors were found in which case it's com
render the same form where the errors can be shown with the help of Spring's `<errors>`
form tag.
+Note that in some cases it may be useful to gain access to an attribute in the
+model without data binding. For such cases you may inject the `Model` into the
+controller or alternatively use the `binding` flag on the annotation:
+
+[source,java,indent=0]
+[subs="verbatim,quotes"]
+----
+@ModelAttribute
+public AccountForm setUpForm() {
+ return new AccountForm();
+}
+
+@ModelAttribute
+public Account findAccount(@PathVariable String accountId) {
+ return accountRepository.findOne(accountId);
+}
+
+@PostMapping("update")
+public String update(@Valid AccountUpdateForm form, BindingResult result,
+ **@ModelAttribute(binding=false)** Account account) {
+
+ // ...
+}
+----
+
In addition to data binding you can also invoke validation using your own custom
validator passing the same `BindingResult` that was used to record data binding errors.
That allows for data binding and validation errors to be accumulated in one place and
@@ -1686,7 +1824,7 @@ subsequently reported back to the user:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RequestMapping(path = "/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
+ @PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(**@ModelAttribute("pet") Pet pet**, BindingResult result) {
new PetValidator().validate(pet, result);
@@ -1705,7 +1843,7 @@ annotation:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RequestMapping(path = "/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
+ @PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(**@Valid @ModelAttribute("pet") Pet pet**, BindingResult result) {
if (result.hasErrors()) {
@@ -1721,6 +1859,7 @@ See <<validation-beanvalidation>> and <<validation>> for details on how to confi
use validation.
+
[[mvc-ann-sessionattrib]]
==== Using @SessionAttributes to store model attributes in the HTTP session between requests
@@ -1744,6 +1883,49 @@ attribute name:
----
+[[mvc-ann-sessionattrib-global]]
+==== Using @SessionAttribute to access pre-existing global session attributes
+
+If you need access to pre-existing session attributes that are managed globally,
+i.e. outside the controller (e.g. by a filter), and may or may not be present
+use the `@SessionAttribute` annotation on a method parameter:
+
+[source,java,indent=0]
+[subs="verbatim,quotes"]
+----
+ @RequestMapping("/")
+ public String handle(**@SessionAttribute** User user) {
+ // ...
+ }
+----
+
+For use cases that require adding or removing session attributes consider injecting
+`org.springframework.web.context.request.WebRequest` or
+`javax.servlet.http.HttpSession` into the controller method.
+
+For temporary storage of model attributes in the session as part of a controller
+workflow consider using `SessionAttributes` as described in
+<<mvc-ann-sessionattrib>>.
+
+
+[[mvc-ann-requestattrib]]
+==== Using @RequestAttribute to access request attributes
+
+Similar to `@SessionAttribute` the `@RequestAttribute` annotation can be used to
+access pre-existing request attributes created by a filter or interceptor:
+
+[source,java,indent=0]
+[subs="verbatim,quotes"]
+----
+ @RequestMapping("/")
+ public String handle(**@RequestAttribute** Client client) {
+ // ...
+ }
+----
+
+
+
+
[[mvc-ann-form-urlencoded-data]]
==== Working with "application/x-www-form-urlencoded" data
@@ -1898,8 +2080,8 @@ binding directly within your controller class. `@InitBinder` identifies methods
initialize the `WebDataBinder` that will be used to populate command and form object
arguments of annotated handler methods.
-Such init-binder methods support all arguments that `@RequestMapping` supports, except
-for command/form objects and corresponding validation result objects. Init-binder
+Such init-binder methods support all arguments that `@RequestMapping` methods support,
+except for command/form objects and corresponding validation result objects. Init-binder
methods must not have a return value. Thus, they are usually declared as `void`.
Typical arguments include `WebDataBinder` in combination with `WebRequest` or
`java.util.Locale`, allowing code to register context-specific editors.
@@ -1914,7 +2096,7 @@ The following example demonstrates the use of `@InitBinder` to configure a
public class MyFormController {
**@InitBinder**
- public void initBinder(WebDataBinder binder) {
+ protected void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
@@ -1937,7 +2119,7 @@ controller-specific tweaking of the binding rules.
public class MyFormController {
**@InitBinder**
- public void initBinder(WebDataBinder binder) {
+ protected void initBinder(WebDataBinder binder) {
binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
}
@@ -1969,13 +2151,13 @@ PropertyEditors required by several of the PetClinic controllers.
</bean>
----
-`@InitBinder` methods can also be defined in an ++@ControllerAdvice++-annotated class in
+`@InitBinder` methods can also be defined in an ``@ControllerAdvice``-annotated class in
which case they apply to matching controllers. This provides an alternative to using a
`WebBindingInitializer`. See the <<mvc-ann-controller-advice>> section for more details.
[[mvc-ann-controller-advice]]
-==== Advising controllers with @ControllerAdvice
+==== Advising controllers with @ControllerAdvice and @RestControllerAdvice
The `@ControllerAdvice` annotation is a component annotation allowing implementation
classes to be auto-detected through classpath scanning. It is automatically enabled when
@@ -1986,8 +2168,10 @@ Classes annotated with `@ControllerAdvice` can contain `@ExceptionHandler`,
`@RequestMapping` methods across all controller hierarchies as opposed to the controller
hierarchy within which they are declared.
-The `@ControllerAdvice` annotation can also target a subset of controllers with its
-attributes:
+`@RestControllerAdvice` is an alternative where `@ExceptionHandler` methods
+assume `@ResponseBody` semantics by default.
+
+Both `@ControllerAdvice` and `@RestControllerAdvice` can target a subset of controllers:
[source,java,indent=0]
[subs="verbatim,quotes"]
@@ -2026,7 +2210,7 @@ the view class or interface to be used:
@RestController
public class UserController {
- @RequestMapping(path = "/user", method = RequestMethod.GET)
+ @GetMapping("/user")
@JsonView(User.WithoutPasswordView.class)
public User getUser() {
return new User("eric", "7!jd#h23");
@@ -2078,7 +2262,7 @@ to the model:
@Controller
public class UserController extends AbstractController {
- @RequestMapping(path = "/user", method = RequestMethod.GET)
+ @GetMapping("/user")
public String getUser(Model model) {
model.addAttribute("user", new User("eric", "7!jd#h23"));
model.addAttribute(JsonView.class.getName(), User.WithoutPasswordView.class);
@@ -2126,7 +2310,7 @@ is an example of such a controller method:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RequestMapping(method=RequestMethod.POST)
+ @PostMapping
public Callable<String> processUpload(final MultipartFile file) {
return new Callable<String>() {
@@ -2357,7 +2541,7 @@ For applications configured with a `web.xml` be sure to update to version 3.0:
----
Asynchronous support must be enabled on the `DispatcherServlet` through the
-`<async-supported>true</async-supported>` web.xml sub-element. Additionally
+`<async-supported>true</async-supported>` sub-element in `web.xml`. Additionally
any `Filter` that participates in asyncrequest processing must be configured
to support the ASYNC dispatcher type. It should be safe to enable the ASYNC
dispatcher type for all filters provided with the Spring Framework since they
@@ -2396,7 +2580,7 @@ Below is some example web.xml configuration:
If using Servlet 3, Java based configuration for example via `WebApplicationInitializer`,
you'll also need to set the "asyncSupported" flag as well as the ASYNC dispatcher type
just like with `web.xml`. To simplify all this configuration, consider extending
-`AbstractDispatcherServletInitializer` or
+`AbstractDispatcherServletInitializer`, or better
`AbstractAnnotationConfigDispatcherServletInitializer` which automatically
set those options and make it very easy to register `Filter` instances.
@@ -2442,7 +2626,7 @@ don't need to do that because the `RequestMappingHandlerMapping` automatically l
all `HandlerMapping` classes extending from `AbstractHandlerMapping` have the following
properties that you can use to customize their behavior:
-* `interceptors` List of interceptors to use. ++HandlerInterceptor++s are discussed in
+* `interceptors` List of interceptors to use. ``HandlerInterceptor``s are discussed in
<<mvc-handlermapping-interceptor>>.
* `defaultHandler` Default handler to use, when this handler mapping does not result in
a matching handler.
@@ -2519,7 +2703,7 @@ the example below:
<property name="openingTime" value="9"/>
<property name="closingTime" value="18"/>
</bean>
- <beans>
+ </beans>
----
[source,java,indent=0]
@@ -2823,7 +3007,7 @@ through `Model` nor `RedirectAttributes`. For example:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RequestMapping(path = "/files/{path}", method = RequestMethod.POST)
+ @PostMapping("/files/{path}")
public String upload(...) {
// ...
return "redirect:files/{path}";
@@ -2995,7 +3179,7 @@ application/atom+xml is shown below.
private List<SampleContent> contentList = new ArrayList<SampleContent>();
- @RequestMapping(path="/content", method=RequestMethod.GET)
+ @GetMapping("/content")
public ModelAndView getContent() {
ModelAndView mav = new ModelAndView();
mav.setViewName("content");
@@ -3143,11 +3327,11 @@ Spring MVC also provides a mechanism for building links to controller methods. F
@RequestMapping("/hotels/{hotel}")
public class BookingController {
- @RequestMapping("/bookings/{booking}")
+ @GetMapping("/bookings/{booking}")
public String getBooking(@PathVariable Long booking) {
// ...
-
+ }
}
----
@@ -3249,7 +3433,7 @@ You can prepare a link from a JSP as follows:
----
<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
...
-<a href="${s:mvcUrl(''PAC#getAddress'').arg(0,''US'').buildAndExpand(''123'')}">Get Address</a>
+<a href="${s:mvcUrl('PAC#getAddress').arg(0,'US').buildAndExpand('123')}">Get Address</a>
----
The above example relies on the `mvcUrl` JSP function declared in the Spring tag library
@@ -3318,7 +3502,7 @@ maximum age. Find below an example of defining a `CookieLocaleResolver`.
<property name="cookieName" value="clientlanguage"/>
<!-- in seconds. If set to -1, the cookie is not persisted (deleted when browser shuts down) -->
- <property name="cookieMaxAge" value="100000">
+ <property name="cookieMaxAge" value="100000"/>
</bean>
----
@@ -3447,9 +3631,9 @@ defined in the previous example to customize the look and feel:
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<html>
<head>
- <link rel="stylesheet" href="<spring:theme code=''styleSheet''/>" type="text/css"/>
+ <link rel="stylesheet" href="<spring:theme code='styleSheet'/>" type="text/css"/>
</head>
- <body style="background=<spring:theme code=''background''/>">
+ <body style="background=<spring:theme code='background'/>">
...
</body>
</html>
@@ -3608,7 +3792,7 @@ use `MultipartHttpServletRequest` or `MultipartFile` in the method parameters:
@Controller
public class FileUploadController {
- @RequestMapping(path = "/form", method = RequestMethod.POST)
+ @PostMapping("/form")
public String handleFormUpload(@RequestParam("name") String name,
@RequestParam("file") MultipartFile file) {
@@ -3637,7 +3821,7 @@ the method parameter:
@Controller
public class FileUploadController {
- @RequestMapping(path = "/form", method = RequestMethod.POST)
+ @PostMapping("/form")
public String handleFormUpload(@RequestParam("name") String name,
@RequestParam("file") Part file) {
@@ -3695,7 +3879,7 @@ multipart:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RequestMapping(path = "/someUrl", method = RequestMethod.POST)
+ @PostMapping("/someUrl")
public String onSubmit(**@RequestPart("meta-data") MetaData metadata,
@RequestPart("file-data") MultipartFile file**) {
@@ -3754,7 +3938,7 @@ may be more convenient to directly set the status of the response and optionally
error content to the body of the response.
You can do that with `@ExceptionHandler` methods. When declared within a controller such
-methods apply to exceptions raised by `@RequestMapping` methods of that contoroller (or
+methods apply to exceptions raised by `@RequestMapping` methods of that controller (or
any of its sub-classes). You can also declare an `@ExceptionHandler` method within an
`@ControllerAdvice` class in which case it handles exceptions from `@RequestMapping`
methods from many controllers. Below is an example of a controller-local
@@ -3802,7 +3986,7 @@ raised the status code may indicate a client error (4xx) or a server error (5xx)
The `DefaultHandlerExceptionResolver` translates Spring MVC exceptions to specific error
status codes. It is registered by default with the MVC namespace, the MVC Java config,
-and also by the the `DispatcherServlet` (i.e. when not using the MVC namespace or Java
+and also by the `DispatcherServlet` (i.e. when not using the MVC namespace or Java
config). Listed below are some of the exceptions handled by this resolver and the
corresponding status codes:
@@ -4205,7 +4389,6 @@ responsible for this, along with conditional headers such as `'Last-Modified'` a
The `'Cache-Control'` HTTP response header advises private caches (e.g. browsers) and
public caches (e.g. proxies) on how they can cache HTTP responses for further reuse.
-mvc-config-static-resources
An http://en.wikipedia.org/wiki/HTTP_ETag[ETag] (entity tag) is an HTTP response header
returned by an HTTP/1.1 compliant web server used to determine change in content at a
given URL. It can be considered to be the more sophisticated successor to the
@@ -4305,14 +4488,14 @@ This involves calculating a lastModified `long` and/or an Etag value for a given
comparing it against the `'If-Modified-Since'` request header value, and potentially returning
a response with status code 304 (Not Modified).
-As described in <<mvc-ann-httpentity>>, Controllers can interact with the request/response using
+As described in <<mvc-ann-httpentity>>, controllers can interact with the request/response using
`HttpEntity` types. Controllers returning `ResponseEntity` can include HTTP caching information
in responses like this:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
- @RequestMapping("/book/{id}")
+ @GetMapping("/book/{id}")
public ResponseEntity<Book> showBook(@PathVariable Long id) {
Book book = findBook(id);
@@ -4353,17 +4536,24 @@ This can be achieved as follows:
----
There are two key elements here: calling `request.checkNotModified(lastModified)` and
-returning `null`. The former sets the response status to 304 before it returns `true`.
+returning `null`. The former sets the appropriate response status and headers
+before it returns `true`.
The latter, in combination with the former, causes Spring MVC to do no further
processing of the request.
Note that there are 3 variants for this:
* `request.checkNotModified(lastModified)` compares lastModified with the
-`'If-Modified-Since'` request header
-* `request.checkNotModified(eTag)` compares eTag with the `'ETag'` request header
+`'If-Modified-Since'` or `'If-Unmodified-Since'` request header
+* `request.checkNotModified(eTag)` compares eTag with the `'If-None-Match'` request header
* `request.checkNotModified(eTag, lastModified)` does both, meaning that both
-conditions should be valid for the server to issue an `HTTP 304 Not Modified` response
+conditions should be valid
+
+When receiving conditional `'GET'`/`'HEAD'` requests, `checkNotModified` will check
+that the resource has not been modified and if so, it will result in a `HTTP 304 Not Modified`
+response. In case of conditional `'POST'`/`'PUT'`/`'DELETE'` requests, `checkNotModified`
+will check that the resource has not been modified and if it has been, it will result in a
+`HTTP 409 Precondition Failed` response to prevent concurrent modifications.
[[mvc-httpcaching-shallowetag]]
@@ -4376,14 +4566,15 @@ ETags, more about that later).The filter caches the content of the rendered JSP
other content), generates an MD5 hash over that, and returns that as an ETag header in
the response. The next time a client sends a request for the same resource, it uses that
hash as the `If-None-Match` value. The filter detects this, renders the view again, and
-compares the two hashes. If they are equal, a `304` is returned. This filter will not
-save processing power, as the view is still rendered. The only thing it saves is
-bandwidth, as the rendered response is not sent back over the wire.
+compares the two hashes. If they are equal, a `304` is returned.
Note that this strategy saves network bandwidth but not CPU, as the full response must be
computed for each request. Other strategies at the controller level (described above) can
save network bandwidth and avoid computation.
-mvc-config-static-resources
+
+This filter has a `writeWeakETag` parameter that configures the filter to write Weak ETags,
+like this: `W/"02a2d595e6ed9a0b24f027f2b63b134d6"`, as defined in
+https://tools.ietf.org/html/rfc7232#section-2.3[RFC 7232 Section 2.3].
You configure the `ShallowEtagHeaderFilter` in `web.xml`:
@@ -4393,6 +4584,12 @@ You configure the `ShallowEtagHeaderFilter` in `web.xml`:
<filter>
<filter-name>etagFilter</filter-name>
<filter-class>org.springframework.web.filter.ShallowEtagHeaderFilter</filter-class>
+ <!-- Optional parameter that configures the filter to write weak ETags
+ <init-param>
+ <param-name>writeWeakETag</param-name>
+ <param-value>true</param-value>
+ </init-param>
+ -->
</filter>
<filter-mapping>
@@ -4453,7 +4650,9 @@ implementation is detected and automatically used to initialize any Servlet 3 co
An abstract base class implementation of `WebApplicationInitializer` named
`AbstractDispatcherServletInitializer` makes it even easier to register the
`DispatcherServlet` by simply overriding methods to specify the servlet mapping and the
-location of the `DispatcherServlet` configuration:
+location of the `DispatcherServlet` configuration.
+
+This is recommended for applications that use Java-based Spring configuration:
[source,java,indent=0]
[subs="verbatim,quotes"]
@@ -4478,8 +4677,7 @@ location of the `DispatcherServlet` configuration:
}
----
-The above example is for an application that uses Java-based Spring configuration. If
-using XML-based Spring configuration, extend directly from
+If using XML-based Spring configuration, you should extend directly from
`AbstractDispatcherServletInitializer`:
[source,java,indent=0]
@@ -4547,9 +4745,9 @@ the MVC Java config and the MVC XML namespace.
The MVC Java config and the MVC namespace provide similar default configuration that
overrides the `DispatcherServlet` defaults. The goal is to spare most applications from
-having to having to create the same configuration and also to provide higher-level
-constructs for configuring Spring MVC that serve as a simple starting point and require
-little or no prior knowledge of the underlying configuration.
+having to create the same configuration and also to provide higher-level constructs for
+configuring Spring MVC that serve as a simple starting point and require little or no
+prior knowledge of the underlying configuration.
You can choose either the MVC Java config or the MVC namespace depending on your
preference. Also as you will see further below, with the MVC Java config it is easier to
diff --git a/src/asciidoc/web-portlet.adoc b/src/asciidoc/web-portlet.adoc
index 91d75608..b93a642e 100644
--- a/src/asciidoc/web-portlet.adoc
+++ b/src/asciidoc/web-portlet.adoc
@@ -333,7 +333,7 @@ action request, handling a render request, and returning a model and a view.
=== AbstractController and PortletContentGenerator
Of course, just a `Controller` interface isn't enough. To provide a basic
-infrastructure, all of Spring Portlet MVC's ++Controller++s inherit from
+infrastructure, all of Spring Portlet MVC's ``Controller``s inherit from
`AbstractController`, a class offering access to Spring's `ApplicationContext` and
control over caching.
@@ -542,7 +542,7 @@ The rest of this section describes three of Spring Portlet MVC's most commonly u
handler mappings. They all extend `AbstractHandlerMapping` and share the following
properties:
-* `interceptors`: The list of interceptors to use. ++HandlerInterceptor++s are discussed
+* `interceptors`: The list of interceptors to use. ``HandlerInterceptor``s are discussed
in <<portlet-handlermapping-interceptor>>.
* `defaultHandler`: The default handler to use, when this handler mapping does not
result in a matching handler.
@@ -1006,7 +1006,7 @@ register any custom property editor because there is no type conversion to be pe
[[portlet-exceptionresolver]]
== Handling exceptions
-Just like Servlet MVC, Portlet MVC provides ++HandlerExceptionResolver++s to ease the
+Just like Servlet MVC, Portlet MVC provides ``HandlerExceptionResolver``s to ease the
pain of unexpected exceptions that occur while your request is being processed by a
handler that matched the request. Portlet MVC also provides a portlet-specific, concrete
`SimpleMappingExceptionResolver` that enables you to take the class name of any
@@ -1462,8 +1462,8 @@ The following example demonstrates the use of `@InitBinder` for configuring a
public class MyFormController {
@InitBinder
- public void initBinder(WebDataBinder binder) {
- SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+ protected void initBinder(WebDataBinder binder) {
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}
diff --git a/src/asciidoc/web-view.adoc b/src/asciidoc/web-view.adoc
index a62ed593..3735a8d3 100644
--- a/src/asciidoc/web-view.adoc
+++ b/src/asciidoc/web-view.adoc
@@ -34,12 +34,12 @@ is another view technology, supported by Spring. This template engine is a templ
aimed at generating XML-like markup (XML, XHTML, HTML5, ...​), but that can be used to generate any
text based content.
-This requires Groovy 2.3.1+ on the the classpath.
+This requires Groovy 2.3.1+ on the classpath.
[[view-groovymarkup-configuration]]
=== Configuration
-Configuring the Groovy Markup Teplate Engine is quite easy:
+Configuring the Groovy Markup Template Engine is quite easy:
[source,java,indent=0]
[subs="verbatim,quotes"]
@@ -106,6 +106,13 @@ applications. The languages are quite similar and serve similar needs and so are
considered together in this section. For semantic and syntactic differences between the
two languages, see the http://www.freemarker.org[FreeMarker] web site.
+[NOTE]
+====
+As of Spring Framework 4.3, Velocity support has been deprecated due to six years
+without active maintenance of the Apache Velocity project. We recommend Spring's
+FreeMarker support instead, or Thymeleaf which comes with Spring support itself.
+====
+
[[view-velocity-dependencies]]
@@ -292,13 +299,10 @@ directly, the files are called spring.vm / spring.ftl and are in the packages
[[view-simple-binding]]
==== Simple binding
-In your html forms (vm / ftl templates) that act as the 'formView' for a Spring form
+In your HTML forms (vm / ftl templates) which act as a form view for a Spring MVC
controller, you can use code similar to the following to bind to field values and
display error messages for each input field in similar fashion to the JSP equivalent.
-Note that the name of the command object is "command" by default, but can be overridden
-in your MVC configuration by setting the 'commandName' bean property on your form
-controller. Example code is shown below for the `personFormV` and `personFormF` views
-configured earlier;
+Example code is shown below for the `personFormV`/`personFormF` views configured earlier:
[source,xml,indent=0]
[subs="verbatim,quotes"]
@@ -308,7 +312,7 @@ configured earlier;
...
<form action="" method="POST">
Name:
- #springBind( "command.name" )
+ #springBind("myModelObject.name")
<input type="text"
name="${status.expression}"
value="$!status.value"/><br>
@@ -331,10 +335,10 @@ configured earlier;
...
<form action="" method="POST">
Name:
- <@spring.bind "command.name"/>
+ <@spring.bind "myModelObject.name"/>
<input type="text"
name="${spring.status.expression}"
- value="${spring.status.value?default("")}"/><br>
+ value="${spring.status.value?html}"/><br>
<#list spring.status.errorMessages as error> <b>${error}</b> <br> </#list>
<br>
...
@@ -818,7 +822,7 @@ The preceding JSP assumes that the variable name of the form backing object is
[source,xml,indent=0]
[subs="verbatim,quotes"]
----
- <form:form commandName="user">
+ <form:form modelAttribute="user">
<table>
<tr>
<td>First Name:</td>
@@ -1390,6 +1394,7 @@ The HTML would look like:
<input type="submit" value="Save Changes"/>
</td>
</tr>
+ </table>
</form>
----
diff --git a/src/asciidoc/web-websocket.adoc b/src/asciidoc/web-websocket.adoc
index 9a2b8087..2776f53c 100644
--- a/src/asciidoc/web-websocket.adoc
+++ b/src/asciidoc/web-websocket.adoc
@@ -1359,7 +1359,8 @@ The return value from an `@MessageMapping` method is converted with a
of a new message that is then sent, by default, to the `"brokerChannel"` with
the same destination as the client message but using the prefix `"/topic"` by
default. An `@SendTo` message level annotation can be used to specify any
-other destination instead.
+other destination instead. It can also be set a class-level to share a common
+destination.
An `@SubscribeMapping` annotation can also be used to map subscription requests
to `@Controller` methods. It is supported on the method level, but can also be
@@ -1529,13 +1530,13 @@ This connection is used for messages originating from the server-side applicatio
only, not for receiving messages. You can configure the STOMP credentials
for this connection, i.e. the STOMP frame `login` and `passcode` headers. This
is exposed in both the XML namespace and the Java config as the
-++systemLogin++/++systemPasscode++ properties with default values ++guest++/++guest++.
+``systemLogin``/``systemPasscode`` properties with default values ``guest``/``guest``.
The STOMP broker relay also creates a separate TCP connection for every connected
WebSocket client. You can configure the STOMP credentials to use for all TCP
connections created on behalf of clients. This is exposed in both the XML namespace
-and the Java config as the ++clientLogin++/++clientPasscode++ properties with default
-values ++guest++/++guest++.
+and the Java config as the ``clientLogin``/``clientPasscode`` properties with default
+values ``guest``/``guest``.
[NOTE]
====
@@ -1699,7 +1700,8 @@ than their name and the generic destination. This is also supported through an
annotation as well as a messaging template.
For example, a message-handling method can send messages to the user associated with
-the message being handled through the `@SendToUser` annotation:
+the message being handled through the `@SendToUser` annotation (also supported on
+the class-level to share a common destination):
[source,java,indent=0]
[subs="verbatim,quotes"]
@@ -1882,41 +1884,40 @@ to access information about the message.
[[websocket-stomp-client]]
=== STOMP Client
-Spring provides STOMP/WebSocket client and STOMP/TCP client support with the
-following built-in choices (other libraries can be adapted):
+Spring provides a STOMP over WebSocket client and a STOMP over TCP client.
-* `WebSocketStompClient` built on the Spring WebSocket API with
- support for standard JSR-356 WebSocket, Jetty 9, as well as SockJS
- for HTTP-based WebSocket emulation (see <<websocket-fallback-sockjs-client>>).
-* `Reactor11TcpStompClient` built on `NettyTcpClient` from the reactor-net project.
-
-To begin, create and configure the client:
+To begin create and configure `WebSocketStompClient`:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
-WebSocketClient transport = new StandardWebSocketClient();
-WebSocketStompClient stompClient = new WebSocketStompClient(transport);
+WebSocketClient webSocketClient = new StandardWebSocketClient();
+WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);
stompClient.setMessageConverter(new StringMessageConverter());
-stompClient.setTaskScheduler(taskScheduler); // for heartbeats, receipts
+stompClient.setTaskScheduler(taskScheduler); // for heartbeats
----
-Then connect to the WebSocket and provide a handler for the STOMP session:
+In the above example `StandardWebSocketClient` could be replaced with `SockJsClient`
+since that is also an implementation of `WebSocketClient`. The `SockJsClient` can
+use WebSocket or HTTP-based transport as a fallback. For more details see
+<<websocket-fallback-sockjs-client>>.
+
+Next establish a connection and provide a handler for the STOMP session:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
String url = "ws://127.0.0.1:8080/endpoint";
-StompSessionHandler handler = ... ;
-stompClient.connect(url, handler);
+StompSessionHandler sessionHandler = new MyStompSessionHandler();
+stompClient.connect(url, sessionHandler);
----
-When the session is ready for use, the handler is notified:
+When the session is ready for use the handler is notified:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
-public class MySessionHandler extends StompSessionHandlerAdapter {
+public class MyStompSessionHandler extends StompSessionHandlerAdapter {
@Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
@@ -1925,7 +1926,8 @@ public class MySessionHandler extends StompSessionHandlerAdapter {
}
----
-Send any Object as the payload and it will be serialized with a `MessageConverter`:
+Once the session is established any payload can be sent and that will be
+serialized with the configured `MessageConverter`:
[source,java,indent=0]
[subs="verbatim,quotes"]
@@ -1933,10 +1935,10 @@ Send any Object as the payload and it will be serialized with a `MessageConverte
session.send("/topic/foo", "payload");
----
-The subscribe methods take a `StompFrameHandler` for messages on the subscription
-and return a `Subscription` handle for unsubscribing. For each received message,
-the handler must help to select the target type and then handle the
-deserialized payload:
+You can also subscribe to destinations. The `subscribe` methods require a handler
+for messages on the subscription and return a `Subscription` handle that can be
+used to unsubscribe. For each received message the handler can specify the target
+Object type the payload should be deserialized to:
[source,java,indent=0]
[subs="verbatim,quotes"]
@@ -1956,11 +1958,19 @@ session.subscribe("/topic/foo", new StompFrameHandler() {
});
----
-STOMP supports heartbeats. To use this feature simply configure the
-`WebSocketStompClient` with a `TaskScheduler` and if desired customize the
-default heartbeat intervals (10, 10 seconds respectively by default) for
-write inactivity which causes a heartbeat to be sent and for read
-inactivity which closes the connection.
+To enable STOMP heartbeat configure `WebSocketStompClient` with a `TaskScheduler`
+and optionally customize the heartbeat intervals, 10 seconds for write inactivity
+which causes a heartbeat to be sent and 10 seconds for read inactivity which
+closes the connection.
+
+[NOTE]
+====
+When using `WebSocketStompClient` for performance tests to simulate thousands
+of clients from the same machine consider turning off heartbeats since each
+connection schedules its own heartbeat tasks and that's not optimized for a
+a large number of clients running on the same machine.
+====
+
The STOMP protocol also supports receipts where the client must add a "receipt"
header to which the server responds with a RECEIPT frame after the send or
@@ -2000,7 +2010,7 @@ public class MyController {
}
----
-It is also possible to declare a Spring-managed bean in the `"websocket"` scope.
+It is also possible to declare a Spring-managed bean in the `websocket` scope.
WebSocket-scoped beans can be injected into controllers and any channel interceptors
registered on the "clientInboundChannel". Those are typically singletons and live
longer than any individual WebSocket session. Therefore you will need to use a
@@ -2010,7 +2020,7 @@ scope proxy mode for WebSocket-scoped beans:
[subs="verbatim,quotes"]
----
@Component
-@Scope(name = "websocket", proxyMode = ScopedProxyMode.TARGET_CLASS)
+@Scope(scopeName = "websocket", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyBean {
@PostConstruct
diff --git a/src/asciidoc/web.adoc b/src/asciidoc/web.adoc
index debdaa66..e6388258 100644
--- a/src/asciidoc/web.adoc
+++ b/src/asciidoc/web.adoc
@@ -9,7 +9,7 @@ for WebSocket-style messaging in web applications.
Spring Framework's own web framework, <<mvc,Spring Web MVC>>, is covered in the
first couple of chapters. Subsequent chapters are concerned with Spring Framework's
-integration with other web technologies, such as <<jsf,JSF>> and.
+integration with other web technologies, such as <<jsf,JSF>>.
Following that is coverage of Spring Framework's MVC <<portlet,portlet framework>>.
diff --git a/src/asciidoc/whats-new.adoc b/src/asciidoc/whats-new.adoc
index 5ad8910d..bb1cb1e5 100644
--- a/src/asciidoc/whats-new.adoc
+++ b/src/asciidoc/whats-new.adoc
@@ -276,7 +276,7 @@ Spring 4.1 also improves its own caching abstraction significantly:
common settings to be shared at the class level **without** enabling any cache operation.
* Better exception handling of cached methods using `CacheErrorHandler`
-Spring 4.1 also has a breaking change in the `CacheInterface` as a new `putIfAbsent`
+Spring 4.1 also has a breaking change in the `Cache` interface as a new `putIfAbsent`
method has been added.
=== Web Improvements
@@ -368,9 +368,9 @@ method has been added.
* Test property sources which automatically override system and application property
sources can be configured via the new `@TestPropertySource` annotation.
** See <<testcontext-ctx-management-property-sources>> for details.
-* Default ++TestExecutionListener++s can now be automatically discovered.
+* Default ``TestExecutionListener``s can now be automatically discovered.
** See <<testcontext-tel-config-automatic-discovery>> for details.
-* Custom ++TestExecutionListener++s can now be automatically merged with the default
+* Custom ``TestExecutionListener``s can now be automatically merged with the default
listeners.
** See <<testcontext-tel-config-merging>> for details.
* The documentation for transactional testing support in the TestContext framework has
@@ -597,13 +597,13 @@ public @interface MyTestConfig {
subsequent release.
* `@Sql` now supports execution of _inlined SQL statements_ via a new
`statements` attribute.
-* The `ContextCache` that is used for caching ++ApplicationContext++s
+* The `ContextCache` that is used for caching ``ApplicationContext``s
between tests is now a public API with a default implementation that
can be replaced for custom caching needs.
* `DefaultTestContext`, `DefaultBootstrapContext`, and
`DefaultCacheAwareContextLoaderDelegate` are now public classes in the
`support` subpackage, allowing for custom extensions.
-* ++TestContextBootstrapper++s are now responsible for building the
+* ``TestContextBootstrapper``s are now responsible for building the
`TestContext`.
* In the Spring MVC Test framework, `MvcResult` details can now be logged
at `DEBUG` level or written to a custom `OutputStream` or `Writer`. See
@@ -616,9 +616,125 @@ public @interface MyTestConfig {
definition profiles.
* Embedded databases can now be automatically assigned a unique name,
allowing common test database configuration to be reused in different
- ++ApplicationContext++s within a test suite.
+ ``ApplicationContext``s within a test suite.
** See <<jdbc-embedded-database-unique-names>> for details.
* `MockHttpServletRequest` and `MockHttpServletResponse` now provide better
support for date header formatting via the `getDateHeader` and `setDateHeader`
methods.
+
+
+[[new-in-4.3]]
+== New Features and Enhancements in Spring Framework 4.3
+
+=== Core Container Improvements
+
+* Core container exceptions provide richer metadata to evaluate programmatically.
+* Java 8 default methods get detected as bean property getters/setters.
+* It is no longer necessary to specify the `@Autowired` annotation if the target
+ bean only defines one constructor.
+* `@Configuration` classes support constructor injection.
+* Any SpEL expression used to specify the `condition` of an `@EventListener` can
+ now refer to beans (e.g. `@beanName.method()`).
+* _Composed annotations_ can now override array attributes in meta-annotations
+ with a single element of the component type of the array. For example, the
+ `String[] path` attribute of `@RequestMapping` can be overridden with
+ `String path` in a composed annotation.
+* `@Scheduled` and `@Schedules` may now be used as _meta-annotations_ to create
+ custom _composed annotations_ with attribute overrides.
+* `@Scheduled` is properly supported on beans of any scope.
+
+=== Data Access Improvements
+
+* `jdbc:initialize-database` and `jdbc:embedded-database` support a configurable
+ separator to be applied to each script.
+
+=== Caching Improvements
+
+Spring 4.3 allows concurrent calls on a given key to be synchronized so that the
+value is only computed once. This is an opt-in feature that should be enabled via
+the new `sync` attribute on `@Cacheable`. This features introduces a breaking
+change in the `Cache` interface as a `get(Object key, Callable<T> valueLoader)`
+method has been added.
+
+Spring 4.3 also improves the caching abstraction as follows:
+
+* SpEL expressions in caches-related annotations can now refer to beans (i.e.
+ `@beanName.method()`).
+* `ConcurrentMapCacheManager` and `ConcurrentMapCache` now support the serialization
+ of cache entries via a new `storeByValue` attribute.
+* `@Cacheable`, `@CacheEvict`, `@CachePut`, and `@Caching` may now be used as
+ _meta-annotations_ to create custom _composed annotations_ with attribute overrides.
+
+=== JMS Improvements
+
+* `@SendTo` can now be specified at the class level to share a common reply destination.
+* `@JmsListener` and `@JmsListeners` may now be used as _meta-annotations_ to create
+ custom _composed annotations_ with attribute overrides.
+
+=== Web Improvements
+
+* Built-in support for <<mvc-ann-requestmapping-head-options,HTTP HEAD and HTTP OPTIONS>>.
+* New `@GetMapping`, `@PostMapping`, `@PutMapping`, `@DeleteMapping`, and `@PatchMapping`
+ _composed annotations_ for `@RequestMapping`.
+** See <<mvc-ann-requestmapping-composed,Composed @RequestMapping Variants>> for details.
+* New `@RequestScope`, `@SessionScope`, and `@ApplicationScope` _composed annotations_
+ for web scopes.
+** See <<beans-factory-scopes-request,Request scope>>,
+ <<beans-factory-scopes-session,Session scope>>, and
+ <<beans-factory-scopes-application,Application scope>> for details.
+* New `@RestControllerAdvice` annotation with combined `@ControllerAdvice` with `@ResponseBody` semantics.
+* `@ResponseStatus` is now supported at the class level and inherited by all methods.
+* New `@SessionAttribute` annotation for access to session attributes (see <<mvc-ann-sessionattrib-global, example>>).
+* New `@RequestAttribute` annotation for access to request attributes (see <<mvc-ann-requestattrib, example>>).
+* `@ModelAttribute` allows preventing data binding via `binding=false` attribute (see <<mvc-ann-modelattrib-method-args, reference>>).
+* Consistent exposure of Errors and custom Throwables to MVC exception handlers.
+* Consistent charset handling in HTTP message converters, including a UTF-8 default for multipart text content.
+* Static resource handling uses the configured `ContentNegotiationManager` for media type determination.
+* `RestTemplate` and `AsyncRestTemplate` support strict URI variable encoding via `DefaultUriTemplateHandler`.
+* `AsyncRestTemplate` supports request interception.
+
+=== WebSocket Messaging Improvements
+
+* `@SendTo` and `@SendToUser` can now be specified at class-level to share a common destination.
+
+=== Testing Improvements
+
+* The JUnit support in the _Spring TestContext Framework_ now requires JUnit 4.12 or higher.
+* New `SpringRunner` _alias_ for the `SpringJUnit4ClassRunner`.
+* Test related annotations may now be declared on interfaces -- for example, for use with
+ _test interfaces_ that make use of Java 8 based interface default methods.
+* An empty declaration of `@ContextConfiguration` can now be completely omitted if default
+ XML files, Groovy scripts, or `@Configuration` classes are detected.
+* `@Transactional` test methods are no longer required to be `public` (e.g., in TestNG and JUnit 5).
+* `@BeforeTransaction` and `@AfterTransaction` methods are no longer required to be `public`
+ and may now be declared on Java 8 based interface default methods.
+* The `ApplicationContext` cache in the _Spring TestContext Framework_ is now bounded with a
+ default maximum size of 32 and a _least recently used_ eviction policy. The maximum size
+ can be configured by setting a JVM system property or Spring property called
+ `spring.test.context.cache.maxSize`.
+* New `ContextCustomizer` API for customizing a test `ApplicationContext` _after_ bean
+ definitions have been loaded into the context but _before_ the context has been refreshed.
+ Customizers can be registered globally by third parties, foregoing the need to implement a
+ custom `ContextLoader`.
+* `@Sql` and `@SqlGroup` may now be used as _meta-annotations_ to create custom _composed
+ annotations_ with attribute overrides.
+* `ReflectionTestUtils` now automatically unwraps proxies when setting or getting a field.
+* Server-side Spring MVC Test supports expectations on response headers with multiple values.
+* Server-side Spring MVC Test parses form data request content and populates request parameters.
+* Server-side Spring MVC Test supports mock-like assertions for invoked handler methods.
+* Client-side REST test support allows indicating how many times a request is expected and
+ whether the order of declaration for expectations should be ignored (see <<spring-mvc-test-client>>).
+* Client-side REST Test supports expectations for form data in the request body.
+
+=== Support for new library and server generations
+
+* Hibernate ORM 5.2 (still supporting 4.2/4.3 and 5.0/5.1 as well, with 3.6 deprecated now)
+* Jackson 2.8 (minimum raised to Jackson 2.6+ as of Spring 4.3)
+* OkHttp 3.x (still supporting OkHttp 2.x side by side)
+* Netty 4.1
+* Undertow 1.4
+* Tomcat 8.5.2 as well as 9.0 M6
+
+Furthermore, Spring Framework 4.3 embeds the updated ASM 5.1 and Objenesis 2.4 in
+`spring-core.jar`.
diff --git a/src/eclipse/org.eclipse.jdt.ui.prefs b/src/eclipse/org.eclipse.jdt.ui.prefs
index 612e03b0..12518b4f 100644
--- a/src/eclipse/org.eclipse.jdt.ui.prefs
+++ b/src/eclipse/org.eclipse.jdt.ui.prefs
@@ -59,4 +59,4 @@ org.eclipse.jdt.ui.importorder=java;javax;org;com;\#;
org.eclipse.jdt.ui.javadoc=true
org.eclipse.jdt.ui.ondemandthreshold=9999
org.eclipse.jdt.ui.staticondemandthreshold=1
-org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates><template autoinsert\="true" context\="gettercomment_context" deleted\="false" description\="Comment for getter method" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.gettercomment" name\="gettercomment">/**\n * @return the ${bare_field_name}\n */</template><template autoinsert\="true" context\="settercomment_context" deleted\="false" description\="Comment for setter method" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.settercomment" name\="settercomment">/**\n * @param ${param} the ${bare_field_name} to set\n */</template><template autoinsert\="true" context\="constructorcomment_context" deleted\="false" description\="Comment for created constructors" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.constructorcomment" name\="constructorcomment">/**\n * ${tags}\n */</template><template autoinsert\="false" context\="filecomment_context" deleted\="false" description\="Comment for created Java files" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.filecomment" name\="filecomment">/*\n * Copyright 2002-${year} the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the "License");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http\://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an "AS IS" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n</template><template autoinsert\="false" context\="typecomment_context" deleted\="false" description\="Comment for created types" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.typecomment" name\="typecomment">/**\n * ${tags}\n * @author ${user}\n * @since 4.2.1\n */</template><template autoinsert\="true" context\="fieldcomment_context" deleted\="false" description\="Comment for fields" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.fieldcomment" name\="fieldcomment">/**\n * \n */</template><template autoinsert\="true" context\="methodcomment_context" deleted\="false" description\="Comment for non-overriding methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.methodcomment" name\="methodcomment">/**\n * ${tags}\n */</template><template autoinsert\="true" context\="overridecomment_context" deleted\="false" description\="Comment for overriding methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.overridecomment" name\="overridecomment">/* (non-Javadoc)\n * ${see_to_overridden}\n */</template><template autoinsert\="true" context\="delegatecomment_context" deleted\="false" description\="Comment for delegate methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.delegatecomment" name\="delegatecomment">/**\n * ${tags}\n * ${see_to_target}\n */</template><template autoinsert\="true" context\="newtype_context" deleted\="false" description\="Newly created files" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.newtype" name\="newtype">${filecomment}\n${package_declaration}\n\n${typecomment}\n${type_declaration}</template><template autoinsert\="true" context\="classbody_context" deleted\="false" description\="Code in new class type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.classbody" name\="classbody">\n</template><template autoinsert\="true" context\="interfacebody_context" deleted\="false" description\="Code in new interface type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.interfacebody" name\="interfacebody">\n</template><template autoinsert\="true" context\="enumbody_context" deleted\="false" description\="Code in new enum type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.enumbody" name\="enumbody">\n</template><template autoinsert\="true" context\="annotationbody_context" deleted\="false" description\="Code in new annotation type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.annotationbody" name\="annotationbody">\n</template><template autoinsert\="true" context\="catchblock_context" deleted\="false" description\="Code in new catch blocks" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.catchblock" name\="catchblock">// ${todo} Auto-generated catch block\n${exception_var}.printStackTrace();</template><template autoinsert\="false" context\="methodbody_context" deleted\="false" description\="Code in created method stubs" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.methodbody" name\="methodbody">// ${todo} Auto-generated method stub\nthrow new UnsupportedOperationException("Auto-generated method stub");</template><template autoinsert\="true" context\="constructorbody_context" deleted\="false" description\="Code in created constructor stubs" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.constructorbody" name\="constructorbody">${body_statement}\n// ${todo} Auto-generated constructor stub</template><template autoinsert\="true" context\="getterbody_context" deleted\="false" description\="Code in created getters" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.getterbody" name\="getterbody">return ${field};</template><template autoinsert\="true" context\="setterbody_context" deleted\="false" description\="Code in created setters" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.setterbody" name\="setterbody">${field} \= ${param};</template></templates>
+org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates><template autoinsert\="true" context\="gettercomment_context" deleted\="false" description\="Comment for getter method" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.gettercomment" name\="gettercomment">/**\n * @return the ${bare_field_name}\n */</template><template autoinsert\="true" context\="settercomment_context" deleted\="false" description\="Comment for setter method" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.settercomment" name\="settercomment">/**\n * @param ${param} the ${bare_field_name} to set\n */</template><template autoinsert\="true" context\="constructorcomment_context" deleted\="false" description\="Comment for created constructors" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.constructorcomment" name\="constructorcomment">/**\n * ${tags}\n */</template><template autoinsert\="false" context\="filecomment_context" deleted\="false" description\="Comment for created Java files" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.filecomment" name\="filecomment">/*\n * Copyright 2002-${year} the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the "License");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http\://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an "AS IS" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n</template><template autoinsert\="false" context\="typecomment_context" deleted\="false" description\="Comment for created types" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.typecomment" name\="typecomment">/**\n * ${tags}\n * @author ${user}\n * @since 4.3\n */</template><template autoinsert\="true" context\="fieldcomment_context" deleted\="false" description\="Comment for fields" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.fieldcomment" name\="fieldcomment">/**\n * \n */</template><template autoinsert\="true" context\="methodcomment_context" deleted\="false" description\="Comment for non-overriding methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.methodcomment" name\="methodcomment">/**\n * ${tags}\n */</template><template autoinsert\="true" context\="overridecomment_context" deleted\="false" description\="Comment for overriding methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.overridecomment" name\="overridecomment">/* (non-Javadoc)\n * ${see_to_overridden}\n */</template><template autoinsert\="true" context\="delegatecomment_context" deleted\="false" description\="Comment for delegate methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.delegatecomment" name\="delegatecomment">/**\n * ${tags}\n * ${see_to_target}\n */</template><template autoinsert\="true" context\="newtype_context" deleted\="false" description\="Newly created files" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.newtype" name\="newtype">${filecomment}\n${package_declaration}\n\n${typecomment}\n${type_declaration}</template><template autoinsert\="true" context\="classbody_context" deleted\="false" description\="Code in new class type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.classbody" name\="classbody">\n</template><template autoinsert\="true" context\="interfacebody_context" deleted\="false" description\="Code in new interface type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.interfacebody" name\="interfacebody">\n</template><template autoinsert\="true" context\="enumbody_context" deleted\="false" description\="Code in new enum type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.enumbody" name\="enumbody">\n</template><template autoinsert\="true" context\="annotationbody_context" deleted\="false" description\="Code in new annotation type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.annotationbody" name\="annotationbody">\n</template><template autoinsert\="true" context\="catchblock_context" deleted\="false" description\="Code in new catch blocks" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.catchblock" name\="catchblock">// ${todo} Auto-generated catch block\n${exception_var}.printStackTrace();</template><template autoinsert\="false" context\="methodbody_context" deleted\="false" description\="Code in created method stubs" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.methodbody" name\="methodbody">// ${todo} Auto-generated method stub\nthrow new UnsupportedOperationException("Auto-generated method stub");</template><template autoinsert\="true" context\="constructorbody_context" deleted\="false" description\="Code in created constructor stubs" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.constructorbody" name\="constructorbody">${body_statement}\n// ${todo} Auto-generated constructor stub</template><template autoinsert\="true" context\="getterbody_context" deleted\="false" description\="Code in created getters" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.getterbody" name\="getterbody">return ${field};</template><template autoinsert\="true" context\="setterbody_context" deleted\="false" description\="Code in created setters" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.setterbody" name\="setterbody">${field} \= ${param};</template></templates>
diff --git a/src/test/java/org/springframework/context/annotation/scope/ClassPathBeanDefinitionScannerScopeIntegrationTests.java b/src/test/java/org/springframework/context/annotation/scope/ClassPathBeanDefinitionScannerScopeIntegrationTests.java
index e0712b69..02cd6cf2 100644
--- a/src/test/java/org/springframework/context/annotation/scope/ClassPathBeanDefinitionScannerScopeIntegrationTests.java
+++ b/src/test/java/org/springframework/context/annotation/scope/ClassPathBeanDefinitionScannerScopeIntegrationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author 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,50 +17,50 @@
package org.springframework.context.annotation.scope;
import org.junit.After;
-import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import org.springframework.aop.support.AopUtils;
-import org.springframework.beans.factory.config.BeanDefinition;
-import org.springframework.beans.factory.support.BeanDefinitionRegistry;
-import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
-import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.stereotype.Component;
+import org.springframework.web.context.annotation.RequestScope;
+import org.springframework.web.context.annotation.SessionScope;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.support.GenericWebApplicationContext;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.springframework.context.annotation.ScopedProxyMode.DEFAULT;
+import static org.springframework.context.annotation.ScopedProxyMode.INTERFACES;
+import static org.springframework.context.annotation.ScopedProxyMode.NO;
+import static org.springframework.context.annotation.ScopedProxyMode.TARGET_CLASS;
+
/**
* @author Mark Fisher
* @author Juergen Hoeller
* @author Chris Beams
+ * @author Sam Brannen
*/
public class ClassPathBeanDefinitionScannerScopeIntegrationTests {
private static final String DEFAULT_NAME = "default";
-
private static final String MODIFIED_NAME = "modified";
- private ServletRequestAttributes oldRequestAttributes;
-
- private ServletRequestAttributes newRequestAttributes;
+ private ServletRequestAttributes oldRequestAttributes = new ServletRequestAttributes(new MockHttpServletRequest());
+ private ServletRequestAttributes newRequestAttributes = new ServletRequestAttributes(new MockHttpServletRequest());
private ServletRequestAttributes oldRequestAttributesWithSession;
-
private ServletRequestAttributes newRequestAttributesWithSession;
@Before
public void setUp() {
- this.oldRequestAttributes = new ServletRequestAttributes(new MockHttpServletRequest());
- this.newRequestAttributes = new ServletRequestAttributes(new MockHttpServletRequest());
-
MockHttpServletRequest oldRequestWithSession = new MockHttpServletRequest();
oldRequestWithSession.setSession(new MockHttpSession());
this.oldRequestAttributesWithSession = new ServletRequestAttributes(oldRequestWithSession);
@@ -72,14 +72,14 @@ public class ClassPathBeanDefinitionScannerScopeIntegrationTests {
@After
public void tearDown() throws Exception {
- RequestContextHolder.setRequestAttributes(null);
+ RequestContextHolder.resetRequestAttributes();
}
@Test
- public void testSingletonScopeWithNoProxy() {
+ public void singletonScopeWithNoProxy() {
RequestContextHolder.setRequestAttributes(oldRequestAttributes);
- ApplicationContext context = createContext(ScopedProxyMode.NO);
+ ApplicationContext context = createContext(NO);
ScopedTestBean bean = (ScopedTestBean) context.getBean("singleton");
// should not be a proxy
@@ -98,9 +98,9 @@ public class ClassPathBeanDefinitionScannerScopeIntegrationTests {
}
@Test
- public void testSingletonScopeIgnoresProxyInterfaces() {
+ public void singletonScopeIgnoresProxyInterfaces() {
RequestContextHolder.setRequestAttributes(oldRequestAttributes);
- ApplicationContext context = createContext(ScopedProxyMode.INTERFACES);
+ ApplicationContext context = createContext(INTERFACES);
ScopedTestBean bean = (ScopedTestBean) context.getBean("singleton");
// should not be a proxy
@@ -119,9 +119,9 @@ public class ClassPathBeanDefinitionScannerScopeIntegrationTests {
}
@Test
- public void testSingletonScopeIgnoresProxyTargetClass() {
+ public void singletonScopeIgnoresProxyTargetClass() {
RequestContextHolder.setRequestAttributes(oldRequestAttributes);
- ApplicationContext context = createContext(ScopedProxyMode.TARGET_CLASS);
+ ApplicationContext context = createContext(TARGET_CLASS);
ScopedTestBean bean = (ScopedTestBean) context.getBean("singleton");
// should not be a proxy
@@ -140,9 +140,9 @@ public class ClassPathBeanDefinitionScannerScopeIntegrationTests {
}
@Test
- public void testRequestScopeWithNoProxy() {
+ public void requestScopeWithNoProxy() {
RequestContextHolder.setRequestAttributes(oldRequestAttributes);
- ApplicationContext context = createContext(ScopedProxyMode.NO);
+ ApplicationContext context = createContext(NO);
ScopedTestBean bean = (ScopedTestBean) context.getBean("request");
// should not be a proxy
@@ -161,9 +161,9 @@ public class ClassPathBeanDefinitionScannerScopeIntegrationTests {
}
@Test
- public void testRequestScopeWithProxiedInterfaces() {
+ public void requestScopeWithProxiedInterfaces() {
RequestContextHolder.setRequestAttributes(oldRequestAttributes);
- ApplicationContext context = createContext(ScopedProxyMode.INTERFACES);
+ ApplicationContext context = createContext(INTERFACES);
IScopedTestBean bean = (IScopedTestBean) context.getBean("request");
// should be dynamic proxy, implementing both interfaces
@@ -182,9 +182,9 @@ public class ClassPathBeanDefinitionScannerScopeIntegrationTests {
}
@Test
- public void testRequestScopeWithProxiedTargetClass() {
+ public void requestScopeWithProxiedTargetClass() {
RequestContextHolder.setRequestAttributes(oldRequestAttributes);
- ApplicationContext context = createContext(ScopedProxyMode.TARGET_CLASS);
+ ApplicationContext context = createContext(TARGET_CLASS);
IScopedTestBean bean = (IScopedTestBean) context.getBean("request");
// should be a class-based proxy
@@ -203,9 +203,9 @@ public class ClassPathBeanDefinitionScannerScopeIntegrationTests {
}
@Test
- public void testSessionScopeWithNoProxy() {
+ public void sessionScopeWithNoProxy() {
RequestContextHolder.setRequestAttributes(oldRequestAttributesWithSession);
- ApplicationContext context = createContext(ScopedProxyMode.NO);
+ ApplicationContext context = createContext(NO);
ScopedTestBean bean = (ScopedTestBean) context.getBean("session");
// should not be a proxy
@@ -224,9 +224,9 @@ public class ClassPathBeanDefinitionScannerScopeIntegrationTests {
}
@Test
- public void testSessionScopeWithProxiedInterfaces() {
+ public void sessionScopeWithProxiedInterfaces() {
RequestContextHolder.setRequestAttributes(oldRequestAttributesWithSession);
- ApplicationContext context = createContext(ScopedProxyMode.INTERFACES);
+ ApplicationContext context = createContext(INTERFACES);
IScopedTestBean bean = (IScopedTestBean) context.getBean("session");
// should be dynamic proxy, implementing both interfaces
@@ -251,9 +251,9 @@ public class ClassPathBeanDefinitionScannerScopeIntegrationTests {
}
@Test
- public void testSessionScopeWithProxiedTargetClass() {
+ public void sessionScopeWithProxiedTargetClass() {
RequestContextHolder.setRequestAttributes(oldRequestAttributesWithSession);
- ApplicationContext context = createContext(ScopedProxyMode.TARGET_CLASS);
+ ApplicationContext context = createContext(TARGET_CLASS);
IScopedTestBean bean = (IScopedTestBean) context.getBean("session");
// should be a class-based proxy
@@ -283,12 +283,7 @@ public class ClassPathBeanDefinitionScannerScopeIntegrationTests {
GenericWebApplicationContext context = new GenericWebApplicationContext();
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
scanner.setIncludeAnnotationConfig(false);
- scanner.setBeanNameGenerator(new BeanNameGenerator() {
- @Override
- public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
- return definition.getScope();
- }
- });
+ scanner.setBeanNameGenerator((definition, registry) -> definition.getScope());
scanner.setScopedProxyMode(scopedProxyMode);
// Scan twice in order to find errors in the bean definition compatibility check.
@@ -300,7 +295,7 @@ public class ClassPathBeanDefinitionScannerScopeIntegrationTests {
}
- public static interface IScopedTestBean {
+ static interface IScopedTestBean {
String getName();
@@ -308,7 +303,7 @@ public class ClassPathBeanDefinitionScannerScopeIntegrationTests {
}
- public static abstract class ScopedTestBean implements IScopedTestBean {
+ static abstract class ScopedTestBean implements IScopedTestBean {
private String name = DEFAULT_NAME;
@@ -321,23 +316,23 @@ public class ClassPathBeanDefinitionScannerScopeIntegrationTests {
@Component
- public static class SingletonScopedTestBean extends ScopedTestBean {
+ static class SingletonScopedTestBean extends ScopedTestBean {
}
- public static interface AnotherScopeTestInterface {
+ static interface AnotherScopeTestInterface {
}
@Component
- @Scope("request")
- public static class RequestScopedTestBean extends ScopedTestBean implements AnotherScopeTestInterface {
+ @RequestScope(proxyMode = DEFAULT)
+ static class RequestScopedTestBean extends ScopedTestBean implements AnotherScopeTestInterface {
}
@Component
- @Scope("session")
- public static class SessionScopedTestBean extends ScopedTestBean implements AnotherScopeTestInterface {
+ @SessionScope(proxyMode = DEFAULT)
+ static class SessionScopedTestBean extends ScopedTestBean implements AnotherScopeTestInterface {
}
}
diff --git a/src/test/java/org/springframework/scheduling/annotation/ScheduledAndTransactionalAnnotationIntegrationTests.java b/src/test/java/org/springframework/scheduling/annotation/ScheduledAndTransactionalAnnotationIntegrationTests.java
index 1a0f250d..16ec6374 100644
--- a/src/test/java/org/springframework/scheduling/annotation/ScheduledAndTransactionalAnnotationIntegrationTests.java
+++ b/src/test/java/org/springframework/scheduling/annotation/ScheduledAndTransactionalAnnotationIntegrationTests.java
@@ -66,7 +66,7 @@ public class ScheduledAndTransactionalAnnotationIntegrationTests {
fail("Should have thrown BeanCreationException");
}
catch (BeanCreationException ex) {
- assertTrue(ex.getRootCause().getMessage().startsWith("@Scheduled method 'scheduled' found"));
+ assertTrue(ex.getRootCause() instanceof IllegalStateException);
}
}
diff --git a/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementIntegrationTests.java b/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementIntegrationTests.java
index 76f9cb95..765fffc2 100644
--- a/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementIntegrationTests.java
+++ b/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementIntegrationTests.java
@@ -68,7 +68,8 @@ public class EnableTransactionManagementIntegrationTests {
try {
assertTxProxying(ctx);
fail("expected exception");
- } catch (AssertionError ex) {
+ }
+ catch (AssertionError ex) {
assertThat(ex.getMessage(), equalTo("FooRepository is not a TX proxy"));
}
}